9
9
# Created: 23/11/2008
11
11
from datetime import datetime, date, timedelta
12
from htmlentitydefs import name2codepoint, codepoint2name
13
12
from optparse import OptionParser
13
from plugin_common import getHTMLText, getSpaces, getTypedValue, getWrappedText, removeLineByString
14
14
from xml.dom import minidom
16
18
import gdata.calendar.service
30
30
app_name = "gtk-desktop-info"
31
31
app_path = os.path.dirname(os.path.abspath(__file__))
32
32
module_name = __file__.replace(os.path.dirname (__file__) + "/", "").replace(".pyc","").replace(".py", "")
34
34
class CalendarData:
36
36
def __init__(self, title):
39
39
class CalendarEventData:
41
41
def __init__(self, title, starttime, endtime, location, description, who=None):
43
43
self.starttime = starttime
107
107
self.cal_client.password = self.config.PASSWORD
108
108
self.cal_client.source = app_name
109
109
self.cal_client.ProgrammaticLogin()
111
111
# load the format for date and time to use
112
112
self.getDatetimeFormat(self.config.DATEFORMAT, self.config.TIMEFORMAT)
114
114
# prepare calendar list to use for event retrieval
115
115
self.getCalendarList()
117
117
except Exception,e:
118
118
self.logger.error("GoogleCalendarEngine Initialisation:Unexpected error:" + e.__str__())
120
120
def loadConfigData(self):
123
123
self.config = GoogleCalendarConfig()
125
125
if self.options.config != None:
126
126
# load the config based on options passed in from the main app
127
127
configfilepath = self.options.config
129
129
# load plugin config from home directory of the user
130
130
configfilepath = os.path.join(os.path.expanduser('~'), ".config/"+app_name+"/"+module_name+".config")
132
132
if os.path.exists(configfilepath):
134
134
self.logger.info("Loading config settings from \"%s\""%configfilepath)
136
136
for line in fileinput.input(os.path.expanduser(configfilepath)):
137
137
line = line.strip()
138
138
if len(line) > 0 and line[0:1] != "#": # ignore commented lines or empty ones
140
140
name = line.split("=")[0].strip().upper() # config setting name on the left of =
141
141
value = line.split("=")[1].split("#")[0].strip() # config value on the right of = (minus any trailing comments)
143
143
if len(value) > 0:
144
144
if name == "HEADERTEMPLATE":
145
self.config.TEMPLATE = self.getTypedValue(value, "string")
145
self.config.TEMPLATE = getTypedValue(value, "string")
146
146
elif name == "TEMPLATE":
147
self.config.TEMPLATE = self.getTypedValue(value, "string")
147
self.config.TEMPLATE = getTypedValue(value, "string")
148
148
elif name == "USERNAME":
149
self.config.USERNAME = self.getTypedValue(value, "string")
149
self.config.USERNAME = getTypedValue(value, "string")
150
150
elif name == "PASSWORD":
151
self.config.PASSWORD = self.getTypedValue(value, "string")
151
self.config.PASSWORD = getTypedValue(value, "string")
152
152
elif name == "REQUESTCALENDARS":
153
self.config.REQUESTCALENDARS = self.getTypedValue(value, "string")
153
self.config.REQUESTCALENDARS = getTypedValue(value, "string")
154
154
elif name == "DAYSAHEAD":
155
self.config.DAYSAHEAD = self.getTypedValue(value, "integer")
155
self.config.DAYSAHEAD = getTypedValue(value, "integer")
156
156
elif name == "STARTDATE":
157
self.config.STARTDATE = self.getTypedValue(value, "string")
157
self.config.STARTDATE = getTypedValue(value, "string")
158
158
elif name == "ENDDATE":
159
self.config.ENDDATE = self.getTypedValue(value, "string")
159
self.config.ENDDATE = getTypedValue(value, "string")
160
160
elif name == "ALLEVENTS":
161
self.config.ALLEVENTS = self.getTypedValue(value, "boolean")
161
self.config.ALLEVENTS = getTypedValue(value, "boolean")
162
162
elif name == "WORDSEARCH":
163
self.config.WORDSEARCH = self.getTypedValue(value, "string")
163
self.config.WORDSEARCH = getTypedValue(value, "string")
164
164
elif name == "LIMIT":
165
self.config.LIMIT = self.getTypedValue(value, "integer")
165
self.config.LIMIT = getTypedValue(value, "integer")
166
166
elif name == "NOWHO":
167
self.config.NOWHO = self.getTypedValue(value, "boolean")
167
self.config.NOWHO = getTypedValue(value, "boolean")
168
168
elif name == "TIMEFORMAT":
169
self.config.TIMEFORMAT = self.getTypedValue(value, "string")
169
self.config.TIMEFORMAT = getTypedValue(value, "string")
170
170
elif name == "DATEFORMAT":
171
self.config.DATEFORMAT = self.getTypedValue(value, "string")
171
self.config.DATEFORMAT = getTypedValue(value, "string")
172
172
elif name == "CONNECTION_TIMEOUT":
173
self.config.CONNECTION_TIMEOUT = self.getTypedValue(value, "integer")
173
self.config.CONNECTION_TIMEOUT = getTypedValue(value, "integer")
175
175
self.logger.error("Unknown option in config file: " + name)
177
177
self.logger.info("Config data file %s not found, using defaults and setting up config file for next time" % configfilepath)
179
179
userconfigpath = os.path.join(os.path.expanduser('~'), ".config/"+app_name+"/")
180
180
configsource = os.path.join(app_path, "config/"+module_name+".config")
182
182
if os.path.exists(userconfigpath) == False:
183
183
os.makedirs(userconfigpath)
185
185
shutil.copy(configsource, configfilepath)
187
187
except Exception, e:
188
188
self.logger.error(e.__str__()+"\n"+traceback.format_exc())
190
def getTypedValue(self, value, expectedtype):
193
if len(value.strip(" ")) == 0:
196
elif value.lower() == "true":
197
if expectedtype == "boolean":
200
self.logger.error("Expected type was '%s', but the value '%s' was given"%(expectedtype, value))
202
elif value.lower() == "false":
203
if expectedtype == "boolean":
206
self.logger.error("Expected type was '%s', but the value '%s' was given"%(expectedtype, value))
208
elif self.isNumeric(value) == True:
209
if expectedtype == "integer":
212
self.logger.error("Expected type was '%s', but the value '%s' was given"%(expectedtype, value))
217
except (TypeError, ValueError):
218
self.logger.error("Cannot convert '%s' to expected type of '%s'"%(value,expectedtype))
221
190
def getCalendarList(self):
223
192
self.logger.info("Preparing google calendar list...")
225
194
# prepare calendar list to retrieve
226
195
if self.config.REQUESTCALENDARS != None:
227
196
self.requestedCalendars = self.config.REQUESTCALENDARS.split(",")
229
198
# get the list of calendars from google
230
199
calendars = self.cal_client.GetAllCalendarsFeed()
250
219
cal.username = urllib.unquote(match.group(1))
251
220
cal.visibility = urllib.unquote(match.group(2))
252
221
cal.projection = urllib.unquote(match.group(3))
254
223
if len(self.requestedCalendars):
256
225
# get the calendar name, updating it if an override is found
257
226
calendarName = cal.title.text
258
227
doc = minidom.parseString(str(cal))
259
228
overrideName = doc.getElementsByTagNameNS(u'http://schemas.google.com/gCal/2005', 'overridename')
260
229
if len(overrideName) > 0:
261
230
calendarName = overrideName[0].getAttribute("value")
263
232
for rc in self.requestedCalendars:
264
233
if rc.lower() == calendarName.lower():
265
234
self.requiredCalendars.append(cal)
267
self.requiredCalendars.append(cal)
236
self.requiredCalendars.append(cal)
270
239
def getOwnedCalendars(self):
274
243
self.logger.info("Fetching owned calendars...")
276
245
feed = self.cal_client.GetOwnCalendarsFeed()
278
247
calendarDataList = []
280
249
for mycalendar in zip(xrange(len(feed.entry)), feed.entry):
281
250
calendarData = CalendarData(mycalendar.title.text)
282
251
calendarDataList.append(calendarData)
284
253
return calendarDataList
286
255
except Exception,e:
287
256
self.logger.error(e.__str__()+"\n"+traceback.format_exc())
290
259
def getAllEvents(self):
294
263
self.logger.info("Fetching all event data...")
296
265
calendarDataEventList = []
298
267
for cal in self.requiredCalendars:
303
272
for j, when in zip(xrange(len(event.when)), event.when):
304
273
for k, where in zip(xrange(len(event.where)), event.where):
305
274
# if the event's end time is after now then add it!
306
if self.getDateFromWhen(when.end_time) > now:
275
if self.getDateFromWhen(when.end_time) > now:
308
277
for l, who in zip(xrange(len(event.who)), event.who):
309
278
whoList.append(who.email)
310
279
if len(whoList) == 0:
312
281
calendarDataEvent = CalendarEventData(event.title.text, when.start_time, when.end_time, where.value_string, event.content.text, whoList)
313
282
calendarDataEventList.append(calendarDataEvent)
315
284
return calendarDataEventList
317
286
except Exception,e:
318
287
self.logger.error(e.__str__()+"\n"+traceback.format_exc())
321
290
def getTextQueryEvents(self, text_query='Project'):
325
294
self.logger.info("Fetching text queried event data using text query of \"%s\"..."%text_query)
327
296
calendarDataEventList = []
329
298
for cal in self.requiredCalendars:
331
300
query = gdata.calendar.service.CalendarEventQuery(cal.username, cal.visibility, cal.projection, text_query)
332
301
query.orderby = 'starttime'
333
query.singleevents = 'true'
302
query.singleevents = 'true'
334
303
feed = self.cal_client.CalendarQuery(query)
335
304
now = datetime.now()
336
305
for i, event in zip(xrange(len(feed.entry)), feed.entry):
337
306
for j, when in zip(xrange(len(event.when)), event.when):
338
307
for k, where in zip(xrange(len(event.where)), event.where):
339
308
# if the event's end time is after now then add it!
340
if self.getDateFromWhen(when.end_time) > now:
309
if self.getDateFromWhen(when.end_time) > now:
342
311
for l, who in zip(xrange(len(event.who)), event.who):
343
312
whoList.append(who.email)
346
315
calendarDataEvent = CalendarEventData(event.title.text, when.start_time, when.end_time, where.value_string, event.content.text, whoList)
347
316
calendarDataEventList.append(calendarDataEvent)
349
318
return calendarDataEventList
351
320
except Exception,e:
352
321
self.logger.error(e.__str__()+"\n"+traceback.format_exc())
355
324
def getDateRangedEvents(self, start_date='2007-01-01', end_date='2020-01-01'):
359
328
self.logger.info("Fetching date ranged event data for dates between %s and %s..."%(start_date,end_date))
361
330
calendarDataEventList = []
363
332
for cal in self.requiredCalendars:
365
334
query = gdata.calendar.service.CalendarEventQuery(cal.username, cal.visibility, cal.projection)
366
335
query.start_min = start_date
367
336
query.start_max = end_date
368
337
query.orderby = 'starttime'
369
query.singleevents = 'true'
338
query.singleevents = 'true'
370
339
feed = self.cal_client.CalendarQuery(query)
371
340
for i, event in zip(xrange(len(feed.entry)), feed.entry):
372
341
for j, when in zip(xrange(len(event.when)), event.when):
406
375
for j, when in zip(xrange(len(event.when)), event.when):
407
376
for k, where in zip(xrange(len(event.where)), event.where):
408
377
# if the event's end time is after now then add it!
409
if self.getDateFromWhen(when.end_time) > now:
378
if self.getDateFromWhen(when.end_time) > now:
411
380
for l, who in zip(xrange(len(event.who)), event.who):
412
381
whoList.append(who.email)
413
382
if len(whoList) == 0:
415
384
calendarDataEvent = CalendarEventData(event.title.text, when.start_time, when.end_time, where.value_string, event.content.text, whoList)
416
385
calendarDataEventList.append(calendarDataEvent)
418
387
return calendarDataEventList
420
389
except Exception,e:
421
390
self.logger.error(e.__str__()+"\n"+traceback.format_exc())
424
393
def getOutputFromTemplate(self, template, title, starttime, endtime, location, description, who):
428
397
output = template
430
399
if title == None or title.strip() == "":
431
output = self.removeLineByString(output,"[title]")
400
output = removeLineByString(output,"[title]")
433
402
output = output.replace("[title]",title)
435
404
if location == None or location.strip() == "":
436
output = self.removeLineByString(output,"[location]")
405
output = removeLineByString(output,"[location]")
438
407
output = output.replace("[location]",location)
440
409
if description == None or description.strip() == "":
441
output = self.removeLineByString(output,"[description]")
410
output = removeLineByString(output,"[description]")
443
412
output = output.replace("[description]",description)
445
414
if who == None or who.strip() == "":
446
output = self.removeLineByString(output,"[who]")
415
output = removeLineByString(output,"[who]")
448
417
#output = output.replace("[who]",";".join(who))
449
418
output = output.replace("[who]",who)
451
420
output = output.replace("[starttime]",starttime)
452
421
output = output.replace("[endtime]",endtime)
454
423
# get rid of any excess crlf's and add just one
455
424
#output = output.rstrip(" \n")
456
425
#output = output + "\n"
458
return output.encode("utf-8")
427
return output #output.encode("utf-8")
460
429
except Exception,e:
461
430
self.logger.error(e.__str__()+"\n"+traceback.format_exc())
464
433
def getDatetimeFormat(self,dateformat=None,timeformat=None):
468
437
# get locale defaults for output
469
438
locale.setlocale(locale.LC_ALL,'')
471
440
# format date based on format setting
472
441
if dateformat == None:
473
442
self.dateformat = "%a "+locale.nl_langinfo(locale.D_FMT)
587
556
template = inputfile.read()
589
558
inputfile.close()
591
560
################ PROCESS OUTPUT #########################################
592
561
calendarEventCount = 0
593
562
calendarEventDataList.sort()
595
564
if len(calendarEventDataList) > 0:
597
566
output = headertemplate
599
568
# walk through the calendar events
600
569
for calendarEventData in calendarEventDataList:
602
571
# keep a tally of events output, if past the limit then exit
603
572
if self.config.LIMIT <> 0:
604
573
calendarEventCount = calendarEventCount+1
605
574
if calendarEventCount > self.config.LIMIT:
608
577
# collect calendar event data and format as we go
609
578
title = calendarEventData.title
610
579
starttime = self.getDateFromWhen(calendarEventData.starttime, True)
611
580
endtime = self.getDateFromWhen(calendarEventData.endtime, True)
613
582
location = calendarEventData.location
614
583
if location <> None:
615
#location = self.getWrappedText(location, self.MAX_WIDTH, indent)
584
#location = getWrappedText(location, self.MAX_WIDTH, indent)
616
585
if len(location) > self.MAX_WIDTH:
617
586
location = location[0:self.MAX_WIDTH].replace("\n","")+"..."
619
588
description = calendarEventData.description
620
589
if description <> None:
621
#description = self.getWrappedText(description, self.MAX_WIDTH, indent)
590
#description = getWrappedText(description, self.MAX_WIDTH, indent)
622
591
if len(description) > self.MAX_WIDTH:
623
592
description = description[0:self.MAX_WIDTH].replace("\n","")+"..."
625
594
who = calendarEventData.who
627
596
if self.config.NOWHO == True:
632
601
if len(who) > self.MAX_WIDTH:
633
602
who = who[0:self.MAX_WIDTH]+"..."
634
603
#who = ";".join(who).replace(";",", ")
635
#who = self.getWrappedText(who, self.MAX_WIDTH, indent)
604
#who = getWrappedText(who, self.MAX_WIDTH, indent)
637
606
# ready text for html output
638
title = self.getHTMLText(title)
639
location = self.getHTMLText(location)
640
description = self.getHTMLText(description)
641
who = self.getHTMLText(who)
607
title = getHTMLText(title)
608
location = getHTMLText(location)
609
description = getHTMLText(description)
610
who = getHTMLText(who)
643
612
# output event data using the template
644
613
output = output + self.getOutputFromTemplate(template, title, starttime, endtime, location, description, who)
646
return output.encode("utf-8")
615
return output #.encode("utf-8")
649
618
output = headertemplate
650
619
output = output + "<p>No Events...<p>"
651
620
return output.encode("utf-8")
653
622
except Exception,e:
654
623
self.logger.error(e.__str__()+"\n"+traceback.format_exc())
656
def removeLineByString(self,lines,string):
658
# define list from multiple lines, to allow remove function
659
lineslist = lines.split("\n")
660
lineslistchanged = False
662
for line in lineslist:
663
if line.find(string) <> -1:
664
lineslist.remove(line)
665
lineslistchanged = True
668
if lineslistchanged == True:
669
# rebuild lines string from updated list
670
lines = "\n".join(lineslist)
674
def getSpaces(self,spaces):
676
for i in range(0, spaces+1):
677
string = string + " "
680
def getWrappedText(self,text,width=40,indent=""):
682
for line in textwrap.wrap(text,width=(width-len(indent)),expand_tabs=False,replace_whitespace=False):
683
wrappedtext = wrappedtext + "\n" + indent + line
686
def getHTMLText(self,text):
689
for char in text: #html:
691
htmlentities.append(char)
693
htmlentities.append('&%s;' % codepoint2name[ord(char)])
694
html = "".join(htmlentities)
696
html = html.replace("\n","<br>\n") # switch out new line for html breaks
701
def getCleanText(self,html):
704
text = text.replace("\n","") # remove new lines from html
705
text = text.replace("'","'") # workaround for shitty xml codes not compliant with html
706
text = text.replace("<br>","\n") # switch out html breaks for new line
707
text = re.sub('<(.|\n)+?>','',text) # remove any html tags
708
text = re.sub('&(%s);' % '|'.join(name2codepoint), lambda m: chr(name2codepoint[m.group(1)]), text)
713
def isNumeric(self, string):
715
dummy = float(string)
720
625
def getHTML(options):
721
626
output = Output(options)
722
627
html = output.getOutput()
726
631
# to enable testing in isolation
727
632
if __name__ == "__main__":
729
634
parser = OptionParser()
730
parser.add_option("--noheader", dest="noheader", default=False, action="store_true", help=u"Turn off header output. This will override any header template setting to be nothing")
635
parser.add_option("--noheader", dest="noheader", default=False, action="store_true", help=u"Turn off header output. This will override any header template setting to be nothing")
731
636
parser.add_option("--headertemplate", dest="headertemplate", type="string", metavar="FILE", help=u"Override the header template for the plugin, default or config based template ignored.")
732
637
parser.add_option("--template", dest="template", type="string", metavar="FILE", help=u"Override the template for the plugin, default or config based template ignored.")
733
638
parser.add_option("--verbose", dest="verbose", default=False, action="store_true", help=u"Outputs verbose info to the terminal")
734
639
parser.add_option("--version", dest="version", default=False, action="store_true", help=u"Displays the version of the script.")
735
parser.add_option("--logfile", dest="logfile", type="string", metavar="FILE", help=u"If a filepath is set, the script logs to the filepath.")
640
parser.add_option("--logfile", dest="logfile", type="string", metavar="FILE", help=u"If a filepath is set, the script logs to the filepath.")
737
642
(options, args) = parser.parse_args()
739
644
output = Output(options)
740
645
html = output.getOutput()