Document attachments
====================
.. contents:: Contents:
:local:
Reasoning
---------
The type of a Soledad document's content is `JSON `_,
which is good for efficient lookup and indexing. On the other hand, this is
particularly bad for storing larger amounts of binary data, because:
* the only way to store data in JSON is as unicode string, and this uses more
space than what is actually needed for binary data storage.
* upon synchronization, the content of a Soledad document needs to be
completelly transferred and decrypted for the document to be available for
use.
Document attachments were introduced as a means to efficiently store large
payloads of binary data while avoiding the need to wait for their transfer to
have access to the documents' contents.
Client-side
-----------
In the client, attachments are stored as (SQLite) BLOBs in a separate SQLCipher
database. Encryption of data before it's sent to the server is the same used
for Soledad documents' content during usual synchronization process (AES-256
GCM mode).
Usage example
^^^^^^^^^^^^^
The attachments API is currently available in the `Document` class, and the
document needs to know about the store to be able to manage attachments. When
you create a new document with soledad, that document will already know about
the store that created it, and can put/get/delete an attachment:
.. code-block:: python
from twisted.internet.defer import inlineCallbacks
@inlineCallbacks
def attachment_example(soledad):
doc = yield soledad.create_doc({})
state = yield doc.get_attachment_state()
dirty = yield doc.is_dirty()
assert state == AttachmentStates.NONE
assert dirty == False
yield doc.put_attachment(open('hackers.txt'))
state = yield doc.get_attachment_state()
dirty = yield doc.is_dirty()
assert state | AttachmentState.LOCAL
assert dirty == True
yield soledad.put_doc(doc)
dirty = yield doc.is_dirty()
assert dirty == False
yield doc.upload_attachment()
state = yield doc.get_attachment_state()
assert state | AttachmentState.REMOTE
assert state == AttachmentState.SYNCED
fd = yield doc.get_attachment()
assert fd.read() == open('hackers.txt').read()
API
^^^
.. autoclass:: leap.soledad.client._document.AttachmentStates
:members:
:undoc-members:
.. autointerface:: leap.soledad.client._document.IDocumentWithAttachment
:members:
:undoc-members:
Server-side
-----------
In the server, a simple REST API is served by a `Twisted Resource
`_
and attachments are stored in the filesystem as they come in without
modification.
A token is used to allow listing, getting, putting and deleting attachments. It
has to be added as an HTTP auth header, as in::
Authorization: Token
The :ref:`IBlobsBackend ` interface is provided, so in the
future there can be different ways to store attachments in the server side
(think of a third-party storage, for example). Currently, the
:ref:`FilesystemBlobsBackend ` is the only backend
that implements that interface.
Some characteristics of the :ref:`FilesystemBlobsBackend
` are:
* Configurable storage path.
* Quota support.
* Username, blob_id and user storage directory sanitization.
Usage example
^^^^^^^^^^^^^
These are the possible ways to interact with the attachments REST API on the
server side:
=========== ================ ======== ==================
HTTP Method URL Content Possible responses
=========== ================ ======== ==================
GET /user_id - 200
GET /user_id/blob_id - 200, 404
PUT /user_id/blob_id The BLOB 200, 409, 507
DELETE /user_id/blob_id - 200
=========== ================ ======== ==================
API
^^^
.. _i-blobs-backend:
.. autoclass:: leap.soledad.server.interfaces.IBlobsBackend
:members:
:undoc-members:
.. _filesystem-blobs-backend:
.. autoclass:: leap.soledad.server._blobs.FilesystemBlobsBackend
:members:
:undoc-members: