@@ -240,58 +240,83 @@ def update(self, vevent_str: str, href: str, etag: str='', calendar: str=None) -
240
240
stuple = (vevent_str , etag , href , calendar )
241
241
self .sql_ex (sql_s , stuple )
242
242
243
- def update_birthday (self , vevent_str : str , href : str , etag : str = '' , calendar : str = None ) -> None :
244
- """
245
- XXX write docstring
243
+ def update_vcf_dates (self , vevent_str : str , href : str , etag : str = '' ,
244
+ calendar : str = None ) -> None :
245
+ """insert events from a vcard into the db
246
+
247
+ This is will parse BDAY, ANNIVERSARY, X-ANNIVERSARY and X-ABDATE fields.
248
+ It will also look for any X-ABLABEL fields associated with an X-ABDATE
249
+ and use that in the event description.
250
+
251
+ :param vevent_str: contact (vcard) to be parsed.
252
+ :param href: href of the card on the server, if this href already
253
+ exists in the db the card gets updated. If no href is given, a
254
+ random href is chosen and it is implied that this card does not yet
255
+ exist on the server, but will be uploaded there on next sync.
256
+ :param etag: the etag of the vcard, if this etag does not match the
257
+ remote etag on next sync, this card will be updated from the server.
258
+ For locally created vcards this should not be set
246
259
"""
247
260
assert calendar is not None
248
261
assert href is not None
249
- self .delete (href , calendar = calendar )
262
+ # Delete all event entries for this contact
263
+ self .deletelike (href + '%' , calendar = calendar )
250
264
ical = utils .cal_from_ics (vevent_str )
251
265
vcard = ical .walk ()[0 ]
252
- if 'BDAY' in vcard .keys ():
253
- bday = vcard ['BDAY' ]
254
- if isinstance (bday , list ):
255
- logger .warning (
256
- 'Vcard {0} in collection {1} has more than one '
257
- 'BIRTHDAY, will be skipped and not be available '
258
- 'in khal.' .format (href , calendar )
259
- )
260
- return
261
- try :
262
- if bday [0 :2 ] == '--' and bday [3 ] != '-' :
263
- bday = '1900' + bday [2 :]
264
- orig_bday = False
266
+ for key in vcard .keys ():
267
+ if key in ['BDAY' , 'X-ANNIVERSARY' , 'ANNIVERSARY' ] or key .endswith ('X-ABDATE' ):
268
+ date = vcard [key ]
269
+ if isinstance (date , list ):
270
+ logger .warning (
271
+ 'Vcard {0} in collection {1} has more than one '
272
+ '{2}, will be skipped and not be available '
273
+ 'in khal.' .format (href , calendar , key )
274
+ )
275
+ continue
276
+ try :
277
+ if date [0 :2 ] == '--' and date [3 ] != '-' :
278
+ date = '1900' + date [2 :]
279
+ orig_date = False
280
+ else :
281
+ orig_date = True
282
+ date = parser .parse (date ).date ()
283
+ except ValueError :
284
+ logger .warning (
285
+ 'cannot parse {0} in {1} in collection {2}' .format (key , href , calendar ))
286
+ continue
287
+ if 'FN' in vcard :
288
+ name = vcard ['FN' ]
265
289
else :
266
- orig_bday = True
267
- bday = parser .parse (bday ).date ()
268
- except ValueError :
269
- logger .warning (
270
- 'cannot parse BIRTHDAY in {0} in collection {1}' .format (href , calendar ))
271
- return
272
- if 'FN' in vcard :
273
- name = vcard ['FN' ]
274
- else :
275
- n = vcard ['N' ].split (';' )
276
- name = ' ' .join ([n [1 ], n [2 ], n [0 ]])
277
- vevent = icalendar .Event ()
278
- vevent .add ('dtstart' , bday )
279
- vevent .add ('dtend' , bday + dt .timedelta (days = 1 ))
280
- if bday .month == 2 and bday .day == 29 : # leap year
281
- vevent .add ('rrule' , {'freq' : 'YEARLY' , 'BYYEARDAY' : 60 })
282
- else :
283
- vevent .add ('rrule' , {'freq' : 'YEARLY' })
284
- if orig_bday :
285
- vevent .add ('x-birthday' ,
286
- '{:04}{:02}{:02}' .format (bday .year , bday .month , bday .day ))
287
- vevent .add ('x-fname' , name )
288
- vevent .add ('summary' , '{0}\' s birthday' .format (name ))
289
- vevent .add ('uid' , href )
290
- vevent_str = vevent .to_ical ().decode ('utf-8' )
291
- self ._update_impl (vevent , href , calendar )
292
- sql_s = ('INSERT INTO events (item, etag, href, calendar) VALUES (?, ?, ?, ?);' )
293
- stuple = (vevent_str , etag , href , calendar )
294
- self .sql_ex (sql_s , stuple )
290
+ n = vcard ['N' ].split (';' )
291
+ name = ' ' .join ([n [1 ], n [2 ], n [0 ]])
292
+ vevent = icalendar .Event ()
293
+ vevent .add ('dtstart' , date )
294
+ vevent .add ('dtend' , date + dt .timedelta (days = 1 ))
295
+ if date .month == 2 and date .day == 29 : # leap year
296
+ vevent .add ('rrule' , {'freq' : 'YEARLY' , 'BYYEARDAY' : 60 })
297
+ else :
298
+ vevent .add ('rrule' , {'freq' : 'YEARLY' })
299
+ description = get_vcard_event_description (vcard , key )
300
+ if orig_date :
301
+ if key == 'BDAY' :
302
+ xtag = 'x-birthday'
303
+ elif key .endswith ('ANNIVERSARY' ):
304
+ xtag = 'x-anniversary'
305
+ else :
306
+ xtag = 'x-abdate'
307
+ vevent .add ('x-ablabel' , description )
308
+ vevent .add (xtag ,
309
+ '{:04}{:02}{:02}' .format (date .year , date .month , date .day ))
310
+ vevent .add ('x-fname' , name )
311
+ vevent .add ('summary' ,
312
+ '{0}\' s {1}' .format (name , description ))
313
+ vevent .add ('uid' , href + key )
314
+ vevent_str = vevent .to_ical ().decode ('utf-8' )
315
+ self ._update_impl (vevent , href + key , calendar )
316
+ sql_s = ('INSERT INTO events (item, etag, href, calendar)'
317
+ ' VALUES (?, ?, ?, ?);' )
318
+ stuple = (vevent_str , etag , href + key , calendar )
319
+ self .sql_ex (sql_s , stuple )
295
320
296
321
def _update_impl (self , vevent : icalendar .cal .Event , href : str , calendar : str ) -> None :
297
322
"""insert `vevent` into the database
@@ -407,6 +432,23 @@ def delete(self, href: str, etag: Any=None, calendar: str=None):
407
432
sql_s = 'DELETE FROM events WHERE href = ? AND calendar = ?;'
408
433
self .sql_ex (sql_s , (href , calendar ))
409
434
435
+ def deletelike (self , href : str , etag : Any = None , calendar : str = None ):
436
+ """
437
+ removes events from the db that match an SQL 'like' statement,
438
+
439
+ :param href: The pattern of hrefs to delete. May contain SQL wildcards
440
+ like '%'
441
+ :param etag: only there for compatibility with vdirsyncer's Storage,
442
+ we always delete
443
+ :returns: None
444
+ """
445
+ assert calendar is not None
446
+ for table in ['recs_loc' , 'recs_float' ]:
447
+ sql_s = 'DELETE FROM {0} WHERE href LIKE ? AND calendar = ?;' .format (table )
448
+ self .sql_ex (sql_s , (href , calendar ))
449
+ sql_s = 'DELETE FROM events WHERE href LIKE ? AND calendar = ?;'
450
+ self .sql_ex (sql_s , (href , calendar ))
451
+
410
452
def list (self , calendar ):
411
453
""" list all events in `calendar`
412
454
@@ -611,3 +653,22 @@ def calc_shift_deltas(vevent: icalendar.Event) -> Tuple[dt.timedelta, dt.timedel
611
653
except KeyError :
612
654
duration = vevent ['DURATION' ].dt
613
655
return start_shift , duration
656
+
657
+
658
+ def get_vcard_event_description (vcard : icalendar .cal .Component , key : str ) -> str :
659
+ if key == 'BDAY' :
660
+ return 'birthday'
661
+ elif key .endswith ('ANNIVERSARY' ):
662
+ return 'anniversary'
663
+ elif key .endswith ('X-ABDATE' ):
664
+ desc_key = key [:- 8 ] + 'X-ABLABEL'
665
+ if desc_key in vcard .keys ():
666
+ return vcard [desc_key ]
667
+ else :
668
+ desc_key = key [:- 8 ] + 'X-ABLabel'
669
+ if desc_key in vcard .keys ():
670
+ return vcard [desc_key ]
671
+ else :
672
+ return 'custom event from vcard'
673
+ else :
674
+ return 'unknown event from vcard'
0 commit comments