import os from collections import defaultdict from itertools import imap from synchronousdeluge.exceptions import DelugeRPCError from synchronousdeluge.protocol import DelugeRPCRequest, DelugeRPCResponse from synchronousdeluge.transfer import DelugeTransfer __all__ = ["DelugeClient"] RPC_RESPONSE = 1 RPC_ERROR = 2 RPC_EVENT = 3 class DelugeClient(object): def __init__(self): """A deluge client session.""" self.transfer = DelugeTransfer() self.modules = [] self._request_counter = 0 def _get_local_auth(self): xdg_config = os.path.expanduser(os.environ.get("XDG_CONFIG_HOME", "~/.config")) config_home = os.path.join(xdg_config, "deluge") auth_file = os.path.join(config_home, "auth") username = password = "" with open(auth_file) as fd: for line in fd: if line.startswith("#"): continue auth = line.split(":") if len(auth) >= 2 and auth[0] == "localclient": username, password = auth[0], auth[1] break return username, password def _create_module_method(self, module, method): fullname = "{0}.{1}".format(module, method) def func(obj, *args, **kwargs): return self.remote_call(fullname, *args, **kwargs) func.__name__ = method return func def _introspect(self): self.modules = [] methods = self.remote_call("daemon.get_method_list").get() methodmap = defaultdict(dict) splitter = lambda v: v.split(".") for module, method in imap(splitter, methods): methodmap[module][method] = self._create_module_method(module, method) for module, methods in methodmap.items(): clsname = "DelugeModule{0}".format(module.capitalize()) cls = type(clsname, (), methods) setattr(self, module, cls()) self.modules.append(module) def remote_call(self, method, *args, **kwargs): req = DelugeRPCRequest(self._request_counter, method, *args, **kwargs) message = next(self.transfer.send_request(req)) response = DelugeRPCResponse() if not isinstance(message, tuple): return if len(message) < 3: return message_type = message[0] # if message_type == RPC_EVENT: # event = message[1] # values = message[2] # # if event in self._event_handlers: # for handler in self._event_handlers[event]: # gevent.spawn(handler, *values) # # elif message_type in (RPC_RESPONSE, RPC_ERROR): if message_type in (RPC_RESPONSE, RPC_ERROR): request_id = message[1] value = message[2] if request_id == self._request_counter : if message_type == RPC_RESPONSE: response.set(value) elif message_type == RPC_ERROR: err = DelugeRPCError(*value) response.set_exception(err) self._request_counter += 1 return response def connect(self, host="127.0.0.1", port=58846, username="", password=""): """Connects to a daemon process. :param host: str, the hostname of the daemon :param port: int, the port of the daemon :param username: str, the username to login with :param password: str, the password to login with """ # Connect transport self.transfer.connect((host, port)) # Attempt to fetch local auth info if needed if not username and host in ("127.0.0.1", "localhost"): username, password = self._get_local_auth() # Authenticate self.remote_call("daemon.login", username, password).get() # Introspect available methods self._introspect() @property def connected(self): return self.transfer.connected def disconnect(self): """Disconnects from the daemon.""" self.transfer.disconnect()