From 253d9f7e55a76d7e5edc420562b8d5ce738a1ead Mon Sep 17 00:00:00 2001 From: Ajordat Date: Mon, 2 Dec 2019 11:42:44 +0100 Subject: [PATCH 1/4] Added parameter `ignore_device` to constructor of DirectorySnapshotDiff. --- src/watchdog/utils/dirsnapshot.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/watchdog/utils/dirsnapshot.py b/src/watchdog/utils/dirsnapshot.py index d18dbbe9..837f787c 100644 --- a/src/watchdog/utils/dirsnapshot.py +++ b/src/watchdog/utils/dirsnapshot.py @@ -70,13 +70,18 @@ class DirectorySnapshotDiff(object): :class:`DirectorySnapshot` """ - def __init__(self, ref, snapshot): + def __init__(self, ref, snapshot, ignore_device=False): created = snapshot.paths - ref.paths deleted = ref.paths - snapshot.paths + if ignore_device: + def get_inode(directory, full_path): return directory.inode(full_path)[0] + else: + def get_inode(directory, full_path): return directory.inode(full_path) + # check that all unchanged paths have the same inode for path in ref.paths & snapshot.paths: - if ref.inode(path) != snapshot.inode(path): + if get_inode(ref, path) != get_inode(snapshot, path): created.add(path) deleted.add(path) @@ -101,7 +106,7 @@ def __init__(self, ref, snapshot): # first check paths that have not moved modified = set() for path in ref.paths & snapshot.paths: - if ref.inode(path) == snapshot.inode(path): + if get_inode(ref, path) == get_inode(snapshot, path): if ref.mtime(path) != snapshot.mtime(path) or ref.size(path) != snapshot.size(path): modified.add(path) From 3ef55329e84eef2a4a2b4e9e4c61b14d4ed8c093 Mon Sep 17 00:00:00 2001 From: Ajordat Date: Mon, 2 Dec 2019 12:00:24 +0100 Subject: [PATCH 2/4] Fixed E704. --- src/watchdog/utils/dirsnapshot.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/watchdog/utils/dirsnapshot.py b/src/watchdog/utils/dirsnapshot.py index 837f787c..cc65391e 100644 --- a/src/watchdog/utils/dirsnapshot.py +++ b/src/watchdog/utils/dirsnapshot.py @@ -75,9 +75,11 @@ def __init__(self, ref, snapshot, ignore_device=False): deleted = ref.paths - snapshot.paths if ignore_device: - def get_inode(directory, full_path): return directory.inode(full_path)[0] + def get_inode(directory, full_path): + return directory.inode(full_path)[0] else: - def get_inode(directory, full_path): return directory.inode(full_path) + def get_inode(directory, full_path): + return directory.inode(full_path) # check that all unchanged paths have the same inode for path in ref.paths & snapshot.paths: From 0639c02c8c24a176733cbabe0de0d029c8ca3ebf Mon Sep 17 00:00:00 2001 From: Ajordat Date: Mon, 2 Dec 2019 17:56:21 +0100 Subject: [PATCH 3/4] Added docstring. --- src/watchdog/utils/dirsnapshot.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/watchdog/utils/dirsnapshot.py b/src/watchdog/utils/dirsnapshot.py index cc65391e..40ff95bc 100644 --- a/src/watchdog/utils/dirsnapshot.py +++ b/src/watchdog/utils/dirsnapshot.py @@ -68,6 +68,16 @@ class DirectorySnapshotDiff(object): with the reference snapshot. :type snapshot: :class:`DirectorySnapshot` + :param ignore_device: + A boolean indicating whether to ignore the device id or not. + By default, a file may be uniquely identified by a combination of its first + inode and its device id. The problem is that the device id may (or may not) + change between system boots. This problem would cause the DirectorySnapshotDiff + to think a file has been deleted and created again but it would be the + exact same file. + Set to True only if you are sure you will always use the same device. + :type ignore_device: + :class:`bool` """ def __init__(self, ref, snapshot, ignore_device=False): From d9e293f694f0cee15dadfb2ef637c2d3b39dc4cc Mon Sep 17 00:00:00 2001 From: Ajordat Date: Tue, 3 Dec 2019 12:38:51 +0100 Subject: [PATCH 4/4] Added test for `ignore_device` parameter on DirectorySnapshotDiff constructor. --- tests/test_snapshot_diff.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/test_snapshot_diff.py b/tests/test_snapshot_diff.py index 9c881c3d..9cae27d3 100644 --- a/tests/test_snapshot_diff.py +++ b/tests/test_snapshot_diff.py @@ -156,3 +156,37 @@ def walk(self, root): # Children of a/b/ are no more accessible and so removed in the new snapshot assert diff.dirs_deleted == [(p('a', 'b', 'c'))] + + +def test_ignore_device(monkeypatch, p): + # Create a file and take a snapshot. + touch(p('file')) + ref = DirectorySnapshot(p('')) + wait() + + def inode(self, path): + # This function will always return a different device_id, + # even for the same file. + result = inode_orig(self, path) + inode.times += 1 + return result[0], result[1] + inode.times + inode.times = 0 + + # Set the custom inode function. + inode_orig = DirectorySnapshot.inode + monkeypatch.setattr(DirectorySnapshot, 'inode', inode) + + # If we make the diff of the same directory, since by default the + # DirectorySnapshotDiff compares the snapshots using the device_id (and it will + # be different), it thinks that the same file has been deleted and created again. + snapshot = DirectorySnapshot(p('')) + diff_with_device = DirectorySnapshotDiff(ref, snapshot) + assert diff_with_device.files_deleted == [(p('file'))] + assert diff_with_device.files_created == [(p('file'))] + + # Otherwise, if we choose to ignore the device, the file will not be detected as + # deleted and re-created. + snapshot = DirectorySnapshot(p('')) + diff_without_device = DirectorySnapshotDiff(ref, snapshot, ignore_device=True) + assert diff_without_device.files_deleted == [] + assert diff_without_device.files_created == []