summaryrefslogtreecommitdiff
path: root/client/src/leap
diff options
context:
space:
mode:
authorKali Kaneko <kali@leap.se>2015-06-05 11:21:17 -0400
committerKali Kaneko <kali@leap.se>2015-06-15 14:24:20 -0400
commitc0c9769f56814fe570c007b7153f60faebea4881 (patch)
tree947e11e71e965cc429f5473e5828fc550281106c /client/src/leap
parent20a76a36f8dce0ebd0fe63690633c0e77727f2c4 (diff)
[feature] add post-sync hooks using twisted plugins
implementing a generic plugin interface to allow other modules to react to soledad syncs, receiving a list of document ids that they've subscribed to. - Resolves: #6996 - Releases: 0.7.1
Diffstat (limited to 'client/src/leap')
-rw-r--r--client/src/leap/soledad/client/adbapi.py1
-rw-r--r--client/src/leap/soledad/client/api.py37
-rw-r--r--client/src/leap/soledad/client/interfaces.py31
-rw-r--r--client/src/leap/soledad/client/sqlcipher.py13
-rw-r--r--client/src/leap/soledad/client/sync.py16
5 files changed, 97 insertions, 1 deletions
diff --git a/client/src/leap/soledad/client/adbapi.py b/client/src/leap/soledad/client/adbapi.py
index 5b882bbe..4f75695f 100644
--- a/client/src/leap/soledad/client/adbapi.py
+++ b/client/src/leap/soledad/client/adbapi.py
@@ -217,6 +217,7 @@ class U1DBConnectionPool(adbapi.ConnectionPool):
"""
meth = getattr(trans, meth)
return meth(*args, **kw)
+ # XXX should return a fetchall?
def _runInteraction(self, interaction, *args, **kw):
"""
diff --git a/client/src/leap/soledad/client/api.py b/client/src/leap/soledad/client/api.py
index 76d6acc3..63b1dfc0 100644
--- a/client/src/leap/soledad/client/api.py
+++ b/client/src/leap/soledad/client/api.py
@@ -42,6 +42,7 @@ except ImportError:
from StringIO import StringIO
from u1db.remote import http_client
from u1db.remote.ssl_match_hostname import match_hostname
+from twisted.plugin import getPlugins
from zope.interface import implements
from leap.common.config import get_path_prefix
@@ -70,6 +71,28 @@ Soledad client and server.
"""
SOLEDAD_CERT = None
+# A whitelist of modules from where to collect plugins dynamically.
+# For the moment restricted to leap namespace, but the idea is that we can pass
+# other "trusted" modules as options to the initialization of soledad.
+PLUGGABLE_LEAP_MODULES = ('mail', 'keymanager')
+
+
+# TODO move to leap.common
+
+def collect_plugins(interface):
+ """
+ Traverse a whitelist of modules and collect all the plugins that implement
+ the passed interface.
+ """
+ plugins = []
+ for namespace in PLUGGABLE_LEAP_MODULES:
+ try:
+ module = __import__('leap.%s.plugins' % namespace, fromlist='.')
+ plugins = plugins + list(getPlugins(interface, module))
+ except ImportError:
+ pass
+ return plugins
+
class Soledad(object):
"""
@@ -656,6 +679,20 @@ class Soledad(object):
defer_decryption=defer_decryption)
def _sync_callback(local_gen):
+ self._last_received_docs = self._dbsyncer.received_docs
+ print "***"
+ print "LAST RECEIVED (API)", self._last_received_docs
+ print "***"
+ received_doc_ids = self._dbsyncer.received_docs
+
+ # Post-Sync Hooks
+ synced_plugin = soledad_interfaces.ISoledadPostSyncPlugin
+ if received_doc_ids:
+ suitable_plugins = collect_plugins(synced_plugin)
+ for plugin in suitable_plugins:
+ # TODO filter the doc_ids here
+ plugin.process_received_docs(received_doc_ids)
+
soledad_events.emit(
soledad_events.SOLEDAD_DONE_DATA_SYNC, self.uuid)
return local_gen
diff --git a/client/src/leap/soledad/client/interfaces.py b/client/src/leap/soledad/client/interfaces.py
index 4f7b0779..14b34d24 100644
--- a/client/src/leap/soledad/client/interfaces.py
+++ b/client/src/leap/soledad/client/interfaces.py
@@ -19,6 +19,37 @@ Interfaces used by the Soledad Client.
"""
from zope.interface import Interface, Attribute
+#
+# Plugins
+#
+
+
+class ISoledadPostSyncPlugin(Interface):
+ """
+ I implement the minimal methods and attributes for a plugin that can be
+ called after a soledad synchronization has ended.
+ """
+
+ def process_received_docs(self, doc_id_list):
+ """
+ Do something with the passed list of doc_ids received after the last
+ sync.
+
+ :param doc_id_list: a list of strings for the received doc_ids
+ """
+
+ watched_doc_types = Attribute("""
+ a tuple of the watched doc types for this plugin. So far, the
+ `doc-types` convention is just the preffix of the doc_id, which is
+ basically its first character, followed by a dash. So, for instance,
+ `M-` is used for meta-docs in mail, and `F-` is used for flag-docs in
+ mail. For now there's no central register of all the doc-types
+ used.""")
+
+
+#
+# Soledad storage
+#
class ILocalStorage(Interface):
"""
diff --git a/client/src/leap/soledad/client/sqlcipher.py b/client/src/leap/soledad/client/sqlcipher.py
index b2025130..75d786a6 100644
--- a/client/src/leap/soledad/client/sqlcipher.py
+++ b/client/src/leap/soledad/client/sqlcipher.py
@@ -456,6 +456,9 @@ class SQLCipherU1DBSync(SQLCipherDatabase):
self._syncers = {}
+ # Storage for the documents received during a sync
+ self.received_docs = []
+
self.running = False
self._sync_threadpool = None
self._initialize_sync_threadpool()
@@ -587,8 +590,16 @@ class SQLCipherU1DBSync(SQLCipherDatabase):
# the following context manager blocks until the syncing lock can be
# acquired.
with self._syncer(url, creds=creds) as syncer:
+
+ def _record_received_docs(result):
+ # beware, closure. syncer is in scope.
+ self.received_docs = syncer.received_docs
+ return result
+
# XXX could mark the critical section here...
- return syncer.sync(defer_decryption=defer_decryption)
+ d = syncer.sync(defer_decryption=defer_decryption)
+ d.addCallback(_record_received_docs)
+ return d
@contextmanager
def _syncer(self, url, creds=None):
diff --git a/client/src/leap/soledad/client/sync.py b/client/src/leap/soledad/client/sync.py
index 53172f31..917c21ea 100644
--- a/client/src/leap/soledad/client/sync.py
+++ b/client/src/leap/soledad/client/sync.py
@@ -39,6 +39,7 @@ class SoledadSynchronizer(Synchronizer):
Also modified to allow for interrupting the synchronization process.
"""
+ received_docs = []
@defer.inlineCallbacks
def sync(self, defer_decryption=True):
@@ -62,6 +63,7 @@ class SoledadSynchronizer(Synchronizer):
:rtype: twisted.internet.defer.Deferred
"""
sync_target = self.sync_target
+ self.received_docs = []
# get target identifier, its current generation,
# and its last-seen database generation for this source
@@ -123,12 +125,14 @@ class SoledadSynchronizer(Synchronizer):
changed_doc_ids = [doc_id for doc_id, _, _ in changes]
docs_to_send = self.source.get_docs(
changed_doc_ids, check_for_conflicts=False, include_deleted=True)
+ ids_sent = []
docs_by_generation = []
idx = 0
for doc in docs_to_send:
_, gen, trans = changes[idx]
docs_by_generation.append((doc, gen, trans))
idx += 1
+ ids_sent.append(doc.doc_id)
# exchange documents and try to insert the returned ones with
# the target, return target synced-up-to gen.
@@ -151,6 +155,18 @@ class SoledadSynchronizer(Synchronizer):
self._syncing_info = info
yield self.complete_sync()
+ _, _, changes = self.source.whats_changed(target_my_gen)
+ changed_doc_ids = [doc_id for doc_id, _, _ in changes]
+
+ print "--------------------------"
+ print "SENT", ids_sent
+ print "CHANGED_DOC_IDS", changed_doc_ids
+
+ just_received = list(set(changed_doc_ids) - set(ids_sent))
+ print "RECEIVED:", just_received
+ print "--------------------------"
+
+ self.received_docs = just_received
defer.returnValue(my_gen)
def complete_sync(self):