1
2
3
4
5
6
7
8
9
10 "Deals with vcard calendar import stuff"
11
12
13 from __future__ import with_statement
14 import contextlib
15 import copy
16 import datetime
17
18
19 import wx
20
21
22 import bpcalendar
23 import bptime
24 import common_calendar
25 import guihelper
26 import helpids
27 import vcard
28
29 module_debug=False
36
40 self._data=[]
41 self._file_name=file_name
42
44 return file(name, 'rt')
45
47 """Read a BEGIN/END block and return a dict of values/params
48 """
49 global module_debug
50 d={}
51 _inblk=False
52 for n,l in vfile:
53 if n[0]=='BEGIN':
54 _blkname=l
55 _inblk=True
56 _vdata=[]
57 elif n[0]=='END' and l==_blkname:
58 d['BEGIN-END']={ 'value': l,
59 'params': self._read_block(_vdata) }
60 _inblk=False
61 elif _inblk:
62 _vdata.append((n, l))
63 else:
64 _params={}
65 for _item in n[1:]:
66 _l=_item.split('=')
67 if len(_l)==1:
68 _params[_l[0]]=None
69 else:
70 _params[_l[0]]=_l[1]
71
72
73
74
75 _val={ 'value': l, 'params': _params }
76 if d.has_key(n[0]):
77
78 if isinstance(d[n[0]], dict):
79 d[n[0]]=[d[n[0]], _val]
80 else:
81 d[n[0]].append(_val)
82 else:
83 d[n[0]]=_val
84 if module_debug:
85 print d,'\n'
86 return d
87
88 - def read(self, file_name=None):
89 self._data=[]
90 if file_name is not None:
91 self._file_name=file_name
92 if self._file_name is None:
93
94 return
95 try:
96 with contextlib.closing(self._open(self._file_name)) as f:
97 vfile=vcard.VFile(f)
98 has_data=False
99 for n,l in vfile:
100 if n[0]=='BEGIN' and l=='VEVENT':
101 has_data=True
102 _vdata=[]
103 elif n[0]=='END' and l=='VEVENT':
104 has_data=False
105 self._data.append(self._read_block(_vdata))
106 elif has_data:
107 _vdata.append((n, l))
108 except:
109 if __debug__:
110 raise
111
113 return copy.deepcopy(self._data)
114 data=property(fget=_get_data)
115
118
119 _default_filter={
120 'start': None,
121 'end': None,
122 'categories': None,
123 'rpt_events': False,
124 'no_alarm': False,
125 'ringtone': None,
126 'alarm_override':False,
127 'vibrate':False,
128 'alarm_value':0
129 }
130 _rrule_dow={
131 'SU': 0x01, 'MO': 0x02, 'TU': 0x04, 'WE': 0x08, 'TH': 0x10,
132 'FR': 0x20, 'SA': 0x40 }
133 _rrule_weekday=_rrule_dow['MO']|_rrule_dow['TU']|\
134 _rrule_dow['WE']|_rrule_dow['TH']|\
135 _rrule_dow['FR']
136 _source_data_class=vCalendarFile
137
139 self._file_name=file_name
140 self._data=[]
141 self._filter=self._default_filter
142 self.read()
143
145
146 if entry.get('repeat', False):
147
148
149 ce=bpcalendar.CalendarEntry()
150 self._populate_entry(entry, ce)
151 if self._filter['start'] is not None and \
152 ce.end[:3]<self._filter['start'][:3]:
153
154 return False
155 if self._filter['end'] is not None and \
156 ce.start[:3]>self._filter['end'][:3]:
157
158 return False
159 else:
160
161 if self._filter['start'] is not None and \
162 entry['start'][:3]<self._filter['start'][:3]:
163 return False
164 if self._filter['end'] is not None and \
165 entry['end'][:3]>self._filter['end'][:3] and \
166 entry['end'][:3]!=common_calendar.no_end_date[:3]:
167 return False
168
169 c=self._filter['categories']
170 if c is None or not len(c):
171
172 return True
173 if len([x for x in entry['categories'] if x in c]):
174 return True
175 return False
176
177 - def _populate_repeat_entry(self, e, ce):
178
179 if not e.get('repeat', False) or e.get('repeat_type', None) is None:
180
181 return
182 rp=bpcalendar.RepeatEntry()
183 rp_type=e['repeat_type']
184 rp_interval=e.get('repeat_interval', 1)
185 rp_interval2=e.get('repeat_interval2', 1)
186 rp_end=e.get('repeat_end', None)
187 rp_num=e.get('repeat_num', None)
188 rp_dow=e.get('repeat_dow', 0)
189 if rp_type==rp.daily:
190
191 rp.repeat_type=rp.daily
192 rp.interval=rp_interval
193 elif rp_type==rp.weekly or rp_type==rp.monthly:
194 rp.repeat_type=rp_type
195 rp.interval=rp_interval
196 rp.interval2=rp_interval2
197 rp.dow=rp_dow
198 elif rp_type==rp.yearly:
199 rp.repeat_type=rp.yearly
200 else:
201
202 return
203 rp.weekstart=e.get('repeat_wkst', 'MO')
204
205 if rp_end is not None:
206
207 ce.end=rp_end[:3]+ce.end[3:]
208 elif rp_num:
209
210 _dt=ce.start[:3]
211 for i in range(rp_num-1):
212 _dt=rp.next_date(_dt)
213 ce.end=_dt[:3]+ce.end[3:]
214 else:
215
216 ce.end=common_calendar.no_end_date[:3]+ce.end[3:]
217
218 for k in e.get('exceptions', []):
219 rp.add_suppressed(*k[:3])
220
221 ce.repeat=rp
222
223 - def _populate_entry(self, e, ce):
224
225 ce.description=e.get('description', None)
226 ce.location=e.get('location', None)
227 v=e.get('priority', None)
228 if v is not None:
229 ce.priority=v
230 if not self._filter.get('no_alarm', False) and \
231 not self._filter.get('alarm_override', False) and \
232 e.get('alarm', False):
233 ce.alarm=e.get('alarm_value', 0)
234 ce.ringtone=self._filter.get('ringtone', "")
235 ce.vibrate=self._filter.get('vibrate', False)
236 elif not self._filter.get('no_alarm', False) and \
237 self._filter.get('alarm_override', False):
238 ce.alarm=self._filter.get('alarm_value', 0)
239 ce.ringtone=self._filter.get('ringtone', "")
240 ce.vibrate=self._filter.get('vibrate', False)
241 ce_start=e.get('start', None)
242 ce_end=e.get('end', None)
243 if ce_start is None and ce_end is None:
244 raise ValueError, "No start or end datetime"
245 if ce_start is not None:
246 ce.start=ce_start
247 if ce_end is not None:
248 ce.end=ce_end
249 if ce_start is None:
250 ce.start=ce.end
251 elif ce_end is None:
252 ce.end=ce.start
253 ce.notes=e.get('notes', None)
254 v=[]
255 for k in e.get('categories', []):
256 v.append({ 'category': k })
257 ce.categories=v
258
259 self._populate_repeat_entry(e, ce)
260 ce.allday=e.get('allday', False)
261
263
264 ce=bpcalendar.CalendarEntry()
265 self._populate_entry(e, ce)
266 l=[]
267 new_e=e.copy()
268 new_e['repeat']=False
269 for k in ('repeat_type', 'repeat_interval', 'repeat_dow'):
270 if new_e.has_key(k):
271 del new_e[k]
272 s_date=datetime.datetime(*self._filter['start'])
273 e_date=datetime.datetime(*self._filter['end'])
274 one_day=datetime.timedelta(1)
275 this_date=s_date
276 while this_date<=e_date:
277 date_l=(this_date.year, this_date.month, this_date.day)
278 if ce.is_active(*date_l):
279 new_e['start']=date_l+new_e['start'][3:]
280 new_e['end']=date_l+new_e['end'][3:]
281 l.append(new_e.copy())
282 this_date+=one_day
283 return l
284
304
306 l=[]
307 for e in self._data:
308 l+=[x for x in e.get('categories', []) if x not in l]
309 return l
310
313
316
318 return [x.strip() for x in v['value'].split(",") if len(x)]
319
321 try:
322 alarm_date=bptime.BPTime(v['value'].split(';')[0])
323 start_date=bptime.BPTime(dd['start'])
324 if alarm_date.get()<start_date.get():
325 dd['alarm_value']=(start_date-alarm_date).seconds/60
326 return True
327 return False
328 except:
329 return False
330
334 try:
335 return int(v['value'])
336 except:
337 return None
339 return v['value'].replace('\,', ',')
340
342
343 s=v['value'].split(' ')
344 dd['repeat_interval']=int(s[0][1:])
345 if len(s)==1:
346
347 return True
348 if s[1][0]=='#':
349
350 dd['repeat_num']=int(s[1][1:])
351 else:
352
353 dd['repeat_end']=bptime.BPTime(s[1]).get()
354 dd['repeat_type']='daily'
355 return True
356
358
359 s=v['value'].split(' ')
360 dd['repeat_interval']=int(s[0][1:])
361 dow=0
362 for i in range(1, len(s)):
363 n=s[i]
364 if n[0].isdigit():
365 dd['repeat_end']=bptime.BPTime(n).get()
366 elif n[0]=='#':
367 dd['repeat_num']=int(n[1:])
368 else:
369
370 dow=dow|self._rrule_dow.get(n, 0)
371 if dow:
372 dd['repeat_dow']=dow
373 dd['repeat_type']='weekly'
374 return True
375
377 global module_debug
378 try:
379
380
381 s=v['value'].split(' ')
382 if s[0][:2]!='MD' and s[0][:2]!='MP':
383 return False
384 dd['repeat_interval2']=int(s[0][2:])
385 if s[0][:2]=='MP':
386
387 n=s[1]
388 if n in ['1+', '2+', '3+', '4+', '1-']:
389 if n[1]=='-':
390 dd['repeat_interval']=5
391 else:
392 dd['repeat_interval']=int(n[0])
393 else:
394 return False
395 dd['repeat_dow']=self._rrule_dow.get(s[2], 0)
396 else:
397 dd['repeat_interval']=dd['repeat_dow']=0
398 dd['repeat_type']='monthly'
399 n=s[-1]
400 if len(n)>7 and n[:8].isdigit():
401
402 dd['repeat_end']=bptime.BPTime(n).get()
403 elif n[0]=='#':
404 dd['repeat_num']=int(n[1:])
405 return True
406 except:
407 if module_debug: raise
408 return False
410 global module_debug
411 try:
412
413 s=v['value'].split(' ')
414 if s[0]!='YM1':
415 return False
416 n=s[-1]
417 if len(n)>7 and n[:8].isdigit():
418
419 dd['repeat_end']=bptime.BPTime(n).get()
420 elif n[0]=='#':
421 dd['repeat_num']=int(n[1:])
422 dd['repeat_type']='yearly'
423 return True
424 except:
425 if module_debug: raise
426 return False
427
438 try:
439 _val=v if isinstance(v, (list, tuple)) else [v]
440 r=[]
441 for _item in _val:
442 for n in _item['value'].split(';'):
443 r.append(bptime.BPTime(n).get())
444 return r
445 except:
446 if __debug__:
447 raise
448 return []
449 _calendar_keys=[
450 ('CATEGORIES', 'categories', _conv_cat),
451 ('DESCRIPTION', 'notes', _conv_str),
452 ('DTSTART', 'start', _conv_date),
453 ('DTEND', 'end', _conv_date),
454 ('LOCATION', 'location', _conv_str),
455 ('PRIORITY', 'priority', _conv_priority),
456 ('SUMMARY', 'description', _conv_str),
457 ('AALARM', 'alarm', _conv_alarm),
458 ('DALARM', 'alarm', _conv_alarm),
459 ('RRULE', 'repeat', _conv_repeat),
460 ('EXDATE', 'exceptions', _conv_exceptions),
461 ]
463 global module_debug
464 for i in vcal:
465 try:
466 dd={'start': None, 'end': None }
467 for j in self._calendar_keys:
468 if i.has_key(j[0]):
469 k=i[j[0]]
470 if j[2] is not None:
471 dd[j[1]]=j[2](self, k, dd)
472 else:
473 dd[j[1]]=k['value']
474 if dd['start'] is None and dd['end'] is None:
475
476 continue
477 if dd['start'] is None:
478 dd['start']=dd['end']
479 elif dd['end'] is None:
480 dd['end']=dd['start']
481 if dd.get('allday', False) and dd['end']>dd['start']:
482
483 dd['end']=(bptime.BPTime(dd['end'])-\
484 bptime.timedelta(days=1)).get()[:3]+(0, 0)
485
486 if module_debug: print dd
487 d.append(dd)
488 except:
489 if module_debug: raise
490
492 cnt=0
493 res={}
494 single_rpt=self._filter.get('rpt_events', False)
495 for k in self._data:
496 if self._accept(k):
497 if k.get('repeat', False) and single_rpt:
498 d=self._generate_repeat_events(k)
499 else:
500 d=[k.copy()]
501 for n in d:
502 if self._filter.get('no_alarm', False):
503 n['alarm']=False
504 res[cnt]=n
505 cnt+=1
506 return res
507
509 if self._file_name is not None:
510 return self._file_name
511 return ''
512
513 - def read(self, file_name=None, update_dlg=None):
514 if file_name is not None:
515 self._file_name=file_name
516 if self._file_name is None:
517
518 return
519 v=self._source_data_class(self._file_name)
520 v.read()
521 self._convert(v.data, self._data)
522
525 _column_labels=[
526 ('description', 'Description', 400, None),
527 ('start', 'Start', 150, common_calendar.bp_date_str),
528 ('end', 'End', 150, common_calendar.bp_date_str),
529 ('repeat_type', 'Repeat', 80, common_calendar.bp_repeat_str),
530 ('alarm', 'Alarm', 80, common_calendar.bp_alarm_str),
531 ('categories', 'Category', 150, common_calendar.category_str)
532 ]
533 _filetype_label="VCalendar File:"
534 _data_type='vCalendar'
535 _import_data_class=VCalendarImportData
542
544 hbs=wx.BoxSizer(wx.HORIZONTAL)
545
546 hbs.Add(wx.StaticText(self, -1, self._filetype_label), 0, wx.ALL|wx.ALIGN_CENTRE, 2)
547
548 self.folderctrl=wx.TextCtrl(self, -1, "")
549 self.folderctrl.SetValue(self._oc.get_file_name())
550 hbs.Add(self.folderctrl, 1, wx.EXPAND|wx.ALL, 2)
551
552 id_browse=wx.NewId()
553 hbs.Add(wx.Button(self, id_browse, 'Browse ...'), 0, wx.EXPAND|wx.ALL, 2)
554 main_bs.Add(hbs, 0, wx.EXPAND|wx.ALL, 5)
555 main_bs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5)
556 wx.EVT_BUTTON(self, id_browse, self.OnBrowseFolder)
557
558 - def getpostcontrols(self, main_bs):
559 main_bs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5)
560 hbs=wx.BoxSizer(wx.HORIZONTAL)
561 id_import=wx.NewId()
562 hbs.Add(wx.Button(self, id_import, 'Import'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
563 hbs.Add(wx.Button(self, wx.ID_OK, 'Replace All'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
564 hbs.Add(wx.Button(self, self.ID_ADD, 'Add'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
565 hbs.Add(wx.Button(self, self.ID_MERGE, 'Merge'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
566 hbs.Add(wx.Button(self, wx.ID_CANCEL, 'Cancel'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
567 id_filter=wx.NewId()
568 hbs.Add(wx.Button(self, id_filter, 'Filter'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
569 hbs.Add(wx.Button(self, wx.ID_HELP, 'Help'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
570 main_bs.Add(hbs, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
571 wx.EVT_BUTTON(self, id_import, self.OnImport)
572 wx.EVT_BUTTON(self, id_filter, self.OnFilter)
573 wx.EVT_BUTTON(self, self.ID_ADD, self.OnEndModal)
574 wx.EVT_BUTTON(self, self.ID_MERGE, self.OnEndModal)
575 wx.EVT_BUTTON(self, wx.ID_HELP, lambda *_: wx.GetApp().displayhelpid(helpids.ID_DLG_CALENDAR_IMPORT))
576
577 @guihelper.BusyWrapper
579 with guihelper.WXDialogWrapper(wx.ProgressDialog('%s Import'%self._data_type,
580 'Importing %s Data, please wait ...'%self._data_type,
581 parent=self)) as dlg:
582 try:
583 self._oc.read(self.folderctrl.GetValue())
584 self.populate(self._oc.get_display_data())
585 except (ValueError, IOError):
586 guihelper.MessageDialog(self, 'Failed to get import data',
587 'Import Error',
588 style=wx.OK|wx.ICON_ERROR)
589 except:
590 if __debug__:
591 raise
592
594 with guihelper.WXDialogWrapper(wx.FileDialog(self, "Pick a %s File"%self._data_type,
595 wildcard='*.vcs;*.ics'),
596 True) as (dlg, retcode):
597 if retcode==wx.ID_OK:
598 self.folderctrl.SetValue(dlg.GetPath())
599
607
609 self.EndModal(evt.GetId())
610
612 return self._oc.get()
613
616
624
627 - def __init__(self, parent, id, title, folder, filters,
628 style=wx.CAPTION|wx.MAXIMIZE_BOX| \
629 wx.SYSTEM_MENU|wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER):
630 self._oc=VCalendarImportData()
631 self._oc.set_filter(filters)
632 self._read=False
633 wx.Dialog.__init__(self, parent, id=id, title=title, style=style)
634 main_bs=wx.BoxSizer(wx.VERTICAL)
635 hbs=wx.BoxSizer(wx.HORIZONTAL)
636
637 hbs.Add(wx.StaticText(self, -1, "VCalendar File:"), 0, wx.ALL|wx.ALIGN_CENTRE, 2)
638
639 self.folderctrl=wx.TextCtrl(self, -1, "", style=wx.TE_READONLY)
640 self.folderctrl.SetValue(folder)
641 hbs.Add(self.folderctrl, 1, wx.EXPAND|wx.ALL, 2)
642
643 id_browse=wx.NewId()
644 hbs.Add(wx.Button(self, id_browse, 'Browse ...'), 0, wx.EXPAND|wx.ALL, 2)
645 main_bs.Add(hbs, 0, wx.EXPAND|wx.ALL, 5)
646 main_bs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5)
647 wx.EVT_BUTTON(self, id_browse, self.OnBrowseFolder)
648 hbs=wx.BoxSizer(wx.HORIZONTAL)
649 hbs.Add(wx.Button(self, wx.ID_OK, 'OK'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
650 hbs.Add(wx.Button(self, wx.ID_CANCEL, 'Cancel'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
651 id_filter=wx.NewId()
652 hbs.Add(wx.Button(self, id_filter, 'Filter'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
653 hbs.Add(wx.Button(self, wx.ID_HELP, 'Help'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
654 main_bs.Add(hbs, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
655 wx.EVT_BUTTON(self, id_filter, self.OnFilter)
656 wx.EVT_BUTTON(self, wx.ID_HELP, lambda *_: wx.GetApp().displayhelpid(helpids.ID_DLG_CALENDAR_IMPORT))
657 self.SetSizer(main_bs)
658 self.SetAutoLayout(True)
659 main_bs.Fit(self)
660
662 with guihelper.WXDialogWrapper(wx.FileDialog(self, "Pick a VCalendar File", wildcard='*.vcs'),
663 True) as (dlg, retcode):
664 if retcode==wx.ID_OK:
665 self.folderctrl.SetValue(dlg.GetPath())
666 self._read=False
667
679
682
685