icalcli 1.1__tar.gz → 1.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {icalcli-1.1 → icalcli-1.1.2}/PKG-INFO +1 -1
- {icalcli-1.1 → icalcli-1.1.2}/icalcli/etesync_backend/etebase_crud.py +74 -26
- {icalcli-1.1 → icalcli-1.1.2}/icalcli/etesync_backend/etebase_interface.py +11 -7
- {icalcli-1.1 → icalcli-1.1.2}/icalcli/icalcli.py +12 -2
- {icalcli-1.1 → icalcli-1.1.2}/icalcli.egg-info/PKG-INFO +1 -1
- {icalcli-1.1 → icalcli-1.1.2}/setup.py +1 -1
- {icalcli-1.1 → icalcli-1.1.2}/LICENSE +0 -0
- {icalcli-1.1 → icalcli-1.1.2}/README.md +0 -0
- {icalcli-1.1 → icalcli-1.1.2}/icalcli/__init__.py +0 -0
- {icalcli-1.1 → icalcli-1.1.2}/icalcli/argparsers.py +0 -0
- {icalcli-1.1 → icalcli-1.1.2}/icalcli/etesync_backend/__init__.py +0 -0
- {icalcli-1.1 → icalcli-1.1.2}/icalcli/etesync_backend/etesync_crud.py +0 -0
- {icalcli-1.1 → icalcli-1.1.2}/icalcli/etesync_backend/etesync_interface.py +0 -0
- {icalcli-1.1 → icalcli-1.1.2}/icalcli/file_backend/__init__.py +0 -0
- {icalcli-1.1 → icalcli-1.1.2}/icalcli/file_backend/ics_interface.py +0 -0
- {icalcli-1.1 → icalcli-1.1.2}/icalcli/printer.py +0 -0
- {icalcli-1.1 → icalcli-1.1.2}/icalcli/utils.py +0 -0
- {icalcli-1.1 → icalcli-1.1.2}/icalcli.egg-info/SOURCES.txt +0 -0
- {icalcli-1.1 → icalcli-1.1.2}/icalcli.egg-info/dependency_links.txt +0 -0
- {icalcli-1.1 → icalcli-1.1.2}/icalcli.egg-info/entry_points.txt +0 -0
- {icalcli-1.1 → icalcli-1.1.2}/icalcli.egg-info/requires.txt +0 -0
- {icalcli-1.1 → icalcli-1.1.2}/icalcli.egg-info/top_level.txt +0 -0
- {icalcli-1.1 → icalcli-1.1.2}/setup.cfg +0 -0
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
# this is part of the etesync 2.0 backend
|
|
4
4
|
# for etesync 1.0 backend see etesync_crud.py
|
|
5
5
|
|
|
6
|
-
from etebase import Client, Account, FetchOptions
|
|
6
|
+
from etebase import Client, Account, FetchOptions, Base64Url
|
|
7
7
|
from time import sleep, time
|
|
8
|
+
import json
|
|
8
9
|
|
|
9
10
|
# The EtesyncCRUD class exposes methods for each of the CRUD operations
|
|
10
11
|
# (Create, Retrieve, Update and Delete) and for sync with the server.
|
|
@@ -31,7 +32,9 @@ from time import sleep, time
|
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
class EtebaseCRUD:
|
|
34
|
-
|
|
35
|
+
|
|
36
|
+
def __init__(self, user, server_url, password, calendar_uid,
|
|
37
|
+
cache_file=None, silent=True):
|
|
35
38
|
"""Initialize
|
|
36
39
|
|
|
37
40
|
Parameters
|
|
@@ -40,77 +43,95 @@ class EtebaseCRUD:
|
|
|
40
43
|
password : etebase password
|
|
41
44
|
server_url : url of etebase server
|
|
42
45
|
calendar_uid : uid of calendar
|
|
46
|
+
cache_file: path to the cache file or None
|
|
43
47
|
"""
|
|
44
48
|
client = Client(user, server_url)
|
|
45
49
|
etebase = Account.login(client, user, password)
|
|
46
50
|
col_mgr = etebase.get_collection_manager()
|
|
47
51
|
collection = col_mgr.fetch(calendar_uid)
|
|
48
52
|
self.item_mgr = col_mgr.get_item_manager(collection)
|
|
53
|
+
self.items = {}
|
|
54
|
+
self.stoken = None
|
|
55
|
+
self.raw_events = []
|
|
56
|
+
self.cache_file = cache_file
|
|
57
|
+
self.load_cache()
|
|
49
58
|
self.sync(silent)
|
|
59
|
+
print("Reading all events")
|
|
60
|
+
self.get_all_events()
|
|
50
61
|
|
|
51
|
-
def create_event(self, event,
|
|
62
|
+
def create_event(self, event, event_uid):
|
|
52
63
|
"""Create event
|
|
53
64
|
|
|
54
65
|
Parameters
|
|
55
66
|
----------
|
|
56
67
|
event : iCalendar file as bytes
|
|
57
68
|
(calendar containing one event to be added)
|
|
69
|
+
event_uid : uid of event to be updated
|
|
58
70
|
"""
|
|
59
71
|
item = self.item_mgr.create(
|
|
60
72
|
{
|
|
61
|
-
"name":
|
|
73
|
+
"name": event_uid,
|
|
62
74
|
"mtime": int(round(time() * 1000))
|
|
63
75
|
},
|
|
64
76
|
event
|
|
65
77
|
)
|
|
66
78
|
self.item_mgr.batch([item])
|
|
67
79
|
|
|
68
|
-
def update_event(self, event,
|
|
80
|
+
def update_event(self, event, event_uid):
|
|
69
81
|
"""Edit event
|
|
70
82
|
|
|
71
83
|
Parameters
|
|
72
84
|
----------
|
|
73
85
|
event : iCalendar file as bytes
|
|
74
86
|
(calendar containing one event to be updated)
|
|
75
|
-
|
|
87
|
+
event_uid : uid of event to be updated
|
|
76
88
|
"""
|
|
77
|
-
item = self.item_mgr.fetch(
|
|
89
|
+
item = self.item_mgr.fetch(self.event_uid_to_item_uid[event_uid])
|
|
90
|
+
assert item.meta['name'] == event_uid
|
|
78
91
|
item.content = event
|
|
92
|
+
# item.meta["mtime"] = int(round(time() * 1000))
|
|
79
93
|
self.item_mgr.batch([item])
|
|
80
94
|
|
|
81
|
-
def retrieve_event(self,
|
|
95
|
+
def retrieve_event(self, event_uid):
|
|
82
96
|
r"""Retrieve event by uid
|
|
83
97
|
|
|
84
98
|
Parameters
|
|
85
99
|
----------
|
|
86
|
-
|
|
100
|
+
event_uid : uid of event to be retrieved
|
|
87
101
|
|
|
88
102
|
Returns
|
|
89
103
|
-------
|
|
90
104
|
iCalendar file (as a string)
|
|
91
105
|
"""
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
"""Retrieve all events in calendar
|
|
96
|
-
|
|
97
|
-
Returns
|
|
98
|
-
-------
|
|
99
|
-
List of iCalendar files (as strings)
|
|
100
|
-
"""
|
|
101
|
-
return [e.content.decode() for e in self.items.data if not e.deleted]
|
|
106
|
+
item = self.item_mgr.fetch(self.event_uid_to_item_uid[event_uid])
|
|
107
|
+
assert item.meta['name'] == event_uid
|
|
108
|
+
return item.content.decode()
|
|
102
109
|
|
|
103
|
-
def delete_event(self,
|
|
110
|
+
def delete_event(self, event_uid):
|
|
104
111
|
"""Delete event and sync calendar
|
|
105
112
|
|
|
106
113
|
Parameters
|
|
107
114
|
----------
|
|
108
115
|
uid : uid of event to be deleted
|
|
109
116
|
"""
|
|
110
|
-
item = self.item_mgr.fetch(
|
|
117
|
+
item = self.item_mgr.fetch(self.event_uid_to_item_uid[event_uid])
|
|
118
|
+
assert item.meta['name'] == event_uid
|
|
119
|
+
item.meta["mtime"] = int(round(time() * 1000))
|
|
111
120
|
item.delete()
|
|
112
121
|
self.item_mgr.batch([item])
|
|
113
122
|
|
|
123
|
+
def get_all_events(self):
|
|
124
|
+
"""Retrieve all events in calendar
|
|
125
|
+
|
|
126
|
+
Returns
|
|
127
|
+
-------
|
|
128
|
+
List of iCalendar files (as strings)
|
|
129
|
+
"""
|
|
130
|
+
self.event_uid_to_item_uid = {
|
|
131
|
+
e.meta['name']: e.uid for e in self.items.values()}
|
|
132
|
+
self.raw_events = [e.content.decode()
|
|
133
|
+
for e in self.items.values() if not e.deleted]
|
|
134
|
+
|
|
114
135
|
def sync(self, silent=False):
|
|
115
136
|
"""Initialize
|
|
116
137
|
|
|
@@ -121,17 +142,44 @@ class EtebaseCRUD:
|
|
|
121
142
|
silent or print("Syncing with server. Please wait")
|
|
122
143
|
msg = "etebase fetch attempt {:} failed. Will retry after {:} seconds"
|
|
123
144
|
delay = 5
|
|
124
|
-
|
|
145
|
+
done = False
|
|
146
|
+
chunk = 100
|
|
125
147
|
for i in range(5):
|
|
126
148
|
try:
|
|
127
|
-
|
|
128
|
-
|
|
149
|
+
while not done:
|
|
150
|
+
items = self.item_mgr.list(
|
|
151
|
+
FetchOptions().stoken(self.stoken).limit(chunk))
|
|
152
|
+
self.items.update(
|
|
153
|
+
{item.uid: item for item in items.data})
|
|
154
|
+
self.stoken = items.stoken
|
|
155
|
+
done = items.done
|
|
156
|
+
# silent or print(".", end='')
|
|
129
157
|
break
|
|
130
158
|
except Exception:
|
|
131
159
|
silent or print(msg.format(i+1, delay))
|
|
132
160
|
sleep(delay)
|
|
133
|
-
if
|
|
161
|
+
if done:
|
|
134
162
|
silent or print("Syncing completed.")
|
|
135
|
-
self.
|
|
163
|
+
self.save_cache()
|
|
136
164
|
else:
|
|
137
165
|
print("Syncing with server failed after 5 attempts")
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
def load_cache(self):
|
|
169
|
+
if self.cache_file:
|
|
170
|
+
d = json.load(open(self.cache_file))
|
|
171
|
+
if 'stoken' in d:
|
|
172
|
+
self.stoken = d['stoken']
|
|
173
|
+
if 'blobs' in d:
|
|
174
|
+
for cache_blob in d['blobs']:
|
|
175
|
+
item = self.item_mgr.cache_load(
|
|
176
|
+
Base64Url.from_base64(cache_blob))
|
|
177
|
+
self.items[item.uid] = item
|
|
178
|
+
|
|
179
|
+
def save_cache(self):
|
|
180
|
+
if self.cache_file:
|
|
181
|
+
cache = dict(
|
|
182
|
+
stoken=self.stoken,
|
|
183
|
+
blobs=[Base64Url.to_base64(self.item_mgr.cache_save(item))
|
|
184
|
+
for item in self.items.values()])
|
|
185
|
+
json.dump(cache, open(self.cache_file, 'w'))
|
|
@@ -26,15 +26,16 @@ from icalendar import Calendar
|
|
|
26
26
|
# etes.create_event, etes.retrieve_event,
|
|
27
27
|
# etes.update_event and etes.delete_event
|
|
28
28
|
|
|
29
|
-
# The calling program must explicitly call
|
|
30
|
-
#
|
|
31
|
-
# (
|
|
29
|
+
# The calling program must explicitly call sync() when the server
|
|
30
|
+
# has been updated from another device
|
|
31
|
+
# sync() is not needed after any CRUD operation
|
|
32
32
|
|
|
33
33
|
# No exception handling is done. That is left to the calling program.
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
class EtebaseInterface (EtebaseCRUD):
|
|
37
|
-
def __init__(self, user, server_url, password, calendar_uid,
|
|
37
|
+
def __init__(self, user, server_url, password, calendar_uid,
|
|
38
|
+
cache_file=None, silent=True):
|
|
38
39
|
r"""Initialize EtebaseInterface
|
|
39
40
|
|
|
40
41
|
Parameters
|
|
@@ -45,12 +46,14 @@ class EtebaseInterface (EtebaseCRUD):
|
|
|
45
46
|
calendar_uid : uid of calendar
|
|
46
47
|
"""
|
|
47
48
|
super(EtebaseInterface, self).__init__(
|
|
48
|
-
user, server_url, password,
|
|
49
|
+
user=user, server_url=server_url, password=password,
|
|
50
|
+
calendar_uid=calendar_uid, cache_file=cache_file, silent=silent)
|
|
51
|
+
print("Parsing all events")
|
|
49
52
|
self.all_events()
|
|
50
53
|
|
|
51
54
|
def all_events(self):
|
|
52
55
|
self.events = [Calendar.from_ical(ev).walk('VEVENT')[0]
|
|
53
|
-
for ev in
|
|
56
|
+
for ev in self.raw_events]
|
|
54
57
|
|
|
55
58
|
def create_event(self, event, vtimezone=None):
|
|
56
59
|
r"""Create event
|
|
@@ -60,7 +63,8 @@ class EtebaseInterface (EtebaseCRUD):
|
|
|
60
63
|
event : event to be added (iCalendar object)
|
|
61
64
|
"""
|
|
62
65
|
ics = self.event_to_ics(event, vtimezone)
|
|
63
|
-
|
|
66
|
+
uid = event.decoded('uid').decode()
|
|
67
|
+
EtebaseCRUD.create_event(self, ics, uid)
|
|
64
68
|
|
|
65
69
|
def update_event(self, event, vtimezone=None):
|
|
66
70
|
r"""Update event
|
|
@@ -1317,7 +1317,8 @@ class IcalendarInterface:
|
|
|
1317
1317
|
continue
|
|
1318
1318
|
self.backend_interface.delete_event(self.uid(event))
|
|
1319
1319
|
self.printer.msg("Event deleted\n")
|
|
1320
|
-
self.
|
|
1320
|
+
if self.backend_interface.sync_after_edit:
|
|
1321
|
+
self.backend_cache_dirty = True
|
|
1321
1322
|
deleted += 1
|
|
1322
1323
|
self.printer.msg(f'{deleted} events deleted\n')
|
|
1323
1324
|
|
|
@@ -1606,7 +1607,8 @@ class IcalendarInterface:
|
|
|
1606
1607
|
self.printer.msg("Event %s\n" % ("updated" if old
|
|
1607
1608
|
else "added"))
|
|
1608
1609
|
self.iterate_events(None, [event], print_count=False)
|
|
1609
|
-
self.
|
|
1610
|
+
if self.backend_interface.sync_after_edit:
|
|
1611
|
+
self.backend_cache_dirty = True
|
|
1610
1612
|
return True
|
|
1611
1613
|
|
|
1612
1614
|
def raw_ics(self, original=None):
|
|
@@ -1721,6 +1723,14 @@ def repl(ecal=None):
|
|
|
1721
1723
|
add_parser=get_add_parser(),
|
|
1722
1724
|
backend_interface=config.backend_interface,
|
|
1723
1725
|
printer=printer, **vars(FLAGS))
|
|
1726
|
+
if not hasattr(ecal.backend_interface, "sync_after_edit"):
|
|
1727
|
+
ecal.backend_interface.sync_after_edit = True
|
|
1728
|
+
# The file and etesync 1.0 backends operate on the cache and
|
|
1729
|
+
# they sync to server only when sync() is called
|
|
1730
|
+
# Some backends may work directly on server
|
|
1731
|
+
# and may require sync() only if server updated from another device
|
|
1732
|
+
# Such backends can set the attribute sync_after_edit = False
|
|
1733
|
+
# If attribute is missing, we assume it is True
|
|
1724
1734
|
if hasattr(config, 'timezones') and 'tz' in config.timezones:
|
|
1725
1735
|
IcalendarInterface.calendar_tz = config.timezones['tz']
|
|
1726
1736
|
else:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|