mirror of
https://github.com/spl0k/supysonic.git
synced 2024-12-22 17:06:17 +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 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()
|
||||||
|
@ -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
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.
|
# 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)
|
||||||
|
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user