diff options
| author | drebs <drebs@leap.se> | 2012-12-10 12:05:31 -0200 | 
|---|---|---|
| committer | drebs <drebs@leap.se> | 2012-12-10 12:05:31 -0200 | 
| commit | ddd41a0c4bbe70cfca3827b93ac7f35b28c49400 (patch) | |
| tree | b06d3aae4ea943ecbb6ef5e4dce937b64bd9cc6c /backends | |
| parent | ded8b721def13751fe0b27e50ab1a3cb999fb292 (diff) | |
Add CouchDB u1db backend.
Diffstat (limited to 'backends')
| -rw-r--r-- | backends/couchdb.py | 97 | ||||
| -rw-r--r-- | backends/objectstore.py | 26 | ||||
| -rw-r--r-- | backends/openstack.py | 20 | 
3 files changed, 128 insertions, 15 deletions
diff --git a/backends/couchdb.py b/backends/couchdb.py new file mode 100644 index 00000000..89b713f9 --- /dev/null +++ b/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/backends/objectstore.py b/backends/objectstore.py index e36df72d..456892b3 100644 --- a/backends/objectstore.py +++ b/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/backends/openstack.py b/backends/openstack.py index f8563d81..5f2a2771 100644 --- a/backends/openstack.py +++ b/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)  | 
