mirror of
https://github.com/samschott/maestral.git
synced 2024-09-22 11:27:35 +03:00
108 lines
5.9 KiB
ReStructuredText
108 lines
5.9 KiB
ReStructuredText
|
|
Sync Logic
|
|
==========
|
|
|
|
The :class:`maestral.sync.SyncEngine` class provides access to the current sync state
|
|
through its properties and provides the methods which are necessary to complete an
|
|
upload or a download sync cycle. This includes methods to wait for and return local and
|
|
remote changes, to sort those changes and discard any excluded items and to apply local
|
|
changes to the Dropbox server and vice versa.
|
|
|
|
The :class:`maestral.sync.SyncMonitor` class coordinates the sync process with its
|
|
threads for startup, download-sync, upload-sync and periodic maintenance.
|
|
|
|
Processing of sync events
|
|
*************************
|
|
|
|
Remote events come in three types: :class:`dropbox.files.DeletedMetadata`,
|
|
:class:`dropbox.files.FolderMetadata` and :class:`dropbox.files.FileMetadata`.
|
|
The Dropbox API does not differentiate between created, moved or modified events.
|
|
Maestral processes remote events as follows:
|
|
|
|
1) :meth:`SyncEngine.wait_for_remote_changes` blocks until remote changes are
|
|
available.
|
|
|
|
2) :meth:`SyncEngine.list_remote_changes` lists all remote changes since the last sync.
|
|
Those events are processed at follows:
|
|
|
|
* Events for entries which are excluded by selective sync and hard-coded file names
|
|
which are always excluded (e.g., '.DS_Store') are filtered out at this stage.
|
|
* Multiple events per file path are combined to one. This is rarely necessary,
|
|
Dropbox typically already provides only a single event per path but this is not
|
|
guaranteed and may change. One exception is sharing a folder: Dropbox does this
|
|
by removing the folder from the user's root and re-mounting it as a shared
|
|
folder. This produces at least one DeletedMetadata and one FolderMetadata event.
|
|
If querying for changes *during* this process, multiple
|
|
:class:`dropbox.files.DeletedMetadata` events may be returned.
|
|
* If a file / folder event implies a type changes, e.g., replacing a folder with a
|
|
file, we explicitly generate the necessary :class:`dropbox.files.DeletedMetadata`
|
|
here to simplify conflict resolution.
|
|
|
|
3) :meth:`SyncEngine.apply_remote_changes`: Sorts all events hierarchically, with
|
|
top-level events coming first. Deleted and folder events are processed in order,
|
|
file events in parallel with up to 6 worker threads.
|
|
|
|
4) :meth:`SyncEngine.notify_user`: Shows a desktop notification for the remote
|
|
changes.
|
|
|
|
Local file events come in eight types: For both files and folders we collect created,
|
|
moved, modified and deleted events. They are processed as follows:
|
|
|
|
1) :meth:`SyncEngine.wait_for_local_changes`: Blocks until local changes are
|
|
registered by :class:`FSEventHandler`.
|
|
|
|
2) :meth:`SyncEngine.list_local_changes`: Lists all local file events. Those are
|
|
processed as follows:
|
|
|
|
* Events ignored by a "mignore" pattern as well as hard-coded file names and
|
|
changes in our cache path are filtered out at this stage.
|
|
* Events are further cleaned up to return the minimum number of events necessary to
|
|
reproduce the actual changes: Multiple events per path are combined into a single
|
|
event which reproduces the file change. The only exception is when the entry type
|
|
changes from file to folder or vice versa: in this case, both deleted and created
|
|
events are kept. Further, when a whole folder is moved or deleted, we discard the
|
|
moved or deleted events for its children.
|
|
|
|
2) :meth:`SyncEngine.apply_local_changes`: Sorts local changes hierarchically and
|
|
applies events in the order of deleted, folders and files. Deleted, created and
|
|
modified events will be applies to the remote Dropbox in parallel with up to 6
|
|
threads. Moves will be carried out synchronously.
|
|
|
|
Before processing, we convert all Dropbox metadata and local file events to a unified
|
|
format of :class:`maestral.database.SyncEvent` instances which are also used to store
|
|
the sync history data in our SQLite database.
|
|
|
|
Detection and resolution of sync conflicts
|
|
******************************************
|
|
|
|
Sync conflicts during a download are detected by comparing the file's "rev" with the
|
|
locally saved revision identifier in Maestral's index. We assign folders a rev of
|
|
``'folder'`` and deleted / non-existent items a rev of ``None``.
|
|
|
|
#. If both revs are equal, the local item is either the same as on Dropbox or newer and
|
|
the local changes haven't been uploaded and committed to our index yet. No download
|
|
sync occurs (including deletion of the local file).
|
|
#. If revs are different, we compare content hashes. If those hashes are equal, no
|
|
download occurs.
|
|
#. If content hashes are different, we check if the local item has been modified since
|
|
the last download sync. In case of a folder, we take the most recent change of any of
|
|
its children. If the local entry has not been modified since the last sync, it will
|
|
be replaced. Otherwise, we create a conflicting copy.
|
|
|
|
Conflict resolution for uploads is handled as follows:
|
|
|
|
#. For created and moved events, we check if the new path has been excluded by the user
|
|
with selective sync but still exists on Dropbox. If yes, it will be renamed by
|
|
appending "(selective sync conflict)".
|
|
#. On case-sensitive file systems, we check if the new path differs only in casing from
|
|
an existing path. If yes, it will be renamed by appending "(case conflict)".
|
|
#. If a file has been replaced with a folder or vice versa, we check if any un-synced
|
|
changes will be lost by replacing the remote item and create a conflicting copy if
|
|
necessary.
|
|
#. For created or modified files, check if the local content hash equals the remote
|
|
content hash. If yes, we don't upload but update our rev number. If no, we upload the
|
|
changes and specify the rev which we want to replace or delete. If the remote item is
|
|
newer (different rev), Dropbox will handle conflict resolution for us.
|
|
#. We finally confirm the successful upload and check if Dropbox has renamed the item to
|
|
a conflicting copy. In the latter case, we apply those changes locally.
|