comparison multimerge.py @ 120:1f7967aa0133

Improve and add comments.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 18 Oct 2016 15:05:55 +0300
parents f671602635b7
children 4500fbf91294
comparison
equal deleted inserted replaced
119:f671602635b7 120:1f7967aa0133
31 from oauth2client import file 31 from oauth2client import file
32 from googleapiclient import discovery 32 from googleapiclient import discovery
33 33
34 34
35 ### 35 ###
36 ### Misc. helper functions 36 ### Misc. helper functions, etc
37 ### 37 ###
38
39 ## List of event tuple fields that should NOT be compared for equality
38 gcm_no_compare_fields = [ 40 gcm_no_compare_fields = [
39 "id", "iCalUID", "etag", "sequence", "gcm_cal_id", 41 "id", "iCalUID", "etag", "sequence", "gcm_cal_id",
40 "created", "updated", "htmlLink", "organizer", "creator", 42 "created", "updated", "htmlLink", "organizer", "creator",
41 ] 43 ]
42 44
45 ## List of logging levels from lowest to highest
43 gcm_log_levels = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"] 46 gcm_log_levels = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]
44 47
45 48
46 def gcm_get_log_level(): 49 def gcm_get_log_level():
47 return gcm_log_levels.index(cfg.logging_level) 50 return gcm_log_levels.index(cfg.logging_level)
56 print(smsg.encode(sys.stdout.encoding)) 59 print(smsg.encode(sys.stdout.encoding))
57 else: 60 else:
58 print(smsg.encode("UTF-8")) 61 print(smsg.encode("UTF-8"))
59 62
60 63
61 ## Fatal errors 64 ## Fatal error handler
62 def gcm_fatal(smsg): 65 def gcm_fatal(smsg):
63 gcm_print(u"ERROR: "+ smsg) 66 gcm_print(u"ERROR: "+ smsg)
64 if cfg.email_ok and cfg.email != "off": 67 if cfg.email_ok and cfg.email != "off":
65 ## If e-mail is set, send e-mail 68 ## If e-mail is not "off", send e-mail
66 msg = MIMEText(("\n".join(gcm_msgbuf)).encode("UTF-8"), "plain") 69 msg = MIMEText(("\n".join(gcm_msgbuf)).encode("UTF-8"), "plain")
67 msg.set_charset("UTF-8") 70 msg.set_charset("UTF-8")
68 msg["Subject"] = cfg.email_subject 71 msg["Subject"] = cfg.email_subject
69 msg["From"] = cfg.email_sender 72 msg["From"] = cfg.email_sender
70 msg["To"] = ",".join(cfg.email_to) 73 msg["To"] = ",".join(cfg.email_to)
71 gcm_print("Sending mail to {0} from {1}, subj: {2} ..".format(";".join(cfg.email_to), cfg.email_sender, cfg.email_subject)) 74 gcm_print("Sending mail to {0} from {1}, subj: {2} ..".format(";".join(cfg.email_to), cfg.email_sender, cfg.email_subject))
72 try: 75 try:
76 # Act based on email mode
73 if cfg.email == "smtp": 77 if cfg.email == "smtp":
74 gcm_print("Using SMTP server {0}, login {1}".format(cfg.email_smtp_server, cfg.email_smtp_user)) 78 gcm_print("Using SMTP server {0}, login {1}".format(cfg.email_smtp_server, cfg.email_smtp_user))
75 server = smtplib.SMTP(cfg.email_smtp_server) 79 server = smtplib.SMTP(cfg.email_smtp_server)
76 if gcm_check_debug(4): 80 if gcm_check_debug(4):
77 server.set_debuglevel(10) 81 server.set_debuglevel(10)
127 if not credentials or credentials.invalid: 131 if not credentials or credentials.invalid:
128 gcm_fatal(u"Failed to authenticate / invalid credentials.") 132 gcm_fatal(u"Failed to authenticate / invalid credentials.")
129 return credentials 133 return credentials
130 134
131 135
136 ## Dump/print a given list of events for debugging purposes
132 def gcm_dump_events(events, show): 137 def gcm_dump_events(events, show):
133 for event in events: 138 for event in events:
134 if show == None or show(event): 139 if show == None or show(event):
135 ev_start = event["start"].get("dateTime", event["start"].get("date")) if "start" in event else "?" 140 ev_start = event["start"].get("dateTime", event["start"].get("date")) if "start" in event else "?"
136 ev_end = event["end"].get("dateTime", event["end"].get("date")) if "end" in event else "?" 141 ev_end = event["end"].get("dateTime", event["end"].get("date")) if "end" in event else "?"
157 if event["gcm_id"] == id: 162 if event["gcm_id"] == id:
158 return event 163 return event
159 return None 164 return None
160 165
161 166
162 167 ## Compare two given events for equality (except for excluded list of fields)
163 def gcm_compare_events(ev1, ev2): 168 def gcm_compare_events(ev1, ev2):
164 for field in ev1: 169 for field in ev1:
165 if not field in gcm_no_compare_fields and ev1[field] != ev2[field]: 170 if not field in gcm_no_compare_fields and ev1[field] != ev2[field]:
166 return False 171 return False
167 return True 172 return True
396 signal.signal(signal.SIGINT, gcm_signal_handler) 401 signal.signal(signal.SIGINT, gcm_signal_handler)
397 402
398 gcm_bench_start = time.time() 403 gcm_bench_start = time.time()
399 404
400 405
401 ## Settings 406 ## Define all the settings
402 cfg_section = "gcm" 407 cfg_section = "gcm"
403 cfg = GCMSettings() 408 cfg = GCMSettings()
404 409
405 cfg.mdef("debug", True, cfg.is_bool, cfg.trans_bool, False) 410 cfg.mdef("debug", True, cfg.is_bool, cfg.trans_bool, False)
406 411
436 #cfg.mdef("scope", False, None, None, "https://www.googleapis.com/auth/calendar.readonly") 441 #cfg.mdef("scope", False, None, None, "https://www.googleapis.com/auth/calendar.readonly")
437 cfg.mdef("secret_file", True, cfg.is_filename, None, "client_secret.json") 442 cfg.mdef("secret_file", True, cfg.is_filename, None, "client_secret.json")
438 cfg.mdef("credential_file", True, cfg.is_filename, None, "client_credentials.json") 443 cfg.mdef("credential_file", True, cfg.is_filename, None, "client_credentials.json")
439 444
440 445
441 ## Check arguments 446 ## Check if we have arguments
442 if len(sys.argv) <= 1: 447 if len(sys.argv) <= 1:
443 gcm_fatal(u"No configuration file specified.\nUsage: {0} <configfile>".format(sys.argv[0])) 448 gcm_fatal(u"No configuration file specified.\nUsage: {0} <configfile>".format(sys.argv[0]))
444 449
445 450
446 ## Read, parse and validate configuration file 451 ## Read, parse and validate configuration file
555 gcm_fatal(u"Could not find target/destination calendar ID for '"+ cfg.dst_name +"'.") 560 gcm_fatal(u"Could not find target/destination calendar ID for '"+ cfg.dst_name +"'.")
556 else: 561 else:
557 gcm_debug(3, u"Target calendar '{0}' [ ID: {1} ]".format(dst_calendar["summary"], dst_calendar["id"])) 562 gcm_debug(3, u"Target calendar '{0}' [ ID: {1} ]".format(dst_calendar["summary"], dst_calendar["id"]))
558 563
559 564
560 ## Fetch colors 565 ## Fetch calendar colors data
561 try: 566 try:
562 colors = service.colors().get().execute() 567 colors = service.colors().get().execute()
563 except Exception as e: 568 except Exception as e:
564 gcm_fatal(u"Failed to fetch calendar color settings:\n\n{0}".format(str(e))) 569 gcm_fatal(u"Failed to fetch calendar color settings:\n\n{0}".format(str(e)))
565 570
566 571
567 ## Now, we fetch and collect events 572 ## Now, fetch and collect events from source calendars
568 gcm_debug(3, u"Fetching calendar events .. ") 573 gcm_debug(3, u"Fetching calendar events .. ")
569 src_events = [] 574 src_events = []
570 for calendar in src_calendars: 575 for calendar in src_calendars:
571 gcm_debug(4, u"- {0} ({1})".format(calendar["id"], calendar["summary"])) 576 gcm_debug(4, u"- {0} ({1})".format(calendar["id"], calendar["summary"]))
577
578 # Find matching color from the source calendar for the event, if one has been set
572 c_found = None 579 c_found = None
573 if "colorId" in calendar and calendar["colorId"] in colors["calendar"]: 580 if "colorId" in calendar and calendar["colorId"] in colors["calendar"]:
574 gcm_debug(4, u" Calendar color: {0}".format(colors["calendar"][calendar["colorId"]])) 581 gcm_debug(4, u" Calendar color: {0}".format(colors["calendar"][calendar["colorId"]]))
575 c_found = gcm_find_nearest_color(colors["event"], colors["calendar"][calendar["colorId"]], 100) 582 c_found = gcm_find_nearest_color(colors["event"], colors["calendar"][calendar["colorId"]], 100)
576 if c_found: 583 if c_found:
577 gcm_debug(4, u" Found nearest event color ID: {0}, {1}".format(c_found, colors["event"][c_found])) 584 gcm_debug(4, u" Found nearest event color ID: {0}, {1}".format(c_found, colors["event"][c_found]))
578 else: 585 else:
579 gcm_debug(4, u" No matching event color found!") 586 gcm_debug(4, u" No matching event color found!")
580 587
581 # Add events, if any, to main list 588 # Fetch and add events, if any, to main source events list
582 events = gcm_generate_ids(gcm_fetch_events(calendar["id"], False), calendar["id"], "___", "id") 589 events = gcm_generate_ids(gcm_fetch_events(calendar["id"], False), calendar["id"], "___", "id")
583 if events: 590 if events:
584 for event in events: 591 for event in events:
592 # Set summary and color for existing events
585 if event["status"] != u"cancelled": 593 if event["status"] != u"cancelled":
586 if c_found != None: 594 if c_found != None:
587 event["colorId"] = c_found 595 event["colorId"] = c_found
588 event["summary"] = u"[{1}] {0}".format(event["summary"], calendar["gcm_id"]) 596 event["summary"] = u"[{1}] {0}".format(event["summary"], calendar["gcm_id"])
597
598 # Add to list of source events
589 src_events.extend(events) 599 src_events.extend(events)
590 if gcm_check_debug(4): 600 if gcm_check_debug(4):
591 gcm_dump_events(events, (lambda ev: ev["status"] != u"cancelled")) 601 gcm_dump_events(events, (lambda ev: ev["status"] != u"cancelled"))
592 602
593 603
594 ## Get current events 604 ## Fetch current events from the target
595 gcm_debug(3, u"Fetching current target calendar events.") 605 gcm_debug(3, u"Fetching current target calendar events.")
596 dst_events = gcm_generate_ids(gcm_fetch_events(cfg.dst_id, True), "", "", "iCalUID") 606 dst_events = gcm_generate_ids(gcm_fetch_events(cfg.dst_id, True), "", "", "iCalUID")
597 gcm_debug(3, u"Found {0} event(s).".format(len(dst_events))) 607 gcm_debug(3, u"Found {0} event(s).".format(len(dst_events)))
598 608
599 609
600 ## Start merging events .. 610 ## Start populating/updating events ..
601 gcm_debug(3, u"Re-merging events to target calendar ..") 611 gcm_debug(3, u"Re-merging events to target calendar ..")
602 dst_ids = frozenset(map(lambda x: x["gcm_id"], dst_events)) 612 dst_ids = frozenset(map(lambda x: x["gcm_id"], dst_events))
603 src_ids = frozenset(map(lambda x: x["gcm_id"], src_events)) 613 src_ids = frozenset(map(lambda x: x["gcm_id"], src_events))
604 614
605 evn_new = evn_updated = evn_unchanged = 0 615 evn_new = evn_updated = evn_unchanged = 0
623 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))) 633 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)))
624 else: 634 else:
625 evn_unchanged += 1 635 evn_unchanged += 1
626 gcm_debug(4, u"No need to update event {0} [{1}]".format(event["id"], event["gcm_id"])) 636 gcm_debug(4, u"No need to update event {0} [{1}]".format(event["id"], event["gcm_id"]))
627 elif event["status"] != u"cancelled": 637 elif event["status"] != u"cancelled":
628 ## Event does not seem to exist. Insert new event. 638 # Event does not seem to exist. Insert new event.
629 gcm_debug(4, u"Inserting new event {0} [{1}]".format(event["id"], event["gcm_id"])) 639 gcm_debug(4, u"Inserting new event {0} [{1}]".format(event["id"], event["gcm_id"]))
630 event.pop("id", None) 640 event.pop("id", None)
631 event["iCalUID"] = event["gcm_id"] # Replace Google generated ID with our own 641 event["iCalUID"] = event["gcm_id"] # Replace Google generated ID with our own
632 try: 642 try:
633 new_event = service.events().insert(calendarId=cfg.dst_id, body=event).execute() 643 new_event = service.events().insert(calendarId=cfg.dst_id, body=event).execute()