Source

gcal2org / gcal2org.py

Felix Geller 99741ab 





















Felix Geller 8fa7d2b 
Felix Geller 88b7cd0 






















Felix Geller 8fa7d2b 



Felix Geller 88b7cd0 

Felix Geller 99741ab 


Felix Geller 08c2b3f 
Felix Geller 0c86e81 
Felix Geller 99741ab 
Felix Geller 88b7cd0 









Felix Geller 08c2b3f 


Felix Geller 99741ab 



Felix Geller 08c2b3f 
Felix Geller 99741ab 
Felix Geller e93ad3b 
Felix Geller 99741ab 







Felix Geller c755c8b 
Felix Geller 2389731 
Felix Geller 99741ab 






Felix Geller 2389731 
Felix Geller 99741ab 

Felix Geller 0c86e81 




Felix Geller 99741ab 
Felix Geller 0c86e81 



Felix Geller 99741ab 

Felix Geller 62f983c 
Felix Geller 99741ab 
Felix Geller 0c86e81 
Felix Geller 99741ab 


Felix Geller 62f983c 
Felix Geller 08c2b3f 
Felix Geller 99741ab 











Felix Geller c755c8b 
Felix Geller 99741ab 
Felix Geller 08c2b3f 
Felix Geller c755c8b 
Felix Geller 99741ab 


Felix Geller 08c2b3f 



Felix Geller 9d67734 
Felix Geller 08c2b3f 
















Felix Geller 88b7cd0 
Felix Geller 08c2b3f 





Felix Geller 549d140 
Felix Geller 08c2b3f 

Felix Geller 2389731 

Felix Geller 549d140 
Felix Geller 08c2b3f 


Felix Geller 88b7cd0 

Felix Geller 08c2b3f 






Felix Geller 99741ab 

#!/usr/bin/env python
# Copyright (C) 2011 by Felix Geller
# 
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# There are two main usages:
# - updating a the title of a single event in default calendar,
# - downloading calendar entries for a given calendar that are at most
#   30 days in the past or future and printing them as a org-entry to
#   a given file.
#
# The first is mainly useful for me, or rather how I use org-mode. I
# use it in connection with the following ELisp snippet to update
# tasks from TODO to DONE. If I toggle the TODO state of an entry and
# if it has a property named GCalId, this script is invoked to publish
# the new title.
#
# (defun fg/publish-state-entry-state-change-to-gcal ()
#   (let ((new-title (org-get-heading))
# 	(gcal-id (org-entry-get nil "GCalId")))
#     (when gcal-id
#       (start-process 
#        "push2gcal" "*push2gcal*" 
#        "gcal2org.py" "me@gmail.com" "update" gcal-id "text" new-title))))
# (add-hook 'org-after-todo-state-change-hook
# 	  'fg/publish-state-entry-state-change-to-gcal) 
#
# Invoke without arguments for usage examples. Or scroll down.
#
# N.B.: Uses authinfo_pw to retrieve a password from an encrypted
# file. Please change this according to your preferences.
#
# Acknowledgment: Thanks to Rasmus for adding the capability of
# specifying other calendar URIs.

import gdata.calendar.client
from datetime import date, datetime, timedelta
from time import strftime, strptime, mktime, localtime, sleep
import sys, re, os, getopt


USAGE = """Download default calendar:
 > gcal2org.py download me@gmail.com file.org

Download other (kitty's) calendar:
 > gcal2org.py download me@gmail.com file.org "http://www.google.com/calendar/feeds/kitty@gmail.com/private/full/"

Update single event:
 > gcal2org.py me@gmail.com update strange-event-id title new-title"""

EVENT_URI_PREFIX = 'http://www.google.com/calendar/feeds/default/private/full/'

PREAMBLE = """#+STARTUP: overview
#+DESCRIPTION: Converted on %s
* Events
"""

ITEM = """** %s
   :PROPERTIES:
   :GCalId:       %s
   :When:     %s
   :Where:    %s
   :Who:      %s
   :END:
 - Description:
%s
"""

def date_range_query(cal_client,
                     cal_uri,
                     max_results = 333, 
                     start_date = str(date.today() - timedelta(30)), 
                     end_date = str(date.today() + timedelta(30))):
  query = gdata.calendar.client.CalendarEventQuery()
  query.start_min = start_date
  query.start_max = end_date
  query.max_results = max_results
  feed = cal_client.GetCalendarEventFeed(q=query, uri=cal_uri)
  return feed.entry

def date_range(start_time, end_time):
  if 'T' in start_time:
    st = strptime(start_time[:16], '%Y-%m-%dT%H:%M')
    et = strptime(end_time[:16], '%Y-%m-%dT%H:%M')
    tstamp = '<%Y-%m-%d %a %H:%M>'
  else:
    st = strptime(start_time, '%Y-%m-%d')
    et = localtime(mktime(strptime(end_time, '%Y-%m-%d')) - 86400.0)
    tstamp = '<%Y-%m-%d %a>'
  return '%s--%s' % tuple([strftime(tstamp, t) for t in (st, et)])

def write_item(event, org_file):
  event_id = event.id.text
  title = event.title.text
  when = date_range(event.when[0].start, event.when[0].end)
  where = event.where[0].text or 'N/A'
  who = ', '.join([x.value for x in event.who])
  description = event.content.text or 'N/A'
  itemdata =  tuple([s.encode('UTF-8') for s in (title, event_id, when, where, who, description)])
  org_file.write(ITEM % itemdata)

def authinfo_pw(login):
  p = re.compile('login %s password ([^ ]+)' % login)
  authinfo = os.popen('gpg -q --no-tty -d ~/.gcal.gpg').read()
  return p.search(authinfo).group(1)

def create_calendar_client(login):
  client = gdata.calendar.client.CalendarClient(source='acme-gcal2org-v1')
  client.ssl = True
  client.ClientLogin(login, authinfo_pw(login), client.source)
  return client

def create_org_file(filename, events):
  org_file = open(filename, 'w')
  org_file.write(PREAMBLE % datetime.now())
  for event in events:
    write_item(event, org_file)
  org_file.close()

def update_event(client, event_id, fields):
  uri = EVENT_URI_PREFIX + event_id
  event = client.GetEventEntry(uri, auth_token=client.auth_token)
  for k,v in fields.items():
    if k == 'title': event.title.text = v
  client.Update(event)

class Usage(Exception):
    def __init__(self, msg):
        self.msg = msg

def restructure_field_args(fs):
  if len(fs) % 2 != 0:
    raise Usage("Please supply key/value pairs, rather than: %s." % fs)
  fields = {}
  for i in range(len(fs)):
    if i % 2 == 0:
      fields[fs[i]] = fs[i+1]
  return fields

def main(retry_count=5):
  try:
    if 4 > len(sys.argv): raise Usage(USAGE)
    else:
      client = create_calendar_client(sys.argv[1])
      op = sys.argv[2]
      if op == 'update':
        event_id = sys.argv[3]
        fields = restructure_field_args(sys.argv[4:])
        update_event(client, event_id, fields)
      elif op == 'download':
        filename = sys.argv[3]
        cal_uri = sys.argv[4] if (5 == len(sys.argv)) else None
        events = date_range_query(client, cal_uri)
        create_org_file(filename, events)
      else: raise Usage('Operation "%s" not supported.' % op)

  except Usage, err:
    print >>sys.stderr, msg
    return 2
  except gdata.client.RedirectError, err:
    if retry_count > 0:
      print >>sys.stderr, ("gcal2org: Retry #%s" % (5 - retry_count))
      sleep(1)
      main(retry_count=(retry_count - 1))
    else:
      raise err

if __name__ == '__main__':
  sys.exit(main())
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.