Package conduit :: Package dataproviders :: Module DataProvider
[hide private]

Source Code for Module conduit.dataproviders.DataProvider

  1  """ 
  2  Cotains classes for representing DataSources or DataSinks. 
  3   
  4  Copyright: John Stowers, 2006 
  5  License: GPLv2 
  6  """ 
  7  import xml.dom.minidom 
  8  import traceback 
  9  import gobject 
 10  from gettext import gettext as _ 
 11  import logging 
 12  log = logging.getLogger("dataproviders.DataProvider") 
 13   
 14  import conduit 
 15  import conduit.ModuleWrapper as ModuleWrapper 
 16  import conduit.utils as Utils 
 17  import conduit.Settings as Settings 
 18   
 19  STATUS_NONE = _("Ready") 
 20  STATUS_CHANGE_DETECTED = _("New data to sync") 
 21  STATUS_REFRESH = _("Refreshing...") 
 22  STATUS_DONE_REFRESH_OK = _("Refreshed OK") 
 23  STATUS_DONE_REFRESH_ERROR = _("Error Refreshing") 
 24  STATUS_SYNC = _("Synchronizing...") 
 25  STATUS_DONE_SYNC_OK = _("Synchronized OK") 
 26  STATUS_DONE_SYNC_ERROR = _("Error Synchronizing") 
 27  STATUS_DONE_SYNC_SKIPPED = _("Synchronization Skipped") 
 28  STATUS_DONE_SYNC_CANCELLED = _("Synchronization Cancelled") 
 29  STATUS_DONE_SYNC_CONFLICT = _("Synchronization Conflict") 
 30  STATUS_DONE_SYNC_NOT_CONFIGURED = _("Not Configured Correctly") 
 31   
32 -class DataProviderBase(gobject.GObject):
33 """ 34 Model of a DataProvider. Can be a source or a sink 35 """ 36 37 __gsignals__ = { 38 "status-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []), 39 "change-detected": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []) 40 } 41 42 _name_ = "" 43 _description_ = "" 44 _icon_ = "" 45 _module_type_ = "dataprovider" 46 _category_ = conduit.dataproviders.CATEGORY_TEST 47 _configurable_ = False 48 _out_type_ = "" 49 _in_type_ = "" 50
51 - def __init__(self, *args):
52 """ 53 All sync functionality should be provided by derived classes 54 """ 55 gobject.GObject.__init__(self) 56 57 self.pendingChangeDetected = False 58 self.icon = None 59 self.status = STATUS_NONE
60
61 - def __emit_status_changed(self):
62 """ 63 Emits a 'status-changed' signal to the main loop. 64 65 You should connect to this signal if you wish to be notified when 66 the derived DataProvider goes through its stages (STATUS_* etc) 67 """ 68 self.emit("status-changed") 69 return False
70
71 - def __emit_change_detected(self):
72 """ 73 Emits a 'change-detected' signal to the main loop. 74 """ 75 log.debug("Change detected in dataproviders data (%s)" % self.get_UID()) 76 77 self.set_status(STATUS_CHANGE_DETECTED) 78 self.emit("change-detected") 79 self.pendingChangeDetected = False
80
81 - def emit(self, *args):
82 """ 83 Override the gobject signal emission so that all signals are emitted 84 from the main loop on an idle handler 85 """ 86 gobject.idle_add(gobject.GObject.emit,self,*args)
87
88 - def initialize(self):
89 """ 90 Called when the module is loaded by the module loader. 91 92 It is called in the main thread so should NOT block. It should perform 93 simple tests to determine whether the dataprovider is applicable to 94 the user and whether is should be presented to them. For example it 95 may check if a specific piece of hardware is loaded, or check if 96 a user has the specific piece of software installed with which 97 it synchronizes. 98 99 @returns: True if the module initialized correctly (is appropriate 100 for the user), False otherwise 101 @rtype: C{bool} 102 """ 103 return True
104
105 - def uninitialize(self):
106 """ 107 Called just before the application quits. 108 """ 109 pass
110
111 - def refresh(self):
112 """ 113 Performs any (conduit.logging in, etc) which must be undertaken on the 114 dataprovider prior to calling get_all(). Should gather all information 115 so a subsequent call to get_all() can return the uids of all the 116 data this dataprovider holds 117 118 This function may be called multiple times so derived classes should 119 be aware of this. 120 121 Derived classes should call this function to ensure the dataprovider 122 status is updated. 123 """ 124 self.set_status(STATUS_REFRESH)
125
126 - def finish(self, *args):
127 """ 128 Perform any post-sync cleanup. For example, free any structures created 129 in refresh that were used in the synchronization. 130 """ 131 if self.pendingChangeDetected: 132 self.__emit_change_detected()
133
134 - def emit_change_detected(self):
135 if self.is_busy(): 136 self.pendingChangeDetected = True 137 else: 138 self.__emit_change_detected()
139
140 - def set_status(self, newStatus):
141 """ 142 Sets the dataprovider status. If the status has changed then emits 143 a status-changed signal 144 """ 145 if newStatus != self.get_status(): 146 self.status = newStatus 147 self.__emit_status_changed()
148
149 - def get_status(self):
150 """ 151 @returns: The current dataproviders status 152 """ 153 return self.status
154
155 - def is_busy(self):
156 """ 157 A DataProvider is busy if it is currently in the middle of the 158 intialization or synchronization process. 159 160 @todo: This simple test introduces a few (many) corner cases where 161 the function will return the wrong result. Think about this harder 162 """ 163 s = self.get_status() 164 if s == STATUS_REFRESH: 165 return True 166 elif s == STATUS_SYNC: 167 return True 168 else: 169 return False
170
171 - def configure(self, window):
172 """ 173 Show a configuration box for configuring the dataprovider instance. 174 @param window: The parent gtk.Window (to show a modal dialog) 175 """ 176 pass
177
178 - def is_configured(self, isSource, isTwoWay):
179 """ 180 Checks if the dp has been configured or not (and if it needs to be) 181 @param isSource: True if the dataprovider is in the source position 182 @param isTwoway: True if the dataprovider is a member of a two-way sync 183 """ 184 return True
185
186 - def get_configuration(self):
187 """ 188 Returns a dictionary of strings to be saved, representing the dataproviders 189 current configuration. Should be overridden by all dataproviders wishing 190 to be able to save their state between application runs 191 @returns: Dictionary of strings containing application settings 192 @rtype: C{dict(string)} 193 """ 194 return {}
195
196 - def get_configuration_xml(self):
197 """ 198 Returns the dataprovider configuration as xml 199 @rtype: C{string} 200 """ 201 doc = xml.dom.minidom.Element("configuration") 202 configDict = self.get_configuration() 203 for config in configDict: 204 configxml = xml.dom.minidom.Element(str(config)) 205 #store the value and value type 206 try: 207 vtype = Settings.TYPE_TO_TYPE_NAME[ type(configDict[config]) ] 208 value = Settings.TYPE_TO_STRING[ type(configDict[config]) ](configDict[config]) 209 except KeyError: 210 log.warn("Cannot convert %s to string. Value of %s not saved" % (type(value), config)) 211 vtype = Settings.TYPE_TO_TYPE_NAME[str] 212 value = Settings.TYPE_TO_STRING[str](configDict[config]) 213 configxml.setAttribute("type", vtype) 214 valueNode = xml.dom.minidom.Text() 215 valueNode.data = value 216 configxml.appendChild(valueNode) 217 doc.appendChild(configxml) 218 219 return doc.toxml()
220
221 - def set_configuration(self, config):
222 """ 223 Restores applications settings 224 @param config: dictionary of dataprovider settings to restore 225 """ 226 for c in config: 227 #Perform these checks to stop malformed xml from stomping on 228 #unintended variables or posing a security risk by overwriting methods 229 if getattr(self, c, None) != None and callable(getattr(self, c, None)) == False: 230 setattr(self,c,config[c]) 231 else: 232 log.warn("Not restoring %s setting: Exists=%s Callable=%s" % ( 233 c, 234 getattr(self, c, False), 235 callable(getattr(self, c, None))) 236 )
237
238 - def set_configuration_xml(self, xmltext):
239 """ 240 Restores applications settings from XML 241 242 @param xmltext: xml representation of settings 243 @type xmltext: C{string} 244 """ 245 doc = xml.dom.minidom.parseString(xmltext) 246 configxml = doc.documentElement 247 248 if configxml.nodeType == configxml.ELEMENT_NODE and configxml.localName == "configuration": 249 settings = {} 250 for s in configxml.childNodes: 251 if s.nodeType == s.ELEMENT_NODE: 252 #now convert the setting to the correct type (if filled out) 253 if s.hasChildNodes(): 254 raw = s.firstChild.data 255 vtype = s.getAttribute("type") 256 try: 257 data = Settings.STRING_TO_TYPE[vtype](raw) 258 except KeyError: 259 #fallback to string type 260 log.warn("Cannot convert string (%s) to native type %s\n" % (raw, vtype, traceback.format_exc())) 261 data = str(raw) 262 settings[s.localName] = data 263 264 try: 265 self.set_configuration(settings) 266 except Exception, err: 267 log.warn("Error restoring %s configuration\n%s" % 268 (self._name_, traceback.format_exc())) 269 else: 270 log.debug("Could not find <configuration> xml fragment")
271
272 - def get_UID(self):
273 """ 274 Returns a UID that represents this dataproviders (locally) unique state 275 and configuration. For example the LUID for a gmail dp may be your 276 username and password. 277 278 Derived types MUST overwride this function 279 @rtype: C{string} 280 """ 281 raise NotImplementedError
282
284 """ 285 Provides a way to pass arguments to conversion functions. For example when 286 transcoding a music file the dataprovider may return a dictionary specifying the 287 conversion encoding, quality, etc 288 @returns: a C{dict} of conversion arguments 289 """ 290 return {}
291
292 - def get_input_type(self):
293 """ 294 Provides a way for dataproviders to change the datatype they accept. In most cases 295 implementing get_in_conversion args is recommended and will let you acomplish what you want. 296 @returs: A C{string} in the form "type_name?arg_name=foo&arg_name2=bar" 297 """ 298 args = self.get_input_conversion_args() 299 if len(args) == 0: 300 return self._in_type_ 301 else: 302 return "%s?%s" % (self._in_type_, Utils.encode_conversion_args(args))
303
305 """ 306 Provides a way to pass arguments to conversion functions. For example when 307 transcoding a music file the dataprovider may return a dictionary specifying the 308 conversion encoding, quality, etc 309 @returns: a C{dict} of conversion arguments 310 """ 311 return {}
312
313 - def get_output_type(self):
314 """ 315 Provides a way for dataproviders to change the datatype they emit. In most cases 316 implementing get_out_conversion args is recommended and will let you acomplish what you want. 317 @returs: A C{string} in the form "type_name?arg_name=foo&arg_name2=bar" 318 """ 319 args = self.get_output_conversion_args() 320 if len(args) == 0: 321 return self._out_type_ 322 else: 323 return "%s?%s" % (self._out_type_, Utils.encode_conversion_args(args))
324
325 - def get_name(self):
326 """ 327 @returns: The DataProvider name, to be displayed in the UI 328 """ 329 return self._name_
330
331 -class DataSource(DataProviderBase):
332 """ 333 Base Class for DataSources. 334 """
335 - def __init__(self):
337
338 - def get(self, LUID):
339 """ 340 Returns data with the specified LUID. This function must be overridden by the 341 appropriate dataprovider. 342 343 Derived classes should call this function to ensure the dataprovider 344 status is updated. 345 346 @param LUID: The index of the data to return 347 @type LUID: C{string} 348 @rtype: L{conduit.DataType.DataType} 349 @returns: An item of data 350 """ 351 self.set_status(STATUS_SYNC) 352 return None
353
354 - def get_num_items(self):
355 """ 356 Returns the number of items requiring sychronization. 357 @returns: The number of items to synchronize 358 @rtype: C{int} 359 """ 360 self.set_status(STATUS_SYNC) 361 return len(self.get_all())
362
363 - def get_all(self):
364 """ 365 Returns an array of all the LUIDs this dataprovider holds. 366 """ 367 self.set_status(STATUS_SYNC) 368 return []
369
370 - def get_changes(self):
371 """ 372 Returns all changes since last sync 373 """ 374 raise NotImplementedError
375
376 - def add(self, LUID):
377 """ 378 Adds an item to the datasource according to LUID. This method 379 is used by the DBus interface 380 381 @returns: True if the data was successfully added 382 """ 383 return False
384 385
386 -class DataSink(DataProviderBase):
387 """ 388 Base Class for DataSinks 389 """
390 - def __init__(self):
392
393 - def put(self, putData, overwrite, LUID):
394 """ 395 Stores data. The derived class is responsible for checking if putData 396 conflicts. 397 398 In the case of a two-way datasource, the derived type should 399 consider the overwrite parameter, which if True, should allow the dp 400 to replace a datatype instance if one is found at the existing location 401 402 Derived classes should call this function to ensure the dataprovider 403 status is updated. 404 405 @param putData: Data which to save 406 @type putData: A L{conduit.DataType.DataType} derived type that this 407 dataprovider is capable of handling 408 @param overwrite: If this argument is True, the DP should overwrite 409 an existing datatype instace (if one exists). Generally used in conflict 410 resolution. 411 @type overwrite: C{bool} 412 @param LUID: A locally unique identifier representing the location 413 where the data was previously put. 414 @raise conduit.Exceptions.SynchronizeConflictError: if there is a 415 conflict between the data being put, and that which it is overwriting 416 a L{conduit.Exceptions.SynchronizeConflictError} is raised. 417 """ 418 self.set_status(STATUS_SYNC)
419
420 - def delete(self, LUID):
421 """ 422 Deletes data with LUID. 423 """ 424 self.set_status(STATUS_SYNC)
425 426
427 -class TwoWay(DataSource, DataSink):
428 """ 429 Abstract Base Class for TwoWay dataproviders 430 """
431 - def __init__(self):
432 DataSource.__init__(self) 433 DataSink.__init__(self)
434 435
436 -class DataProviderFactory(gobject.GObject):
437 """ 438 Abstract base class for a factory which emits Dataproviders. Users should 439 inherit from this if they wish to provide a loadable module in which 440 dynamic dataproviders become available at runtime. 441 """ 442 __gsignals__ = { 443 "dataprovider-added" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [ 444 gobject.TYPE_PYOBJECT, #Wrapper 445 gobject.TYPE_PYOBJECT]), #Class 446 "dataprovider-removed" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [ 447 gobject.TYPE_STRING]) #Unique key 448 } 449 450 _module_type_ = "dataprovider-factory" 451
452 - def __init__(self, **kwargs):
453 gobject.GObject.__init__(self)
454
455 - def emit_added(self, klass, initargs, category, customKey=None):
456 dpw = ModuleWrapper.ModuleWrapper( 457 klass=klass, 458 initargs=initargs, 459 category=category 460 ) 461 dpw.set_dnd_key(customKey) 462 key = dpw.get_dnd_key() 463 log.debug("DataProviderFactory %s: Emitting dataprovider-added for %s" % (self, key)) 464 self.emit("dataprovider-added", dpw, klass) 465 return key
466
467 - def emit_removed(self, key):
468 log.debug("DataProviderFactory %s: Emitting dataprovider-removed for %s" % (self, key)) 469 self.emit("dataprovider-removed", key)
470
471 - def probe(self):
472 pass
473
474 - def quit(self):
475 """ 476 Shutdown cleanup... 477 """ 478 pass
479
481 """ 482 If the factory needs to offer configuration options then 483 it should return a gtk.widget here. This widget is then packed 484 into the configuration notebook. 485 """ 486 return None
487
488 - def save_configuration(self, ok):
489 """ 490 @param ok: True if the user closed the prefs panel with OK, false if 491 they cancelled it. 492 """ 493 pass
494
495 - def get_name(self):
496 return self.__class__.__name__
497