summaryrefslogtreecommitdiff
path: root/src/leap/soledad/backends/couch.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/leap/soledad/backends/couch.py')
-rw-r--r--src/leap/soledad/backends/couch.py75
1 files changed, 58 insertions, 17 deletions
diff --git a/src/leap/soledad/backends/couch.py b/src/leap/soledad/backends/couch.py
index a3909596..78026af8 100644
--- a/src/leap/soledad/backends/couch.py
+++ b/src/leap/soledad/backends/couch.py
@@ -1,8 +1,10 @@
+import sys
+import uuid
+from base64 import b64encode, b64decode
from u1db import errors
-from u1db.remote.http_target import HTTPSyncTarget
-from couchdb.client import Server, Document
+from u1db.sync import LocalSyncTarget
+from couchdb.client import Server, Document as CouchDocument
from couchdb.http import ResourceNotFound
-
from leap.soledad.backends.objectstore import ObjectStore
from leap.soledad.backends.leap_backend import LeapDocument
@@ -15,7 +17,7 @@ except ImportError:
class CouchDatabase(ObjectStore):
"""A U1DB implementation that uses Couch as its persistence layer."""
- def __init__(self, url, database, full_commit=True, session=None):
+ def __init__(self, url, database, replica_uid=None, full_commit=True, session=None):
"""Create a new Couch data container."""
self._url = url
self._full_commit = full_commit
@@ -23,6 +25,7 @@ class CouchDatabase(ObjectStore):
self._server = Server(url=self._url,
full_commit=self._full_commit,
session=self._session)
+ self._dbname = database
# this will ensure that transaction and sync logs exist and are
# up-to-date.
self.set_document_factory(LeapDocument)
@@ -31,22 +34,26 @@ class CouchDatabase(ObjectStore):
except ResourceNotFound:
self._server.create(database)
self._database = self._server[database]
- super(CouchDatabase, self).__init__()
+ super(CouchDatabase, self).__init__(replica_uid=replica_uid)
#-------------------------------------------------------------------------
# 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.
+ """
+ Get just the document content, without fancy handling.
"""
cdoc = self._database.get(doc_id)
if cdoc is None:
return None
- doc = self._factory(doc_id=doc_id, rev=cdoc['u1db_rev'])
+ has_conflicts = False
+ if check_for_conflicts:
+ has_conflicts = self._has_conflicts(doc_id)
+ doc = self._factory(
+ doc_id=doc_id,
+ rev=cdoc['u1db_rev'],
+ has_conflicts=has_conflicts)
if cdoc['u1db_json'] is not None:
doc.content = json.loads(cdoc['u1db_json'])
else:
@@ -58,7 +65,9 @@ class CouchDatabase(ObjectStore):
generation = self._get_generation()
results = []
for doc_id in self._database:
- doc = self._get_doc(doc_id)
+ if doc_id == self.U1DB_DATA_DOC_ID:
+ continue
+ doc = self._get_doc(doc_id, check_for_conflicts=True)
if doc.content is None and not include_deleted:
continue
results.append(doc)
@@ -66,7 +75,7 @@ class CouchDatabase(ObjectStore):
def _put_doc(self, doc):
# prepare couch's Document
- cdoc = Document()
+ cdoc = CouchDocument()
cdoc['_id'] = doc.doc_id
# we have to guarantee that couch's _rev is cosistent
old_cdoc = self._database.get(doc.doc_id)
@@ -79,35 +88,68 @@ class CouchDatabase(ObjectStore):
cdoc['u1db_json'] = doc.get_json()
else:
cdoc['u1db_json'] = None
+ # save doc in db
self._database.save(cdoc)
def get_sync_target(self):
return CouchSyncTarget(self)
def close(self):
- raise NotImplementedError(self.close)
+ # TODO: fix this method so the connection is properly closed and
+ # test_close (+tearDown, which deletes the db) works without problems.
+ self._url = None
+ self._full_commit = None
+ self._session = None
+ #self._server = None
+ self._database = None
+ return True
+
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)
+ def _initialize(self):
+ if self._replica_uid is None:
+ self._replica_uid = uuid.uuid4().hex
+ doc = self._factory(doc_id=self.U1DB_DATA_DOC_ID)
+ doc.content = { 'sync_log' : [],
+ 'transaction_log' : [],
+ 'conflict_log' : b64encode(json.dumps([])),
+ 'replica_uid' : self._replica_uid }
+ self._put_doc(doc)
+
def _get_u1db_data(self):
cdoc = self._database.get(self.U1DB_DATA_DOC_ID)
content = json.loads(cdoc['u1db_json'])
self._sync_log.log = content['sync_log']
self._transaction_log.log = content['transaction_log']
+ self._conflict_log.log = json.loads(b64decode(content['conflict_log']))
self._replica_uid = content['replica_uid']
self._couch_rev = cdoc['_rev']
+ def _set_u1db_data(self):
+ doc = self._factory(doc_id=self.U1DB_DATA_DOC_ID)
+ doc.content = { 'sync_log' : self._sync_log.log,
+ 'transaction_log' : self._transaction_log.log,
+ # Here, the b64 encode ensures that document content
+ # does not cause strange behaviour in couchdb because
+ # of encoding.
+ 'conflict_log' : b64encode(json.dumps(self._conflict_log.log)),
+ 'replica_uid' : self._replica_uid,
+ '_rev' : self._couch_rev}
+ self._put_doc(doc)
+
#-------------------------------------------------------------------------
# Couch specific methods
#-------------------------------------------------------------------------
- # no specific methods so far.
+ def delete_database(self):
+ del(self._server[self._dbname])
-class CouchSyncTarget(HTTPSyncTarget):
+
+class CouchSyncTarget(LocalSyncTarget):
def get_sync_info(self, source_replica_uid):
source_gen, source_trans_id = self._db._get_replica_gen_and_trans_id(
@@ -125,4 +167,3 @@ class CouchSyncTarget(HTTPSyncTarget):
source_replica_uid, source_replica_generation,
source_replica_transaction_id)
-