Friday, September 4, 2009

Okay, Now I'm Really Done

Finished porting my street cleaning reminder program to Python for AppEngine, where it seems to be working fine. One unfortunate thing about moving the original desktop C# 3.5 code to ASP.NET 2.0 was that I had to replace all my nice Linq queries with regular loops, so being able to make use of Python features like tuples and list comprehensions was enjoyable.

Here is the code, with my personal identification information removed, and which I hereby release into the public domain:


import cgi


from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app


import gdata.service
import gdata.calendar
import gdata.calendar.service
import atom.service
import gdata.alt.appengine


import string
import time


class Cleanendar(webapp.RequestHandler):
  
  def __init__(self):


    manager = CalendarManager()
    self.commands = { 'n': manager.north, 's': manager.south, '': manager.clear }




  def post(self):


    if self.request.get('event') == 'MO':
      if self.request.get('uid') == MY_ZEEP_UID:


        self.response.headers['Content-Type'] = 'text/plain'
        self.response.out.write(self.process_command(self.request.get('body').lower()))
        return


    self.response.clear()
    self.response.set_status(400)




  def process_command(self, body):


    if body in self.commands:
      self.commands[body]()
      return ''


    else:
      return 'Bad command [' + body + ']'






class CalendarManager:


  def __init__(self):


    self.service = self.get_service()
    (self.calendar, hcalendar) = self.get_calendars(self.service)
    self.id = self.calendar.id.text.split('/')[-1]
    self.holidays = self.get_events(hcalendar.id.text.split('/')[-1])


    self.prefix = 'Cleanendar: '
    self.begin = time.strptime(time.strftime('%Y%m%d', time.localtime()) + '1230', '%Y%m%d%H%M')
    self.end = time.strptime(time.strftime('%Y%m%d', time.localtime()) + '1400', '%Y%m%d%H%M')
    self.daynames = ['Mo','Tu','We','Th','Fr','Sa','Su']




  def get_service(self):


    service = gdata.calendar.service.CalendarService(MY_GOOGLE_ID, MY_GOOGLE_PW, MY_APP_ID)
    service.ProgrammaticLogin()
    return service



  def get_calendars(self, service):


    feed = service.GetAllCalendarsFeed()
    calendar = None
    holidays = None
    for i, c in enumerate(feed.entry):
      if c.title.text == "Cleanendar":
        calendar = c
      elif c.title.text == "2009 NYC Alternate Side Parking":
        holidays = c
    return (calendar, holidays)




  def north(self):
    self.set_days([0,3])




  def south(self):
    self.set_days([1,4])




  def clear(self):
    events = self.get_events(self.id)
    for i, e in enumerate(events.entry):
      if e.title.text.startswith(self.prefix):
        self.service.DeleteEvent(e.GetEditLink().href)




  def set_days(self, days):
    self.clear()
    self.add_event(self.make_event(self.begin, "Move car", days))
    self.add_event(self.make_event(self.end, "Move back", days))



  def get_events(self, id):
    return self.service.GetCalendarEventFeed('/calendar/feeds/' + id + '/private/full')




  def make_event(self, t, desc, days):
    event = gdata.calendar.CalendarEventEntry()
    event.title = atom.Title(text=self.prefix + 'Street cleaning')
    event.content = atom.Content(text=desc)
    event.recurrence = self.make_recurrence(t, days)
    event.reminder = self.make_reminder()
    return event




  def add_event(self, event):
    return self.service.InsertEvent(event, '/calendar/feeds/' + self.id + '/private/full')




  def make_recurrence(self, t, days):
    data = ('DTSTART;TZID=UTC;VALUE=DATE-TIME:' + time.strftime('%Y%m%dT%H%M%S', t) + '\r\n' +
      'DURATION:PT5M\r\n' +
      'RRULE:FREQ=WEEKLY;BYDAY=' + string.join([self.daynames[d] for d in days], ',') + ';' +
      'UNTIL=' + time.strftime('%Y', t) + '1231\r\n' +
      'EXDATE:' + string.join([time.strftime('%Y%m%d', d) + time.strftime('T%H%M%S', t) for d in self.make_exclusions(days)], ',') + '\r\n')
    return gdata.calendar.Recurrence(text=data)




  def make_exclusions(self, days):
    e = []
    for i, h in enumerate(self.holidays.entry):
      for t in [time.strptime(w.start_time, '%Y-%m-%d') for w in h.when]:
        if t.tm_wday in [0,3]:
          e.append(t)
    return e




  def make_reminder(self):
    return gdata.calendar.Reminder(minutes=10, method='sms')




application = webapp.WSGIApplication([('/', Cleanendar)],
                                     debug=True)


def main():
  run_wsgi_app(application)


if __name__ == "__main__":
  main()

No comments: