Package conduit :: Package modules :: Module SynceModule
[hide private]

Source Code for Module conduit.modules.SynceModule

  1  import conduit 
  2  import conduit.utils as Utils 
  3  import conduit.dataproviders.DataProvider as DataProvider 
  4  import conduit.dataproviders.DataProviderCategory as DataProviderCategory 
  5  import conduit.dataproviders.HalFactory as HalFactory 
  6  import conduit.datatypes.Note as Note 
  7  import conduit.datatypes.Contact as Contact 
  8  import conduit.Exceptions as Exceptions 
  9   
 10  import xml.dom.minidom 
 11  import vobject 
 12   
 13  import logging 
 14  log = logging.getLogger("modules.SynCE") 
 15   
 16  import os.path 
 17  import traceback 
 18  import dbus 
 19  import threading 
 20  import gobject 
 21  import array 
 22   
 23  from gettext import gettext as _ 
 24   
 25  SYNC_ITEM_CALENDAR  = 0 
 26  SYNC_ITEM_CONTACTS  = 1 
 27  SYNC_ITEM_EMAIL     = 2 
 28  SYNC_ITEM_FAVORITES = 3 
 29  SYNC_ITEM_FILES     = 4 
 30  SYNC_ITEM_MEDIA     = 5 
 31  SYNC_ITEM_NOTES     = 6 
 32  SYNC_ITEM_TASKS     = 7 
 33   
 34  TYPETONAMES = { 
 35      SYNC_ITEM_CONTACTS  : "Contacts", 
 36      SYNC_ITEM_CALENDAR  : "Calendar", 
 37      SYNC_ITEM_TASKS     : "Tasks" 
 38  } 
 39   
 40  CHANGE_ADDED        = 1 
 41  CHANGE_MODIFIED     = 4 
 42  CHANGE_DELETED      = 3 
 43   
 44  MODULES = { 
 45      "SynceFactory" :        { "type": "dataprovider-factory" }, 
 46  } 
 47   
48 -class SynceFactory(HalFactory.HalFactory):
49 50 SUPPORTED_PARTNERSHIPS = ("Calendar", "Contacts", "Tasks") 51
52 - def __init__(self, **kwargs):
53 HalFactory.HalFactory.__init__(self, **kwargs) 54 self._found = False 55 self._item_types = {} 56 self._partnerships = []
57
58 - def _get_item_types_rx(self, item_types):
59 self._item_types = item_types
60
61 - def _get_partnerships_rx(self, partnerships):
62 self._partnerships = partnerships
63
64 - def _create_partnership_rx(self, guid):
65 print args
66
67 - def _create_partnership_error(self, e):
68 log.warn("Failed to create partnership: %s" % e._dbus_error_name)
69
70 - def _on_create_partnership_clicked(self, sender, mod):
71 #create partnerships for Contact, Calendar, Tasks 72 ids = [id for id, name in self._item_types.items() if str(name) in self.SUPPORTED_PARTNERSHIPS] 73 self.engine.CreatePartnership( 74 "Conduit", #partnership name 75 ids, #ids of those items to sync 76 reply_handler=self._create_partnership_rx, 77 error_handler=self._create_partnership_error 78 )
79
80 - def _found_device(self):
81 self._found = True 82 83 #call async so we dont block at startup 84 #reply_handler will be called with the method's return values as arguments; or 85 #the error_handler 86 self.engine = dbus.Interface( 87 dbus.SessionBus().get_object('org.synce.SyncEngine', '/org/synce/SyncEngine'), 88 'org.synce.SyncEngine' 89 ) 90 self.engine.GetItemTypes( 91 reply_handler=self._get_item_types_rx, 92 error_handler=lambda *args: None 93 ) 94 self.engine.GetPartnerships( 95 reply_handler=self._get_partnerships_rx, 96 error_handler=lambda *args: None 97 )
98
99 - def is_interesting(self, device, props):
100 if props.has_key("sync.plugin") and props["sync.plugin"]=="synce": 101 self._found_device() 102 return True 103 return False
104
105 - def get_category(self, udi, **kwargs):
106 return DataProviderCategory.DataProviderCategory( 107 "Windows Mobile", 108 "windows", 109 udi)
110
111 - def get_dataproviders(self, udi, **kwargs):
112 return [SynceContactsTwoWay, SynceCalendarTwoWay, SynceTasksTwoWay]
113
114 - def setup_configuration_widget(self):
115 116 if self._found: 117 import gtk 118 import socket 119 120 vbox = gtk.VBox(False,5) 121 mod = gtk.ListStore( 122 gobject.TYPE_PYOBJECT, #parnership id 123 gobject.TYPE_PYOBJECT, #parnership guid 124 str,str,str) #device name, pc name, items 125 treeview = gtk.TreeView(mod) 126 127 #Three colums: device name, pc name, items 128 index = 2 129 for name in ("Device", "Computer", "Items to Synchronize"): 130 col = gtk.TreeViewColumn( 131 name, 132 gtk.CellRendererText(), 133 text=index) 134 treeview.append_column(col) 135 index = index + 1 136 vbox.pack_start(treeview,True,True) 137 138 btn = gtk.Button(None,gtk.STOCK_ADD) 139 btn.set_label("Create Partnership") 140 btn.connect("clicked", self._on_create_partnership_clicked, mod) 141 vbox.pack_start(btn, False, False) 142 143 #add the existing partnerships 144 for id,guid,name,hostname,devicename,storetype,items in self._partnerships: 145 mod.append(( 146 id, 147 guid, 148 str(devicename), 149 str(hostname), 150 ", ".join([str(self._item_types[item]) for item in items])) 151 ) 152 #disable partnership if one exists 153 if str(hostname) == socket.gethostname(): 154 btn.set_sensitive(False) 155 156 return vbox 157 return None
158
159 - def save_configuration(self, ok):
160 pass
161
162 -class SyncEngineWrapper(object):
163 """ 164 Wrap the SyncEngine dbus foo (thinly) 165 Make it synchronous and (eventually) borg it so multiple dp's share one connection 166 """ 167
168 - def __init__(self):
169 self.engine = None 170 self.SyncEvent = threading.Event() 171 self.PrefillEvent = threading.Event()
172
173 - def _OnSynchronized(self):
174 log.info("Synchronize: Got _OnSynchronized") 175 self.SyncEvent.set()
176
177 - def _OnPrefillComplete(self):
178 log.info("Synchronize: Got _OnPrefillComplete") 179 self.PrefillEvent.set()
180
181 - def Connect(self):
182 if not self.engine: 183 self.bus = dbus.SessionBus() 184 proxy = self.bus.get_object("org.synce.SyncEngine", "/org/synce/SyncEngine") 185 self.engine = dbus.Interface(proxy, "org.synce.SyncEngine") 186 self.engine.connect_to_signal("Synchronized", lambda: gobject.idle_add(self._OnSynchronized)) 187 self.engine.connect_to_signal("PrefillComplete", lambda: gobject.idle_add(self._OnPrefillComplete))
188
189 - def Prefill(self, items):
190 self.PrefillEvent.clear() 191 rc = self.engine.PrefillRemote(items) 192 if rc == 1: 193 self.PrefillEvent.wait(10) 194 log.info("Prefill: completed (rc=%d)" % rc) 195 return rc
196
197 - def Synchronize(self):
198 self.SyncEvent.clear() 199 self.engine.Synchronize() 200 self.SyncEvent.wait(10) 201 log.info("Synchronize: completed")
202
203 - def GetRemoteChanges(self, type_ids):
204 return self.engine.GetRemoteChanges(type_ids)
205
206 - def AcknowledgeRemoteChanges(self, acks):
207 self.engine.AcknowledgeRemoteChanges(acks)
208
209 - def AddLocalChanges(self, chgset):
210 self.engine.AddLocalChanges(chgset)
211
212 - def FlushItemDB(self):
213 self.engine.FlushItemDB()
214
215 - def Disconnect(self):
216 self.engine = None
217
218 -class SynceTwoWay(DataProvider.TwoWay):
219 - def __init__(self, *args):
220 DataProvider.TwoWay.__init__(self) 221 self.objects = {}
222
223 - def refresh(self):
224 DataProvider.TwoWay.refresh(self) 225 self.engine = SyncEngineWrapper() 226 self.engine.Connect() 227 self.engine.Synchronize()
228
229 - def get_all(self):
230 DataProvider.TwoWay.get_all(self) 231 self.objects = {} 232 self.engine.Prefill([TYPETONAMES[self._type_id_]]) 233 chgs = self.engine.GetRemoteChanges([self._type_id_]) 234 for guid, chgtype, data in chgs[self._type_id_]: 235 uid = array.array('B', guid).tostring() 236 blob = array.array('B', data).tostring() 237 self.objects[uid] = self._blob_to_data(uid, blob) 238 239 log.info("Got %s objects" % len(self.objects)) 240 return self.objects.keys()
241
242 - def get(self, LUID):
243 DataProvider.TwoWay.get(self, LUID) 244 return self.objects[LUID]
245
246 - def put(self, obj, overwrite, LUID=None):
247 DataProvider.TwoWay.put(self, obj, overwrite, LUID) 248 existing = None 249 if LUID != None: 250 existing = self.get(LUID) 251 if existing != None: 252 comp = obj.compare(existing) 253 if comp == conduit.datatypes.COMPARISON_EQUAL: 254 log.info("objects are equal") 255 elif overwrite == True or comp == conduit.datatypes.COMPARISON_NEWER: 256 self.update(LUID, obj) 257 else: 258 raise Exceptions.SynchronizeConflictError(comp, obj, existing) 259 else: 260 LUID = self.add(obj) 261 return self.get(LUID).get_rid()
262
263 - def _commit(self, uid, chgtype, blob):
264 _uid = array.array('B') 265 _uid.fromstring(uid) 266 _blob = array.array('B') 267 _blob.fromstring(blob) 268 self.engine.AddLocalChanges({ 269 self._type_id_: ( 270 (_uid, chgtype, _blob), 271 ) 272 }) 273 # FIXME: This is a HACK to make it easy (ish) to return a RID in put() 274 if chgtype != CHANGE_DELETED: 275 self.objects[uid] = self._blob_to_data(uid,blob) 276 else: 277 del self.objects[uid]
278
279 - def add(self, obj):
280 LUID = Utils.uuid_string() 281 self._commit(LUID, CHANGE_ADDED, self._data_to_blob(obj)) 282 return LUID
283
284 - def update(self, LUID, obj):
285 self._commit(LUID, CHANGE_MODIFIED, self._data_to_blob(obj))
286
287 - def delete(self, LUID):
288 DataProvider.TwoWay.delete(self,LUID) 289 self._commit(LUID, CHANGE_DELETED, "")
290
291 - def finish(self, aborted, error, conflict):
292 DataProvider.TwoWay.finish(self) 293 #self.engine.AcknowledgeRemoteChanges 294 self.engine.Synchronize() 295 self.engine.FlushItemDB()
296
297 - def _blob_to_data(self, uid, blob):
298 #raise NotImplementedError 299 d = Note.Note(uid, blob) 300 d.set_UID(uid) 301 return d
302
303 - def _data_to_blob(self, data):
304 #raise NotImplementedError 305 return data.get_contents()
306
307 - def get_UID(self):
308 return "synce-%d" % self._type_id_
309
310 -class SynceContactsTwoWay(SynceTwoWay):
311 _name_ = "Contacts" 312 _description_ = "Windows Mobile Contacts" 313 _module_type_ = "twoway" 314 _in_type_ = "contact" 315 _out_type_ = "contact" 316 _icon_ = "contact-new" 317 _type_id_ = SYNC_ITEM_CONTACTS 318 _configurable_ = False 319
320 - def _blob_to_data(self, uid, blob):
321 parser = xml.dom.minidom.parseString(blob) 322 root = parser.getElementsByTagName("contact")[0] 323 324 c = Contact.Contact() 325 c.set_UID(uid) 326 327 def S(node): 328 if node and node[0].childNodes: 329 return node[0].firstChild.wholeText 330 return ""
331 332 for node in root.childNodes: 333 if node.nodeName == "FileAs": 334 pass 335 elif node.nodeName == "FormattedName": 336 try: 337 c.vcard.fn 338 except: 339 c.vcard.add('fn') 340 c.vcard.fn.value = S(node.getElementsByTagName('Content')) 341 elif node.nodeName == "Name": 342 family = S(node.getElementsByTagName('LastName')) 343 given = S(node.getElementsByTagName('FirstName')) 344 try: 345 c.vcard.n 346 except: 347 c.vcard.add('n') 348 c.vcard.n.value = vobject.vcard.Name(family=family, given=given) 349 elif node.nodeName == "Nickname": 350 pass 351 elif node.nodeName == "EMail": 352 email = c.vcard.add('email') 353 email.value = S(node.getElementsByTagName('Content')) 354 email.type_param = 'INTERNET' 355 elif node.nodeName == "Photo": 356 pass 357 elif node.nodeName == "Categories": 358 pass 359 elif node.nodeName == "Assistant": 360 pass 361 elif node.nodeName == "Manager": 362 pass 363 elif node.nodeName == "Organization": 364 pass 365 elif node.nodeName == "Spouse": 366 pass 367 elif node.nodeName == "Telephone": 368 tel = c.vcard.add('tel') 369 tel.value = S(node.getElementsByTagName('Content')) 370 for type_param in node.getElementsByTagName('Type'): 371 tel.params.setdefault('TYPE',[]).append(S([type_param])) 372 elif node.nodeName == "Title": 373 pass 374 elif node.nodeName == "Url": 375 pass 376 elif node.nodeName == "Uid": 377 pass 378 elif node.nodeName == "Revision": 379 pass 380 else: 381 log.warning("Unhandled node: %s" % node.nodeName) 382 383 return c
384
385 - def _data_to_blob(self, data):
386 v = data.vcard 387 doc = xml.dom.minidom.Document() 388 node = doc.createElement("contact") 389 for chunk, value in v.contents.iteritems(): 390 if chunk == "account": 391 pass 392 elif chunk == "tel": 393 for v in value: 394 t = doc.createElement("Telephone") 395 if 'TYPE' in v.params: 396 for type_param in v.params['TYPE']: 397 k = doc.createElement("Type") 398 k.appendChild(doc.createTextNode(type_param)) 399 t.appendChild(k) 400 c = doc.createElement("Content") 401 c.appendChild(doc.createTextNode(v.value)) 402 t.appendChild(c) 403 node.appendChild(t) 404 elif chunk == "bday": 405 pass 406 elif chunk == "n": 407 v = value[0] 408 n = doc.createElement("Name") 409 f = doc.createElement("FirstName") 410 f.appendChild(doc.createTextNode(v.value.given)) 411 n.appendChild(f) 412 l = doc.createElement("LastName") 413 l.appendChild(doc.createTextNode(v.value.family)) 414 n.appendChild(l) 415 a = doc.createElement("Additional") 416 n.appendChild(a) 417 p = doc.createElement("Prefix") 418 n.appendChild(p) 419 s = doc.createElement("Suffix") 420 n.appendChild(s) 421 node.appendChild(n) 422 elif chunk == "version": 423 pass 424 elif chunk == "org": 425 pass 426 elif chunk == "nickname": 427 pass 428 elif chunk == "email": 429 for v in value: 430 e = doc.createElement("EMail") 431 c = doc.createElement("Content") 432 c.appendChild(doc.createTextNode(v.value)) 433 e.appendChild(c) 434 n.appendChild(e) 435 elif chunk == "fn": 436 v = value[0] 437 fn = doc.createElement("FormattedName") 438 c = doc.createElement("Content") 439 c.appendChild(doc.createTextNode(v.value)) 440 fn.appendChild(c) 441 node.appendChild(fn) 442 else: 443 log.warning("Unhandled chunk (%s)" % chunk) 444 445 doc.appendChild(node) 446 return doc.toxml()
447
448 -class SynceCalendarTwoWay(SynceTwoWay):
449 _name_ = "Calendar" 450 _description_ = "Windows Mobile Calendar" 451 _module_type_ = "twoway" 452 _in_type_ = "note" 453 _out_type_ = "note" 454 _icon_ = "contact-new" 455 _type_id_ = SYNC_ITEM_CALENDAR 456 _configurable_ = False
457
458 -class SynceTasksTwoWay(SynceTwoWay):
459 _name_ = "Tasks" 460 _description_ = "Windows Mobile Tasks" 461 _module_type_ = "twoway" 462 _in_type_ = "note" 463 _out_type_ = "note" 464 _icon_ = "contact-new" 465 _type_id_ = SYNC_ITEM_TASKS 466 _configurable_ = False
467