sharing/bday/item: add conversion function

This commit is contained in:
Peter Bieringer
2026-03-13 21:34:46 +01:00
parent 03b5ed162f
commit 4cc1f96ada

View File

@@ -26,6 +26,7 @@ Module for address books and calendar entries (see ``Item``).
import binascii
import contextlib
import logging
import math
import os
import re
@@ -33,7 +34,7 @@ from datetime import datetime, timedelta
from hashlib import sha256
from itertools import chain
from typing import (Any, Callable, List, MutableMapping, Optional, Sequence,
Tuple)
Tuple, Union)
import vobject
@@ -42,6 +43,11 @@ from radicale import pathutils, utils
from radicale.item import filter as radicale_filter
from radicale.log import logger
# Product ID for auto-conversion
PRODID_CONVERTED = u"-//Radicale//NONSGML " + utils.package_version("radicale") + "//EN (auto-converted)"
PRODID_SUFFIX = " (auto-converted by Radicale " + utils.package_version("radicale") + ")"
UID_SUFFIX = "-auto-converted-by-Radicale"
def read_components(s: str) -> List[vobject.base.Component]:
"""Wrapper for vobject.readComponents"""
@@ -498,3 +504,114 @@ class Item:
self.time_range
self.component_name
self._vobject_item = orig_vobject_item
def convert_vcf_to_ics(self) -> Union["Item" | None]:
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/item/convert_vcf_to_ics: convert VCF to ICS (href): %r", self.href)
logger.debug("TRACE/item/convert_vcf_to_ics: convert VCF to ICS (vobject): %r", self.vobject_item)
if self.vobject_item.name != "VCARD":
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/item/convert_vcf_to_ics: item is not a VCARD (skip): %r", self.href)
return None
else:
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/item/convert_vcf_to_ics: item is a VCARD (ok): %r", self.href)
if not hasattr(self.vobject_item, "bday"):
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/item/convert_vcf_to_ics: miss bday (skip): %r", self.href)
return None
else:
pass
bday = self.vobject_item.bday
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/item/convert_vcf_to_ics: has bday (ok): %r -> %r", self.href, bday.value)
pattern = re.compile('^([0-9]{4})-?([0-9]{2})-?([0-9]{2})$')
match = pattern.match(bday.value)
if not match:
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/item/convert_vcf_to_ics: has unsupported bday: %r -> %r", self.href, bday.value)
return None
else:
pass
bdayY = int(match[1])
bdayM = int(match[2])
bdayD = int(match[3])
# create ICS
if hasattr(self.vobject_item, "fn"):
name = self.vobject_item.fn.value
elif hasattr(self.vobject_item, "n"):
name = self.vobject_item.n.value
elif hasattr(self.vobject_item, "nickname"):
name = self.vobject_item.nickname.value
else:
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/item/convert_vcf_to_ics: has bday but neither FN or N or NICKNAME (skip): %r", self.href)
return None
# create VCALENDAR
item_ics = vobject.newFromBehavior('vcalendar')
# set PRODID
if hasattr(self.vobject_item, "prodid"):
item_ics.add('prodid').value = self.vobject_item.prodid.value + PRODID_SUFFIX
else:
item_ics.add('prodid').value = PRODID_CONVERTED
# create EVENT
item_ics.add('vevent')
# set DTSTART
date = datetime(bdayY, bdayM, bdayD)
dtstart = date.strftime('%Y%m%d')
item_ics.vevent.add('dtstart;VALUE=DATE').value = dtstart
# calculate and set DTEND
date += timedelta(days=1)
item_ics.vevent.add('dtend;VALUE=DATE').value = date.strftime('%Y%m%d')
# set UID
if hasattr(self.vobject_item, "uid"):
pattern = re.compile('^(.*)-[0-9a-fA-F]{12}(.*)$')
match = pattern.match(self.vobject_item.uid.value)
if match:
# replace part of UUID by bday
item_ics.vevent.add('uid').value = match[1] + '-' + 'bda0' + dtstart + match[2]
else:
item_ics.vevent.add('uid').value = self.vobject_item.uid.value + UID_SUFFIX
else:
item_ics.vevent.add('uid').value = match[1] + match[2] + match[3] + "@" + name.replace(" ", "-") + UID_SUFFIX
# set SUMMARY
item_ics.vevent.add('summary').value = name + " (BDAY)"
# set RRULE
item_ics.vevent.add('rrule').value = "FREQ=YEARLY"
# add transparency
item_ics.vevent.add('transp').value = "TRANSPARENT"
href = self.href
if href is not None:
href = href.rstrip(".vcf") + ".ics"
etag = self.etag
# replace 14 leading chars of etag "<hexdigits>" by special format bda0YYYYMMDD00
etag = '"bda0' + dtstart + '00' + etag[15:]
item_new: Item = Item(
collection=self.collection,
etag=etag,
href=href,
vobject_item=item_ics)
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/storage: item generated/vobject: %r", item_ics.serialize())
logger.debug("TRACE/storage: item orig /etag : %r", self.etag)
logger.debug("TRACE/storage: item generated/etag : %r", item_new.etag)
logger.debug("TRACE/storage: item generated/href : %r", item_new.href)
return item_new