Mercurial > hg > gcmultimerge
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() |