1
2
3
4
5
6
7
8
9
10 "Deals with Outlook calendar import stuff"
11
12
13 from __future__ import with_statement
14 import datetime
15 import pywintypes
16 import sys
17 import time
18
19
20 import wx
21 import wx.calendar
22 import wx.lib.mixins.listctrl as listmix
23
24
25
26
27 import bpcalendar
28 import common
29 import common_calendar
30 import guihelper
31 import guiwidgets
32 import helpids
33 import native.outlook
43
58
60 return [x.strip() for x in v.split(",") if len(x)]
61
70
94
97 _non_auto_sync_calendar_keys=[
98
99 ('Subject', 'description', None),
100 ('Location', 'location', None),
101 ('Start', 'start', to_bp_date),
102 ('End', 'end', to_bp_date),
103 ('Categories', 'categories', convert_categories),
104 ('IsRecurring', 'repeat', None),
105 ('ReminderSet', 'alarm', None),
106 ('ReminderMinutesBeforeStart', 'alarm_value', None),
107 ('Importance', 'priority', None),
108 ('Body', 'notes', None),
109 ('AllDayEvent', 'allday', None)
110 ]
111
112
113 _auto_sync_calendar_keys=[
114
115 ('Subject', 'description', None),
116 ('Location', 'location', None),
117 ('Start', 'start', to_bp_date),
118 ('End', 'end', to_bp_date),
119 ('Categories', 'categories', convert_categories),
120 ('IsRecurring', 'repeat', None),
121 ('ReminderSet', 'alarm', None),
122 ('ReminderMinutesBeforeStart', 'alarm_value', None),
123 ('Importance', 'priority', None),
124 ('AllDayEvent', 'allday', None)
125 ]
126 _recurrence_keys=[
127
128 ('NoEndDate', 'NoEndDate', None),
129 ('PatternStartDate', 'PatternStartDate', to_bp_date),
130 ('PatternEndDate', 'PatternEndDate', to_bp_date),
131 ('Instance', 'Instance', None),
132 ('DayOfWeekMask', 'DayOfWeekMask', None),
133 ('Interval', 'Interval', None),
134 ('Occurrences', 'Occurrences', None),
135 ('RecurrenceType', 'RecurrenceType', None)
136 ]
137 _exception_keys=[
138
139 ('OriginalDate', 'exception_date', to_bp_date),
140 ('Deleted', 'deleted', None)
141 ]
142 _default_filter={
143 'start': None,
144 'end': None,
145 'categories': None,
146 'rpt_events': False,
147 'no_alarm': False,
148 'ringtone': None,
149 'alarm_override':False,
150 'vibrate':False,
151 'alarm_value':0
152 }
153
154
155 olRecursDaily = native.outlook.outlook_com.constants.olRecursDaily
156 olRecursMonthNth = native.outlook.outlook_com.constants.olRecursMonthNth
157 olRecursMonthly = native.outlook.outlook_com.constants.olRecursMonthly
158 olRecursWeekly = native.outlook.outlook_com.constants.olRecursWeekly
159 olRecursYearNth = native.outlook.outlook_com.constants.olRecursYearNth
160 olRecursYearly = native.outlook.outlook_com.constants.olRecursYearly
161 olImportanceHigh = native.outlook.outlook_com.constants.olImportanceHigh
162 olImportanceLow = native.outlook.outlook_com.constants.olImportanceLow
163 olImportanceNormal = native.outlook.outlook_com.constants.olImportanceNormal
164
180
182 s_date=entry['start'][:3]
183 e_date=entry['end'][:3]
184 if entry.get('repeat', False):
185
186 if self._filter['start'] is not None and \
187 e_date<self._filter['start'][:3]:
188 return False
189 if self._filter['end'] is not None and \
190 s_date>self._filter['end'][:3]:
191 return False
192 else:
193
194 if self._filter['start'] is not None and \
195 e_date<self._filter['start'][:3]:
196 return False
197 if self._filter['end'] is not None and \
198 e_date>self._filter['end'][:3]:
199 return False
200
201 c=self._filter.get('categories', None)
202 if c is None or not len(c):
203
204 return True
205 if len([x for x in entry['categories'] if x in c]):
206 return True
207 return False
208
209 - def _populate_entry(self, e, ce):
210
211 ce.description=e.get('description', None)
212 ce.location=e.get('location', None)
213 v=e.get('priority', None)
214 if v is not None:
215 if v==self.olImportanceNormal:
216 ce.priority=ce.priority_normal
217 elif v==self.olImportanceLow:
218 ce.priority=ce.priority_low
219 elif v==self.olImportanceHigh:
220 ce.priority=ce.priority_high
221 if not self._filter.get('no_alarm', False) and \
222 not self._filter.get('alarm_override', False) and \
223 e.get('alarm', False):
224 ce.alarm=e.get('alarm_value', 0)
225 ce.ringtone=self._filter.get('ringtone', "")
226 ce.vibrate=self._filter.get('vibrate', False)
227 elif not self._filter.get('no_alarm', False) and \
228 self._filter.get('alarm_override', False):
229 ce.alarm=self._filter.get('alarm_value', 0)
230 ce.ringtone=self._filter.get('ringtone', "")
231 ce.vibrate=self._filter.get('vibrate', False)
232 ce.allday=e.get('allday', False)
233 if ce.allday:
234 if not e.get('repeat', False):
235 ce.start=e['start'][:3]+(0,0)
236
237
238 _dt=datetime.datetime(*e['end'])-datetime.timedelta(1)
239 ce.end=(_dt.year, _dt.month, _dt.day, 23, 59)
240 else:
241
242
243 ce.start=e['start'][:3]+(0,0)
244 ce.end=e['end'][:3]+(23,59)
245 else:
246 ce.start=e['start']
247 ce.end=e['end']
248 ce.notes=e.get('notes', None)
249 v=[]
250 for k in e.get('categories', []):
251 v.append({ 'category': k })
252 ce.categories=v
253
254 if not e.get('repeat', False):
255
256 return
257 rp=bpcalendar.RepeatEntry()
258 rt=e['repeat_type']
259 r_interval=e.get('repeat_interval', 0)
260 r_interval2=e.get('repeat_interval2', 1)
261 r_dow=e.get('repeat_dow', 0)
262 if rt==self.olRecursDaily:
263 rp.repeat_type=rp.daily
264 elif rt==self.olRecursWeekly:
265 if r_interval:
266
267 rp.repeat_type=rp.weekly
268 else:
269
270 rp.repeat_type=rp.daily
271 elif rt==self.olRecursMonthly or rt==self.olRecursMonthNth:
272 rp.repeat_type=rp.monthly
273 else:
274 rp.repeat_type=rp.yearly
275 if rp.repeat_type==rp.daily:
276 rp.interval=r_interval
277 elif rp.repeat_type==rp.weekly or rp.repeat_type==rp.monthly:
278 rp.interval=r_interval
279 rp.interval2=r_interval2
280 rp.dow=r_dow
281
282 if rp.repeat_type==rp.monthly and \
283 rp.dow in (rp.dow_weekday, rp.dow_weekend):
284 rp.dow=0
285
286 for k in e.get('exceptions', []):
287 rp.add_suppressed(*k[:3])
288 ce.repeat=rp
289
291
292 ce=bpcalendar.CalendarEntry()
293 self._populate_entry(e, ce)
294 l=[]
295 new_e=e.copy()
296 new_e['repeat']=False
297 for k in ('repeat_type', 'repeat_interval', 'repeat_dow'):
298 if new_e.has_key(k):
299 del new_e[k]
300 s_date=datetime.datetime(*self._filter['start'])
301 e_date=datetime.datetime(*self._filter['end'])
302 one_day=datetime.timedelta(1)
303 this_date=s_date
304 while this_date<=e_date:
305 date_l=(this_date.year, this_date.month, this_date.day)
306 if ce.is_active(*date_l):
307 new_e['start']=date_l+new_e['start'][3:]
308 new_e['end']=date_l+new_e['end'][3:]
309 l.append(new_e.copy())
310 this_date+=one_day
311 return l
312
327
329 cnt=0
330 res={}
331 single_rpt=self._filter.get('rpt_events', False)
332 no_alarm=self._filter.get('no_alarm', False)
333 for k in self._data:
334 if self._accept(k):
335 if k.get('repeat', False) and single_rpt:
336 d=self._generate_repeat_events(k)
337 else:
338 d=[k.copy()]
339 for n in d:
340 if no_alarm:
341 n['alarm']=False
342 res[cnt]=n
343 cnt+=1
344 return res
345
347 l=[]
348 for e in self._data:
349 l+=[x for x in e.get('categories', []) if x not in l]
350 return l
351
354
356 if f is None:
357
358 self._folder=self._outlook.getfolderfromid('', True, 'calendar')
359 else:
360 self._folder=f
361
364
370
373
376
378 if self._folder is None:
379 return ''
380 return self._outlook.getfoldername(self._folder)
381
382 - def read(self, folder=None, update_dlg=None):
383
384 if folder is not None:
385 self._folder=folder
386 if self._folder is None:
387 self._folder=self._outlook.getfolderfromid('', True, 'calendar')
388 self._update_dlg=update_dlg
389 self._total_count=self._folder.Items.Count
390 self._current_count=0
391 self._exception_list=[]
392 self._data, self._error_list=self._outlook.getdata(self._folder,
393 self._calendar_keys,
394 {}, self,
395 set_recurrence)
396
397 self._data+=self._exception_list
398
400 dict['start']=r['PatternStartDate'][:3]+dict['start'][3:]
401 dict['end']=r['PatternEndDate'][:3]+dict['end'][3:]
402 dict['repeat_type']=r['RecurrenceType']
403
405 if r['RecurrenceType']==self.olRecursDaily or \
406 r['RecurrenceType']==self.olRecursWeekly:
407 self._set_repeat_dates(dict, r)
408 dict['repeat_interval']=r['Interval']
409 dict['repeat_dow']=r['DayOfWeekMask']
410 return True
411 return False
412
414 if r['RecurrenceType']==self.olRecursMonthly or \
415 r['RecurrenceType']==self.olRecursMonthNth:
416 self._set_repeat_dates(dict, r)
417 dict['repeat_interval2']=r['Interval']
418 if r['RecurrenceType']==self.olRecursMonthNth:
419 dict['repeat_interval']=r['Instance']
420 dict['repeat_dow']=r['DayOfWeekMask']
421 return True
422 return False
423
430
432
433 r_ex=r.Exceptions
434 if not r_ex.Count:
435
436 return
437 for i in range(1, r_ex.Count+1):
438 ex=self._outlook.getitemdata(r_ex.Item(i), {},
439 self._exception_keys, self)
440 dict.setdefault('exceptions', []).append(ex['exception_date'])
441 if not ex['deleted']:
442
443 appt=self._outlook.getitemdata(r_ex.Item(i).AppointmentItem,
444 {}, self._calendar_keys, self)
445
446 appt['repeat']=False
447 appt['end']=appt['start'][:3]+appt['end'][3:]
448
449 self._exception_list.append(appt)
450
466
468
469 self._current_count += 1
470 if self._update_dlg is not None:
471 self._update_dlg.Update(100*self._current_count/self._total_count)
472
474 return bool(self._error_list)
476
477 res=[]
478 for d in self._error_list:
479
480 _start=d.get('start', None)
481 s=''
482 if _start:
483 if len(_start)>4:
484 s='%02d-%02d-%02d %02d:%02d '%_start[:5]
485 elif len(_start)>2:
486 s='%02d-%02d-%02d '%_start[:3]
487 _desc=d.get('description', None)
488 if _desc:
489 s+=_desc
490 if not s:
491 s='<Unknown>'
492 res.append(s)
493 return res
494
497 _column_labels=[
498 ('description', 'Description', 400, None),
499 ('start', 'Start', 150, common_calendar.bp_date_str),
500 ('end', 'End', 150, common_calendar.bp_date_str),
501 ('repeat_type', 'Repeat', 80, bp_repeat_str),
502 ('alarm', 'Alarm', 80, common_calendar.bp_alarm_str),
503 ('categories', 'Category', 150, common_calendar.category_str)
504 ]
505
506 _config_name='import/calendar/outlookdialog'
507 _browse_label='Outlook Calendar Folder:'
508 _progress_dlg_title='Outlook Calendar Import'
509 _error_dlg_title='Outlook Calendar Import Error'
510 _error_dlg_text='Outlook Calendar Items that failed to import:'
511 _data_class=OutlookCalendarImportData
512 _filter_dlg_class=common_calendar.FilterDialog
513
521
523 hbs=wx.BoxSizer(wx.HORIZONTAL)
524
525 hbs.Add(wx.StaticText(self, -1, self._browse_label), 0, wx.ALL|wx.ALIGN_CENTRE, 2)
526
527 self.folderctrl=wx.TextCtrl(self, -1, "", style=wx.TE_READONLY)
528 self.folderctrl.SetValue(self._oc.get_folder_name())
529 hbs.Add(self.folderctrl, 1, wx.EXPAND|wx.ALL, 2)
530
531 id_browse=wx.NewId()
532 hbs.Add(wx.Button(self, id_browse, 'Browse ...'), 0, wx.EXPAND|wx.ALL, 2)
533 main_bs.Add(hbs, 0, wx.EXPAND|wx.ALL, 5)
534 main_bs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5)
535 wx.EVT_BUTTON(self, id_browse, self.OnBrowseFolder)
536
537 - def getpostcontrols(self, main_bs):
538 main_bs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5)
539 hbs=wx.BoxSizer(wx.HORIZONTAL)
540 id_import=wx.NewId()
541 hbs.Add(wx.Button(self, id_import, 'Import'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
542 hbs.Add(wx.Button(self, wx.ID_OK, 'Replace All'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
543 hbs.Add(wx.Button(self, self.ID_ADD, 'Add'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
544 hbs.Add(wx.Button(self, self.ID_MERGE, 'Merge'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
545 hbs.Add(wx.Button(self, wx.ID_CANCEL, 'Cancel'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
546 id_filter=wx.NewId()
547 hbs.Add(wx.Button(self, id_filter, 'Filter'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
548 hbs.Add(wx.Button(self, wx.ID_HELP, 'Help'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
549 main_bs.Add(hbs, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
550 wx.EVT_BUTTON(self, id_import, self.OnImport)
551 wx.EVT_BUTTON(self, id_filter, self.OnFilter)
552 wx.EVT_BUTTON(self, self.ID_ADD, self.OnEndModal)
553 wx.EVT_BUTTON(self, self.ID_MERGE, self.OnEndModal)
554 wx.EVT_BUTTON(self, wx.ID_HELP, lambda *_: wx.GetApp().displayhelpid(helpids.ID_DLG_CALENDAR_IMPORT))
555
556 @guihelper.BusyWrapper
571
578
586
588 self.EndModal(evt.GetId())
589
591 return self._oc.get()
592
595
604
607 - def __init__(self, parent, id, title, folder, filters,
608 style=wx.CAPTION|wx.MAXIMIZE_BOX| \
609 wx.SYSTEM_MENU|wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER):
610 self._oc=OutlookCalendarImportData(native.outlook, auto_sync_only=1)
611 self._oc.set_folder_id(folder)
612 self._oc.set_filter(filters)
613 self.__read=False
614 wx.Dialog.__init__(self, parent, id=id, title=title, style=style)
615 main_bs=wx.BoxSizer(wx.VERTICAL)
616 hbs=wx.BoxSizer(wx.HORIZONTAL)
617
618 hbs.Add(wx.StaticText(self, -1, "Outlook Calendar Folder:"), 0, wx.ALL|wx.ALIGN_CENTRE, 2)
619
620 self.folderctrl=wx.TextCtrl(self, -1, "", style=wx.TE_READONLY)
621 self.folderctrl.SetValue(self._oc.get_folder_name())
622 hbs.Add(self.folderctrl, 1, wx.EXPAND|wx.ALL, 2)
623
624 id_browse=wx.NewId()
625 hbs.Add(wx.Button(self, id_browse, 'Browse ...'), 0, wx.EXPAND|wx.ALL, 2)
626 main_bs.Add(hbs, 0, wx.EXPAND|wx.ALL, 5)
627 main_bs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5)
628 wx.EVT_BUTTON(self, id_browse, self.OnBrowseFolder)
629 hbs=wx.BoxSizer(wx.HORIZONTAL)
630 hbs.Add(wx.Button(self, wx.ID_OK, 'OK'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
631 hbs.Add(wx.Button(self, wx.ID_CANCEL, 'Cancel'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
632 id_filter=wx.NewId()
633 hbs.Add(wx.Button(self, id_filter, 'Filter'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
634 hbs.Add(wx.Button(self, wx.ID_HELP, 'Help'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
635 main_bs.Add(hbs, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
636 wx.EVT_BUTTON(self, id_filter, self.OnFilter)
637 wx.EVT_BUTTON(self, wx.ID_HELP, lambda *_: wx.GetApp().displayhelpid(helpids.ID_DLG_CALENDAR_IMPORT))
638 self.SetSizer(main_bs)
639 self.SetAutoLayout(True)
640 main_bs.Fit(self)
641
649
660
663
666