00001 """
Cotains classes for representing DataSources or DataSinks.
Copyright: John Stowers, 2006
License: GPLv2
"""
import xml.dom.minidom
import traceback
import gobject
from gettext import gettext as _
import logging
log = logging.getLogger("dataproviders.DataProvider")
import conduit
import conduit.ModuleWrapper as ModuleWrapper
import conduit.utils as Utils
import conduit.Settings as Settings
STATUS_NONE = _("Ready")
STATUS_CHANGE_DETECTED = _("New data to sync")
STATUS_REFRESH = _("Refreshing...")
STATUS_DONE_REFRESH_OK = _("Refreshed OK")
STATUS_DONE_REFRESH_ERROR = _("Error Refreshing")
STATUS_SYNC = _("Synchronizing...")
STATUS_DONE_SYNC_OK = _("Synchronized OK")
STATUS_DONE_SYNC_ERROR = _("Error Synchronizing")
STATUS_DONE_SYNC_SKIPPED = _("Synchronization Skipped")
STATUS_DONE_SYNC_CANCELLED = _("Synchronization Cancelled")
STATUS_DONE_SYNC_CONFLICT = _("Synchronization Conflict")
STATUS_DONE_SYNC_NOT_CONFIGURED = _("Not Configured Correctly")
00032 class DataProviderBase(gobject.GObject):
"""
Model of a DataProvider. Can be a source or a sink
"""
__gsignals__ = {
"status-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
"change-detected": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [])
}
_name_ = ""
_description_ = ""
_icon_ = ""
_module_type_ = "dataprovider"
_category_ = conduit.dataproviders.CATEGORY_TEST
_configurable_ = False
_out_type_ = ""
_in_type_ = ""
00051 def __init__(self, *args):
"""
All sync functionality should be provided by derived classes
"""
gobject.GObject.__init__(self)
self.pendingChangeDetected = False
self.icon = None
self.status = STATUS_NONE
00061 def __emit_status_changed(self):
"""
Emits a 'status-changed' signal to the main loop.
You should connect to this signal if you wish to be notified when
the derived DataProvider goes through its stages (STATUS_* etc)
"""
self.emit("status-changed")
return False
00071 def __emit_change_detected(self):
"""
Emits a 'change-detected' signal to the main loop.
"""
log.debug("Change detected in dataproviders data (%s)" % self.get_UID())
self.set_status(STATUS_CHANGE_DETECTED)
self.emit("change-detected")
self.pendingChangeDetected = False
00081 def emit(self, *args):
"""
Override the gobject signal emission so that all signals are emitted
from the main loop on an idle handler
"""
gobject.idle_add(gobject.GObject.emit,self,*args)
00088 def initialize(self):
"""
Called when the module is loaded by the module loader.
It is called in the main thread so should NOT block. It should perform
simple tests to determine whether the dataprovider is applicable to
the user and whether is should be presented to them. For example it
may check if a specific piece of hardware is loaded, or check if
a user has the specific piece of software installed with which
it synchronizes.
@returns: True if the module initialized correctly (is appropriate
for the user), False otherwise
@rtype: C{bool}
"""
return True
00105 def uninitialize(self):
"""
Called just before the application quits.
"""
pass
00111 def refresh(self):
"""
Performs any (conduit.logging in, etc) which must be undertaken on the
dataprovider prior to calling get_all(). Should gather all information
so a subsequent call to get_all() can return the uids of all the
data this dataprovider holds
This function may be called multiple times so derived classes should
be aware of this.
Derived classes should call this function to ensure the dataprovider
status is updated.
"""
self.set_status(STATUS_REFRESH)
00126 def finish(self, *args):
"""
Perform any post-sync cleanup. For example, free any structures created
in refresh that were used in the synchronization.
"""
if self.pendingChangeDetected:
self.__emit_change_detected()
def emit_change_detected(self):
if self.is_busy():
self.pendingChangeDetected = True
else:
self.__emit_change_detected()
00140 def set_status(self, newStatus):
"""
Sets the dataprovider status. If the status has changed then emits
a status-changed signal
"""
if newStatus != self.get_status():
self.status = newStatus
self.__emit_status_changed()
00149 def get_status(self):
"""
@returns: The current dataproviders status
"""
return self.status
00155 def is_busy(self):
"""
A DataProvider is busy if it is currently in the middle of the
intialization or synchronization process.
@todo: This simple test introduces a few (many) corner cases where
the function will return the wrong result. Think about this harder
"""
s = self.get_status()
if s == STATUS_REFRESH:
return True
elif s == STATUS_SYNC:
return True
else:
return False
00171 def configure(self, window):
"""
Show a configuration box for configuring the dataprovider instance.
@param window: The parent gtk.Window (to show a modal dialog)
"""
pass
00178 def is_configured(self, isSource, isTwoWay):
"""
Checks if the dp has been configured or not (and if it needs to be)
@param isSource: True if the dataprovider is in the source position
@param isTwoway: True if the dataprovider is a member of a two-way sync
"""
return True
00186 def get_configuration(self):
"""
Returns a dictionary of strings to be saved, representing the dataproviders
current configuration. Should be overridden by all dataproviders wishing
to be able to save their state between application runs
@returns: Dictionary of strings containing application settings
@rtype: C{dict(string)}
"""
return {}
00196 def get_configuration_xml(self):
"""
Returns the dataprovider configuration as xml
@rtype: C{string}
"""
doc = xml.dom.minidom.Element("configuration")
configDict = self.get_configuration()
for config in configDict:
configxml = xml.dom.minidom.Element(str(config))
try:
vtype = Settings.TYPE_TO_TYPE_NAME[ type(configDict[config]) ]
value = Settings.TYPE_TO_STRING[ type(configDict[config]) ](configDict[config])
except KeyError:
log.warn("Cannot convert %s to string. Value of %s not saved" % (type(value), config))
vtype = Settings.TYPE_TO_TYPE_NAME[str]
value = Settings.TYPE_TO_STRING[str](configDict[config])
configxml.setAttribute("type", vtype)
valueNode = xml.dom.minidom.Text()
valueNode.data = value
configxml.appendChild(valueNode)
doc.appendChild(configxml)
return doc.toxml()
00221 def set_configuration(self, config):
"""
Restores applications settings
@param config: dictionary of dataprovider settings to restore
"""
for c in config:
if getattr(self, c, None) != None and callable(getattr(self, c, None)) == False:
setattr(self,c,config[c])
else:
log.warn("Not restoring %s setting: Exists=%s Callable=%s" % (
c,
getattr(self, c, False),
callable(getattr(self, c, None)))
)
00238 def set_configuration_xml(self, xmltext):
"""
Restores applications settings from XML
@param xmltext: xml representation of settings
@type xmltext: C{string}
"""
doc = xml.dom.minidom.parseString(xmltext)
configxml = doc.documentElement
if configxml.nodeType == configxml.ELEMENT_NODE and configxml.localName == "configuration":
settings = {}
for s in configxml.childNodes:
if s.nodeType == s.ELEMENT_NODE:
if s.hasChildNodes():
raw = s.firstChild.data
vtype = s.getAttribute("type")
try:
data = Settings.STRING_TO_TYPE[vtype](raw)
except KeyError:
log.warn("Cannot convert string (%s) to native type %s\n" % (raw, vtype, traceback.format_exc()))
data = str(raw)
settings[s.localName] = data
try:
self.set_configuration(settings)
except Exception, err:
log.warn("Error restoring %s configuration\n%s" %
(self._name_, traceback.format_exc()))
else:
log.debug("Could not find <configuration> xml fragment")
00272 def get_UID(self):
"""
Returns a UID that represents this dataproviders (locally) unique state
and configuration. For example the LUID for a gmail dp may be your
username and password.
Derived types MUST overwride this function
@rtype: C{string}
"""
raise NotImplementedError
00283 def get_input_conversion_args(self):
"""
Provides a way to pass arguments to conversion functions. For example when
transcoding a music file the dataprovider may return a dictionary specifying the
conversion encoding, quality, etc
@returns: a C{dict} of conversion arguments
"""
return {}
00292 def get_input_type(self):
"""
Provides a way for dataproviders to change the datatype they accept. In most cases
implementing get_in_conversion args is recommended and will let you acomplish what you want.
@returs: A C{string} in the form "type_name?arg_name=foo&arg_name2=bar"
"""
args = self.get_input_conversion_args()
if len(args) == 0:
return self._in_type_
else:
return "%s?%s" % (self._in_type_, Utils.encode_conversion_args(args))
00304 def get_output_conversion_args(self):
"""
Provides a way to pass arguments to conversion functions. For example when
transcoding a music file the dataprovider may return a dictionary specifying the
conversion encoding, quality, etc
@returns: a C{dict} of conversion arguments
"""
return {}
00313 def get_output_type(self):
"""
Provides a way for dataproviders to change the datatype they emit. In most cases
implementing get_out_conversion args is recommended and will let you acomplish what you want.
@returs: A C{string} in the form "type_name?arg_name=foo&arg_name2=bar"
"""
args = self.get_output_conversion_args()
if len(args) == 0:
return self._out_type_
else:
return "%s?%s" % (self._out_type_, Utils.encode_conversion_args(args))
00325 def get_name(self):
"""
@returns: The DataProvider name, to be displayed in the UI
"""
return self._name_
00331 class DataSource(DataProviderBase):
"""
Base Class for DataSources.
"""
def __init__(self):
DataProviderBase.__init__(self)
00338 def get(self, LUID):
"""
Returns data with the specified LUID. This function must be overridden by the
appropriate dataprovider.
Derived classes should call this function to ensure the dataprovider
status is updated.
@param LUID: The index of the data to return
@type LUID: C{string}
@rtype: L{conduit.DataType.DataType}
@returns: An item of data
"""
self.set_status(STATUS_SYNC)
return None
00354 def get_num_items(self):
"""
Returns the number of items requiring sychronization.
@returns: The number of items to synchronize
@rtype: C{int}
"""
self.set_status(STATUS_SYNC)
return len(self.get_all())
00363 def get_all(self):
"""
Returns an array of all the LUIDs this dataprovider holds.
"""
self.set_status(STATUS_SYNC)
return []
00370 def get_changes(self):
"""
Returns all changes since last sync
"""
raise NotImplementedError
00376 def add(self, LUID):
"""
Adds an item to the datasource according to LUID. This method
is used by the DBus interface
@returns: True if the data was successfully added
"""
return False
00386 class DataSink(DataProviderBase):
"""
Base Class for DataSinks
"""
def __init__(self):
DataProviderBase.__init__(self)
00393 def put(self, putData, overwrite, LUID):
"""
Stores data. The derived class is responsible for checking if putData
conflicts.
In the case of a two-way datasource, the derived type should
consider the overwrite parameter, which if True, should allow the dp
to replace a datatype instance if one is found at the existing location
Derived classes should call this function to ensure the dataprovider
status is updated.
@param putData: Data which to save
@type putData: A L{conduit.DataType.DataType} derived type that this
dataprovider is capable of handling
@param overwrite: If this argument is True, the DP should overwrite
an existing datatype instace (if one exists). Generally used in conflict
resolution.
@type overwrite: C{bool}
@param LUID: A locally unique identifier representing the location
where the data was previously put.
@raise conduit.Exceptions.SynchronizeConflictError: if there is a
conflict between the data being put, and that which it is overwriting
a L{conduit.Exceptions.SynchronizeConflictError} is raised.
"""
self.set_status(STATUS_SYNC)
00420 def delete(self, LUID):
"""
Deletes data with LUID.
"""
self.set_status(STATUS_SYNC)
00427 class TwoWay(DataSource, DataSink):
"""
Abstract Base Class for TwoWay dataproviders
"""
def __init__(self):
DataSource.__init__(self)
DataSink.__init__(self)
00436 class DataProviderFactory(gobject.GObject):
"""
Abstract base class for a factory which emits Dataproviders. Users should
inherit from this if they wish to provide a loadable module in which
dynamic dataproviders become available at runtime.
"""
__gsignals__ = {
"dataprovider-added" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
gobject.TYPE_PYOBJECT,
gobject.TYPE_PYOBJECT]),
"dataprovider-removed" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
gobject.TYPE_STRING])
}
_module_type_ = "dataprovider-factory"
def __init__(self, **kwargs):
gobject.GObject.__init__(self)
def emit_added(self, klass, initargs, category, customKey=None):
dpw = ModuleWrapper.ModuleWrapper(
klass=klass,
initargs=initargs,
category=category
)
dpw.set_dnd_key(customKey)
key = dpw.get_dnd_key()
log.debug("DataProviderFactory %s: Emitting dataprovider-added for %s" % (self, key))
self.emit("dataprovider-added", dpw, klass)
return key
def emit_removed(self, key):
log.debug("DataProviderFactory %s: Emitting dataprovider-removed for %s" % (self, key))
self.emit("dataprovider-removed", key)
def probe(self):
pass
00474 def quit(self):
"""
Shutdown cleanup...
"""
pass
00480 def setup_configuration_widget(self):
"""
If the factory needs to offer configuration options then
it should return a gtk.widget here. This widget is then packed
into the configuration notebook.
"""
return None
00488 def save_configuration(self, ok):
"""
@param ok: True if the user closed the prefs panel with OK, false if
they cancelled it.
"""
pass
def get_name(self):
return self.__class__.__name__