summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--client/pkg/requirements.pip2
-rw-r--r--client/src/leap/soledad/client/__init__.py6
-rw-r--r--client/src/leap/soledad/client/_crypto.py6
-rw-r--r--client/src/leap/soledad/client/_database/__init__.py0
-rw-r--r--client/src/leap/soledad/client/_database/adbapi.py (renamed from client/src/leap/soledad/client/adbapi.py)9
-rw-r--r--client/src/leap/soledad/client/_database/blobs.py (renamed from client/src/leap/soledad/client/_blobs.py)57
-rw-r--r--client/src/leap/soledad/client/_database/dbschema.sql (renamed from common/src/leap/soledad/common/l2db/backends/dbschema.sql)0
-rw-r--r--client/src/leap/soledad/client/_database/pragmas.py (renamed from client/src/leap/soledad/client/pragmas.py)0
-rw-r--r--client/src/leap/soledad/client/_database/sqlcipher.py (renamed from client/src/leap/soledad/client/sqlcipher.py)38
-rw-r--r--client/src/leap/soledad/client/_database/sqlite.py (renamed from common/src/leap/soledad/common/l2db/backends/sqlite_backend.py)0
-rw-r--r--client/src/leap/soledad/client/_document.py253
-rw-r--r--client/src/leap/soledad/client/_secrets/storage.py4
-rw-r--r--client/src/leap/soledad/client/api.py36
-rw-r--r--client/src/leap/soledad/client/crypto.py8
-rw-r--r--client/src/leap/soledad/client/examples/benchmarks/measure_index_times.py2
-rw-r--r--client/src/leap/soledad/client/examples/benchmarks/measure_index_times_custom_docid.py2
-rw-r--r--client/src/leap/soledad/client/examples/use_adbapi.py2
-rw-r--r--client/src/leap/soledad/client/http_target/fetch.py4
-rw-r--r--client/src/leap/soledad/client/interfaces.py12
-rw-r--r--common/src/leap/soledad/common/l2db/__init__.py4
-rw-r--r--common/src/leap/soledad/common/l2db/remote/server_state.py14
-rw-r--r--testing/test_soledad/u1db_tests/__init__.py7
-rw-r--r--testing/test_soledad/u1db_tests/test_open.py11
-rw-r--r--testing/test_soledad/util.py4
-rw-r--r--testing/tests/blobs/test_blob_manager.py4
-rw-r--r--testing/tests/blobs/test_decrypter_buffer.py10
-rw-r--r--testing/tests/blobs/test_sqlcipher_client_backend.py2
-rw-r--r--testing/tests/client/test_attachments.py81
-rw-r--r--testing/tests/client/test_aux_methods.py2
-rw-r--r--testing/tests/server/test_blobs_server.py4
-rw-r--r--testing/tests/sqlcipher/test_async.py6
-rw-r--r--testing/tests/sqlcipher/test_backend.py9
-rw-r--r--testing/tests/sync/test_sync_target.py6
34 files changed, 487 insertions, 120 deletions
diff --git a/.gitignore b/.gitignore
index 1cad979a..80ccf5aa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,7 +19,9 @@ MANIFEST
_trial_temp
.DS_Store
scripts/profiling/sync/profiles
+tags
testing/htmlcov
testing/.coverage
testing/test-env
+testing/.benchmarks
diff --git a/client/pkg/requirements.pip b/client/pkg/requirements.pip
index 8983b6b5..5a61a7b5 100644
--- a/client/pkg/requirements.pip
+++ b/client/pkg/requirements.pip
@@ -5,3 +5,5 @@ cryptography
pysqlcipher;python_version=="2.7"
pysqlcipher3;python_version=="3.4"
treq
+weakref
+enum34
diff --git a/client/src/leap/soledad/client/__init__.py b/client/src/leap/soledad/client/__init__.py
index 3a114021..bcad78db 100644
--- a/client/src/leap/soledad/client/__init__.py
+++ b/client/src/leap/soledad/client/__init__.py
@@ -17,12 +17,14 @@
"""
Soledad - Synchronization Of Locally Encrypted Data Among Devices.
"""
-from leap.soledad.client.api import Soledad
from leap.soledad.common import soledad_assert
+from .api import Soledad
+from ._document import Document, AttachmentStates
from ._version import get_versions
__version__ = get_versions()['version']
del get_versions
-__all__ = ['soledad_assert', 'Soledad', '__version__']
+__all__ = ['soledad_assert', 'Soledad', 'Document', 'AttachmentStates',
+ '__version__']
diff --git a/client/src/leap/soledad/client/_crypto.py b/client/src/leap/soledad/client/_crypto.py
index 7a1d508e..e66cc600 100644
--- a/client/src/leap/soledad/client/_crypto.py
+++ b/client/src/leap/soledad/client/_crypto.py
@@ -135,7 +135,7 @@ class SoledadCrypto(object):
and wrapping the result as a simple JSON string with a "raw" key.
:param doc: the document to be encrypted.
- :type doc: SoledadDocument
+ :type doc: Document
:return: A deferred whose callback will be invoked with a JSON string
containing the ciphertext as the value of "raw" key.
:rtype: twisted.internet.defer.Deferred
@@ -159,7 +159,7 @@ class SoledadCrypto(object):
the decrypted cleartext content from the encrypted document.
:param doc: the document to be decrypted.
- :type doc: SoledadDocument
+ :type doc: Document
:return: The decrypted cleartext content of the document.
:rtype: str
"""
@@ -225,7 +225,7 @@ def decrypt_sym(data, key, iv, method=ENC_METHOD.aes_256_gcm):
class BlobEncryptor(object):
"""
Produces encrypted data from the cleartext data associated with a given
- SoledadDocument using AES-256 cipher in GCM mode.
+ Document using AES-256 cipher in GCM mode.
The production happens using a Twisted's FileBodyProducer, which uses a
Cooperator to schedule calls and can be paused/resumed. Each call takes at
diff --git a/client/src/leap/soledad/client/_database/__init__.py b/client/src/leap/soledad/client/_database/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/client/src/leap/soledad/client/_database/__init__.py
diff --git a/client/src/leap/soledad/client/adbapi.py b/client/src/leap/soledad/client/_database/adbapi.py
index b002055e..5c28d108 100644
--- a/client/src/leap/soledad/client/adbapi.py
+++ b/client/src/leap/soledad/client/_database/adbapi.py
@@ -30,8 +30,9 @@ from zope.proxy import ProxyBase, setProxiedObject
from leap.soledad.common.log import getLogger
from leap.soledad.common.errors import DatabaseAccessError
-from leap.soledad.client import sqlcipher as soledad_sqlcipher
-from leap.soledad.client.pragmas import set_init_pragmas
+
+from . import sqlcipher
+from . import pragmas
if sys.version_info[0] < 3:
from pysqlcipher import dbapi2
@@ -73,7 +74,7 @@ def getConnectionPool(opts, openfun=None, driver="pysqlcipher"):
:rtype: U1DBConnectionPool
"""
if openfun is None and driver == "pysqlcipher":
- openfun = partial(set_init_pragmas, opts=opts)
+ openfun = partial(pragmas.set_init_pragmas, opts=opts)
return U1DBConnectionPool(
opts,
# the following params are relayed "as is" to twisted's
@@ -87,7 +88,7 @@ class U1DBConnection(adbapi.Connection):
A wrapper for a U1DB connection instance.
"""
- u1db_wrapper = soledad_sqlcipher.SoledadSQLCipherWrapper
+ u1db_wrapper = sqlcipher.SoledadSQLCipherWrapper
"""
The U1DB wrapper to use.
"""
diff --git a/client/src/leap/soledad/client/_blobs.py b/client/src/leap/soledad/client/_database/blobs.py
index 1475b302..79404bf3 100644
--- a/client/src/leap/soledad/client/_blobs.py
+++ b/client/src/leap/soledad/client/_database/blobs.py
@@ -20,8 +20,9 @@ Clientside BlobBackend Storage.
from urlparse import urljoin
+import binascii
+import errno
import os
-import uuid
import base64
from io import BytesIO
@@ -34,13 +35,18 @@ from twisted.web.client import FileBodyProducer
import treq
-from leap.soledad.client.sqlcipher import SQLCipherOptions
-from leap.soledad.client import pragmas
-from leap.soledad.client._pipes import TruncatedTailPipe, PreamblePipe
from leap.soledad.common.errors import SoledadError
-from _crypto import DocInfo, BlobEncryptor, BlobDecryptor
-from _http import HTTPClient
+from .._document import BlobDoc
+from .._crypto import DocInfo
+from .._crypto import BlobEncryptor
+from .._crypto import BlobDecryptor
+from .._http import HTTPClient
+from .._pipes import TruncatedTailPipe
+from .._pipes import PreamblePipe
+
+from . import pragmas
+from . import sqlcipher
logger = Logger()
@@ -129,6 +135,16 @@ class DecrypterBuffer(object):
return self.decrypter._end_stream(), real_size
+def mkdir_p(path):
+ try:
+ os.makedirs(path)
+ except OSError as exc:
+ if exc.errno == errno.EEXIST and os.path.isdir(path):
+ pass
+ else:
+ raise
+
+
class BlobManager(object):
"""
Ideally, the decrypting flow goes like this:
@@ -149,6 +165,7 @@ class BlobManager(object):
self, local_path, remote, key, secret, user, token=None,
cert_file=None):
if local_path:
+ mkdir_p(os.path.dirname(local_path))
self.local = SQLiteBlobBackend(local_path, key)
self.remote = remote
self.secret = secret
@@ -280,10 +297,13 @@ class SQLiteBlobBackend(object):
def __init__(self, path, key=None):
self.path = os.path.abspath(
os.path.join(path, 'soledad_blob.db'))
+ mkdir_p(os.path.dirname(self.path))
if not key:
raise ValueError('key cannot be None')
backend = 'pysqlcipher.dbapi2'
- opts = SQLCipherOptions('/tmp/ignored', key)
+ opts = sqlcipher.SQLCipherOptions(
+ '/tmp/ignored', binascii.b2a_hex(key),
+ is_raw_key=True, create=True)
pragmafun = partial(pragmas.set_init_pragmas, opts=opts)
openfun = _sqlcipherInitFactory(pragmafun)
@@ -292,7 +312,11 @@ class SQLiteBlobBackend(object):
cp_openfun=openfun, cp_min=1, cp_max=2, cp_name='blob_pool')
def close(self):
- return self.dbpool.close()
+ from twisted._threads import AlreadyQuit
+ try:
+ self.dbpool.close()
+ except AlreadyQuit:
+ pass
@defer.inlineCallbacks
def put(self, blob_id, blob_fd, size=None):
@@ -352,20 +376,6 @@ def _sqlcipherInitFactory(fun):
return _initialize
-class BlobDoc(object):
-
- # TODO probably not needed, but convenient for testing for now.
-
- def __init__(self, content, blob_id):
-
- self.blob_id = blob_id
- self.is_blob = True
- self.blob_fd = content
- if blob_id is None:
- blob_id = uuid.uuid4().get_hex()
- self.blob_id = blob_id
-
-
#
# testing facilities
#
@@ -431,8 +441,7 @@ def testit(reactor):
# TODO convert these into proper unittests
def _manager():
- if not os.path.isdir(args.path):
- os.makedirs(args.path)
+ mkdir_p(os.path.dirname(args.path))
manager = BlobManager(
args.path, args.url,
'A' * 32, args.secret,
diff --git a/common/src/leap/soledad/common/l2db/backends/dbschema.sql b/client/src/leap/soledad/client/_database/dbschema.sql
index ae027fc5..ae027fc5 100644
--- a/common/src/leap/soledad/common/l2db/backends/dbschema.sql
+++ b/client/src/leap/soledad/client/_database/dbschema.sql
diff --git a/client/src/leap/soledad/client/pragmas.py b/client/src/leap/soledad/client/_database/pragmas.py
index 870ed63e..870ed63e 100644
--- a/client/src/leap/soledad/client/pragmas.py
+++ b/client/src/leap/soledad/client/_database/pragmas.py
diff --git a/client/src/leap/soledad/client/sqlcipher.py b/client/src/leap/soledad/client/_database/sqlcipher.py
index 2c995d5a..d22017bd 100644
--- a/client/src/leap/soledad/client/sqlcipher.py
+++ b/client/src/leap/soledad/client/_database/sqlcipher.py
@@ -50,16 +50,16 @@ from twisted.internet import reactor
from twisted.internet import defer
from twisted.enterprise import adbapi
-from leap.soledad.common.document import SoledadDocument
from leap.soledad.common.log import getLogger
from leap.soledad.common.l2db import errors as u1db_errors
-from leap.soledad.common.l2db import Document
-from leap.soledad.common.l2db.backends import sqlite_backend
from leap.soledad.common.errors import DatabaseAccessError
from leap.soledad.client.http_target import SoledadHTTPSyncTarget
from leap.soledad.client.sync import SoledadSynchronizer
-from leap.soledad.client import pragmas
+
+from .._document import Document
+from . import sqlite
+from . import pragmas
if sys.version_info[0] < 3:
from pysqlcipher import dbapi2 as sqlcipher_dbapi2
@@ -69,8 +69,8 @@ else:
logger = getLogger(__name__)
-# Monkey-patch u1db.backends.sqlite_backend with pysqlcipher.dbapi2
-sqlite_backend.dbapi2 = sqlcipher_dbapi2
+# Monkey-patch u1db.backends.sqlite with pysqlcipher.dbapi2
+sqlite.dbapi2 = sqlcipher_dbapi2
# we may want to collect statistics from the sync process
@@ -193,7 +193,7 @@ class SQLCipherOptions(object):
# The SQLCipher database
#
-class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase):
+class SQLCipherDatabase(sqlite.SQLitePartialExpandDatabase):
"""
A U1DB implementation that uses SQLCipher as its persistence layer.
"""
@@ -232,7 +232,7 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase):
# ---------------------------------------------------------
self._ensure_schema()
- self.set_document_factory(soledad_doc_factory)
+ self.set_document_factory(doc_factory)
self._prime_replica_uid()
def _prime_replica_uid(self):
@@ -341,7 +341,7 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase):
:param doc: The new version of the document.
:type doc: u1db.Document
"""
- sqlite_backend.SQLitePartialExpandDatabase._put_and_update_indexes(
+ sqlite.SQLitePartialExpandDatabase._put_and_update_indexes(
self, old_doc, doc)
c = self._db_handle.cursor()
c.execute('UPDATE document SET syncable=? WHERE doc_id=?',
@@ -361,7 +361,7 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase):
:return: a Document object.
:type: u1db.Document
"""
- doc = sqlite_backend.SQLitePartialExpandDatabase._get_doc(
+ doc = sqlite.SQLitePartialExpandDatabase._get_doc(
self, doc_id, check_for_conflicts)
if doc:
c = self._db_handle.cursor()
@@ -437,7 +437,7 @@ class SQLCipherU1DBSync(SQLCipherDatabase):
self._opts, check_same_thread=False)
self._real_replica_uid = None
self._ensure_schema()
- self.set_document_factory(soledad_doc_factory)
+ self.set_document_factory(doc_factory)
except sqlcipher_dbapi2.DatabaseError as e:
raise DatabaseAccessError(str(e))
@@ -505,7 +505,7 @@ class SQLCipherU1DBSync(SQLCipherDatabase):
return self._get_generation()
-class U1DBSQLiteBackend(sqlite_backend.SQLitePartialExpandDatabase):
+class U1DBSQLiteBackend(sqlite.SQLitePartialExpandDatabase):
"""
A very simple wrapper for u1db around sqlcipher backend.
@@ -537,7 +537,7 @@ class SoledadSQLCipherWrapper(SQLCipherDatabase):
self._db_handle = conn
self._real_replica_uid = None
self._ensure_schema()
- self.set_document_factory(soledad_doc_factory)
+ self.set_document_factory(doc_factory)
self._prime_replica_uid()
@@ -562,7 +562,7 @@ def _assert_db_is_encrypted(opts):
# If the regular backend succeeds, then we need to stop because
# the database was not properly initialized.
try:
- sqlite_backend.SQLitePartialExpandDatabase(opts.path)
+ sqlite.SQLitePartialExpandDatabase(opts.path)
except sqlcipher_dbapi2.DatabaseError:
# assert that we can access it using SQLCipher with the given
# key
@@ -583,17 +583,17 @@ class DatabaseIsNotEncrypted(Exception):
pass
-def soledad_doc_factory(doc_id=None, rev=None, json='{}', has_conflicts=False,
- syncable=True):
+def doc_factory(doc_id=None, rev=None, json='{}', has_conflicts=False,
+ syncable=True):
"""
Return a default Soledad Document.
Used in the initialization for SQLCipherDatabase
"""
- return SoledadDocument(doc_id=doc_id, rev=rev, json=json,
- has_conflicts=has_conflicts, syncable=syncable)
+ return Document(doc_id=doc_id, rev=rev, json=json,
+ has_conflicts=has_conflicts, syncable=syncable)
-sqlite_backend.SQLiteDatabase.register_implementation(SQLCipherDatabase)
+sqlite.SQLiteDatabase.register_implementation(SQLCipherDatabase)
#
diff --git a/common/src/leap/soledad/common/l2db/backends/sqlite_backend.py b/client/src/leap/soledad/client/_database/sqlite.py
index 4f7b1259..4f7b1259 100644
--- a/common/src/leap/soledad/common/l2db/backends/sqlite_backend.py
+++ b/client/src/leap/soledad/client/_database/sqlite.py
diff --git a/client/src/leap/soledad/client/_document.py b/client/src/leap/soledad/client/_document.py
new file mode 100644
index 00000000..9ba08e93
--- /dev/null
+++ b/client/src/leap/soledad/client/_document.py
@@ -0,0 +1,253 @@
+# -*- coding: utf-8 -*-
+# _document.py
+# Copyright (C) 2017 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+Everything related to documents.
+"""
+
+import enum
+import weakref
+import uuid
+
+from twisted.internet import defer
+
+from zope.interface import Interface
+from zope.interface import implementer
+
+from leap.soledad.common.document import SoledadDocument
+
+
+class IDocumentWithAttachment(Interface):
+ """
+ A document that can have an attachment.
+ """
+
+ def set_store(self, store):
+ """
+ Set the store used by this file to manage attachments.
+
+ :param store: The store used to manage attachments.
+ :type store: Soledad
+ """
+
+ def put_attachment(self, fd):
+ """
+ Attach data to this document.
+
+ Add the attachment to local storage, enqueue for upload.
+
+ The document content will be updated with a pointer to the attachment,
+ but the document has to be manually put in the database to reflect
+ modifications.
+
+ :param fd: A file-like object whose content will be attached to this
+ document.
+ :type fd: file-like
+
+ :return: A deferred which fires when the attachment has been added to
+ local storage.
+ :rtype: Deferred
+ """
+
+ def get_attachment(self):
+ """
+ Return the data attached to this document.
+
+ If document content contains a pointer to the attachment, try to get
+ the attachment from local storage and, if not found, from remote
+ storage.
+
+ :return: A deferred which fires with a file like-object whose content
+ is the attachment of this document, or None if nothing is
+ attached.
+ :rtype: Deferred
+ """
+
+ def delete_attachment(self):
+ """
+ Delete the attachment of this document.
+
+ The pointer to the attachment will be removed from the document
+ content, but the document has to be manually put in the database to
+ reflect modifications.
+
+ :return: A deferred which fires when the attachment has been deleted
+ from local storage.
+ :rtype: Deferred
+ """
+
+ def attachment_state(self):
+ """
+ Return the state of the attachment of this document.
+
+ The state is a member of AttachmentStates and is of one of NONE,
+ LOCAL, REMOTE or SYNCED.
+
+ :return: A deferred which fires with The state of the attachment of
+ this document.
+ :rtype: Deferred
+ """
+
+ def is_dirty(self):
+ """
+ Return wether this document's content differs from the contents stored
+ in local database.
+
+ :return: Whether this document is dirty or not.
+ :rtype: bool
+ """
+
+ def upload_attachment(self):
+ """
+ Upload this document's attachment.
+
+ :return: A deferred which fires with the state of the attachment after
+ it's been uploaded, or NONE if there's no attachment for this
+ document.
+ :rtype: Deferred
+ """
+
+ def download_attachment(self):
+ """
+ Download this document's attachment.
+
+ :return: A deferred which fires with the state of the attachment after
+ it's been downloaded, or NONE if there's no attachment for
+ this document.
+ :rtype: Deferred
+ """
+
+
+class BlobDoc(object):
+
+ # TODO probably not needed, but convenient for testing for now.
+
+ def __init__(self, content, blob_id):
+
+ self.blob_id = blob_id
+ self.is_blob = True
+ self.blob_fd = content
+ if blob_id is None:
+ blob_id = uuid.uuid4().get_hex()
+ self.blob_id = blob_id
+
+
+class AttachmentStates(enum.IntEnum):
+ NONE = 0
+ LOCAL = 1
+ REMOTE = 2
+ SYNCED = 4
+
+
+@implementer(IDocumentWithAttachment)
+class Document(SoledadDocument):
+
+ def __init__(self, doc_id=None, rev=None, json='{}', has_conflicts=False,
+ syncable=True, store=None):
+ SoledadDocument.__init__(self, doc_id=doc_id, rev=rev, json=json,
+ has_conflicts=has_conflicts,
+ syncable=syncable)
+ self.set_store(store)
+
+ #
+ # properties
+ #
+
+ @property
+ def _manager(self):
+ if not self.store or not hasattr(self.store, 'blobmanager'):
+ raise Exception('No blob manager found to manage attachments.')
+ return self.store.blobmanager
+
+ @property
+ def _blob_id(self):
+ if self.content and 'blob_id' in self.content:
+ return self.content['blob_id']
+ return None
+
+ def get_store(self):
+ return self._store() if self._store else None
+
+ def set_store(self, store):
+ self._store = weakref.ref(store) if store else None
+
+ store = property(get_store, set_store)
+
+ #
+ # attachment api
+ #
+
+ def put_attachment(self, fd):
+ # add pointer to content
+ blob_id = self._blob_id or str(uuid.uuid4())
+ if not self.content:
+ self.content = {}
+ self.content['blob_id'] = blob_id
+ # put using manager
+ blob = BlobDoc(fd, blob_id)
+ fd.seek(0, 2)
+ size = fd.tell()
+ fd.seek(0)
+ return self._manager.put(blob, size)
+
+ def get_attachment(self):
+ if not self._blob_id:
+ return defer.succeed(None)
+ return self._manager.get(self._blob_id)
+
+ def delete_attachment(self):
+ raise NotImplementedError
+
+ @defer.inlineCallbacks
+ def attachment_state(self):
+ state = AttachmentStates.NONE
+
+ if not self._blob_id:
+ defer.returnValue(state)
+
+ local_list = yield self._manager.local_list()
+ if self._blob_id in local_list:
+ state |= AttachmentStates.LOCAL
+
+ remote_list = yield self._manager.remote_list()
+ if self._blob_id in remote_list:
+ state |= AttachmentStates.REMOTE
+
+ defer.returnValue(state)
+
+ @defer.inlineCallbacks
+ def is_dirty(self):
+ stored = yield self.store.get_doc(self.doc_id)
+ if stored.content != self.content:
+ defer.returnValue(True)
+ defer.returnValue(False)
+
+ @defer.inlineCallbacks
+ def upload_attachment(self):
+ if not self._blob_id:
+ defer.returnValue(AttachmentStates.NONE)
+
+ fd = yield self._manager.get_blob(self._blob_id)
+ # TODO: turn following method into a public one
+ yield self._manager._encrypt_and_upload(self._blob_id, fd)
+ defer.returnValue(self.attachment_state())
+
+ @defer.inlineCallbacks
+ def download_attachment(self):
+ if not self._blob_id:
+ defer.returnValue(None)
+ yield self.get_attachment()
+ defer.returnValue(self.attachment_state())
diff --git a/client/src/leap/soledad/client/_secrets/storage.py b/client/src/leap/soledad/client/_secrets/storage.py
index 6ea89900..85713a48 100644
--- a/client/src/leap/soledad/client/_secrets/storage.py
+++ b/client/src/leap/soledad/client/_secrets/storage.py
@@ -23,8 +23,8 @@ from hashlib import sha256
from leap.soledad.common import SHARED_DB_NAME
from leap.soledad.common.log import getLogger
-from leap.soledad.common.document import SoledadDocument
from leap.soledad.client.shared_db import SoledadSharedDatabase
+from leap.soledad.client._document import Document
from leap.soledad.client._secrets.util import emit, UserDataMixin
@@ -111,7 +111,7 @@ class SecretsStorage(UserDataMixin):
def save_remote(self, encrypted):
doc = self._remote_doc
if not doc:
- doc = SoledadDocument(doc_id=self._remote_doc_id())
+ doc = Document(doc_id=self._remote_doc_id())
doc.content = encrypted
db = self._shared_db
if not db:
diff --git a/client/src/leap/soledad/client/api.py b/client/src/leap/soledad/client/api.py
index 18fb35d7..3dd99227 100644
--- a/client/src/leap/soledad/client/api.py
+++ b/client/src/leap/soledad/client/api.py
@@ -51,13 +51,14 @@ from leap.soledad.common.l2db.remote import http_client
from leap.soledad.common.l2db.remote.ssl_match_hostname import match_hostname
from leap.soledad.common.errors import DatabaseAccessError
-from leap.soledad.client import adbapi
-from leap.soledad.client import events as soledad_events
-from leap.soledad.client import interfaces as soledad_interfaces
-from leap.soledad.client import sqlcipher
-from leap.soledad.client._recovery_code import RecoveryCode
-from leap.soledad.client._secrets import Secrets
-from leap.soledad.client._crypto import SoledadCrypto
+from . import events as soledad_events
+from . import interfaces as soledad_interfaces
+from ._crypto import SoledadCrypto
+from ._database import adbapi
+from ._database import blobs
+from ._database import sqlcipher
+from ._recovery_code import RecoveryCode
+from ._secrets import Secrets
logger = getLogger(__name__)
@@ -171,6 +172,7 @@ class Soledad(object):
self._recovery_code = RecoveryCode()
self._secrets = Secrets(self)
self._crypto = SoledadCrypto(self._secrets.remote_secret)
+ self._init_blobmanager()
try:
# initialize database access, trap any problems so we can shutdown
@@ -262,6 +264,13 @@ class Soledad(object):
sync_exchange_phase = _p
return sync_phase, sync_exchange_phase
+ def _init_blobmanager(self):
+ path = os.path.join(os.path.dirname(self._local_db_path), 'blobs')
+ url = urlparse.urljoin(self.server_url, 'blobs/%s' % uuid)
+ key = self._secrets.local_key
+ self.blobmanager = blobs.BlobManager(path, url, key, self.uuid,
+ self.token, SOLEDAD_CERT)
+
#
# Closing methods
#
@@ -272,6 +281,7 @@ class Soledad(object):
"""
logger.debug("closing soledad")
self._dbpool.close()
+ self.blobmanager.close()
if getattr(self, '_dbsyncer', None):
self._dbsyncer.close()
@@ -306,7 +316,7 @@ class Soledad(object):
============================== WARNING ==============================
:param doc: A document with new content.
- :type doc: leap.soledad.common.document.SoledadDocument
+ :type doc: leap.soledad.common.document.Document
:return: A deferred whose callback will be invoked with the new
revision identifier for the document. The document object will
also be updated.
@@ -323,7 +333,7 @@ class Soledad(object):
This will also set doc.content to None.
:param doc: A document to be deleted.
- :type doc: leap.soledad.common.document.SoledadDocument
+ :type doc: leap.soledad.common.document.Document
:return: A deferred.
:rtype: twisted.internet.defer.Deferred
"""
@@ -387,6 +397,7 @@ class Soledad(object):
"""
return self._defer("get_all_docs", include_deleted)
+ @defer.inlineCallbacks
def create_doc(self, content, doc_id=None):
"""
Create a new document.
@@ -408,8 +419,9 @@ class Soledad(object):
# create_doc (and probably to put_doc too). There are cases (mail
# payloads for example) in which we already have the encoding in the
# headers, so we don't need to guess it.
- d = self._defer("create_doc", content, doc_id=doc_id)
- return d
+ doc = yield self._defer("create_doc", content, doc_id=doc_id)
+ doc.set_store(self)
+ defer.returnValue(doc)
def create_doc_from_json(self, json, doc_id=None):
"""
@@ -590,7 +602,7 @@ class Soledad(object):
the time you GET_DOC_CONFLICTS until the point where you RESOLVE)
:param doc: A Document with the new content to be inserted.
- :type doc: SoledadDocument
+ :type doc: Document
:param conflicted_doc_revs: A list of revisions that the new content
supersedes.
:type conflicted_doc_revs: list(str)
diff --git a/client/src/leap/soledad/client/crypto.py b/client/src/leap/soledad/client/crypto.py
index 09e90171..4795846c 100644
--- a/client/src/leap/soledad/client/crypto.py
+++ b/client/src/leap/soledad/client/crypto.py
@@ -167,7 +167,7 @@ class SoledadCrypto(object):
Wrapper around encrypt_docstr that accepts the document as argument.
:param doc: the document.
- :type doc: SoledadDocument
+ :type doc: Document
"""
key = self.doc_passphrase(doc.doc_id)
@@ -179,7 +179,7 @@ class SoledadCrypto(object):
Wrapper around decrypt_doc_dict that accepts the document as argument.
:param doc: the document.
- :type doc: SoledadDocument
+ :type doc: Document
:return: json string with the decrypted document
:rtype: str
@@ -194,7 +194,7 @@ class SoledadCrypto(object):
#
-# Crypto utilities for a SoledadDocument.
+# Crypto utilities for a Document.
#
def mac_doc(doc_id, doc_rev, ciphertext, enc_scheme, enc_method, enc_iv,
@@ -439,7 +439,7 @@ def is_symmetrically_encrypted(doc):
Return True if the document was symmetrically encrypted.
:param doc: The document to check.
- :type doc: SoledadDocument
+ :type doc: Document
:rtype: bool
"""
diff --git a/client/src/leap/soledad/client/examples/benchmarks/measure_index_times.py b/client/src/leap/soledad/client/examples/benchmarks/measure_index_times.py
index 92bc85d6..276b1200 100644
--- a/client/src/leap/soledad/client/examples/benchmarks/measure_index_times.py
+++ b/client/src/leap/soledad/client/examples/benchmarks/measure_index_times.py
@@ -28,7 +28,7 @@ from twisted.internet import defer, reactor
from leap.soledad.common import l2db
from leap.soledad.client import adbapi
-from leap.soledad.client.sqlcipher import SQLCipherOptions
+from leap.soledad.client._database.sqlcipher import SQLCipherOptions
folder = os.environ.get("TMPDIR", "tmp")
diff --git a/client/src/leap/soledad/client/examples/benchmarks/measure_index_times_custom_docid.py b/client/src/leap/soledad/client/examples/benchmarks/measure_index_times_custom_docid.py
index 429566c7..d288582b 100644
--- a/client/src/leap/soledad/client/examples/benchmarks/measure_index_times_custom_docid.py
+++ b/client/src/leap/soledad/client/examples/benchmarks/measure_index_times_custom_docid.py
@@ -27,7 +27,7 @@ import sys
from twisted.internet import defer, reactor
from leap.soledad.client import adbapi
-from leap.soledad.client.sqlcipher import SQLCipherOptions
+from leap.soledad.client._database.sqlcipher import SQLCipherOptions
from leap.soledad.common import l2db
diff --git a/client/src/leap/soledad/client/examples/use_adbapi.py b/client/src/leap/soledad/client/examples/use_adbapi.py
index 39301b41..25b32307 100644
--- a/client/src/leap/soledad/client/examples/use_adbapi.py
+++ b/client/src/leap/soledad/client/examples/use_adbapi.py
@@ -24,7 +24,7 @@ import os
from twisted.internet import defer, reactor
from leap.soledad.client import adbapi
-from leap.soledad.client.sqlcipher import SQLCipherOptions
+from leap.soledad.client._database.sqlcipher import SQLCipherOptions
from leap.soledad.common import l2db
diff --git a/client/src/leap/soledad/client/http_target/fetch.py b/client/src/leap/soledad/client/http_target/fetch.py
index cf4984d1..9d456830 100644
--- a/client/src/leap/soledad/client/http_target/fetch.py
+++ b/client/src/leap/soledad/client/http_target/fetch.py
@@ -23,10 +23,10 @@ from leap.soledad.client.events import emit_async
from leap.soledad.client.http_target.support import RequestBody
from leap.soledad.common.log import getLogger
from leap.soledad.client._crypto import is_symmetrically_encrypted
-from leap.soledad.common.document import SoledadDocument
from leap.soledad.common.l2db import errors
from leap.soledad.client import crypto as old_crypto
+from .._document import Document
from . import fetch_protocol
logger = getLogger(__name__)
@@ -113,7 +113,7 @@ class HTTPDocFetcher(object):
@defer.inlineCallbacks
def __atomic_doc_parse(self, doc_info, content, total):
- doc = SoledadDocument(doc_info['id'], doc_info['rev'], content)
+ doc = Document(doc_info['id'], doc_info['rev'], content)
if is_symmetrically_encrypted(content):
content = (yield self._crypto.decrypt_doc(doc)).getvalue()
elif old_crypto.is_symmetrically_encrypted(doc):
diff --git a/client/src/leap/soledad/client/interfaces.py b/client/src/leap/soledad/client/interfaces.py
index 1be47df7..0600449f 100644
--- a/client/src/leap/soledad/client/interfaces.py
+++ b/client/src/leap/soledad/client/interfaces.py
@@ -69,7 +69,7 @@ class ILocalStorage(Interface):
Update a document in the local encrypted database.
:param doc: the document to update
- :type doc: SoledadDocument
+ :type doc: Document
:return:
a deferred that will fire with the new revision identifier for
@@ -82,7 +82,7 @@ class ILocalStorage(Interface):
Delete a document from the local encrypted database.
:param doc: the document to delete
- :type doc: SoledadDocument
+ :type doc: Document
:return:
a deferred that will fire with ...
@@ -102,7 +102,7 @@ class ILocalStorage(Interface):
:return:
A deferred that will fire with the document object, containing a
- SoledadDocument, or None if it could not be found
+ Document, or None if it could not be found
:rtype: Deferred
"""
@@ -147,7 +147,7 @@ class ILocalStorage(Interface):
:type doc_id: str
:return:
- A deferred tht will fire with the new document (SoledadDocument
+ A deferred tht will fire with the new document (Document
instance).
:rtype: Deferred
"""
@@ -167,7 +167,7 @@ class ILocalStorage(Interface):
:param doc_id: An optional identifier specifying the document id.
:type doc_id:
:return:
- A deferred that will fire with the new document (A SoledadDocument
+ A deferred that will fire with the new document (A Document
instance)
:rtype: Deferred
"""
@@ -304,7 +304,7 @@ class ILocalStorage(Interface):
Mark a document as no longer conflicted.
:param doc: a document with the new content to be inserted.
- :type doc: SoledadDocument
+ :type doc: Document
:param conflicted_doc_revs:
A deferred that will fire with a list of revisions that the new
content supersedes.
diff --git a/common/src/leap/soledad/common/l2db/__init__.py b/common/src/leap/soledad/common/l2db/__init__.py
index 568897c4..ebbf546a 100644
--- a/common/src/leap/soledad/common/l2db/__init__.py
+++ b/common/src/leap/soledad/common/l2db/__init__.py
@@ -37,8 +37,8 @@ def open(path, create, document_factory=None):
parameters as Document.__init__.
:return: An instance of Database.
"""
- from leap.soledad.common.l2db.backends import sqlite_backend
- return sqlite_backend.SQLiteDatabase.open_database(
+ from leap.soledad.client._database import sqlite
+ return sqlite.SQLiteDatabase.open_database(
path, create=create, document_factory=document_factory)
diff --git a/common/src/leap/soledad/common/l2db/remote/server_state.py b/common/src/leap/soledad/common/l2db/remote/server_state.py
index e20b4679..89ac5742 100644
--- a/common/src/leap/soledad/common/l2db/remote/server_state.py
+++ b/common/src/leap/soledad/common/l2db/remote/server_state.py
@@ -42,10 +42,9 @@ class ServerState(object):
def open_database(self, path):
"""Open a database at the given location."""
- from u1db.backends import sqlite_backend
+ from leap.soledad.client._database import sqlite
full_path = self._relpath(path)
- return sqlite_backend.SQLiteDatabase.open_database(full_path,
- create=False)
+ return sqlite.SQLiteDatabase.open_database(full_path, create=False)
def check_database(self, path):
"""Check if the database at the given location exists.
@@ -57,14 +56,13 @@ class ServerState(object):
def ensure_database(self, path):
"""Ensure database at the given location."""
- from u1db.backends import sqlite_backend
+ from leap.soledad.client._database import sqlite
full_path = self._relpath(path)
- db = sqlite_backend.SQLiteDatabase.open_database(full_path,
- create=True)
+ db = sqlite.SQLiteDatabase.open_database(full_path, create=True)
return db, db._replica_uid
def delete_database(self, path):
"""Delete database at the given location."""
- from u1db.backends import sqlite_backend
+ from leap.soledad.client._database import sqlite
full_path = self._relpath(path)
- sqlite_backend.SQLiteDatabase.delete_database(full_path)
+ sqlite.SQLiteDatabase.delete_database(full_path)
diff --git a/testing/test_soledad/u1db_tests/__init__.py b/testing/test_soledad/u1db_tests/__init__.py
index 1575b859..ccbb6ca6 100644
--- a/testing/test_soledad/u1db_tests/__init__.py
+++ b/testing/test_soledad/u1db_tests/__init__.py
@@ -37,11 +37,12 @@ from twisted.internet import reactor
from leap.soledad.common.l2db import errors
from leap.soledad.common.l2db import Document
from leap.soledad.common.l2db.backends import inmemory
-from leap.soledad.common.l2db.backends import sqlite_backend
from leap.soledad.common.l2db.remote import server_state
from leap.soledad.common.l2db.remote import http_app
from leap.soledad.common.l2db.remote import http_target
+from leap.soledad.client._database import sqlite
+
if sys.version_info[0] < 3:
from pysqlcipher import dbapi2
else:
@@ -140,7 +141,7 @@ def copy_memory_database_for_test(test, db):
def make_sqlite_partial_expanded_for_test(test, replica_uid):
- db = sqlite_backend.SQLitePartialExpandDatabase(':memory:')
+ db = sqlite.SQLitePartialExpandDatabase(':memory:')
db._set_replica_uid(replica_uid)
return db
@@ -151,7 +152,7 @@ def copy_sqlite_partial_expanded_for_test(test, db):
# CORRECTLY DETECT IT HAPPENING SO THAT WE CAN RAISE ERRORS RATHER THAN
# CORRUPT USER DATA. USE SYNC INSTEAD, OR WE WILL SEND NINJA TO YOUR
# HOUSE.
- new_db = sqlite_backend.SQLitePartialExpandDatabase(':memory:')
+ new_db = sqlite.SQLitePartialExpandDatabase(':memory:')
tmpfile = StringIO()
for line in db._db_handle.iterdump():
if 'sqlite_sequence' not in line: # work around bug in iterdump
diff --git a/testing/test_soledad/u1db_tests/test_open.py b/testing/test_soledad/u1db_tests/test_open.py
index b572fba0..eaaee72f 100644
--- a/testing/test_soledad/u1db_tests/test_open.py
+++ b/testing/test_soledad/u1db_tests/test_open.py
@@ -27,7 +27,8 @@ from test_soledad.u1db_tests.test_backends import TestAlternativeDocument
from leap.soledad.common.l2db import errors
from leap.soledad.common.l2db import open as u1db_open
-from leap.soledad.common.l2db.backends import sqlite_backend
+
+from leap.soledad.client._database import sqlite
@skip("Skiping tests imported from U1DB.")
@@ -47,7 +48,7 @@ class TestU1DBOpen(tests.TestCase):
db = u1db_open(self.db_path, create=True)
self.addCleanup(db.close)
self.assertTrue(os.path.exists(self.db_path))
- self.assertIsInstance(db, sqlite_backend.SQLiteDatabase)
+ self.assertIsInstance(db, sqlite.SQLiteDatabase)
def test_open_with_factory(self):
db = u1db_open(self.db_path, create=True,
@@ -56,7 +57,7 @@ class TestU1DBOpen(tests.TestCase):
self.assertEqual(TestAlternativeDocument, db._factory)
def test_open_existing(self):
- db = sqlite_backend.SQLitePartialExpandDatabase(self.db_path)
+ db = sqlite.SQLitePartialExpandDatabase(self.db_path)
self.addCleanup(db.close)
doc = db.create_doc_from_json(tests.simple_doc)
# Even though create=True, we shouldn't wipe the db
@@ -66,8 +67,8 @@ class TestU1DBOpen(tests.TestCase):
self.assertEqual(doc, doc2)
def test_open_existing_no_create(self):
- db = sqlite_backend.SQLitePartialExpandDatabase(self.db_path)
+ db = sqlite.SQLitePartialExpandDatabase(self.db_path)
self.addCleanup(db.close)
db2 = u1db_open(self.db_path, create=False)
self.addCleanup(db2.close)
- self.assertIsInstance(db2, sqlite_backend.SQLitePartialExpandDatabase)
+ self.assertIsInstance(db2, sqlite.SQLitePartialExpandDatabase)
diff --git a/testing/test_soledad/util.py b/testing/test_soledad/util.py
index 6ffb60b6..0335d544 100644
--- a/testing/test_soledad/util.py
+++ b/testing/test_soledad/util.py
@@ -46,9 +46,9 @@ from leap.soledad.common.couch.state import CouchServerState
from leap.soledad.client import Soledad
from leap.soledad.client import http_target
from leap.soledad.client import auth
-from leap.soledad.client.sqlcipher import SQLCipherDatabase
-from leap.soledad.client.sqlcipher import SQLCipherOptions
from leap.soledad.client._crypto import is_symmetrically_encrypted
+from leap.soledad.client._database.sqlcipher import SQLCipherDatabase
+from leap.soledad.client._database.sqlcipher import SQLCipherOptions
from leap.soledad.server import SoledadApp
diff --git a/testing/tests/blobs/test_blob_manager.py b/testing/tests/blobs/test_blob_manager.py
index 7609936d..69a272c8 100644
--- a/testing/tests/blobs/test_blob_manager.py
+++ b/testing/tests/blobs/test_blob_manager.py
@@ -19,8 +19,8 @@ Tests for BlobManager.
"""
from twisted.trial import unittest
from twisted.internet import defer
-from leap.soledad.client._blobs import BlobManager, BlobDoc, FIXED_REV
-from leap.soledad.client._blobs import BlobAlreadyExistsError
+from leap.soledad.client._database.blobs import BlobManager, BlobDoc, FIXED_REV
+from leap.soledad.client._database.blobs import BlobAlreadyExistsError
from io import BytesIO
from mock import Mock
import pytest
diff --git a/testing/tests/blobs/test_decrypter_buffer.py b/testing/tests/blobs/test_decrypter_buffer.py
index 288c9975..edaa66e2 100644
--- a/testing/tests/blobs/test_decrypter_buffer.py
+++ b/testing/tests/blobs/test_decrypter_buffer.py
@@ -18,12 +18,16 @@
Tests for blobs decrypter buffer. A component which is used as a decryption
sink during blob stream download.
"""
+from io import BytesIO
+from mock import Mock
+
from twisted.trial import unittest
from twisted.internet import defer
-from leap.soledad.client._blobs import DecrypterBuffer, BlobManager, FIXED_REV
+
+from leap.soledad.client._database.blobs import DecrypterBuffer
+from leap.soledad.client._database.blobs import BlobManager
+from leap.soledad.client._database.blobs import FIXED_REV
from leap.soledad.client import _crypto
-from io import BytesIO
-from mock import Mock
class DecrypterBufferCase(unittest.TestCase):
diff --git a/testing/tests/blobs/test_sqlcipher_client_backend.py b/testing/tests/blobs/test_sqlcipher_client_backend.py
index 951bac22..865a64e1 100644
--- a/testing/tests/blobs/test_sqlcipher_client_backend.py
+++ b/testing/tests/blobs/test_sqlcipher_client_backend.py
@@ -19,7 +19,7 @@ Tests for sqlcipher backend on blobs client.
"""
from twisted.trial import unittest
from twisted.internet import defer
-from leap.soledad.client._blobs import SQLiteBlobBackend
+from leap.soledad.client._database.blobs import SQLiteBlobBackend
from io import BytesIO
import pytest
diff --git a/testing/tests/client/test_attachments.py b/testing/tests/client/test_attachments.py
new file mode 100644
index 00000000..ad4595e9
--- /dev/null
+++ b/testing/tests/client/test_attachments.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+# test_attachments.py
+# Copyright (C) 2017 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+Tests for document attachments.
+"""
+
+import pytest
+
+from io import BytesIO
+from mock import Mock
+
+from twisted.internet import defer
+from test_soledad.util import BaseSoledadTest
+
+
+from leap.soledad.client import AttachmentStates
+
+
+def mock_response(doc):
+ doc._manager._client.get = Mock(
+ return_value=defer.succeed(Mock(code=200, json=lambda: [])))
+ doc._manager._client.put = Mock(
+ return_value=defer.succeed(Mock(code=200)))
+
+
+@pytest.mark.usefixture('method_tmpdir')
+class AttachmentTests(BaseSoledadTest):
+
+ @defer.inlineCallbacks
+ def test_create_doc_saves_store(self):
+ doc = yield self._soledad.create_doc({})
+ self.assertEqual(self._soledad, doc.store)
+
+ @defer.inlineCallbacks
+ def test_put_attachment(self):
+ doc = yield self._soledad.create_doc({})
+ mock_response(doc)
+ yield doc.put_attachment(BytesIO('test'))
+ local_list = yield doc._manager.local_list()
+ self.assertIn(doc._blob_id, local_list)
+
+ @defer.inlineCallbacks
+ def test_get_attachment(self):
+ doc = yield self._soledad.create_doc({})
+ mock_response(doc)
+ yield doc.put_attachment(BytesIO('test'))
+ fd = yield doc.get_attachment()
+ self.assertEqual('test', fd.read())
+
+ @defer.inlineCallbacks
+ def test_attachment_state(self):
+ doc = yield self._soledad.create_doc({})
+ state = yield doc.attachment_state()
+ self.assertEqual(AttachmentStates.NONE, state)
+ mock_response(doc)
+ yield doc.put_attachment(BytesIO('test'))
+ state = yield doc.attachment_state()
+ self.assertEqual(AttachmentStates.LOCAL, state)
+
+ @defer.inlineCallbacks
+ def test_is_dirty(self):
+ doc = yield self._soledad.create_doc({})
+ dirty = yield doc.is_dirty()
+ self.assertFalse(dirty)
+ doc.content = {'test': True}
+ dirty = yield doc.is_dirty()
+ self.assertTrue(dirty)
diff --git a/testing/tests/client/test_aux_methods.py b/testing/tests/client/test_aux_methods.py
index 729aa28a..da33255c 100644
--- a/testing/tests/client/test_aux_methods.py
+++ b/testing/tests/client/test_aux_methods.py
@@ -22,7 +22,7 @@ import os
from pytest import inlineCallbacks
from leap.soledad.client import Soledad
-from leap.soledad.client.adbapi import U1DBConnectionPool
+from leap.soledad.client._database.adbapi import U1DBConnectionPool
from leap.soledad.client._secrets.util import SecretsError
from test_soledad.util import BaseSoledadTest
diff --git a/testing/tests/server/test_blobs_server.py b/testing/tests/server/test_blobs_server.py
index 2e5af01f..cd39833f 100644
--- a/testing/tests/server/test_blobs_server.py
+++ b/testing/tests/server/test_blobs_server.py
@@ -24,8 +24,10 @@ from twisted.web.server import Site
from twisted.internet import reactor
from twisted.internet import defer
from treq._utils import set_global_pool
+
from leap.soledad.server import _blobs as server_blobs
-from leap.soledad.client._blobs import BlobManager, BlobAlreadyExistsError
+from leap.soledad.client._database.blobs import BlobManager
+from leap.soledad.client._database.blobs import BlobAlreadyExistsError
class BlobServerTestCase(unittest.TestCase):
diff --git a/testing/tests/sqlcipher/test_async.py b/testing/tests/sqlcipher/test_async.py
index 42c315fe..dac6c6b9 100644
--- a/testing/tests/sqlcipher/test_async.py
+++ b/testing/tests/sqlcipher/test_async.py
@@ -20,8 +20,8 @@ import hashlib
from twisted.internet import defer
from test_soledad.util import BaseSoledadTest
-from leap.soledad.client import adbapi
-from leap.soledad.client.sqlcipher import SQLCipherOptions
+from leap.soledad.client._database import adbapi
+from leap.soledad.client._database import sqlcipher
class ASyncSQLCipherRetryTestCase(BaseSoledadTest):
@@ -42,7 +42,7 @@ class ASyncSQLCipherRetryTestCase(BaseSoledadTest):
def _get_dbpool(self):
tmpdb = os.path.join(self.tempdir, "test.soledad")
- opts = SQLCipherOptions(tmpdb, "secret", create=True)
+ opts = sqlcipher.SQLCipherOptions(tmpdb, "secret", create=True)
return adbapi.getConnectionPool(opts)
def _get_sample(self):
diff --git a/testing/tests/sqlcipher/test_backend.py b/testing/tests/sqlcipher/test_backend.py
index 6e9595db..4f614fb3 100644
--- a/testing/tests/sqlcipher/test_backend.py
+++ b/testing/tests/sqlcipher/test_backend.py
@@ -26,14 +26,13 @@ import sys
# l2db stuff.
from leap.soledad.common.l2db import errors
from leap.soledad.common.l2db import query_parser
-from leap.soledad.common.l2db.backends.sqlite_backend \
- import SQLitePartialExpandDatabase
# soledad stuff.
from leap.soledad.common.document import SoledadDocument
-from leap.soledad.client.sqlcipher import SQLCipherDatabase
-from leap.soledad.client.sqlcipher import SQLCipherOptions
-from leap.soledad.client.sqlcipher import DatabaseIsNotEncrypted
+from leap.soledad.client._database.sqlite import SQLitePartialExpandDatabase
+from leap.soledad.client._database.sqlcipher import SQLCipherDatabase
+from leap.soledad.client._database.sqlcipher import SQLCipherOptions
+from leap.soledad.client._database.sqlcipher import DatabaseIsNotEncrypted
# u1db tests stuff.
from test_soledad import u1db_tests as tests
diff --git a/testing/tests/sync/test_sync_target.py b/testing/tests/sync/test_sync_target.py
index a54a02ee..e1bd7de1 100644
--- a/testing/tests/sync/test_sync_target.py
+++ b/testing/tests/sync/test_sync_target.py
@@ -32,9 +32,9 @@ from twisted.internet import defer
from leap.soledad.client import http_target as target
from leap.soledad.client.http_target.fetch_protocol import DocStreamReceiver
-from leap.soledad.client.sqlcipher import SQLCipherU1DBSync
-from leap.soledad.client.sqlcipher import SQLCipherOptions
-from leap.soledad.client.sqlcipher import SQLCipherDatabase
+from leap.soledad.client._database.sqlcipher import SQLCipherU1DBSync
+from leap.soledad.client._database.sqlcipher import SQLCipherOptions
+from leap.soledad.client._database.sqlcipher import SQLCipherDatabase
from leap.soledad.client import _crypto
from leap.soledad.common import l2db