1
0
mirror of https://github.com/spl0k/supysonic.git synced 2024-11-09 19:52:16 +00:00

Daemon now listen for remot commands, adding/removing watched folders on the fly

#77 part 5
This commit is contained in:
spl0k 2019-04-08 19:02:37 +02:00
parent 970ee6ee3c
commit 751f00dac8
5 changed files with 121 additions and 26 deletions

View File

@ -12,15 +12,14 @@ import logging
from logging.handlers import TimedRotatingFileHandler
from signal import signal, SIGTERM, SIGINT
from time import sleep
from supysonic.config import IniConfig
from supysonic.daemon import Daemon
from supysonic.db import init_database, release_database
from supysonic.watcher import SupysonicWatcher
logger = logging.getLogger('supysonic')
watcher = None
daemon = None
def setup_logging(config):
if config['log_file']:
@ -39,7 +38,7 @@ def setup_logging(config):
def __terminate(signum, frame):
logger.debug("Got signal %i. Stopping...", signum)
watcher.stop()
daemon.terminate()
release_database()
if __name__ == "__main__":
@ -50,11 +49,6 @@ if __name__ == "__main__":
signal(SIGINT, __terminate)
init_database(config.BASE['database_uri'])
watcher = SupysonicWatcher(config)
watcher.start()
while watcher.running:
sleep(2)
daemon = Daemon(config.DAEMON['socket'])
daemon.run(config)
release_database()

View File

@ -3,7 +3,7 @@
# This file is part of Supysonic.
# Supysonic is a Python implementation of the Subsonic server API.
#
# Copyright (C) 2013-2018 Alban 'spl0k' Féron
# Copyright (C) 2013-2019 Alban 'spl0k' Féron
# 2017 Óscar García Amor
#
# Distributed under terms of the GNU AGPLv3 license.
@ -35,6 +35,7 @@ class DefaultConfig(object):
'mount_api': True
}
DAEMON = {
'socket': os.path.join(tempdir, 'supysonic.sock'),
'wait_delay': 5,
'log_file': None,
'log_level': 'WARNING'

78
supysonic/daemon.py Normal file
View File

@ -0,0 +1,78 @@
# coding: utf-8
#
# This file is part of Supysonic.
# Supysonic is a Python implementation of the Subsonic server API.
#
# Copyright (C) 2019 Alban 'spl0k' Féron
#
# Distributed under terms of the GNU AGPLv3 license.
import logging
from multiprocessing.connection import Client, Listener
from .config import IniConfig
from .py23 import strtype
from .utils import get_secret_key
from .watcher import SupysonicWatcher
__all__ = [ 'Daemon', 'DaemonClient' ]
logger = logging.getLogger(__name__)
WATCHER = 0
W_ADD = 0
W_DEL = 1
class DaemonClient(object):
def __init__(self, address = None):
self.__address = address or IniConfig.from_common_locations().DAEMON['socket']
self.__key = get_secret_key('daemon_key')
def __get_connection(self):
return Client(address = self.__address, authkey = self.__key)
def add_watched_folder(self, folder):
if not isinstance(folder, strtype):
raise TypeError('Expecting string, got ' + str(type(folder)))
with self.__get_connection() as c:
c.send((WATCHER, W_ADD, folder))
def remove_watched_folder(self, folder):
if not isinstance(folder, strtype):
raise TypeError('Expecting string, got ' + str(type(folder)))
with self.__get_connection() as c:
c.send((WATCHER, W_DEL, folder))
class Daemon(object):
def __init__(self, address):
self.__address = address
self.__listener = None
self.__watcher = None
def __handle_connection(self, connection):
try:
module, cmd, *args = connection.recv()
if module == WATCHER:
if cmd == W_ADD:
self.__watcher.add_folder(*args)
elif cmd == W_DEL:
self.__watcher.remove_folder(*args)
except ValueError:
logger.warn('Received unknown data')
def run(self, config):
self.__listener = Listener(address = self.__address, authkey = get_secret_key('daemon_key'))
logger.info("Listening to %s", self.__listener.address)
self.__watcher = SupysonicWatcher(config)
self.__watcher.start()
while True:
conn = self.__listener.accept()
self.__handle_connection(conn)
def terminate(self):
self.__listener.close()
self.__watcher.stop()

View File

@ -3,7 +3,7 @@
# This file is part of Supysonic.
# Supysonic is a Python implementation of the Subsonic server API.
#
# Copyright (C) 2013-2018 Alban 'spl0k' Féron
# Copyright (C) 2013-2019 Alban 'spl0k' Féron
#
# Distributed under terms of the GNU AGPLv3 license.
@ -13,6 +13,7 @@ import uuid
from pony.orm import select
from pony.orm import ObjectNotFound
from ..daemon import DaemonClient
from ..db import Folder, Track, Artist, Album, User, RatingTrack, StarredTrack
from ..py23 import strtype
@ -43,7 +44,13 @@ class FolderManager:
if Folder.exists(lambda f: f.path.startswith(path)):
raise ValueError('This path contains a folder that is already registered')
return Folder(root = True, name = name, path = path)
folder = Folder(root = True, name = name, path = path)
try:
DaemonClient().add_watched_folder(path)
except (ConnectionRefusedError, FileNotFoundError):
pass
return folder
@staticmethod
def delete(uid):
@ -51,6 +58,11 @@ class FolderManager:
if not folder.root:
raise ObjectNotFound(Folder)
try:
DaemonClient().remove_watched_folder(folder.path)
except (ConnectionRefusedError, FileNotFoundError):
pass
for user in User.select(lambda u: u.last_play.root_folder == folder):
user.last_play = None
RatingTrack.select(lambda r: r.rated.root_folder == folder).delete(bulk = True)

View File

@ -3,7 +3,7 @@
# This file is part of Supysonic.
# Supysonic is a Python implementation of the Subsonic server API.
#
# Copyright (C) 2014-2018 Alban 'spl0k' Féron
# Copyright (C) 2014-2019 Alban 'spl0k' Féron
#
# Distributed under terms of the GNU AGPLv3 license.
@ -18,7 +18,7 @@ from watchdog.events import PatternMatchingEventHandler
from . import covers
from .db import Folder
from .py23 import dict
from .py23 import dict, strtype
from .scanner import Scanner
OP_SCAN = 1
@ -246,25 +246,35 @@ class SupysonicWatcher(object):
self.__delay = config.DAEMON['wait_delay']
self.__handler = SupysonicWatcherEventHandler(config.BASE['scanner_extensions'])
self.__folders = {}
self.__queue = None
self.__observer = None
def start(self):
with db_session:
folders = Folder.select(lambda f: f.root)
shouldrun = folders.exists()
if not shouldrun:
logger.info("No folder set.")
return
def add_folder(self, folder):
if isinstance(folder, Folder):
path = folder.path
elif isinstance(folder, strtype):
path = folder
else:
raise TypeError('Expecting string or Folder, got ' + str(type(folder)))
logger.info("Scheduling watcher for %s", path)
watch = self.__observer.schedule(self.__handler, path, recursive = True)
self.__folders[path] = watch
def remove_folder(self, path):
logger.info("Unscheduling watcher for %s", path)
self.__observer.unschedule(self.__folders[path])
del self.__folders[path]
def start(self):
self.__queue = ScannerProcessingQueue(self.__delay)
self.__observer = Observer()
self.__handler.queue = self.__queue
with db_session:
for folder in folders:
logger.info("Scheduling watcher for %s", folder.path)
self.__observer.schedule(self.__handler, folder.path, recursive = True)
for folder in Folder.select(lambda f: f.root):
self.add_folder(folder)
logger.info("Starting watcher")
self.__queue.start()