class Daemon(object): """The Deluge Daemon class""" def __init__(self, listen_interface=None, interface=None, port=None, standalone=False, read_only_config_keys=None): """ Args: listen_interface (str, optional): The IP address to listen to bittorrent connections on. interface (str, optional): The IP address the daemon will listen for UI connections on. port (int, optional): The port the daemon will listen for UI connections on. standalone (bool, optional): If True the client is in Standalone mode otherwise, if False, start the daemon as separate process. read_only_config_keys (list of str, optional): A list of config keys that will not be altered by core.set_config() RPC method. """ self.standalone = standalone self.pid_file = get_config_dir('deluged.pid') log.info('Deluge daemon %s', get_version()) if is_daemon_running(self.pid_file): raise DaemonRunningError( 'Deluge daemon already running with this config directory!') # Twisted catches signals to terminate, so just have it call the shutdown method. reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown) # Catch some Windows specific signals if windows_check(): def win_handler(ctrl_type): """Handle the Windows shutdown or close events.""" log.debug('windows handler ctrl_type: %s', ctrl_type) if ctrl_type == CTRL_CLOSE_EVENT or ctrl_type == CTRL_SHUTDOWN_EVENT: self._shutdown() return 1 SetConsoleCtrlHandler(win_handler) # Start the core as a thread and join it until it's done self.core = Core(listen_interface=listen_interface, read_only_config_keys=read_only_config_keys) if port is None: port = self.core.config['daemon_port'] self.port = port if interface and not is_ip(interface): log.error('Invalid UI interface (must be IP Address): %s', interface) interface = None self.rpcserver = RPCServer( port=port, allow_remote=self.core.config['allow_remote'], listen=not standalone, interface=interface) log.debug('Listening to UI on: %s:%s and bittorrent on: %s', interface, port, listen_interface) def start(self): # Register the daemon and the core RPCs self.rpcserver.register_object(self.core) self.rpcserver.register_object(self) # Make sure we start the PreferencesManager first component.start('PreferencesManager') if not self.standalone: log.info('Deluge daemon starting...') # Create pid file to track if deluged is running, also includes the port number. pid = os.getpid() log.debug('Storing pid %s & port %s in: %s', pid, self.port, self.pid_file) with open(self.pid_file, 'w') as _file: _file.write('%s;%s\n' % (pid, self.port)) component.start() try: reactor.run() finally: log.debug('Remove pid file: %s', self.pid_file) os.remove(self.pid_file) log.info('Deluge daemon shutdown successfully') @export() def shutdown(self, *args, **kwargs): log.debug('Deluge daemon shutdown requested...') reactor.callLater(0, reactor.stop) def _shutdown(self, *args, **kwargs): log.info( 'Deluge daemon shutting down, waiting for components to shutdown...' ) if not self.standalone: return component.shutdown() @export() def get_method_list(self): """Returns a list of the exported methods.""" return self.rpcserver.get_method_list() @export(1) def authorized_call(self, rpc): """Determines if session auth_level is authorized to call RPC. Args: rpc (str): A RPC, e.g. core.get_torrents_status Returns: bool: True if authorized to call RPC, otherwise False. """ if rpc not in self.get_method_list(): return False return self.rpcserver.get_session_auth_level( ) >= self.rpcserver.get_rpc_auth_level(rpc)
class Daemon(object): def __init__(self, options=None, args=None, classic=False): # Check for another running instance of the daemon if os.path.isfile(deluge.configmanager.get_config_dir("deluged.pid")): # Get the PID and the port of the supposedly running daemon try: (pid, port) = open( deluge.configmanager.get_config_dir("deluged.pid") ).read().strip().split(";") pid = int(pid) port = int(port) except ValueError: pid = None port = None def process_running(pid): if deluge.common.windows_check(): # Do some fancy WMI junk to see if the PID exists in Windows from win32com.client import GetObject def get_proclist(): WMI = GetObject('winmgmts:') processes = WMI.InstancesOf('Win32_Process') return [process.Properties_('ProcessID').Value for process in processes] return pid in get_proclist() else: # We can just use os.kill on UNIX to test if the process is running try: os.kill(pid, 0) except OSError: return False else: return True if pid is not None and process_running(pid): # Ok, so a process is running with this PID, let's make doubly-sure # it's a deluged process by trying to open a socket to it's port. import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.connect(("127.0.0.1", port)) except socket.error: # Can't connect, so it must not be a deluged process.. pass else: # This is a deluged! s.close() raise deluge.error.DaemonRunningError( "There is a deluge daemon running with this config " "directory!" ) # Twisted catches signals to terminate, so just have it call the shutdown # method. reactor.addSystemEventTrigger("before", "shutdown", self._shutdown) # Catch some Windows specific signals if deluge.common.windows_check(): from win32api import SetConsoleCtrlHandler from win32con import CTRL_CLOSE_EVENT from win32con import CTRL_SHUTDOWN_EVENT def win_handler(ctrl_type): log.debug("ctrl_type: %s", ctrl_type) if ctrl_type == CTRL_CLOSE_EVENT or ctrl_type == CTRL_SHUTDOWN_EVENT: self._shutdown() return 1 SetConsoleCtrlHandler(win_handler) version = deluge.common.get_version() log.info("Deluge daemon %s", version) log.debug("options: %s", options) log.debug("args: %s", args) # Set the config directory if options and options.config: deluge.configmanager.set_config_dir(options.config) if options and options.listen_interface: listen_interface = options.listen_interface else: listen_interface = "" from deluge.core.core import Core # Start the core as a thread and join it until it's done self.core = Core(listen_interface=listen_interface) port = self.core.config["daemon_port"] if options and options.port: port = options.port if options and options.ui_interface: interface = options.ui_interface else: interface = "" self.rpcserver = RPCServer( port=port, allow_remote=self.core.config["allow_remote"], listen=not classic, interface=interface ) # Register the daemon and the core RPCs self.rpcserver.register_object(self.core) self.rpcserver.register_object(self) # Make sure we start the PreferencesManager first component.start("PreferencesManager") if not classic: # Write out a pid file all the time, we use this to see if a deluged is running # We also include the running port number to do an additional test open(deluge.configmanager.get_config_dir("deluged.pid"), "wb").write( "%s;%s\n" % (os.getpid(), port)) component.start() try: reactor.run() finally: self._shutdown() @export() def shutdown(self, *args, **kwargs): reactor.callLater(0, reactor.stop) def _shutdown(self, *args, **kwargs): if os.path.exists(deluge.configmanager.get_config_dir("deluged.pid")): try: os.remove(deluge.configmanager.get_config_dir("deluged.pid")) except Exception, e: log.exception(e) log.error("Error removing deluged.pid!") log.info("Waiting for components to shutdown..") d = component.shutdown() return d
class Daemon(object): def __init__(self, options=None, args=None, classic=False): # Check for another running instance of the daemon if os.path.isfile(deluge.configmanager.get_config_dir("deluged.pid")): # Get the PID and the port of the supposedly running daemon try: (pid, port) = open( deluge.configmanager.get_config_dir( "deluged.pid")).read().strip().split(";") pid = int(pid) port = int(port) except ValueError: pid = None port = None def process_running(pid): if deluge.common.windows_check(): # Do some fancy WMI junk to see if the PID exists in Windows from win32com.client import GetObject def get_proclist(): WMI = GetObject('winmgmts:') processes = WMI.InstancesOf('Win32_Process') return [ process.Properties_('ProcessID').Value for process in processes ] return pid in get_proclist() else: # We can just use os.kill on UNIX to test if the process is running try: os.kill(pid, 0) except OSError: return False else: return True if pid is not None and process_running(pid): # Ok, so a process is running with this PID, let's make doubly-sure # it's a deluged process by trying to open a socket to it's port. import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.connect(("127.0.0.1", port)) except socket.error: # Can't connect, so it must not be a deluged process.. pass else: # This is a deluged! s.close() raise deluge.error.DaemonRunningError( "There is a deluge daemon running with this config " "directory!") # Initialize gettext deluge.common.setup_translations() # Twisted catches signals to terminate, so just have it call the shutdown # method. reactor.addSystemEventTrigger("after", "shutdown", self.shutdown) # Catch some Windows specific signals if deluge.common.windows_check(): from win32api import SetConsoleCtrlHandler from win32con import CTRL_CLOSE_EVENT from win32con import CTRL_SHUTDOWN_EVENT def win_handler(ctrl_type): log.debug("ctrl_type: %s", ctrl_type) if ctrl_type == CTRL_CLOSE_EVENT or ctrl_type == CTRL_SHUTDOWN_EVENT: self.__shutdown() return 1 SetConsoleCtrlHandler(win_handler) version = deluge.common.get_version() log.info("Deluge daemon %s", version) log.debug("options: %s", options) log.debug("args: %s", args) # Set the config directory if options and options.config: deluge.configmanager.set_config_dir(options.config) from deluge.core.core import Core # Start the core as a thread and join it until it's done self.core = Core() port = self.core.config["daemon_port"] if options and options.port: port = options.port if options and options.ui_interface: interface = options.ui_interface else: interface = "" self.rpcserver = RPCServer( port=port, allow_remote=self.core.config["allow_remote"], listen=not classic, interface=interface) # Register the daemon and the core RPCs self.rpcserver.register_object(self.core) self.rpcserver.register_object(self) # Make sure we start the PreferencesManager first component.start("PreferencesManager") if not classic: # Write out a pid file all the time, we use this to see if a deluged is running # We also include the running port number to do an additional test open(deluge.configmanager.get_config_dir("deluged.pid"), "wb").write("%s;%s\n" % (os.getpid(), port)) component.start() try: reactor.run() finally: self._shutdown() @export() def shutdown(self, *args, **kwargs): reactor.callLater(0, reactor.stop) def _shutdown(self, *args, **kwargs): try: os.remove(deluge.configmanager.get_config_dir("deluged.pid")) except Exception, e: log.exception(e) log.error("Error removing deluged.pid!") component.shutdown() try: reactor.stop() except twisted.internet.error.ReactorNotRunning: log.debug("Tried to stop the reactor but it is not running..")
class RPCServerTestCase(BaseTestCase, TestCaseDebug): """ This class tests that the exported RPC functions in core work throught the Deluge RPC protocol which requires all transmitted data to be serializable with rencode. """ def set_up(self): self.set_unittest_maxdiff(None) self.rpcserver = RPCServer(listen=False) self.rpcserver.factory.protocol = DelugeRPCProtocolTester self.factory = self.rpcserver.factory self.session_id = '0' self.request_id = 11 self.protocol = self.rpcserver.factory.protocol(self.session_id) self.protocol.factory = self.factory self.factory.session_protocols[self.session_id] = self.protocol self.factory.interested_events[self.session_id] = [ 'TorrentFolderRenamedEvent' ] self.protocol.sessionno = self.session_id self.factory.authorized_sessions[ self.session_id] = self.protocol.AuthLevel( rpcserver.AUTH_LEVEL_DEFAULT, '') self.config = test_common.get_test_config() self.core = Core("test") # Must call enable to create the RSSFeedScheduler in core self.core.enable(config=self.config) self.rpcserver.register_object(self.core) return component.start() def tear_down(self): def on_shutdown(result): del self.rpcserver return component.shutdown().addCallback(on_shutdown) def test_core_get_completion_paths(self): tmp_paths_dir = TempDir(prefix='yarss2_unit_tests') test_dirs = [] for i in range(3): dirpath = tmp_paths_dir.mkdirs("dir%d" % (i)) test_dirs.append("%s/" % dirpath) method = "core.get_completion_paths" args = [] completion_text = "%s/d" % tmp_paths_dir.path arg = {"completion_text": completion_text, "show_hidden_files": False} args.append(arg) self.protocol.dispatch(self.request_id, method, args, {}) msg_bytes = self.protocol.transport.messages_written[0] self.protocol.transport.dataReceived(msg_bytes) msg_received = self.protocol.transport.messages_received[0] self.assertEqual(msg_received[0], rpcserver.RPC_RESPONSE, str(msg_received)) self.assertEqual(msg_received[1], self.request_id, str(msg_received)) expected_result = dict(arg) expected_result.update({ 'paths': tuple(test_dirs), }) self.assertEqual(expected_result, msg_received[2]) def test_core_get_rssfeed_parsed(self): method = "core.get_rssfeed_parsed" args = [] filename = "ezrss-rss-2.xml" file_url = yarss2.util.common.get_resource(filename, path="tests/data/feeds") rssfeed_data = { 'active': True, 'key': '3', 'last_update': '2019-10-22T23:28:21+00:00', 'name': 'hd-torrents.org', 'obey_ttl': False, 'prefer_magnet': False, 'site': 'hd-torrents.org', 'update_interval': 5, 'update_on_startup': False, 'url': file_url, 'user_agent': '' } args.append(rssfeed_data) # Makes a call to core.get_rssfeed_parsed self.protocol.dispatch(self.request_id, method, args, {}) msg_bytes = self.protocol.transport.messages_written[0] self.protocol.transport.dataReceived(msg_bytes) msg_received = self.protocol.transport.messages_received[0] self.assertEqual(msg_received[0], rpcserver.RPC_RESPONSE, str(msg_received)) self.assertEqual(msg_received[1], self.request_id, str(msg_received)) self.assertEqual(msg_received[2]['user_agent'], None) items = msg_received[2]['raw_result']['items'] expected_item0 = { 'title': 'Lolly Tang 2009 09 26 WEB x264-TBS', 'link': 'https://eztv.io/ep/1369854/lolly-tang-2009-09-26-web-x264-tbs/', 'description': None, 'author': None, 'categories': ('TV', ), 'comments': None, 'enclosures': ({ 'url': 'https://zoink.ch/torrent/Lolly.Tang.2009.09.26.WEB.x264-TBS[eztv].mkv.torrent', 'length': 288475596, 'type': 'application/x-bittorrent' }, ), 'guid': 'https://eztv.io/ep/1369854/lolly-tang-2009-09-26-web-x264-tbs/', 'source': None, 'torrent': { 'filename': 'Lolly.Tang.2009.09.26.WEB.x264-TBS[eztv].mkv', 'contentlength': '288475596', 'infohash': '4CF874831F61F5DB9C3299E503E28A8103047BA0', 'magneturi': 'magnet:?xt=urn:btih:4CF874831F61F5DB9C3299E503E28A8103047BA0&dn=Lolly.Tang.2009.09.26.WEB.x264-TBS%5Beztv%5D.mkv&tr=udp%3A%2F%2Ftracker.publicbt.com%2Fannounce&tr=udp%3A%2F%2Fopen.demonii.com%3A1337&tr=http%3A%2F%2Ftracker.trackerfix.com%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Fexodus.desync.com%3A6969' # noqa: E501 }, 'torrent_item': None, 'content_encoded': None, 'published_date': '2019-09-27T08:12:48-04:00' } self.assertEqual(items[0], expected_item0)
class Daemon(object): """The Deluge Daemon class""" def __init__(self, listen_interface=None, interface=None, port=None, standalone=False, read_only_config_keys=None): """ Args: listen_interface (str, optional): The IP address to listen to bittorrent connections on. interface (str, optional): The IP address the daemon will listen for UI connections on. port (int, optional): The port the daemon will listen for UI connections on. standalone (bool, optional): If True the client is in Standalone mode otherwise, if False, start the daemon as separate process. read_only_config_keys (list of str, optional): A list of config keys that will not be altered by core.set_config() RPC method. """ self.standalone = standalone self.pid_file = get_config_dir('deluged.pid') log.info('Deluge daemon %s', get_version()) if is_daemon_running(self.pid_file): raise DaemonRunningError('Deluge daemon already running with this config directory!') # Twisted catches signals to terminate, so just have it call the shutdown method. reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown) # Catch some Windows specific signals if windows_check(): def win_handler(ctrl_type): """Handle the Windows shutdown or close events.""" log.debug('windows handler ctrl_type: %s', ctrl_type) if ctrl_type == CTRL_CLOSE_EVENT or ctrl_type == CTRL_SHUTDOWN_EVENT: self._shutdown() return 1 SetConsoleCtrlHandler(win_handler) # Start the core as a thread and join it until it's done self.core = Core(listen_interface=listen_interface, read_only_config_keys=read_only_config_keys) if port is None: port = self.core.config['daemon_port'] self.port = port if interface and not is_ip(interface): log.error('Invalid UI interface (must be IP Address): %s', interface) interface = None self.rpcserver = RPCServer( port=port, allow_remote=self.core.config['allow_remote'], listen=not standalone, interface=interface ) log.debug('Listening to UI on: %s:%s and bittorrent on: %s', interface, port, listen_interface) def start(self): # Register the daemon and the core RPCs self.rpcserver.register_object(self.core) self.rpcserver.register_object(self) # Make sure we start the PreferencesManager first component.start('PreferencesManager') if not self.standalone: log.info('Deluge daemon starting...') # Create pid file to track if deluged is running, also includes the port number. pid = os.getpid() log.debug('Storing pid %s & port %s in: %s', pid, self.port, self.pid_file) with open(self.pid_file, 'w') as _file: _file.write('%s;%s\n' % (pid, self.port)) component.start() try: reactor.run() finally: log.debug('Remove pid file: %s', self.pid_file) os.remove(self.pid_file) log.info('Deluge daemon shutdown successfully') @export() def shutdown(self, *args, **kwargs): log.debug('Deluge daemon shutdown requested...') reactor.callLater(0, reactor.stop) def _shutdown(self, *args, **kwargs): log.info('Deluge daemon shutting down, waiting for components to shutdown...') if not self.standalone: return component.shutdown() @export() def get_method_list(self): """Returns a list of the exported methods.""" return self.rpcserver.get_method_list() @export(1) def authorized_call(self, rpc): """Determines if session auth_level is authorized to call RPC. Args: rpc (str): A RPC, e.g. core.get_torrents_status Returns: bool: True if authorized to call RPC, otherwise False. """ if rpc not in self.get_method_list(): return False return self.rpcserver.get_session_auth_level() >= self.rpcserver.get_rpc_auth_level(rpc)