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
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
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
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
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
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
106 """
107 Called just before the application quits.
108 """
109 pass
110
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
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
139
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
150 """
151 @returns: The current dataproviders status
152 """
153 return self.status
154
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
177
185
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
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
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
222 """
223 Restores applications settings
224 @param config: dictionary of dataprovider settings to restore
225 """
226 for c in config:
227
228
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
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
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
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
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
291
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
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
326 """
327 @returns: The DataProvider name, to be displayed in the UI
328 """
329 return self._name_
330
332 """
333 Base Class for DataSources.
334 """
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
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
364 """
365 Returns an array of all the LUIDs this dataprovider holds.
366 """
367 self.set_status(STATUS_SYNC)
368 return []
369
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
387 """
388 Base Class for DataSinks
389 """
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
425
426
427 -class TwoWay(DataSource, DataSink):
428 """
429 Abstract Base Class for TwoWay dataproviders
430 """
434
435
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,
445 gobject.TYPE_PYOBJECT]),
446 "dataprovider-removed" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
447 gobject.TYPE_STRING])
448 }
449
450 _module_type_ = "dataprovider-factory"
451
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
468 log.debug("DataProviderFactory %s: Emitting dataprovider-removed for %s" % (self, key))
469 self.emit("dataprovider-removed", key)
470
473
475 """
476 Shutdown cleanup...
477 """
478 pass
479
487
489 """
490 @param ok: True if the user closed the prefs panel with OK, false if
491 they cancelled it.
492 """
493 pass
494
496 return self.__class__.__name__
497