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:
parent
970ee6ee3c
commit
751f00dac8
@ -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()
|
||||
|
@ -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
78
supysonic/daemon.py
Normal 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()
|
@ -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)
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user