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))