115 lines
3.3 KiB
Python
115 lines
3.3 KiB
Python
import logging
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
from typing import Any
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _extract_uid(ical_data) -> str:
|
|
raw = ical_data
|
|
if isinstance(raw, bytes):
|
|
raw = raw.decode("utf-8", errors="replace")
|
|
for line in str(raw).split("\r\n"):
|
|
if line.upper().startswith("UID:"):
|
|
return line[4:].strip()
|
|
return ""
|
|
|
|
|
|
def _find_event_by_uid(calendar: Any, uid: str) -> Any | None:
|
|
try:
|
|
for event in calendar.events():
|
|
raw = event.data
|
|
if isinstance(raw, bytes):
|
|
content = raw.decode("utf-8", errors="replace")
|
|
else:
|
|
content = str(raw)
|
|
for line in content.split("\r\n"):
|
|
if line.upper().startswith("UID:"):
|
|
if line[4:].strip() == uid:
|
|
return event
|
|
except Exception as exc:
|
|
logger.error("Error scanning events for UID %s: %s", uid, exc)
|
|
return None
|
|
|
|
|
|
def _add_event(calendar: Any, uid: str, ical_data: bytes) -> bool:
|
|
try:
|
|
calendar.add_event(ical_data)
|
|
return True
|
|
except Exception as exc:
|
|
logger.error("Failed to add event %s: %s", uid, exc)
|
|
return False
|
|
|
|
|
|
def _delete_event(calendar: Any, uid: str) -> bool:
|
|
try:
|
|
event = _find_event_by_uid(calendar, uid)
|
|
if event:
|
|
event.delete()
|
|
return True
|
|
logger.error("Event with UID %s not found", uid)
|
|
return False
|
|
except Exception as exc:
|
|
logger.error("Failed to delete event %s: %s", uid, exc)
|
|
return False
|
|
|
|
|
|
def _update_event(calendar: Any, uid: str, ical_data: bytes) -> bool:
|
|
if not _delete_event(calendar, uid):
|
|
return False
|
|
return _add_event(calendar, uid, ical_data)
|
|
|
|
|
|
def apply_adds(calendar: Any, events: dict[str, bytes], max_workers: int = 10) -> tuple[int, int]:
|
|
success = 0
|
|
errors = 0
|
|
|
|
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
futures = {
|
|
executor.submit(_add_event, calendar, uid, data): uid
|
|
for uid, data in events.items()
|
|
}
|
|
for future in as_completed(futures):
|
|
if future.result():
|
|
success += 1
|
|
else:
|
|
errors += 1
|
|
|
|
return success, errors
|
|
|
|
|
|
def apply_updates(calendar: Any, events: dict[str, bytes], max_workers: int = 10) -> tuple[int, int]:
|
|
success = 0
|
|
errors = 0
|
|
|
|
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
futures = {
|
|
executor.submit(_update_event, calendar, uid, data): uid
|
|
for uid, data in events.items()
|
|
}
|
|
for future in as_completed(futures):
|
|
if future.result():
|
|
success += 1
|
|
else:
|
|
errors += 1
|
|
|
|
return success, errors
|
|
|
|
|
|
def apply_deletes(calendar: Any, uids: list[str], max_workers: int = 10) -> tuple[int, int]:
|
|
success = 0
|
|
errors = 0
|
|
|
|
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
futures = {
|
|
executor.submit(_delete_event, calendar, uid): uid
|
|
for uid in uids
|
|
}
|
|
for future in as_completed(futures):
|
|
if future.result():
|
|
success += 1
|
|
else:
|
|
errors += 1
|
|
|
|
return success, errors
|