Personal noctalia plugins collection
1#!/usr/bin/env python3
2"""Update or delete a VTODO item via Evolution Data Server."""
3
4import argparse
5import json
6import sys
7from datetime import datetime, timezone
8
9import gi
10gi.require_version("ECal", "2.0")
11gi.require_version("EDataServer", "1.2")
12gi.require_version("ICalGLib", "3.0")
13from gi.repository import ECal, EDataServer, ICalGLib
14
15
16def find_task_source(registry, task_list_uid):
17 source = registry.ref_source(task_list_uid)
18 if source and source.has_extension(EDataServer.SOURCE_EXTENSION_TASK_LIST):
19 return source
20 for src in registry.list_sources(EDataServer.SOURCE_EXTENSION_TASK_LIST):
21 if src.get_display_name() == task_list_uid or src.get_uid() == task_list_uid:
22 return src
23 return None
24
25
26def remove_property(comp, kind):
27 """Remove all properties of the given kind from a component."""
28 prop = comp.get_first_property(kind)
29 while prop:
30 comp.remove_property(prop)
31 prop = comp.get_first_property(kind)
32
33
34def main():
35 parser = argparse.ArgumentParser(description="Update/delete EDS VTODO item")
36 parser.add_argument("--task-list", required=True, help="Task list UID")
37 parser.add_argument("--uid", required=True, help="VTODO UID")
38 parser.add_argument("--action", required=True, choices=["complete", "uncomplete", "delete"],
39 help="Action to perform")
40 args = parser.parse_args()
41
42 try:
43 registry = EDataServer.SourceRegistry.new_sync(None)
44 source = find_task_source(registry, args.task_list)
45 if not source:
46 print(json.dumps({"success": False, "error": f"Task list not found: {args.task_list}"}))
47 sys.exit(1)
48
49 client = ECal.Client.connect_sync(
50 source, ECal.ClientSourceType.TASKS, -1, None
51 )
52
53 if args.action == "delete":
54 client.remove_object_sync(args.uid, None, ECal.ObjModType.ALL, ECal.OperationFlags.NONE, None)
55 print(json.dumps({"success": True}))
56 return
57
58 # For complete/uncomplete, fetch the existing component first
59 success, comp = client.get_object_sync(args.uid, None, None)
60 if not success or not comp:
61 print(json.dumps({"success": False, "error": "VTODO not found"}))
62 sys.exit(1)
63
64 ical = comp.get_icalcomponent()
65
66 if args.action == "complete":
67 ical.set_status(ICalGLib.PropertyStatus.COMPLETED)
68
69 # Set PERCENT-COMPLETE to 100
70 remove_property(ical, ICalGLib.PropertyKind.PERCENTCOMPLETE_PROPERTY)
71 prop = ICalGLib.Property.new_percentcomplete(100)
72 ical.add_property(prop)
73
74 # Set COMPLETED timestamp
75 remove_property(ical, ICalGLib.PropertyKind.COMPLETED_PROPERTY)
76 now = datetime.now(timezone.utc)
77 completed_time = ICalGLib.Time.new_null_time()
78 completed_time.set_date(now.year, now.month, now.day)
79 completed_time.set_time(now.hour, now.minute, now.second)
80 completed_time.set_timezone(ICalGLib.Timezone.get_utc_timezone())
81 prop = ICalGLib.Property.new_completed(completed_time)
82 ical.add_property(prop)
83
84 elif args.action == "uncomplete":
85 ical.set_status(ICalGLib.PropertyStatus.NEEDSACTION)
86
87 # Set PERCENT-COMPLETE to 0
88 remove_property(ical, ICalGLib.PropertyKind.PERCENTCOMPLETE_PROPERTY)
89 prop = ICalGLib.Property.new_percentcomplete(0)
90 ical.add_property(prop)
91
92 # Remove COMPLETED timestamp
93 remove_property(ical, ICalGLib.PropertyKind.COMPLETED_PROPERTY)
94
95 client.modify_object_sync(comp, ECal.ObjModType.ALL, ECal.OperationFlags.NONE, None)
96 print(json.dumps({"success": True}))
97
98 except Exception as e:
99 print(json.dumps({"success": False, "error": str(e)}))
100 sys.exit(1)
101
102
103if __name__ == "__main__":
104 main()