#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
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')
self.localeval = config.getlocaleval()
self.ui = UIBase.getglobalui()
self.refreshperiod = self.getconffloat('autorefresh', 0.0)
+ self.quicknum = 0
if self.refreshperiod == 0.0:
self.refreshperiod = None
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.
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()
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
+ elif quickconfig > 0:
+ if self.quicknum == 0 or self.quicknum > quickconfig:
+ self.quicknum = 1
+ quick = False
+ else:
+ self.quicknum = self.quicknum + 1
+ quick = True
+ else:
+ quick = False
+
try:
remoterepos = self.remoterepos
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))
- 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()
localrepos.holdordropconnections()
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
def syncfolder(accountname, remoterepos, remotefolder, localrepos,
- statusrepos):
+ statusrepos, quick):
global mailboxes
ui = UIBase.getglobalui()
ui.registerthread(accountname)
replace(remoterepos.getsep(), localrepos.getsep()))
# Write the mailboxes
mbnames.add(accountname, localfolder.getvisiblename())
- # Load local folder
- ui.syncingfolder(remoterepos, remotefolder, localrepos, localfolder)
- ui.loadmessagelist(localrepos, localfolder)
- localfolder.cachemessagelist()
- ui.messagelistloaded(localrepos, localfolder, len(localfolder.getmessagelist().keys()))
-
# Load status folder.
statusfolder = statusrepos.getfolder(remotefolder.getvisiblename().\
statusfolder.cachemessagelist()
+ if quick:
+ if not localfolder.quickchanged(statusfolder) \
+ and not remotefolder.quickchanged(statusfolder):
+ ui.skippingfolder(remotefolder)
+ localrepos.restore_atime()
+ return
+
+ # Load local folder
+ ui.syncingfolder(remoterepos, remotefolder, localrepos, localfolder)
+ ui.loadmessagelist(localrepos, localfolder)
+ localfolder.cachemessagelist()
+ ui.messagelistloaded(localrepos, localfolder, len(localfolder.getmessagelist().keys()))
+
# If either the local or the status folder has messages and there is a UID
# validity problem, warn and abort. If there are no messages, UW IMAPd
# loses UIDVALIDITY. But we don't really need it if both local folders are
# empty. So, in that case, just save it off.
if len(localfolder.getmessagelist()) or len(statusfolder.getmessagelist()):
if not localfolder.isuidvalidityok():
- ui.validityproblem(localfolder, localfolder.getsaveduidvalidity(),
- localfolder.getuidvalidity())
+ ui.validityproblem(localfolder)
+ localrepos.restore_atime()
return
if not remotefolder.isuidvalidityok():
- ui.validityproblem(remotefolder, remotefolder.getsaveduidvalidity(),
- remotefolder.getuidvalidity())
+ ui.validityproblem(remotefolder)
+ localrepos.restore_atime()
return
else:
localfolder.saveuidvalidity()
ui.syncingmessages(localrepos, localfolder, statusrepos, statusfolder)
localfolder.syncmessagesto(statusfolder)
statusfolder.save()
+ localrepos.restore_atime()