why?
This commit is contained in:
DiMartinoX committed 2015-01-09 14:11:21 +03:00
commit 6fef9834fe
396 files changed
+49459

No files matched your search

View File
Whitespace-only changes.
Binary file not shown.
Binary file not shown.
+65
View File
@@ -0,0 +1,65 @@
# File: __init__.py
# Library: DOPAL - DO Python Azureus Library
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details ( see the COPYING file ).
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
__version__ = (0, 6, 0)
__version_str__ = '%s.%s' % (__version__[0], ''.join([str(part) for part in __version__[1:]]))
__user_agent__ = 'DOPAL/' + __version_str__
__all__ = [
# Module variables.
'__version__', '__version_str__', '__user_agent__',
# Front-end modules.
'interact', 'main', 'scripting',
# Core-level modules.
'aztypes', 'core', 'debug', 'errors', 'utils', 'xmlutils',
# Object-level modules.
'classes', 'class_defs', 'convert', 'objects', 'obj_impl', 'persistency',
'logutils',
]
# Mode definitions:
# 0 - Normal behaviour - should always be distributed with this value.
# 1 - Debug mode - raise debug errors when appropriate.
# 2 - Epydoc mode - used when Epydoc API documentation is being generated.
__dopal_mode__ = 0
__doc__ = '''
DOPAL - DO Python Azureus Library (version %(__version_str__)s)
@var __version__: DOPAL version as a tuple.
@var __version_str__: DOPAL version as a string.
@var __user_agent__: User agent string used by DOPAL when communicating with
Azureus.
@var __dopal_mode__: Debug internal variable which controls some of the
behaviour of how DOPAL works - not meant for external use.
@group Front-end modules: interact, main, scripting
@group Core-level modules: aztypes, core, debug, errors, utils, xmlutils
@group Object-level modules: classes, class_defs, convert, objects, obj_impl,
persistency, logutils
''' % vars()
# If we are in debug mode, auto-detect whether Epydoc is running and adjust the
# mode accordingly.
import sys
if __dopal_mode__ == 1 and 'epydoc' in sys.modules:
__dopal_mode__ = 2
del sys
Binary file not shown.
Binary file not shown.
+367
View File
@@ -0,0 +1,367 @@
# File: aztypes.py
# Library: DOPAL - DO Python Azureus Library
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details ( see the COPYING file ).
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
'''
Utilities used to convert basic values between DOPAL and Azureus.
'''
__pychecker__ = 'no-unreachable'
import types
from dopal.errors import ArgumentWrapError, WrapError, UnwrapError, \
InvalidWrapTypeError, InvalidUnwrapTypeError, InconsistentWrapTypeError, \
NoSuchAzMethodError, IncorrectArgumentCountError, MethodArgumentWrapError, \
raise_as
#
# Methods / classes related to type wrappers
#
class TypeWrapper:
def __init__(self, xml_type, xml_value): # TypeWrapper
self.xml_type = xml_type
self.xml_value = xml_value
def get_xml_type(self): # TypeWrapper
return self.xml_type
def as_xml(self): # TypeWrapper
return self.xml_value
# Stores the type name to _wrapobject mappings.
_wrap_value_dict = {}
class _wrapobject(object):
# If it is a native type, then it means that you can wrap the
# object without having to return a TypeWrapper instance.
native_type = False
def __init__(self, type_name, instance_type): # _wrapobject
self.type_name = type_name
self.instance_type = instance_type
_wrap_value_dict[type_name] = self
def __call__(self, value): # _wrapobject
return self.wrap(value)
# Only used if the value is not an instance of the type specified in
# instance_type.
def normalise_for_wrapping(self, value): # _wrapobject
raise WrapError("invalid type of object for %s: %s" % (self.type_name, type(value)), obj=value)
# Converts the value into a form where it can be added to a wrapper
# (usually as a string). For certain types (native types, like String,
# integer, long, float, boolean), it can return a value with one of those
# types instead.
def wrap_value(self, value): # _wrapobject
if not isinstance(value, types.StringTypes):
value = str(value)
return value
def wrap(self, value, force_wrapper=True): # _wrapobject
if self.instance_type is None or not isinstance(value, self.instance_type):
try:
value = self.normalise_for_wrapping(value)
except (TypeError, ValueError), error:
raise_as(error, WrapError)
if self.native_type and not force_wrapper:
return value
if self.wrap_value is None:
raise InvalidWrapTypeError(obj=self.type_name)
value = self.wrap_value(value)
return TypeWrapper(self.type_name, self.wrap_value(value))
def unwrap(self, value): # _wrapobject
if self.unwrap_value is None:
raise InvalidUnwrapTypeError(obj=self.type_name)
try:
value = self.unwrap_value(value)
except (TypeError, ValueError), error:
raise UnwrapError, error
return value
def unwrap_value(self, value): # _wrapobject
return value
def __str__(self): # _wrapobject
return '<wrapper object for type "%s">' % self.type_name
class _wrapnative(_wrapobject):
native_type = True
# I know that it is confusing that the arguments are swapped round...
def __init__(self, type_obj):
_wrapobject.__init__(self, type_obj.__name__, type_obj)
self.normalise_for_wrapping = type_obj
self.unwrap_value = type_obj
self.wrap_value = type_obj
def _value_as_boolean(value):
# Although this library is intended to be Python 2.2 compatible (i.e.
# before booleans were introduced), we only support versions of Python 2.2
# where False and True global constants are defined.
if value in [False, 0, 'False', 'false']:
return False
elif value in [True, 1, 'True', 'true']:
return True
else:
raise ValueError, "does not represent a boolean value"
def _boolean_as_string(value):
if value:
return 'true'
else:
return 'false'
wrap_int = _wrapnative(int)
wrap_long = _wrapnative(long)
wrap_float = _wrapnative(float)
# We create it without passing an instance type, and then attempt to assign it
# afterwards - Python 2.2 compatibility.
wrap_boolean = _wrapobject('boolean', None)
import sys
if sys.version_info >= (2, 3):
wrap_boolean.instance_type = bool
wrap_boolean.native_type = True
wrap_boolean.normalise_for_wrapping = _value_as_boolean
wrap_boolean.wrap_value = _boolean_as_string
# This function is a bit overkill for our needs, but it will suffice for now.
wrap_boolean.unwrap_value = _value_as_boolean
# We don't set normalise_for_wrapping, we want to impose a string type here.
wrap_url = _wrapobject('URL', types.StringTypes)
wrap_file = _wrapobject('File', types.StringTypes)
wrap_short = _wrapobject('short', int)
wrap_short.normalise_for_wrapping = int
wrap_short.wrap_value = int
wrap_short.unwrap_value = int
from dopal.utils import string_to_hex_form, hex_string_to_binary
# None - let the below functions deal with non string values.
wrap_byte_array = _wrapobject('byte[]', None)
wrap_byte_array.normalise_for_wrapping = string_to_hex_form
wrap_byte_array.unwrap_value = hex_string_to_binary
del string_to_hex_form, hex_string_to_binary
wrap_string = _wrapobject('String', types.StringTypes)
wrap_string.normalise_for_wrapping = str
wrap_string.native_type = True
def _unwrap_void(value):
if value == '':
return None
else:
raise ValueError, "non-null return value: %s" % value
wrap_void = _wrapobject('void', None)
wrap_void.wrap_value = None # Not an argument type.
wrap_void.unwrap_value = _unwrap_void
def wrap_value(value, value_type, force_wrapper=False):
if hasattr(value, 'get_xml_type'):
stored_type = value.get_xml_type()
if stored_type != value_type:
raise InconsistentWrapTypeError(stored_type, value_type)
return value
try:
converter = _wrap_value_dict[value_type]
except KeyError:
raise InvalidWrapTypeError(obj=value_type)
else:
return converter.wrap(value, force_wrapper=force_wrapper)
def unwrap_value(value, value_type):
try:
converter = _wrap_value_dict[value_type]
except KeyError:
raise InvalidUnwrapTypeError(obj=value_type)
else:
return converter.unwrap(value)
def is_java_argument_type(java_type):
return getattr(_wrap_value_dict.get(java_type), 'wrap_value', None) is not None
def is_java_return_type(java_type):
return getattr(_wrap_value_dict.get(java_type), 'unwrap_value', None) is not None
def get_component_type(java_type):
if isinstance(java_type, str):
if java_type[-2:] == '[]':
return java_type[:-2]
return None
def get_basic_component_type(java_type):
while True:
component_type = get_component_type(java_type)
if component_type is None:
return java_type
java_type = component_type
# This can't happen, but it just stops PyChecker getting worried about it.
return None
def is_array_type(java_type):
return get_component_type(java_type) is not None
class AzMethod(object):
def __init__(self, name, arguments=(), return_type='void'): # AzMethod
object.__init__(super)
self.name = name
self.arg_types = arguments
self.arg_count = len(arguments)
self.return_type = return_type
def has_return_type(self): # AzMethod
return self.return_type == 'void'
def wrap_args(self, *args): # AzMethod
if len(args) != self.arg_count:
raise IncorrectArgumentCountError, (self.name, len(args), self.arg_count)
result = []
for i in range(self.arg_count):
try:
result.append(wrap_value(args[i], self.arg_types[i]))
except WrapError, error:
raise ArgumentWrapError(i, args[i], self.arg_types[i], error)
return result
def __eq__(self, other): # AzMethod
if not isinstance(other, AzMethod):
return False
for attr in ['name', 'return_type', 'arg_types']:
if getattr(self, attr) != getattr(other, attr):
return False
return True
def __ne__(self, other): # AzMethod
return not (self == other)
def __str__(self): # AzMethod
arg_string = ', '.join(self.arg_types)
return "%s %s(%s)" % (self.return_type, self.name, arg_string)
def __repr__(self): # AzMethod
return "AzMethod [%s]" % self
class AzureusMethods(object):
def __init__(self, methods=None):
object.__init__(self)
self.__data = {}
if methods:
map(self.add_method, methods)
def add_method(self, az_method):
methods_dict = self.__data.setdefault(az_method.name, {})
method_args_seq = methods_dict.setdefault(az_method.arg_count, [])
if az_method not in method_args_seq:
method_args_seq.append(az_method)
def get_method_names(self):
names = self.__data.keys()
names.sort()
return names
def get_method_arg_count(self, name):
try:
result = self.__data[name].keys()
except KeyError:
return []
else:
result.sort()
return result
def match_method_spec(self, name, argcount):
try:
return self.__data[name][argcount]
except KeyError:
try:
argcounts_dict = self.__data[name]
except KeyError:
raise NoSuchAzMethodError, name
argcounts = argcounts_dict.keys()
argcounts.sort()
raise IncorrectArgumentCountError, (name, argcount, argcounts_dict.keys())
def get_matching_methods(self, name, args):
methods = self.match_method_spec(name, len(args))
accepted_methods = {}
rejected_methods = {}
for methodobj in methods:
try:
accepted_methods[methodobj] = methodobj.wrap_args(*args)
except ArgumentWrapError, error:
rejected_methods[methodobj] = error
if not accepted_methods:
raise MethodArgumentWrapError, (name, rejected_methods.items())
return accepted_methods.items()
def wrap_args(self, name, args):
method_data_tpls = self.get_matching_methods(name, args)
if len(method_data_tpls) > 1:
method_data = self.resolve_ambiguous_method(args, method_data_tpls)
else:
method_data = method_data_tpls[0]
return method_data[1], method_data[0].return_type
def resolve_ambiguous_method(self, args, method_data_tpls):
# XXX: We'll implement something cleverer at a later date.
return method_data_tpls[0]
def get_all_methods(self):
result = []
for method_name, method_dict in self.__data.items():
for argcount, method_seq in method_dict.items():
result.extend(method_seq)
return result
def update(self, azmethodsobj):
for azmethod in azmethodsobj.get_all_methods():
self.add_method(azmethod)
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large. Load diff
Binary file not shown.
Binary file not shown.
+317
View File
@@ -0,0 +1,317 @@
# File: classes.py
# Library: DOPAL - DO Python Azureus Library
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details ( see the COPYING file ).
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
'''
Contains basic class definitions for objects - use this module when doing
instance checking.
This module contains a few utility functions in determining what classes are supported by DOPAL - it has an internal list of all classes that DOPAL is aware of.
'''
from dopal.aztypes import get_component_type as _get_component_type
#
# List of classes created by classes_make.py.
#
azureus_class_list = [
('org.gudy.azureus2.plugins', 'LaunchablePlugin'),
('org.gudy.azureus2.plugins', 'Plugin'),
('org.gudy.azureus2.plugins', 'PluginConfig'),
('org.gudy.azureus2.plugins', 'PluginConfigListener'),
('org.gudy.azureus2.plugins', 'PluginEvent'),
('org.gudy.azureus2.plugins', 'PluginEventListener'),
('org.gudy.azureus2.plugins', 'PluginInterface'),
('org.gudy.azureus2.plugins', 'PluginListener'),
('org.gudy.azureus2.plugins', 'PluginManager'),
('org.gudy.azureus2.plugins', 'PluginManagerArgumentHandler'),
('org.gudy.azureus2.plugins', 'PluginManagerDefaults'),
('org.gudy.azureus2.plugins', 'UnloadablePlugin'),
('org.gudy.azureus2.plugins.clientid', 'ClientIDGenerator'),
('org.gudy.azureus2.plugins.clientid', 'ClientIDManager'),
('org.gudy.azureus2.plugins.config', 'ConfigParameter'),
('org.gudy.azureus2.plugins.config', 'ConfigParameterListener'),
('org.gudy.azureus2.plugins.ddb', 'DistributedDatabase'),
('org.gudy.azureus2.plugins.ddb', 'DistributedDatabaseContact'),
('org.gudy.azureus2.plugins.ddb', 'DistributedDatabaseEvent'),
('org.gudy.azureus2.plugins.ddb', 'DistributedDatabaseKey'),
('org.gudy.azureus2.plugins.ddb', 'DistributedDatabaseListener'),
('org.gudy.azureus2.plugins.ddb', 'DistributedDatabaseProgressListener'),
('org.gudy.azureus2.plugins.ddb', 'DistributedDatabaseTransferHandler'),
('org.gudy.azureus2.plugins.ddb', 'DistributedDatabaseTransferType'),
('org.gudy.azureus2.plugins.ddb', 'DistributedDatabaseValue'),
('org.gudy.azureus2.plugins.disk', 'DiskManager'),
('org.gudy.azureus2.plugins.disk', 'DiskManagerChannel'),
('org.gudy.azureus2.plugins.disk', 'DiskManagerEvent'),
('org.gudy.azureus2.plugins.disk', 'DiskManagerFileInfo'),
('org.gudy.azureus2.plugins.disk', 'DiskManagerListener'),
('org.gudy.azureus2.plugins.disk', 'DiskManagerRequest'),
('org.gudy.azureus2.plugins.download', 'Download'),
('org.gudy.azureus2.plugins.download', 'DownloadAnnounceResult'),
('org.gudy.azureus2.plugins.download', 'DownloadAnnounceResultPeer'),
('org.gudy.azureus2.plugins.download', 'DownloadListener'),
('org.gudy.azureus2.plugins.download', 'DownloadManager'),
('org.gudy.azureus2.plugins.download', 'DownloadManagerListener'),
('org.gudy.azureus2.plugins.download', 'DownloadManagerStats'),
('org.gudy.azureus2.plugins.download', 'DownloadPeerListener'),
('org.gudy.azureus2.plugins.download', 'DownloadPropertyEvent'),
('org.gudy.azureus2.plugins.download', 'DownloadPropertyListener'),
('org.gudy.azureus2.plugins.download', 'DownloadScrapeResult'),
('org.gudy.azureus2.plugins.download', 'DownloadStats'),
('org.gudy.azureus2.plugins.download', 'DownloadTrackerListener'),
('org.gudy.azureus2.plugins.download', 'DownloadWillBeAddedListener'),
('org.gudy.azureus2.plugins.download', 'DownloadWillBeRemovedListener'),
('org.gudy.azureus2.plugins.download.session', 'SessionAuthenticator'),
('org.gudy.azureus2.plugins.installer', 'FilePluginInstaller'),
('org.gudy.azureus2.plugins.installer', 'InstallablePlugin'),
('org.gudy.azureus2.plugins.installer', 'PluginInstaller'),
('org.gudy.azureus2.plugins.installer', 'PluginInstallerListener'),
('org.gudy.azureus2.plugins.installer', 'StandardPlugin'),
('org.gudy.azureus2.plugins.ipc', 'IPCInterface'),
('org.gudy.azureus2.plugins.ipfilter', 'IPBlocked'),
('org.gudy.azureus2.plugins.ipfilter', 'IPFilter'),
('org.gudy.azureus2.plugins.ipfilter', 'IPRange'),
('org.gudy.azureus2.plugins.logging', 'Logger'),
('org.gudy.azureus2.plugins.logging', 'LoggerAlertListener'),
('org.gudy.azureus2.plugins.logging', 'LoggerChannel'),
('org.gudy.azureus2.plugins.logging', 'LoggerChannelListener'),
('org.gudy.azureus2.plugins.messaging', 'Message'),
('org.gudy.azureus2.plugins.messaging', 'MessageManager'),
('org.gudy.azureus2.plugins.messaging', 'MessageManagerListener'),
('org.gudy.azureus2.plugins.messaging', 'MessageStreamDecoder'),
('org.gudy.azureus2.plugins.messaging', 'MessageStreamEncoder'),
('org.gudy.azureus2.plugins.network', 'Connection'),
('org.gudy.azureus2.plugins.network', 'ConnectionListener'),
('org.gudy.azureus2.plugins.network', 'ConnectionManager'),
('org.gudy.azureus2.plugins.network', 'IncomingMessageQueue'),
('org.gudy.azureus2.plugins.network', 'IncomingMessageQueueListener'),
('org.gudy.azureus2.plugins.network', 'OutgoingMessageQueue'),
('org.gudy.azureus2.plugins.network', 'OutgoingMessageQueueListener'),
('org.gudy.azureus2.plugins.network', 'RawMessage'),
('org.gudy.azureus2.plugins.network', 'Transport'),
('org.gudy.azureus2.plugins.peers', 'Peer'),
('org.gudy.azureus2.plugins.peers', 'PeerEvent'),
('org.gudy.azureus2.plugins.peers', 'PeerListener'),
('org.gudy.azureus2.plugins.peers', 'PeerListener2'),
('org.gudy.azureus2.plugins.peers', 'PeerManager'),
('org.gudy.azureus2.plugins.peers', 'PeerManagerListener'),
('org.gudy.azureus2.plugins.peers', 'PeerManagerStats'),
('org.gudy.azureus2.plugins.peers', 'PeerReadRequest'),
('org.gudy.azureus2.plugins.peers', 'PeerStats'),
('org.gudy.azureus2.plugins.peers.protocol', 'PeerProtocolBT'),
('org.gudy.azureus2.plugins.peers.protocol', 'PeerProtocolExtensionHandler'),
('org.gudy.azureus2.plugins.peers.protocol', 'PeerProtocolManager'),
('org.gudy.azureus2.plugins.platform', 'PlatformManager'),
('org.gudy.azureus2.plugins.sharing', 'ShareItem'),
('org.gudy.azureus2.plugins.sharing', 'ShareManager'),
('org.gudy.azureus2.plugins.sharing', 'ShareManagerListener'),
('org.gudy.azureus2.plugins.sharing', 'ShareResource'),
('org.gudy.azureus2.plugins.sharing', 'ShareResourceDir'),
('org.gudy.azureus2.plugins.sharing', 'ShareResourceDirContents'),
('org.gudy.azureus2.plugins.sharing', 'ShareResourceEvent'),
('org.gudy.azureus2.plugins.sharing', 'ShareResourceFile'),
('org.gudy.azureus2.plugins.sharing', 'ShareResourceListener'),
('org.gudy.azureus2.plugins.sharing', 'ShareResourceWillBeDeletedListener'),
('org.gudy.azureus2.plugins.torrent', 'Torrent'),
('org.gudy.azureus2.plugins.torrent', 'TorrentAnnounceURLList'),
('org.gudy.azureus2.plugins.torrent', 'TorrentAnnounceURLListSet'),
('org.gudy.azureus2.plugins.torrent', 'TorrentAttribute'),
('org.gudy.azureus2.plugins.torrent', 'TorrentAttributeEvent'),
('org.gudy.azureus2.plugins.torrent', 'TorrentAttributeListener'),
('org.gudy.azureus2.plugins.torrent', 'TorrentDownloader'),
('org.gudy.azureus2.plugins.torrent', 'TorrentFile'),
('org.gudy.azureus2.plugins.torrent', 'TorrentManager'),
('org.gudy.azureus2.plugins.torrent', 'TorrentManagerEvent'),
('org.gudy.azureus2.plugins.torrent', 'TorrentManagerListener'),
('org.gudy.azureus2.plugins.tracker', 'Tracker'),
('org.gudy.azureus2.plugins.tracker', 'TrackerListener'),
('org.gudy.azureus2.plugins.tracker', 'TrackerPeer'),
('org.gudy.azureus2.plugins.tracker', 'TrackerPeerEvent'),
('org.gudy.azureus2.plugins.tracker', 'TrackerPeerListener'),
('org.gudy.azureus2.plugins.tracker', 'TrackerTorrent'),
('org.gudy.azureus2.plugins.tracker', 'TrackerTorrentListener'),
('org.gudy.azureus2.plugins.tracker', 'TrackerTorrentRequest'),
('org.gudy.azureus2.plugins.tracker', 'TrackerTorrentWillBeRemovedListener'),
('org.gudy.azureus2.plugins.tracker.web', 'TrackerAuthenticationListener'),
('org.gudy.azureus2.plugins.tracker.web', 'TrackerWebContext'),
('org.gudy.azureus2.plugins.tracker.web', 'TrackerWebPageGenerator'),
('org.gudy.azureus2.plugins.tracker.web', 'TrackerWebPageRequest'),
('org.gudy.azureus2.plugins.tracker.web', 'TrackerWebPageResponse'),
('org.gudy.azureus2.plugins.ui', 'Graphic'),
('org.gudy.azureus2.plugins.ui', 'UIInstance'),
('org.gudy.azureus2.plugins.ui', 'UIInstanceFactory'),
('org.gudy.azureus2.plugins.ui', 'UIManager'),
('org.gudy.azureus2.plugins.ui', 'UIManagerEvent'),
('org.gudy.azureus2.plugins.ui', 'UIManagerEventListener'),
('org.gudy.azureus2.plugins.ui', 'UIManagerListener'),
('org.gudy.azureus2.plugins.ui', 'UIPluginView'),
('org.gudy.azureus2.plugins.ui.components', 'UIComponent'),
('org.gudy.azureus2.plugins.ui.components', 'UIProgressBar'),
('org.gudy.azureus2.plugins.ui.components', 'UIPropertyChangeEvent'),
('org.gudy.azureus2.plugins.ui.components', 'UIPropertyChangeListener'),
('org.gudy.azureus2.plugins.ui.components', 'UITextArea'),
('org.gudy.azureus2.plugins.ui.components', 'UITextField'),
('org.gudy.azureus2.plugins.ui.config', 'ActionParameter'),
('org.gudy.azureus2.plugins.ui.config', 'BooleanParameter'),
('org.gudy.azureus2.plugins.ui.config', 'ConfigSection'),
('org.gudy.azureus2.plugins.ui.config', 'ConfigSectionSWT'),
('org.gudy.azureus2.plugins.ui.config', 'DirectoryParameter'),
('org.gudy.azureus2.plugins.ui.config', 'EnablerParameter'),
('org.gudy.azureus2.plugins.ui.config', 'IntParameter'),
('org.gudy.azureus2.plugins.ui.config', 'LabelParameter'),
('org.gudy.azureus2.plugins.ui.config', 'Parameter'),
('org.gudy.azureus2.plugins.ui.config', 'ParameterGroup'),
('org.gudy.azureus2.plugins.ui.config', 'ParameterListener'),
('org.gudy.azureus2.plugins.ui.config', 'PasswordParameter'),
('org.gudy.azureus2.plugins.ui.config', 'PluginConfigUIFactory'),
('org.gudy.azureus2.plugins.ui.config', 'StringListParameter'),
('org.gudy.azureus2.plugins.ui.config', 'StringParameter'),
('org.gudy.azureus2.plugins.ui.menus', 'MenuItem'),
('org.gudy.azureus2.plugins.ui.menus', 'MenuItemFillListener'),
('org.gudy.azureus2.plugins.ui.menus', 'MenuItemListener'),
('org.gudy.azureus2.plugins.ui.model', 'BasicPluginConfigModel'),
('org.gudy.azureus2.plugins.ui.model', 'BasicPluginViewModel'),
('org.gudy.azureus2.plugins.ui.model', 'PluginConfigModel'),
('org.gudy.azureus2.plugins.ui.model', 'PluginViewModel'),
('org.gudy.azureus2.plugins.ui.SWT', 'GraphicSWT'),
('org.gudy.azureus2.plugins.ui.SWT', 'SWTManager'),
('org.gudy.azureus2.plugins.ui.tables', 'TableCell'),
('org.gudy.azureus2.plugins.ui.tables', 'TableCellAddedListener'),
('org.gudy.azureus2.plugins.ui.tables', 'TableCellDisposeListener'),
('org.gudy.azureus2.plugins.ui.tables', 'TableCellMouseListener'),
('org.gudy.azureus2.plugins.ui.tables', 'TableCellRefreshListener'),
('org.gudy.azureus2.plugins.ui.tables', 'TableCellToolTipListener'),
('org.gudy.azureus2.plugins.ui.tables', 'TableColumn'),
('org.gudy.azureus2.plugins.ui.tables', 'TableContextMenuItem'),
('org.gudy.azureus2.plugins.ui.tables', 'TableManager'),
('org.gudy.azureus2.plugins.ui.tables', 'TableRow'),
('org.gudy.azureus2.plugins.ui.tables.mytorrents', 'MyTorrentsTableItem'),
('org.gudy.azureus2.plugins.ui.tables.mytorrents', 'PluginMyTorrentsItem'),
('org.gudy.azureus2.plugins.ui.tables.mytorrents', 'PluginMyTorrentsItemFactory'),
('org.gudy.azureus2.plugins.ui.tables.peers', 'PeerTableItem'),
('org.gudy.azureus2.plugins.ui.tables.peers', 'PluginPeerItem'),
('org.gudy.azureus2.plugins.ui.tables.peers', 'PluginPeerItemFactory'),
('org.gudy.azureus2.plugins.update', 'UpdatableComponent'),
('org.gudy.azureus2.plugins.update', 'Update'),
('org.gudy.azureus2.plugins.update', 'UpdateChecker'),
('org.gudy.azureus2.plugins.update', 'UpdateCheckerListener'),
('org.gudy.azureus2.plugins.update', 'UpdateCheckInstance'),
('org.gudy.azureus2.plugins.update', 'UpdateCheckInstanceListener'),
('org.gudy.azureus2.plugins.update', 'UpdateInstaller'),
('org.gudy.azureus2.plugins.update', 'UpdateListener'),
('org.gudy.azureus2.plugins.update', 'UpdateManager'),
('org.gudy.azureus2.plugins.update', 'UpdateManagerDecisionListener'),
('org.gudy.azureus2.plugins.update', 'UpdateManagerListener'),
('org.gudy.azureus2.plugins.update', 'UpdateProgressListener'),
('org.gudy.azureus2.plugins.utils', 'AggregatedDispatcher'),
('org.gudy.azureus2.plugins.utils', 'AggregatedList'),
('org.gudy.azureus2.plugins.utils', 'AggregatedListAcceptor'),
('org.gudy.azureus2.plugins.utils', 'ByteArrayWrapper'),
('org.gudy.azureus2.plugins.utils', 'Formatters'),
('org.gudy.azureus2.plugins.utils', 'LocaleDecoder'),
('org.gudy.azureus2.plugins.utils', 'LocaleListener'),
('org.gudy.azureus2.plugins.utils', 'LocaleUtilities'),
('org.gudy.azureus2.plugins.utils', 'Monitor'),
('org.gudy.azureus2.plugins.utils', 'PooledByteBuffer'),
('org.gudy.azureus2.plugins.utils', 'Semaphore'),
('org.gudy.azureus2.plugins.utils', 'ShortCuts'),
('org.gudy.azureus2.plugins.utils', 'Utilities'),
('org.gudy.azureus2.plugins.utils', 'UTTimer'),
('org.gudy.azureus2.plugins.utils', 'UTTimerEvent'),
('org.gudy.azureus2.plugins.utils', 'UTTimerEventPerformer'),
('org.gudy.azureus2.plugins.utils.resourcedownloader', 'ResourceDownloader'),
('org.gudy.azureus2.plugins.utils.resourcedownloader', 'ResourceDownloaderDelayedFactory'),
('org.gudy.azureus2.plugins.utils.resourcedownloader', 'ResourceDownloaderFactory'),
('org.gudy.azureus2.plugins.utils.resourcedownloader', 'ResourceDownloaderListener'),
('org.gudy.azureus2.plugins.utils.resourceuploader', 'ResourceUploader'),
('org.gudy.azureus2.plugins.utils.resourceuploader', 'ResourceUploaderFactory'),
('org.gudy.azureus2.plugins.utils.security', 'CertificateListener'),
('org.gudy.azureus2.plugins.utils.security', 'PasswordListener'),
('org.gudy.azureus2.plugins.utils.security', 'SESecurityManager'),
('org.gudy.azureus2.plugins.utils.xml.rss', 'RSSChannel'),
('org.gudy.azureus2.plugins.utils.xml.rss', 'RSSFeed'),
('org.gudy.azureus2.plugins.utils.xml.rss', 'RSSItem'),
('org.gudy.azureus2.plugins.utils.xml.simpleparser', 'SimpleXMLParserDocument'),
('org.gudy.azureus2.plugins.utils.xml.simpleparser', 'SimpleXMLParserDocumentAttribute'),
('org.gudy.azureus2.plugins.utils.xml.simpleparser', 'SimpleXMLParserDocumentFactory'),
('org.gudy.azureus2.plugins.utils.xml.simpleparser', 'SimpleXMLParserDocumentNode'),
]
# Record the existance of the classes which are mentioned above.
# (We need this for lookups.)
_known_class_names = dict([(cls_tpl[1], None) for cls_tpl in azureus_class_list]).keys()
import dopal
if dopal.__dopal_mode__ == 1:
# Check we don't get any nameclashes.
if len(azureus_class_list) != len(_known_class_names):
raise RuntimeError, 'difference in class sizes'
# We do more to generate a nice docstring in epydoc mode.
if dopal.__dopal_mode__ == 2:
grouped_classes = {}
for package_name, class_name in azureus_class_list:
grouped_classes.setdefault(package_name, []).append(class_name)
ordered_grouped_packages = grouped_classes.keys()
ordered_grouped_packages.sort()
generated_lines = []
base_url = 'http://azureus.sourceforge.net/plugins/docCVS/'
package_tmpl = base_url + '%s/package-summary.html'
class_tmpl = base_url + '%s/%s.html'
for package_name in ordered_grouped_packages:
package_path = package_name.replace('.', '/')
full_package_url = package_tmpl % package_path
generated_lines.append(
'\n - Package C{U{%(package_name)s<%(full_package_url)s>}}' % vars()
)
for class_name in grouped_classes[package_name]:
full_class_url = class_tmpl % (package_path, class_name)
generated_lines.append(
' - Class C{U{%(class_name)s<%(full_class_url)s>}}' % vars()
)
__doc__ += "\n\nThe following classes are well-supported by DOPAL (the "
__doc__ += 'links below link to the Azureus\'s own '
__doc__ += 'U{Javadoc API documentation<%(base_url)s>}):\n' % vars()
__doc__ += '\n'.join(generated_lines)
del package_path, full_package_url, full_class_url
del base_url, package_tmpl, class_tmpl
del package_name, class_name, generated_lines
del grouped_classes, ordered_grouped_packages
del azureus_class_list
def is_azureus_class(class_name):
return class_name in _known_class_names
is_azureus_argument_class = is_azureus_class
def is_azureus_return_class(class_name):
if is_azureus_class(class_name):
return True
class_component_type = _get_component_type(class_name)
if class_component_type is not None:
if is_azureus_class(class_name):
return True
return False
Binary file not shown.
Binary file not shown.
+518
View File
@@ -0,0 +1,518 @@
# File: convert.py
# Library: DOPAL - DO Python Azureus Library
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details ( see the COPYING file ).
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
'''
Contains classes used to convert an XML structure back into an object
structure.
'''
# We disable the override checks (subclasses of mixins are allowed to have
# different signatures - arguments they want can be explicitly named, arguments
# they don't want can be left unnamed in kwargs).
#
# We also disable the class attribute checks - converter calls a lot of methods
# which are only defined in mixin classes.
__pychecker__ = 'unusednames=attributes,kwargs,object_id,value,self no-override no-classattr no-objattr'
import types
from dopal.aztypes import is_array_type, get_component_type, \
is_java_argument_type, is_java_return_type
from dopal.classes import is_azureus_argument_class, is_azureus_return_class
from dopal.errors import AbortConversion, DelayConversion, SkipConversion
from dopal.utils import Sentinel
ATOM_TYPE = Sentinel('atom')
SEQUENCE_TYPE = Sentinel('sequence')
MAPPING_TYPE = Sentinel('mapping')
OBJECT_TYPE = Sentinel('object')
NULL_TYPE = Sentinel('null')
del Sentinel
class Converter(object):
def __call__(self, value, result_type=None):
return self.convert(value, source_parent=None, target_parent=None,
attribute=None, sequence_index=None, suggested_type=result_type)
def convert(self, value, **kwargs):
# The keyword arguments we have here include data for the reader and
# the writer. We separate kwargs into three parts -
# 1) Reader-only values.
# 2) Writer-only values.
# 3) All keyword arguments.
reader_kwargs = kwargs.copy()
writer_kwargs = kwargs.copy()
convert_kwargs = kwargs
del kwargs
writer_kwargs['parent'] = writer_kwargs['target_parent']
reader_kwargs['parent'] = reader_kwargs['source_parent']
del reader_kwargs['target_parent']
del reader_kwargs['source_parent']
del writer_kwargs['target_parent']
del writer_kwargs['source_parent']
# Keyword arguments:
# attribute
# mapping_key
# sequence_index
# suggested_type
#
# parent (reader_kwargs and writer_kwargs, not in standard kwargs)
# source_parent (not in reader_kwargs, not in writer_kwargs)
# target_parent (not in reader_kwargs, not in writer_kwargs)
conversion_type = self.categorise_object(value, **reader_kwargs)
if conversion_type is NULL_TYPE:
return None
elif conversion_type is ATOM_TYPE:
atomic_value = self.get_atomic_value(value, **reader_kwargs)
return self.convert_atom(atomic_value, **writer_kwargs)
elif conversion_type is SEQUENCE_TYPE:
accepted_seq = []
rejected_seq = []
# The item we are currently looking at (value) is ignored.
# It is a normal sequence which doesn't contain any useful
# data, so we act as if each item in the sequence is
# actually an attribute of the source object (where the
# attribute name is taken from the attribute name of the
# list).
# Note - I would use enumerate, but I'm trying to leave this
# Python 2.2 compatible.
sequence_items = self.get_sequence_items(value, **reader_kwargs)
for i in range(len(sequence_items)):
item = sequence_items[i]
this_kwargs = convert_kwargs.copy()
this_kwargs['sequence_index'] = i
this_kwargs['suggested_type'] = self.get_suggested_type_for_sequence_component(value, **writer_kwargs)
try:
sub_element = self.convert(item, **this_kwargs)
except SkipConversion, error:
pass
except AbortConversion, error:
import sys
self.handle_error(item, error, sys.exc_info()[2])
rejected_seq.append((item, error, sys.exc_info()[2]))
else:
accepted_seq.append(sub_element)
del this_kwargs
if rejected_seq:
self.handle_errors(rejected_seq, accepted_seq, conversion_type)
return self.make_sequence(accepted_seq, **writer_kwargs)
elif conversion_type is MAPPING_TYPE:
accepted_dict = {}
rejected_dict = {}
for map_key, map_value in self.get_mapping_items(value, **reader_kwargs):
this_kwargs = convert_kwargs.copy()
this_kwargs['mapping_key'] = map_key
this_kwargs['suggested_type'] = self.get_suggested_type_for_mapping_component(value, **this_kwargs)
try:
converted_value = self.convert(map_value, **this_kwargs)
except SkipConversion, error:
pass
except AbortConversion, error:
import sys
self.handle_error(map_value, error, sys.exc_info()[2])
rejected_dict[map_key] = (map_value, error, sys.exc_info()[2])
else:
accepted_dict[map_key] = converted_value
del this_kwargs
if rejected_dict:
self.handle_errors(rejected_dict, accepted_dict, conversion_type)
return self.make_mapping(accepted_dict, **writer_kwargs)
elif conversion_type is OBJECT_TYPE:
object_id = self.get_object_id(value, **reader_kwargs)
source_attributes = self.get_object_attributes(value, **reader_kwargs)
# Try and convert the object attributes first.
#
# If we can't, because the parent object is requested, then
# we'll convert that first instead.
#
# If the code which converts the parent object requests that
# the attributes should be defined first, then we just exit
# with an error - we can't have attributes requesting that the
# object is converted first, and the object requesting attributes
# are converted first.
try:
attributes = self._get_object_attributes(value, None, source_attributes)
except DelayConversion:
# We will allow DelayConversions which arise from this block
# to propogate.
new_object = self.make_object(object_id, attributes=None, **writer_kwargs)
attributes = self._get_object_attributes(value, new_object, source_attributes)
self.add_attributes_to_object(attributes, new_object, **writer_kwargs)
else:
new_object = self.make_object(object_id, attributes, **writer_kwargs)
return new_object
else:
raise ValueError, "bad result from categorise_object: %s" % conversion_type
def _get_object_attributes(self, value, parent, source_attributes):
accepted = {}
rejected = {}
for attribute_name, attribute_value in source_attributes.items():
this_kwargs = {}
this_kwargs['source_parent'] = value
this_kwargs['target_parent'] = parent
this_kwargs['attribute'] = attribute_name
this_kwargs['mapping_key'] = None
this_kwargs['sequence_index'] = None
this_kwargs['suggested_type'] = self.get_suggested_type_for_attribute(value=attribute_value, parent=parent,
attribute=attribute_name,
mapping_key=None)
try:
converted_value = self.convert(attribute_value, **this_kwargs)
except SkipConversion, error:
pass
except AbortConversion, error:
import sys
self.handle_error(attribute_value, error, sys.exc_info()[2])
rejected[attribute_name] = (attribute_value, error, sys.exc_info()[2])
else:
accepted[attribute_name] = converted_value
if rejected:
self.handle_errors(rejected, accepted, OBJECT_TYPE)
return accepted
def handle_errors(self, rejected, accepted, conversion_type):
if isinstance(rejected, dict):
error_seq = rejected.itervalues()
else:
error_seq = iter(rejected)
attribute, error, traceback = error_seq.next()
raise error, None, traceback
def handle_error(self, object_, error, traceback):
raise error, None, traceback
class ReaderMixin(object):
# Need to be implemented by subclasses:
#
# def categorise_object(self, value, suggested_type, **kwargs):
# def get_object_id(self, value, **kwargs):
# def get_object_attributes(self, value, **kwargs):
# You can raise DelayConversion here.
def get_atomic_value(self, value, **kwargs):
return value
def get_sequence_items(self, value, **kwargs):
return value
def get_mapping_items(self, value, **kwargs):
return value
class WriterMixin(object):
# Need to be implemented by subclasses:
#
# def make_object(self, object_id, attributes, **kwargs):
# You can raise DelayConversion here.
def convert_atom(self, atomic_value, suggested_type, **kwargs):
if suggested_type is None:
return atomic_value
else:
from dopal.aztypes import unwrap_value
return unwrap_value(atomic_value, suggested_type)
def make_sequence(self, sequence, **kwargs):
return sequence
def make_mapping(self, mapping, **kwargs):
return mapping
def add_attributes_to_object(self, attributes, new_object, **kwargs):
new_object.update_remote_data(attributes)
def get_suggested_type_for_sequence_component(self, value, **kwargs):
return None
def get_suggested_type_for_mapping_component(self, value, **kwargs):
return None
def get_suggested_type_for_attribute(self, value, **kwargs):
return None
class XMLStructureReader(ReaderMixin):
def categorise_object(self, node, suggested_type, **kwargs):
from xml.dom import Node
if node is None:
number_of_nodes = 0
null_value = True
elif isinstance(node, types.StringTypes):
number_of_nodes = -1 # Means "no-node type".
null_value = not node
else:
number_of_nodes = len(node.childNodes)
null_value = not number_of_nodes
# This is a bit ambiguous - how on earth are we meant to determine
# this? We'll see whether an explicit type is given here, otherwise
# we'll have to just guess.
if null_value:
if suggested_type == 'mapping':
return MAPPING_TYPE
elif is_array_type(suggested_type):
return SEQUENCE_TYPE
# If the suggested type is atomic, then we inform them that
# it is an atomic object. Some atomic types make sense with
# no nodes (like an empty string). Some don't, of course
# (like an integer), but never mind. It's better to inform
# the caller code that it is an atom if the desired type is
# an atom - otherwise for empty strings, we will get None
# instead.
elif is_java_return_type(suggested_type):
# We'll assume it is just an atom. It can't be an object
# without an object ID.
return ATOM_TYPE
# Oh well, let's just say it's null then.
else:
return NULL_TYPE
if number_of_nodes == -1:
return ATOM_TYPE
if number_of_nodes == 1 and node.firstChild.nodeType == Node.TEXT_NODE:
return ATOM_TYPE
if number_of_nodes and node.firstChild.nodeName == 'ENTRY':
return SEQUENCE_TYPE
if suggested_type == 'mapping':
return MAPPING_TYPE
return OBJECT_TYPE
def get_atomic_value(self, node, **kwargs):
if node is None:
# The only atomic type which provides an empty response are
# string types, so we will return an empty string.
return ''
elif isinstance(node, types.StringTypes):
return node
else:
from dopal.xmlutils import get_text_content
return get_text_content(node)
def get_sequence_items(self, node, **kwargs):
if node is None:
return []
return node.childNodes
def get_mapping_items(self, node, **kwargs):
if node is None:
return {}
# Not actually used, but just in case...
result_dict = {}
for child_node in node.childNodes:
if result_dict.has_key(child_node.nodeName):
raise AbortConversion("duplicate attribute - " + child_node.nodeName, child_node)
result_dict[child_node.nodeName] = child_node
return result_dict
def get_object_id(node, **kwargs):
if node is None:
return None
for child_node in node.childNodes:
if child_node.nodeName == '_object_id':
from dopal.xmlutils import get_text_content
return long(get_text_content(child_node))
else:
return None
# Used by StructuredResponse.get_object_id, so we make it static.
get_object_id = staticmethod(get_object_id)
def get_object_attributes(self, node, **kwargs):
result_dict = self.get_mapping_items(node, **kwargs)
for key in result_dict.keys():
if key.startswith('azureus_'):
del result_dict[key]
elif key in ['_connection_id', '_object_id']:
del result_dict[key]
return result_dict
class ObjectWriterMixin(WriterMixin):
connection = None
# attributes may be None if not defined at this point.
#
# You can raise DelayConversion here.
def make_object(self, object_id, attributes, suggested_type=None, parent=None, attribute=None, **kwargs):
class_to_use = None
if suggested_type is not None:
class_to_use = self.get_class_for_object(suggested_type, attributes, parent, attribute)
if class_to_use is None:
class_to_use = self.get_default_class_for_object()
if class_to_use is None:
# - Drop the attribute.
# - Put the attribute as is (convert it into a typeless
# object)
# - Raise an error.
#
# For now, we'll avoid creating the attribute altogether.
#
# Note - if the object has no parent, then that's a more
# serious situation. We may actually be returning a blank
# value instead of a representive object - in my opinion,
# it is better to fail in these cases.
#
# A broken object (missing attributes) is more desirable than
# having an object missing entirely if it is the actual object
# being returned.
if parent is None:
cls_to_use = AbortConversion
else:
cls_to_use = SkipConversion
raise cls_to_use(text="no default class defined by converter", obj=(parent, attribute, suggested_type))
# Alternative error-based code to use:
#
# err_kwargs = {}
# err_kwargs['obj'] = suggested_type
# if parent is None:
# if attribute is None:
# pass
# else:
# err_kwargs['text'] = 'attr=' + attribute
# else:
# err_kwargs['text'] = "%(parent)r.%(attribute)s" %
# locals()
# raise InvalidRemoteClassTypeError(**err_kwargs)
result = class_to_use(self.connection, object_id)
if attributes is not None:
self.add_attributes_to_object(attributes, result)
return result
def get_class_for_object(self, suggested_type, attributes=None, parent=None, attribute=None):
return None
def get_default_class_for_object(self):
return None
class RemoteObjectWriterMixin(ObjectWriterMixin):
class_map = {}
# XXX: This will need to be changed to something which will:
# - If true, raise an error if the parent does not return an appropriate
# type for any given attribute.
# - If false, will never complain.
# - If none (default), complain only when debug mode is on.
force_attribute_types = False
def get_class_for_object(self, suggested_type, attributes=None, parent=None, attribute=None):
if suggested_type is None:
return None
return self.class_map.get(suggested_type, None)
def get_default_class_for_object(self):
if self.class_map.has_key(None):
return self.class_map[None]
_super = super(RemoteObjectWriterMixin, self)
return _super.get_default_class_for_object()
def get_suggested_type_for_sequence_component(self, value, suggested_type, **kwargs):
if suggested_type is None:
return None
if is_array_type(suggested_type):
return get_component_type(suggested_type)
else:
raise AbortConversion("parent of value is a sequence, but the suggested type is not an array type",
obj=value)
def _get_suggested_type_for_named_item(self, value, parent, attribute, mapping_key=None, **kwargs):
if parent is None:
raise DelayConversion
result_type = None
if hasattr(parent, '_get_type_for_attribute'):
result_type = parent._get_type_for_attribute(attribute, mapping_key)
if self.force_attribute_types and result_type is None:
txt = "%(parent)r could not provide type for '%(attribute)s'"
if mapping_key is not None:
txt += ", [%(mapping_key)s]"
raise AbortConversion(txt % locals())
return result_type
get_suggested_type_for_mapping_component = \
get_suggested_type_for_attribute = _get_suggested_type_for_named_item
class RemoteObjectConverter(Converter,
XMLStructureReader, RemoteObjectWriterMixin):
def __init__(self, connection=None):
super(Converter, self).__init__()
self.connection = connection
def is_azureus_argument_type(java_type):
return is_java_argument_type(java_type) or \
is_azureus_argument_class(java_type)
def is_azureus_return_type(java_type):
return is_java_return_type(java_type) or \
is_azureus_return_class(java_type)
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large. Load diff
Binary file not shown.
Binary file not shown.
+177
View File
@@ -0,0 +1,177 @@
# File: debug.py
# Library: DOPAL - DO Python Azureus Library
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details ( see the COPYING file ).
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
'''
Contains functions and objects useful for debugging DOPAL.
'''
class DebugObject(object):
pass
class LinkDebugObject(DebugObject):
pass
class ErrorLinkDebug(LinkDebugObject):
def __init__(self, cgi_path, error):
self.cgi_path = cgi_path
self.error = error
class OutgoingExchangeDebug(LinkDebugObject):
def __init__(self, cgi_path, data_to_send):
self.cgi_path = cgi_path
self.data_to_send = data_to_send
class ConnectionExchangeDebug(LinkDebugObject):
def __init__(self, cgi_path, data_sent, data_received):
self.cgi_path = cgi_path
self.data_sent = data_sent
self.data_received = data_received
class ConnectionDebugObject(DebugObject):
pass
class MethodRequestDebug(ConnectionDebugObject):
def __init__(self, object_id, request_method):
self.object_id = object_id
self.request_method = request_method
class MethodResponseDebug(ConnectionDebugObject):
def __init__(self, object_id, request_method, response):
self.object_id = object_id
self.request_method = request_method
self.response = response
def print_everything(debug_object):
if not isinstance(debug_object, LinkDebugObject):
return
print
print '---------------'
print
if isinstance(debug_object, OutgoingExchangeDebug):
print 'Sending to "%s"' % debug_object.cgi_path
print
print debug_object.data_to_send
elif isinstance(debug_object, ConnectionExchangeDebug):
print 'Recieved from "%s"' % debug_object.cgi_path
print
print debug_object.data_received
elif isinstance(debug_object, ErrorLinkDebug):
error = debug_object.error
print 'Error from "%s"' % debug_object.cgi_path
print
print '%s: %s' % (error.__class__.__name__, error)
print
print '---------------'
print
def print_everything_with_stack(debug_object):
if isinstance(debug_object, OutgoingExchangeDebug):
import traceback
print
print '---------------'
print
print 'Stack trace of request:'
traceback.print_stack()
print
print '---------------'
print
print_everything(debug_object)
def print_method(debug_object):
from dopal.utils import make_short_object_id as _sid
if isinstance(debug_object, MethodRequestDebug):
print
print '---------------'
print ' Object:', debug_object.object_id,
if debug_object.object_id is not None:
print "[sid=%s]" % _sid(debug_object.object_id),
print
print ' Method:', debug_object.request_method
print
elif isinstance(debug_object, MethodResponseDebug):
import dopal.core
if isinstance(debug_object.response, dopal.core.ErrorResponse):
print ' Response Type: ERROR'
print ' Response Data:', debug_object.response.response_data
elif isinstance(debug_object.response, dopal.core.AtomicResponse):
print ' Response Type: VALUE'
print ' Response Data:', debug_object.response.response_data
elif isinstance(debug_object.response, dopal.core.NullResponse):
print ' Response Type: NULL / EMPTY'
print ' Response Data: None'
elif isinstance(debug_object.response, dopal.core.StructuredResponse):
print ' Response Type: STRUCTURE'
print ' Response Data:',
obj_id = debug_object.response.get_object_id()
if obj_id is not None:
print 'Object [id=%s, sid=%s]' % (obj_id, _sid(obj_id))
else:
print 'Non-object value'
print '---------------'
print
def print_method_with_stack(debug_object):
if isinstance(debug_object, MethodResponseDebug):
import traceback
print
print '---------------'
print
print 'Stack trace of request:'
traceback.print_stack()
print
print '---------------'
print
print_method(debug_object)
class DebugGrabber(object):
debug_object = None
def get_in(self):
if self.debug_object is None:
raise Exception, "not captured any data yet"
return self.debug_object.data_sent
def get_out(self):
if self.debug_object is None:
raise Exception, "not captured any data yet"
return self.debug_object.data_received
def __call__(self, debug_object):
if isinstance(debug_object, ConnectionExchangeDebug):
self.debug_object = debug_object
Binary file not shown.
Binary file not shown.
+515
View File
@@ -0,0 +1,515 @@
# File: errors.py
# Library: DOPAL - DO Python Azureus Library
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details ( see the COPYING file ).
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
'''
Module containing all errors defined in DOPAL.
'''
def as_error(error, error_class, **kwargs):
if not isinstance(error, error_class):
error = error_class(error=error, **kwargs)
return error
def raise_as(error, error_class, **kwargs):
import sys
raise as_error(error, error_class, **kwargs), None, sys.exc_info()[2]
class DopalError(Exception):
"Subclass of all errors in the Dopal library"
def __init__(self, *args, **kwargs):
if len(args) + len(kwargs) > 3:
raise ValueError, "DopalError.__init__ takes at most 3 arguments - %s positional argument(s) given, %s keyword argument(s) given" % (
len(args), len(kwargs))
# Filters out invalid keywords.
from dopal.utils import handle_kwargs
handle_kwargs(kwargs, error=None, obj=None, text=None)
error = obj = text = None
has_error = has_object = has_text = False
import types
for kwname, kwvalue in kwargs.items():
if kwname == 'error':
if not isinstance(kwvalue, Exception):
msg = "'error' keyword argument is not Exception: %r"
raise TypeError, msg % (kwvalue,)
has_error = True
error = kwvalue
elif kwname == 'text':
if not isinstance(kwvalue, types.StringTypes):
msg = "'text' keyword argument is not a String type: %r"
raise TypeError, msg % (kwvalue,)
has_text = True
text = kwvalue
else: # kwname == 'obj'
has_object = True
obj = kwvalue
import types
for arg in args:
if isinstance(arg, Exception) and not has_error:
has_error = True
error = arg
elif isinstance(arg, types.StringTypes) and not has_text:
has_text = True
text = arg
else:
if has_object:
msg = "could not determine Dopal argument type for %r"
raise TypeError, msg % (arg,)
has_object = True
obj = arg
dopal_arg_tuple = args
if kwargs:
dopal_arg_tuple += tuple(kwargs.values())
dopal_arg_dict = {}
if has_error:
dopal_arg_dict['error'] = error
if has_object:
dopal_arg_dict['object'] = obj
if has_text:
dopal_arg_dict['text'] = text
self.dopal_arg_tuple = dopal_arg_tuple
self.dopal_arg_dict = dopal_arg_dict
self.error = error
self.obj = obj
self.text = text
self.has_object = has_object
self.has_error = has_error
self.has_text = has_text
# super(DopalError, self).__init__(dopal_arg_tuple)
Exception.__init__(self, *dopal_arg_tuple)
def __str__(self):
# Allow the subclass to render the string if:
# 1) self.args is not the tuple that this class passed to the super
# constructor; or
# 2) We have 2 or more values given to us - the default behaviour for
# rendering the string by the superclass for one or no arguments
# is fine.
if self.args != self.dopal_arg_tuple or \
len(self.args) < 2:
#return super(DopalError, self).__str__()
return Exception.__str__(self)
if not self.has_error:
tmpl = "%(text)s (%(object)r)"
elif not self.has_object:
tmpl = "%(text)s - %(error)s"
elif not self.has_text:
tmpl = "%(error)s (%(object)r)"
else:
tmpl = "%(text)s - %(error)s (%(object)r)"
return tmpl % self.dopal_arg_dict
def __repr__(self):
arg_parts = ["%s=%r" % item_tpl for item_tpl in self.dopal_arg_dict.items()]
return "%s(%s)" % (self.__class__.__name__, ', '.join(arg_parts))
# An alternative to str() of the object - this will make a string of the
# form:
# ErrorClassName: <error output>
#
# (This is similar to the last line you see of a traceback). The main
# difference here is that we will use the class name of the internal error
# if there is one, otherwise we will use the class name of the object
# itself.
#
# The error output will be the same string as you get when you apply str()
# to this object.
#
# Setting use_error to False will force it to always ignore the internal
# error.
def to_error_string(self, use_error=True):
if use_error and self.has_error:
error_to_use = self.error
else:
error_to_use = self
error_output = str(self)
result = error_to_use.__class__.__name__
if error_output:
result += ": " + error_output
return result
# ---- core module ----#
class LinkError(DopalError):
"Error communicating with Azureus (low-level)"
class RemoteError(DopalError): # Base class.
"Error reported by Azureus"
class RemoteInvocationError(RemoteError): # Base class.
"Unable to invoke remote method"
class NoSuchMethodError(RemoteInvocationError):
"""
This error is thrown when Azureus reports that the requested method does
not exist, or is not allowed.
A NoSuchMethodError is a representation of the response Azureus returns
when trying to invoke a method, but is unable to map the requested method
to a method it can actually invoke.
Causes
======
There are various reasons why this error may occur, but here are the
most likely.
Wrong method requested
----------------------
The wrong method signature was used - this is possible for a variety
of reasons (though it isn't likely). Check that the method you want to
use is the same one being reported in the NoSuchMethodError instance.
Method not available in this version of Azureus
-----------------------------------------------
This method may not be available in the version of Azureus you are
using - although DOPAL normally supports all methods made available in
the latest beta release, this error will occur if the version of Azureus
does not support that method.
XML/HTTP request processor may not support this method
------------------------------------------------------
The request processor used by Azureus may not be able to resolve that
method. Versions 2.3.0.6 and older only allow a small subset of methods
defined in the plugin API to be called. Version 2.4.0.0 (as well as some
later beta versions of 2.3.0.7) have been changed to allow any method to
be called. To enable this, go to the XML/HTTP plugin configuration page,
and tick the I{"Advanced Settings -> Use generic classes"} setting.
Non read-only method requested, but XML/HTTP in view mode
---------------------------------------------------------
The XML/HTTP plugin in Azureus is set up to be in "view" mode, so only
certain methods are allowed. Note - if you are unable to call a method
which you think should be allowed in read only mode, contact the
developers of the XML/HTTP plugin.
@ivar obj: This will be a string which describes the method signature
which was requested - for example::
getDownloads
setPosition[int]
setTorrentAttribute[TorrentAttribute,String]
"""
def __init__(self, method_sig):
"""
Creates a new NoSuchMethodError instance.
"""
RemoteInvocationError.__init__(self, obj=method_sig)
class NoObjectIDGivenError(DopalError, ValueError):
"No object ID given when needed"
class NoEstablishedConnectionError(DopalError, TypeError):
"Connection object has no valid connection established"
# Raised by generate_remote_error (which means it is indirectly raised in
# AzureusConnection.invoke_remote_method).
#
# These errors are masked by ExtendedAzureusConnection.invoke_remote_method
# who throw one of the subclass errors (InvalidRemoteObjectError and
# InvalidConnectionIDError).
#
# This error shouldn't arise if you are using a ExtendedAzureusConnection or
# higher-level connection object.
class InvalidObjectIDError(RemoteInvocationError):
"Invalid remote object ID given (bad object or bad connection)"
class InvalidRemoteObjectError(InvalidObjectIDError):
"Invalid remote object ID used"
class InvalidConnectionIDError(InvalidObjectIDError):
"Invalid connection ID used"
class MissingObjectIDError(RemoteInvocationError):
"Missing object ID"
# Raised by generate_remote_error (which means it is indirectly raised in
# AzureusConnection.invoke_remote_method).
#
# Higher-level connections (like AzureusObjectConnection) may raise subclasses
# of this error, if they are able to give a more precise error can be
# determined.
class RemoteMethodError(RemoteError):
"Error thrown by remote method"
class RemoteInternalError(RemoteError):
"Internal error occurred during remote method invocation"
class AzureusResponseXMLError(DopalError):
"Error while parsing XML returned by Azureus"
#---- core module ----#
#---- types module ----#
class ConversionError(DopalError): # Base class.
"Error converting value (Azureus <--> Python)"
class WrapError(ConversionError):
"Error converting value to remote method argument"
class UnwrapError(ConversionError):
"Error converting remote method result to Python value"
class InvalidWrapTypeError(WrapError, TypeError):
'''
Invalid wrap type given.
This error is raised when a value is passed which cannot be converted into
something that can be represented in Azureus. This either means that the
value doesn't meet the criteria as something which can be represented, or
the value doesn't fit the type that it is being wrapped as (e.g. a
non-integer string as a integer).
@see: L{wrap_value<dopal.aztypes.wrap_value>}
@see: L{remote_method_call_to_xml<dopal.core.remote_method_call_to_xml>}
'''
class InvalidUnwrapTypeError(UnwrapError, TypeError):
"Invalid unwrap type given."
class InconsistentWrapTypeError(WrapError, TypeError):
"Object has wrap type different to requested type"
#---- types module ----#
#---- types (AzMethod) module ----#
class AzMethodError(DopalError): # Base class.
"Error selecting matching AzMethod"
def __init__(self, obj, *args, **kwargs):
kwargs['obj'] = obj
self.method_name = obj
DopalError.__init__(self, *args, **kwargs)
class IncorrectArgumentCountError(AzMethodError, TypeError):
"Wrong number of arguments given for AzMethod"
def __init__(self, obj, given_arg_count, required_arg_count):
TypeError.__init__(self)
# self.required_arg_count is a list
# required_count is used for the 'text' variable
self.given_arg_count = given_arg_count
if isinstance(required_arg_count, (int, long)):
self.required_arg_count = [required_arg_count]
required_count = required_arg_count
elif len(required_arg_count) == 1:
self.required_arg_count = required_arg_count
required_count = required_arg_count[0]
else:
self.required_arg_count = required_arg_count
required_count = required_arg_count
required_count = list(required_count)
required_count.sort()
text = "wrong number of arguments given (wanted %(required_count)s, given %(given_arg_count)s)" % locals()
AzMethodError.__init__(self, obj, text=text)
class ArgumentWrapError(AzMethodError):
"Error wrapping argument for AzMethod"
def __init__(self, arg_index, value, arg_type, error):
text = "error converting arg %(arg_index)s to %(arg_type)s" % locals()
AzMethodError.__init__(self, obj=value, error=error, text=text)
self.arg_index = arg_index
self.arg_type = arg_type
class NoSuchAzMethodError(AzMethodError, AttributeError):
"No method of that name available"
def __init__(self, *args, **kwargs):
AttributeError.__init__(self)
AzMethodError.__init__(self, *args, **kwargs)
class MethodArgumentWrapError(AzMethodError):
"Error wrapping argument for multiple AzMethods"
def __init__(self, name, invocation_errors):
AzMethodError.__init__(self, name)
self.invocation_errors = invocation_errors
def __str__(self):
text = "Error wrapping arguments:"
error_data = [(str(method_data), str(error.__class__.__name__), str(error)) for (method_data, error) in
self.invocation_errors]
error_data.sort()
for method_data, err_class, error in error_data:
text += "\n %(method_data)s - %(err_class)s: %(error)s" % locals()
return text
#---- types (AzMethod) module ----#
#---- objects module ----#
class ConnectionlessObjectError(DopalError):
"Object has no remote connection"
class NonRefreshableObjectError(DopalError): # Base class
"Object cannot be refreshed - refresh not implemented"
class NonRefreshableConnectionlessObjectError(NonRefreshableObjectError, ConnectionlessObjectError):
"Object cannot be refreshed - no connection attached"
def __init__(self, *args, **kwargs):
NonRefreshableObjectError.__init__(self)
ConnectionlessObjectError.__init__(self, *args, **kwargs)
#_superclass = super(NonRefreshableConnectionlessObjectError, self)
#_superclass.__init__(*args, **kwargs)
class NonRefreshableObjectTypeError(NonRefreshableObjectError):
"Object cannot be refreshed - not implemented for this type"
class NonRefreshableIncompleteObjectError(NonRefreshableObjectError):
"Object cannot be refreshed - insufficient information on object"
class StaleObjectReferenceError(NonRefreshableObjectError):
"Object used belongs to old connection, which doesn't have persistency enabled"
class MissingRemoteAttributeError(DopalError, AttributeError):
"Object does not have remote attribute available"
#---- objects module ----#
#---- convert module ----#
class InvalidRemoteClassTypeError(DopalError, TypeError):
"Invalid remote class type given"
# Base exception class - used when something cannot be converted.
class StructureConversionError(ConversionError): # Base class.
"Error converting response structure"
# Base class for flow control exceptions.
class ConversionControl(StructureConversionError): # Base class.
"Base class for structured conversion control"
# Use this class if you want to skip converting the object which
# is being handled.
class SkipConversion(ConversionControl):
"Structured conversion of object skipped"
# Use this class if you want to stop converting the object which
# is being handled (essentially signalling a somewhat "fatal" error).
class AbortConversion(ConversionControl):
"Structured conversion of object aborted"
# Use this class if you want to signal that you need more information
# before you can proceed with the conversion - either that you need
# the items lower down to be converted first, or you need the items
# higher up converted first.
class DelayConversion(ConversionControl):
"Structured conversion of object delayed"
# Use this class if you want to halt conversion completely - this is a more
# severe form of AbortConversion, where it won't be passed to
# Converter.handle_errors.
class HaltConversion(ConversionControl):
"Structured conversion of object halted"
class DopalDeprecationWarning(DeprecationWarning, DopalError):
pass
# PendingDeprecationWarning class doesn't exist in Python 2.2.
try:
class DopalPendingDeprecationWarning(DopalDeprecationWarning, PendingDeprecationWarning):
pass
except NameError:
class DopalPendingDeprecationWarning(DopalDeprecationWarning):
pass
class NoDefaultScriptConnectionError(DopalError):
pass
class ScriptFunctionError(DopalError):
"Error occurred inside script function."
Binary file not shown.
Binary file not shown.
+100
View File
@@ -0,0 +1,100 @@
# File: interact.py
# Library: DOPAL - DO Python Azureus Library
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details ( see the COPYING file ).
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
'''
Interactive Python application which initialises DOPAL to connect with a chosen
Azureus server.
'''
def main():
'''Function to invoke this application.'''
# Get host and port.
connection_details = {}
connection_details['host'] = raw_input('Enter host: ')
port_text = raw_input('Enter port (default is 6884): ')
if port_text:
connection_details['port'] = int(port_text)
# Username and password.
username = raw_input('Enter user name (leave blank if not applicable): ')
password = None
if username:
import getpass
connection_details['user'] = username
connection_details['password'] = getpass.getpass('Enter password: ')
my_locals = {}
from dopal.main import make_connection
connection = make_connection(**connection_details)
connection.is_persistent_connection = True
from dopal.errors import LinkError
try:
interface = connection.get_plugin_interface()
except LinkError, error:
interface = None
connection_error = error
else:
connection_error = None
from dopal import __version_str__
banner = "DOPAL %s - interact module\n\n" % __version_str__
banner += "Connection object stored in 'connection' variable.\n"
if connection_error is None:
banner += "Plugin interface stored in 'interface' variable.\n"
else:
banner += "\nError getting plugin interface object - could not connect to Azureus, error:\n %s" % connection_error.to_error_string()
import dopal
if dopal.__dopal_mode__ == 1:
banner += "\nRunning in DEBUG mode.\n"
elif dopal.__dopal_mode__ == 2:
banner += '\nWARNING: Running in "epydoc" mode.\n'
my_locals['connection'] = connection
if interface is not None:
my_locals['interface'] = interface
my_locals['__import__'] = __import__
print
print '------------------------'
print
import code
code.interact(banner, local=my_locals)
if __name__ == '__main__':
def _main(env):
return main()
import dopal.scripting
dopal.scripting.ext_run(
'dopal.interact', _main,
make_connection=False,
setup_logging=False,
timeout=8,
pause_on_exit=2,
)
Binary file not shown.
+35
View File
@@ -0,0 +1,35 @@
# File: logutils.py
# Library: DOPAL - DO Python Azureus Library
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details ( see the COPYING file ).
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
'''
Module containing various logging-related utilities and classes.
'''
# From:
# http://news.hping.org/comp.lang.python.archive/19937.html
def noConfig():
'''
This function should be called to indicate that you are explicitly
notifying the logging module that you do not intend to add any
handlers to the root logger.
This suppresses the warning C{No handlers could be found for logger
"root"} from being emitted. This function performs the following
call to disable the warning::
logging.root.manager.emittedNoHandlerWarning = True
'''
import logging
logging.root.manager.emittedNoHandlerWarning = True
+84
View File
@@ -0,0 +1,84 @@
# File: main.py
# Library: DOPAL - DO Python Azureus Library
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details ( see the COPYING file ).
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
'''
Main module for using DOPAL with little effort.
'''
def make_connection(persistent=False, **kwargs):
'''
Generate a L{DopalConnection} to an Azureus server, whose location is
specified by keyword arguments.
To see what keywords are accepted, see the
L{set_link_details<dopal.core.AzureusLink.set_link_details>} method. This
method also takes an additional keyword - C{persistent}, which determines
whether the connection should be persistent or not (by default, it is not
persistent).
This function will return a DopalConnection instance.
@rtype: L{DopalConnection}
@see: L{set_link_details<dopal.core.AzureusLink.set_link_details>}
'''
connection = DopalConnection()
connection.set_link_details(**kwargs)
if not persistent:
connection.is_persistent_connection = False
return connection
from dopal.objects import AzureusObjectConnection
class DopalConnection(AzureusObjectConnection):
'''
A subclass of
L{AzureusObjectConnection<dopal.objects.AzureusObjectConnection>} which
contains an extended API.
This class defines an extended API, similar to the way that C{Dopal}
classes contain additional methods compared to their C{Azureus}
counterparts. It also sets up some different default behaviours (compared
to L{AzureusObjectConnection<dopal.objects.AzureusObjectConnection>}):
- All instances are I{persistent} connections by default.
- A L{RemoteObjectConverter<dopal.convert.RemoteObjectConverter>}
instance is installed as the default handler for converting XML to
its appropriate object representation.
- The L{DOPAL class map<dopal.obj_impl.DOPAL_CLASS_MAP>} is used as the
standard class mapping.
@see: The L{obj_impl<dopal.obj_impl>} module documentation.
'''
def __init__(self):
super(DopalConnection, self).__init__()
from dopal.convert import RemoteObjectConverter
converter = RemoteObjectConverter(self)
from dopal.obj_impl import DOPAL_CLASS_MAP
converter.class_map = DOPAL_CLASS_MAP
self.converter = converter
self.is_persistent_connection = True
del AzureusObjectConnection
Binary file not shown.
Binary file not shown.
+321
View File
@@ -0,0 +1,321 @@
# File: obj_impl.py
# Library: DOPAL - DO Python Azureus Library
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details ( see the COPYING file ).
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
'''
Implementation of classes defined by Azureus's plugin API.
Not all classes are defined here are mentioned in the API documentation (simply
because too much documentation will be generated).
For each class that DOPAL has explicit support for, there will be two classes
defined in this module. The list of classes supported in this version is
described in L{classes}.
For each class supported, there will be a class named
I{AzureusE{<}classnameE{>}}, and another class named I{DopalE{<}classnameE{>}}.
The I{Azureus*} class is a subclass of L{AzureusObject} mixed in with the
I{*DataType} class in the L{class_defs} module - the API closely resembles
that of the actual object in Azureus.
The I{Dopal*} class is a subclass of the I{Azureus*} class, mixed in with the
L{DopalObjectMixin} class. These classes exist to define an extended API of
convenience functions beyond the API supplied by Azureus itself. Although all
plugin classes have a I{Dopal*} representation, only those classes mentioned
in the API documentation have any extended behaviour defined for them.
@group Standard class implementations: %(standard_classes)s
@group DOPAL class implementation: %(dopal_classes)s
'''
import dopal.class_defs as _cdefs
from dopal.objects import AzureusObject, TypelessRemoteObject
from dopal.errors import MissingRemoteAttributeError
# Temporary value - should be removed once the import has finished.
import dopal
__epydoc_mode = dopal.__dopal_mode__ == 2
del dopal
# Imported just for the __str__ method of DopalObjectMixin.
import sys
# The code here is used to create representive classes of each of the remote
# Azureus class types that we support.
import new
def _make_class(common_cls, data_type_cls, name_prefix, class_map_dict=None):
az_class_name = data_type_cls.get_xml_type()
new_class_name = name_prefix + az_class_name
# Is the class already defined in the global namespace? If so - we
# avoid defining it again.
if globals().has_key(new_class_name):
classobj = globals()[new_class_name]
else:
base_classes = (common_cls, data_type_cls)
classobj = new.classobj(new_class_name, base_classes, {})
del base_classes
globals()[new_class_name] = classobj
if __epydoc_mode:
classobj.__plugin_class__ = True
if class_map_dict is not None:
class_map_dict[az_class_name] = classobj
return classobj
# The two class maps we provide by default.
STANDARD_CLASS_MAP = {}
DOPAL_CLASS_MAP = {}
# These methods are common on all DOPAL variants of classes we create.
class DopalObjectMixin:
# Used by repr.
def short_description(self):
try:
result = self._short_description()
except MissingRemoteAttributeError:
result = None
if result is None:
return ''
return result
# Used by str.
def full_description(self):
try:
result = self._full_description()
except MissingRemoteAttributeError:
result = None
if result is None:
return ''
return result
def _short_description(self):
return None
def _full_description(self):
return self.short_description()
def __str__(self): # DopalObjectMixin
'''
Generates a string representation of this object - the value of this
string will be the result returned by the L{__unicode__} method.
Note - this method should return a string which is appropriate for
the system's encoding (so C{UnicodeEncodeError}s should not occur), but
it makes no guarantee I{how} it will do this.
As of DOPAL 0.60, it encodes the string using the default system
encoding, using 'replace' as the default way to handle encoding
problems.
'''
# What should be the default behaviour?
#
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/466341
#
# 1) Use encoding - "raw_unicode_escape".
# 2) Use error handler - "replace" (current).
# 3) Use error handler - "ignore".
return unicode(self).encode(sys.getdefaultencoding(), 'replace')
def __unicode__(self):
'''
Generates a text representation of this object. If the
L{full_description} returns a useful representation, then the string
will have this format::
RemoteTypeName: FullDescriptionString
Otherwise, it will resort to the superclass string representation.
Example::
Download: The Subways - Staring at the Sun.mp3 [Stopped, 100.0%]
'''
nice_name = self.full_description()
if nice_name:
result = "%s: %s" % (self.get_remote_type(), nice_name)
else:
result = AzureusObject.__str__(self)
try:
return unicode(result)
# Python 2.2 doesn't define UnicodeDecodeError, we have to use
# UnicodeError.
except UnicodeError, error:
# string_escape only defined in Python 2.3.
if sys.version_info >= (2, 3):
return unicode(result.encode('string_escape'))
else:
return unicode(AzureusObject.__str__(self))
#
def __repr__(self):
nice_name = self.short_description()
repr_string = AzureusObject.__repr__(self)
if nice_name:
if repr_string[-1:] == ">":
repr_string = repr_string[:-1] + \
', for "%s">' % nice_name
return repr_string
class DopalObjectStatsMixin(DopalObjectMixin):
def _short_description(self):
return "S:%s P:%s" % (self.seed_count, self.non_seed_count)
def _full_description(self):
return "Seeds: %s, Peers: %s" % (self.seed_count, self.non_seed_count)
# Some classes which are basically stat counts share these methods.
# Now we create the classes - the standard variants first, then the DOPAL
# enhanced variants afterwards.
#
# The DOPAL variants are only automatically generated if we haven't defined
# them manually. We only define them manually if we have methods we want
# to define on them.
for az_class in _cdefs._class_map.values():
_make_class(AzureusObject, az_class, 'Azureus', STANDARD_CLASS_MAP)
del az_class
#
#
# We've now created all the classes we wanted. We now define extra methods
# on particular classes we care about.
#
#
# Now we declare DOPAL variants of these classes - these classes will end up
# providing a richer API than just the standard plugin classes.
class DopalPluginConfig(DopalObjectMixin, AzureusPluginConfig):
def get_upload_speed_limit(self):
return self.getIntParameter(self.CORE_PARAM_INT_MAX_UPLOAD_SPEED_KBYTES_PER_SEC, 0)
def get_download_speed_limit(self):
return self.getIntParameter(self.CORE_PARAM_INT_MAX_DOWNLOAD_SPEED_KBYTES_PER_SEC, 0)
def set_upload_speed_limit(self, limit):
if limit is None:
limit = 0
self.setIntParameter(self.CORE_PARAM_INT_MAX_UPLOAD_SPEED_KBYTES_PER_SEC, limit)
def set_download_speed_limit(self, limit):
if limit is None:
limit = 0
self.setIntParameter(self.CORE_PARAM_INT_MAX_DOWNLOAD_SPEED_KBYTES_PER_SEC, limit)
class DopalDownload(DopalObjectMixin, AzureusDownload):
def _short_description(self):
return self.torrent.short_description()
def _full_description(self):
result = self.short_description()
if not result:
return result
result += " " + self.stats.full_description()
return result
class DopalDownloadStats(DopalObjectMixin, AzureusDownloadStats):
def _full_description(self):
return "[%s, %.1f%%]" % (self.status, float(self.completed) / 10)
class DopalDiskManagerFileInfo(DopalObjectMixin, AzureusDiskManagerFileInfo):
def _full_description(self):
filename = self.short_description()
if not filename:
return None
if self.is_skipped:
return filename + " [skipped]"
elif self.is_priority:
return filename + " [high]"
else:
return filename + " [normal]"
def _short_description(self):
import os.path
return os.path.basename(self.file)
class DopalLoggerChannel(DopalObjectMixin, AzureusLoggerChannel):
def _full_description(self):
result = self.name
if not self.enabled:
result += " [disabled]"
return result
def _short_description(self):
return self.name
class DopalPeer(DopalObjectMixin, AzureusPeer):
def _full_description(self):
return "%s:%s" % (self.ip, self.port)
def _short_description(self):
return self.ip
class DopalPluginInterface(DopalObjectMixin, AzureusPluginInterface):
def _full_description(self):
return self.plugin_name
def _short_description(self):
return self.plugin_id
class DopalTorrent(DopalObjectMixin, AzureusTorrent):
def _short_description(self):
return self.name
# Let's define the rest of the DOPAL classes.
for az_class in [AzureusDownloadAnnounceResult, AzureusDownloadScrapeResult]:
_make_class(DopalObjectStatsMixin, az_class, 'Dopal', DOPAL_CLASS_MAP)
for az_class in STANDARD_CLASS_MAP.values():
_make_class(DopalObjectMixin, az_class, 'Dopal', DOPAL_CLASS_MAP)
del az_class
# Amend the docstring to contain all the object types defined.
doc_string_sub_dict = {}
for class_map_dict, dict_entry in [
(STANDARD_CLASS_MAP, 'standard_classes'),
(DOPAL_CLASS_MAP, 'dopal_classes'),
]:
cls = None
classes_in_map = [cls.__name__ for cls in class_map_dict.values()]
classes_in_map.sort()
doc_string_sub_dict[dict_entry] = ', '.join(classes_in_map)
del classes_in_map, cls
__doc__ = __doc__ % doc_string_sub_dict
del doc_string_sub_dict
del __epydoc_mode
STANDARD_CLASS_MAP[None] = DOPAL_CLASS_MAP[None] = TypelessRemoteObject
Binary file not shown.
Binary file not shown.
+578
View File
@@ -0,0 +1,578 @@
# File: objects.py
# Library: DOPAL - DO Python Azureus Library
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details ( see the COPYING file ).
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
'''
Defines the object layer framework.
'''
from dopal.core import ExtendedAzureusConnection
from dopal.errors import AzMethodError, InvalidObjectIDError, \
RemoteMethodError, StaleObjectReferenceError, ConnectionlessObjectError, \
NonRefreshableConnectionlessObjectError, MissingRemoteAttributeError, \
NonRefreshableIncompleteObjectError, NonRefreshableObjectError
import dopal.utils
class AzureusObjectConnection(ExtendedAzureusConnection):
'''
This connection class generates remote representations of each object available in Azureus.
@ivar is_persistent_connection: Boolean indicating whether the connection should be persistent. Default is C{False}.
@ivar converter: Callable object which will be used to convert response data into objects.
This will usually be a L{RemoteObjectConverter<dopal.convert.RemoteObjectConverter>} instance which will convert the results of method invocations into objects. A value must be assigned for this object to work - no suitable default is provided automatically.
'''
def __init__(self): # AzureusObjectConnection
ExtendedAzureusConnection.__init__(self)
self.is_persistent_connection = False
self.converter = None
self.cached_plugin_interface_object = None
def _on_reconnect(self): # AzureusObjectConnection
if not self.is_persistent_connection:
self.cached_plugin_interface_object = None
def get_plugin_interface(self): # AzureusObjectConnection
# XXX: Add docstring.
obj = self.cached_plugin_interface_object
if self.cached_plugin_interface_object is not None:
# Try to verify that it exists.
#
# Why do we verify the object? Well, we just want to ensure that
# the object we return here is valid. It would be valid if we
# returned getPluginInterface. If the object needs repairing,
# then it is better to do it immediately.
#
# Why do we not rely on the object just sorting itself out?
# Well, the default definitions for extracting the object from the
# root will use the plugin interface object as the root.
#
try:
self.verify_objects([self.cached_plugin_interface_object])
except NonRefreshableObjectError:
# Subclasses of this error will occur if there's a problem
# refreshing the object (for whatever reason). Refreshing
# it will only occur if the object is not valid.
#
# If, for whatever reason, our cached plugin interface hasn't
# repaired itself, we'll just lose the cached version and get
# a new object.
#
# Why do we not just get a plugin interface object and update
# the cached plugin interface object? If the object is
# 'broken', or object persistency is not enabled, there's no
# reason to repair it - we wouldn't normally do that for any
# other object.
#
# But it is important we return a valid object.
self.cached_plugin_interface_object = None
if self.cached_plugin_interface_object is None:
self.cached_plugin_interface_object = self.getPluginInterface()
return self.cached_plugin_interface_object
def getPluginInterface(self): # AzureusObjectConnection
return self.invoke_object_method(None, 'getSingleton', (), 'PluginInterface')
# Invoke the remote method - nothing regarding object persistency is
# handled here.
def _invoke_object_method(self, az_object, method_name, method_args, result_type=None): # AzureusObjectConnection
if az_object is None:
az_object_id = None
else:
az_object_id = az_object.get_object_id()
# We don't need to extract the object ID's for objects which are in
# method_args - they will be an instance of one of the wrapper type
# classes, which will be appropriate enough to pull out the correct
# type to use.
response = self.invoke_remote_method(az_object_id, method_name, method_args)
result = self.converter(response.response_data, result_type=result_type)
return result
def invoke_object_method(self, az_object, method_name, method_args, result_type=None): # AzureusObjectConnection
objects = [obj for obj in method_args if isinstance(obj, RemoteObject)]
if az_object is not None:
objects.insert(0, az_object)
self.verify_objects(objects)
try:
return self._invoke_object_method(az_object, method_name, method_args, result_type)
except InvalidObjectIDError, error:
# You don't need to call is_connection_valid, since you'll know
# from the subclasses error. It's an unnecessary method call - fix
# it!
if not self.is_persistent_connection:
raise
if self.is_connection_valid():
raise
self.establish_connection()
self.verify_objects(objects)
# Two very quick failures of this type is unlikely to happen -
# it is more likely to be a logic error in this case, so we
# don't retry if it fails again.
return self._invoke_object_method(az_object, method_name, method_args, result_type)
def verify_objects(self, objects): # AzureusObjectConnection
# I did write this as a list expression initially, but I guess we
# should keep it readable.
has_refreshed_objects = False
for obj in objects:
if obj.__connection_id__ != self.connection_id:
if self.is_persistent_connection:
obj._refresh_object(self)
has_refreshed_objects = True
else:
raise StaleObjectReferenceError, obj
return has_refreshed_objects
class RemoteObject(object):
def __init__(self, connection, object_id, attributes=None): # RemoteObject
if connection is None:
self.__connection_id__ = None
elif isinstance(connection, AzureusObjectConnection):
self.__connection_id__ = connection.connection_id
else:
err = "connection must be instance of AzureusObjectConnection: %s"
raise ValueError, err % connection
self.__connection__ = connection
self.__object_id__ = object_id
if attributes is not None:
self.update_remote_data(attributes)
def __repr__(self): # RemoteObject
txt = "<%s object at 0x%08X" % (self.__class__.__name__, id(self))
# "sid" stands for short ID.
sid = self.get_short_object_id()
if sid is not None:
txt += ", sid=%s" % sid
return txt + ">"
def __str__(self): # RemoteObject
sid = self.get_short_object_id()
if sid is None:
return RemoteObject.__repr__(self)
else:
return "<%s, sid=%s>" % (self.__class__.__name__, sid)
def get_short_object_id(self):
if self.__object_id__ is None:
return None
return dopal.utils.make_short_object_id(self.__object_id__)
def get_object_id(self): # RemoteObject
return self.__object_id__
def get_remote_type(self): # RemoteObject
if not hasattr(self, 'get_xml_type'):
return None
return self.get_xml_type()
def get_remote_attributes(self): # RemoteObject
result = {}
result['__connection__'] = self.__connection__
result['__connection_id__'] = self.__connection_id__
result['__object_id__'] = self.__object_id__
return result
# set_remote_attribute and update_remote_data are very closely
# linked - the former sets one attribute at a time while the
# other sets multiple attributes together. It is recommended
# that set_remote_attribute is not overridden, but
# update_remote_data is instead. If you choose to override
# set_remote_attribute, you should override update_remote_data
# to use set_remote_attribute.
def set_remote_attribute(self, name, value): # RemoteObject
return self.update_remote_data({name: value})
def update_remote_data(self, attribute_data): # RemoteObject
for key, value in attribute_data.items():
setattr(self, key, value)
def get_connection(self): # RemoteObject
if self.__connection__ is None:
raise ConnectionlessObjectError, self
return self.__connection__
# Exits quietly if the current connection is valid.
#
# If it is invalid, then this object's _refresh_object method will be
# called instead to retrieve a new object ID (if applicable), but only
# if this object's connection is a persistent one. If not, it will raise
# a StaleObjectReferenceError.
def verify_connection(self): # RemoteObject
return self.get_connection().verify_objects([self])
def refresh_object(self): # RemoteObject
'''
Updates the remote attributes on this object.
@raise NonRefreshableConnectionlessObjectError: If the object is not
attached to a connection.
@return: None
'''
try:
if not self.verify_connection():
self._refresh_object(self.__connection__)
except ConnectionlessObjectError:
raise NonRefreshableConnectionlessObjectError, self
def _refresh_object(self, connection_to_use): # RemoteObject
'''
Internal method which refreshes the attributes on the object.
This method actually performs two different functionalities.
If the connection to use is the same as the one already attached,
with the same connection ID, then a refresh will take place.
If the connection is either a different connection, or the connection
ID is different, then an attempt will be made to retrieve the
equivalent object to update the attributes.
@param connection_to_use: The connection object to update with.
@type connection_to_use: L{AzureusObjectConnection}
@raise NonRefreshableObjectTypeError: Raised when the object type is
not one which can be refreshed on broken connections.
@raise NonRefreshableIncompleteObjectError: Raised when the object is
missing certain attributes which prevents it being refreshed on broken
connections.
@return: None
'''
# If the object is still valid, let's use the refresh method.
if (self.__connection__ == connection_to_use) and \
self.__connection_id__ == connection_to_use.connection_id:
new_object = connection_to_use.invoke_object_method(
self, '_refresh', (), result_type=self.get_xml_type())
# The object is no longer valid. Let's grab the equivalent object.
else:
# Special case - if the object is the cached plugin interface
# object, then we need to avoid calling get_plugin_interface.
#
# Why? Because that'll pick up that the object is invalid, and
# then attempt to refresh it. Recursive infinite loop.
#
# So in that case, we just get a plugin interface object
# directly.
if connection_to_use.cached_plugin_interface_object is self:
new_object = connection_to_use.getPluginInterface()
else:
root = connection_to_use.get_plugin_interface()
new_object = self._get_self_from_root_object(root)
del root
# Get the attributes...
new_data = new_object.get_remote_attributes()
# (Make sure that the important attributes are there...)
if __debug__:
attrs = ['__connection__', '__connection_id__', '__object_id__']
for key in attrs:
if key not in new_data:
err = "%r.get_remote_attributes() is missing values!"
raise AssertionError, err % self
del attrs, key
# Update the values.
self.update_remote_data(new_data)
# This method is used to locate the remote equivalent object from the
# plugin interface object. If the object cannot be retrieved from the
# PluginInterface, you should raise a NonRefreshableObjectTypeError
# instead (this is the default behaviour).
def _get_self_from_root_object(self, plugin_interface): # RemoteObject
raise NonRefreshableObjectError, self
def invoke_object_method(self, method, method_args, result_type=None): # RemoteObject
try:
return self.get_connection().invoke_object_method(self, method, method_args, result_type=result_type)
except RemoteMethodError, error:
# There's three different ways an error can be generated here:
# 1) _handle_invocation_error raises an error - this will have
# the traceback of where it was raised.
# 2) _handle_invocation_error returns an error object - this
# will have the traceback of the original exception.
# 3) _handle_invocation_error returns None - this will just
# reraise the original error.
error = self._handle_invocation_error(error, method, method_args)
if error is not None:
import sys
raise error, None, sys.exc_info()[2]
raise
def _handle_invocation_error(self, error, method_name, method_args): # RemoteObject
# Default behaviour - just reraise the old error.
return None
# Called by the converter classes to determine the type of a remote
# attribute.
def _get_type_for_attribute(self, attrib_name, mapping_key=None): # RemoteObject
return None
class RemoteConstantsMetaclass(type):
def __init__(cls, name, bases, cls_dict):
super(RemoteConstantsMetaclass, cls).__init__(name, bases, cls_dict)
if hasattr(cls, '__az_constants__'):
for key, value in cls.__az_constants__.items():
setattr(cls, key, value)
# This just used for interrogation purposes - the guts of this function will
# be used to build other functions (see below).
#
# Poor little function - ends up being consumed and tossed aside, like a
# Hollow devouring a human soul.
def _invoke_remote_method(self, *args, **kwargs):
from dopal.utils import handle_kwargs
kwargs = handle_kwargs(kwargs, result_type=None)
return self.invoke_object_method(__funcname__, args, **kwargs)
from dopal.utils import MethodFactory
_methodobj = MethodFactory(_invoke_remote_method)
make_instance_remote_method = _methodobj.make_instance_method
make_class_remote_method = _methodobj.make_class_method
del _methodobj, MethodFactory
from dopal.aztypes import AzureusMethods
class RemoteMethodMetaclass(type):
def __init__(cls, name, bases, cls_dict):
super(RemoteMethodMetaclass, cls).__init__(name, bases, cls_dict)
az_key = '__az_methods__'
if az_key not in cls_dict:
methodsobj = AzureusMethods()
for base in bases:
if hasattr(base, az_key):
methodsobj.update(getattr(base, az_key))
setattr(cls, az_key, methodsobj)
else:
methodsobj = getattr(cls, az_key)
# Create the real methods based on those in __az_methods__.
for method_name in methodsobj.get_method_names():
if not hasattr(cls, method_name):
_mobj = make_class_remote_method(method_name, cls)
setattr(cls, method_name, _mobj)
class RemoteMethodMixin(object):
__use_dynamic_methods__ = False
__use_type_checking__ = True
def __getattr__(self, name):
# Anything which starts with an underscore is unlikely to be a public
# method.
if (not name.startswith('_')) and self.__use_dynamic_methods__:
return self._get_remote_method_on_demand(name)
_superclass = super(RemoteMethodMixin, self)
# Influenced by code here:
# http://aspn.activestate.com/ASPN/Mail/Message/python-list/1620146
#
# The problem is that we can't use the super object to get a
# __getattr__ method for the appropriate class.
self_mro = list(self.__class__.__mro__)
for cls in self_mro[self_mro.index(RemoteMethodMixin) + 1:]:
if hasattr(cls, '__getattr__'):
return cls.__getattr__(self, name)
else:
# Isn't there something I can call to fall back on default
# behaviour?
text = "'%s' object has no attribute '%s'"
raise AttributeError, text % (type(self).__name__, name)
# Used to create a remote method object on demand.
def _get_remote_method_on_demand(self, name):
return make_instance_remote_method(name, self)
def invoke_object_method(self, method, method_args, result_type=None):
if self.__use_type_checking__:
try:
az_methods = self.__az_methods__
except AttributeError:
if not self.__use_dynamic_methods__:
raise RuntimeError, "%s uses type checking, but has no methods to check against" % type(
self).__name__
else:
try:
method_args, result_type = \
az_methods.wrap_args(method, method_args)
except AzMethodError:
if not self.__use_dynamic_methods__:
raise
return super(RemoteMethodMixin, self).invoke_object_method(method, method_args, result_type=result_type)
class RemoteAttributeMetaclass(type):
# XXX: What the hell is this meant to do!?
def __init__(cls, name, bases, cls_dict):
deft_names = '__default_remote_attribute_names__'
az_attrs = '__az_attributes__'
attr_dict = cls_dict.setdefault(deft_names, {})
attr_dict.update(cls_dict.get(az_attrs, {}))
for base in bases:
attr_dict.update(getattr(base, deft_names, {}))
attr_dict.update(getattr(base, az_attrs, {}))
setattr(cls, deft_names, attr_dict)
super(RemoteAttributeMetaclass, cls).__init__(name, bases, cls_dict)
class RemoteAttributesMixin(object):
__default_remote_attribute_names__ = {}
__reset_attributes_on_refresh__ = False
__protect_remote_attributes__ = True
def __init__(self, *args, **kwargs):
# Class attribute becomes instance attribute.
super(RemoteAttributesMixin, self).__init__(*args, **kwargs)
self.__remote_attribute_names__ = self.__default_remote_attribute_names__.copy()
def __getattr__(self, name):
if name in self.__remote_attribute_names__:
raise MissingRemoteAttributeError, name
# Influenced by code here:
# http://aspn.activestate.com/ASPN/Mail/Message/python-list/1620146
self_mro = list(self.__class__.__mro__)
for cls in self_mro[self_mro.index(RemoteAttributesMixin) + 1:]:
if hasattr(cls, '__getattr__'):
return cls.__getattr__(self, name)
else:
# Isn't there something I can call to fall back on default
# behaviour?
text = "'%s' object has no attribute '%s'"
raise AttributeError, text % (type(self).__name__, name)
def __setattr__(self, name, value):
if self.__protect_remote_attributes__ and not name.startswith('__'):
if name in self.__remote_attribute_names__:
err = "cannot set remote attribute directly: %s"
raise AttributeError, err % name
return super(RemoteAttributesMixin, self).__setattr__(name, value)
def set_remote_attribute(self, name, value):
if name not in self.__remote_attribute_names__:
self.__remote_attribute_names__[name] = None
return super(RemoteAttributesMixin, self).__setattr__(name, value)
def get_remote_attributes(self):
result = super(RemoteAttributesMixin, self).get_remote_attributes()
for attribute in self.__remote_attribute_names__:
if hasattr(self, attribute):
result[attribute] = getattr(self, attribute)
return result
def is_remote_attribute(self, name):
return name in self.__remote_attribute_names__
def update_remote_data(self, remote_attribute_dict):
if self.__reset_attributes_on_refresh__:
for attrib in self.__remote_attribute_names__:
try:
delattr(self, attrib)
except AttributeError:
pass
_super = super(RemoteAttributesMixin, self)
# XXX: Do a better fix than this!
pra_value = self.__protect_remote_attributes__
self.__protect_remote_attributes__ = False
try:
return _super.update_remote_data(remote_attribute_dict)
finally:
self.__protect_remote_attributes__ = pra_value
def _get_type_for_attribute(self, name, mapping_key=None):
if mapping_key is not None:
key_to_use = name + ',' + mapping_key
else:
key_to_use = name
result = self.__remote_attribute_names__.get(key_to_use)
if result is not None:
return result
else:
import dopal
if dopal.__dopal_mode__ == 1:
raise RuntimeError, (self, key_to_use)
_superfunc = super(RemoteAttributesMixin, self)._get_type_for_attribute
return _superfunc(name, mapping_key)
class AzureusObjectMetaclass(RemoteConstantsMetaclass, RemoteMethodMetaclass, RemoteAttributeMetaclass):
pass
class AzureusObject(RemoteAttributesMixin, RemoteMethodMixin, RemoteObject):
__metaclass__ = AzureusObjectMetaclass
def _get_self_from_root_object(self, plugin_interface):
# XXX: Err, this is a bit incorrect - it should be get_remote_type.
# But it will do for now. Need to think more carefully about
# the responsibilities of the two methods.
if hasattr(self, 'get_xml_type'):
from dopal.persistency import get_equivalent_object_from_root
return get_equivalent_object_from_root(self, plugin_interface)
return super(AzureusObject, self)._get_self_from_root_object(plugin_interface)
class TypelessRemoteObject(RemoteAttributesMixin, RemoteMethodMixin, RemoteObject):
__use_dynamic_methods__ = True
TYPELESS_CLASS_MAP = {None: TypelessRemoteObject}
# XXX: Define converter here?
# Add type checking code (though this proably should be core)
# Add some code to read data from statistics file (what level should this be at?)
# # Allow some code to make link_error_handler assignable
# Converter - needs to have some default behaviours (easily changeable):
# a) Atoms - what to do if no type is suggested. (not so important this one)
# b) Objects - what to do if no class is given (if no id is given?)
Binary file not shown.
Binary file not shown.
+61
View File
@@ -0,0 +1,61 @@
# File: persistency.py
# Library: DOPAL - DO Python Azureus Library
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details ( see the COPYING file ).
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
'''
Support module containing code which supports the persistency functionality offered by DOPAL.
'''
_refresh_methods = {
# Simple ones.
'PluginConfig': lambda pi, obj: \
pi.getPluginconfig(),
'DownloadManager': lambda pi, obj: \
pi.getDownloadManager(),
'IPFilter': lambda pi, obj: \
pi.getIPFilter(),
'ShortCuts': lambda pi, obj: \
pi.getShortCuts(),
'TorrentManager': lambda pi, obj: \
pi.getTorrentManager(),
'PluginInterface': lambda pi, obj: \
pi,
# Not so simple ones.
'Download': lambda pi, obj: \
pi.getShortCuts().getDownload(obj.torrent.hash),
'Torrent': lambda pi, obj: \
pi.getShortCuts().getDownload(obj.hash).torrent,
}
# XXX: Test and document.
def get_equivalent_object_from_root(original_object, plugin_interface):
import dopal.objects
if not isinstance(original_object, dopal.objects.RemoteObject):
raise ValueError, "%s is not a RemoteObject" % (original_object,)
from dopal.errors import NonRefreshableObjectTypeError, \
MissingRemoteAttributeError, NonRefreshableIncompleteObjectError
remote_type = original_object.get_remote_type()
try:
refresh_function = _refresh_methods[remote_type]
except KeyError:
raise NonRefreshableObjectTypeError(obj=original_object)
try:
return refresh_function(plugin_interface, original_object)
except MissingRemoteAttributeError, error:
raise NonRefreshableIncompleteObjectError(obj=original_object, error=error)
File diff suppressed because it is too large. Load diff
+195
View File
@@ -0,0 +1,195 @@
# File: utils.py
# Library: DOPAL - DO Python Azureus Library
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details ( see the COPYING file ).
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
'''
General utility functions.
'''
__pychecker__ = 'unusednames=metaclass_,attr_,no-unreachable'
# Converts a binary string character into a hexidecimal equivalent.
#
# >>> byte_to_hex_form('N')
# u'4E'
def byte_to_hex_form(byte):
# ord is used to convert the byte into it's numeric equivalent.
# hex will turn the number into a string like "0x5" or "0xa3"
# the slicing will chop off the "0x" prefix.
# zfill will change any single digit values to have a leading 0 character.
# upper will force all the A-F characters to be uppercase.
return unicode(hex(ord(byte))[2:].zfill(2).upper())
# Converts a binary string into a hexidecimal equivalent.
#
# >>> string_to_hex_form('xK3-')
# u'784B332D'
def string_to_hex_form(chars):
if not chars:
raise ValueError, "cannot convert empty string"
return ''.join([byte_to_hex_form(char) for char in chars])
# Converts a 2 character hexidecimal string into a binary character.
#
# >>> hex_form_to_byte(u'4E')
# 'L'
def hex_pair_to_byte(hex_pair):
return chr(int(hex_pair.encode('utf-8'), 16))
# Converts a hexidecimal string (a string containing only characters valid
# in use for displaying a hexidecimal number, i.e. 0123456789ABCDEF) into
# a binary string.
#
# >>> hex_string_to_binary(u'784B332D')
# 'xK3-'
def hex_string_to_binary(hex_string):
if len(hex_string) % 2:
raise ValueError, "string given has odd-number of characters, must be even"
if not hex_string:
raise ValueError, "cannot convert empty string"
return ''.join([hex_pair_to_byte(hex_string[i:i + 2]) for i in range(0, len(hex_string), 2)])
def make_short_object_id(object_id):
# Due to the way Azureus generates object ID's (it tends to be a
# long integer which is then incremented for each object it
# generates), we don't bother rendering the entire object ID
# as it would be too long (it can be as long as 20 characters).
#
# So instead, we only use the last 6 digits - turning the ID into
# hexidecimal. This gives us a range of 16**6 = 16.7 million. So
# Azureus would have to generate more than 16 million objects
# before it generates an ID which has the same short ID form as
# another object.
#
# We show the short ID as a way of easily seeing whether two
# objects represent the same remote object or not.
hex_id = hex(object_id)
if hex_id[-1] == 'L':
hex_short_id = hex_id[-7:-1]
else:
hex_short_id = hex_id[-6:]
return hex_short_id
def parse_azureus_version_string(ver_string):
ver_bits = ver_string.split('_', 2)
if len(ver_bits) == 1:
major_ver, minor_ver = ver_string, None
else:
major_ver, minor_ver = ver_bits
ver_segments = [int(bit) for bit in major_ver.split('.')]
if minor_ver:
if minor_ver[0].lower() == 'b':
ver_segments.append('b')
try:
beta_ver = int(minor_ver[1:])
except ValueError:
pass
else:
ver_segments.append(beta_ver)
return tuple(ver_segments)
#
# I love this code. :)
#
# I might turn it into something more generic, and use it elsewhere..
#
# Would be nicer if there was a better API for doing this, but given the amount
# of hackery that I'm doing right now, I won't complain. :)
#
# What a lot of effort just to act as if these methods were defined in the
# class itself.
import new
class MethodFactory(object):
def __init__(self, method_object):
_codeobj = method_object.func_code
code_arguments = [
_codeobj.co_argcount, _codeobj.co_nlocals, _codeobj.co_stacksize,
_codeobj.co_flags, _codeobj.co_code, _codeobj.co_consts,
_codeobj.co_names, _codeobj.co_varnames, _codeobj.co_filename,
_codeobj.co_name, _codeobj.co_firstlineno, _codeobj.co_lnotab,
]
self.code_arguments = code_arguments
def _build_function(self, name):
code_args = self.code_arguments[:]
code_args[9] = name
# code_args[8] = <modulename>
codeobj = new.code(*code_args)
return new.function(codeobj, {'__funcname__': name, '__builtins__': __builtins__}, name)
def make_instance_method(self, name, instanceobj):
method = self._build_function(name)
return new.instancemethod(method, instanceobj, type(instanceobj))
def make_class_method(self, name, classobj):
method = self._build_function(name)
return new.instancemethod(method, None, classobj)
def _not_implemented(self, *args, **kwargs):
class_name = self.__class__.__name__
funcname = __funcname__
raise NotImplementedError, "%(class_name)s.%(funcname)s" % locals()
not_implemented_factory = MethodFactory(_not_implemented)
make_not_implemented_class_method = not_implemented_factory.make_class_method
del _not_implemented, not_implemented_factory
def handle_kwargs(kwargs, *required, **optional):
result = {}
result.update(optional)
required_args = dict([(x, None) for x in required])
for kwarg_key, kwarg_value in kwargs.iteritems():
if optional.has_key(kwarg_key):
pass
else:
try:
required_args.pop(kwarg_key)
except KeyError:
raise TypeError, "unexpected keyword argument: %r" % kwarg_key
result[kwarg_key] = kwarg_value
if required_args:
missing_key = required_args.popitem()[0]
raise TypeError, "missing keyword argument: %r" % missing_key
return result
class Sentinel(object):
def __init__(self, value):
self.value = value
def __str__(self):
return str(self.value)
def __repr__(self):
return '<sentinel object (%r) at 0x%08X>' % (self.value, id(self))
Binary file not shown.
Binary file not shown.
+214
View File
@@ -0,0 +1,214 @@
# File: xmlutils.py
# Library: DOPAL - DO Python Azureus Library
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details ( see the COPYING file ).
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
'''
XML utility functions.
'''
# Given an object which has the same interface as xml.dom.Node:
# a) Join all concurrent text nodes together.
# b) Strip all trailing and leading whitespace from each text node.
#
# This function will recursively process the tree structure given in the node
# object. No value will be returned by this function, instead the given object
# will be modified.
def normalise_xml_structure(xml_node):
# Concurrent text nodes should be joined together.
xml_node.normalize()
# Strip all text nodes which are empty of content (whitespace is not
# content).
from xml.dom import Node
nodes_to_delete = []
for node in xml_node.childNodes:
if node.nodeType == Node.TEXT_NODE:
stripped_text = node.nodeValue.strip()
if stripped_text:
node.nodeValue = stripped_text
else:
nodes_to_delete.append(node)
else:
normalise_xml_structure(node)
for node in nodes_to_delete:
xml_node.removeChild(node)
node.unlink()
def get_text_content(node):
from xml.dom import Node
# Text content is stored directly in this node.
if node.nodeType == Node.TEXT_NODE:
return node.nodeValue
# Otherwise, must be in a child node.
#elif len(node.childNodes) == 1 and \
# node.firstChild.nodeType == Node.TEXT_NODE:
# return node.firstChild.nodeValue
# Sometimes happens for attributes with no real value.
elif len(node.childNodes) == 0:
return ''
text_node = None
err_text = None
for child in node.childNodes:
if child.nodeType == Node.TEXT_NODE:
if text_node is None:
text_node = child
else:
err_text = "contained multiple text nodes"
break
else:
if text_node is None:
if len(node.childNodes) != 1:
err_text = "contained multiple nodes, but none were text"
else:
err_text = "did not contain a character string as its value"
else:
return text_node.nodeValue
raise ValueError, ("the node %s " % node.nodeName) + err_text
from xml.sax.saxutils import quoteattr, escape
# This base class will be removed when XMLObject is removed.
class _XMLObjectBase(object):
def __init__(self, tag_name):
self.tag_name = tag_name
self.attributes = {}
self.contents = []
def add_attribute(self, attribute_name, attribute_value):
self.attributes[attribute_name] = attribute_value
def add_content(self, content):
self.contents.append(content)
def to_string(self, out=None, indent=0):
if out is None:
# We use StringIO instead of cStringIO not to lose unicode strings.
import StringIO
out = StringIO.StringIO()
return_as_string = True
else:
return_as_string = False
indent_string = ' ' * indent
out.write(indent_string)
out.write('<')
out.write(self.tag_name)
for attr_name, attr_value in self.attributes.items():
out.write(' ')
out.write(attr_name)
out.write('=')
out.write(quoteattr(attr_value))
# If we have no contents, we'll close the tag here.
if not self.contents:
out.write(' />\n')
else:
out.write('>')
# If we have one piece of content, which is just a string, then
# we'll put it on the same line as the opening tag is on.
if len(self.contents) == 1 and not hasattr(self.contents[0], 'to_string'):
out.write(escape(self.contents[0]))
# Otherwise, we assume we have some more XML blocks to write out,
# so we'll indent them and put them on newlines.
elif self.contents:
out.write('\n')
for content in self.contents:
content.to_string(out, indent + 2)
out.write(indent_string)
# Write out the closing tag (if we haven't written it already).
if self.contents:
out.write('</')
out.write(self.tag_name)
out.write('>\n')
# If the invocation of this method was not passed a buffer to write
# into, then we return the string representation.
if return_as_string:
return out.getvalue()
return None
class XMLObject(_XMLObjectBase):
'''
B{Deprecated:} An object representing a block of XML.
@attention: B{Deprecated:} This class does not provide any guarantees in
the way that byte strings are handled. Use L{UXMLObject} instead.
'''
def __init__(self, tag_name):
from dopal.errors import DopalPendingDeprecationWarning
import warnings
warnings.warn("XMLObject is deprecated - use UXMLObject instead", DopalPendingDeprecationWarning)
_XMLObjectBase.__init__(self, tag_name)
class UXMLObject(_XMLObjectBase):
'''
An object representing a block of XML.
Any string which is added to this block (either through the L{add_content}
or L{add_attribute} methods should be a unicode string, rather than a byte
string. If it is a byte string, then it must be a string which contains
text in the system's default encoding - attempting to add text encoding in
other formats is not allowed.
'''
def to_string(self, out=None, indent=0):
result = _XMLObjectBase.to_string(self, out, indent)
if result is None:
return None
return unicode(result)
def encode(self, encoding='UTF-8'):
return (('<?xml version="1.0" encoding="%s"?>\n' % encoding) + self.to_string()).encode(encoding)
def __unicode__(self):
return self.to_string()
def make_xml_ref_for_az_object(object_id):
'''
Creates an XML block which represents a remote object in Azureus with the given object ID.
@param object_id: The object ID to reference.
@type object_id: int / long
@return: A L{UXMLObject} instance.
'''
object_id_block = UXMLObject('_object_id')
object_id_block.add_content(str(object_id))
object_block = UXMLObject('OBJECT')
object_block.add_content(object_id_block)
return object_block
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large. Load diff
Binary file not shown.