Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Watchdog 2.0 #276

Merged
merged 37 commits into from
Feb 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
6605ef7
remove our own fsevents module
Dec 11, 2020
9079098
[sync] detect deleted dropbox folder through DirDeletedEvent
Dec 11, 2020
670866e
bump watchdog to branch with macos fixes
Jan 7, 2021
58eafd6
[sync] add nice repr to `_Ignore`
Jan 8, 2021
3ee2350
[sync] ignore up to two FileCreatedEvents on move on macOS
Jan 8, 2021
f048dd2
[tests] minor cleanup
Jan 8, 2021
e3cc731
[sync] whitelist which event types are handled
Jan 18, 2021
600e12b
[tests] increase time to wait for idle
Jan 18, 2021
151bad7
[tests] prefer creating folder remotely
Jan 18, 2021
07e963f
[tests] increase test timeouts
Jan 19, 2021
dad6208
install watchdog from master branch
Jan 19, 2021
84e5791
[sync] remove unused import
Jan 20, 2021
d43aa38
switch to main watchdog repo
Jan 21, 2021
ed912ee
[fsevents] use our own polling observer again
Feb 3, 2021
475b7fb
[sync] remove special event ignores for macOS
Feb 3, 2021
35cb31c
[sync] simplify notification for local events
Feb 3, 2021
7eb78b8
[sync] move FSEventHandler to SyncEngine
Feb 4, 2021
e005ee0
[sync] added additional debug message
Feb 4, 2021
89691cd
[sync] don't emit DirModifiedEvent on startup scan
Feb 4, 2021
714656f
[database] show item type in SyncEvent repr
Feb 4, 2021
3e1bfe2
remove our own fsevents module
Dec 11, 2020
98ab286
bump watchdog to branch with macos fixes
Jan 7, 2021
4621fae
[sync] ignore up to two FileCreatedEvents on move on macOS
Jan 8, 2021
63a4e58
[tests] increase time to wait for idle
Jan 18, 2021
01e1a03
[tests] increase test timeouts
Jan 19, 2021
cd81374
install watchdog from master branch
Jan 19, 2021
d03e7bd
switch to main watchdog repo
Jan 21, 2021
f49d1ac
[fsevents] use our own polling observer again
Feb 3, 2021
be39d62
[sync] remove special event ignores for macOS
Feb 3, 2021
690d2a8
[tests] reduce wait_for_idle time
Feb 5, 2021
0218f26
bump to watchdog 2.0
Feb 11, 2021
78b2622
[fsevents] moved doc strings for polling emitter
Feb 11, 2021
4cbcacb
updated changelog
Feb 11, 2021
a0d231f
bump desktop-notifier to 3.1.2
Feb 11, 2021
4417257
[tests] record fsevents log
Feb 12, 2021
4c81529
[tests] use sync_lock instead of start / stop
Feb 12, 2021
1640cee
slightly relax performance requirement
Feb 12, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@

* Bumped survey to version >=3.2.2,<4.0.
* Bumped keyring to version >=22.
* Bumped watchdog to version >= 2.0.
* Added desktop-notifier dependency. This is spin-off from Maestral, built on
the code from the `notify` module.
* Removed bugsnag dependency.
Expand Down
5 changes: 2 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
install_requires = [
"alembic>=1.3",
"click>=7.1.1",
"desktop-notifier>=3.1",
"desktop-notifier>=3.1.2",
"dropbox>=10.9.0,<12.0",
"fasteners>=0.15",
"importlib_metadata;python_version<'3.8'",
Expand All @@ -24,8 +24,7 @@
"setuptools",
"sqlalchemy>=1.3",
"survey>=3.2.2,<4.0",
"watchdog>=0.10.0,<=0.10.3;sys_platform=='darwin'",
"watchdog>=0.10.0;sys_platform=='linux'",
"watchdog>=2.0",
]

gui_requires = [
Expand Down
3 changes: 2 additions & 1 deletion src/maestral/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,8 @@ def is_download(self) -> bool:
def __repr__(self):
return (
f"<{self.__class__.__name__}(direction={self.direction.name}, "
f"change_type={self.change_type.name}, dbx_path='{self.dbx_path}')>"
f"change_type={self.change_type.name}, item_type={self.item_type}, "
f"dbx_path='{self.dbx_path}')>"
)

@classmethod
Expand Down
86 changes: 6 additions & 80 deletions src/maestral/fsevents/__init__.py
Original file line number Diff line number Diff line change
@@ -1,97 +1,23 @@
# -*- coding: utf-8 -*-
"""
This module provides custom event emitters for the :obj:`watchdog` package that sort
file system events in an order which can be applied to reproduce the new state from the
old state. This is only required for event emitters which internally use
This module provides a custom polling file system event emitter for the
:obj:`watchdog` package that sorts file system events in an order which can be applied
to reproduce the new state from the old state. This is only required for the polling
emitter which uses period directory snapshots and compares them with a
:class:`watchdog.utils.dirsnapshot.DirectorySnapshotDiff` to generate file system
events. This includes the macOS FSEvents emitter and the Polling emitter but not inotify
emitters.

Looking at the source code for :class:`watchdog.utils.dirsnapshot.DirectorySnapshotDiff`,
the event types are categorised as follows:

* Created event: The inode is unique to the new snapshot. The path may be unique to the
new snapshot or exist in both. In the second case, there will be a preceding Deleted
event or a Moved event with the path as starting point (the old item was deleted or
moved away).

* Deleted event: The inode is unique to the old snapshot. The path may be unique to the
old snapshot or exist in both. In the second case, there will be a subsequent Created
event or a Moved event with the path as end point (something else was created at or
moved to the location).

* Moved event: The inode exists in both snapshots but with different paths.

* Modified event: The inode exists in both snapshots and the mtime or file size are
different. DirectorySnapshotDiff will always use the inode’s path from the old
snapshot.

From the above classification, there can be at most two created/deleted/moved events
that share the same path in one snapshot diff:

* Deleted(path1) + Created(path1)
* Moved(path1, path2) + Created(path1)
* Deleted(path1) + Moved(path0, path1)

Any Modified event will come before a Moved event or stand alone. Modified events will
never be combined by themselves with created or deleted events because they require the
inode to be present in both snapshots.

From the above, we can achieve correct ordering for unique path by always adding Deleted
events to the queue first, Modified events second, Moved events third and Created events
last:

Deleted -> Modified -> Moved -> Created

The ordering won’t be correct between unrelated paths and between files and folder. The
first does not matter for syncing. We solve the second by assuming that when a directory
is deleted, so are its children. And before a child is created, its parent dircetory
must exist.

MovedEvents which are not unique (their paths appear in other events) will be split
into Deleted and Created events by Maestral.
events.
"""

import os
from typing import Union

from watchdog.utils import platform # type: ignore
from watchdog.utils import UnsupportedLibc


if platform.is_darwin():
from .fsevents import OrderedFSEventsObserver as Observer
elif platform.is_linux():
if platform.is_linux():
try:
from watchdog.observers.inotify import InotifyObserver as Observer # type: ignore
except UnsupportedLibc:
from .polling import OrderedPollingObserver as Observer
else:
from watchdog.observers import Observer # type: ignore


# patch encoding / decoding of paths in watchdog


def _patched_decode(path: Union[str, bytes]) -> str:
if isinstance(path, bytes):
return os.fsdecode(path)
return path


def _patched_encode(path: Union[str, bytes]) -> bytes:
if isinstance(path, str):
return os.fsencode(path)
return path


try:
from watchdog.utils import unicode_paths
except ImportError:
pass
else:
unicode_paths.decode = _patched_decode
unicode_paths.encode = _patched_encode


__all__ = ["Observer"]
99 changes: 0 additions & 99 deletions src/maestral/fsevents/fsevents.py

This file was deleted.

48 changes: 46 additions & 2 deletions src/maestral/fsevents/polling.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,50 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
"""
Looking at the source code for :class:`watchdog.utils.dirsnapshot.DirectorySnapshotDiff`,
the event types are categorised as follows:

* Created event: The inode is unique to the new snapshot. The path may be unique to the
new snapshot or exist in both. In the second case, there will be a preceding Deleted
event or a Moved event with the path as starting point (the old item was deleted or
moved away).

* Deleted event: The inode is unique to the old snapshot. The path may be unique to the
old snapshot or exist in both. In the second case, there will be a subsequent Created
event or a Moved event with the path as end point (something else was created at or
moved to the location).

* Moved event: The inode exists in both snapshots but with different paths.

* Modified event: The inode exists in both snapshots and the mtime or file size are
different. DirectorySnapshotDiff will always use the inode’s path from the old
snapshot.

From the above classification, there can be at most two created/deleted/moved events
that share the same path in one snapshot diff:

* Deleted(path1) + Created(path1)
* Moved(path1, path2) + Created(path1)
* Deleted(path1) + Moved(path0, path1)

Any Modified event will come before a Moved event or stand alone. Modified events will
never be combined by themselves with created or deleted events because they require the
inode to be present in both snapshots.

From the above, we can achieve correct ordering for unique path by always adding Deleted
events to the queue first, Modified events second, Moved events third and Created events
last:

Deleted -> Modified -> Moved -> Created

The ordering won’t be correct between unrelated paths and between files and folder. The
first does not matter for syncing. We solve the second by assuming that when a directory
is deleted, so are its children. And before a child is created, its parent directory
must exist.

MovedEvents which are not unique (their paths appear in other events) will be split
into Deleted and Created events by Maestral.
"""

# Copyright 2011 Yesudeep Mangalapilly <yesudeep@gmail.com>
# Copyright 2012 Google, Inc.
#
Expand Down
2 changes: 1 addition & 1 deletion src/maestral/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1071,7 +1071,7 @@ def _remove_after_excluded(self, dbx_path: str) -> None:
pass
else:
event_cls = DirDeletedEvent if osp.isdir(local_path) else FileDeletedEvent
with self.monitor.fs_event_handler.ignore(event_cls(local_path)):
with self.monitor.sync.fs_events.ignore(event_cls(local_path)):
delete(local_path)

def include_item(self, dbx_path: str) -> None:
Expand Down
Loading