Module auto_sync
[hide private]
[frames] | no frames]

Source Code for Module auto_sync

  1  ### BITPIM 
  2  ### 
  3  ### Copyright (C) 2005 Simon Capper <skyjunky@sbcglobal.net> 
  4  ### 
  5  ### This program is free software; you can redistribute it and/or modify 
  6  ### it under the terms of the BitPim license as detailed in the LICENSE file. 
  7  ### 
  8   
  9  """ 
 10  Auto synchronization of calender 
 11  This module provides functionality to automatically sync the calender on your PC (outlook etc.) 
 12  with your phone. It will do so at a regular interval if the phone is connected and BitPim is running 
 13  This feature works with all BitPim calender types and phones that support the writing of calenders 
 14   
 15  """ 
 16   
 17  # standard modules 
 18  import copy 
 19  import sha 
 20  import time 
 21  import re 
 22   
 23  # wx modules 
 24  import wx 
 25   
 26  # BitPim modules 
 27  import database 
 28  import guiwidgets 
 29  import common_calendar 
 30  import guiwidgets 
 31  import gui 
 32   
 33  _data_key='auto_sync_settings' 
 34  _filter_keys=['start_offset', 'end_offset', 'no_alarm', 'rpt_events', "alarm_value", 
 35                'ringtone', 'vibrate', 'categories', 'alarm_override'] 
 36   
37 -def _getsettings(mw, profile):
38 settings=AutoSyncSettingsEntry() 39 dict=mw.GetActiveDatabase().getmajordictvalues(_data_key, autosyncsettingsobjectfactory) 40 if profile in dict: 41 settings.set_db_dict(dict[profile]) 42 return settings
43
44 -def UpdateOnConnect(mw, profile="Default Autosync"):
45 settings=_getsettings(mw, profile) 46 return settings.sync_on_connect
47
48 -class SyncSchedule(object):
49 - def __init__(self, log=None):
50 # get standard commport parameters 51 self.__log=log 52 self.__data={}
53
54 - def log(self, log_str):
55 if self.__log is None: 56 print log_str 57 else: 58 self.__log.log(log_str)
59 - def logdata(self, log_str, log_data):
60 if self.__log is None: 61 print log_str,log_data 62 else: 63 self.__log.logdata(log_str, log_data)
64
65 - def importcalenderdata(self):
66 res=0 67 for entry in self.mw.calenders: 68 if entry[0]==self.settings.caltype: 69 filter={} 70 for setting in _filter_keys: 71 if self.settings._data.has_key(setting) and self.settings._data[setting]!=None: 72 # need to convert start and end from scalable values 73 if setting=='start_offset': 74 tm=time.gmtime(time.time()-(self.settings._data[setting]*24*60*60)) 75 date=(tm.tm_year, tm.tm_mon, tm.tm_mday) 76 filter['start']=date 77 elif setting=='end_offset': 78 tm=time.gmtime(time.time()+(self.settings._data[setting]*24*60*60)) 79 date=(tm.tm_year, tm.tm_mon, tm.tm_mday) 80 filter['end']=date 81 elif setting=="categories": 82 # convert categories into list 83 filter[setting]=self.settings._data[setting].split("||") 84 else: 85 filter[setting]=self.settings._data[setting] 86 else: 87 if setting=='start_offset': 88 filter['start']=None 89 if setting=='end_offset': 90 filter['end']=None 91 res=entry[2](self.mw.tree.GetActivePhone(), self.settings.calender_id, filter) 92 if res==1: 93 # imported calender OK!!! 94 self.log("Auto Sync: Imported calender OK") 95 if not res: 96 self.log("Auto Sync: Failed to import calender") 97 return res
98
99 - def sendcalendertophone(self):
100 res=1 101 data={} 102 todo=[] 103 data['calendar_version']=self.mw.phoneprofile.BP_Calendar_Version 104 self.mw.GetActiveCalendarWidget().getdata(data) 105 todo.append( (self.mw.wt.writecalendar, "Calendar", False) ) 106 todo.append((self.mw.wt.rebootcheck, "Phone Reboot")) 107 self.mw.MakeCall(gui.Request(self.mw.wt.getfundamentals), 108 gui.Callback(self.OnDataSendPhoneGotFundamentals, data, todo)) 109 return res
110
111 - def OnDataSendPhoneGotFundamentals(self, data, todo, exception, results):
112 if exception!=None: 113 if not self.silent: 114 self.mw.HandleException(exception) 115 self.log("Auto Sync: Failed, Exception getting phone fundementals") 116 self.mw.OnBusyEnd() 117 return 118 data.update(results) 119 # Now scribble to phone 120 self.log("Auto Sync: Sending results to phone") 121 self.mw.MakeCall(gui.Request(self.mw.wt.senddata, data, todo), 122 gui.Callback(self.OnDataSendCalenderResults))
123
124 - def OnDataSendCalenderResults(self, exception, results):
125 if exception!=None: 126 if not self.silent: 127 self.mw.HandleException(exception) 128 self.log("Auto Sync: Failed, Exception writing calender to phone") 129 self.mw.OnBusyEnd() 130 return 131 if self.silent==0: 132 wx.MessageBox('Phone Synchronized OK', 133 'Synchronize Complete', wx.OK) 134 self.log("Auto Sync: Synchronize Completed OK") 135 self.mw.OnBusyEnd()
136
137 - def sync(self, mw, silent, profile="Default Autosync"):
138 self.silent=silent 139 # start the autosync process 140 # import the calender, find the entry point for the import function 141 self.mw=mw 142 if mw.config.ReadInt("SafeMode", False): 143 self.log("Auto Sync: Disabled, BitPim in safe mode") 144 return 0 145 if wx.IsBusy(): 146 self.log("Auto Sync: Failed, BitPim busy") 147 return 0 148 self.log("Auto Sync: Starting (silent mode=%d)..." % (silent)) 149 self.mw.OnBusyStart() 150 self.mw.GetStatusBar().progressminor(0, 100, 'Auto Calendar Import in progress ...') 151 # retrieve the configuration 152 self.settings=_getsettings(mw, profile) 153 # update BitPims calender 154 res=self.importcalenderdata() 155 if res==1: 156 # send updated calender to the phone 157 res=self.sendcalendertophone() 158 else: 159 self.mw.OnBusyEnd() 160 if silent==0: 161 wx.MessageBox('Unable to Auto-Import Calendar Data', 162 'Auto Calendar Import failed', wx.OK) 163 self.log("Auto Sync: Failed, Unable to synchronize phone schedule") 164 return res
165 166 #-------------------------------------------------------------------------------
167 -class AutoSyncSettingsobject(database.basedataobject):
168 _knownproperties=['caltype', 'calender_id', 'sync_on_connect', 'sync_frequency', \ 169 'start_offset', 'end_offset', 'no_alarm', 'rpt_events', 'categories', \ 170 'ringtone', 'vibrate', 'alarm_override', 'alarm_value'] 171 _knownlistproperties=database.basedataobject._knownlistproperties.copy()
172 - def __init__(self, data=None):
173 if data is None or not isinstance(data, AutoSyncSettingsEntry): 174 return; 175 self.update(data.get_db_dict())
176 autosyncsettingsobjectfactory=database.dataobjectfactory(AutoSyncSettingsobject) 177 178 #-------------------------------------------------------------------------------
179 -class AutoSyncSettingsEntry(object):
180 _caltype_key='caltype' 181 _calender_id_key='calender_id' 182 _sync_on_connect_key='sync_on_connect' 183 _sync_frequency_key='sync_frequency' 184 #_start_offset_key='start_offset' 185 #_end_offset_key='end_offset' 186 #_no_alarm_key='no_alarm' 187 #_categories_key='categories' 188 #_rpt_event_key='rpt_event' 189 #_vibrate_key='vibrate'
190 - def __init__(self):
191 self._data={ 'serials': [] }
192 # we only expect one record, so the ID is fixed 193
194 - def __eq__(self, rhs):
195 return self.caltype==rhs.caltype and self.calender_id==rhs.calender_id and\ 196 self.sync_frequency==rhs.sync_frequency and self.sync_on_connect==rhs.sync_on_connect
197 - def __ne__(self, rhs):
198 return (not __eq__(rhs))
199 - def get(self):
200 return copy.deepcopy(self._data, {})
201 - def set(self, d):
202 self._data={} 203 self._data.update(d)
204
205 - def get_db_dict(self):
206 return self.get()
207 - def set_db_dict(self, d):
208 self.set(d)
209
210 - def _get_id(self):
211 s=self._data.get('serials', []) 212 for n in s: 213 if n.get('sourcetype', None)=='bitpim': 214 return n.get('id', None) 215 return None
216 - def _set_id(self, id):
217 s=self._data.get('serials', []) 218 for n in s: 219 if n.get('sourcetype', None)=='bitpim': 220 n['id']=id 221 return 222 self._data['serials'].append({'sourcetype': 'bitpim', 'id': id } )
223 id=property(fget=_get_id, fset=_set_id) 224
225 - def _set_or_del(self, key, v, v_list=[]):
226 if v is None or v in v_list: 227 if self._data.has_key(key): 228 del self._data[key] 229 else: 230 self._data[key]=v
231
232 - def _get_caltype(self):
233 return self._data.get(self._caltype_key, 'None')
234 - def _set_caltype(self, v):
235 self._set_or_del(self._caltype_key, v, [''])
236 caltype=property(fget=_get_caltype, fset=_set_caltype) 237
238 - def _get_calender_id(self):
239 return self._data.get(self._calender_id_key, '')
240 - def _set_calender_id(self, v):
241 self._set_or_del(self._calender_id_key, v, [''])
242 calender_id=property(fget=_get_calender_id, fset=_set_calender_id) 243
244 - def _get_sync_on_connect(self):
245 return self._data.get(self._sync_on_connect_key, False)
246 - def _set_sync_on_connect(self, v):
247 self._set_or_del(self._sync_on_connect_key, v, [''])
248 sync_on_connect=property(fget=_get_sync_on_connect, fset=_set_sync_on_connect) 249
250 - def _get_sync_frequency(self):
251 return self._data.get(self._sync_frequency_key, 0)
252 - def _set_sync_frequency(self, v):
253 self._set_or_del(self._sync_frequency_key, v, [''])
254 sync_frequency=property(fget=_get_sync_frequency, fset=_set_sync_frequency)
255 256 ### 257 ### The autosync settings dialog 258 ### 259
260 -class AutoSyncSettingsDialog(wx.Dialog):
261 ID_CALSETTINGS=wx.NewId()
262 - def __init__(self, mainwindow, frame, title="Auto Calendar Import Settings", profile="Default Autosync", id=-1):
263 t=title+" - "+profile 264 wx.Dialog.__init__(self, frame, id, t, 265 style=wx.CAPTION|wx.SYSTEM_MENU|wx.DEFAULT_DIALOG_STYLE) 266 self.mw=mainwindow 267 self.profile=profile 268 269 gs=wx.GridBagSizer(10, 10) 270 gs.AddGrowableCol(1) 271 272 # calender type 273 gs.Add( wx.StaticText(self, -1, "Calender Type"), pos=(0,0), flag=wx.ALIGN_CENTER_VERTICAL) 274 # get a list of the possible calender types supported 275 calendertype=('None',) 276 # build a list of calender types for the user to select from 277 for entry in self.mw.calenders: 278 calendertype+=(entry[0], ) 279 280 self.caltype=wx.ComboBox(self, -1, calendertype[0], style=wx.CB_DROPDOWN|wx.CB_READONLY,choices=calendertype) 281 gs.Add( self.caltype, pos=(0,1), flag=wx.ALIGN_CENTER_VERTICAL) 282 gs.Add( wx.Button(self, self.ID_CALSETTINGS, "Calender Settings..."), pos=(0,2), flag=wx.ALIGN_CENTER_VERTICAL) 283 284 # on connect 285 gs.Add( wx.StaticText(self, -1, "Update when phone re-connected"), pos=(1,0), flag=wx.ALIGN_CENTER_VERTICAL) 286 self.sync_on_connect=wx.CheckBox(self, wx.NewId(), "") 287 gs.Add( self.sync_on_connect, pos=(1,1), flag=wx.ALIGN_CENTER_VERTICAL) 288 289 # frequency 290 gs.Add( wx.StaticText(self, -1, "Update Frequency (mins) 0=never"), pos=(2,0), flag=wx.ALIGN_CENTER_VERTICAL) 291 self.sync_frequency=wx.lib.intctrl.IntCtrl(self, -1, value=0, min=0, max=1440) 292 gs.Add( self.sync_frequency, pos=(2,1), flag=wx.ALIGN_CENTER_VERTICAL) 293 294 # crud at the bottom 295 bs=wx.BoxSizer(wx.VERTICAL) 296 bs.Add(gs, 0, wx.EXPAND|wx.ALL, 10) 297 bs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 7) 298 299 but=self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.HELP) 300 bs.Add(but, 0, wx.CENTER|wx.ALL, 10) 301 302 wx.EVT_BUTTON(self, wx.ID_HELP, self.OnHelp) 303 wx.EVT_BUTTON(self, wx.ID_CANCEL, self.OnCancel) 304 wx.EVT_BUTTON(self, wx.ID_OK, self.OnOK) 305 wx.EVT_BUTTON(self, self.ID_CALSETTINGS, self.OnConfigCalender) 306 wx.EVT_COMBOBOX(self, self.caltype.GetId(), self.OnCaltypeChange) 307 308 self.settings=AutoSyncSettingsEntry() 309 310 self.SetSizer(bs) 311 self.SetAutoLayout(True) 312 bs.Fit(self) 313 # read initial values from database 314 self.getfromfs() 315 self.auto_sync_timer_id = wx.NewId() 316 self.auto_sync_timer = wx.Timer(self, self.auto_sync_timer_id) 317 # start the timer 318 self.SetAutoSyncTimer() 319 320 # Retrieve saved settings... (we only care about position) 321 guiwidgets.set_size("AutoSyncSettingsDialog", self, screenpct=-1, aspect=3.5) 322 323 wx.EVT_CLOSE(self, self.OnClose)
324
325 - def OnCaltypeChange(self, _):
326 #see if the value has changed, if so automatically fire up the configuration for the calender 327 if self.settings.caltype!=self.caltype.GetValue(): 328 self.OnConfigCalender() 329 return
330
331 - def OnConfigCalender(self, _=None):
332 old_folder=self.settings.calender_id 333 for entry in self.mw.calenders: 334 if entry[0]==self.caltype.GetValue(): 335 # if the calender type is changing blank out the folder name 336 if self.settings.caltype!=self.caltype.GetValue(): 337 self.settings.calender_id='' 338 filter={} 339 for setting in _filter_keys: 340 if self.settings._data.has_key(setting) and self.settings._data[setting]!=None: 341 if setting=="categories": 342 # convert categories into list 343 filter[setting]=self.settings._data[setting].split("||") 344 else: 345 filter[setting]=self.settings._data[setting] 346 res, temp=entry[1](self.mw, self.settings.calender_id, filter) 347 if res==wx.ID_OK and res != None: 348 # temp is a tuple of the calender_id and the filter settings 349 self.settings.calender_id=temp[0] 350 for setting in _filter_keys: 351 if(temp[1].has_key(setting) and temp[1][setting]!=None): 352 if setting=="categories": 353 # convert categories into storable type 354 cat_str="" 355 for cat in temp[1][setting]: 356 #use a || to separate individual categories 357 if len(cat_str): 358 cat_str=cat_str+"||"+cat 359 else: 360 cat_str=cat 361 self.settings._data[setting]=cat_str 362 else: 363 self.settings._data[setting]=temp[1][setting] 364 else: 365 if self.settings._data.has_key(setting): 366 del self.settings._data[setting] 367 self.settings.caltype=self.caltype.GetValue() 368 else: # cancel pressed 369 #revert back to previous value 370 self.caltype.SetValue(self.settings.caltype) 371 self.settings.calender_id=old_folder 372 return 373 return
374
375 - def OnCancel(self, _):
376 self.saveSize() 377 self.EndModal(wx.ID_CANCEL) 378 return
379
380 - def OnOK(self, _):
381 self.saveSize() 382 self.EndModal(wx.ID_OK) 383 return
384
385 - def OnHelp(self, _):
386 #wx.GetApp().displayhelpid(helpids.ID_AUTOSYNC_DIALOG) 387 return
388
389 - def OnClose(self, evt):
390 self.saveSize() 391 # Don't destroy the dialong, just put it away... 392 self.EndModal(wx.ID_CANCEL) 393 return
394
395 - def _save_to_db(self):
396 db_rr={} 397 self.settings.caltype=self.caltype.GetValue() 398 self.settings.sync_on_connect=self.sync_on_connect.GetValue() 399 self.settings.sync_frequency=self.sync_frequency.GetValue() 400 self.settings.id=self.profile 401 db_rr[self.settings.id]=AutoSyncSettingsobject(self.settings) 402 database.ensurerecordtype(db_rr, autosyncsettingsobjectfactory) 403 self.mw.tree.GetActivePhone().GetDatabase().savemajordict(_data_key, db_rr)
404
405 - def getfromfs(self):
406 self.settings=_getsettings(self.mw, self.profile) 407 self.caltype.SetValue(self.settings.caltype) 408 self.sync_on_connect.SetValue(int(self.settings.sync_on_connect)) 409 self.sync_frequency.SetValue(int(self.settings.sync_frequency)) 410 return
411
412 - def IsConfigured(self):
413 return self.settings.caltype!='None'
414
415 - def updatevariables(self):
416 self.mw.auto_save_dict=self.settings
417
418 - def ShowModal(self):
419 self.getfromfs() 420 ec=wx.Dialog.ShowModal(self) 421 if ec==wx.ID_OK: 422 self._save_to_db() 423 self.updatevariables() 424 self.SetAutoSyncTimer() 425 return ec
426
427 - def saveSize(self):
428 guiwidgets.save_size("AutoSyncSettingsDialog", self.GetRect())
429
430 - def SetAutoSyncTimer(self):
431 # stop the previous timer (if any) 432 self.auto_sync_timer.Stop() 433 oneShot = True 434 timeout=self.settings.sync_frequency*60000 # convert msecs 435 if timeout: 436 self.auto_sync_timer.Start(timeout, oneShot) 437 self.Bind(wx.EVT_TIMER, self.OnTimer, self.auto_sync_timer)
438
439 - def OnTimer(self, event):
440 self.mw.log("Auto Sync: Timed update") 441 SyncSchedule(self.mw).sync(self.mw, silent=1) 442 self.SetAutoSyncTimer()
443