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)
+# 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.
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.write()
localrepos.forgetfolders()
remoterepos.forgetfolders()
from threading import *
import threading, socket
from getopt import getopt
+import signal
try:
import fcntl
lock(config, ui)
+ def sigterm_handler(signum, frame):
+ # die immediately
+ ui.terminate(errormsg="terminating...")
+ signal.signal(signal.SIGTERM,sigterm_handler)
+
try:
pidfd = open(config.getmetadatadir() + "/pid", "w")
pidfd.write(str(os.getpid()) + "\n")
else:
threadutil.initInstanceLimit(instancename,
config.getdefaultint('Repository ' + reposname, "maxconnections", 1))
+ siglisteners = []
+ def sig_handler(signum, frame):
+ if signum == signal.SIGUSR1:
+ # tell each account to do a full sync asap
+ signum = (1,)
+ elif signum == signal.SIGHUP:
+ # tell each account to die asap
+ signum = (2,)
+ elif signum == signal.SIGUSR2:
+ # tell each account to do a full sync asap, then die
+ signum = (1, 2)
+ # one listener per account thread (up to maxsyncaccounts)
+ for listener in siglisteners:
+ for sig in signum:
+ listener.put_nowait(sig)
+ signal.signal(signal.SIGHUP,sig_handler)
+ signal.signal(signal.SIGUSR1,sig_handler)
+ signal.signal(signal.SIGUSR2,sig_handler)
threadutil.initexitnotify()
t = ExitNotifyThread(target=syncmaster.syncitall,
name='Sync Runner',
kwargs = {'accounts': syncaccounts,
- 'config': config})
+ 'config': config,
+ 'siglisteners': siglisteners})
t.setDaemon(1)
t.start()
except:
from offlineimap import imapserver, repository, folder, mbnames, threadutil, version
from offlineimap.threadutil import InstanceLimitedThread, ExitNotifyThread
import offlineimap.accounts
-from offlineimap.accounts import SyncableAccount
+from offlineimap.accounts import SyncableAccount, SigListener
from offlineimap.ui import UIBase
import re, os, os.path, offlineimap, sys
from ConfigParser import ConfigParser
from threading import *
-def syncaccount(threads, config, accountname):
+def syncaccount(threads, config, accountname, siglisteners):
account = SyncableAccount(config, accountname)
+ siglistener = SigListener()
thread = InstanceLimitedThread(instancename = 'ACCOUNTLIMIT',
target = account.syncrunner,
- name = "Account sync %s" % accountname)
+ name = "Account sync %s" % accountname,
+ kwargs = {'siglistener': siglistener} )
+ # the Sync Runner thread is the only one that will mutate siglisteners
+ siglisteners.append(siglistener)
thread.setDaemon(1)
thread.start()
threads.add(thread)
-def syncitall(accounts, config):
+def syncitall(accounts, config, siglisteners):
currentThread().setExitMessage('SYNC_WITH_TIMER_TERMINATE')
ui = UIBase.getglobalui()
threads = threadutil.threadlist()
mbnames.init(config, accounts)
for accountname in accounts:
- syncaccount(threads, config, accountname)
+ syncaccount(threads, config, accountname, siglisteners)
# Wait for the threads to finish.
threads.reset()
s.gettf().setcolor('white')
s.__class__.__bases__[-1].callhook(s, msg)
- def sleep(s, sleepsecs):
+ def sleep(s, sleepsecs, siglistener):
s.gettf().setcolor('red')
s.getaccountframe().startsleep(sleepsecs)
- UIBase.sleep(s, sleepsecs)
+ return UIBase.sleep(s, sleepsecs, siglistener)
def sleeping(s, sleepsecs, remainingsecs):
if remainingsecs and s.gettf().getcolor() == 'black':
s.c.stop()
UIBase.mainException(s)
- def sleep(s, sleepsecs):
+ def sleep(s, sleepsecs, siglistener):
s.gettf().setcolor('red')
s._msg("Next sync in %d:%02d" % (sleepsecs / 60, sleepsecs % 60))
- BlinkenBase.sleep(s, sleepsecs)
+ return BlinkenBase.sleep(s, sleepsecs, siglistener)
if __name__ == '__main__':
x = Blinkenlights(None)
warntxt = 'warning'
sys.stderr.write(warntxt + ": " + str(msg) + "\n")
- def sleep(s, sleepsecs):
+ def sleep(s, sleepsecs, siglistener):
if s.verbose >= 0:
s._msg("Sleeping for %d:%02d" % (sleepsecs / 60, sleepsecs % 60))
- UIBase.sleep(s, sleepsecs)
+ return UIBase.sleep(s, sleepsecs, siglistener)
def sleeping(s, sleepsecs, remainingsecs):
if sleepsecs > 0:
import offlineimap.version
import re, time, sys, traceback, threading, thread
from StringIO import StringIO
+from Queue import Empty
debugtypes = {'imap': 'IMAP protocol debugging',
'maildir': 'Maildir repository debugging',
################################################## Other
- def sleep(s, sleepsecs):
+ def sleep(s, sleepsecs, siglistener):
"""This function does not actually output anything, but handles
the overall sleep, dealing with updates as necessary. It will,
however, call sleeping() which DOES output something.
abortsleep = 0
while sleepsecs > 0 and not abortsleep:
- abortsleep = s.sleeping(1, sleepsecs)
+ try:
+ abortsleep = siglistener.get_nowait()
+ # retrieved signal while sleeping: 1 means immediately resynch, 2 means immediately die
+ except Empty:
+ # no signal
+ abortsleep = s.sleeping(1, sleepsecs)
sleepsecs -= 1
s.sleeping(0, 0) # Done sleeping.
return abortsleep