diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/leap/soledad/backends/couch.py | 8 | ||||
| -rw-r--r-- | src/leap/soledad/backends/objectstore.py | 69 | ||||
| -rw-r--r-- | src/leap/soledad/tests/test_couch.py | 83 | ||||
| -rw-r--r-- | src/leap/soledad/tests/test_logs.py | 28 | ||||
| -rw-r--r-- | src/leap/soledad/util.py | 17 | 
5 files changed, 172 insertions, 33 deletions
| diff --git a/src/leap/soledad/backends/couch.py b/src/leap/soledad/backends/couch.py index 8603a36b..14021737 100644 --- a/src/leap/soledad/backends/couch.py +++ b/src/leap/soledad/backends/couch.py @@ -58,6 +58,8 @@ class CouchDatabase(ObjectStore):          generation = self._get_generation()          results = []          for doc_id in self._database: +            if doc_id == self.U1DB_DATA_DOC_ID: +                continue              doc = self._get_doc(doc_id)              if doc.content is None and not include_deleted:                  continue @@ -106,13 +108,15 @@ class CouchDatabase(ObjectStore):          content = json.loads(cdoc['u1db_json'])          self._sync_log.log = content['sync_log']          self._transaction_log.log = content['transaction_log'] +        self._conflict_log.log = 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 = { 'transaction_log' : self._transaction_log.log, -                        'sync_log'        : self._sync_log.log, +        doc.content = { 'sync_log'        : self._sync_log.log, +                        'transaction_log' : self._transaction_log.log, +                        'conflict_log'    : self._conflict_log.log,                          'replica_uid'     : self._replica_uid,                          '_rev'            : self._couch_rev}          self._put_doc(doc) diff --git a/src/leap/soledad/backends/objectstore.py b/src/leap/soledad/backends/objectstore.py index 54ffa9dd..cd051588 100644 --- a/src/leap/soledad/backends/objectstore.py +++ b/src/leap/soledad/backends/objectstore.py @@ -16,6 +16,7 @@ class ObjectStore(CommonBackend):          self.set_document_factory(Document)          self._sync_log = soledadutil.SyncLog()          self._transaction_log = soledadutil.TransactionLog() +        self._conflict_log = soledadutil.ConflictLog()          self._replica_uid = replica_uid          self._ensure_u1db_data() @@ -44,6 +45,12 @@ class ObjectStore(CommonBackend):      def _put_doc(self, doc):          raise NotImplementedError(self._put_doc) +    def _update_gen_and_transaction_log(self, doc_id): +        new_gen = self._get_generation() + 1 +        trans_id = self._allocate_transaction_id() +        self._transaction_log.append((new_gen, doc_id, trans_id)) +        self._set_u1db_data() +      def put_doc(self, doc):          # consistency check          if doc.doc_id is None: @@ -66,11 +73,7 @@ class ObjectStore(CommonBackend):              new_rev = self._allocate_doc_rev(doc.rev)          doc.rev = new_rev          self._put_doc(doc) -        # 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() +        self._update_gen_and_transaction_log(doc.doc_id)          return doc.rev      def delete_doc(self, doc): @@ -87,6 +90,7 @@ class ObjectStore(CommonBackend):          doc.rev = new_rev          doc.make_tombstone()          self._put_doc(doc) +        self._update_gen_and_transaction_log(doc.doc_id)          return new_rev      # start of index-related methods: these are not supported by this backend. @@ -130,6 +134,16 @@ class ObjectStore(CommonBackend):                                                      other_transaction_id)          self._set_u1db_data() +    def _do_set_replica_gen_and_trans_id(self, other_replica_uid, +                                         other_generation, other_transaction_id): +        return self._set_replica_gen_and_trans_id( +                 other_replica_uid, +                 other_generation, +                 other_transaction_id) + +    def _get_transaction_log(self): +        return self._transaction_log.get_transaction_log() +      #-------------------------------------------------------------------------      # implemented methods from CommonBackend      #------------------------------------------------------------------------- @@ -146,9 +160,10 @@ class ObjectStore(CommonBackend):          # Documents never have conflicts on server.          return False -    def _put_and_update_indexes(self, doc_id, old_doc, new_rev, content): -        raise NotImplementedError(self._put_and_update_indexes) - +    def _put_and_update_indexes(self, old_doc, doc): +        # TODO: implement index update +        self._put_doc(doc) +        self._update_gen_and_transaction_log(doc.doc_id)      def _get_trans_id_for_gen(self, generation):          self._get_u1db_data() @@ -187,8 +202,9 @@ class ObjectStore(CommonBackend):          if self._replica_uid is None:              self._replica_uid = uuid.uuid4().hex          doc = self._factory(doc_id=self.U1DB_DATA_DOC_ID) -        doc.content = { 'transaction_log' : [], -                        'sync_log' : [], +        doc.content = { 'sync_log' : [], +                        'transaction_log' : [], +                        'conflict_log' : [],                          'replica_uid' : self._replica_uid }          self._put_doc(doc) @@ -213,3 +229,36 @@ class ObjectStore(CommonBackend):      replica_uid = property(          _get_replica_uid, _set_replica_uid, doc="Replica UID of the database") + + +    #------------------------------------------------------------------------- +    # The methods below were cloned from u1db sqlite backend. They should at +    # least exist and raise a NotImplementedError exception in CommonBackend +    # (should we maybe fill a bug in u1db bts?). +    #------------------------------------------------------------------------- + +    def _add_conflict(self, doc_id, my_doc_rev, my_content): +        self._conflict_log.append((doc_id, my_doc_rev, my_content)) + +    def _delete_conflicts(self, doc, conflict_revs): +        deleting = [(doc.doc_id, c_rev) for c_rev in conflict_revs] +        self._conflict_log.delete_conflicts(deleting) +        doc.has_conflicts = self._has_conflicts(doc.doc_id) + +    def _prune_conflicts(self, doc, doc_vcr): +        if self._has_conflicts(doc.doc_id): +            autoresolved = False +            c_revs_to_prune = [] +            for c_doc in self._get_conflicts(doc.doc_id): +                c_vcr = vectorclock.VectorClockRev(c_doc.rev) +                if doc_vcr.is_newer(c_vcr): +                    c_revs_to_prune.append(c_doc.rev) +                elif doc.same_content_as(c_doc): +                    c_revs_to_prune.append(c_doc.rev) +                    doc_vcr.maximize(c_vcr) +                    autoresolved = True +            if autoresolved: +                doc_vcr.increment(self._replica_uid) +                doc.rev = doc_vcr.as_str() +            c = self._db_handle.cursor() +            self._delete_conflicts(c, doc, c_revs_to_prune) diff --git a/src/leap/soledad/tests/test_couch.py b/src/leap/soledad/tests/test_couch.py index 6a2c7dab..3f6c45f6 100644 --- a/src/leap/soledad/tests/test_couch.py +++ b/src/leap/soledad/tests/test_couch.py @@ -1,4 +1,8 @@ -"""Test ObjectStore backend bits.""" +"""Test ObjectStore backend bits. + +For these tests to run, a couch server has to be running on (default) port +5984. +"""  import sys  import copy @@ -6,8 +10,20 @@ import testtools  import testscenarios  from leap.soledad.backends import couch  from leap.soledad.tests import u1db_tests as tests -from leap.soledad.tests.u1db_tests.test_backends import AllDatabaseTests +from leap.soledad.tests.u1db_tests.test_backends import ( +  TestAlternativeDocument, +  AllDatabaseTests, +  LocalDatabaseTests, +  LocalDatabaseValidateGenNTransIdTests, +  LocalDatabaseValidateSourceGenTests, +  LocalDatabaseWithConflictsTests, +  DatabaseIndexTests, +) + +#----------------------------------------------------------------------------- +# The following tests come from `u1db.tests.test_common_backends`. +#-----------------------------------------------------------------------------  class TestCouchBackendImpl(tests.TestCase): @@ -19,6 +35,11 @@ class TestCouchBackendImpl(tests.TestCase):          int(doc_id1[len('D-'):], 16)          self.assertNotEqual(doc_id1, db._allocate_doc_id()) + +#----------------------------------------------------------------------------- +# The following tests come from `u1db.tests.test_backends`. +#----------------------------------------------------------------------------- +  def make_couch_database_for_test(test, replica_uid, path='test'):      return couch.CouchDatabase('http://localhost:5984', 'u1db_tests',                                 replica_uid=replica_uid) @@ -34,28 +55,66 @@ def copy_couch_database_for_test(test, db):      new_db._ensure_u1db_data()      return new_db -def make_couch_app(test): -    pass - -class CouchTests(AllDatabaseTests): - -    scenarios = [ +COUCH_SCENARIOS = [          ('couch', {'make_database_for_test': make_couch_database_for_test,                    'copy_database_for_test': copy_couch_database_for_test,                    'make_document_for_test': tests.make_document_for_test,}),          ] + +class CouchTests(AllDatabaseTests): + +    scenarios = COUCH_SCENARIOS +      def tearDown(self):          self.db.delete_database()          super(CouchTests, self).tearDown() -    #make_database_for_test = make_couch_database_for_test -    #copy_database_for_test = copy_couch_database_for_test +class CouchDatabaseTests(LocalDatabaseTests): + +    scenarios = COUCH_SCENARIOS + +    def tearDown(self): +        self.db.delete_database() +        super(CouchDatabaseTests, self).tearDown() + -#    def runTest(self): -#        pass +#class CouchValidateGenNTransIdTests(LocalDatabaseValidateGenNTransIdTests): +# +#    scenarios = COUCH_SCENARIOS +# +#    def tearDown(self): +#        self.db.delete_database() +#        super(CouchTests, self).tearDown() +# +# +#class CouchValidateSourceGenTests(LocalDatabaseValidateSourceGenTests): +# +#    scenarios = COUCH_SCENARIOS +# +#    def tearDown(self): +#        self.db.delete_database() +#        super(CouchTests, self).tearDown() +# +# +#class CouchWithConflictsTests(LocalDatabaseWithConflictsTests): +# +#    scenarios = COUCH_SCENARIOS +# +#    def tearDown(self): +#        self.db.delete_database() +#        super(CouchTests, self).tearDown() +# +# +#class CouchIndexTests(DatabaseIndexTests): +# +#    scenarios = COUCH_SCENARIOS +# +#    def tearDown(self): +#        self.db.delete_database() +#        super(CouchTests, self).tearDown()  #  load_tests = tests.load_with_scenarios diff --git a/src/leap/soledad/tests/test_logs.py b/src/leap/soledad/tests/test_logs.py index 072ac1a5..7fbb1cb7 100644 --- a/src/leap/soledad/tests/test_logs.py +++ b/src/leap/soledad/tests/test_logs.py @@ -1,5 +1,5 @@  import unittest2 as unittest -from leap.soledad.util import TransactionLog, SyncLog +from leap.soledad.util import TransactionLog, SyncLog, ConflictLog  class LogTestCase(unittest.TestCase): @@ -49,25 +49,37 @@ class LogTestCase(unittest.TestCase):      def test_whats_changed(self):          data = [ -          (2, "doc_3", "tran_3"), -          (3, "doc_2", "tran_2"), -          (1, "doc_1", "tran_1") -        ] +            (1, "doc_1", "tran_1"), +            (2, "doc_2", "tran_2"), +            (3, "doc_3", "tran_3") +          ]          log = TransactionLog()          log.log = data          self.assertEqual(            log.whats_changed(3), -          (3, "tran_2", []), +          (3, "tran_3", []),            'error getting whats changed.')          self.assertEqual(            log.whats_changed(2), -          (3, "tran_2", [("doc_2",3,"tran_2")]), +          (3, "tran_3", [("doc_3",3,"tran_3")]),            'error getting whats changed.')          self.assertEqual(            log.whats_changed(1), -          (3, "tran_2", [("doc_3",2,"tran_3"),("doc_2",3,"tran_2")]), +          (3, "tran_3", [("doc_2",2,"tran_2"),("doc_3",3,"tran_3")]),            'error getting whats changed.') +    def test_conflict_log(self): +        data = [('1', 'my:1', 'irrelevant'), +                ('2', 'my:1', 'irrelevant'), +                ('3', 'my:1', 'irrelevant')] +        log = ConflictLog() +        log.log = data +        log.delete_conflicts([('1','my:1'),('2','my:1')]) +        self.assertEqual( +          log.log, +          [('3', 'my:1', 'irrelevant')], +          'error deleting conflicts.') +  if __name__ == '__main__':      unittest.main() diff --git a/src/leap/soledad/util.py b/src/leap/soledad/util.py index af38cd76..8683fbb9 100644 --- a/src/leap/soledad/util.py +++ b/src/leap/soledad/util.py @@ -149,7 +149,13 @@ class TransactionLog(SimpleLog):                  cur_gen, _, newest_trans_id = results[0]          return cur_gen, newest_trans_id, changes -         + + +    def get_transaction_log(self): +        """ +        Return only a list of (doc_id, transaction_id) +        """ +        return map(lambda x: (x[1], x[2]), sorted(self._log))  class SyncLog(SimpleLog): @@ -182,3 +188,12 @@ class SyncLog(SimpleLog):          self.append((other_replica_uid, other_generation,                       other_transaction_id)) +class ConflictLog(SimpleLog): +    """ +    A list of (doc_id, my_doc_rev, my_content) tuples. +    """ +     +    def delete_conflicts(self, conflicts): +        for conflict in conflicts: +            self.log = self.filter(lambda x: +                         x[0] != conflict[0] or x[1] != conflict[1]) | 
