conduit r1417 - in trunk: . conduit/modules/RTMModule
- From: jstowers svn gnome org
- To: svn-commits-list gnome org
- Subject: conduit r1417 - in trunk: . conduit/modules/RTMModule
- Date: Fri, 11 Apr 2008 11:42:21 +0100 (BST)
Author: jstowers
Date: Fri Apr 11 11:42:21 2008
New Revision: 1417
URL: http://svn.gnome.org/viewvc/conduit?rev=1417&view=rev
Log:
2008-04-11 Kevin Kubasik <kevin kubasik net>
* conduit/modules/RTMModule/Makefile.am:
* conduit/modules/RTMModule/RTMModule.py:
* conduit/modules/RTMModule/config.glade:
* conduit/modules/RTMModule/rtm.py: Add preliminary support for remember
the milk.
Added:
trunk/conduit/modules/RTMModule/
trunk/conduit/modules/RTMModule/Makefile.am
trunk/conduit/modules/RTMModule/RTMModule.py
trunk/conduit/modules/RTMModule/config.glade
trunk/conduit/modules/RTMModule/rtm.py
Modified:
trunk/ChangeLog
Added: trunk/conduit/modules/RTMModule/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/RTMModule/Makefile.am Fri Apr 11 11:42:21 2008
@@ -0,0 +1,8 @@
+conduit_handlersdir = $(libdir)/conduit/modules/RTMModule
+conduit_handlers_PYTHON = RTMModule.py
+
+conduit_handlers_DATA = config.glade
+EXTRA_DIST = config.glade
+
+clean-local:
+ rm -rf *.pyc *.pyo
Added: trunk/conduit/modules/RTMModule/RTMModule.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/RTMModule/RTMModule.py Fri Apr 11 11:42:21 2008
@@ -0,0 +1,174 @@
+"""
+RTM Sync.
+"""
+import os, sys
+import traceback
+import md5
+import logging
+log = logging.getLogger("modules.RTM")
+import dateutil
+import urllib, urllib2
+import vobject
+import itertools
+import conduit
+import conduit.utils as Utils
+import conduit.Web as Web
+import conduit.datatypes.Event as Event
+import conduit.Exceptions as Exceptions
+from conduit.datatypes import Rid
+import conduit.dataproviders.DataProvider as DataProvider
+
+from gettext import gettext as _
+Utils.dataprovider_add_dir_to_path(__file__)
+import rtm
+MODULES = {
+ "RTMTasksTwoWay" : {"type":"dataprovider" }
+}
+
+class RTMTasksTwoWay(DataProvider.TwoWay):
+
+ DEFAULT_TASK_URI = "default"
+
+ _name_ = _("Remember The Milk Tasks")
+ _description_ = _("Sync your tasks to Remember The Milk")
+ _category_ = conduit.dataproviders.CATEGORY_OFFICE
+ _module_type_ = "twoway"
+ _in_type_ = "event"
+ _out_type_ = "event"
+ _icon_ = "tomboy"
+
+ API_KEY = 'fe049e2cec86568f3d79c964d4a45f5c'
+ SECRET='b57757de51f7e919'
+
+ def __init__(self, *args):
+ DataProvider.TwoWay.__init__(self)
+ self.uids = None
+ self.token = None
+ self.tasks = {}
+ self.ical = None
+ self.username = None
+ self.password = None
+ self.rtm = rtm.RTM(apiKey=self.API_KEY,secret=self.SECRET,token=self.get_configuration().get('token'))
+
+
+ def refresh(self):
+ DataProvider.TwoWay.refresh(self)
+ self.tasks = {}
+ tempt = rtm.get_all_tasks(self.rtm)
+ #self.tasks.sort(key='id')
+ self.ical =open(urllib.urlretrieve('http://%s:%s www rememberthemilk com/icalendar/%s/events/'%(
+ self.get_configuration().get('username'),
+ self.get_configuration().get('password'),
+ self.get_configuration().get('username')))[0]).read()
+
+ for t in tempt:
+ self.tasks[t.id] = t
+
+
+ def get_all(self):
+ return list(self.tasks.iterkeys())
+
+ def get (self, LUID):
+ log.warn("LUID: %s"%LUID)
+ task = None
+ if self.tasks.has_key(LUID):
+ task = self.tasks[LUID]
+ else:
+ self.refresh()
+ if self.tasks.has_key(LUID):
+ task = self.tasks[LUID]
+ else :
+ task = None
+ e = Event.Event()
+ e.set_UID(self.get_UID())
+ e.set_mtime( dateutil.parser.parse(task.modified))
+ e.set_open_URI('http://www.rememberthemilk.com/home/%s/#%s'%(self.username,task.id))
+ res = [sum for sum in vobject.readComponents(self.ical) if sum.vevent.summary.value == task.name]
+ e.set_from_ical_string(res[0].serialize())
+ return e
+
+
+ def delete(self, LUID):
+ self.rtm.tasks.delete(task_id=LUID)
+ del self.tasks[LUID]
+
+ def put(self, putData, overwrite, LUID):
+ raise NotImplementedError("Not done")
+
+ #----------------------------------------------------------------------
+ def get_UID(self):
+ """"""
+ return "RTM#%s"%(self.get_configuration().get("username"))
+
+ def _login(self):
+
+
+
+ if self.token is None:
+ # get the ticket and open login url
+ #self.token = self.rtm.getToken()
+ url = self.rtm.getAuthURL()
+
+ #wait for log in
+ Web.LoginMagic("Log into RememberTheMilk", url, login_function=self._try_login)
+ def _try_login (self):
+ """
+ Try to perform a login, return None if it does not succeed
+ """
+ try:
+
+ self.token = self.rtm.getToken()
+ return self.token
+ except:
+ return None
+
+
+ def configure(self, window):
+ """
+ Configures the RTM Backend
+ """
+ import gtk
+ import gobject
+ def on_login_finish(*args):
+ Utils.dialog_reset_cursor(dlg)
+
+ def on_response(sender, responseID):
+ if responseID == gtk.RESPONSE_OK:
+ self.username = str(tree.get_widget('user_entry').get_text())
+ self.password = str(tree.get_widget('pass_entry').get_text())
+
+ def load_button_clicked(button):
+ Utils.dialog_set_busy_cursor(dlg)
+ conduit.GLOBALS.syncManager.run_blocking_dataprovider_function_calls(
+ self,
+ on_login_finish,
+ self._login)
+
+
+ tree = Utils.dataprovider_glade_get_widget(
+ __file__,
+ "config.glade",
+ "RTMConfigDialog")
+
+ #get a whole bunch of widgets
+ load_button = tree.get_widget("load_button")
+ dlg = tree.get_widget("RTMConfigDialog")
+
+ # load button
+ load_button.connect('clicked', load_button_clicked)
+
+ # run the dialog
+ Utils.run_dialog_non_blocking(dlg, on_response, window)
+
+ def is_configured (self, isSource, isTwoWay):
+ return self.token is not None
+
+ def get_configuration(self):
+ return {
+ "token" : self.token,
+ "username" : self.username,
+ "password" : self.password
+ }
+
+
+
Added: trunk/conduit/modules/RTMModule/config.glade
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/RTMModule/config.glade Fri Apr 11 11:42:21 2008
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--*- mode: xml -*-->
+<glade-interface>
+ <widget class="GtkDialog" id="RTMConfigDialog">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Box.net</property>
+ <property name="resizable">False</property>
+ <property name="default_width">250</property>
+ <property name="default_height">350</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="vbox30">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkLabel" id="folderlabel">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="xpad">2</property>
+ <property name="ypad">2</property>
+ <property name="label" translatable="yes">Remember The Milk:</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="load_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Click To Login</property>
+ <property name="use_underline">True</property>
+ <property name="response_id">0</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Username:</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Password:</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkEntry" id="user_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="pass_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="visibility">False</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="hbuttonbox12">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <widget class="GtkButton" id="button32">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">-6</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button33">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="label">gtk-ok</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">-5</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+</glade-interface>
Added: trunk/conduit/modules/RTMModule/rtm.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/RTMModule/rtm.py Fri Apr 11 11:42:21 2008
@@ -0,0 +1,426 @@
+# Python library for Remember The Milk API
+
+__author__ = 'Sridhar Ratnakumar <http://nearfar.org/>'
+__all__ = (
+ 'API',
+ 'createRTM',
+ 'set_log_level',
+ )
+
+
+import new
+import warnings
+import urllib
+import logging
+from md5 import md5
+_use_simplejson = False
+try:
+ import simplejson
+ _use_simplejson = True
+except ImportError:
+ pass
+
+logging.basicConfig()
+LOG = logging.getLogger(__name__)
+LOG.setLevel(logging.INFO)
+
+SERVICE_URL = 'http://api.rememberthemilk.com/services/rest/'
+AUTH_SERVICE_URL = 'http://www.rememberthemilk.com/services/auth/'
+
+
+class RTMError(Exception): pass
+
+class RTMAPIError(RTMError): pass
+
+class AuthStateMachine(object):
+
+ class NoData(RTMError): pass
+
+ def __init__(self, states):
+ self.states = states
+ self.data = {}
+
+ def dataReceived(self, state, datum):
+ if state not in self.states:
+ raise RTMError, "Invalid state <%s>" % state
+ self.data[state] = datum
+
+ def get(self, state):
+ if state in self.data:
+ return self.data[state]
+ else:
+ raise AuthStateMachine.NoData, 'No data for <%s>' % state
+
+
+class RTM(object):
+
+ def __init__(self, apiKey, secret, token=None):
+ self.apiKey = apiKey
+ self.secret = secret
+ self.authInfo = AuthStateMachine(['frob', 'token'])
+ self.userdata = None
+ # this enables one to do 'rtm.tasks.getList()', for example
+ for prefix, methods in API.items():
+ setattr(self, prefix,
+ RTMAPICategory(self, prefix, methods))
+
+ if token:
+ self.authInfo.dataReceived('token', token)
+
+ def _sign(self, params):
+ "Sign the parameters with MD5 hash"
+ pairs = ''.join(['%s%s' % (k,v) for k,v in sortedItems(params)])
+ return md5(self.secret+pairs).hexdigest()
+
+ def get(self, **params):
+ "Get the XML response for the passed `params`."
+ params['api_key'] = self.apiKey
+ params['format'] = 'json'
+ params['api_sig'] = self._sign(params)
+
+ json = openURL(SERVICE_URL, params).read()
+
+ LOG.debug("JSON response: \n%s" % json)
+
+ if _use_simplejson:
+ data = dottedDict('ROOT', simplejson.loads(json))
+ else:
+ data = dottedJSON(json)
+ rsp = data.rsp
+
+ if rsp.stat == 'fail':
+ raise RTMAPIError, 'API call failed - %s (%s)' % (
+ rsp.err.msg, rsp.err.code)
+ else:
+ return rsp
+
+ def getNewFrob(self):
+ rsp = self.get(method='rtm.auth.getFrob')
+ self.authInfo.dataReceived('frob', rsp.frob)
+ return rsp.frob
+
+ def getAuthURL(self):
+ try:
+ frob = self.authInfo.get('frob')
+ except AuthStateMachine.NoData:
+ frob = self.getNewFrob()
+
+ params = {
+ 'api_key': self.apiKey,
+ 'perms' : 'delete',
+ 'frob' : frob
+ }
+ params['api_sig'] = self._sign(params)
+ return AUTH_SERVICE_URL + '?' + urllib.urlencode(params)
+
+ def getToken(self):
+ frob = self.authInfo.get('frob')
+ rsp = self.get(method='rtm.auth.getToken', frob=frob)
+ self.authInfo.dataReceived('token', rsp.auth.token)
+ self.userdata = rsp.auth.user
+ return rsp.auth.token
+
+class RTMAPICategory:
+ "See the `API` structure and `RTM.__init__`"
+
+ def __init__(self, rtm, prefix, methods):
+ self.rtm = rtm
+ self.prefix = prefix
+ self.methods = methods
+
+ def __getattr__(self, attr):
+ if attr in self.methods:
+ rargs, oargs = self.methods[attr]
+ aname = 'rtm.%s.%s' % (self.prefix, attr)
+ return lambda **params: self.callMethod(
+ aname, rargs, oargs, **params)
+ else:
+ raise AttributeError, 'No such attribute: %s' % attr
+
+ def callMethod(self, aname, rargs, oargs, **params):
+ # Sanity checks
+ for requiredArg in rargs:
+ if requiredArg not in params:
+ raise TypeError, 'Required parameter (%s) missing' % requiredArg
+
+ for param in params:
+ if param not in rargs + oargs:
+ warnings.warn('Invalid parameter (%s)' % param)
+
+ return self.rtm.get(method=aname,
+ auth_token=self.rtm.authInfo.get('token'),
+ **params)
+
+
+
+# Utility functions
+
+def sortedItems(dictionary):
+ "Return a list of (key, value) sorted based on keys"
+ keys = dictionary.keys()
+ keys.sort()
+ for key in keys:
+ yield key, dictionary[key]
+
+def openURL(url, queryArgs=None):
+ if queryArgs:
+ url = url + '?' + urllib.urlencode(queryArgs)
+ LOG.debug("URL> %s", url)
+ return urllib.urlopen(url)
+
+class dottedDict(object):
+ "Make dictionary items accessible via the object-dot notation."
+
+ def __init__(self, name, dictionary):
+ self._name = name
+
+ if type(dictionary) is dict:
+ for key, value in dictionary.items():
+ if type(value) is dict:
+ value = dottedDict(key, value)
+ elif type(value) in (list, tuple):
+ value = [dottedDict('%s_%d' % (key, i), item)
+ for i, item in indexed(value)]
+ setattr(self, key, value)
+
+ def __repr__(self):
+ children = [c for c in dir(self) if not c.startswith('_')]
+ return 'dotted <%s> : %s' % (
+ self._name,
+ ', '.join(children))
+
+
+def safeEval(string):
+ return eval(string, {}, {})
+
+def dottedJSON(json):
+ return dottedDict('ROOT', safeEval(json))
+
+def indexed(seq):
+ index = 0
+ for item in seq:
+ yield index, item
+ index += 1
+
+
+# API spec
+
+API = {
+ 'auth': {
+ 'checkToken':
+ [('auth_token'), ()],
+ 'getFrob':
+ [(), ()],
+ 'getToken':
+ [('frob'), ()]
+ },
+ 'contacts': {
+ 'add':
+ [('timeline', 'contact'), ()],
+ 'delete':
+ [('timeline', 'contact_id'), ()],
+ 'getList':
+ [(), ()]
+ },
+ 'groups': {
+ 'add':
+ [('timeline', 'group'), ()],
+ 'addContact':
+ [('timeline', 'group_id', 'contact_id'), ()],
+ 'delete':
+ [('timeline', 'group_id'), ()],
+ 'getList':
+ [(), ()],
+ 'removeContact':
+ [('timeline', 'group_id', 'contact_id'), ()],
+ },
+ 'lists': {
+ 'add':
+ [('timeline', 'name'), ('filter'), ()],
+ 'archive':
+ [('timeline', 'list_id'), ()],
+ 'delete':
+ [('timeline', 'list_id'), ()],
+ 'getList':
+ [(), ()],
+ 'setDefaultList':
+ [('timeline'), ('list_id'), ()],
+ 'setName':
+ [('timeline', 'list_id', 'name'), ()],
+ 'unarchive':
+ [('timeline'), ('list_id'), ()],
+ },
+ 'locations': {
+ 'getList':
+ [(), ()]
+ },
+ 'reflection': {
+ 'getMethodInfo':
+ [('methodName',), ()],
+ 'getMethods':
+ [(), ()]
+ },
+ 'settings': {
+ 'getList':
+ [(), ()]
+ },
+ 'tasks': {
+ 'add':
+ [('timeline', 'name',), ('list_id', 'parse',)],
+ 'addTags':
+ [('timeline', 'list_id', 'taskseries_id', 'task_id', 'tags'),
+ ()],
+ 'complete':
+ [('timeline', 'list_id', 'taskseries_id', 'task_id',), ()],
+ 'delete':
+ [('timeline', 'list_id', 'taskseries_id', 'task_id'), ()],
+ 'getList':
+ [(),
+ ('list_id', 'filter', 'last_sync')],
+ 'movePriority':
+ [('timeline', 'list_id', 'taskseries_id', 'task_id', 'direction'),
+ ()],
+ 'moveTo':
+ [('timeline', 'from_list_id', 'to_list_id', 'taskseries_id', 'task_id'),
+ ()],
+ 'postpone':
+ [('timeline', 'list_id', 'taskseries_id', 'task_id'),
+ ()],
+ 'removeTags':
+ [('timeline', 'list_id', 'taskseries_id', 'task_id', 'tags'),
+ ()],
+ 'setDueDate':
+ [('timeline', 'list_id', 'taskseries_id', 'task_id'),
+ ('due', 'has_due_time', 'parse')],
+ 'setEstimate':
+ [('timeline', 'list_id', 'taskseries_id', 'task_id'),
+ ('estimate',)],
+ 'setLocation':
+ [('timeline', 'list_id', 'taskseries_id', 'task_id'),
+ ('location_id',)],
+ 'setName':
+ [('timeline', 'list_id', 'taskseries_id', 'task_id', 'name'),
+ ()],
+ 'setPriority':
+ [('timeline', 'list_id', 'taskseries_id', 'task_id'),
+ ('priority',)],
+ 'setRecurrence':
+ [('timeline', 'list_id', 'taskseries_id', 'task_id'),
+ ('repeat',)],
+ 'setTags':
+ [('timeline', 'list_id', 'taskseries_id', 'task_id'),
+ ('tags',)],
+ 'setURL':
+ [('timeline', 'list_id', 'taskseries_id', 'task_id'),
+ ('url',)],
+ 'uncomplete':
+ [('timeline', 'list_id', 'taskseries_id', 'task_id'),
+ ()],
+ },
+ 'tasksNotes': {
+ 'add':
+ [('timeline', 'list_id', 'taskseries_id', 'task_id', 'note_title', 'note_text'), ()],
+ 'delete':
+ [('timeline', 'note_id'), ()],
+ 'edit':
+ [('timeline', 'note_id', 'note_title', 'note_text'), ()]
+ },
+ 'test': {
+ 'echo':
+ [(), ()],
+ 'login':
+ [(), ()]
+ },
+ 'time': {
+ 'convert':
+ [('to_timezone',), ('from_timezone', 'to_timezone', 'time')],
+ 'parse':
+ [('text',), ('timezone', 'dateformat')]
+ },
+ 'timelines': {
+ 'create':
+ [(), ()]
+ },
+ 'timezones': {
+ 'getList':
+ [(), ()]
+ },
+ 'transactions': {
+ 'undo':
+ [('timeline', 'transaction_id'), ()]
+ },
+ }
+
+def createRTM(apiKey, secret, token=None):
+ rtm = RTM(apiKey, secret, token)
+
+ if token is None:
+ print 'No token found'
+ print 'Give me access here:', rtm.getAuthURL()
+ raw_input('Press enter once you gave access')
+ print 'Note down this token for future use:', rtm.getToken()
+
+ return rtm
+
+def test(apiKey, secret, token=None):
+ rtm = createRTM(apiKey, secret, token)
+
+ rspTasks = rtm.tasks.getList(filter='dueWithin:"1 week of today"')
+ print [t.name for t in rspTasks.tasks.list.taskseries]
+ print rspTasks.tasks.list.id
+
+ rspLists = rtm.lists.getList()
+ # print rspLists.lists.list
+ print [(x.name, x.id) for x in rspLists.lists.list]
+
+def set_log_level(level):
+ '''Sets the log level of the logger used by the module.
+
+ >>> import rtm
+ >>> import logging
+ >>> rtm.set_log_level(logging.INFO)
+ '''
+
+ LOG.setLevel(level)
+
+#----------------------------------------------------------------------
+def get_all_tasks(rtm):
+ """"""
+ rspLists = rtm.lists.getList()
+ tasks = list()
+ tasksf = list()
+ for l in rspLists.lists.list:
+ tasks.append(rtm.tasks.getList(list_id=l.id))
+ temp = [a.tasks.list for a in tasks.__iter__()]
+ map(lambda (x): tasksf.extend(x) , [ a.taskseries for a in temp if hasattr(a,'taskseries')])
+ tasks_sum = set([t for t in tasksf])
+ print len(tasksf)
+ print tasks_sum
+
+ return list(tasks_sum)
+ #tasks_sum = list()
+
+ #map(lambda (x): tasks_sum.extend(x), tasksf)
+
+ #tasks = [t.name for t in rspTasks.tasks.list]
+
+
+import unittest
+
+
+########################################################################
+class TestRTM(unittest.TestCase):
+ """"""
+
+ #----------------------------------------------------------------------
+ def runTest(self):
+ """"""
+ import rtm
+ rtm2 = rtm.createRTM(apiKey='fe049e2cec86568f3d79c964d4a45f5c',secret='b57757de51f7e919',token=None)
+ get_all_tasks(rtm2)
+
+
+
+
+
+
\ No newline at end of file
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]