epiphany-extensions r1809 - in trunk: . extensions/epilicious



Author: mtherning
Date: Wed Feb 11 23:10:17 2009
New Revision: 1809
URL: http://svn.gnome.org/viewvc/epiphany-extensions?rev=1809&view=rev

Log:
Adding diigo backend.

Catching up with recent out-of-tree development.


Added:
   trunk/extensions/epilicious/DiigoStore.py
   trunk/extensions/epilicious/diigo.py
Modified:
   trunk/   (props changed)
   trunk/extensions/epilicious/BaseStore.py
   trunk/extensions/epilicious/DeliciousStore.py
   trunk/extensions/epilicious/EpiphanyStore.py
   trunk/extensions/epilicious/Makefile.am
   trunk/extensions/epilicious/__init__.py
   trunk/extensions/epilicious/backend.py
   trunk/extensions/epilicious/config.py
   trunk/extensions/epilicious/epilicious.py.in
   trunk/extensions/epilicious/epilicious.schemas.in
   trunk/extensions/epilicious/magnolia.py

Modified: trunk/extensions/epilicious/BaseStore.py
==============================================================================
--- trunk/extensions/epilicious/BaseStore.py	(original)
+++ trunk/extensions/epilicious/BaseStore.py	Wed Feb 11 23:10:17 2009
@@ -16,9 +16,28 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
-class BaseStore:
+class BaseStore(object):
     '''The base of all stores. Never to be instantiated.'''
 
+    def __init__(self):
+        self.store = 1
+        self.fstore = []
+
+    def perform_calls(self):
+        for f, s, args, kwargs in self.fstore:
+            yield f(s, *args, **kwargs)
+        self.fstore = []
+
+    @classmethod
+    def store_call(cls, func):
+        def inner(self, *args, **kwargs):
+            if self.store:
+                self.fstore.append((func, self, args, kwargs))
+            else:
+                func(self, *args, **kwargs)
+
+        return inner
+
     def get_snapshot(self):
         '''Calculates a snapshot of the store's bookmarks.
 

Modified: trunk/extensions/epilicious/DeliciousStore.py
==============================================================================
--- trunk/extensions/epilicious/DeliciousStore.py	(original)
+++ trunk/extensions/epilicious/DeliciousStore.py	Wed Feb 11 23:10:17 2009
@@ -20,24 +20,25 @@
 from sets import Set
 import libepilicious
 from libepilicious.BaseStore import BaseStore
-from libepilicious.backend import create_backend
+from libepilicious.magnolia import Magnolia
 
 
 class DeliciousStore(BaseStore):
     '''The class representing the storage of bookmarks on a backend with a
     del.icio.us-like API.'''
 
-    def __init__(self, user, pwd, space_repl, backend):
+    def __init__(self, user, pwd, space_repl):
         '''Constructor.
 
         @param user: Delicious username
         @param pwd: Delicious password
         @param space_repl: Character that replaces space
         '''
+        BaseStore.__init__(self)
         self.__un = user
         self.__pwd = pwd
         self.__sr = space_repl
-        self.__d = create_backend(backend)(user, pwd)
+        self.__d = Magnolia(user, pwd)
         self.__snap_utd = 0
 
     def get_snapshot(self):
@@ -63,6 +64,7 @@
         self.__snap_utd = 1
         return res
 
+    @BaseStore.store_call
     def url_delete(self, url):
         '''Deletes a URL from the storage.
 
@@ -77,6 +79,7 @@
         except:
             libepilicious.get_logger().exception('Failed to delete URL %s' % url)
 
+    @BaseStore.store_call
     def url_sync(self, url, desc, to_del, to_add):
         '''Synchronises a URL's tags. The URL is added if it doesn't exist.
 

Added: trunk/extensions/epilicious/DiigoStore.py
==============================================================================
--- (empty file)
+++ trunk/extensions/epilicious/DiigoStore.py	Wed Feb 11 23:10:17 2009
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2009 by Magnus Therning
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+from sets import Set
+
+import libepilicious
+from libepilicious.BaseStore import BaseStore
+from libepilicious.diigo import Diigo2
+
+class DiigoStore(BaseStore):
+    '''A class representing the storage of bookmarks on a Diigo backend.
+    '''
+
+    def __init__(self, user, pwd, space_repl):
+        BaseStore.__init__(self)
+        self.__un = user
+        self.__pwd = pwd
+        self.__sr = space_repl
+        self.__bkend = Diigo2(user, pwd)
+        self.__snap_utd = False
+        self.__snap = None
+
+    def get_snapshot(self):
+        '''Retrieves a snapshot of bookmarks.
+
+        @note: L{BaseStore.get_snapshot} documents the format of the return
+        value.
+        '''
+        def fixup_bm(b):
+            url = b['url']
+            title = b['title']
+            tags = [t.replace(self.__sr, ' ') for t in b['tags'].split(',')]
+            return (url, [title, tags])
+
+        if self.__snap_utd:
+            return self.__snap
+        bms = self.__bkend.all_bookmarks()
+        self.__snap = dict([fixup_bm(b) for b in bms])
+        self.__snap_utd = True
+        return self.__snap
+
+    @BaseStore.store_call
+    def url_delete(self, url):
+        '''Deletes a URL from storage.
+
+        @param url: The URL to delete
+        '''
+        try:
+            self.__bkend.delete_bookmark(url)
+            self.__snap_utd = False
+        except:
+            print 'DiigoStore: Failed to delete URL %s' % url
+            libepilicious.get_logger().exception('DiigoStore: Failed to delete URL %s' % url)
+
+    @BaseStore.store_call
+    def url_sync(self, url, desc, to_del, to_add):
+        if to_del or to_add:
+            snap = self.get_snapshot()
+            if snap.has_key(url):
+                tags = (Set(snap[url][1]) | to_add) - to_del
+            else:
+                tags = to_add
+            tag_str = ' '.join([t.replace(' ', self.__sr) for t in tags])
+            try:
+                self.__bkend.add_bookmark(url=url, title=desc, tags=tag_str)
+                self.__snap_utd = False
+            except:
+                print 'DiigoStore: Failed to synchronise URL %s' % url
+                libepilicious.get_logger().exception('DiigoStore: Failed to synchronise URL %s' % url)

Modified: trunk/extensions/epilicious/EpiphanyStore.py
==============================================================================
--- trunk/extensions/epilicious/EpiphanyStore.py	(original)
+++ trunk/extensions/epilicious/EpiphanyStore.py	Wed Feb 11 23:10:17 2009
@@ -25,6 +25,7 @@
 class EpiphanyStore(BaseStore):
 
     def __init__(self, keyword = None, exclude = False):
+        BaseStore.__init__(self)
         # TODO: add a check for the priority of the keyword and set it to 0, at
         # the moment it's not possible since there's no method for it
         # (ephy_node_set_property() isn't in the Python API)
@@ -81,6 +82,7 @@
 
         return res
 
+    @BaseStore.store_call
     def url_delete(self, url):
         '''Delete a bookmark.
 
@@ -94,6 +96,7 @@
             for kw in self.__bms.get_keywords().get_children():
                 self.__bms.unset_keyword(kw, bm)
 
+    @BaseStore.store_call
     def url_sync(self, url, desc, to_del, to_add):
         '''Synchronise a bookmark.
 

Modified: trunk/extensions/epilicious/Makefile.am
==============================================================================
--- trunk/extensions/epilicious/Makefile.am	(original)
+++ trunk/extensions/epilicious/Makefile.am	Wed Feb 11 23:10:17 2009
@@ -6,10 +6,12 @@
 epiliciouslib_PYTHON = \
 	BaseStore.py \
 	DeliciousStore.py \
+	DiigoStore.py \
 	EpiphanyStore.py \
 	__init__.py \
 	backend.py \
 	config.py \
+	diigo.py \
 	magnolia.py \
 	progress.py
 

Modified: trunk/extensions/epilicious/__init__.py
==============================================================================
--- trunk/extensions/epilicious/__init__.py	(original)
+++ trunk/extensions/epilicious/__init__.py	Wed Feb 11 23:10:17 2009
@@ -88,7 +88,7 @@
     @param old: The base snapshot
     @param remote: The remote snapshot
     @param local: The local snapshot
-    @param rem_store: The remote storage (L{DeliciousStore})
+    @param rem_store: The remote storage (L{BaseStore})
     @param loc_store: The local storage (L{EpiphanyStore})
     '''
     o = Set(old.keys())
@@ -178,7 +178,7 @@
     @param old: The base snapshot
     @param remote: The remote snapshot
     @param local: The local snapshot
-    @param rem_store: The remote storage (L{DeliciousStore})
+    @param rem_store: The remote storage (L{BaseStore})
     @param loc_store: The local storage (L{EpiphanyStore})
     '''
     get_logger().info('Number of urls to sync %i.' % len(curls))

Modified: trunk/extensions/epilicious/backend.py
==============================================================================
--- trunk/extensions/epilicious/backend.py	(original)
+++ trunk/extensions/epilicious/backend.py	Wed Feb 11 23:10:17 2009
@@ -16,11 +16,13 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
-from libepilicious import magnolia
+from libepilicious.DeliciousStore import DeliciousStore
+from libepilicious.DiigoStore import DiigoStore
 
-backends = { \
-        'ma.gnolia' : magnolia.Magnolia, \
+stores = { \
+        'ma.gnolia' : DeliciousStore, \
+        'diigo'     : DiigoStore, \
         }
 
-def create_backend(b):
-    return backends[b]
+def create_store(b):
+    return stores[b]

Modified: trunk/extensions/epilicious/config.py
==============================================================================
--- trunk/extensions/epilicious/config.py	(original)
+++ trunk/extensions/epilicious/config.py	Wed Feb 11 23:10:17 2009
@@ -53,7 +53,7 @@
         self.rbexclude.set_active(self.client.get_bool(libepilicious.GCONF_EXCL))
 
         # setting up of the combo box is a bit ugly, it's all about DRY
-        for k in libepilicious.backend.backends.keys():
+        for k in libepilicious.backend.stores.keys():
             self.cbbackend.append_text(k)
         self.cbbackend.remove_text(0)
         be = self.client.get_string(libepilicious.GCONF_BACK)

Added: trunk/extensions/epilicious/diigo.py
==============================================================================
--- (empty file)
+++ trunk/extensions/epilicious/diigo.py	Wed Feb 11 23:10:17 2009
@@ -0,0 +1,112 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2009 by Magnus Therning
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import httplib2
+import urllib
+import urllib2
+import simplejson
+import time
+
+# {{{1 Constants
+_REALM = 'Application'
+_BASE_URI = 'http://api2.diigo.com/'
+_API_URI = _BASE_URI
+
+class DiigoError(Exception): # {{{1
+    def __init__(self, status, msg):
+        self.status = status
+        self.msg = msg
+
+    def __str__(self):
+        return 'DiigoError(status=%(status)i,msg=%(msg)s)' % self.__dict__
+
+def _handle_http_error(function): # {{{1
+
+    def decorate(*args, **kwargs):
+        tries = 0
+        while tries < 5:
+            try:
+                return function(*args, **kwargs)
+            except DiigoError, e:
+                if e.status == 503:
+                    time.sleep(3 * tries)
+                    tries += 1
+                else:
+                    raise e
+
+    # It'd be a shame to lose the docstrings, wouldn't it?
+    decorate.__doc__ = function.__doc__
+    return decorate
+
+class Diigo2(object): # {{{1
+    '''Class for accessing Diigo's webservice API version 2.'''
+
+    def __init__(self, user_name, password):
+        self.__user = user_name
+        self.__passwd = password
+
+    @_handle_http_error
+    def __do_request(self, function, method='GET', body=None, **kwargs):
+        params = urllib.urlencode(kwargs)
+        h = httplib2.Http()
+        h.add_credentials(self.__user, self.__passwd)
+        resp, cont = h.request(_API_URI + function + '?' + params, method=method, body=body)
+        if resp['status'] != '200':
+            raise DiigoError(int(resp['status']), function)
+        return simplejson.loads(cont)
+
+    def all_bookmarks(self, users=None, filter='all'):
+        '''Retrieve all bookmarks.
+
+        @param users  : limit to specific users (defaults to yourself)
+        @param filter : all/public (default 'all')
+        '''
+        if users:
+            u = users
+        else:
+            u = self.__user
+        res = []
+        idx = 0
+        l = 100
+        while l == 100:
+            tmp = self.__do_request('bookmarks', users=u, filter=filter, start=idx, rows=100)
+            res, l, idx = res + tmp, len(tmp), idx+100
+        return res
+
+    @_handle_http_error
+    def delete_bookmark(self, url):
+        '''Delete a bookmark.
+
+        @param url : the URL
+        '''
+        return self.__do_request('bookmarks', method='DELETE', url=url)
+
+    @_handle_http_error
+    def add_bookmark(self, title, url, desc='', tags='', shared='yes'):
+        '''Add/update a bookmark.
+
+          @param url    : the URL
+          @param title  : title for the bookmark
+          @param desc   : description of the bookmark (default "")
+          @param tags   : string of tags for the URL (separated by spaces, default "")
+          @param shared : whether the URL should be shared or not (default "yes")
+        '''
+        # ToDo: diigo seems to barf on 'dangerous' URLs like file://, come up
+        # with a way to deal with it!
+        return self.__do_request('bookmarks', method='POST', body=' ', \
+                title=title, url=url, desc=desc, tags=tags, shared=shared)

Modified: trunk/extensions/epilicious/epilicious.py.in
==============================================================================
--- trunk/extensions/epilicious/epilicious.py.in	(original)
+++ trunk/extensions/epilicious/epilicious.py.in	Wed Feb 11 23:10:17 2009
@@ -55,7 +55,7 @@
 
     def __init__(self):
         self.username = ''
-        self.password = ''
+        self.__password = ''
         self.backend = ''
         self.keyword = ''
         self.exclude = False
@@ -86,7 +86,7 @@
 
     def _new_pwd(self, client, *args, **kwargs):
         '''Callback to handle the password is modified in GConf.'''
-        self.password = client.get_string(libepilicious.GCONF_PWD)
+        self.__password = client.get_string(libepilicious.GCONF_PWD)
 
     def _new_backend(self, client, *args, **kargs):
         '''Callback to handle when the backend is modified in GConf.'''
@@ -102,6 +102,11 @@
         '''Callback to handle the exclude is modified in GConf.'''
         self.exclude = client.get_bool(libepilicious.GCONF_EXCL)
 
+    # {{{2 Retrieve password out of keyring (in the future)
+    def _get_password(self):
+        # silly implementation for now
+        return self.__password
+
     # {{{2 Synchronisation
     def _CB_Sync(self, action, window):
         # Using gobject.idle_add() together with a generator is much easier than
@@ -134,15 +139,14 @@
         from libepilicious.progress import ProgressBar
         pbar = ProgressBar()
         try:
-            from libepilicious.DeliciousStore import DeliciousStore
+            from libepilicious.backend import create_store
             from libepilicious.EpiphanyStore import EpiphanyStore
 
             pbar.show()
             stepper = pbar.step()
 
-            remote_store = DeliciousStore(user=self.username, \
-                    pwd=self.password, space_repl=self.space_repl, \
-                    backend=self.backend)
+            remote_store = create_store(self.backend)(user=self.username, \
+                    pwd=self._get_password(), space_repl=self.space_repl)
             local_store = EpiphanyStore(keyword=self.keyword, \
                     exclude=self.exclude)
             stepper.next()
@@ -167,7 +171,20 @@
                     local = local,
                     rem_store = remote_store,
                     loc_store = local_store)
-            yield True
+            actions = remote_store.perform_calls()
+            try:
+                while 1:
+                    actions.next()
+                    yield True
+            except StopIteration, e:
+                pass
+            actions = local_store.perform_calls()
+            try:
+                while 1:
+                    actions.next()
+                    yield True
+            except StopIteration, e:
+                pass
             stepper.next()
 
             changed_urls = libepilicious.find_changed_urls(old = old,
@@ -180,7 +197,20 @@
                     local = local,
                     rem_store = remote_store,
                     loc_store = local_store)
-            yield True
+            actions = remote_store.perform_calls()
+            try:
+                while 1:
+                    actions.next()
+                    yield True
+            except StopIteration, e:
+                pass
+            actions = local_store.perform_calls()
+            try:
+                while 1:
+                    actions.next()
+                    yield True
+            except StopIteration, e:
+                pass
             stepper.next()
 
             # Save the current state for future sync

Modified: trunk/extensions/epilicious/epilicious.schemas.in
==============================================================================
--- trunk/extensions/epilicious/epilicious.schemas.in	(original)
+++ trunk/extensions/epilicious/epilicious.schemas.in	Wed Feb 11 23:10:17 2009
@@ -36,7 +36,7 @@
       <locale name="C">
         <short>epilicious backend</short>
         <long>The backend that epilicious will use for synchronizing
-          bookmarks. Allowed value: 'ma.gnolia'</long>
+          bookmarks. Allowed values: 'diigo', 'ma.gnolia'</long>
       </locale>
     </schema>
 

Modified: trunk/extensions/epilicious/magnolia.py
==============================================================================
--- trunk/extensions/epilicious/magnolia.py	(original)
+++ trunk/extensions/epilicious/magnolia.py	Wed Feb 11 23:10:17 2009
@@ -147,7 +147,7 @@
         replace: whether an already existing bookmark should be replaced ("yes"
                  or "no", default "yes")
         shared: whether the URL should be shared or not ("yes" or "no", default
-                 "no")
+                 "yes")
 
         Returns 1 on success, 0 on failure.
         '''



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]