]> code.delx.au - offlineimap/blobdiff - offlineimap/accounts.py
Sort mbnames for each account before writing
[offlineimap] / offlineimap / accounts.py
index 9f224d03d943cf10747aec8cba692fa56429883d..7a20c37908168f59c19c8ee4959b2a102c2a9365 100644 (file)
@@ -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,20 +146,25 @@ class Account(CustomConfig.ConfigHelperMixin):
             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
+#         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()
@@ -111,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
@@ -145,21 +217,27 @@ class AccountSynchronizationMixin:
             localrepos = self.localrepos
             statusrepos = self.statusrepos
             self.ui.syncfolders(remoterepos, localrepos)
-            remoterepos.syncfoldersto(localrepos)
-
-            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)
+            remoterepos.syncfoldersto(localrepos, [statusrepos])
+
+            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 +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
@@ -214,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()