From d2127030c2e5dee53559e190f7949c9a8c8b979f Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Thu, 18 May 2017 20:42:08 +0200 Subject: [PATCH] Use inode number in addition to mtime (#632) * Use inode number in addition to mtime * Changelog --- CHANGELOG.rst | 3 +++ vdirsyncer/storage/singlefile.py | 12 ++++++------ vdirsyncer/utils.py | 6 +++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f5b8b3d..af4ad0d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -26,6 +26,9 @@ Version 0.16.0 - **Vdirsyncer is now licensed under the 3-clause BSD license**, see :gh:`610`. - Vdirsyncer now includes experimental support for `EteSync `_, see :ghpr:`614`. +- Vdirsyncer now uses more filesystem metadata for determining whether an item + changed. You will notice a **possibly heavy CPU/IO spike on the first sync + after upgrading**. Version 0.15.0 ============== diff --git a/vdirsyncer/storage/singlefile.py b/vdirsyncer/storage/singlefile.py index b86a0db..17f224b 100644 --- a/vdirsyncer/storage/singlefile.py +++ b/vdirsyncer/storage/singlefile.py @@ -11,7 +11,7 @@ from atomicwrites import atomic_write from .base import Storage from .. import exceptions -from ..utils import checkfile, expand_path +from ..utils import checkfile, expand_path, get_etag_from_file from ..vobject import Item, join_collection, split_collection logger = logging.getLogger(__name__) @@ -88,7 +88,7 @@ class SingleFileStorage(Storage): _read_mode = 'rb' _items = None - _last_mtime = None + _last_etag = None def __init__(self, path, encoding='utf-8', **kwargs): super(SingleFileStorage, self).__init__(**kwargs) @@ -149,7 +149,7 @@ class SingleFileStorage(Storage): self._items = collections.OrderedDict() try: - self._last_mtime = os.path.getmtime(self.path) + self._last_etag = get_etag_from_file(self.path) with open(self.path, self._read_mode) as f: text = f.read().decode(self.encoding) except OSError as e: @@ -210,8 +210,8 @@ class SingleFileStorage(Storage): del self._items[href] def _write(self): - if self._last_mtime is not None and \ - self._last_mtime != os.path.getmtime(self.path): + if self._last_etag is not None and \ + self._last_etag != get_etag_from_file(self.path): raise exceptions.PreconditionFailed( 'Some other program modified the file {r!}. Re-run the ' 'synchronization and make sure absolutely no other program is ' @@ -224,7 +224,7 @@ class SingleFileStorage(Storage): f.write(text.encode(self.encoding)) finally: self._items = None - self._last_mtime = None + self._last_etag = None @contextlib.contextmanager def at_once(self): diff --git a/vdirsyncer/utils.py b/vdirsyncer/utils.py index db5b986..0ef355e 100644 --- a/vdirsyncer/utils.py +++ b/vdirsyncer/utils.py @@ -59,10 +59,10 @@ def uniq(s): def get_etag_from_file(f): - '''Get mtime-based etag from a filepath or file-like object. + '''Get etag from a filepath or file-like object. This function will flush/sync the file as much as necessary to obtain a - correct mtime. + correct value. ''' if hasattr(f, 'read'): f.flush() # Only this is necessary on Linux @@ -75,7 +75,7 @@ def get_etag_from_file(f): mtime = getattr(stat, 'st_mtime_ns', None) if mtime is None: mtime = stat.st_mtime - return '{:.9f}'.format(mtime) + return '{:.9f};{}'.format(mtime, stat.st_ino) def get_storage_init_specs(cls, stop_at=object):