|
|
|
import os
|
|
|
|
import platform
|
|
|
|
|
|
|
|
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):
|
|
|
|
auth_file = ""
|
|
|
|
username = password = ""
|
|
|
|
if platform.system() in ('Windows', 'Microsoft'):
|
|
|
|
appDataPath = os.environ.get("APPDATA")
|
|
|
|
if not appDataPath:
|
|
|
|
import _winreg
|
|
|
|
hkey = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders")
|
|
|
|
appDataReg = _winreg.QueryValueEx(hkey, "AppData")
|
|
|
|
appDataPath = appDataReg[0]
|
|
|
|
_winreg.CloseKey(hkey)
|
|
|
|
|
|
|
|
auth_file = os.path.join(appDataPath, "deluge", "auth")
|
|
|
|
else:
|
|
|
|
from xdg.BaseDirectory import save_config_path
|
|
|
|
try:
|
|
|
|
auth_file = os.path.join(save_config_path("deluge"), "auth")
|
|
|
|
except OSError, e:
|
|
|
|
return username, password
|
|
|
|
|
|
|
|
|
|
|
|
if os.path.exists(auth_file):
|
|
|
|
for line in open(auth_file):
|
|
|
|
if line.startswith("#"):
|
|
|
|
# This is a comment line
|
|
|
|
continue
|
|
|
|
line = line.strip()
|
|
|
|
try:
|
|
|
|
lsplit = line.split(":")
|
|
|
|
except Exception, e:
|
|
|
|
continue
|
|
|
|
|
|
|
|
if len(lsplit) == 2:
|
|
|
|
username, password = lsplit
|
|
|
|
elif len(lsplit) == 3:
|
|
|
|
username, password, level = lsplit
|
|
|
|
else:
|
|
|
|
continue
|
|
|
|
|
|
|
|
if username == "localclient":
|
|
|
|
return (username, password)
|
|
|
|
|
|
|
|
return ("", "")
|
|
|
|
|
|
|
|
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()
|
|
|
|
|