summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordrebs <drebs@leap.se>2012-12-10 12:05:31 -0200
committerdrebs <drebs@leap.se>2012-12-10 12:05:31 -0200
commitb3090f710e3777bad2a9f996444e5099883c9f03 (patch)
treeb129323e5277ac2fc854f8f58a408dc092e6c128
parentf89f2e0fe490899ecc4baf3395f3441111da328f (diff)
Add CouchDB u1db backend.
-rw-r--r--src/leap/soledad/README2
-rw-r--r--src/leap/soledad/__init__.py5
-rw-r--r--src/leap/soledad/backends/couchdb.py97
-rw-r--r--src/leap/soledad/backends/objectstore.py26
-rw-r--r--src/leap/soledad/backends/openstack.py20
5 files changed, 131 insertions, 19 deletions
diff --git a/src/leap/soledad/README b/src/leap/soledad/README
index 894ce6af..97976b01 100644
--- a/src/leap/soledad/README
+++ b/src/leap/soledad/README
@@ -11,7 +11,9 @@ Soledad depends on the following python libraries:
* u1db 0.1.4 [1]
* python-swiftclient 1.2.0 [2]
* python-gnupg 0.3.1 [3]
+ * CouchDB 0.8 [4]
[1] http://pypi.python.org/pypi/u1db/0.1.4
[2] http://pypi.python.org/pypi/python-swiftclient/1.2.0
[3] http://pypi.python.org/pypi/python-gnupg/0.3.1
+[4] http://pypi.python.org/pypi/CouchDB/0.8
diff --git a/src/leap/soledad/__init__.py b/src/leap/soledad/__init__.py
index 78f1f768..d07567b5 100644
--- a/src/leap/soledad/__init__.py
+++ b/src/leap/soledad/__init__.py
@@ -1,9 +1,6 @@
# License?
-"""A U1DB implementation that uses OpenStack Swift as its persistence layer."""
-
-from backends.leap import *
-from backends.openstack import *
+"""A U1DB implementation for using Object Stores as its persistence layer."""
import gnupg
diff --git a/src/leap/soledad/backends/couchdb.py b/src/leap/soledad/backends/couchdb.py
new file mode 100644
index 00000000..89b713f9
--- /dev/null
+++ b/src/leap/soledad/backends/couchdb.py
@@ -0,0 +1,97 @@
+from u1db import errors
+from u1db.remote.http_target import HTTPSyncTarget
+from couchdb import *
+from soledad.backends.objectstore import ObjectStore
+
+
+class CouchDatabase(ObjectStore):
+ """A U1DB implementation that uses Couch as its persistence layer."""
+
+ def __init__(self, url, database, full_commit=True, session=None):
+ """Create a new Couch data container."""
+ self._url = url
+ self._full_commit = full_commit
+ self._session = session
+ self._server = couchdb.Server(url=self._url,
+ full_commit=self._full_commit,
+ session=self._session)
+ # this will ensure that transaction and sync logs exist and are
+ # up-to-date.
+ super(CouchDatabase, self)
+ self._database = self._server[database]
+
+ #-------------------------------------------------------------------------
+ # implemented methods from Database
+ #-------------------------------------------------------------------------
+
+ def _get_doc(self, doc_id, check_for_conflicts=False):
+ """Get just the document content, without fancy handling.
+
+ Conflicts do not happen on server side, so there's no need to check
+ for them.
+ """
+ cdoc = self._database.get(doc_id)
+ if cdoc is not None:
+ content = {}
+ for key, value in content:
+ if not key in ['_id', '_rev', '_u1db_rev']:
+ content[key] = value
+ doc = self._factory(doc_id=doc_id, rev=cdoc['_u1db_rev'])
+ doc.content = content
+ return doc
+
+ def get_all_docs(self, include_deleted=False):
+ """Get all documents from the database."""
+ generation = self._get_generation()
+ results = []
+ for doc_id in self._database:
+ doc = self._get_doc(doc_id)
+ if doc.content is None and not include_deleted:
+ continue
+ results.append(doc)
+ return (generation, results)
+
+ def _put_doc(self, doc, new_rev):
+ # map u1db metadata to couch
+ content = doc.content
+ content['_id'] = doc.doc_id
+ content['_u1db_rev'] = new_rev
+ self._database.save(doc.content)
+
+ def get_sync_target(self):
+ return CouchSyncTarget(self)
+
+ def close(self):
+ raise NotImplementedError(self.close)
+
+ def sync(self, url, creds=None, autocreate=True):
+ from u1db.sync import Synchronizer
+ from u1db.remote.http_target import CouchSyncTarget
+ return Synchronizer(self, CouchSyncTarget(url, creds=creds)).sync(
+ autocreate=autocreate)
+
+ #-------------------------------------------------------------------------
+ # Couch specific methods
+ #-------------------------------------------------------------------------
+
+ # no specific methods so far.
+
+class CouchSyncTarget(HTTPSyncTarget):
+
+ def get_sync_info(self, source_replica_uid):
+ source_gen, source_trans_id = self._db._get_replica_gen_and_trans_id(
+ source_replica_uid)
+ my_gen, my_trans_id = self._db._get_generation_info()
+ return (
+ self._db._replica_uid, my_gen, my_trans_id, source_gen,
+ source_trans_id)
+
+ def record_sync_info(self, source_replica_uid, source_replica_generation,
+ source_replica_transaction_id):
+ if self._trace_hook:
+ self._trace_hook('record_sync_info')
+ self._db._set_replica_gen_and_trans_id(
+ source_replica_uid, source_replica_generation,
+ source_replica_transaction_id)
+
+
diff --git a/src/leap/soledad/backends/objectstore.py b/src/leap/soledad/backends/objectstore.py
index e36df72d..456892b3 100644
--- a/src/leap/soledad/backends/objectstore.py
+++ b/src/leap/soledad/backends/objectstore.py
@@ -1,11 +1,17 @@
from u1db.backends import CommonBackend
+from soledad import SyncLog, TransactionLog
class ObjectStore(CommonBackend):
def __init__(self):
+ # This initialization method should be called after the connection
+ # with the database is established, so it can ensure that u1db data is
+ # configured and up-to-date.
+ self.set_document_factory(LeapDocument)
self._sync_log = SyncLog()
self._transaction_log = TransactionLog()
+ self._ensure_u1db_data()
#-------------------------------------------------------------------------
# implemented methods from Database
@@ -29,6 +35,26 @@ class ObjectStore(CommonBackend):
return None
return doc
+ def _put_doc(self, doc)
+ raise NotImplementedError(self._put_doc)
+
+ def put_doc(self, doc)
+ # consistency check
+ if doc.doc_id is None:
+ raise errors.InvalidDocId()
+ self._check_doc_id(doc.doc_id)
+ self._check_doc_size(doc)
+ # put the document
+ new_rev = self._allocate_doc_rev(doc.rev)
+ self._put_doc(doc, new_rev)
+ doc.rev = new_rev
+ # update u1db generation and logs
+ new_gen = self._get_generation() + 1
+ trans_id = self._allocate_transaction_id()
+ self._transaction_log.append((new_gen, doc.doc_id, trans_id))
+ self._set_u1db_data()
+ return new_rev
+
def delete_doc(self, doc):
old_doc = self._get_doc(doc.doc_id, check_for_conflicts=True)
if old_doc is None:
diff --git a/src/leap/soledad/backends/openstack.py b/src/leap/soledad/backends/openstack.py
index f8563d81..5f2a2771 100644
--- a/src/leap/soledad/backends/openstack.py
+++ b/src/leap/soledad/backends/openstack.py
@@ -1,5 +1,4 @@
from u1db import errors
-from u1db.backends import CommonBackend
from u1db.remote.http_target import HTTPSyncTarget
from swiftclient import client
from soledad.backends.objectstore import ObjectStore
@@ -10,16 +9,15 @@ class OpenStackDatabase(ObjectStore):
def __init__(self, auth_url, user, auth_key, container):
"""Create a new OpenStack data container."""
- super(OpenStackDatabase, self)
self._auth_url = auth_url
self._user = user
self._auth_key = auth_key
self._container = container
- self.set_document_factory(LeapDocument)
self._connection = swiftclient.Connection(self._auth_url, self._user,
self._auth_key)
self._get_auth()
- self._ensure_u1db_data()
+ # this will ensure transaction and sync logs exist and are up-to-date.
+ super(OpenStackDatabase, self)
#-------------------------------------------------------------------------
# implemented methods from Database
@@ -33,6 +31,7 @@ class OpenStackDatabase(ObjectStore):
"""
try:
response, contents = self._connection.get_object(self._container, doc_id)
+ # TODO: change revision to be a dictionary element?
rev = response['x-object-meta-rev']
return self._factory(doc_id, rev, contents)
except swiftclient.ClientException:
@@ -51,21 +50,12 @@ class OpenStackDatabase(ObjectStore):
results.append(doc)
return (generation, results)
- def put_doc(self, doc):
- if doc.doc_id is None:
- raise errors.InvalidDocId()
- self._check_doc_id(doc.doc_id)
- self._check_doc_size(doc)
- # TODO: check for conflicts?
+ def _put_doc(self, doc, new_rev):
new_rev = self._allocate_doc_rev(doc.rev)
+ # TODO: change revision to be a dictionary element?
headers = { 'X-Object-Meta-Rev' : new_rev }
self._connection.put_object(self._container, doc_id, doc.get_json(),
headers=headers)
- new_gen = self._get_generation() + 1
- trans_id = self._allocate_transaction_id()
- self._transaction_log.append((new_gen, doc.doc_id, trans_id))
- self._set_u1db_data()
- return new_rev
def get_sync_target(self):
return OpenStackSyncTarget(self)