diff --git a/Changelog.draft.rst b/Changelog.draft.rst index 909ebd6..1b1e644 100644 --- a/Changelog.draft.rst +++ b/Changelog.draft.rst @@ -13,6 +13,13 @@ others. New Features ------------ +* Implement per-account locking, so that it will possible to sync + different accounts at the same time. The old global lock is still in + place for backward compatibility reasons (to be able to run old and + new versions of OfflineImap concurrently) and will be removed in the + future. Starting with this version, OfflineImap will be + forward-compatible with the per-account locking style. + Changes ------- diff --git a/offlineimap/accounts.py b/offlineimap/accounts.py index 428cbd8..5a2a120 100644 --- a/offlineimap/accounts.py +++ b/offlineimap/accounts.py @@ -25,6 +25,11 @@ import os from sys import exc_info import traceback +try: + import fcntl +except: + pass # ok if this fails, we can do without + def getaccountlist(customconfig): return customconfig.getsectionlist('Account') @@ -159,6 +164,35 @@ class SyncableAccount(Account): functions :meth:`syncrunner`, :meth:`sync`, :meth:`syncfolders`, used for syncing.""" + def __init__(self, *args, **kwargs): + Account.__init__(self, *args, **kwargs) + self._lockfd = None + self._lockfilepath = os.path.join(self.config.getmetadatadir(), + "%s.lock" % self) + + def lock(self): + """Lock the account, throwing an exception if it is locked already""" + self._lockfd = open(self._lockfilepath, 'w') + try: + fcntl.lockf(self._lockfd, fcntl.LOCK_EX|fcntl.LOCK_NB) + except NameError: + #fcntl not available (Windows), disable file locking... :( + pass + except IOError: + self._lockfd.close() + raise OfflineImapError("Could not lock account %s." % self, + OfflineImapError.ERROR.REPO) + + def unlock(self): + """Unlock the account, deleting the lock file""" + #If we own the lock file, delete it + if self._lockfd and not self._lockfd.closed: + self._lockfd.close() + try: + os.unlink(self._lockfilepath) + except OSError: + pass #Failed to delete for some reason. + def syncrunner(self): self.ui.registerthread(self.name) self.ui.acct(self.name) @@ -175,6 +209,7 @@ class SyncableAccount(Account): while looping: try: try: + self.lock() self.sync() except (KeyboardInterrupt, SystemExit): raise @@ -194,6 +229,7 @@ class SyncableAccount(Account): if self.refreshperiod: looping = 3 finally: + self.unlock() if looping and self.sleeper() >= 2: looping = 0 self.ui.acctdone(self.name) diff --git a/offlineimap/init.py b/offlineimap/init.py index 93b7224..f7b2eef 100644 --- a/offlineimap/init.py +++ b/offlineimap/init.py @@ -30,14 +30,6 @@ from offlineimap.ui import UI_LIST, setglobalui, getglobalui from offlineimap.CustomConfig import CustomConfigParser -try: - import fcntl - hasfcntl = 1 -except: - hasfcntl = 0 - -lockfd = None - class OfflineImap: """The main class that encapsulates the high level use of OfflineImap. @@ -46,17 +38,6 @@ class OfflineImap: oi = OfflineImap() oi.run() """ - def lock(self, config, ui): - global lockfd, hasfcntl - if not hasfcntl: - return - lockfd = open(config.getmetadatadir() + "/lock", "w") - try: - fcntl.flock(lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB) - except IOError: - ui.locked() - ui.terminate(1) - def run(self): """Parse the commandline and invoke everything""" @@ -253,7 +234,6 @@ class OfflineImap: config.set(section, "folderfilter", folderfilter) config.set(section, "folderincludes", folderincludes) - self.lock(config, ui) self.config = config def sigterm_handler(signum, frame):