PyXR

c:\projects\bitpim\src \ auto_sync.py



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