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