diff --git a/bin/supysonic-watcher b/bin/supysonic-watcher index 9ee3442..ff5b669 100755 --- a/bin/supysonic-watcher +++ b/bin/supysonic-watcher @@ -8,47 +8,11 @@ # # Distributed under terms of the GNU AGPLv3 license. -import logging - -from logging.handlers import TimedRotatingFileHandler -from signal import signal, SIGTERM, SIGINT - -from supysonic.config import IniConfig -from supysonic.daemon import Daemon -from supysonic.db import init_database, release_database - -logger = logging.getLogger('supysonic') - -daemon = None - -def setup_logging(config): - if config['log_file']: - if config['log_file'] == '/dev/null': - log_handler = logging.NullHandler() - else: - log_handler = TimedRotatingFileHandler(config['log_file'], when = 'midnight') - log_handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")) - else: - log_handler = logging.StreamHandler() - log_handler.setFormatter(logging.Formatter("[%(levelname)s] %(message)s")) - logger.addHandler(log_handler) - if 'log_level' in config: - level = getattr(logging, config['log_level'].upper(), logging.NOTSET) - logger.setLevel(level) - -def __terminate(signum, frame): - logger.debug("Got signal %i. Stopping...", signum) - daemon.terminate() - release_database() +import warnings +from supysonic.daemon import main if __name__ == "__main__": - config = IniConfig.from_common_locations() - setup_logging(config.DAEMON) - - signal(SIGTERM, __terminate) - signal(SIGINT, __terminate) - - init_database(config.BASE['database_uri']) - daemon = Daemon(config) - daemon.run() - release_database() + warnings.warn( + "You're using an old version of the `supysonic-watcher` script.\nNo worries though, it will still work (for some time), but you should call `supysonic-daemon` instead.", + DeprecationWarning) + main() diff --git a/setup.py b/setup.py index 6635309..2c8bf92 100755 --- a/setup.py +++ b/setup.py @@ -1,6 +1,5 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- -# vim:fenc=utf-8 +# coding: utf-8 # # This file is part of Supysonic. # Supysonic is a Python implementation of the Subsonic server API. @@ -38,7 +37,8 @@ setup( license=project.LICENSE, packages=find_packages(exclude=['tests*']), install_requires = reqs, - scripts=['bin/supysonic-cli', 'bin/supysonic-watcher'], + scripts=['bin/supysonic-cli'], + entry_points={ 'console_scripts': ['supysonic-daemon=supysonic.daemon:main'] }, zip_safe=False, include_package_data=True, test_suite='tests.suite', diff --git a/supysonic/daemon/__init__.py b/supysonic/daemon/__init__.py index afae6eb..18591f6 100644 --- a/supysonic/daemon/__init__.py +++ b/supysonic/daemon/__init__.py @@ -1,102 +1,61 @@ # 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 +# Copyright (C) 2014-2019 Alban 'spl0k' Féron # # Distributed under terms of the GNU AGPLv3 license. import logging -import time -from multiprocessing.connection import Listener, Client -from pony.orm import db_session, select -from threading import Thread, Event +from logging.handlers import TimedRotatingFileHandler +from signal import signal, SIGTERM, SIGINT -from .client import DaemonCommand -from ..db import Folder -from ..scanner import Scanner -from ..utils import get_secret_key -from ..watcher import SupysonicWatcher +from .client import DaemonClient +from .server import Daemon -__all__ = [ 'Daemon' ] +from ..config import IniConfig +from ..db import init_database, release_database -logger = logging.getLogger(__name__) +__all__ = [ 'Daemon', 'DaemonClient' ] -class Daemon(object): - def __init__(self, config): - self.__config = config - self.__listener = None - self.__watcher = None - self.__scanner = None - self.__stopped = Event() +logger = logging.getLogger("supysonic") - watcher = property(lambda self: self.__watcher) - scanner = property(lambda self: self.__scanner) +daemon = None - def __handle_connection(self, connection): - cmd = connection.recv() - logger.debug('Received %s', cmd) - if cmd is None: - pass - elif isinstance(cmd, DaemonCommand): - cmd.apply(connection, self) +def setup_logging(config): + if config['log_file']: + if config['log_file'] == '/dev/null': + log_handler = logging.NullHandler() else: - logger.warn('Received unknown command %s', cmd) + log_handler = TimedRotatingFileHandler(config['log_file'], when = 'midnight') + log_handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")) + else: + log_handler = logging.StreamHandler() + log_handler.setFormatter(logging.Formatter("[%(levelname)s] %(message)s")) + logger.addHandler(log_handler) + if 'log_level' in config: + level = getattr(logging, config['log_level'].upper(), logging.NOTSET) + logger.setLevel(level) - def run(self): - self.__listener = Listener(address = self.__config.DAEMON['socket'], authkey = get_secret_key('daemon_key')) - logger.info("Listening to %s", self.__listener.address) +def __terminate(signum, frame): + global daemon - if self.__config.DAEMON['run_watcher']: - self.__watcher = SupysonicWatcher(self.__config) - self.__watcher.start() + logger.debug("Got signal %i. Stopping...", signum) + daemon.terminate() + release_database() - Thread(target=self.__listen).start() - while not self.__stopped.is_set(): - time.sleep(1) +def main(): + global daemon - def __listen(self): - while not self.__stopped.is_set(): - conn = self.__listener.accept() - self.__handle_connection(conn) + config = IniConfig.from_common_locations() + setup_logging(config.DAEMON) - def start_scan(self, folders = [], force = False): - if not folders: - with db_session: - folders = select(f.name for f in Folder if f.root)[:] + signal(SIGTERM, __terminate) + signal(SIGINT, __terminate) - if self.__scanner is not None and self.__scanner.is_alive(): - for f in folders: - self.__scanner.queue_folder(f) - return - - extensions = self.__config.BASE['scanner_extensions'] - if extensions: - extensions = extensions.split(' ') - - self.__scanner = Scanner(force = force, extensions = extensions, on_folder_start = self.__unwatch, on_folder_end = self.__watch) - for f in folders: - self.__scanner.queue_folder(f) - - self.__scanner.start() - - def __watch(self, folder): - if self.__watcher is not None: - self.__watcher.add_folder(folder.path) - - def __unwatch(self, folder): - if self.__watcher is not None: - self.__watcher.remove_folder(folder.path) - - def terminate(self): - self.__stopped.set() - with Client(self.__listener.address, authkey = self.__listener._authkey) as c: - c.send(None) - - if self.__scanner is not None: - self.__scanner.stop() - self.__scanner.join() - if self.__watcher is not None: - self.__watcher.stop() + init_database(config.BASE['database_uri']) + daemon = Daemon(config) + daemon.run() + release_database() diff --git a/supysonic/daemon/__main__.py b/supysonic/daemon/__main__.py new file mode 100755 index 0000000..fd3fc9b --- /dev/null +++ b/supysonic/daemon/__main__.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +# 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. + +from . import main + +if __name__ == "__main__": + main() diff --git a/supysonic/daemon/server.py b/supysonic/daemon/server.py new file mode 100644 index 0000000..afae6eb --- /dev/null +++ b/supysonic/daemon/server.py @@ -0,0 +1,102 @@ +# 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 +import time + +from multiprocessing.connection import Listener, Client +from pony.orm import db_session, select +from threading import Thread, Event + +from .client import DaemonCommand +from ..db import Folder +from ..scanner import Scanner +from ..utils import get_secret_key +from ..watcher import SupysonicWatcher + +__all__ = [ 'Daemon' ] + +logger = logging.getLogger(__name__) + +class Daemon(object): + def __init__(self, config): + self.__config = config + self.__listener = None + self.__watcher = None + self.__scanner = None + self.__stopped = Event() + + watcher = property(lambda self: self.__watcher) + scanner = property(lambda self: self.__scanner) + + def __handle_connection(self, connection): + cmd = connection.recv() + logger.debug('Received %s', cmd) + if cmd is None: + pass + elif isinstance(cmd, DaemonCommand): + cmd.apply(connection, self) + else: + logger.warn('Received unknown command %s', cmd) + + def run(self): + self.__listener = Listener(address = self.__config.DAEMON['socket'], authkey = get_secret_key('daemon_key')) + logger.info("Listening to %s", self.__listener.address) + + if self.__config.DAEMON['run_watcher']: + self.__watcher = SupysonicWatcher(self.__config) + self.__watcher.start() + + Thread(target=self.__listen).start() + while not self.__stopped.is_set(): + time.sleep(1) + + def __listen(self): + while not self.__stopped.is_set(): + conn = self.__listener.accept() + self.__handle_connection(conn) + + def start_scan(self, folders = [], force = False): + if not folders: + with db_session: + folders = select(f.name for f in Folder if f.root)[:] + + if self.__scanner is not None and self.__scanner.is_alive(): + for f in folders: + self.__scanner.queue_folder(f) + return + + extensions = self.__config.BASE['scanner_extensions'] + if extensions: + extensions = extensions.split(' ') + + self.__scanner = Scanner(force = force, extensions = extensions, on_folder_start = self.__unwatch, on_folder_end = self.__watch) + for f in folders: + self.__scanner.queue_folder(f) + + self.__scanner.start() + + def __watch(self, folder): + if self.__watcher is not None: + self.__watcher.add_folder(folder.path) + + def __unwatch(self, folder): + if self.__watcher is not None: + self.__watcher.remove_folder(folder.path) + + def terminate(self): + self.__stopped.set() + with Client(self.__listener.address, authkey = self.__listener._authkey) as c: + c.send(None) + + if self.__scanner is not None: + self.__scanner.stop() + self.__scanner.join() + if self.__watcher is not None: + self.__watcher.stop()