Patch for error handling / separation of accounts etc.

Dear All,
I have made the attached patch to try and make offlineimap a bit more
stable in challenging situations.  It's extremely useful in slow
connection environments - but sometimes if one account had the wrong
password or the connection went down then unfortunately the whole
program would crash.

I have tested this on our connection and tried throwing at it just about
every situation - connection, up down, up, down again, change password,
error whilst copying one message, etc.  I have been running this patch
for the last 5 days or so syncing 6 accounts at the moment...  It seems
to work and stay alive nicely (even if your connection does not)...

Hope that this can go in for the next release... Please let me know if
anyone notices any problems with this...

Regards,

-Mike

-- Attached file included as plaintext by Ecartis --
-- File: submit

From 1d6777cab23637eb830031c7cab0ae9b8589afd6 Mon Sep 17 00:00:00 2001
From: mike <mike@mikelaptop.(none)>
Date: Mon, 24 Aug 2009 19:37:59 +0430
Subject: [PATCH] This patch attempts to introduce a little more error handling - e.g.
 if one account has an error because of a changed password or something
 that should not affect the other accounts.

Specifically:
If one sync run has an issue this is in a try-except clause - if it
has an auto refresh period the thread will sleep and try again - this
could be quite useful in the event of the connection going down for a
little while, changed password etc.

If one folder cannot be created an error message will be displayed through
the UI and the program will continue (e.g. permission denied to create a folder)

If one message does not want to copy for whatever resaon an error message
will be displayed through the UI and at least the other messages will
be copied

If one folder run has an exception then the others will still run
This commit is contained in:
Mike Dawson 2009-08-24 19:47:57 +04:30 committed by John Goerzen
parent 43ead072a1
commit 30344587d9
4 changed files with 221 additions and 164 deletions

View File

@ -23,6 +23,7 @@ from subprocess import Popen, PIPE
from threading import Event, Lock
import os
from Queue import Queue, Empty
import sys
class SigListener(Queue):
def __init__(self):
@ -181,8 +182,12 @@ class AccountSynchronizationMixin:
#might need changes here to ensure that one account sync does not crash others...
if not self.refreshperiod:
try:
self.sync(siglistener)
except:
self.ui.warn("Error occured attempting to sync account " + self.name \
+ ": " + str(sys.exc_info()[1]))
finally:
self.ui.acctdone(self.name)
return
@ -190,7 +195,12 @@ class AccountSynchronizationMixin:
looping = 1
while looping:
try:
self.sync(siglistener)
except:
self.ui.warn("Error occured attempting to sync account " + self.name \
+ ": " + str(sys.exc_info()[1]))
finally:
looping = self.sleeper(siglistener) != 2
self.ui.acctdone(self.name)
@ -276,6 +286,7 @@ def syncfolder(accountname, remoterepos, remotefolder, localrepos,
global mailboxes
ui = UIBase.getglobalui()
ui.registerthread(accountname)
try:
# Load local folder.
localfolder = localrepos.\
getfolder(remotefolder.getvisiblename().\
@ -355,4 +366,6 @@ def syncfolder(accountname, remoterepos, remotefolder, localrepos,
localfolder.syncmessagesto(statusfolder)
statusfolder.save()
localrepos.restore_atime()
except:
ui.warn("ERROR in syncfolder for " + accountname + " folder " + \
remotefolder.getvisiblename() +" : " +str(sys.exc_info()[1]))

View File

@ -21,6 +21,7 @@ from offlineimap import threadutil
from offlineimap.threadutil import InstanceLimitedThread
from offlineimap.ui import UIBase
import os.path, re
import sys
class BaseFolder:
def __init__(self):
@ -266,12 +267,14 @@ class BaseFolder:
# synced to the status cache. This is only a problem with
# self.getmessage(). So, don't call self.getmessage unless
# really needed.
try:
if register:
UIBase.getglobalui().registerthread(self.getaccountname())
UIBase.getglobalui().copyingmessage(uid, self, applyto)
message = ''
# If any of the destinations actually stores the message body,
# load it up.
for object in applyto:
if object.storesmessages():
message = self.getmessage(uid)
@ -285,6 +288,9 @@ class BaseFolder:
self.savemessage(newuid, message, flags, rtime)
self.deletemessage(uid)
uid = newuid
except:
UIBase.getglobalui().warn("ERROR attempting to copy message " + str(uid) \
+ " for account " + self.getaccountname() + ":" + str(sys.exc_info()[1]))
def syncmessagesto_copy(self, dest, applyto):
@ -385,14 +391,29 @@ class BaseFolder:
if applyto == None:
applyto = [dest]
try:
self.syncmessagesto_neguid(dest, applyto)
except:
UIBase.getglobalui().warn("ERROR attempting to handle negative uids " \
+ "for account " + self.getaccountname() + ":" + str(sys.exc_info()[1]))
#all threads launched here are in try / except clauses when they copy anyway...
self.syncmessagesto_copy(dest, applyto)
try:
self.syncmessagesto_delete(dest, applyto)
except:
UIBase.getglobalui().warn("ERROR attempting to delete messages " \
+ "for account " + self.getaccountname() + ":" + str(sys.exc_info()[1]))
# Now, the message lists should be identical wrt the uids present.
# (except for potential negative uids that couldn't be placed
# anywhere)
try:
self.syncmessagesto_flags(dest, applyto)
except:
UIBase.getglobalui().warn("ERROR attempting to sync flags " \
+ "for account " + self.getaccountname() + ":" + str(sys.exc_info()[1]))

View File

@ -246,7 +246,11 @@ class IMAPServer:
self.connectionlock.release() # Release until need to modify data
""" Must be careful here that if we fail we should bail out gracefully
and release locks / threads so that the next attempt can try...
"""
success = 0
try:
while not success:
# Generate a new connection.
if self.tunnel:
@ -277,7 +281,8 @@ class IMAPServer:
'GSSAPI Authentication failed')
else:
self.gssapi = True
self.password = None
#if we do self.password = None then the next attempt cannot try...
#self.password = None
if not self.gssapi:
if 'AUTH=CRAM-MD5' in imapobj.capabilities:
@ -294,7 +299,8 @@ class IMAPServer:
self.goodpassword = self.password
except imapobj.error, val:
self.passworderror = str(val)
self.password = None
raise
#self.password = None
if self.delim == None:
listres = imapobj.list(self.reference, '""')[1]
@ -312,6 +318,16 @@ class IMAPServer:
self.lastowner[imapobj] = thread.get_ident()
self.connectionlock.release()
return imapobj
except:
"""If we are here then we did not succeed in getting a connection -
we should clean up and then re-raise the error..."""
self.semaphore.release()
#Make sure that this can be retried the next time...
self.passworderror = None
if(self.connectionlock.locked()):
self.connectionlock.release()
raise
def connectionwait(self):
"""Waits until there is a connection available. Note that between

View File

@ -17,7 +17,9 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
from offlineimap import CustomConfig
from offlineimap.ui import UIBase
import os.path
import sys
def LoadRepository(name, account, reqtype):
from offlineimap.repository.Gmail import GmailRepository
@ -152,9 +154,14 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
for key in srchash.keys():
if not key in desthash:
try:
dest.makefolder(key)
for copyfolder in copyfolders:
copyfolder.makefolder(key.replace(dest.getsep(), copyfolder.getsep()))
except:
UIBase.getglobalui().warn("ERROR Attempting to make folder " \
+ key + ":" +str(sys.exc_info()[1]))
#
# Find deleted folders.