obj_impl.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. # File: obj_impl.py
  2. # Library: DOPAL - DO Python Azureus Library
  3. #
  4. # This program is free software; you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation; version 2 of the License.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details ( see the COPYING file ).
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program; if not, write to the Free Software
  15. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  16. '''
  17. Implementation of classes defined by Azureus's plugin API.
  18. Not all classes are defined here are mentioned in the API documentation (simply
  19. because too much documentation will be generated).
  20. For each class that DOPAL has explicit support for, there will be two classes
  21. defined in this module. The list of classes supported in this version is
  22. described in L{classes}.
  23. For each class supported, there will be a class named
  24. I{AzureusE{<}classnameE{>}}, and another class named I{DopalE{<}classnameE{>}}.
  25. The I{Azureus*} class is a subclass of L{AzureusObject} mixed in with the
  26. I{*DataType} class in the L{class_defs} module - the API closely resembles
  27. that of the actual object in Azureus.
  28. The I{Dopal*} class is a subclass of the I{Azureus*} class, mixed in with the
  29. L{DopalObjectMixin} class. These classes exist to define an extended API of
  30. convenience functions beyond the API supplied by Azureus itself. Although all
  31. plugin classes have a I{Dopal*} representation, only those classes mentioned
  32. in the API documentation have any extended behaviour defined for them.
  33. @group Standard class implementations: %(standard_classes)s
  34. @group DOPAL class implementation: %(dopal_classes)s
  35. '''
  36. import dopal.class_defs as _cdefs
  37. from dopal.objects import AzureusObject, AzureusObjectMetaclass, TypelessRemoteObject
  38. from dopal.errors import MissingRemoteAttributeError
  39. # Temporary value - should be removed once the import has finished.
  40. import dopal
  41. __epydoc_mode = dopal.__dopal_mode__ == 2
  42. del dopal
  43. # Imported just for the __str__ method of DopalObjectMixin.
  44. import sys
  45. # The code here is used to create representive classes of each of the remote
  46. # Azureus class types that we support.
  47. import new
  48. def _make_class(common_cls, data_type_cls, name_prefix, class_map_dict=None):
  49. az_class_name = data_type_cls.get_xml_type()
  50. new_class_name = name_prefix + az_class_name
  51. # Is the class already defined in the global namespace? If so - we
  52. # avoid defining it again.
  53. if globals().has_key(new_class_name):
  54. classobj = globals()[new_class_name]
  55. else:
  56. base_classes = (common_cls, data_type_cls)
  57. classobj = new.classobj(new_class_name, base_classes, {})
  58. del base_classes
  59. globals()[new_class_name] = classobj
  60. if __epydoc_mode:
  61. classobj.__plugin_class__ = True
  62. if class_map_dict is not None:
  63. class_map_dict[az_class_name] = classobj
  64. return classobj
  65. # The two class maps we provide by default.
  66. STANDARD_CLASS_MAP = {}
  67. DOPAL_CLASS_MAP = {}
  68. # These methods are common on all DOPAL variants of classes we create.
  69. class DopalObjectMixin:
  70. # Used by repr.
  71. def short_description(self):
  72. try:
  73. result = self._short_description()
  74. except MissingRemoteAttributeError:
  75. result = None
  76. if result is None:
  77. return ''
  78. return result
  79. # Used by str.
  80. def full_description(self):
  81. try:
  82. result = self._full_description()
  83. except MissingRemoteAttributeError:
  84. result = None
  85. if result is None:
  86. return ''
  87. return result
  88. def _short_description(self):
  89. return None
  90. def _full_description(self):
  91. return self.short_description()
  92. def __str__(self): # DopalObjectMixin
  93. '''
  94. Generates a string representation of this object - the value of this
  95. string will be the result returned by the L{__unicode__} method.
  96. Note - this method should return a string which is appropriate for
  97. the system's encoding (so C{UnicodeEncodeError}s should not occur), but
  98. it makes no guarantee I{how} it will do this.
  99. As of DOPAL 0.60, it encodes the string using the default system
  100. encoding, using 'replace' as the default way to handle encoding
  101. problems.
  102. '''
  103. # What should be the default behaviour?
  104. #
  105. # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/466341
  106. #
  107. # 1) Use encoding - "raw_unicode_escape".
  108. # 2) Use error handler - "replace" (current).
  109. # 3) Use error handler - "ignore".
  110. return unicode(self).encode(sys.getdefaultencoding(), 'replace')
  111. def __unicode__(self):
  112. '''
  113. Generates a text representation of this object. If the
  114. L{full_description} returns a useful representation, then the string
  115. will have this format::
  116. RemoteTypeName: FullDescriptionString
  117. Otherwise, it will resort to the superclass string representation.
  118. Example::
  119. Download: The Subways - Staring at the Sun.mp3 [Stopped, 100.0%]
  120. '''
  121. nice_name = self.full_description()
  122. if nice_name:
  123. result = "%s: %s" % (self.get_remote_type(), nice_name)
  124. else:
  125. result = AzureusObject.__str__(self)
  126. try:
  127. return unicode(result)
  128. # Python 2.2 doesn't define UnicodeDecodeError, we have to use
  129. # UnicodeError.
  130. except UnicodeError, error:
  131. # string_escape only defined in Python 2.3.
  132. if sys.version_info >= (2, 3):
  133. return unicode(result.encode('string_escape'))
  134. else:
  135. return unicode(AzureusObject.__str__(self))
  136. #
  137. def __repr__(self):
  138. nice_name = self.short_description()
  139. repr_string = AzureusObject.__repr__(self)
  140. if nice_name:
  141. if repr_string[-1:] == ">":
  142. repr_string = repr_string[:-1] + \
  143. ', for "%s">' % nice_name
  144. return repr_string
  145. class DopalObjectStatsMixin(DopalObjectMixin):
  146. def _short_description(self):
  147. return "S:%s P:%s" % (self.seed_count, self.non_seed_count)
  148. def _full_description(self):
  149. return "Seeds: %s, Peers: %s" % (self.seed_count, self.non_seed_count)
  150. # Some classes which are basically stat counts share these methods.
  151. # Now we create the classes - the standard variants first, then the DOPAL
  152. # enhanced variants afterwards.
  153. #
  154. # The DOPAL variants are only automatically generated if we haven't defined
  155. # them manually. We only define them manually if we have methods we want
  156. # to define on them.
  157. for az_class in _cdefs._class_map.values():
  158. _make_class(AzureusObject, az_class, 'Azureus', STANDARD_CLASS_MAP)
  159. del az_class
  160. #
  161. #
  162. # We've now created all the classes we wanted. We now define extra methods
  163. # on particular classes we care about.
  164. #
  165. #
  166. # Now we declare DOPAL variants of these classes - these classes will end up
  167. # providing a richer API than just the standard plugin classes.
  168. class DopalPluginConfig(DopalObjectMixin, AzureusPluginConfig):
  169. def get_upload_speed_limit(self):
  170. return self.getIntParameter(self.CORE_PARAM_INT_MAX_UPLOAD_SPEED_KBYTES_PER_SEC, 0)
  171. def get_download_speed_limit(self):
  172. return self.getIntParameter(self.CORE_PARAM_INT_MAX_DOWNLOAD_SPEED_KBYTES_PER_SEC, 0)
  173. def set_upload_speed_limit(self, limit):
  174. if limit is None:
  175. limit = 0
  176. self.setIntParameter(self.CORE_PARAM_INT_MAX_UPLOAD_SPEED_KBYTES_PER_SEC, limit)
  177. def set_download_speed_limit(self, limit):
  178. if limit is None:
  179. limit = 0
  180. self.setIntParameter(self.CORE_PARAM_INT_MAX_DOWNLOAD_SPEED_KBYTES_PER_SEC, limit)
  181. class DopalDownload(DopalObjectMixin, AzureusDownload):
  182. def _short_description(self):
  183. return self.torrent.short_description()
  184. def _full_description(self):
  185. result = self.short_description()
  186. if not result:
  187. return result
  188. result += " " + self.stats.full_description()
  189. return result
  190. class DopalDownloadStats(DopalObjectMixin, AzureusDownloadStats):
  191. def _full_description(self):
  192. return "[%s, %.1f%%]" % (self.status, float(self.completed) / 10)
  193. class DopalDiskManagerFileInfo(DopalObjectMixin, AzureusDiskManagerFileInfo):
  194. def _full_description(self):
  195. filename = self.short_description()
  196. if not filename:
  197. return None
  198. if self.is_skipped:
  199. return filename + " [skipped]"
  200. elif self.is_priority:
  201. return filename + " [high]"
  202. else:
  203. return filename + " [normal]"
  204. def _short_description(self):
  205. import os.path
  206. return os.path.basename(self.file)
  207. class DopalLoggerChannel(DopalObjectMixin, AzureusLoggerChannel):
  208. def _full_description(self):
  209. result = self.name
  210. if not self.enabled:
  211. result += " [disabled]"
  212. return result
  213. def _short_description(self):
  214. return self.name
  215. class DopalPeer(DopalObjectMixin, AzureusPeer):
  216. def _full_description(self):
  217. return "%s:%s" % (self.ip, self.port)
  218. def _short_description(self):
  219. return self.ip
  220. class DopalPluginInterface(DopalObjectMixin, AzureusPluginInterface):
  221. def _full_description(self):
  222. return self.plugin_name
  223. def _short_description(self):
  224. return self.plugin_id
  225. class DopalTorrent(DopalObjectMixin, AzureusTorrent):
  226. def _short_description(self):
  227. return self.name
  228. # Let's define the rest of the DOPAL classes.
  229. for az_class in [AzureusDownloadAnnounceResult, AzureusDownloadScrapeResult]:
  230. _make_class(DopalObjectStatsMixin, az_class, 'Dopal', DOPAL_CLASS_MAP)
  231. for az_class in STANDARD_CLASS_MAP.values():
  232. _make_class(DopalObjectMixin, az_class, 'Dopal', DOPAL_CLASS_MAP)
  233. del az_class
  234. # Bugfix for tf-b4rt: don't try to use/change __doc__ if it's
  235. # empty, which is the case if Python was invoked with -OO
  236. # (except for early Python 2.5 releases where -OO is broken:
  237. # http://mail.python.org/pipermail/python-bugs-list/2007-June/038590.html).
  238. if __doc__ is not None:
  239. # Amend the docstring to contain all the object types defined.
  240. doc_string_sub_dict = {}
  241. for class_map_dict, dict_entry in [
  242. (STANDARD_CLASS_MAP, 'standard_classes'),
  243. (DOPAL_CLASS_MAP, 'dopal_classes'),
  244. ]:
  245. cls = None
  246. classes_in_map = [cls.__name__ for cls in class_map_dict.values()]
  247. classes_in_map.sort()
  248. doc_string_sub_dict[dict_entry] = ', '.join(classes_in_map)
  249. del classes_in_map, cls
  250. __doc__ = __doc__ % doc_string_sub_dict
  251. del doc_string_sub_dict
  252. del __epydoc_mode
  253. STANDARD_CLASS_MAP[None] = DOPAL_CLASS_MAP[None] = TypelessRemoteObject