Mercurial > hg > gcmultimerge
annotate multimerge.py @ 31:6becdaa5c45f
Set event colors (just sequentially now) based on which source calendar they belong to.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Tue, 05 Jul 2016 10:33:57 +0300 |
parents | b07612201831 |
children | 5a22a7a08785 |
rev | line source |
---|---|
1 | 1 #!/usr/bin/python |
3 | 2 # coding=utf-8 |
2 | 3 ### |
4 ### Google Calendar MultiMerge v0.000001 | |
5 ### (C) 2016 Matti 'ccr' Hamalainen <ccr@tnsp.org> | |
6 ### | |
7 ### Python 2.7 <= x < 3 required! Please refer to | |
8 ### README.txt for information on other depencies. | |
9 ### | |
1 | 10 import os |
11 import sys | |
12 import signal | |
13 import re | |
29
50711871fd1e
Comment out currently unnecessary imports.
Matti Hamalainen <ccr@tnsp.org>
parents:
28
diff
changeset
|
14 #import time |
50711871fd1e
Comment out currently unnecessary imports.
Matti Hamalainen <ccr@tnsp.org>
parents:
28
diff
changeset
|
15 #import datetime |
14
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
16 |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
17 import smtplib |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
18 from email.mime.text import MIMEText |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
19 |
1 | 20 import httplib2 |
21 import ConfigParser | |
20 | 22 |
1 | 23 import oauth2client |
24 from oauth2client import client | |
25 from oauth2client import tools | |
4 | 26 from oauth2client import file |
1 | 27 from googleapiclient import discovery |
28 | |
29 | |
30 ### | |
31 ### Misc. helper functions | |
32 ### | |
33 | |
34 ## Wrapper for print() that does not break when redirecting stdin/out | |
35 ## because of piped output not having a defined encoding. We default | |
36 ## to UTF-8 encoding in output here. | |
37 def gcm_print(smsg): | |
38 gcm_msgbuf.append(smsg.encode("UTF-8")) | |
39 if sys.stdout.encoding != None: | |
40 print(smsg.encode(sys.stdout.encoding)) | |
41 else: | |
42 print(smsg.encode("UTF-8")) | |
43 | |
44 | |
45 ## Fatal errors | |
46 def gcm_fatal(smsg): | |
47 gcm_print(u"ERROR: "+ smsg) | |
14
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
48 if cfg.email_ok and cfg.email: |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
49 ## If e-mail is set, send e-mail |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
50 msg = MIMEText(("\n".join(gcm_msgbuf)).encode("UTF-8"), "plain") |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
51 msg.set_charset("UTF-8") |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
52 msg["Subject"] = cfg.email_subject |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
53 msg["From"] = cfg.email_sender |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
54 msg["To"] = ",".join(cfg.email_to) |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
55 try: |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
56 smtpH = smtplib.SMTP('localhost') |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
57 smtpH.sendmail(cfg.email_sender, cfg.email_to, msg.as_string()) |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
58 smtpH.quit() |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
59 except: |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
60 gcm_print("FATAL: Oh crap, e-mail sending failed.") |
1 | 61 sys.exit(1) |
62 | |
63 | |
64 ## Debug messages | |
65 def gcm_debug(smsg): | |
66 if cfg.debug: | |
67 gcm_print(u"DBG: "+ smsg) | |
68 else: | |
69 gcm_msgbuf.append(u"DBG: "+ smsg.encode("UTF-8")) | |
70 | |
71 | |
24 | 72 ## Handler for SIGINT signals |
1 | 73 def gcm_signal_handler(signal, frame): |
74 gcm_print("\nQuitting due to SIGINT / Ctrl+C!") | |
75 sys.exit(0) | |
76 | |
77 | |
24 | 78 ## Function for handling Google API credentials |
1 | 79 def gcm_get_credentials(mcfg): |
80 store = oauth2client.file.Storage(mcfg.credential_file) | |
81 credentials = store.get() | |
82 if not credentials or credentials.invalid: | |
83 flow = client.flow_from_clientsecrets(mcfg.secret_file, mcfg.scope) | |
84 flow.user_agent = mcfg.app_name | |
85 credentials = tools.run_flow(flow, store, mcfg) | |
86 if not credentials or credentials.invalid: | |
87 gcm_fatal("Failed to authenticate / invalid credentials.") | |
88 return credentials | |
89 | |
90 | |
91 def gcm_dump_events(events): | |
92 for event in events: | |
93 ev_start = event["start"].get("dateTime", event["start"].get("date")) | |
94 ev_end = event["end"].get("dateTime", event["end"].get("date")) | |
95 gcm_print(u"{0:25} - {1:25} : {2}".format(ev_start, ev_end, event["summary"])) | |
96 | |
26
1267d61f6224
Add function for generating unique internal IDs.
Matti Hamalainen <ccr@tnsp.org>
parents:
25
diff
changeset
|
97 def gcm_generate_ids(events, calendar_id): |
1267d61f6224
Add function for generating unique internal IDs.
Matti Hamalainen <ccr@tnsp.org>
parents:
25
diff
changeset
|
98 if not events: |
1267d61f6224
Add function for generating unique internal IDs.
Matti Hamalainen <ccr@tnsp.org>
parents:
25
diff
changeset
|
99 return events |
1267d61f6224
Add function for generating unique internal IDs.
Matti Hamalainen <ccr@tnsp.org>
parents:
25
diff
changeset
|
100 |
1267d61f6224
Add function for generating unique internal IDs.
Matti Hamalainen <ccr@tnsp.org>
parents:
25
diff
changeset
|
101 for ev in events: |
28 | 102 ev["gcm_cal_id"] = calendar_id |
30 | 103 ev["gcm_id"] = re.sub("[^a-v0-9]", "0", calendar_id.lower()) + ev["id"] |
26
1267d61f6224
Add function for generating unique internal IDs.
Matti Hamalainen <ccr@tnsp.org>
parents:
25
diff
changeset
|
104 |
1267d61f6224
Add function for generating unique internal IDs.
Matti Hamalainen <ccr@tnsp.org>
parents:
25
diff
changeset
|
105 return events |
1267d61f6224
Add function for generating unique internal IDs.
Matti Hamalainen <ccr@tnsp.org>
parents:
25
diff
changeset
|
106 |
1 | 107 |
24 | 108 ## |
109 ## Class for handling configuration / settings | |
110 ## | |
1 | 111 class GCMSettings(dict): |
112 def __init__(self): | |
113 self.m_data = {} | |
114 self.m_saveable = {} | |
5
9d4152f32223
Add some code for settings validation.
Matti Hamalainen <ccr@tnsp.org>
parents:
4
diff
changeset
|
115 self.m_validate = {} |
1 | 116 self.m_translate = {} |
117 | |
118 def __getattr__(self, name): | |
119 if name in self.m_data: | |
120 return self.m_data[name] | |
121 else: | |
122 gcm_fatal("GCMSettings.__getattr__(): No such attribute '"+ name +"'.") | |
123 | |
5
9d4152f32223
Add some code for settings validation.
Matti Hamalainen <ccr@tnsp.org>
parents:
4
diff
changeset
|
124 def mvalidate(self, name, value): |
9d4152f32223
Add some code for settings validation.
Matti Hamalainen <ccr@tnsp.org>
parents:
4
diff
changeset
|
125 if name in self.m_validate and self.m_validate[name]: |
9d4152f32223
Add some code for settings validation.
Matti Hamalainen <ccr@tnsp.org>
parents:
4
diff
changeset
|
126 if not self.m_validate[name](value): |
9d4152f32223
Add some code for settings validation.
Matti Hamalainen <ccr@tnsp.org>
parents:
4
diff
changeset
|
127 gcm_fatal("GCMSettings.mvalidate(): Invalid value for attribute '{0}': {1}".format(name, value)) |
9d4152f32223
Add some code for settings validation.
Matti Hamalainen <ccr@tnsp.org>
parents:
4
diff
changeset
|
128 |
1 | 129 def mtranslate(self, name, value): |
130 if name in self.m_translate and self.m_translate[name]: | |
131 return self.m_translate[name](value) | |
132 else: | |
133 return value | |
134 | |
135 def mdef(self, name, saveable, validate, translate, value): | |
5
9d4152f32223
Add some code for settings validation.
Matti Hamalainen <ccr@tnsp.org>
parents:
4
diff
changeset
|
136 self.mvalidate(name, value) |
1 | 137 self.m_saveable[name] = saveable |
5
9d4152f32223
Add some code for settings validation.
Matti Hamalainen <ccr@tnsp.org>
parents:
4
diff
changeset
|
138 self.m_validate[name] = validate |
9d4152f32223
Add some code for settings validation.
Matti Hamalainen <ccr@tnsp.org>
parents:
4
diff
changeset
|
139 self.m_translate[name] = translate |
1 | 140 self.m_data[name] = self.mtranslate(name, value) |
141 | |
142 def mset(self, name, value): | |
5
9d4152f32223
Add some code for settings validation.
Matti Hamalainen <ccr@tnsp.org>
parents:
4
diff
changeset
|
143 self.mvalidate(name, value) |
1 | 144 if name in self.m_data: |
145 self.m_data[name] = self.mtranslate(name, value) | |
146 else: | |
147 gcm_fatal("GCMSettings.mset(): No such attribute '"+ name +"'.") | |
148 | |
149 def mget(self, name): | |
150 if name in self.m_data: | |
151 return self.m_data[name] | |
152 else: | |
153 return None | |
154 | |
6
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
155 def mread(self, cfgparser, sect): |
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
156 for name in self.m_saveable: |
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
157 if cfgparser.has_option(sect, name): |
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
158 value = cfgparser.get(sect, name) |
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
159 self.mset(name, value) |
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
160 gcm_debug("{0} -> '{1}' == {2}".format(name, value, self.mget(name))) |
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
161 |
23 | 162 def is_str(self, mvalue): |
163 return isinstance(mvalue, basestring) | |
164 | |
165 def is_string(self, mvalue): | |
166 return mvalue == None or self.is_str(mvalue) | |
167 | |
168 def is_log_level(self, mvalue): | |
169 if not self.is_str(mvalue): | |
170 return False | |
171 else: | |
172 return mvalue.upper() in ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"] | |
173 | |
174 def trans_log_level(self, mvalue): | |
175 return mvalue.upper() | |
176 | |
177 def is_filename(self, mvalue): | |
178 if not self.is_str(mvalue): | |
179 return False | |
180 else: | |
181 return re.match("^[a-z0-9][a-z0-9\.\_\-]+$", mvalue, flags=re.IGNORECASE) | |
182 | |
183 def trans_bool(self, mvalue): | |
184 if self.is_str(mvalue): | |
185 if re.match("^\s*(true|1|on|yes)\s*$", mvalue, re.IGNORECASE): | |
186 mvalue = True | |
187 elif re.match("^\s*(false|0|off|no)\s*$", mvalue, re.IGNORECASE): | |
188 mvalue = False | |
189 else: | |
190 return None | |
191 return mvalue | |
192 | |
193 def is_bool(self, mvalue): | |
194 mval = self.trans_bool(mvalue) | |
195 if not isinstance(mval, bool): | |
196 gcm_fatal("GCMSettings.is_bool(): Invalid boolean value '{0}', should be true|false|1|0|on|off|yes|no.".format(mvalue)) | |
197 else: | |
198 return True | |
199 | |
200 def trans_list(self, mvalue): | |
201 morig = mvalue | |
202 if self.is_str(mvalue): | |
203 mvalue = re.split("\s*,\s*", mvalue, flags=re.IGNORECASE) | |
204 if not isinstance(mvalue, list): | |
205 gcm_fatal("GCMSettings.trans_list(): Could not parse list '{0}'.".format(mvalue)) | |
206 elif not isinstance(mvalue, list): | |
207 gcm_fatal("GCMSettings.trans_list(): Invalid value '{0}'.".format(mvalue)) | |
208 return mvalue | |
209 | |
210 def is_list(self, mvalue): | |
211 return self.trans_list(mvalue) | |
212 | |
213 def is_email(self, mvalue): | |
214 if not self.is_string(mvalue): | |
215 return False | |
216 else: | |
217 return re.match("^.*?\s+<[a-z0-9]+[a-z0-9\.\+\-]*\@[a-z0-9]+[a-z0-9\.\-]+>\s*$|[a-z0-9]+[a-z0-9\.\+\-]*\@[a-z0-9]+[a-z0-9\.\-]+", mvalue, flags=re.IGNORECASE) | |
218 | |
219 def trans_email_list(self, mvalue): | |
220 if mvalue == None: | |
221 return mvalue | |
222 else: | |
223 return self.trans_list(mvalue.strip()) | |
224 | |
225 def is_email_list(self, mvalue): | |
226 mvalue = self.trans_email_list(mvalue) | |
227 if mvalue != None: | |
228 for email in mvalue: | |
229 if not self.is_email(email): | |
230 gcm_fatal("Invalid e-mail address '{0}' in list {1}.".format(email, ", ".join(mvalue))) | |
231 return True | |
232 | |
1 | 233 |
234 ### | |
235 ### Main program starts | |
236 ### | |
237 gcm_msgbuf = [] | |
238 signal.signal(signal.SIGINT, gcm_signal_handler) | |
239 | |
240 | |
241 ## Settings | |
242 cfg = GCMSettings() | |
243 | |
23 | 244 cfg.mdef("debug", True, cfg.is_bool, cfg.trans_bool, False) |
1 | 245 |
14
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
246 cfg.mdef("email_ok", False, None, None, False) |
23 | 247 cfg.mdef("email", True, cfg.is_bool, cfg.trans_bool, False) |
248 cfg.mdef("email_to", True, cfg.is_email_list, cfg.trans_email_list, None) | |
249 cfg.mdef("email_sender", True, cfg.is_email, None, None) | |
250 cfg.mdef("email_subject", True, cfg.is_string, None, "Google Calendar MultiMerge status") | |
14
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
251 |
23 | 252 cfg.mdef("source_regex", True, cfg.is_string, None, "^R:\s*(.*?)\s*\(\s*(.+?)\s*\)\s*$") |
253 cfg.mdef("source_regmap", False, cfg.is_list, cfg.trans_list, [1, 2]) | |
1 | 254 cfg.mdef("source_regmap_len", False, None, None, len(cfg.source_regmap)) |
255 | |
23 | 256 cfg.mdef("dest_name", True, cfg.is_string, None, u"Raahen kansainvälisyystoiminta") |
257 cfg.mdef("dest_id", True, cfg.is_string, None, None) | |
1 | 258 |
259 cfg.mdef("noauth_local_webserver", False, None, None, True) | |
260 #cfg.mdef("auth_host_name", False, None, None, "localhost") | |
261 #cfg.mdef("auth_host_port", False, None, None, [8080, 8090]) | |
23 | 262 cfg.mdef("logging_level", True, cfg.is_log_level, cfg.trans_log_level, "ERROR") |
1 | 263 |
264 # No need to touch these | |
265 cfg.mdef("app_name", False, None, None, "Google Calendar MultiMerge") | |
266 cfg.mdef("scope", False, None, None, "https://www.googleapis.com/auth/calendar") | |
267 #cfg.mdef("scope", False, None, None, "https://www.googleapis.com/auth/calendar.readonly") | |
23 | 268 cfg.mdef("secret_file", True, cfg.is_filename, None, "client_secret.json") |
269 cfg.mdef("credential_file", True, cfg.is_filename, None, "client_credentials.json") | |
1 | 270 |
6
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
271 |
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
272 ## Read, parse and validate configuration file |
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
273 if len(sys.argv) > 1: |
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
274 gcm_debug("Reading configuration from '{0}'.".format(sys.argv[1])) |
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
275 try: |
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
276 cfgparser = ConfigParser.RawConfigParser() |
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
277 cfgparser.read(sys.argv[1]) |
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
278 except Exception as e: |
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
279 gcm_fatal("Failed to read configuration file '{0}': {1}".format(sys.argv[1], str(e))) |
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
280 |
7
f2ecfb3e04ee
Check that the required section exists in configuration.
Matti Hamalainen <ccr@tnsp.org>
parents:
6
diff
changeset
|
281 # Check that the required section exists |
f2ecfb3e04ee
Check that the required section exists in configuration.
Matti Hamalainen <ccr@tnsp.org>
parents:
6
diff
changeset
|
282 section = "gcm" |
f2ecfb3e04ee
Check that the required section exists in configuration.
Matti Hamalainen <ccr@tnsp.org>
parents:
6
diff
changeset
|
283 if not cfgparser.has_section(section): |
f2ecfb3e04ee
Check that the required section exists in configuration.
Matti Hamalainen <ccr@tnsp.org>
parents:
6
diff
changeset
|
284 gcm_fatal("Invalid configuration, missing '{0}' section.".format(section)) |
f2ecfb3e04ee
Check that the required section exists in configuration.
Matti Hamalainen <ccr@tnsp.org>
parents:
6
diff
changeset
|
285 |
10
b237b96602ad
We need to handle "debug" setting before other settings, so we need a
Matti Hamalainen <ccr@tnsp.org>
parents:
9
diff
changeset
|
286 # Debug setting is a special case, we need to get it |
b237b96602ad
We need to handle "debug" setting before other settings, so we need a
Matti Hamalainen <ccr@tnsp.org>
parents:
9
diff
changeset
|
287 # set before everything else, so do it here .. |
b237b96602ad
We need to handle "debug" setting before other settings, so we need a
Matti Hamalainen <ccr@tnsp.org>
parents:
9
diff
changeset
|
288 if cfgparser.has_option(section, "debug"): |
b237b96602ad
We need to handle "debug" setting before other settings, so we need a
Matti Hamalainen <ccr@tnsp.org>
parents:
9
diff
changeset
|
289 cfg.mset("debug", cfgparser.get(section, "debug")) |
b237b96602ad
We need to handle "debug" setting before other settings, so we need a
Matti Hamalainen <ccr@tnsp.org>
parents:
9
diff
changeset
|
290 |
6
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
291 # Parse the settings and validate |
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
292 cfg.mread(cfgparser, section) |
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
293 |
8 | 294 |
295 ## Validate settings | |
14
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
296 if cfg.email: |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
297 if cfg.email_subject == None or len(cfg.email_subject) == 0: |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
298 gcm_fatal("E-mail enabled but email_subject not set.") |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
299 elif cfg.email_sender == None: |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
300 gcm_fatal("E-mail enabled but email_sender not set.") |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
301 elif cfg.email_to == None: |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
302 gcm_fatal("E-mail enabled but email_to not set.") |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
303 else: |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
304 cfg.mset("email_ok", True) |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
305 |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
306 |
9
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
307 if len(cfg.source_regmap) != cfg.source_regmap_len: |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
308 gcm_fatal("Setting source_regmap list must be {0} items.".format(cfg.source_regmap_len)) |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
309 else: |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
310 # Force to integers |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
311 try: |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
312 cfg.source_regmap = map(lambda x: int(x), cfg.source_regmap) |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
313 except Exception as e: |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
314 gcm_fatal("Invalid source_regmap: {0}".format(str(e))) |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
315 |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
316 |
8 | 317 if not cfg.dest_name and not cfg.dest_id: |
318 gcm_fatal("Target calendar ID or name required, but not set.") | |
319 | |
320 | |
321 if cfg.dest_name: | |
322 cfg.mset("dest_name", cfg.mget("dest_name").strip()) | |
323 | |
324 | |
1 | 325 ## Initialize and authorize API connection |
326 credentials = gcm_get_credentials(cfg) | |
327 http = credentials.authorize(httplib2.Http()) | |
328 service = discovery.build("calendar", "v3", http=http) | |
329 | |
330 | |
331 ## Fetch complete calendar list | |
332 gcm_debug("Fetching available calendars ..") | |
333 calendars = [] | |
19 | 334 cal_token = None |
1 | 335 while True: |
336 # We want everything except deleted and hidden calendars | |
17 | 337 result = service.calendarList().list( |
1 | 338 showHidden=False, |
339 showDeleted=False, | |
19 | 340 pageToken=cal_token |
1 | 341 ).execute() |
342 | |
17 | 343 calendars.extend(result.get("items", [])) |
19 | 344 cal_token = result.get("nextPageToken") |
345 if not cal_token: | |
1 | 346 break |
347 | |
348 if len(calendars) == 0: | |
349 gcm_fatal("No calendars found?") | |
350 | |
351 | |
9
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
352 ## Filter desired SOURCE calendars based on specified regexp |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
353 src_re = re.compile(cfg.source_regex) |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
354 src_calendars = [] |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
355 for calendar in calendars: |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
356 if "summary" in calendar: |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
357 if not cfg.dest_id and cfg.dest_name == calendar["summary"].strip(): |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
358 cfg.mset("dest_id", calendar["id"]) |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
359 |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
360 mre = src_re.match(calendar["summary"]) |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
361 if mre: |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
362 calendar["gcm_title"] = mre.group(cfg.source_regmap[0]) |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
363 calendar["gcm_id"] = mre.group(cfg.source_regmap[1]) |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
364 src_calendars.append(calendar) |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
365 |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
366 |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
367 ## Check if we have target ID |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
368 if not cfg.dest_id: |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
369 gcm_fatal(u"Could not find target/destination calendar ID for '"+ cfg.dest_name +"'.") |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
370 |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
371 |
11
fcdee7c04ed8
Implement fetching of source events.
Matti Hamalainen <ccr@tnsp.org>
parents:
10
diff
changeset
|
372 ## Now, we fetch and collect events |
fcdee7c04ed8
Implement fetching of source events.
Matti Hamalainen <ccr@tnsp.org>
parents:
10
diff
changeset
|
373 gcm_debug(u"Fetching calendar events .. ") |
19 | 374 src_events = [] |
31
6becdaa5c45f
Set event colors (just sequentially now) based on which source calendar they belong to.
Matti Hamalainen <ccr@tnsp.org>
parents:
30
diff
changeset
|
375 color_id = 0 |
11
fcdee7c04ed8
Implement fetching of source events.
Matti Hamalainen <ccr@tnsp.org>
parents:
10
diff
changeset
|
376 for calendar in src_calendars: |
31
6becdaa5c45f
Set event colors (just sequentially now) based on which source calendar they belong to.
Matti Hamalainen <ccr@tnsp.org>
parents:
30
diff
changeset
|
377 color_id = color_id + 1 |
11
fcdee7c04ed8
Implement fetching of source events.
Matti Hamalainen <ccr@tnsp.org>
parents:
10
diff
changeset
|
378 gcm_debug("- "+calendar["id"]) |
17 | 379 result = service.events().list( |
11
fcdee7c04ed8
Implement fetching of source events.
Matti Hamalainen <ccr@tnsp.org>
parents:
10
diff
changeset
|
380 timeZone="EEST", |
fcdee7c04ed8
Implement fetching of source events.
Matti Hamalainen <ccr@tnsp.org>
parents:
10
diff
changeset
|
381 calendarId=calendar["id"], |
fcdee7c04ed8
Implement fetching of source events.
Matti Hamalainen <ccr@tnsp.org>
parents:
10
diff
changeset
|
382 singleEvents=True, |
fcdee7c04ed8
Implement fetching of source events.
Matti Hamalainen <ccr@tnsp.org>
parents:
10
diff
changeset
|
383 showDeleted=False, |
17 | 384 # orderBy="startTime", |
385 ).execute() | |
11
fcdee7c04ed8
Implement fetching of source events.
Matti Hamalainen <ccr@tnsp.org>
parents:
10
diff
changeset
|
386 |
fcdee7c04ed8
Implement fetching of source events.
Matti Hamalainen <ccr@tnsp.org>
parents:
10
diff
changeset
|
387 # Add events, if any, to main list |
26
1267d61f6224
Add function for generating unique internal IDs.
Matti Hamalainen <ccr@tnsp.org>
parents:
25
diff
changeset
|
388 events = gcm_generate_ids(result.get("items", []), calendar["id"]) |
11
fcdee7c04ed8
Implement fetching of source events.
Matti Hamalainen <ccr@tnsp.org>
parents:
10
diff
changeset
|
389 if events: |
31
6becdaa5c45f
Set event colors (just sequentially now) based on which source calendar they belong to.
Matti Hamalainen <ccr@tnsp.org>
parents:
30
diff
changeset
|
390 for event in events: |
6becdaa5c45f
Set event colors (just sequentially now) based on which source calendar they belong to.
Matti Hamalainen <ccr@tnsp.org>
parents:
30
diff
changeset
|
391 event["colorId"] = color_id |
21
f392d495c5b6
We need to extend a list, not append to it.
Matti Hamalainen <ccr@tnsp.org>
parents:
20
diff
changeset
|
392 src_events.extend(events) |
11
fcdee7c04ed8
Implement fetching of source events.
Matti Hamalainen <ccr@tnsp.org>
parents:
10
diff
changeset
|
393 if cfg.debug: |
fcdee7c04ed8
Implement fetching of source events.
Matti Hamalainen <ccr@tnsp.org>
parents:
10
diff
changeset
|
394 gcm_dump_events(events) |
fcdee7c04ed8
Implement fetching of source events.
Matti Hamalainen <ccr@tnsp.org>
parents:
10
diff
changeset
|
395 |
fcdee7c04ed8
Implement fetching of source events.
Matti Hamalainen <ccr@tnsp.org>
parents:
10
diff
changeset
|
396 |
13
dd240a7ad913
Fetch current events in destination calendar.
Matti Hamalainen <ccr@tnsp.org>
parents:
11
diff
changeset
|
397 ## Get current events |
dd240a7ad913
Fetch current events in destination calendar.
Matti Hamalainen <ccr@tnsp.org>
parents:
11
diff
changeset
|
398 gcm_debug(u"Fetching current target calendar events {0}".format(cfg.dest_id)) |
17 | 399 result = service.events().list( |
13
dd240a7ad913
Fetch current events in destination calendar.
Matti Hamalainen <ccr@tnsp.org>
parents:
11
diff
changeset
|
400 calendarId=cfg.dest_id, |
dd240a7ad913
Fetch current events in destination calendar.
Matti Hamalainen <ccr@tnsp.org>
parents:
11
diff
changeset
|
401 singleEvents=True, |
dd240a7ad913
Fetch current events in destination calendar.
Matti Hamalainen <ccr@tnsp.org>
parents:
11
diff
changeset
|
402 showDeleted=True).execute() |
dd240a7ad913
Fetch current events in destination calendar.
Matti Hamalainen <ccr@tnsp.org>
parents:
11
diff
changeset
|
403 |
26
1267d61f6224
Add function for generating unique internal IDs.
Matti Hamalainen <ccr@tnsp.org>
parents:
25
diff
changeset
|
404 dst_events = gcm_generate_ids(result.get("items", []), cfg.dest_id) |
22 | 405 if dst_events: |
406 gcm_debug(u"Found {0} event(s).".format(len(dst_events))) | |
13
dd240a7ad913
Fetch current events in destination calendar.
Matti Hamalainen <ccr@tnsp.org>
parents:
11
diff
changeset
|
407 else: |
dd240a7ad913
Fetch current events in destination calendar.
Matti Hamalainen <ccr@tnsp.org>
parents:
11
diff
changeset
|
408 gcm_debug(u"No current events.") |
dd240a7ad913
Fetch current events in destination calendar.
Matti Hamalainen <ccr@tnsp.org>
parents:
11
diff
changeset
|
409 |
dd240a7ad913
Fetch current events in destination calendar.
Matti Hamalainen <ccr@tnsp.org>
parents:
11
diff
changeset
|
410 |
27 | 411 ## Start merging events .. |
412 gcm_debug("Re-merging events to target calendar ..") | |
413 dst_gcm_ids = map(lambda x: x["gcm_id"], dst_events) | |
414 src_ids = map(lambda x: x["id"], src_events) | |
415 dst_ids = map(lambda x: x["id"], dst_events) | |
416 rm_ids = [x for x in dst_ids if x not in src_ids] | |
417 | |
418 for event in src_events: | |
419 # Does the event exist already in the target? | |
420 if event["gcm_id"] in dst_gcm_ids: | |
421 # Yes. Thus, we just update the event | |
422 else: | |
423 ## Insert new event | |
424 | |
425 gcm_debug("Finished.") |