LogoSearch packages:      

Sourcecode: conduit version File versions

DataProvider.py

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))
                #store the value and value type
                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:
            #Perform these checks to stop malformed xml from stomping on
            #unintended variables or posing a security risk by overwriting methods
            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:
                    #now convert the setting to the correct type (if filled out)
                    if s.hasChildNodes():
                        raw = s.firstChild.data
                        vtype = s.getAttribute("type")
                        try:
                            data = Settings.STRING_TO_TYPE[vtype](raw)
                        except KeyError:
                            #fallback to string type
                            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,      #Wrapper
            gobject.TYPE_PYOBJECT]),    #Class
        "dataprovider-removed" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
            gobject.TYPE_STRING])       #Unique key
    }

    _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__
        


Generated by  Doxygen 1.5.1   Back to index