0001 ### BITPIM 0002 ### 0003 ### Copyright (C) 2005 Simon Capper <skyjunky@sbcglobal.net> 0004 ### 0005 ### This program is free software; you can redistribute it and/or modify 0006 ### it under the terms of the BitPim license as detailed in the LICENSE file. 0007 ### 0008 0009 """ 0010 Auto synchronization of calender 0011 This module provides functionality to automatically sync the calender on your PC (outlook etc.) 0012 with your phone. It will do so at a regular interval if the phone is connected and BitPim is running 0013 This feature works with all BitPim calender types and phones that support the writing of calenders 0014 0015 """ 0016 0017 # standard modules 0018 import copy 0019 import sha 0020 import time 0021 import re 0022 0023 # wx modules 0024 import wx 0025 0026 # BitPim modules 0027 import database 0028 import guiwidgets 0029 import common_calendar 0030 import guiwidgets 0031 import gui 0032 0033 _data_key='auto_sync_settings' 0034 _filter_keys=['start_offset', 'end_offset', 'no_alarm', 'rpt_events', "alarm_value", 0035 'ringtone', 'vibrate', 'categories', 'alarm_override'] 0036 0037 def _getsettings(mw, profile): 0038 settings=AutoSyncSettingsEntry() 0039 dict=mw.GetActiveDatabase().getmajordictvalues(_data_key, autosyncsettingsobjectfactory) 0040 if profile in dict: 0041 settings.set_db_dict(dict[profile]) 0042 return settings 0043 0044 def UpdateOnConnect(mw, profile="Default Autosync"): 0045 settings=_getsettings(mw, profile) 0046 return settings.sync_on_connect 0047 0048 class SyncSchedule(object): 0049 def __init__(self, log=None): 0050 # get standard commport parameters 0051 self.__log=log 0052 self.__data={} 0053 0054 def log(self, log_str): 0055 if self.__log is None: 0056 print log_str 0057 else: 0058 self.__log.log(log_str) 0059 def logdata(self, log_str, log_data): 0060 if self.__log is None: 0061 print log_str,log_data 0062 else: 0063 self.__log.logdata(log_str, log_data) 0064 0065 def importcalenderdata(self): 0066 res=0 0067 for entry in self.mw.calenders: 0068 if entry[0]==self.settings.caltype: 0069 filter={} 0070 for setting in _filter_keys: 0071 if self.settings._data.has_key(setting) and self.settings._data[setting]!=None: 0072 # need to convert start and end from scalable values 0073 if setting=='start_offset': 0074 tm=time.gmtime(time.time()-(self.settings._data[setting]*24*60*60)) 0075 date=(tm.tm_year, tm.tm_mon, tm.tm_mday) 0076 filter['start']=date 0077 elif setting=='end_offset': 0078 tm=time.gmtime(time.time()+(self.settings._data[setting]*24*60*60)) 0079 date=(tm.tm_year, tm.tm_mon, tm.tm_mday) 0080 filter['end']=date 0081 elif setting=="categories": 0082 # convert categories into list 0083 filter[setting]=self.settings._data[setting].split("||") 0084 else: 0085 filter[setting]=self.settings._data[setting] 0086 else: 0087 if setting=='start_offset': 0088 filter['start']=None 0089 if setting=='end_offset': 0090 filter['end']=None 0091 res=entry[2](self.mw.tree.GetActivePhone(), self.settings.calender_id, filter) 0092 if res==1: 0093 # imported calender OK!!! 0094 self.log("Auto Sync: Imported calender OK") 0095 if not res: 0096 self.log("Auto Sync: Failed to import calender") 0097 return res 0098 0099 def sendcalendertophone(self): 0100 res=1 0101 data={} 0102 todo=[] 0103 data['calendar_version']=self.mw.phoneprofile.BP_Calendar_Version 0104 self.mw.GetActiveCalendarWidget().getdata(data) 0105 todo.append( (self.mw.wt.writecalendar, "Calendar", False) ) 0106 todo.append((self.mw.wt.rebootcheck, "Phone Reboot")) 0107 self.mw.MakeCall(gui.Request(self.mw.wt.getfundamentals), 0108 gui.Callback(self.OnDataSendPhoneGotFundamentals, data, todo)) 0109 return res 0110 0111 def OnDataSendPhoneGotFundamentals(self, data, todo, exception, results): 0112 if exception!=None: 0113 if not self.silent: 0114 self.mw.HandleException(exception) 0115 self.log("Auto Sync: Failed, Exception getting phone fundementals") 0116 self.mw.OnBusyEnd() 0117 return 0118 data.update(results) 0119 # Now scribble to phone 0120 self.log("Auto Sync: Sending results to phone") 0121 self.mw.MakeCall(gui.Request(self.mw.wt.senddata, data, todo), 0122 gui.Callback(self.OnDataSendCalenderResults)) 0123 0124 def OnDataSendCalenderResults(self, exception, results): 0125 if exception!=None: 0126 if not self.silent: 0127 self.mw.HandleException(exception) 0128 self.log("Auto Sync: Failed, Exception writing calender to phone") 0129 self.mw.OnBusyEnd() 0130 return 0131 if self.silent==0: 0132 wx.MessageBox('Phone Synchronized OK', 0133 'Synchronize Complete', wx.OK) 0134 self.log("Auto Sync: Synchronize Completed OK") 0135 self.mw.OnBusyEnd() 0136 0137 def sync(self, mw, silent, profile="Default Autosync"): 0138 self.silent=silent 0139 # start the autosync process 0140 # import the calender, find the entry point for the import function 0141 self.mw=mw 0142 if mw.config.ReadInt("SafeMode", False): 0143 self.log("Auto Sync: Disabled, BitPim in safe mode") 0144 return 0 0145 if wx.IsBusy(): 0146 self.log("Auto Sync: Failed, BitPim busy") 0147 return 0 0148 self.log("Auto Sync: Starting (silent mode=%d)..." % (silent)) 0149 self.mw.OnBusyStart() 0150 self.mw.GetStatusBar().progressminor(0, 100, 'Auto Calendar Import in progress ...') 0151 # retrieve the configuration 0152 self.settings=_getsettings(mw, profile) 0153 # update BitPims calender 0154 res=self.importcalenderdata() 0155 if res==1: 0156 # send updated calender to the phone 0157 res=self.sendcalendertophone() 0158 else: 0159 self.mw.OnBusyEnd() 0160 if silent==0: 0161 wx.MessageBox('Unable to Auto-Import Calendar Data', 0162 'Auto Calendar Import failed', wx.OK) 0163 self.log("Auto Sync: Failed, Unable to synchronize phone schedule") 0164 return res 0165 0166 #------------------------------------------------------------------------------- 0167 class AutoSyncSettingsobject(database.basedataobject): 0168 _knownproperties=['caltype', 'calender_id', 'sync_on_connect', 'sync_frequency', \ 0169 'start_offset', 'end_offset', 'no_alarm', 'rpt_events', 'categories', \ 0170 'ringtone', 'vibrate', 'alarm_override', 'alarm_value'] 0171 _knownlistproperties=database.basedataobject._knownlistproperties.copy() 0172 def __init__(self, data=None): 0173 if data is None or not isinstance(data, AutoSyncSettingsEntry): 0174 return; 0175 self.update(data.get_db_dict()) 0176 autosyncsettingsobjectfactory=database.dataobjectfactory(AutoSyncSettingsobject) 0177 0178 #------------------------------------------------------------------------------- 0179 class AutoSyncSettingsEntry(object): 0180 _caltype_key='caltype' 0181 _calender_id_key='calender_id' 0182 _sync_on_connect_key='sync_on_connect' 0183 _sync_frequency_key='sync_frequency' 0184 #_start_offset_key='start_offset' 0185 #_end_offset_key='end_offset' 0186 #_no_alarm_key='no_alarm' 0187 #_categories_key='categories' 0188 #_rpt_event_key='rpt_event' 0189 #_vibrate_key='vibrate' 0190 def __init__(self): 0191 self._data={ 'serials': [] } 0192 # we only expect one record, so the ID is fixed 0193 0194 def __eq__(self, rhs): 0195 return self.caltype==rhs.caltype and self.calender_id==rhs.calender_id and\ 0196 self.sync_frequency==rhs.sync_frequency and self.sync_on_connect==rhs.sync_on_connect 0197 def __ne__(self, rhs): 0198 return (not __eq__(rhs)) 0199 def get(self): 0200 return copy.deepcopy(self._data, {}) 0201 def set(self, d): 0202 self._data={} 0203 self._data.update(d) 0204 0205 def get_db_dict(self): 0206 return self.get() 0207 def set_db_dict(self, d): 0208 self.set(d) 0209 0210 def _get_id(self): 0211 s=self._data.get('serials', []) 0212 for n in s: 0213 if n.get('sourcetype', None)=='bitpim': 0214 return n.get('id', None) 0215 return None 0216 def _set_id(self, id): 0217 s=self._data.get('serials', []) 0218 for n in s: 0219 if n.get('sourcetype', None)=='bitpim': 0220 n['id']=id 0221 return 0222 self._data['serials'].append({'sourcetype': 'bitpim', 'id': id } ) 0223 id=property(fget=_get_id, fset=_set_id) 0224 0225 def _set_or_del(self, key, v, v_list=[]): 0226 if v is None or v in v_list: 0227 if self._data.has_key(key): 0228 del self._data[key] 0229 else: 0230 self._data[key]=v 0231 0232 def _get_caltype(self): 0233 return self._data.get(self._caltype_key, 'None') 0234 def _set_caltype(self, v): 0235 self._set_or_del(self._caltype_key, v, ['']) 0236 caltype=property(fget=_get_caltype, fset=_set_caltype) 0237 0238 def _get_calender_id(self): 0239 return self._data.get(self._calender_id_key, '') 0240 def _set_calender_id(self, v): 0241 self._set_or_del(self._calender_id_key, v, ['']) 0242 calender_id=property(fget=_get_calender_id, fset=_set_calender_id) 0243 0244 def _get_sync_on_connect(self): 0245 return self._data.get(self._sync_on_connect_key, False) 0246 def _set_sync_on_connect(self, v): 0247 self._set_or_del(self._sync_on_connect_key, v, ['']) 0248 sync_on_connect=property(fget=_get_sync_on_connect, fset=_set_sync_on_connect) 0249 0250 def _get_sync_frequency(self): 0251 return self._data.get(self._sync_frequency_key, 0) 0252 def _set_sync_frequency(self, v): 0253 self._set_or_del(self._sync_frequency_key, v, ['']) 0254 sync_frequency=property(fget=_get_sync_frequency, fset=_set_sync_frequency) 0255 0256 ### 0257 ### The autosync settings dialog 0258 ### 0259 0260 class AutoSyncSettingsDialog(wx.Dialog): 0261 ID_CALSETTINGS=wx.NewId() 0262 def __init__(self, mainwindow, frame, title="Auto Calendar Import Settings", profile="Default Autosync", id=-1): 0263 t=title+" - "+profile 0264 wx.Dialog.__init__(self, frame, id, t, 0265 style=wx.CAPTION|wx.SYSTEM_MENU|wx.DEFAULT_DIALOG_STYLE) 0266 self.mw=mainwindow 0267 self.profile=profile 0268 0269 gs=wx.GridBagSizer(10, 10) 0270 gs.AddGrowableCol(1) 0271 0272 # calender type 0273 gs.Add( wx.StaticText(self, -1, "Calender Type"), pos=(0,0), flag=wx.ALIGN_CENTER_VERTICAL) 0274 # get a list of the possible calender types supported 0275 calendertype=('None',) 0276 # build a list of calender types for the user to select from 0277 for entry in self.mw.calenders: 0278 calendertype+=(entry[0], ) 0279 0280 self.caltype=wx.ComboBox(self, -1, calendertype[0], style=wx.CB_DROPDOWN|wx.CB_READONLY,choices=calendertype) 0281 gs.Add( self.caltype, pos=(0,1), flag=wx.ALIGN_CENTER_VERTICAL) 0282 gs.Add( wx.Button(self, self.ID_CALSETTINGS, "Calender Settings..."), pos=(0,2), flag=wx.ALIGN_CENTER_VERTICAL) 0283 0284 # on connect 0285 gs.Add( wx.StaticText(self, -1, "Update when phone re-connected"), pos=(1,0), flag=wx.ALIGN_CENTER_VERTICAL) 0286 self.sync_on_connect=wx.CheckBox(self, wx.NewId(), "") 0287 gs.Add( self.sync_on_connect, pos=(1,1), flag=wx.ALIGN_CENTER_VERTICAL) 0288 0289 # frequency 0290 gs.Add( wx.StaticText(self, -1, "Update Frequency (mins) 0=never"), pos=(2,0), flag=wx.ALIGN_CENTER_VERTICAL) 0291 self.sync_frequency=wx.lib.intctrl.IntCtrl(self, -1, value=0, min=0, max=1440) 0292 gs.Add( self.sync_frequency, pos=(2,1), flag=wx.ALIGN_CENTER_VERTICAL) 0293 0294 # crud at the bottom 0295 bs=wx.BoxSizer(wx.VERTICAL) 0296 bs.Add(gs, 0, wx.EXPAND|wx.ALL, 10) 0297 bs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 7) 0298 0299 but=self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.HELP) 0300 bs.Add(but, 0, wx.CENTER|wx.ALL, 10) 0301 0302 wx.EVT_BUTTON(self, wx.ID_HELP, self.OnHelp) 0303 wx.EVT_BUTTON(self, wx.ID_CANCEL, self.OnCancel) 0304 wx.EVT_BUTTON(self, wx.ID_OK, self.OnOK) 0305 wx.EVT_BUTTON(self, self.ID_CALSETTINGS, self.OnConfigCalender) 0306 wx.EVT_COMBOBOX(self, self.caltype.GetId(), self.OnCaltypeChange) 0307 0308 self.settings=AutoSyncSettingsEntry() 0309 0310 self.SetSizer(bs) 0311 self.SetAutoLayout(True) 0312 bs.Fit(self) 0313 # read initial values from database 0314 self.getfromfs() 0315 self.auto_sync_timer_id = wx.NewId() 0316 self.auto_sync_timer = wx.Timer(self, self.auto_sync_timer_id) 0317 # start the timer 0318 self.SetAutoSyncTimer() 0319 0320 # Retrieve saved settings... (we only care about position) 0321 guiwidgets.set_size("AutoSyncSettingsDialog", self, screenpct=-1, aspect=3.5) 0322 0323 wx.EVT_CLOSE(self, self.OnClose) 0324 0325 def OnCaltypeChange(self, _): 0326 #see if the value has changed, if so automatically fire up the configuration for the calender 0327 if self.settings.caltype!=self.caltype.GetValue(): 0328 self.OnConfigCalender() 0329 return 0330 0331 def OnConfigCalender(self, _=None): 0332 old_folder=self.settings.calender_id 0333 for entry in self.mw.calenders: 0334 if entry[0]==self.caltype.GetValue(): 0335 # if the calender type is changing blank out the folder name 0336 if self.settings.caltype!=self.caltype.GetValue(): 0337 self.settings.calender_id='' 0338 filter={} 0339 for setting in _filter_keys: 0340 if self.settings._data.has_key(setting) and self.settings._data[setting]!=None: 0341 if setting=="categories": 0342 # convert categories into list 0343 filter[setting]=self.settings._data[setting].split("||") 0344 else: 0345 filter[setting]=self.settings._data[setting] 0346 res, temp=entry[1](self.mw, self.settings.calender_id, filter) 0347 if res==wx.ID_OK and res != None: 0348 # temp is a tuple of the calender_id and the filter settings 0349 self.settings.calender_id=temp[0] 0350 for setting in _filter_keys: 0351 if(temp[1].has_key(setting) and temp[1][setting]!=None): 0352 if setting=="categories": 0353 # convert categories into storable type 0354 cat_str="" 0355 for cat in temp[1][setting]: 0356 #use a || to separate individual categories 0357 if len(cat_str): 0358 cat_str=cat_str+"||"+cat 0359 else: 0360 cat_str=cat 0361 self.settings._data[setting]=cat_str 0362 else: 0363 self.settings._data[setting]=temp[1][setting] 0364 else: 0365 if self.settings._data.has_key(setting): 0366 del self.settings._data[setting] 0367 self.settings.caltype=self.caltype.GetValue() 0368 else: # cancel pressed 0369 #revert back to previous value 0370 self.caltype.SetValue(self.settings.caltype) 0371 self.settings.calender_id=old_folder 0372 return 0373 return 0374 0375 def OnCancel(self, _): 0376 self.saveSize() 0377 self.EndModal(wx.ID_CANCEL) 0378 return 0379 0380 def OnOK(self, _): 0381 self.saveSize() 0382 self.EndModal(wx.ID_OK) 0383 return 0384 0385 def OnHelp(self, _): 0386 #wx.GetApp().displayhelpid(helpids.ID_AUTOSYNC_DIALOG) 0387 return 0388 0389 def OnClose(self, evt): 0390 self.saveSize() 0391 # Don't destroy the dialong, just put it away... 0392 self.EndModal(wx.ID_CANCEL) 0393 return 0394 0395 def _save_to_db(self): 0396 db_rr={} 0397 self.settings.caltype=self.caltype.GetValue() 0398 self.settings.sync_on_connect=self.sync_on_connect.GetValue() 0399 self.settings.sync_frequency=self.sync_frequency.GetValue() 0400 self.settings.id=self.profile 0401 db_rr[self.settings.id]=AutoSyncSettingsobject(self.settings) 0402 database.ensurerecordtype(db_rr, autosyncsettingsobjectfactory) 0403 self.mw.tree.GetActivePhone().GetDatabase().savemajordict(_data_key, db_rr) 0404 0405 def getfromfs(self): 0406 self.settings=_getsettings(self.mw, self.profile) 0407 self.caltype.SetValue(self.settings.caltype) 0408 self.sync_on_connect.SetValue(int(self.settings.sync_on_connect)) 0409 self.sync_frequency.SetValue(int(self.settings.sync_frequency)) 0410 return 0411 0412 def IsConfigured(self): 0413 return self.settings.caltype!='None' 0414 0415 def updatevariables(self): 0416 self.mw.auto_save_dict=self.settings 0417 0418 def ShowModal(self): 0419 self.getfromfs() 0420 ec=wx.Dialog.ShowModal(self) 0421 if ec==wx.ID_OK: 0422 self._save_to_db() 0423 self.updatevariables() 0424 self.SetAutoSyncTimer() 0425 return ec 0426 0427 def saveSize(self): 0428 guiwidgets.save_size("AutoSyncSettingsDialog", self.GetRect()) 0429 0430 def SetAutoSyncTimer(self): 0431 # stop the previous timer (if any) 0432 self.auto_sync_timer.Stop() 0433 oneShot = True 0434 timeout=self.settings.sync_frequency*60000 # convert msecs 0435 if timeout: 0436 self.auto_sync_timer.Start(timeout, oneShot) 0437 self.Bind(wx.EVT_TIMER, self.OnTimer, self.auto_sync_timer) 0438 0439 def OnTimer(self, event): 0440 self.mw.log("Auto Sync: Timed update") 0441 SyncSchedule(self.mw).sync(self.mw, silent=1) 0442 self.SetAutoSyncTimer() 0443 0444
Generated by PyXR 0.9.4