Mercurial > hg > gcmultimerge
comparison multimerge.py @ 138:d3135eff1bab
Cleanup.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Sat, 22 Jan 2022 23:58:22 +0200 |
parents | afdef805e9b7 |
children | 23fc7cd1cd53 |
comparison
equal
deleted
inserted
replaced
137:4bf07e58baa8 | 138:d3135eff1bab |
---|---|
68 print("{0} | {1}".format(gcm_timestamp(time.localtime()), smsg)) | 68 print("{0} | {1}".format(gcm_timestamp(time.localtime()), smsg)) |
69 | 69 |
70 | 70 |
71 ## Fatal error handler | 71 ## Fatal error handler |
72 def gcm_fatal(smsg): | 72 def gcm_fatal(smsg): |
73 gcm_print(u"ERROR: "+ smsg) | 73 gcm_print("ERROR: "+ smsg) |
74 if cfg.email_ok and cfg.email != "off": | 74 if cfg.email_ok and cfg.email != "off": |
75 ## If e-mail is not "off", send e-mail | 75 ## If e-mail is not "off", send e-mail |
76 msg = MIMEText(("\n".join(gcm_msgbuf)).encode("UTF-8"), "plain") | 76 msg = MIMEText(("\n".join(gcm_msgbuf)).encode("UTF-8"), "plain") |
77 msg.set_charset("UTF-8") | 77 msg.set_charset("UTF-8") |
78 msg["Subject"] = cfg.email_subject | 78 msg["Subject"] = cfg.email_subject |
98 # Use local sendmail | 98 # Use local sendmail |
99 gcm_print("Using sendmail {0}".format(cfg.email_sendmail)) | 99 gcm_print("Using sendmail {0}".format(cfg.email_sendmail)) |
100 p = Popen([cfg.email_sendmail, "-t", "-oi"], stdin=PIPE) | 100 p = Popen([cfg.email_sendmail, "-t", "-oi"], stdin=PIPE) |
101 p.communicate(msg.as_string()) | 101 p.communicate(msg.as_string()) |
102 except Exception as e: | 102 except Exception as e: |
103 gcm_print(u"FATAL: Oh noes, e-mail sending failed: {0}".format(str(e))) | 103 gcm_print("FATAL: Oh noes, e-mail sending failed: {0}".format(str(e))) |
104 sys.exit(1) | 104 sys.exit(1) |
105 | 105 |
106 | 106 |
107 ## Debug messages | 107 ## Debug messages |
108 def gcm_check_debug(level): | 108 def gcm_check_debug(level): |
109 return cfg.debug and gcm_get_log_level() >= level | 109 return cfg.debug and gcm_get_log_level() >= level |
110 | 110 |
111 def gcm_debug(level, smsg): | 111 def gcm_debug(level, smsg): |
112 if gcm_check_debug(level): | 112 if gcm_check_debug(level): |
113 gcm_print(u"DBG: {0}".format(smsg)) | 113 gcm_print("DBG: {0}".format(smsg)) |
114 else: | 114 else: |
115 gcm_msgbuf.append(u"DBG: {0}".format(smsg)) | 115 gcm_msgbuf.append("DBG: {0}".format(smsg)) |
116 | 116 |
117 | 117 |
118 ## Handler for SIGINT signals | 118 ## Handler for SIGINT signals |
119 def gcm_signal_handler(signal, frame): | 119 def gcm_signal_handler(signal, frame): |
120 gcm_print(u"\nQuitting due to SIGINT / Ctrl+C!") | 120 gcm_print("\nQuitting due to SIGINT / Ctrl+C!") |
121 sys.exit(0) | 121 sys.exit(0) |
122 | 122 |
123 | 123 |
124 ## Function for handling Google API credentials | 124 ## Function for handling Google API credentials |
125 def gcm_get_credentials(mcfg, credential_file, secret_file): | 125 def gcm_get_credentials(mcfg, credential_file, secret_file): |
126 try: | 126 try: |
127 store = oauth2client.file.Storage(credential_file) | 127 store = oauth2client.file.Storage(credential_file) |
128 except Exception as e: | 128 except Exception as e: |
129 gcm_fatal(u"Failed to read credential file:\n{0}\n\nERROR: {1}\n".format(credential_file, str(e))) | 129 gcm_fatal("Failed to read credential file:\n{0}\n\nERROR: {1}\n".format(credential_file, str(e))) |
130 | 130 |
131 credentials = store.get() | 131 credentials = store.get() |
132 if not credentials or credentials.invalid: | 132 if not credentials or credentials.invalid: |
133 try: | 133 try: |
134 flow = client.flow_from_clientsecrets(secret_file, mcfg.scope) | 134 flow = client.flow_from_clientsecrets(secret_file, mcfg.scope) |
135 except Exception as e: | 135 except Exception as e: |
136 gcm_fatal(u"Failed to fetch client secret:\n{0}\n\nERROR: {1}\n".format(secret_file, str(e))) | 136 gcm_fatal("Failed to fetch client secret:\n{0}\n\nERROR: {1}\n".format(secret_file, str(e))) |
137 | 137 |
138 flow.user_agent = mcfg.app_name | 138 flow.user_agent = mcfg.app_name |
139 credentials = tools.run_flow(flow, store, mcfg) | 139 credentials = tools.run_flow(flow, store, mcfg) |
140 if not credentials or credentials.invalid: | 140 if not credentials or credentials.invalid: |
141 gcm_fatal(u"Failed to authenticate / invalid credentials.") | 141 gcm_fatal("Failed to authenticate / invalid credentials.") |
142 return credentials | 142 return credentials |
143 | 143 |
144 | 144 |
145 ## Dump/print a given list of events for debugging purposes | 145 ## Dump/print a given list of events for debugging purposes |
146 def gcm_dump_events(events, show): | 146 def gcm_dump_events(events, show): |
148 if show == None or show(event): | 148 if show == None or show(event): |
149 ev_start = event["start"].get("dateTime", event["start"].get("date")) if "start" in event else "?" | 149 ev_start = event["start"].get("dateTime", event["start"].get("date")) if "start" in event else "?" |
150 ev_end = event["end"].get("dateTime", event["end"].get("date")) if "end" in event else "?" | 150 ev_end = event["end"].get("dateTime", event["end"].get("date")) if "end" in event else "?" |
151 summary = event["summary"] if "summary" in event else "?" | 151 summary = event["summary"] if "summary" in event else "?" |
152 status = "*" if event["status"] != u"cancelled" else "!" | 152 status = "*" if event["status"] != u"cancelled" else "!" |
153 gcm_print(u"[{0}] {1:25} - {2:25} : {3} [{4}] [{5}]".format(status, ev_start, ev_end, summary, event["iCalUID"], event["id"])) | 153 gcm_print("[{0}] {1:25} - {2:25} : {3} [{4}] [{5}]".format(status, ev_start, ev_end, summary, event["iCalUID"], event["id"])) |
154 | 154 |
155 | 155 |
156 ## Generate gcm IDs for given list of events | 156 ## Generate gcm IDs for given list of events |
157 def gcm_generate_ids(events, calendar_id, sep, field): | 157 def gcm_generate_ids(events, calendar_id, sep, field): |
158 if not events: | 158 if not events: |
192 showDeleted=showDeleted, | 192 showDeleted=showDeleted, |
193 singleEvents=False, | 193 singleEvents=False, |
194 pageToken=ev_token, | 194 pageToken=ev_token, |
195 ).execute() | 195 ).execute() |
196 except Exception as e: | 196 except Exception as e: |
197 gcm_fatal(u"Failed to fetch calendar events for {0}:\n\nERROR: {1}\n".format(calendarId, str(e))) | 197 gcm_fatal("Failed to fetch calendar events for {0}:\n\nERROR: {1}\n".format(calendarId, str(e))) |
198 | 198 |
199 events.extend(result.get("items", [])) | 199 events.extend(result.get("items", [])) |
200 ev_token = result.get("nextPageToken") | 200 ev_token = result.get("nextPageToken") |
201 if not ev_token: | 201 if not ev_token: |
202 break | 202 break |
219 elif len(src) == 7 and src[0] == "#": | 219 elif len(src) == 7 and src[0] == "#": |
220 self.r = int(src[1:3], 16) | 220 self.r = int(src[1:3], 16) |
221 self.g = int(src[3:5], 16) | 221 self.g = int(src[3:5], 16) |
222 self.b = int(src[6:7], 16) | 222 self.b = int(src[6:7], 16) |
223 else: | 223 else: |
224 gcm_fatal(u"Expected hex-triplet string for GCMColor() initializer: {0}".format(src)) | 224 gcm_fatal("Expected hex-triplet string for GCMColor() initializer: {0}".format(src)) |
225 elif isinstance(src, GCMColor): | 225 elif isinstance(src, GCMColor): |
226 self.r = src.r | 226 self.r = src.r |
227 self.g = src.g | 227 self.g = src.g |
228 self.b = src.b | 228 self.b = src.b |
229 else: | 229 else: |
230 gcm_fatal(u"Invalid initializer for GCMColor() object.") | 230 gcm_fatal("Invalid initializer for GCMColor() object.") |
231 | 231 |
232 def to_hexrgb(): | 232 def to_hexrgb(): |
233 return "{0:02X}{1:02X}{2:02X}".format(self.r, self.g, self.b) | 233 return "{0:02X}{1:02X}{2:02X}".format(self.r, self.g, self.b) |
234 | 234 |
235 def to_hexrgb_lc(): | 235 def to_hexrgb_lc(): |
280 | 280 |
281 def __getattr__(self, name): | 281 def __getattr__(self, name): |
282 if name in self.m_data: | 282 if name in self.m_data: |
283 return self.m_data[name] | 283 return self.m_data[name] |
284 else: | 284 else: |
285 gcm_fatal(u"GCMSettings.__getattr__(): No such attribute '"+ name +"'.") | 285 gcm_fatal("GCMSettings.__getattr__(): No such attribute '"+ name +"'.") |
286 | 286 |
287 def mvalidate(self, name, value): | 287 def mvalidate(self, name, value): |
288 if name in self.m_validate and self.m_validate[name]: | 288 if name in self.m_validate and self.m_validate[name]: |
289 if not self.m_validate[name](value): | 289 if not self.m_validate[name](value): |
290 gcm_fatal(u"GCMSettings.mvalidate(): Invalid value for attribute '{0}': {1}".format(name, value)) | 290 gcm_fatal("GCMSettings.mvalidate(): Invalid value for attribute '{0}': {1}".format(name, value)) |
291 | 291 |
292 def mtranslate(self, name, value): | 292 def mtranslate(self, name, value): |
293 if name in self.m_translate and self.m_translate[name]: | 293 if name in self.m_translate and self.m_translate[name]: |
294 return self.m_translate[name](value) | 294 return self.m_translate[name](value) |
295 else: | 295 else: |
305 def mset(self, name, value): | 305 def mset(self, name, value): |
306 self.mvalidate(name, value) | 306 self.mvalidate(name, value) |
307 if name in self.m_data: | 307 if name in self.m_data: |
308 self.m_data[name] = self.mtranslate(name, value) | 308 self.m_data[name] = self.mtranslate(name, value) |
309 else: | 309 else: |
310 gcm_fatal(u"GCMSettings.mset(): No such attribute '"+ name +"'.") | 310 gcm_fatal("GCMSettings.mset(): No such attribute '"+ name +"'.") |
311 | 311 |
312 def mget(self, name): | 312 def mget(self, name): |
313 if name in self.m_data: | 313 if name in self.m_data: |
314 return self.m_data[name] | 314 return self.m_data[name] |
315 else: | 315 else: |
318 def mread(self, cfg_parser, sect): | 318 def mread(self, cfg_parser, sect): |
319 for name in self.m_settable: | 319 for name in self.m_settable: |
320 if cfg_parser.has_option(sect, name): | 320 if cfg_parser.has_option(sect, name): |
321 value = cfg_parser.get(sect, name) | 321 value = cfg_parser.get(sect, name) |
322 self.mset(name, value) | 322 self.mset(name, value) |
323 gcm_debug(4, u"{0} -> '{1}' == {2}".format(name, value, self.mget(name))) | 323 gcm_debug(4, "{0} -> '{1}' == {2}".format(name, value, self.mget(name))) |
324 | 324 |
325 def is_str(self, mvalue): | 325 def is_str(self, mvalue): |
326 return isinstance(mvalue, str) | 326 return isinstance(mvalue, str) |
327 | 327 |
328 def is_string(self, mvalue): | 328 def is_string(self, mvalue): |
339 | 339 |
340 def is_email_state(self, mvalue): | 340 def is_email_state(self, mvalue): |
341 if not self.is_str(mvalue): | 341 if not self.is_str(mvalue): |
342 return False | 342 return False |
343 else: | 343 else: |
344 return mvalue.lower() in [u"off", u"sendmail", u"smtp"] | 344 return mvalue.lower() in [u"off", "sendmail", "smtp"] |
345 | 345 |
346 def trans_email_state(self, mvalue): | 346 def trans_email_state(self, mvalue): |
347 return mvalue.lower() | 347 return mvalue.lower() |
348 | 348 |
349 def is_filename(self, mvalue): | 349 def is_filename(self, mvalue): |
363 return mvalue | 363 return mvalue |
364 | 364 |
365 def is_bool(self, mvalue): | 365 def is_bool(self, mvalue): |
366 mval = self.trans_bool(mvalue) | 366 mval = self.trans_bool(mvalue) |
367 if not isinstance(mval, bool): | 367 if not isinstance(mval, bool): |
368 gcm_fatal(u"GCMSettings.is_bool(): Invalid boolean value '{0}', should be true|false|1|0|on|off|yes|no.".format(mvalue)) | 368 gcm_fatal("GCMSettings.is_bool(): Invalid boolean value '{0}', should be true|false|1|0|on|off|yes|no.".format(mvalue)) |
369 else: | 369 else: |
370 return True | 370 return True |
371 | 371 |
372 def trans_list(self, mvalue): | 372 def trans_list(self, mvalue): |
373 morig = mvalue | 373 morig = mvalue |
374 if self.is_str(mvalue): | 374 if self.is_str(mvalue): |
375 mvalue = re.split("\s*,\s*", mvalue, flags=re.IGNORECASE) | 375 mvalue = re.split("\s*,\s*", mvalue, flags=re.IGNORECASE) |
376 if not isinstance(mvalue, list): | 376 if not isinstance(mvalue, list): |
377 gcm_fatal(u"GCMSettings.trans_list(): Could not parse list '{0}'.".format(mvalue)) | 377 gcm_fatal("GCMSettings.trans_list(): Could not parse list '{0}'.".format(mvalue)) |
378 elif not isinstance(mvalue, list): | 378 elif not isinstance(mvalue, list): |
379 gcm_fatal(u"GCMSettings.trans_list(): Invalid value '{0}'.".format(mvalue)) | 379 gcm_fatal("GCMSettings.trans_list(): Invalid value '{0}'.".format(mvalue)) |
380 return mvalue | 380 return mvalue |
381 | 381 |
382 def is_list(self, mvalue): | 382 def is_list(self, mvalue): |
383 return self.trans_list(mvalue) | 383 return self.trans_list(mvalue) |
384 | 384 |
397 def is_email_list(self, mvalue): | 397 def is_email_list(self, mvalue): |
398 mvalue = self.trans_email_list(mvalue) | 398 mvalue = self.trans_email_list(mvalue) |
399 if mvalue != None: | 399 if mvalue != None: |
400 for email in mvalue: | 400 for email in mvalue: |
401 if not self.is_email(email): | 401 if not self.is_email(email): |
402 gcm_fatal(u"Invalid e-mail address '{0}' in list {1}.".format(email, ", ".join(mvalue))) | 402 gcm_fatal("Invalid e-mail address '{0}' in list {1}.".format(email, ", ".join(mvalue))) |
403 return True | 403 return True |
404 | 404 |
405 | 405 |
406 ### | 406 ### |
407 ### Main program starts | 407 ### Main program starts |
417 cfg = GCMSettings() | 417 cfg = GCMSettings() |
418 | 418 |
419 cfg.mdef("debug", True, cfg.is_bool, cfg.trans_bool, False) | 419 cfg.mdef("debug", True, cfg.is_bool, cfg.trans_bool, False) |
420 | 420 |
421 cfg.mdef("email_ok", False, None, None, False) | 421 cfg.mdef("email_ok", False, None, None, False) |
422 cfg.mdef("email", True, cfg.is_email_state, cfg.trans_email_state, u"off") | 422 cfg.mdef("email", True, cfg.is_email_state, cfg.trans_email_state, "off") |
423 | 423 |
424 cfg.mdef("email_to", True, cfg.is_email_list, cfg.trans_email_list, None) | 424 cfg.mdef("email_to", True, cfg.is_email_list, cfg.trans_email_list, None) |
425 cfg.mdef("email_sender", True, cfg.is_email, None, None) | 425 cfg.mdef("email_sender", True, cfg.is_email, None, None) |
426 cfg.mdef("email_subject", True, cfg.is_string, None, u"Google Calendar MultiMerge status") | 426 cfg.mdef("email_subject", True, cfg.is_string, None, "Google Calendar MultiMerge status") |
427 | 427 |
428 cfg.mdef("email_sendmail", True, cfg.is_string, None, "/usr/sbin/sendmail") | 428 cfg.mdef("email_sendmail", True, cfg.is_string, None, "/usr/sbin/sendmail") |
429 cfg.mdef("email_smtp_tls", True, cfg.is_bool, cfg.trans_bool, False) | 429 cfg.mdef("email_smtp_tls", True, cfg.is_bool, cfg.trans_bool, False) |
430 cfg.mdef("email_smtp_server", True, cfg.is_string, None, None) | 430 cfg.mdef("email_smtp_server", True, cfg.is_string, None, None) |
431 cfg.mdef("email_smtp_user", True, cfg.is_string, None, None) | 431 cfg.mdef("email_smtp_user", True, cfg.is_string, None, None) |
432 cfg.mdef("email_smtp_password", True, cfg.is_string, None, None) | 432 cfg.mdef("email_smtp_password", True, cfg.is_string, None, None) |
433 | 433 |
434 cfg.mdef("src_regex", True, cfg.is_string, None, u"^R:\s*(.*?)\s*\(\s*(.+?)\s*\)\s*$") | 434 cfg.mdef("src_regex", True, cfg.is_string, None, "^R:\s*(.*?)\s*\(\s*(.+?)\s*\)\s*$") |
435 cfg.mdef("src_regmap", False, cfg.is_list, cfg.trans_list, [1, 2]) | 435 cfg.mdef("src_regmap", False, cfg.is_list, cfg.trans_list, [1, 2]) |
436 cfg.mdef("src_regmap_len", False, None, None, len(cfg.src_regmap)) | 436 cfg.mdef("src_regmap_len", False, None, None, len(cfg.src_regmap)) |
437 | 437 |
438 cfg.mdef("dst_name", True, cfg.is_string, None, None) | 438 cfg.mdef("dst_name", True, cfg.is_string, None, None) |
439 cfg.mdef("dst_regex", True, cfg.is_string, None, None) | 439 cfg.mdef("dst_regex", True, cfg.is_string, None, None) |
452 cfg.mdef("credential_file", True, cfg.is_filename, None, "client_credentials.json") | 452 cfg.mdef("credential_file", True, cfg.is_filename, None, "client_credentials.json") |
453 | 453 |
454 | 454 |
455 ## Check if we have arguments | 455 ## Check if we have arguments |
456 if len(sys.argv) <= 1: | 456 if len(sys.argv) <= 1: |
457 gcm_fatal(u"No configuration file specified.\nUsage: {0} <configfile>".format(sys.argv[0])) | 457 gcm_fatal("No configuration file specified.\nUsage: {0} <configfile>".format(sys.argv[0])) |
458 | 458 |
459 | 459 |
460 ## Read, parse and validate configuration file | 460 ## Read, parse and validate configuration file |
461 gcm_debug(3, u"Reading configuration from '{0}'.".format(sys.argv[1])) | 461 gcm_debug(3, "Reading configuration from '{0}'.".format(sys.argv[1])) |
462 try: | 462 try: |
463 cfg_parser = ConfigParser.RawConfigParser() | 463 cfg_parser = ConfigParser.RawConfigParser() |
464 cfg_parser.readfp(codecs.open(sys.argv[1], "r", "UTF-8")) | 464 cfg_parser.readfp(codecs.open(sys.argv[1], "r", "UTF-8")) |
465 except Exception as e: | 465 except Exception as e: |
466 gcm_fatal(u"Failed to read configuration file '{0}': {1}".format(sys.argv[1], str(e))) | 466 gcm_fatal("Failed to read configuration file '{0}': {1}".format(sys.argv[1], str(e))) |
467 | 467 |
468 # Check that the required section exists | 468 # Check that the required section exists |
469 if not cfg_parser.has_section(cfg_section): | 469 if not cfg_parser.has_section(cfg_section): |
470 gcm_fatal(u"Invalid configuration, missing '{0}' section.".format(cfg_section)) | 470 gcm_fatal("Invalid configuration, missing '{0}' section.".format(cfg_section)) |
471 | 471 |
472 # Debug setting is a special case, we need to get it | 472 # Debug setting is a special case, we need to get it |
473 # set before everything else, so do it here .. | 473 # set before everything else, so do it here .. |
474 if cfg_parser.has_option(cfg_section, "debug"): | 474 if cfg_parser.has_option(cfg_section, "debug"): |
475 cfg.mset("debug", cfg_parser.get(cfg_section, "debug")) | 475 cfg.mset("debug", cfg_parser.get(cfg_section, "debug")) |
479 | 479 |
480 | 480 |
481 ## Validate settings | 481 ## Validate settings |
482 if cfg.email != "off": | 482 if cfg.email != "off": |
483 if cfg.email_subject == None or len(cfg.email_subject) == 0: | 483 if cfg.email_subject == None or len(cfg.email_subject) == 0: |
484 gcm_fatal(u"E-mail enabled but email_subject not set.") | 484 gcm_fatal("E-mail enabled but email_subject not set.") |
485 elif cfg.email_sender == None: | 485 elif cfg.email_sender == None: |
486 gcm_fatal(u"E-mail enabled but email_sender not set.") | 486 gcm_fatal("E-mail enabled but email_sender not set.") |
487 elif cfg.email_to == None: | 487 elif cfg.email_to == None: |
488 gcm_fatal(u"E-mail enabled but email_to not set.") | 488 gcm_fatal("E-mail enabled but email_to not set.") |
489 else: | 489 else: |
490 cfg.mset("email_ok", True) | 490 cfg.mset("email_ok", True) |
491 | 491 |
492 | 492 |
493 if len(cfg.src_regmap) != cfg.src_regmap_len: | 493 if len(cfg.src_regmap) != cfg.src_regmap_len: |
494 gcm_fatal(u"Setting src_regmap list must be {0} items.".format(cfg.src_regmap_len)) | 494 gcm_fatal("Setting src_regmap list must be {0} items.".format(cfg.src_regmap_len)) |
495 else: | 495 else: |
496 # Force convert values to integers | 496 # Force convert values to integers |
497 try: | 497 try: |
498 cfg.src_regmap = [int(x) for x in cfg.src_regmap] | 498 cfg.src_regmap = [int(x) for x in cfg.src_regmap] |
499 except Exception as e: | 499 except Exception as e: |
500 gcm_fatal(u"Invalid src_regmap: {0}".format(str(e))) | 500 gcm_fatal("Invalid src_regmap: {0}".format(str(e))) |
501 | 501 |
502 | 502 |
503 if not cfg.dst_regex and not cfg.dst_id: | 503 if not cfg.dst_regex and not cfg.dst_id: |
504 gcm_fatal(u"Target calendar ID or name required, but not set.") | 504 gcm_fatal("Target calendar ID or name required, but not set.") |
505 | 505 |
506 | 506 |
507 ## Initialize and authorize API connection | 507 ## Initialize and authorize API connection |
508 credentials = gcm_get_credentials(cfg, cfg.credential_file, cfg.secret_file) | 508 credentials = gcm_get_credentials(cfg, cfg.credential_file, cfg.secret_file) |
509 http = credentials.authorize(httplib2.Http()) | 509 http = credentials.authorize(httplib2.Http()) |
510 service = discovery.build("calendar", "v3", http=http) | 510 service = discovery.build("calendar", "v3", http=http) |
511 | 511 |
512 | 512 |
513 ## Fetch complete calendar list | 513 ## Fetch complete calendar list |
514 gcm_debug(3, u"Fetching available calendars ..") | 514 gcm_debug(3, "Fetching available calendars ..") |
515 calendars = [] | 515 calendars = [] |
516 cal_token = None | 516 cal_token = None |
517 while True: | 517 while True: |
518 # We want everything except deleted and hidden calendars | 518 # We want everything except deleted and hidden calendars |
519 try: | 519 try: |
521 showHidden=False, | 521 showHidden=False, |
522 showDeleted=False, | 522 showDeleted=False, |
523 pageToken=cal_token | 523 pageToken=cal_token |
524 ).execute() | 524 ).execute() |
525 except Exception as e: | 525 except Exception as e: |
526 gcm_fatal(u"Failed to fetch calendar list:\n\nERROR: {0}\n".format(str(e))) | 526 gcm_fatal("Failed to fetch calendar list:\n\nERROR: {0}\n".format(str(e))) |
527 | 527 |
528 calendars.extend(result.get("items", [])) | 528 calendars.extend(result.get("items", [])) |
529 cal_token = result.get("nextPageToken") | 529 cal_token = result.get("nextPageToken") |
530 if not cal_token: | 530 if not cal_token: |
531 break | 531 break |
532 | 532 |
533 if len(calendars) == 0: | 533 if len(calendars) == 0: |
534 gcm_fatal(u"No calendars found?") | 534 gcm_fatal("No calendars found?") |
535 | 535 |
536 gcm_debug(3, u"{0} calendars total found.".format(len(calendars))) | 536 gcm_debug(3, "{0} calendars total found.".format(len(calendars))) |
537 | 537 |
538 | 538 |
539 ## Filter desired SOURCE calendars based on specified regexp | 539 ## Filter desired SOURCE calendars based on specified regexp |
540 src_re = re.compile(cfg.src_regex, re.UNICODE) | 540 src_re = re.compile(cfg.src_regex, re.UNICODE) |
541 dst_re = re.compile(cfg.dst_regex, re.UNICODE) | 541 dst_re = re.compile(cfg.dst_regex, re.UNICODE) |
559 if mre: | 559 if mre: |
560 calendar["gcm_title"] = mre.group(cfg.src_regmap[0]) | 560 calendar["gcm_title"] = mre.group(cfg.src_regmap[0]) |
561 calendar["gcm_id"] = mre.group(cfg.src_regmap[1]) | 561 calendar["gcm_id"] = mre.group(cfg.src_regmap[1]) |
562 src_calendars.append(calendar) | 562 src_calendars.append(calendar) |
563 | 563 |
564 gcm_debug(3, u"{0} source calendars found.".format(len(src_calendars))) | 564 gcm_debug(3, "{0} source calendars found.".format(len(src_calendars))) |
565 | 565 |
566 | 566 |
567 ## Check if we have destination calendar ID | 567 ## Check if we have destination calendar ID |
568 if not dst_calendar: | 568 if not dst_calendar: |
569 gcm_fatal(u"Could not find target/destination calendar ID for '"+ cfg.dst_name +"'.") | 569 gcm_fatal("Could not find target/destination calendar ID for '"+ cfg.dst_name +"'.") |
570 else: | 570 else: |
571 gcm_debug(3, u"Target calendar '{0}' [ ID: {1} ]".format(dst_calendar["summary"], dst_calendar["id"])) | 571 gcm_debug(3, "Target calendar '{0}' [ ID: {1} ]".format(dst_calendar["summary"], dst_calendar["id"])) |
572 | 572 |
573 | 573 |
574 ## Fetch calendar colors data | 574 ## Fetch calendar colors data |
575 try: | 575 try: |
576 colors = service.colors().get().execute() | 576 colors = service.colors().get().execute() |
577 except Exception as e: | 577 except Exception as e: |
578 gcm_fatal(u"Failed to fetch calendar color settings:\n\n{0}".format(str(e))) | 578 gcm_fatal("Failed to fetch calendar color settings:\n\n{0}".format(str(e))) |
579 | 579 |
580 | 580 |
581 ## Now, fetch and collect events from source calendars | 581 ## Now, fetch and collect events from source calendars |
582 gcm_debug(3, u"Fetching calendar events .. ") | 582 gcm_debug(3, "Fetching calendar events .. ") |
583 src_events = [] | 583 src_events = [] |
584 for calendar in src_calendars: | 584 for calendar in src_calendars: |
585 gcm_debug(4, u"- {0} ({1})".format(calendar["id"], calendar["summary"])) | 585 gcm_debug(4, "- {0} ({1})".format(calendar["id"], calendar["summary"])) |
586 | 586 |
587 # Find matching color from the source calendar for the event, if one has been set | 587 # Find matching color from the source calendar for the event, if one has been set |
588 c_found = None | 588 c_found = None |
589 if "colorId" in calendar and calendar["colorId"] in colors["calendar"]: | 589 if "colorId" in calendar and calendar["colorId"] in colors["calendar"]: |
590 gcm_debug(4, u" Calendar color: {0}".format(colors["calendar"][calendar["colorId"]])) | 590 gcm_debug(4, " Calendar color: {0}".format(colors["calendar"][calendar["colorId"]])) |
591 c_found = gcm_find_nearest_color(colors["event"], colors["calendar"][calendar["colorId"]], 100) | 591 c_found = gcm_find_nearest_color(colors["event"], colors["calendar"][calendar["colorId"]], 100) |
592 if c_found: | 592 if c_found: |
593 gcm_debug(4, u" Found nearest event color ID: {0}, {1}".format(c_found, colors["event"][c_found])) | 593 gcm_debug(4, " Found nearest event color ID: {0}, {1}".format(c_found, colors["event"][c_found])) |
594 else: | 594 else: |
595 gcm_debug(4, u" No matching event color found!") | 595 gcm_debug(4, " No matching event color found!") |
596 | 596 |
597 # Fetch and add events, if any, to main source events list | 597 # Fetch and add events, if any, to main source events list |
598 events = gcm_generate_ids(gcm_fetch_events(calendar["id"], False), calendar["id"], "___", "id") | 598 events = gcm_generate_ids(gcm_fetch_events(calendar["id"], False), calendar["id"], "___", "id") |
599 if events: | 599 if events: |
600 for event in events: | 600 for event in events: |
612 if gcm_check_debug(4): | 612 if gcm_check_debug(4): |
613 gcm_dump_events(events, (lambda ev: ev["status"] != u"cancelled")) | 613 gcm_dump_events(events, (lambda ev: ev["status"] != u"cancelled")) |
614 | 614 |
615 | 615 |
616 ## Fetch current events from the target | 616 ## Fetch current events from the target |
617 gcm_debug(3, u"Fetching current target calendar events.") | 617 gcm_debug(3, "Fetching current target calendar events.") |
618 dst_events = gcm_generate_ids(gcm_fetch_events(cfg.dst_id, True), "", "", "iCalUID") | 618 dst_events = gcm_generate_ids(gcm_fetch_events(cfg.dst_id, True), "", "", "iCalUID") |
619 gcm_debug(3, u"Found {0} event(s).".format(len(dst_events))) | 619 gcm_debug(3, "Found {0} event(s).".format(len(dst_events))) |
620 | 620 |
621 | 621 |
622 ## Start populating/updating events .. | 622 ## Start populating/updating events .. |
623 gcm_debug(3, u"Re-merging events to target calendar ..") | 623 gcm_debug(3, "Re-merging events to target calendar ..") |
624 dst_ids = frozenset([x["gcm_id"] for x in dst_events]) | 624 dst_ids = frozenset([x["gcm_id"] for x in dst_events]) |
625 src_ids = frozenset([x["gcm_id"] for x in src_events]) | 625 src_ids = frozenset([x["gcm_id"] for x in src_events]) |
626 | 626 |
627 evn_new = evn_updated = evn_unchanged = 0 | 627 evn_new = evn_updated = evn_unchanged = 0 |
628 | 628 |
629 for event in src_events: | 629 for event in src_events: |
630 # Does the event exist already in the target? | 630 # Does the event exist already in the target? |
631 if event["gcm_id"] in dst_ids: | 631 if event["gcm_id"] in dst_ids: |
632 # Check if event NEEDS updating .. aka compare data | 632 # Check if event NEEDS updating .. aka compare data |
633 gcm_debug(4, u"Event {0} [{1}] exists, checking ..".format(event["id"], event["gcm_id"])) | 633 gcm_debug(4, "Event {0} [{1}] exists, checking ..".format(event["id"], event["gcm_id"])) |
634 d_event = gcm_get_event_by_gcm_id(dst_events, event["gcm_id"]) | 634 d_event = gcm_get_event_by_gcm_id(dst_events, event["gcm_id"]) |
635 if not gcm_compare_events(event, d_event): | 635 if not gcm_compare_events(event, d_event): |
636 # Seems we need to update | 636 # Seems we need to update |
637 gcm_debug(4, u"Updating event {0} [{1}]".format(event["id"], event["gcm_id"])) | 637 gcm_debug(4, "Updating event {0} [{1}]".format(event["id"], event["gcm_id"])) |
638 try: | 638 try: |
639 # We need to remove the sequence and id fields, as they will be replaced by target | 639 # We need to remove the sequence and id fields, as they will be replaced by target |
640 event.pop("sequence", None) | 640 event.pop("sequence", None) |
641 event.pop("id", None) | 641 event.pop("id", None) |
642 event["iCalUID"] = event["gcm_id"] | 642 event["iCalUID"] = event["gcm_id"] |
643 new_event = service.events().update(calendarId=cfg.dst_id, eventId=d_event["id"], body=event).execute() | 643 new_event = service.events().update(calendarId=cfg.dst_id, eventId=d_event["id"], body=event).execute() |
644 evn_updated += 1 | 644 evn_updated += 1 |
645 except Exception as e: | 645 except Exception as e: |
646 gcm_fatal(u"Failed to update event {0} [{1}]:\n\n{2}\n\nERROR: {3}\n".format(event["id"], event["gcm_id"], event, str(e))) | 646 gcm_fatal("Failed to update event {0} [{1}]:\n\n{2}\n\nERROR: {3}\n".format(event["id"], event["gcm_id"], event, str(e))) |
647 else: | 647 else: |
648 evn_unchanged += 1 | 648 evn_unchanged += 1 |
649 gcm_debug(4, u"No need to update event {0} [{1}]".format(event["id"], event["gcm_id"])) | 649 gcm_debug(4, "No need to update event {0} [{1}]".format(event["id"], event["gcm_id"])) |
650 elif event["status"] not in [u"cancelled", u"confirmed"]: | 650 elif event["status"] not in [u"cancelled", "confirmed"]: |
651 # Event does not seem to exist. Insert new event. | 651 # Event does not seem to exist. Insert new event. |
652 gcm_debug(4, u"Inserting new event {0} [{1}]".format(event["id"], event["gcm_id"])) | 652 gcm_debug(4, "Inserting new event {0} [{1}]".format(event["id"], event["gcm_id"])) |
653 # Remove original id field, otherwise it will clash | 653 # Remove original id field, otherwise it will clash |
654 event.pop("id", None) | 654 event.pop("id", None) |
655 event["iCalUID"] = event["gcm_id"] # Replace Google generated ID with our own | 655 event["iCalUID"] = event["gcm_id"] # Replace Google generated ID with our own |
656 try: | 656 try: |
657 new_event = service.events().insert(calendarId=cfg.dst_id, body=event).execute() | 657 new_event = service.events().insert(calendarId=cfg.dst_id, body=event).execute() |
658 evn_new += 1 | 658 evn_new += 1 |
659 except Exception as e: | 659 except Exception as e: |
660 gcm_fatal(u"Failed to insert new event:\n\n{0}\n\nERROR: {1}\n".format(event, str(e))) | 660 gcm_fatal("Failed to insert new event:\n\n{0}\n\nERROR: {1}\n".format(event, str(e))) |
661 | 661 |
662 gcm_debug(3, "{0} new events, {1} updated, {2} unchanged.".format(evn_new, evn_updated, evn_unchanged)) | 662 gcm_debug(3, "{0} new events, {1} updated, {2} unchanged.".format(evn_new, evn_updated, evn_unchanged)) |
663 | 663 |
664 | 664 |
665 ## Remove "stale" events | 665 ## Remove "stale" events |
666 gcm_debug(3, u"Purging stale events ..") | 666 gcm_debug(3, "Purging stale events ..") |
667 evn_purged = 0 | 667 evn_purged = 0 |
668 for event in dst_events: | 668 for event in dst_events: |
669 gcm_debug(4, u"Checking event {0}".format(event["gcm_id"])) | 669 gcm_debug(4, "Checking event {0}".format(event["gcm_id"])) |
670 if not event["gcm_id"] in src_ids and event["status"] != u"cancelled": | 670 if not event["gcm_id"] in src_ids and event["status"] != u"cancelled": |
671 gcm_debug(4, u"Deleting event {0} [{1}]".format(event["id"], event["gcm_id"])) | 671 gcm_debug(4, "Deleting event {0} [{1}]".format(event["id"], event["gcm_id"])) |
672 evn_purged += 1 | 672 evn_purged += 1 |
673 try: | 673 try: |
674 service.events().delete(calendarId=cfg.dst_id, eventId=event["id"]).execute() | 674 service.events().delete(calendarId=cfg.dst_id, eventId=event["id"]).execute() |
675 except Exception as e: | 675 except Exception as e: |
676 gcm_fatal(u"Failed to delete stale event:\n{0}\n\nERROR: {1}\n".format(event, str(e))) | 676 gcm_fatal("Failed to delete stale event:\n{0}\n\nERROR: {1}\n".format(event, str(e))) |
677 | 677 |
678 gcm_debug(3, "{0} events purged.".format(evn_purged)) | 678 gcm_debug(3, "{0} events purged.".format(evn_purged)) |
679 | 679 |
680 | 680 |
681 ## | 681 ## |
682 ## Finally, update the calendar name with timestamp | 682 ## Finally, update the calendar name with timestamp |
683 ## | 683 ## |
684 t_time = time.localtime() | 684 t_time = time.localtime() |
685 t_str = time.strftime("%d.%m.%Y %H:%M", t_time) | 685 t_str = time.strftime("%d.%m.%Y %H:%M", t_time) |
686 gcm_debug(3, u"Updating target calendar name timestamp {0}".format(t_str)) | 686 gcm_debug(3, "Updating target calendar name timestamp {0}".format(t_str)) |
687 | 687 |
688 try: | 688 try: |
689 dst_calendar["summary"] = cfg.dst_name.format(t_str) | 689 dst_calendar["summary"] = cfg.dst_name.format(t_str) |
690 new_calendar = service.calendars().update(calendarId=cfg.dst_id, body=dst_calendar).execute() | 690 new_calendar = service.calendars().update(calendarId=cfg.dst_id, body=dst_calendar).execute() |
691 except Exception as e: | 691 except Exception as e: |
692 gcm_fatal(u"Failed to update target calendar:\n{0}\n\nERROR: {1}\n".format(dst_calendar, str(e))) | 692 gcm_fatal("Failed to update target calendar:\n{0}\n\nERROR: {1}\n".format(dst_calendar, str(e))) |
693 | 693 |
694 | 694 |
695 gcm_bench_end = time.time() | 695 gcm_bench_end = time.time() |
696 gcm_bench_elapsed = gcm_bench_end - gcm_bench_start | 696 gcm_bench_elapsed = gcm_bench_end - gcm_bench_start |
697 | 697 |
698 gcm_debug(3, u"Finished. {0} seconds elapsed.".format(gcm_bench_elapsed)) | 698 gcm_debug(3, "Finished. {0} seconds elapsed.".format(gcm_bench_elapsed)) |