X-Git-Url: https://code.delx.au/offlineimap/blobdiff_plain/2a852a8f484cdaa8712d203262c26b33057143fb..a8a2a87e1e75136bc1a657048bb83968977ca8ab:/offlineimap/accounts.py diff --git a/offlineimap/accounts.py b/offlineimap/accounts.py index a86485d..7a20c37 100644 --- a/offlineimap/accounts.py +++ b/offlineimap/accounts.py @@ -19,8 +19,72 @@ from offlineimap import threadutil, mbnames, CustomConfig import offlineimap.repository.Base, offlineimap.repository.LocalStatus from offlineimap.ui import UIBase from offlineimap.threadutil import InstanceLimitedThread, ExitNotifyThread -from threading import Event +from subprocess import Popen, PIPE +from threading import Event, Lock import os +from Queue import Queue, Empty + +class SigListener(Queue): + def __init__(self): + self.folderlock = Lock() + self.folders = None + Queue.__init__(self, 20) + def put_nowait(self, sig): + self.folderlock.acquire() + try: + if sig == 1: + if self.folders is None or not self.autorefreshes: + # folders haven't yet been added, or this account is once-only; drop signal + return + elif self.folders: + for folder in self.folders: + # requeue folder + self.folders[folder] = True + self.quick = False + return + # else folders have already been cleared, put signal... + finally: + self.folderlock.release() + Queue.put_nowait(self, sig) + def addfolders(self, remotefolders, autorefreshes, quick): + self.folderlock.acquire() + try: + self.folders = {} + self.quick = quick + self.autorefreshes = autorefreshes + for folder in remotefolders: + # new folders are queued + self.folders[folder] = True + finally: + self.folderlock.release() + def clearfolders(self): + self.folderlock.acquire() + try: + for folder in self.folders: + if self.folders[folder]: + # some folders still in queue + return False + self.folders.clear() + return True + finally: + self.folderlock.release() + def queuedfolders(self): + self.folderlock.acquire() + try: + dirty = True + while dirty: + dirty = False + for folder in self.folders: + if self.folders[folder]: + # mark folder as no longer queued + self.folders[folder] = False + dirty = True + quick = self.quick + self.folderlock.release() + yield (folder, quick) + self.folderlock.acquire() + finally: + self.folderlock.release() def getaccountlist(customconfig): return customconfig.getsectionlist('Account') @@ -61,7 +125,7 @@ class Account(CustomConfig.ConfigHelperMixin): def getsection(self): return 'Account ' + self.getname() - def sleeper(self): + def sleeper(self, siglistener): """Sleep handler. Returns same value as UIBase.sleep: 0 if timeout expired, 1 if there was a request to cancel the timer, and 2 if there is a request to abort the program. @@ -82,14 +146,25 @@ class Account(CustomConfig.ConfigHelperMixin): item.startkeepalive() refreshperiod = int(self.refreshperiod * 60) - sleepresult = self.ui.sleep(refreshperiod) +# try: +# sleepresult = siglistener.get_nowait() +# # retrieved signal before sleep started +# if sleepresult == 1: +# # catching signal 1 here means folders were cleared before signal was posted +# pass +# except Empty: +# sleepresult = self.ui.sleep(refreshperiod, siglistener) + sleepresult = self.ui.sleep(refreshperiod, siglistener) + if sleepresult == 1: + self.quicknum = 0 + # Cancel keepalive for item in kaobjs: item.stopkeepalive() return sleepresult class AccountSynchronizationMixin: - def syncrunner(self): + def syncrunner(self, siglistener): self.ui.registerthread(self.name) self.ui.acct(self.name) accountmetadata = self.getaccountmeta() @@ -105,22 +180,25 @@ class AccountSynchronizationMixin: self.statusrepos = offlineimap.repository.LocalStatus.LocalStatusRepository(self.getconf('localrepository'), self) if not self.refreshperiod: - self.sync() + self.sync(siglistener) self.ui.acctdone(self.name) return looping = 1 while looping: - self.sync() - looping = self.sleeper() != 2 + self.sync(siglistener) + looping = self.sleeper(siglistener) != 2 self.ui.acctdone(self.name) def getaccountmeta(self): return os.path.join(self.metadatadir, 'Account-' + self.name) - def sync(self): + def sync(self, siglistener): # We don't need an account lock because syncitall() goes through # each account once, then waits for all to finish. + hook = self.getconf('presynchook', '') + self.callhook(hook) + quickconfig = self.getconfint('quick', 0) if quickconfig < 0: quick = True @@ -141,19 +219,25 @@ class AccountSynchronizationMixin: self.ui.syncfolders(remoterepos, localrepos) remoterepos.syncfoldersto(localrepos, [statusrepos]) - folderthreads = [] - for remotefolder in remoterepos.getfolders(): - thread = InstanceLimitedThread(\ - instancename = 'FOLDER_' + self.remoterepos.getname(), - target = syncfolder, - name = "Folder sync %s[%s]" % \ - (self.name, remotefolder.getvisiblename()), - args = (self.name, remoterepos, remotefolder, localrepos, - statusrepos, quick)) - thread.setDaemon(1) - thread.start() - folderthreads.append(thread) - threadutil.threadsreset(folderthreads) + siglistener.addfolders(remoterepos.getfolders(), bool(self.refreshperiod), quick) + + while True: + folderthreads = [] + for remotefolder, quick in siglistener.queuedfolders(): + thread = InstanceLimitedThread(\ + instancename = 'FOLDER_' + self.remoterepos.getname(), + target = syncfolder, + name = "Folder sync %s[%s]" % \ + (self.name, remotefolder.getvisiblename()), + args = (self.name, remoterepos, remotefolder, localrepos, + statusrepos, quick)) + thread.setDaemon(1) + thread.start() + folderthreads.append(thread) + threadutil.threadsreset(folderthreads) + if siglistener.clearfolders(): + break + mbnames.sort(self.name, remoterepos.foldersort) mbnames.write() localrepos.forgetfolders() remoterepos.forgetfolders() @@ -161,6 +245,23 @@ class AccountSynchronizationMixin: remoterepos.holdordropconnections() finally: pass + + hook = self.getconf('postsynchook', '') + self.callhook(hook) + + def callhook(self, cmd): + if not cmd: + return + try: + self.ui.callhook("Calling hook: " + cmd) + p = Popen(cmd, shell=True, + stdin=PIPE, stdout=PIPE, stderr=PIPE, + close_fds=True) + r = p.communicate() + self.ui.callhook("Hook stdout: %s\nHook stderr:%s\n" % r) + self.ui.callhook("Hook return code: %d" % p.returncode) + except: + self.ui.warn("Exception occured while calling hook") class SyncableAccount(Account, AccountSynchronizationMixin): pass @@ -208,11 +309,11 @@ def syncfolder(accountname, remoterepos, remotefolder, localrepos, if len(localfolder.getmessagelist()) or len(statusfolder.getmessagelist()): if not localfolder.isuidvalidityok(): ui.validityproblem(localfolder) - localrepos.restore_atime() + localrepos.restore_atime() return if not remotefolder.isuidvalidityok(): ui.validityproblem(remotefolder) - localrepos.restore_atime() + localrepos.restore_atime() return else: localfolder.saveuidvalidity()