lol
why?
This commit is contained in:
commit
6fef9834fe
396 files changed
+49459
No files matched your search
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -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
|
||||
@@ -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.
@@ -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.
@@ -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.
@@ -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
@@ -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.
@@ -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.
Reference in new issue
Block a user