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:
parent
8d9b2658d2
commit
e29ab91a97
@ -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()
|
|
||||||
|
6
setup.py
6
setup.py
@ -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',
|
||||||
|
@ -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
14
supysonic/daemon/__main__.py
Executable 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
102
supysonic/daemon/server.py
Normal 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()
|
Loading…
Reference in New Issue
Block a user