X-Git-Url: https://code.delx.au/offlineimap/blobdiff_plain/7b4e651d1282a50a20614074f731dd22f3ec642f..HEAD:/offlineimap/accounts.py diff --git a/offlineimap/accounts.py b/offlineimap/accounts.py index 8e96347..a493396 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 foldernr in range(len(self.folders)): + # requeue folder + self.folders[foldernr][1] = 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.append([folder, True]) + finally: + self.folderlock.release() + def clearfolders(self): + self.folderlock.acquire() + try: + for folder, queued in self.folders: + if queued: + # some folders still in queue + return False + self.folders[:] = [] + return True + finally: + self.folderlock.release() + def queuedfolders(self): + self.folderlock.acquire() + try: + dirty = True + while dirty: + dirty = False + for foldernr, (folder, queued) in enumerate(self.folders): + if queued: + # mark folder as no longer queued + self.folders[foldernr][1] = 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') @@ -45,6 +109,7 @@ class Account(CustomConfig.ConfigHelperMixin): self.localeval = config.getlocaleval() self.ui = UIBase.getglobalui() self.refreshperiod = self.getconffloat('autorefresh', 0.0) + self.quickrefreshcount = self.getconfint('quick', 0) self.quicknum = 0 if self.refreshperiod == 0.0: self.refreshperiod = None @@ -61,7 +126,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. @@ -80,22 +145,30 @@ class Account(CustomConfig.ConfigHelperMixin): for item in kaobjs: item.startkeepalive() - - refreshperiod = int(self.refreshperiod * 60) - sleepresult = self.ui.sleep(refreshperiod) - if sleepresult == 2: - # Cancel keep-alive, but don't bother terminating threads - for item in kaobjs: - item.stopkeepalive(abrupt = 1) - return sleepresult - else: - # Cancel keep-alive and wait for thread to terminate. - for item in kaobjs: - item.stopkeepalive(abrupt = 0) - return sleepresult + + sleeptime = int(self.refreshperiod * 60) + if (self.quickrefreshcount > 0): + sleeptime = int(sleeptime / self.quickrefreshcount) + +# 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(sleeptime, siglistener) + sleepresult = self.ui.sleep(sleeptime, 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() @@ -111,22 +184,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 @@ -147,19 +223,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() @@ -167,6 +249,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 @@ -214,11 +313,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()