From 002d2bfdbc4ca62733478524ec588cf0aa9f9383 Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 10 Dec 2012 18:39:56 -0200 Subject: CouchDB backend can put and get objects. --- src/leap/soledad/backends/couch.py | 115 +++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 src/leap/soledad/backends/couch.py (limited to 'src/leap/soledad/backends/couch.py') diff --git a/src/leap/soledad/backends/couch.py b/src/leap/soledad/backends/couch.py new file mode 100644 index 00000000..5586ea9c --- /dev/null +++ b/src/leap/soledad/backends/couch.py @@ -0,0 +1,115 @@ +from u1db import errors +from u1db.remote.http_target import HTTPSyncTarget +from couchdb.client import Server, Document +from couchdb.http import ResourceNotFound +from soledad.backends.objectstore import ObjectStore +from soledad.backends.leap import LeapDocument + + +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 = 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. + self.set_document_factory(LeapDocument) + try: + self._database = self._server[database] + except ResourceNotFound: + self._server.create(database) + self._database = self._server[database] + super(CouchDatabase, self).__init__() + + #------------------------------------------------------------------------- + # 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 None: + return None + content = {} + for (key, value) in cdoc.items(): + if key not 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): + # map u1db metadata to couch + content = doc.content + cdoc = Document() + cdoc['_id'] = doc.doc_id + cdoc['u1db_rev'] = doc.rev + for (key, value) in content.items(): + cdoc[key] = value + self._database.save(cdoc) + + 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) + + def _get_u1db_data(self): + cdoc = self._database.get(self.U1DB_DATA_DOC_ID) + self._sync_log.log = cdoc['sync_log'] + self._transaction_log.log = cdoc['transaction_log'] + self._replica_uid = cdoc['replica_uid'] + self._couch_rev = cdoc['_rev'] + + #------------------------------------------------------------------------- + # 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) + + -- cgit v1.2.3 From 703224c26e868546d37e9850db75747df1f92348 Mon Sep 17 00:00:00 2001 From: drebs Date: Tue, 11 Dec 2012 11:47:38 -0200 Subject: Store u1db contents in couch as json string. --- src/leap/soledad/backends/couch.py | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) (limited to 'src/leap/soledad/backends/couch.py') diff --git a/src/leap/soledad/backends/couch.py b/src/leap/soledad/backends/couch.py index 5586ea9c..ed356fdd 100644 --- a/src/leap/soledad/backends/couch.py +++ b/src/leap/soledad/backends/couch.py @@ -5,6 +5,11 @@ from couchdb.http import ResourceNotFound from soledad.backends.objectstore import ObjectStore from soledad.backends.leap import LeapDocument +try: + import simplejson as json +except ImportError: + import json # noqa + class CouchDatabase(ObjectStore): """A U1DB implementation that uses Couch as its persistence layer.""" @@ -40,12 +45,11 @@ class CouchDatabase(ObjectStore): cdoc = self._database.get(doc_id) if cdoc is None: return None - content = {} - for (key, value) in cdoc.items(): - if key not in ['_id', '_rev', 'u1db_rev']: - content[key] = value doc = self._factory(doc_id=doc_id, rev=cdoc['u1db_rev']) - doc.content = content + if cdoc['u1db_json'] is not None: + doc.content = json.loads(cdoc['u1db_json']) + else: + doc.make_tombstone() return doc def get_all_docs(self, include_deleted=False): @@ -60,13 +64,20 @@ class CouchDatabase(ObjectStore): return (generation, results) def _put_doc(self, doc): - # map u1db metadata to couch - content = doc.content + # prepare couch's Document cdoc = Document() cdoc['_id'] = doc.doc_id + # we have to guarantee that couch's _rev is cosistent + old_cdoc = self._database.get(doc.doc_id) + if old_cdoc is not None: + cdoc['_rev'] = old_cdoc['_rev'] + # store u1db's rev cdoc['u1db_rev'] = doc.rev - for (key, value) in content.items(): - cdoc[key] = value + # store u1db's content as json string + if not doc.is_tombstone(): + cdoc['u1db_json'] = doc.get_json() + else: + cdoc['u1db_json'] = None self._database.save(cdoc) def get_sync_target(self): @@ -83,9 +94,10 @@ class CouchDatabase(ObjectStore): def _get_u1db_data(self): cdoc = self._database.get(self.U1DB_DATA_DOC_ID) - self._sync_log.log = cdoc['sync_log'] - self._transaction_log.log = cdoc['transaction_log'] - self._replica_uid = cdoc['replica_uid'] + content = json.loads(cdoc['u1db_json']) + self._sync_log.log = content['sync_log'] + self._transaction_log.log = content['transaction_log'] + self._replica_uid = content['replica_uid'] self._couch_rev = cdoc['_rev'] #------------------------------------------------------------------------- -- cgit v1.2.3