/libs/synchronousdeluge/client.py

https://gitlab.com/132nd-etcher/CouchPotatoServer · Python · 162 lines · 101 code · 38 blank · 23 comment · 24 complexity · 0d7e49e170618916404c85952a575561 MD5 · raw file

  1. import os
  2. import platform
  3. from collections import defaultdict
  4. from itertools import imap
  5. from synchronousdeluge.exceptions import DelugeRPCError
  6. from synchronousdeluge.protocol import DelugeRPCRequest, DelugeRPCResponse
  7. from synchronousdeluge.transfer import DelugeTransfer
  8. __all__ = ["DelugeClient"]
  9. RPC_RESPONSE = 1
  10. RPC_ERROR = 2
  11. RPC_EVENT = 3
  12. class DelugeClient(object):
  13. def __init__(self):
  14. """A deluge client session."""
  15. self.transfer = DelugeTransfer()
  16. self.modules = []
  17. self._request_counter = 0
  18. def _get_local_auth(self):
  19. auth_file = ""
  20. username = password = ""
  21. if platform.system() in ('Windows', 'Microsoft'):
  22. appDataPath = os.environ.get("APPDATA")
  23. if not appDataPath:
  24. import _winreg
  25. hkey = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders")
  26. appDataReg = _winreg.QueryValueEx(hkey, "AppData")
  27. appDataPath = appDataReg[0]
  28. _winreg.CloseKey(hkey)
  29. auth_file = os.path.join(appDataPath, "deluge", "auth")
  30. else:
  31. from xdg.BaseDirectory import save_config_path
  32. try:
  33. auth_file = os.path.join(save_config_path("deluge"), "auth")
  34. except OSError, e:
  35. return username, password
  36. if os.path.exists(auth_file):
  37. for line in open(auth_file):
  38. if line.startswith("#"):
  39. # This is a comment line
  40. continue
  41. line = line.strip()
  42. try:
  43. lsplit = line.split(":")
  44. except Exception, e:
  45. continue
  46. if len(lsplit) == 2:
  47. username, password = lsplit
  48. elif len(lsplit) == 3:
  49. username, password, level = lsplit
  50. else:
  51. continue
  52. if username == "localclient":
  53. return (username, password)
  54. return ("", "")
  55. def _create_module_method(self, module, method):
  56. fullname = "{0}.{1}".format(module, method)
  57. def func(obj, *args, **kwargs):
  58. return self.remote_call(fullname, *args, **kwargs)
  59. func.__name__ = method
  60. return func
  61. def _introspect(self):
  62. self.modules = []
  63. methods = self.remote_call("daemon.get_method_list").get()
  64. methodmap = defaultdict(dict)
  65. splitter = lambda v: v.split(".")
  66. for module, method in imap(splitter, methods):
  67. methodmap[module][method] = self._create_module_method(module, method)
  68. for module, methods in methodmap.items():
  69. clsname = "DelugeModule{0}".format(module.capitalize())
  70. cls = type(clsname, (), methods)
  71. setattr(self, module, cls())
  72. self.modules.append(module)
  73. def remote_call(self, method, *args, **kwargs):
  74. req = DelugeRPCRequest(self._request_counter, method, *args, **kwargs)
  75. message = next(self.transfer.send_request(req))
  76. response = DelugeRPCResponse()
  77. if not isinstance(message, tuple):
  78. return
  79. if len(message) < 3:
  80. return
  81. message_type = message[0]
  82. # if message_type == RPC_EVENT:
  83. # event = message[1]
  84. # values = message[2]
  85. #
  86. # if event in self._event_handlers:
  87. # for handler in self._event_handlers[event]:
  88. # gevent.spawn(handler, *values)
  89. #
  90. # elif message_type in (RPC_RESPONSE, RPC_ERROR):
  91. if message_type in (RPC_RESPONSE, RPC_ERROR):
  92. request_id = message[1]
  93. value = message[2]
  94. if request_id == self._request_counter :
  95. if message_type == RPC_RESPONSE:
  96. response.set(value)
  97. elif message_type == RPC_ERROR:
  98. err = DelugeRPCError(*value)
  99. response.set_exception(err)
  100. self._request_counter += 1
  101. return response
  102. def connect(self, host="127.0.0.1", port=58846, username="", password=""):
  103. """Connects to a daemon process.
  104. :param host: str, the hostname of the daemon
  105. :param port: int, the port of the daemon
  106. :param username: str, the username to login with
  107. :param password: str, the password to login with
  108. """
  109. # Connect transport
  110. self.transfer.connect((host, port))
  111. # Attempt to fetch local auth info if needed
  112. if not username and host in ("127.0.0.1", "localhost"):
  113. username, password = self._get_local_auth()
  114. # Authenticate
  115. self.remote_call("daemon.login", username, password).get()
  116. # Introspect available methods
  117. self._introspect()
  118. @property
  119. def connected(self):
  120. return self.transfer.connected
  121. def disconnect(self):
  122. """Disconnects from the daemon."""
  123. self.transfer.disconnect()