1
0
mirror of https://github.com/spl0k/supysonic.git synced 2024-12-22 17:06:17 +00:00

Renamed supysonic-watcher to supysonic-daemon

Since it no longer only watches files
Also changed the way it's installed
This commit is contained in:
spl0k 2019-06-10 16:16:24 +02:00
parent 8d9b2658d2
commit e29ab91a97
5 changed files with 164 additions and 125 deletions

View File

@ -8,47 +8,11 @@
# #
# Distributed under terms of the GNU AGPLv3 license. # Distributed under terms of the GNU AGPLv3 license.
import logging import warnings
from supysonic.daemon import main
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()
if __name__ == "__main__": if __name__ == "__main__":
config = IniConfig.from_common_locations() warnings.warn(
setup_logging(config.DAEMON) "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)
signal(SIGTERM, __terminate) main()
signal(SIGINT, __terminate)
init_database(config.BASE['database_uri'])
daemon = Daemon(config)
daemon.run()
release_database()

View File

@ -1,6 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # coding: utf-8
# vim:fenc=utf-8
# #
# 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.
@ -38,7 +37,8 @@ setup(
license=project.LICENSE, license=project.LICENSE,
packages=find_packages(exclude=['tests*']), packages=find_packages(exclude=['tests*']),
install_requires = reqs, 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, zip_safe=False,
include_package_data=True, include_package_data=True,
test_suite='tests.suite', test_suite='tests.suite',

View File

@ -1,102 +1,61 @@
# coding: utf-8 # coding: utf-8
#
# 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) 2019 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.
import logging import logging
import time
from multiprocessing.connection import Listener, Client from logging.handlers import TimedRotatingFileHandler
from pony.orm import db_session, select from signal import signal, SIGTERM, SIGINT
from threading import Thread, Event
from .client import DaemonCommand from .client import DaemonClient
from ..db import Folder from .server import Daemon
from ..scanner import Scanner
from ..utils import get_secret_key
from ..watcher import SupysonicWatcher
__all__ = [ 'Daemon' ] from ..config import IniConfig
from ..db import init_database, release_database
logger = logging.getLogger(__name__) __all__ = [ 'Daemon', 'DaemonClient' ]
class Daemon(object): logger = logging.getLogger("supysonic")
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) daemon = None
scanner = property(lambda self: self.__scanner)
def __handle_connection(self, connection): def setup_logging(config):
cmd = connection.recv() if config['log_file']:
logger.debug('Received %s', cmd) if config['log_file'] == '/dev/null':
if cmd is None: log_handler = logging.NullHandler()
pass
elif isinstance(cmd, DaemonCommand):
cmd.apply(connection, self)
else: 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): def __terminate(signum, frame):
self.__listener = Listener(address = self.__config.DAEMON['socket'], authkey = get_secret_key('daemon_key')) global daemon
logger.info("Listening to %s", self.__listener.address)
if self.__config.DAEMON['run_watcher']: logger.debug("Got signal %i. Stopping...", signum)
self.__watcher = SupysonicWatcher(self.__config) daemon.terminate()
self.__watcher.start() release_database()
Thread(target=self.__listen).start() def main():
while not self.__stopped.is_set(): global daemon
time.sleep(1)
def __listen(self): config = IniConfig.from_common_locations()
while not self.__stopped.is_set(): setup_logging(config.DAEMON)
conn = self.__listener.accept()
self.__handle_connection(conn)
def start_scan(self, folders = [], force = False): signal(SIGTERM, __terminate)
if not folders: signal(SIGINT, __terminate)
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(): init_database(config.BASE['database_uri'])
for f in folders: daemon = Daemon(config)
self.__scanner.queue_folder(f) daemon.run()
return release_database()
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()

14
supysonic/daemon/__main__.py Executable file
View File

@ -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()

102
supysonic/daemon/server.py Normal file
View File

@ -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()