Merge branch 'ss/remove-siglistener-class' into next

Conflicts:
	offlineimap/accounts.py
This commit is contained in:
Nicolas Sebrecht
2011-05-08 14:37:47 +02:00
8 changed files with 127 additions and 164 deletions

View File

@ -20,76 +20,11 @@ from offlineimap.repository import Repository
from offlineimap.ui import getglobalui
from offlineimap.threadutil import InstanceLimitedThread
from subprocess import Popen, PIPE
from threading import Lock
from threading import Event
import os
from Queue import Queue
import sys
import traceback
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()
except:
self.folderlock.release()
raise
self.folderlock.release()
def getaccountlist(customconfig):
return customconfig.getsectionlist('Account')
@ -110,6 +45,8 @@ class Account(CustomConfig.ConfigHelperMixin):
Most of the time you will actually want to use the derived
:class:`accounts.SyncableAccount` which contains all functions used
for syncing an account."""
#signal gets set when we should stop looping
abort_signal = Event()
def __init__(self, config, name):
"""
@ -144,13 +81,49 @@ class Account(CustomConfig.ConfigHelperMixin):
def getsection(self):
return 'Account ' + self.getname()
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.
@classmethod
def set_abort_event(cls, config, signum):
"""Set skip sleep/abort event for all accounts
Also, returns 100 if configured to not sleep at all."""
If we want to skip a current (or the next) sleep, or if we want
to abort an autorefresh loop, the main thread can use
set_abort_event() to send the corresponding signal. Signum = 1
implies that we want all accounts to abort or skip the current
or next sleep phase. Signum = 2 will end the autorefresh loop,
ie all accounts will return after they finished a sync.
This is a class method, it will send the signal to all accounts.
"""
if signum == 1:
# resync signal, set config option for all accounts
for acctsection in getaccountlist(config):
config.set('Account ' + acctsection, "skipsleep", '1')
elif signum == 2:
# don't autorefresh anymore
cls.abort_signal.set()
def get_abort_event(self):
"""Checks if an abort signal had been sent
If the 'skipsleep' config option for this account had been set,
with `set_abort_event(config, 1)` it will get cleared in this
function. Ie, we will only skip one sleep and not all.
:returns: True, if the main thread had called
:meth:`set_abort_event` earlier, otherwise 'False'.
"""
skipsleep = self.getconfboolean("skipsleep", 0)
if skipsleep:
self.config.set(self.getsection(), "skipsleep", '0')
return skipsleep or Account.abort_signal.is_set()
def sleeper(self):
"""Sleep if the account is set to autorefresh
:returns: 0:timeout expired, 1: canceled the timer,
2:request to abort the program,
100: if configured to not sleep at all.
"""
if not self.refreshperiod:
return 100
@ -165,22 +138,18 @@ class Account(CustomConfig.ConfigHelperMixin):
item.startkeepalive()
refreshperiod = int(self.refreshperiod * 60)
# 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
sleepresult = self.ui.sleep(refreshperiod, self)
# Cancel keepalive
for item in kaobjs:
item.stopkeepalive()
return sleepresult
if sleepresult:
if Account.abort_signal.is_set():
return 2
self.quicknum = 0
return 1
return 0
class SyncableAccount(Account):
@ -190,14 +159,13 @@ class SyncableAccount(Account):
functions :meth:`syncrunner`, :meth:`sync`, :meth:`syncfolders`,
used for syncing."""
def syncrunner(self, siglistener):
def syncrunner(self):
self.ui.registerthread(self.name)
self.ui.acct(self.name)
accountmetadata = self.getaccountmeta()
if not os.path.exists(accountmetadata):
os.mkdir(accountmetadata, 0700)
# get all three repositories
self.remoterepos = Repository(self, 'remote')
self.localrepos = Repository(self, 'local')
self.statusrepos = Repository(self, 'status')
@ -207,14 +175,14 @@ class SyncableAccount(Account):
while looping:
try:
try:
self.sync(siglistener)
self.sync()
except (KeyboardInterrupt, SystemExit):
raise
except OfflineImapError, e:
self.ui.warn(e.reason)
#stop looping and bubble up Exception if needed
# Stop looping and bubble up Exception if needed.
if e.severity >= OfflineImapError.ERROR.REPO:
if looping:
if looping:
looping -= 1
if e.severity >= OfflineImapError.ERROR.CRITICAL:
raise
@ -226,17 +194,21 @@ class SyncableAccount(Account):
if self.refreshperiod:
looping = 3
finally:
if self.sleeper(siglistener) >= 2:
if looping and self.sleeper() >= 2:
looping = 0
self.ui.acctdone(self.name)
def getaccountmeta(self):
return os.path.join(self.metadatadir, 'Account-' + self.name)
def sync(self, siglistener):
# We don't need an account lock because syncitall() goes through
# each account once, then waits for all to finish.
def sync(self):
"""Synchronize the account once, then return
Assumes that `self.remoterepos`, `self.localrepos`, and
`self.statusrepos` has already been populated, so it should only
be called from the :meth:`syncrunner` function.
"""
folderthreads = []
hook = self.getconf('presynchook', '')
self.callhook(hook)
@ -263,23 +235,20 @@ class SyncableAccount(Account):
self.ui.syncfolders(remoterepos, localrepos)
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]" % self,
args = (self.name, remoterepos, remotefolder, localrepos,
statusrepos, quick))
thread.setDaemon(1)
thread.start()
folderthreads.append(thread)
threadutil.threadsreset(folderthreads)
if siglistener.clearfolders():
break
# iterate through all folders on the remote repo and sync
for remotefolder in remoterepos.getfolders():
thread = InstanceLimitedThread(\
instancename = 'FOLDER_' + self.remoterepos.getname(),
target = syncfolder,
name = "Folder sync [%s]" % self,
args = (self.name, remoterepos, remotefolder, localrepos,
statusrepos, quick))
thread.setDaemon(1)
thread.start()
folderthreads.append(thread)
# wait for all threads to finish
for thr in folderthreads:
thr.join()
mbnames.write()
localrepos.forgetfolders()
remoterepos.forgetfolders()