Package thirdparty :: Package conduit :: Module SyncmlModule
[hide private]
[frames] | no frames]

Source Code for Module thirdparty.conduit.SyncmlModule

  1  """ 
  2  The original of this file was taken from a Conduit branch of John Carr (http://git.gnome.org/cgit/conduit/log/?h=syncml), 
  3  It was modified significantly for PISI use by Michael Pilgermann. 
  4   
  5  Major keys are: 
  6   - there is a bit of a problem as Syncml is actually the server (making all the comparing) - but PISI has its engine itself 
  7   - consequently, we are always using slow sync (all items have to be transmitted) and tell the Syncml server our changes afterwards 
  8    
  9  The entire implementation (of libsyncml) is asynchronous. We just tell the server, we want to sync - then the server comes back at some 
 10  point to ask for further information. So it's all based on handlers, which have to be registered in the beginning - all the work has 
 11  to be done in there. 
 12  """ 
 13   
 14  import pysyncml 
 15  import enums 
 16  import threading 
 17  import uuid 
 18   
 19  SYNCML_LOGFILE = '/tmp/pisi-syncml.log' 
 20  """Log file for Syncml module debug output""" 
 21   
22 -class Log:
23 """ 24 There was this nice logging facility in the original 25 26 I only redirected to a file - there might always be some problems with this complex protocol. 27 """
28 - def __init__(self):
29 self._file = open(SYNCML_LOGFILE, 'w')
30
31 - def error(self, arg):
32 self._file.write("Error: " + arg + "\n")
33
34 - def info(self, arg):
35 self._file.write("Info: " + arg + "\n")
36
37 - def debug(self, arg):
38 self._file.write("Debug: " + arg + "\n")
39 40 log = Log() 41
42 -class SyncmlDataProvider():
43 """ 44 This class is doing all the work 45 46 Don't instantiate it - there is a datastore and a connection missing; go for SyncmlContactsInterface instead. 47 """ 48 49 _syncml_version_ = "1.1" 50
51 - def handle_event(self, sync_object, event, userdata, err):
52 """ handle_event is called by libsyncml at different stages of a sync 53 This includes when this connect and disconnect and when errors occur. 54 55 It WILL happen in a different thread to whatever thread called syncobject.run() 56 """ 57 if event == enums.SML_DATA_SYNC_EVENT_ERROR: 58 log.error("CB: An error has occurred: %s" % err.message) 59 return 60 61 if event == enums.SML_DATA_SYNC_EVENT_CONNECT: 62 log.info("CB: Connect") 63 return 64 65 if event == enums.SML_DATA_SYNC_EVENT_DISCONNECT: 66 log.info("CB: Disconnect") 67 return 68 69 if event == enums.SML_DATA_SYNC_EVENT_FINISHED: 70 log.info("CB: Session complete") 71 self._refresh_lock.set() 72 return 73 74 if event == enums.SML_DATA_SYNC_EVENT_GOT_ALL_ALERTS: 75 log.info("CB: Got all alerts" ) 76 self._syncml_sendall() 77 return 78 79 if event == enums.SML_DATA_SYNC_EVENT_GOT_ALL_CHANGES: 80 log.info("CB: Got All Changes") 81 return 82 83 if event == enums.SML_DATA_SYNC_EVENT_GOT_ALL_MAPPINGS: 84 log.info("CB: Got All Mappings") 85 return 86 87 log.error("An error has occurred (Unexpected event)")
88 89
90 - def handle_change(self, sync_object, source, type, uid, data, size, userdata, err):
91 """ handle_change is called by libsyncml to tells us about changes on the server or device 92 we are synchronising to. 93 94 This WILL happen in a different thread to where sync is happening. 95 """ 96 log.debug("got change (%s)" %(['UNKNOWN','ADD', 'REPLACE','DELETE'][type])) 97 self._rawContacts[uid] = data 98 err = pysyncml.Error() 99 self.syncobj.add_mapping(source, uid, uid, pysyncml.byref(err)) 100 return 1
101
102 - def handle_devinf(self, sync_object, info, userdata, err):
103 """ handle_devinf is called by libsyncml to tells us information such as device mfr and firmware 104 version of whatever we are syncing against. 105 106 This WILL happen in a different thread to where sync is happening. 107 There is a known bug with SE C902 where this is called twice - ignore the 2nd one or crashes 108 occur 109 """ 110 return 1
111
112 - def handle_change_status(self, sync_object, code, newuid, userdata, err):
113 """ 114 200 is good ... 115 201 seems to indicate OK (sometimes not :() 116 211 - I think not 117 """ 118 log.info("CB: Handle Change Status (ret: %s)" %code) 119 if code < 200 or 299 < code: 120 return 0 121 return 1
122
123 - def handle_get_anchor(self, sync_object, name, userdata, err):
124 anchor = self.anchor[name] if name in self.anchor else None 125 log.debug("get_anchor('%s') returns %s" % (name, anchor or "None")) 126 return pysyncml.strdup(anchor) if anchor else None
127
128 - def handle_set_anchor(self, sync_object, name, value, userdata, err):
129 log.info("CB: Set Anchor") 130 self.anchor[name] = value 131 return 1
132
133 - def handle_get_alert_type(self, sync_object, source, alert_type, userdata, err):
134 log.info("CB: Get Alert Type") 135 return enums.SML_ALERT_SLOW_SYNC
136
137 - def _syncml_sendall(self):
138 err = pysyncml.Error() 139 for key in self._actions[0].keys(): 140 LUID = str(uuid.uuid4()) 141 self.syncobj.add_change(self._store_, enums.SML_CHANGE_ADD, LUID, self._actions[0][key], len(self._actions[0][key]), "", pysyncml.byref(err)) 142 for key in self._actions[1].keys(): 143 self.syncobj.add_change(self._store_, enums.SML_CHANGE_DELETE, key, "", 0, key, pysyncml.byref(err)) 144 for key in self._actions[2].keys(): 145 self.syncobj.add_change(self._store_, enums.SML_CHANGE_REPLACE, key, self._actions[2][key], len(self._actions[2][key]), key, pysyncml.byref(err)) 146 self.syncobj.send_changes(pysyncml.byref(err))
147
148 - def _syncml_run(self):
149 err = pysyncml.Error() 150 151 self._setup_connection(err) 152 self._setup_datastore(err) 153 154 self.syncobj.set_option(enums.SML_DATA_SYNC_CONFIG_VERSION, self._syncml_version_, pysyncml.byref(err)) 155 self.syncobj.set_option(enums.SML_DATA_SYNC_CONFIG_IDENTIFIER, self._syncml_identifier_, pysyncml.byref(err)) 156 self.syncobj.set_option(enums.SML_DATA_SYNC_CONFIG_USE_WBXML, "1", pysyncml.byref(err)) 157 158 self.syncobj.register_event_callback(self._handle_event, None) 159 self.syncobj.register_change_callback(self._handle_change, None) 160 self.syncobj.register_handle_remote_devinf_callback(self._handle_devinf, None) 161 self.syncobj.register_change_status_callback(self._handle_change_status) 162 self.syncobj.register_set_anchor_callback(self._handle_set_anchor, None) 163 self.syncobj.register_get_anchor_callback(self._handle_get_anchor, None) 164 self.syncobj.register_get_alert_type_callback(self._handle_get_alert_type, None) 165 166 if not self.syncobj.init(pysyncml.byref(err)): 167 log.error("Unable to prepare synchronisation") 168 return 169 170 if not self.syncobj.run(pysyncml.byref(err)): 171 log.error("Unable to synchronise") 172 log.error (err.message) 173 return 174 175 log.info("running sync..") 176 return err
177
178 - def __init__(self, address):
179 """ 180 Initializes all parameters 181 """ 182 self.address = address 183 self.anchor = {} 184 self._rawContacts = {} 185 self._actions = [{}, {}, {}] 186 187 self._handle_event = pysyncml.EventCallback(self.handle_event) 188 self._handle_change = pysyncml.ChangeCallback(self.handle_change) 189 self._handle_devinf = pysyncml.HandleRemoteDevInfCallback(self.handle_devinf) 190 self._handle_change_status = pysyncml.ChangeStatusCallback(self.handle_change_status) 191 self._handle_get_anchor = pysyncml.GetAnchorCallback(self.handle_get_anchor) 192 self._handle_set_anchor = pysyncml.SetAnchorCallback(self.handle_set_anchor) 193 self._handle_get_alert_type = pysyncml.GetAlertTypeCallback(self.handle_get_alert_type) 194 195 self._refresh_lock = threading.Event()
196 # self._put_lock = threading.Event() 197
198 - def downloadItems(self):
199 """ 200 Public Interface: download all contacts from server 201 """ 202 self._syncml_run() 203 self._refresh_lock.wait(60) 204 self._refresh_lock.clear() 205 return self._rawContacts
206
207 - def applyChanges(self, adds = {}, dels = {}, mods = {}):
208 """ 209 Public Interface: apply changes on server 210 """ 211 self._syncml_run() 212 self._refresh_lock.wait(60) 213 self._refresh_lock.clear() 214 215 self._actions = [adds, dels, mods] 216 self._syncml_run() 217 self._refresh_lock.wait(60) 218 self._refresh_lock.clear()
219
220 - def finish(self):
221 self.syncobj.unref(pysyncml.byref(self.syncobj))
222 223 224
225 -class HttpClient(SyncmlDataProvider):
226 """ 227 Encapsulates the connection establishment via HTTP 228 """ 229
230 - def __init__(self, username, password):
231 SyncmlDataProvider.__init__(self, self._address_) 232 self.username = username 233 self.password = password
234
235 - def _setup_connection(self, err = pysyncml.Error()):
236 self.syncobj = pysyncml.SyncObject.new(enums.SML_SESSION_TYPE_CLIENT, enums.SML_TRANSPORT_HTTP_CLIENT, pysyncml.byref(err)) 237 self.syncobj.set_option(enums.SML_TRANSPORT_CONFIG_URL, self._address_, pysyncml.byref(err)) 238 239 if self.username != None and len(self.username) > 0: 240 self.syncobj.set_option(enums.SML_DATA_SYNC_CONFIG_AUTH_USERNAME, self.username, pysyncml.byref(err)) 241 self.syncobj.set_option(enums.SML_DATA_SYNC_CONFIG_AUTH_PASSWORD, self.password, pysyncml.byref(err)) 242 243 self._session_type = enums.SML_SESSION_TYPE_CLIENT
244 245
246 -class ContactsProvider(SyncmlDataProvider):
247 """ 248 Encapsulated the data handling (VCF) 249 """ 250 251 _name_ = "Contacts" 252 _description_ = "Contacts" 253 _module_type_ = "twoway" 254 _in_type_ = "contact" 255 _out_type_ = "contact" 256 _icon_ = "contact-new" 257 _configurable_ = False 258 259 _mime_ = "text/x-vcard" 260
261 - def _setup_datastore(self, err = pysyncml.Error()):
262 self.syncobj.add_datastore(self._mime_, None, self._store_, pysyncml.byref(err))
263 264
265 -class CalendarProvider(SyncmlDataProvider):
266 """ 267 Encapsulated the data handling (ICS) 268 """ 269 _name_ = "Calendar" 270 _description_ = "Calendar" 271 _module_type_ = "twoway" 272 _in_type_ = "event" 273 _out_type_ = "event" 274 _icon_ = "x-office-calendar" 275 _configurable_ = False 276 277 _mime_ = "text/x-calendar" 278
279 - def _setup_datastore(self):
280 err = pysyncml.Error() 281 self.syncobj.add_datastore(self._mime_, None, self._store_, pysyncml.byref(err))
282 283
284 -class SyncmlContactsInterface(HttpClient, ContactsProvider):
285 """ 286 Public Interface that should be used from outside 287 """ 288 _mime_ = "text/vcard" 289 _syncml_version_ = "1.2" 290
291 - def __init__(self, url, username, password, store, syncml_identifier):
292 self._address_ = url 293 self._store_ = store 294 self._syncml_identifier_ = syncml_identifier 295 HttpClient.__init__(self, username, password)
296
297 -class SyncmlCalendarInterface(HttpClient, CalendarProvider):
298 """ 299 Public Interface that should be used from outside 300 """ 301 _mime_ = "text/x-calendar" 302 _syncml_version_ = "1.2" 303
304 - def __init__(self, url, username, password, store, syncml_identifier):
305 self._address_ = url 306 self._store_ = store 307 self._syncml_identifier_ = syncml_identifier 308 HttpClient.__init__(self, username, password)
309