1
0
mirror of https://github.com/spl0k/supysonic.git synced 2024-12-23 01:16:18 +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 logging.handlers import TimedRotatingFileHandler
from signal import signal, SIGTERM, SIGINT from signal import signal, SIGTERM, SIGINT
from time import sleep
from supysonic.config import IniConfig from supysonic.config import IniConfig
from supysonic.daemon import Daemon
from supysonic.db import init_database, release_database from supysonic.db import init_database, release_database
from supysonic.watcher import SupysonicWatcher
logger = logging.getLogger('supysonic') logger = logging.getLogger('supysonic')
watcher = None daemon = None
def setup_logging(config): def setup_logging(config):
if config['log_file']: if config['log_file']:
@ -39,7 +38,7 @@ def setup_logging(config):
def __terminate(signum, frame): def __terminate(signum, frame):
logger.debug("Got signal %i. Stopping...", signum) logger.debug("Got signal %i. Stopping...", signum)
watcher.stop() daemon.terminate()
release_database() release_database()
if __name__ == "__main__": if __name__ == "__main__":
@ -50,11 +49,6 @@ if __name__ == "__main__":
signal(SIGINT, __terminate) signal(SIGINT, __terminate)
init_database(config.BASE['database_uri']) init_database(config.BASE['database_uri'])
daemon = Daemon(config.DAEMON['socket'])
watcher = SupysonicWatcher(config) daemon.run(config)
watcher.start()
while watcher.running:
sleep(2)
release_database() release_database()

View File

@ -3,7 +3,7 @@
# This file is part of Supysonic. # This file is part of Supysonic.
# Supysonic is a Python implementation of the Subsonic server API. # 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 # 2017 Óscar García Amor
# #
# Distributed under terms of the GNU AGPLv3 license. # Distributed under terms of the GNU AGPLv3 license.
@ -35,6 +35,7 @@ class DefaultConfig(object):
'mount_api': True 'mount_api': True
} }
DAEMON = { DAEMON = {
'socket': os.path.join(tempdir, 'supysonic.sock'),
'wait_delay': 5, 'wait_delay': 5,
'log_file': None, 'log_file': None,
'log_level': 'WARNING' '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. # This file is part of Supysonic.
# Supysonic is a Python implementation of the Subsonic server API. # 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. # Distributed under terms of the GNU AGPLv3 license.
@ -13,6 +13,7 @@ import uuid
from pony.orm import select from pony.orm import select
from pony.orm import ObjectNotFound from pony.orm import ObjectNotFound
from ..daemon import DaemonClient
from ..db import Folder, Track, Artist, Album, User, RatingTrack, StarredTrack from ..db import Folder, Track, Artist, Album, User, RatingTrack, StarredTrack
from ..py23 import strtype from ..py23 import strtype
@ -43,7 +44,13 @@ class FolderManager:
if Folder.exists(lambda f: f.path.startswith(path)): if Folder.exists(lambda f: f.path.startswith(path)):
raise ValueError('This path contains a folder that is already registered') 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 @staticmethod
def delete(uid): def delete(uid):
@ -51,6 +58,11 @@ class FolderManager:
if not folder.root: if not folder.root:
raise ObjectNotFound(Folder) 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): for user in User.select(lambda u: u.last_play.root_folder == folder):
user.last_play = None user.last_play = None
RatingTrack.select(lambda r: r.rated.root_folder == folder).delete(bulk = True) RatingTrack.select(lambda r: r.rated.root_folder == folder).delete(bulk = True)

View File

@ -3,7 +3,7 @@
# This file is part of Supysonic. # This file is part of Supysonic.
# Supysonic is a Python implementation of the Subsonic server API. # 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. # Distributed under terms of the GNU AGPLv3 license.
@ -18,7 +18,7 @@ from watchdog.events import PatternMatchingEventHandler
from . import covers from . import covers
from .db import Folder from .db import Folder
from .py23 import dict from .py23 import dict, strtype
from .scanner import Scanner from .scanner import Scanner
OP_SCAN = 1 OP_SCAN = 1
@ -246,25 +246,35 @@ class SupysonicWatcher(object):
self.__delay = config.DAEMON['wait_delay'] self.__delay = config.DAEMON['wait_delay']
self.__handler = SupysonicWatcherEventHandler(config.BASE['scanner_extensions']) self.__handler = SupysonicWatcherEventHandler(config.BASE['scanner_extensions'])
self.__folders = {}
self.__queue = None self.__queue = None
self.__observer = None self.__observer = None
def start(self): def add_folder(self, folder):
with db_session: if isinstance(folder, Folder):
folders = Folder.select(lambda f: f.root) path = folder.path
shouldrun = folders.exists() elif isinstance(folder, strtype):
if not shouldrun: path = folder
logger.info("No folder set.") else:
return 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.__queue = ScannerProcessingQueue(self.__delay)
self.__observer = Observer() self.__observer = Observer()
self.__handler.queue = self.__queue self.__handler.queue = self.__queue
with db_session: with db_session:
for folder in folders: for folder in Folder.select(lambda f: f.root):
logger.info("Scheduling watcher for %s", folder.path) self.add_folder(folder)
self.__observer.schedule(self.__handler, folder.path, recursive = True)
logger.info("Starting watcher") logger.info("Starting watcher")
self.__queue.start() self.__queue.start()