Mercurial > hg > gcmultimerge
annotate multimerge.py @ 76:a63cc3633adb
Put log levels array into its own global variable.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Wed, 13 Jul 2016 11:50:25 +0300 |
parents | ee05430f1a4d |
children | e5e7b6e9bd44 |
rev | line source |
---|---|
1 | 1 #!/usr/bin/python |
3 | 2 # coding=utf-8 |
2 | 3 ### |
36 | 4 ### Google Calendar MultiMerge |
2 | 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 | |
45
035be8a9e982
Force reading of configuration in Unicode UTF-8.
Matti Hamalainen <ccr@tnsp.org>
parents:
44
diff
changeset
|
14 import codecs |
62
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
15 import math |
73
b3f8621f1a25
Change how the target calendar settings work a bit. Also implement display
Matti Hamalainen <ccr@tnsp.org>
parents:
72
diff
changeset
|
16 import time |
29
50711871fd1e
Comment out currently unnecessary imports.
Matti Hamalainen <ccr@tnsp.org>
parents:
28
diff
changeset
|
17 #import datetime |
14
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
18 |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
19 import smtplib |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
20 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
|
21 |
1 | 22 import httplib2 |
23 import ConfigParser | |
20 | 24 |
1 | 25 import oauth2client |
26 from oauth2client import client | |
27 from oauth2client import tools | |
4 | 28 from oauth2client import file |
1 | 29 from googleapiclient import discovery |
30 | |
31 | |
32 ### | |
33 ### Misc. helper functions | |
34 ### | |
66
b08d159e35c7
Sanitize gcm_no_compare_fields[]
Matti Hamalainen <ccr@tnsp.org>
parents:
65
diff
changeset
|
35 gcm_no_compare_fields = [ |
b08d159e35c7
Sanitize gcm_no_compare_fields[]
Matti Hamalainen <ccr@tnsp.org>
parents:
65
diff
changeset
|
36 "id", "iCalUID", "etag", "sequence", "gcm_cal_id", |
72
1ab40033bb87
Add 'creator' to event equality comparision excluded fields.
Matti Hamalainen <ccr@tnsp.org>
parents:
71
diff
changeset
|
37 "created", "updated", "htmlLink", "organizer", "creator", |
66
b08d159e35c7
Sanitize gcm_no_compare_fields[]
Matti Hamalainen <ccr@tnsp.org>
parents:
65
diff
changeset
|
38 ] |
b08d159e35c7
Sanitize gcm_no_compare_fields[]
Matti Hamalainen <ccr@tnsp.org>
parents:
65
diff
changeset
|
39 |
76
a63cc3633adb
Put log levels array into its own global variable.
Matti Hamalainen <ccr@tnsp.org>
parents:
75
diff
changeset
|
40 gcm_log_levels = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"] |
a63cc3633adb
Put log levels array into its own global variable.
Matti Hamalainen <ccr@tnsp.org>
parents:
75
diff
changeset
|
41 |
1 | 42 |
43 ## Wrapper for print() that does not break when redirecting stdin/out | |
44 ## because of piped output not having a defined encoding. We default | |
45 ## to UTF-8 encoding in output here. | |
46 def gcm_print(smsg): | |
47 | 47 gcm_msgbuf.append(smsg) |
1 | 48 if sys.stdout.encoding != None: |
49 print(smsg.encode(sys.stdout.encoding)) | |
50 else: | |
51 print(smsg.encode("UTF-8")) | |
52 | |
53 | |
54 ## Fatal errors | |
55 def gcm_fatal(smsg): | |
56 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
|
57 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
|
58 ## 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
|
59 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
|
60 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
|
61 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
|
62 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
|
63 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
|
64 try: |
71
41881ba09ba8
Allow setting SMTP server .. needs more work tho.
Matti Hamalainen <ccr@tnsp.org>
parents:
70
diff
changeset
|
65 smtpH = smtplib.SMTP(cfg.email_server) |
14
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
66 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
|
67 smtpH.quit() |
70
d5e3ca4b609d
Improve error handling when e-mail sending fails.
Matti Hamalainen <ccr@tnsp.org>
parents:
68
diff
changeset
|
68 except Exception as e: |
d5e3ca4b609d
Improve error handling when e-mail sending fails.
Matti Hamalainen <ccr@tnsp.org>
parents:
68
diff
changeset
|
69 gcm_print(u"FATAL: Oh crap, e-mail sending failed: {0}".format(str(e))) |
1 | 70 sys.exit(1) |
71 | |
72 | |
73 ## Debug messages | |
74 def gcm_debug(smsg): | |
75 if cfg.debug: | |
76 gcm_print(u"DBG: "+ smsg) | |
77 else: | |
47 | 78 gcm_msgbuf.append(u"DBG: {0}".format(smsg)) |
1 | 79 |
80 | |
24 | 81 ## Handler for SIGINT signals |
1 | 82 def gcm_signal_handler(signal, frame): |
54 | 83 gcm_print(u"\nQuitting due to SIGINT / Ctrl+C!") |
1 | 84 sys.exit(0) |
85 | |
86 | |
24 | 87 ## Function for handling Google API credentials |
1 | 88 def gcm_get_credentials(mcfg): |
42 | 89 try: |
90 store = oauth2client.file.Storage(mcfg.credential_file) | |
91 except Exception as e: | |
65 | 92 gcm_fatal(u"Failed to read credential file:\n{0}\n\nERROR: {1}\n".format(mcfg.credential_file, str(e))) |
42 | 93 |
1 | 94 credentials = store.get() |
95 if not credentials or credentials.invalid: | |
42 | 96 try: |
97 flow = client.flow_from_clientsecrets(mcfg.secret_file, mcfg.scope) | |
98 except Exception as e: | |
65 | 99 gcm_fatal(u"Failed to fetch client secret:\n{0}\n\nERROR: {1}\n".format(mcfg.secret_file, str(e))) |
42 | 100 |
1 | 101 flow.user_agent = mcfg.app_name |
102 credentials = tools.run_flow(flow, store, mcfg) | |
103 if not credentials or credentials.invalid: | |
65 | 104 gcm_fatal(u"Failed to authenticate / invalid credentials.") |
1 | 105 return credentials |
106 | |
107 | |
108 def gcm_dump_events(events): | |
109 for event in events: | |
110 ev_start = event["start"].get("dateTime", event["start"].get("date")) | |
111 ev_end = event["end"].get("dateTime", event["end"].get("date")) | |
112 gcm_print(u"{0:25} - {1:25} : {2}".format(ev_start, ev_end, event["summary"])) | |
113 | |
56 | 114 |
115 ## Generate gcm IDs for given list of events | |
63 | 116 def gcm_generate_ids(events, calendar_id, sep): |
26
1267d61f6224
Add function for generating unique internal IDs.
Matti Hamalainen <ccr@tnsp.org>
parents:
25
diff
changeset
|
117 if not events: |
1267d61f6224
Add function for generating unique internal IDs.
Matti Hamalainen <ccr@tnsp.org>
parents:
25
diff
changeset
|
118 return events |
1267d61f6224
Add function for generating unique internal IDs.
Matti Hamalainen <ccr@tnsp.org>
parents:
25
diff
changeset
|
119 |
1267d61f6224
Add function for generating unique internal IDs.
Matti Hamalainen <ccr@tnsp.org>
parents:
25
diff
changeset
|
120 for ev in events: |
28 | 121 ev["gcm_cal_id"] = calendar_id |
63 | 122 ev["gcm_id"] = calendar_id + sep + ev["iCalUID"] |
26
1267d61f6224
Add function for generating unique internal IDs.
Matti Hamalainen <ccr@tnsp.org>
parents:
25
diff
changeset
|
123 |
1267d61f6224
Add function for generating unique internal IDs.
Matti Hamalainen <ccr@tnsp.org>
parents:
25
diff
changeset
|
124 return events |
1267d61f6224
Add function for generating unique internal IDs.
Matti Hamalainen <ccr@tnsp.org>
parents:
25
diff
changeset
|
125 |
1 | 126 |
53
ea62e0ed05ae
Initial implementation of event comparision amd generally more clever about
Matti Hamalainen <ccr@tnsp.org>
parents:
52
diff
changeset
|
127 ## Find event by its gcm_id from given list or return None if not found |
ea62e0ed05ae
Initial implementation of event comparision amd generally more clever about
Matti Hamalainen <ccr@tnsp.org>
parents:
52
diff
changeset
|
128 def gcm_get_event_by_gcm_id(list, id): |
ea62e0ed05ae
Initial implementation of event comparision amd generally more clever about
Matti Hamalainen <ccr@tnsp.org>
parents:
52
diff
changeset
|
129 for event in list: |
ea62e0ed05ae
Initial implementation of event comparision amd generally more clever about
Matti Hamalainen <ccr@tnsp.org>
parents:
52
diff
changeset
|
130 if event["gcm_id"] == id: |
ea62e0ed05ae
Initial implementation of event comparision amd generally more clever about
Matti Hamalainen <ccr@tnsp.org>
parents:
52
diff
changeset
|
131 return event |
ea62e0ed05ae
Initial implementation of event comparision amd generally more clever about
Matti Hamalainen <ccr@tnsp.org>
parents:
52
diff
changeset
|
132 return None |
ea62e0ed05ae
Initial implementation of event comparision amd generally more clever about
Matti Hamalainen <ccr@tnsp.org>
parents:
52
diff
changeset
|
133 |
ea62e0ed05ae
Initial implementation of event comparision amd generally more clever about
Matti Hamalainen <ccr@tnsp.org>
parents:
52
diff
changeset
|
134 |
ea62e0ed05ae
Initial implementation of event comparision amd generally more clever about
Matti Hamalainen <ccr@tnsp.org>
parents:
52
diff
changeset
|
135 |
ea62e0ed05ae
Initial implementation of event comparision amd generally more clever about
Matti Hamalainen <ccr@tnsp.org>
parents:
52
diff
changeset
|
136 def gcm_compare_events(ev1, ev2): |
ea62e0ed05ae
Initial implementation of event comparision amd generally more clever about
Matti Hamalainen <ccr@tnsp.org>
parents:
52
diff
changeset
|
137 for field in ev1: |
ea62e0ed05ae
Initial implementation of event comparision amd generally more clever about
Matti Hamalainen <ccr@tnsp.org>
parents:
52
diff
changeset
|
138 if not field in gcm_no_compare_fields and ev1[field] != ev2[field]: |
ea62e0ed05ae
Initial implementation of event comparision amd generally more clever about
Matti Hamalainen <ccr@tnsp.org>
parents:
52
diff
changeset
|
139 return False |
ea62e0ed05ae
Initial implementation of event comparision amd generally more clever about
Matti Hamalainen <ccr@tnsp.org>
parents:
52
diff
changeset
|
140 return True |
ea62e0ed05ae
Initial implementation of event comparision amd generally more clever about
Matti Hamalainen <ccr@tnsp.org>
parents:
52
diff
changeset
|
141 |
63 | 142 |
67 | 143 ### |
144 ### Class for parsing and manipulating RGB colors | |
145 ### | |
62
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
146 class GCMColor(): |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
147 def __init__(self, src = None): |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
148 if src == None: |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
149 self.r = self.g = self.b = 0 |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
150 elif isinstance(src, basestring): |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
151 if len(src) == 6: |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
152 self.r = int(src[0:2], 16) |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
153 self.g = int(src[2:4], 16) |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
154 self.b = int(src[4:6], 16) |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
155 elif len(src) == 7 and src[0] == "#": |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
156 self.r = int(src[1:3], 16) |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
157 self.g = int(src[3:5], 16) |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
158 self.b = int(src[6:7], 16) |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
159 else: |
65 | 160 gcm_fatal(u"Expected hex-triplet string for GCMColor() initializer: {0}".format(src)) |
62
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
161 elif isinstance(src, GCMColor): |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
162 self.r = src.r |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
163 self.g = src.g |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
164 self.b = src.b |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
165 else: |
65 | 166 gcm_fatal(u"Invalid initializer for GCMColor() object.") |
62
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
167 |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
168 def delta(self, other): |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
169 ctmp = GCMColor() |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
170 ctmp.r = other.r - self.r |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
171 ctmp.g = other.g - self.g |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
172 ctmp.b = other.b - self.b |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
173 return ctmp |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
174 |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
175 def dist(self, other): |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
176 ctmp = self.delta(other) |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
177 return math.sqrt(ctmp.r * ctmp.r + ctmp.g * ctmp.g + ctmp.b * ctmp.b) |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
178 |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
179 |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
180 def gcm_find_nearest_color(colors, cfind, maxdist): |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
181 c_fg = GCMColor(cfind["foreground"]) |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
182 c_bg = GCMColor(cfind["background"]) |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
183 |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
184 bdist_fg = 99999999999 |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
185 bdist_bg = 99999999999 |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
186 best_fit = None |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
187 for id, col in colors.iteritems(): |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
188 dist_fg = GCMColor(col["foreground"]).dist(c_fg) |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
189 dist_bg = GCMColor(col["background"]).dist(c_bg) |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
190 if dist_fg <= bdist_fg and dist_bg <= bdist_bg: |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
191 best_fit = id |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
192 bdist_fg = dist_fg |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
193 bdist_bg = dist_bg |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
194 |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
195 if bdist_fg <= maxdist and bdist_bg <= maxdist: |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
196 return best_fit |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
197 else: |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
198 return None |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
199 |
53
ea62e0ed05ae
Initial implementation of event comparision amd generally more clever about
Matti Hamalainen <ccr@tnsp.org>
parents:
52
diff
changeset
|
200 |
24 | 201 ## |
202 ## Class for handling configuration / settings | |
203 ## | |
1 | 204 class GCMSettings(dict): |
205 def __init__(self): | |
206 self.m_data = {} | |
207 self.m_saveable = {} | |
5
9d4152f32223
Add some code for settings validation.
Matti Hamalainen <ccr@tnsp.org>
parents:
4
diff
changeset
|
208 self.m_validate = {} |
1 | 209 self.m_translate = {} |
210 | |
211 def __getattr__(self, name): | |
212 if name in self.m_data: | |
213 return self.m_data[name] | |
214 else: | |
65 | 215 gcm_fatal(u"GCMSettings.__getattr__(): No such attribute '"+ name +"'.") |
1 | 216 |
5
9d4152f32223
Add some code for settings validation.
Matti Hamalainen <ccr@tnsp.org>
parents:
4
diff
changeset
|
217 def mvalidate(self, name, value): |
9d4152f32223
Add some code for settings validation.
Matti Hamalainen <ccr@tnsp.org>
parents:
4
diff
changeset
|
218 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
|
219 if not self.m_validate[name](value): |
65 | 220 gcm_fatal(u"GCMSettings.mvalidate(): Invalid value for attribute '{0}': {1}".format(name, value)) |
5
9d4152f32223
Add some code for settings validation.
Matti Hamalainen <ccr@tnsp.org>
parents:
4
diff
changeset
|
221 |
1 | 222 def mtranslate(self, name, value): |
223 if name in self.m_translate and self.m_translate[name]: | |
224 return self.m_translate[name](value) | |
225 else: | |
226 return value | |
227 | |
228 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
|
229 self.mvalidate(name, value) |
1 | 230 self.m_saveable[name] = saveable |
5
9d4152f32223
Add some code for settings validation.
Matti Hamalainen <ccr@tnsp.org>
parents:
4
diff
changeset
|
231 self.m_validate[name] = validate |
9d4152f32223
Add some code for settings validation.
Matti Hamalainen <ccr@tnsp.org>
parents:
4
diff
changeset
|
232 self.m_translate[name] = translate |
1 | 233 self.m_data[name] = self.mtranslate(name, value) |
234 | |
235 def mset(self, name, value): | |
5
9d4152f32223
Add some code for settings validation.
Matti Hamalainen <ccr@tnsp.org>
parents:
4
diff
changeset
|
236 self.mvalidate(name, value) |
1 | 237 if name in self.m_data: |
238 self.m_data[name] = self.mtranslate(name, value) | |
239 else: | |
43 | 240 gcm_fatal(u"GCMSettings.mset(): No such attribute '"+ name +"'.") |
1 | 241 |
242 def mget(self, name): | |
243 if name in self.m_data: | |
244 return self.m_data[name] | |
245 else: | |
246 return None | |
247 | |
6
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
248 def mread(self, cfgparser, sect): |
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
249 for name in self.m_saveable: |
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
250 if cfgparser.has_option(sect, name): |
46
51c87abe5a1e
This Unicode conversion here is probably redundant.
Matti Hamalainen <ccr@tnsp.org>
parents:
45
diff
changeset
|
251 value = cfgparser.get(sect, name) |
6
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
252 self.mset(name, value) |
43 | 253 gcm_debug(u"{0} -> '{1}' == {2}".format(name, value, self.mget(name))) |
6
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
254 |
23 | 255 def is_str(self, mvalue): |
256 return isinstance(mvalue, basestring) | |
257 | |
258 def is_string(self, mvalue): | |
259 return mvalue == None or self.is_str(mvalue) | |
260 | |
261 def is_log_level(self, mvalue): | |
262 if not self.is_str(mvalue): | |
263 return False | |
264 else: | |
76
a63cc3633adb
Put log levels array into its own global variable.
Matti Hamalainen <ccr@tnsp.org>
parents:
75
diff
changeset
|
265 return mvalue.upper() in gcm_log_levels |
23 | 266 |
267 def trans_log_level(self, mvalue): | |
268 return mvalue.upper() | |
269 | |
270 def is_filename(self, mvalue): | |
271 if not self.is_str(mvalue): | |
272 return False | |
273 else: | |
274 return re.match("^[a-z0-9][a-z0-9\.\_\-]+$", mvalue, flags=re.IGNORECASE) | |
275 | |
276 def trans_bool(self, mvalue): | |
277 if self.is_str(mvalue): | |
278 if re.match("^\s*(true|1|on|yes)\s*$", mvalue, re.IGNORECASE): | |
279 mvalue = True | |
280 elif re.match("^\s*(false|0|off|no)\s*$", mvalue, re.IGNORECASE): | |
281 mvalue = False | |
282 else: | |
283 return None | |
284 return mvalue | |
285 | |
286 def is_bool(self, mvalue): | |
287 mval = self.trans_bool(mvalue) | |
288 if not isinstance(mval, bool): | |
65 | 289 gcm_fatal(u"GCMSettings.is_bool(): Invalid boolean value '{0}', should be true|false|1|0|on|off|yes|no.".format(mvalue)) |
23 | 290 else: |
291 return True | |
292 | |
293 def trans_list(self, mvalue): | |
294 morig = mvalue | |
295 if self.is_str(mvalue): | |
296 mvalue = re.split("\s*,\s*", mvalue, flags=re.IGNORECASE) | |
297 if not isinstance(mvalue, list): | |
65 | 298 gcm_fatal(u"GCMSettings.trans_list(): Could not parse list '{0}'.".format(mvalue)) |
23 | 299 elif not isinstance(mvalue, list): |
65 | 300 gcm_fatal(u"GCMSettings.trans_list(): Invalid value '{0}'.".format(mvalue)) |
23 | 301 return mvalue |
302 | |
303 def is_list(self, mvalue): | |
304 return self.trans_list(mvalue) | |
305 | |
306 def is_email(self, mvalue): | |
307 if not self.is_string(mvalue): | |
308 return False | |
309 else: | |
310 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) | |
311 | |
312 def trans_email_list(self, mvalue): | |
313 if mvalue == None: | |
314 return mvalue | |
315 else: | |
316 return self.trans_list(mvalue.strip()) | |
317 | |
318 def is_email_list(self, mvalue): | |
319 mvalue = self.trans_email_list(mvalue) | |
320 if mvalue != None: | |
321 for email in mvalue: | |
322 if not self.is_email(email): | |
65 | 323 gcm_fatal(u"Invalid e-mail address '{0}' in list {1}.".format(email, ", ".join(mvalue))) |
23 | 324 return True |
325 | |
1 | 326 |
327 ### | |
328 ### Main program starts | |
329 ### | |
330 gcm_msgbuf = [] | |
331 signal.signal(signal.SIGINT, gcm_signal_handler) | |
332 | |
333 | |
334 ## Settings | |
335 cfg = GCMSettings() | |
336 | |
23 | 337 cfg.mdef("debug", True, cfg.is_bool, cfg.trans_bool, False) |
1 | 338 |
14
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
339 cfg.mdef("email_ok", False, None, None, False) |
23 | 340 cfg.mdef("email", True, cfg.is_bool, cfg.trans_bool, False) |
341 cfg.mdef("email_to", True, cfg.is_email_list, cfg.trans_email_list, None) | |
342 cfg.mdef("email_sender", True, cfg.is_email, None, None) | |
49 | 343 cfg.mdef("email_subject", True, cfg.is_string, None, u"Google Calendar MultiMerge status") |
71
41881ba09ba8
Allow setting SMTP server .. needs more work tho.
Matti Hamalainen <ccr@tnsp.org>
parents:
70
diff
changeset
|
344 cfg.mdef("email_server", True, cfg.is_string, None, None) |
14
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
345 |
49 | 346 cfg.mdef("source_regex", True, cfg.is_string, None, u"^R:\s*(.*?)\s*\(\s*(.+?)\s*\)\s*$") |
23 | 347 cfg.mdef("source_regmap", False, cfg.is_list, cfg.trans_list, [1, 2]) |
1 | 348 cfg.mdef("source_regmap_len", False, None, None, len(cfg.source_regmap)) |
349 | |
73
b3f8621f1a25
Change how the target calendar settings work a bit. Also implement display
Matti Hamalainen <ccr@tnsp.org>
parents:
72
diff
changeset
|
350 cfg.mdef("dest_name", True, cfg.is_string, None, u"Raahen kansainvälisyystoiminta [{0}]") |
b3f8621f1a25
Change how the target calendar settings work a bit. Also implement display
Matti Hamalainen <ccr@tnsp.org>
parents:
72
diff
changeset
|
351 cfg.mdef("dest_regex", True, cfg.is_string, None, u"^Raahen kansainvälisyystoiminta") |
23 | 352 cfg.mdef("dest_id", True, cfg.is_string, None, None) |
1 | 353 |
354 cfg.mdef("noauth_local_webserver", False, None, None, True) | |
355 #cfg.mdef("auth_host_name", False, None, None, "localhost") | |
356 #cfg.mdef("auth_host_port", False, None, None, [8080, 8090]) | |
23 | 357 cfg.mdef("logging_level", True, cfg.is_log_level, cfg.trans_log_level, "ERROR") |
1 | 358 |
359 # No need to touch these | |
360 cfg.mdef("app_name", False, None, None, "Google Calendar MultiMerge") | |
361 cfg.mdef("scope", False, None, None, "https://www.googleapis.com/auth/calendar") | |
362 #cfg.mdef("scope", False, None, None, "https://www.googleapis.com/auth/calendar.readonly") | |
23 | 363 cfg.mdef("secret_file", True, cfg.is_filename, None, "client_secret.json") |
364 cfg.mdef("credential_file", True, cfg.is_filename, None, "client_credentials.json") | |
1 | 365 |
6
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
366 |
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
367 ## Read, parse and validate configuration file |
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
368 if len(sys.argv) > 1: |
54 | 369 gcm_debug(u"Reading configuration from '{0}'.".format(sys.argv[1])) |
6
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
370 try: |
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
371 cfgparser = ConfigParser.RawConfigParser() |
45
035be8a9e982
Force reading of configuration in Unicode UTF-8.
Matti Hamalainen <ccr@tnsp.org>
parents:
44
diff
changeset
|
372 cfgparser.readfp(codecs.open(sys.argv[1], "r", "UTF-8")) |
6
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
373 except Exception as e: |
65 | 374 gcm_fatal(u"Failed to read configuration file '{0}': {1}".format(sys.argv[1], str(e))) |
6
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
375 |
7
f2ecfb3e04ee
Check that the required section exists in configuration.
Matti Hamalainen <ccr@tnsp.org>
parents:
6
diff
changeset
|
376 # Check that the required section exists |
f2ecfb3e04ee
Check that the required section exists in configuration.
Matti Hamalainen <ccr@tnsp.org>
parents:
6
diff
changeset
|
377 section = "gcm" |
f2ecfb3e04ee
Check that the required section exists in configuration.
Matti Hamalainen <ccr@tnsp.org>
parents:
6
diff
changeset
|
378 if not cfgparser.has_section(section): |
65 | 379 gcm_fatal(u"Invalid configuration, missing '{0}' section.".format(section)) |
7
f2ecfb3e04ee
Check that the required section exists in configuration.
Matti Hamalainen <ccr@tnsp.org>
parents:
6
diff
changeset
|
380 |
10
b237b96602ad
We need to handle "debug" setting before other settings, so we need a
Matti Hamalainen <ccr@tnsp.org>
parents:
9
diff
changeset
|
381 # 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
|
382 # 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
|
383 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
|
384 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
|
385 |
6
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
386 # Parse the settings and validate |
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
387 cfg.mread(cfgparser, section) |
ee6bf617f839
Implement configuration file reading.
Matti Hamalainen <ccr@tnsp.org>
parents:
5
diff
changeset
|
388 |
8 | 389 |
390 ## Validate settings | |
14
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
391 if cfg.email: |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
392 if cfg.email_subject == None or len(cfg.email_subject) == 0: |
65 | 393 gcm_fatal(u"E-mail enabled but email_subject not set.") |
14
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
394 elif cfg.email_sender == None: |
65 | 395 gcm_fatal(u"E-mail enabled but email_sender not set.") |
14
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
396 elif cfg.email_to == None: |
65 | 397 gcm_fatal(u"E-mail enabled but email_to not set.") |
14
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
398 else: |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
399 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
|
400 |
8262efacf3fb
Initial implementation of sending e-mail in fatal error cases.
Matti Hamalainen <ccr@tnsp.org>
parents:
13
diff
changeset
|
401 |
9
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
402 if len(cfg.source_regmap) != cfg.source_regmap_len: |
65 | 403 gcm_fatal(u"Setting source_regmap list must be {0} items.".format(cfg.source_regmap_len)) |
9
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
404 else: |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
405 # Force to integers |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
406 try: |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
407 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
|
408 except Exception as e: |
65 | 409 gcm_fatal(u"Invalid source_regmap: {0}".format(str(e))) |
9
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
410 |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
411 |
73
b3f8621f1a25
Change how the target calendar settings work a bit. Also implement display
Matti Hamalainen <ccr@tnsp.org>
parents:
72
diff
changeset
|
412 if not cfg.dest_regex and not cfg.dest_id: |
65 | 413 gcm_fatal(u"Target calendar ID or name required, but not set.") |
8 | 414 |
415 | |
416 | |
1 | 417 ## Initialize and authorize API connection |
418 credentials = gcm_get_credentials(cfg) | |
419 http = credentials.authorize(httplib2.Http()) | |
420 service = discovery.build("calendar", "v3", http=http) | |
421 | |
422 | |
423 ## Fetch complete calendar list | |
54 | 424 gcm_debug(u"Fetching available calendars ..") |
1 | 425 calendars = [] |
19 | 426 cal_token = None |
1 | 427 while True: |
428 # We want everything except deleted and hidden calendars | |
17 | 429 result = service.calendarList().list( |
1 | 430 showHidden=False, |
431 showDeleted=False, | |
19 | 432 pageToken=cal_token |
1 | 433 ).execute() |
434 | |
17 | 435 calendars.extend(result.get("items", [])) |
19 | 436 cal_token = result.get("nextPageToken") |
437 if not cal_token: | |
1 | 438 break |
439 | |
440 if len(calendars) == 0: | |
65 | 441 gcm_fatal(u"No calendars found?") |
1 | 442 |
50
f8618bae162a
Improve debug messages and comments.
Matti Hamalainen <ccr@tnsp.org>
parents:
49
diff
changeset
|
443 gcm_debug(u"{0} calendars total found.".format(len(calendars))) |
f8618bae162a
Improve debug messages and comments.
Matti Hamalainen <ccr@tnsp.org>
parents:
49
diff
changeset
|
444 |
1 | 445 |
9
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
446 ## Filter desired SOURCE calendars based on specified regexp |
49 | 447 src_re = re.compile(cfg.source_regex, re.UNICODE) |
73
b3f8621f1a25
Change how the target calendar settings work a bit. Also implement display
Matti Hamalainen <ccr@tnsp.org>
parents:
72
diff
changeset
|
448 dst_re = re.compile(cfg.dest_regex, re.UNICODE) |
9
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
449 src_calendars = [] |
60
37705c4a35e1
Show target calendar information in debug mode.
Matti Hamalainen <ccr@tnsp.org>
parents:
59
diff
changeset
|
450 dst_calendar = None |
9
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
451 for calendar in calendars: |
51
54644b29a9a3
Match also summaryOverride attribute against the source calendar regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
50
diff
changeset
|
452 if u"summary" in calendar: |
50
f8618bae162a
Improve debug messages and comments.
Matti Hamalainen <ccr@tnsp.org>
parents:
49
diff
changeset
|
453 # Find destination calendar ID if not set |
73
b3f8621f1a25
Change how the target calendar settings work a bit. Also implement display
Matti Hamalainen <ccr@tnsp.org>
parents:
72
diff
changeset
|
454 if not cfg.dest_id and dst_re.match(calendar["summary"]): |
9
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
455 cfg.mset("dest_id", calendar["id"]) |
60
37705c4a35e1
Show target calendar information in debug mode.
Matti Hamalainen <ccr@tnsp.org>
parents:
59
diff
changeset
|
456 dst_calendar = calendar |
9
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
457 |
51
54644b29a9a3
Match also summaryOverride attribute against the source calendar regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
50
diff
changeset
|
458 # If summary or summaryOverride match the regexp, add calendar |
9
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
459 mre = src_re.match(calendar["summary"]) |
52 | 460 if not mre and u"summaryOverride" in calendar: |
461 mre = src_re.match(calendar[u"summaryOverride"]) | |
462 | |
9
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
463 if mre: |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
464 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
|
465 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
|
466 src_calendars.append(calendar) |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
467 |
50
f8618bae162a
Improve debug messages and comments.
Matti Hamalainen <ccr@tnsp.org>
parents:
49
diff
changeset
|
468 gcm_debug(u"{0} source calendars found.".format(len(src_calendars))) |
9
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
469 |
50
f8618bae162a
Improve debug messages and comments.
Matti Hamalainen <ccr@tnsp.org>
parents:
49
diff
changeset
|
470 |
f8618bae162a
Improve debug messages and comments.
Matti Hamalainen <ccr@tnsp.org>
parents:
49
diff
changeset
|
471 ## Check if we have destination calendar ID |
60
37705c4a35e1
Show target calendar information in debug mode.
Matti Hamalainen <ccr@tnsp.org>
parents:
59
diff
changeset
|
472 if not dst_calendar: |
9
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
473 gcm_fatal(u"Could not find target/destination calendar ID for '"+ cfg.dest_name +"'.") |
60
37705c4a35e1
Show target calendar information in debug mode.
Matti Hamalainen <ccr@tnsp.org>
parents:
59
diff
changeset
|
474 else: |
37705c4a35e1
Show target calendar information in debug mode.
Matti Hamalainen <ccr@tnsp.org>
parents:
59
diff
changeset
|
475 gcm_debug(u"Target calendar '{0}' id {1}.".format(dst_calendar["summary"], dst_calendar["id"])) |
9
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
476 |
01c933dba120
Filter source calendars based on regexp.
Matti Hamalainen <ccr@tnsp.org>
parents:
8
diff
changeset
|
477 |
62
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
478 ## Fetch colors |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
479 try: |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
480 colors = service.colors().get().execute() |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
481 except Exception as e: |
65 | 482 gcm_fatal(u"Failed to fetch calendar color settings:\n\n{0}".format(str(e))) |
62
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
483 |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
484 |
11
fcdee7c04ed8
Implement fetching of source events.
Matti Hamalainen <ccr@tnsp.org>
parents:
10
diff
changeset
|
485 ## Now, we fetch and collect events |
fcdee7c04ed8
Implement fetching of source events.
Matti Hamalainen <ccr@tnsp.org>
parents:
10
diff
changeset
|
486 gcm_debug(u"Fetching calendar events .. ") |
19 | 487 src_events = [] |
11
fcdee7c04ed8
Implement fetching of source events.
Matti Hamalainen <ccr@tnsp.org>
parents:
10
diff
changeset
|
488 for calendar in src_calendars: |
54 | 489 gcm_debug(u"- "+calendar["id"]) |
68 | 490 try: |
491 result = service.events().list( | |
492 timeZone="EEST", | |
493 calendarId=calendar["id"], | |
494 singleEvents=True, | |
495 showDeleted=False, | |
496 # orderBy="startTime", | |
497 ).execute() | |
498 except Exception as e: | |
499 gcm_fatal(u"Failed to fetch calendar events for {0}:\n\n{1}\n\nERROR: {2}\n".format(calendar["id"], calendar, str(e))) | |
11
fcdee7c04ed8
Implement fetching of source events.
Matti Hamalainen <ccr@tnsp.org>
parents:
10
diff
changeset
|
500 |
62
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
501 c_found = None |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
502 if "colorId" in calendar and calendar["colorId"] in colors["calendar"]: |
65 | 503 gcm_debug(u"Calendar color: {0}".format(colors["calendar"][calendar["colorId"]])) |
62
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
504 c_found = gcm_find_nearest_color(colors["event"], colors["calendar"][calendar["colorId"]], 100) |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
505 if c_found: |
65 | 506 gcm_debug(u"Found nearest event color ID: {0}, {1}".format(c_found, colors["event"][c_found])) |
62
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
507 else: |
65 | 508 gcm_debug(u"No matching event color found!") |
62
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
509 |
11
fcdee7c04ed8
Implement fetching of source events.
Matti Hamalainen <ccr@tnsp.org>
parents:
10
diff
changeset
|
510 # Add events, if any, to main list |
63 | 511 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
|
512 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
|
513 for event in events: |
62
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
514 if c_found != None: |
4891ed8d77d5
Implement nearest matching color search via cubic distance, and use it for
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
515 event["colorId"] = c_found |
59
a8941896c21c
Swap order of source name and event title in target events.
Matti Hamalainen <ccr@tnsp.org>
parents:
58
diff
changeset
|
516 event["summary"] = u"[{1}] {0}".format(event["summary"], calendar["gcm_id"]) |
21
f392d495c5b6
We need to extend a list, not append to it.
Matti Hamalainen <ccr@tnsp.org>
parents:
20
diff
changeset
|
517 src_events.extend(events) |
11
fcdee7c04ed8
Implement fetching of source events.
Matti Hamalainen <ccr@tnsp.org>
parents:
10
diff
changeset
|
518 if cfg.debug: |
fcdee7c04ed8
Implement fetching of source events.
Matti Hamalainen <ccr@tnsp.org>
parents:
10
diff
changeset
|
519 gcm_dump_events(events) |
fcdee7c04ed8
Implement fetching of source events.
Matti Hamalainen <ccr@tnsp.org>
parents:
10
diff
changeset
|
520 |
fcdee7c04ed8
Implement fetching of source events.
Matti Hamalainen <ccr@tnsp.org>
parents:
10
diff
changeset
|
521 |
13
dd240a7ad913
Fetch current events in destination calendar.
Matti Hamalainen <ccr@tnsp.org>
parents:
11
diff
changeset
|
522 ## Get current events |
dd240a7ad913
Fetch current events in destination calendar.
Matti Hamalainen <ccr@tnsp.org>
parents:
11
diff
changeset
|
523 gcm_debug(u"Fetching current target calendar events {0}".format(cfg.dest_id)) |
17 | 524 result = service.events().list( |
13
dd240a7ad913
Fetch current events in destination calendar.
Matti Hamalainen <ccr@tnsp.org>
parents:
11
diff
changeset
|
525 calendarId=cfg.dest_id, |
dd240a7ad913
Fetch current events in destination calendar.
Matti Hamalainen <ccr@tnsp.org>
parents:
11
diff
changeset
|
526 singleEvents=True, |
32 | 527 showDeleted=True, |
528 ).execute() | |
13
dd240a7ad913
Fetch current events in destination calendar.
Matti Hamalainen <ccr@tnsp.org>
parents:
11
diff
changeset
|
529 |
63 | 530 dst_events = gcm_generate_ids(result.get("items", []), "", "") |
58 | 531 gcm_debug(u"Found {0} event(s).".format(len(dst_events))) |
532 | |
13
dd240a7ad913
Fetch current events in destination calendar.
Matti Hamalainen <ccr@tnsp.org>
parents:
11
diff
changeset
|
533 |
27 | 534 ## Start merging events .. |
54 | 535 gcm_debug(u"Re-merging events to target calendar ..") |
55 | 536 dst_ids = frozenset(map(lambda x: x["gcm_id"], dst_events)) |
537 src_ids = frozenset(map(lambda x: x["gcm_id"], src_events)) | |
27 | 538 |
539 for event in src_events: | |
540 # Does the event exist already in the target? | |
55 | 541 if event["gcm_id"] in dst_ids: |
39
693db3f8cbe5
Begin importing event comparision stuff.
Matti Hamalainen <ccr@tnsp.org>
parents:
38
diff
changeset
|
542 # Check if event NEEDS updating .. aka compare data |
63 | 543 gcm_debug(u"Event {0} : {1} exists, checking ..".format(event["id"], event["gcm_id"])) |
53
ea62e0ed05ae
Initial implementation of event comparision amd generally more clever about
Matti Hamalainen <ccr@tnsp.org>
parents:
52
diff
changeset
|
544 d_event = gcm_get_event_by_gcm_id(dst_events, event["gcm_id"]) |
55 | 545 if not gcm_compare_events(event, d_event): |
57 | 546 # Seems we need to update |
63 | 547 gcm_debug(u"Updating event {0} : {1}..".format(event["id"], event["gcm_id"])) |
39
693db3f8cbe5
Begin importing event comparision stuff.
Matti Hamalainen <ccr@tnsp.org>
parents:
38
diff
changeset
|
548 try: |
55 | 549 event.pop("sequence", None) |
63 | 550 event.pop("id", None) |
551 event["iCalUID"] = event["gcm_id"] | |
552 new_event = service.events().update(calendarId=cfg.dest_id, eventId=d_event["id"], body=event).execute() | |
39
693db3f8cbe5
Begin importing event comparision stuff.
Matti Hamalainen <ccr@tnsp.org>
parents:
38
diff
changeset
|
553 except Exception as e: |
65 | 554 gcm_fatal(u"Failed to update event {0}:\n\n{1}\n\nERROR: {2}\n".format(event["gcm_id"], event, str(e))) |
39
693db3f8cbe5
Begin importing event comparision stuff.
Matti Hamalainen <ccr@tnsp.org>
parents:
38
diff
changeset
|
555 else: |
63 | 556 gcm_debug(u"No need to update event {0} : {1}.".format(event["id"], event["gcm_id"])) |
27 | 557 else: |
34 | 558 ## Event does not seem to exist. Insert new event. |
54 | 559 gcm_debug(u"Inserting new event {0}".format(event["gcm_id"])) |
63 | 560 event.pop("id", None) |
561 event["iCalUID"] = event["gcm_id"] # Replace Google generated ID with our own | |
34 | 562 try: |
563 new_event = service.events().insert(calendarId=cfg.dest_id, body=event).execute() | |
564 except Exception as e: | |
65 | 565 gcm_fatal(u"Failed to insert new event:\n\n{0}\n\nERROR: {1}\n".format(event, str(e))) |
63 | 566 |
34 | 567 |
568 ## Remove "stale" events | |
54 | 569 gcm_debug(u"Purging stale events ..") |
38
54405de302d0
Attempt to delete stale events. Still needs a check for already deleted
Matti Hamalainen <ccr@tnsp.org>
parents:
37
diff
changeset
|
570 for event in dst_events: |
56 | 571 gcm_debug(u"Checking event {0}".format(event["gcm_id"])) |
55 | 572 if not event["gcm_id"] in src_ids and event["status"] != u"cancelled": |
573 gcm_debug(u"Deleting event {0}".format(event["gcm_id"])) | |
63 | 574 try: |
575 service.events().delete(calendarId=cfg.dest_id, eventId=event["id"]).execute() | |
576 except Exception as e: | |
65 | 577 gcm_fatal(u"Failed to delete stale event:\n{0}\n\nERROR: {1}\n".format(event, str(e))) |
38
54405de302d0
Attempt to delete stale events. Still needs a check for already deleted
Matti Hamalainen <ccr@tnsp.org>
parents:
37
diff
changeset
|
578 |
75 | 579 ## |
580 ## Finally, update the calendar name with timestamp | |
581 ## | |
73
b3f8621f1a25
Change how the target calendar settings work a bit. Also implement display
Matti Hamalainen <ccr@tnsp.org>
parents:
72
diff
changeset
|
582 t_time = time.localtime() |
b3f8621f1a25
Change how the target calendar settings work a bit. Also implement display
Matti Hamalainen <ccr@tnsp.org>
parents:
72
diff
changeset
|
583 t_str = time.strftime("%d.%m.%Y %H:%M", t_time) |
b3f8621f1a25
Change how the target calendar settings work a bit. Also implement display
Matti Hamalainen <ccr@tnsp.org>
parents:
72
diff
changeset
|
584 gcm_debug(u"Updating target calendar name timestamp {0}".format(t_str)) |
b3f8621f1a25
Change how the target calendar settings work a bit. Also implement display
Matti Hamalainen <ccr@tnsp.org>
parents:
72
diff
changeset
|
585 |
b3f8621f1a25
Change how the target calendar settings work a bit. Also implement display
Matti Hamalainen <ccr@tnsp.org>
parents:
72
diff
changeset
|
586 try: |
b3f8621f1a25
Change how the target calendar settings work a bit. Also implement display
Matti Hamalainen <ccr@tnsp.org>
parents:
72
diff
changeset
|
587 dst_calendar["summary"] = cfg.dest_name.format(t_str) |
b3f8621f1a25
Change how the target calendar settings work a bit. Also implement display
Matti Hamalainen <ccr@tnsp.org>
parents:
72
diff
changeset
|
588 new_calendar = service.calendars().update(calendarId=cfg.dest_id, body=dst_calendar).execute() |
b3f8621f1a25
Change how the target calendar settings work a bit. Also implement display
Matti Hamalainen <ccr@tnsp.org>
parents:
72
diff
changeset
|
589 except Exception as e: |
b3f8621f1a25
Change how the target calendar settings work a bit. Also implement display
Matti Hamalainen <ccr@tnsp.org>
parents:
72
diff
changeset
|
590 gcm_fatal(u"Failed to update target calendar:\n{0}\n\nERROR: {1}\n".format(dst_calendar, str(e))) |
b3f8621f1a25
Change how the target calendar settings work a bit. Also implement display
Matti Hamalainen <ccr@tnsp.org>
parents:
72
diff
changeset
|
591 |
27 | 592 |
54 | 593 gcm_debug(u"Finished.") |