Merge branch 'next'
This commit is contained in:
commit
ce901800ff
@ -20,6 +20,7 @@ Bug Fixes
|
|||||||
---------
|
---------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Pending for the next major release
|
Pending for the next major release
|
||||||
==================================
|
==================================
|
||||||
|
|
||||||
|
@ -12,6 +12,38 @@ ChangeLog
|
|||||||
releases announces.
|
releases announces.
|
||||||
|
|
||||||
|
|
||||||
|
OfflineIMAP v6.3.4-rc3 (2011-07-07)
|
||||||
|
===================================
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-----
|
||||||
|
|
||||||
|
Here is a surprising release. :-)
|
||||||
|
|
||||||
|
As expected we have a lot bug fixes in this round (see git log for details),
|
||||||
|
including a fix for a bug we had for ages (details below) which is a very good
|
||||||
|
news.
|
||||||
|
|
||||||
|
What makes this cycle so unusual is that I merged a feature to support StartTLS
|
||||||
|
automatically (thanks Sebastian!). Another very good news.
|
||||||
|
|
||||||
|
We usually don't do much changes so late in a cycle. Now, things are highly
|
||||||
|
calming down and I hope a lot of people will test this release. Next one could
|
||||||
|
be the stable!
|
||||||
|
|
||||||
|
New Features
|
||||||
|
------------
|
||||||
|
|
||||||
|
* Added StartTLS support, it will automatically be used if the server
|
||||||
|
supports it.
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
* We protect more robustly against asking for inexistent messages from the
|
||||||
|
IMAP server, when someone else deletes or moves messages while we sync.
|
||||||
|
|
||||||
|
|
||||||
OfflineIMAP v6.3.4-rc2 (2011-06-15)
|
OfflineIMAP v6.3.4-rc2 (2011-06-15)
|
||||||
===================================
|
===================================
|
||||||
|
|
||||||
|
@ -1,22 +1,21 @@
|
|||||||
__all__ = ['OfflineImap']
|
__all__ = ['OfflineImap']
|
||||||
|
|
||||||
__productname__ = 'OfflineIMAP'
|
__productname__ = 'OfflineIMAP'
|
||||||
__version__ = "6.3.4-rc2"
|
__version__ = "6.3.4-rc3"
|
||||||
__copyright__ = "Copyright (C) 2002 - 2010 John Goerzen"
|
__copyright__ = "Copyright 2002-2011 John Goerzen & contributors"
|
||||||
__author__ = "John Goerzen"
|
__author__ = "John Goerzen"
|
||||||
__author_email__= "john@complete.org"
|
__author_email__= "john@complete.org"
|
||||||
__description__ = "Disconnected Universal IMAP Mail Synchronization/Reader Support"
|
__description__ = "Disconnected Universal IMAP Mail Synchronization/Reader Support"
|
||||||
|
__license__ = "Licensed under the GNU GPL v2+ (v2 or any later version)"
|
||||||
__bigcopyright__ = """%(__productname__)s %(__version__)s
|
__bigcopyright__ = """%(__productname__)s %(__version__)s
|
||||||
%(__copyright__)s <%(__author_email__)s>""" % locals()
|
%(__copyright__)s.
|
||||||
|
%(__license__)s.
|
||||||
banner = __bigcopyright__ + """
|
""" % locals()
|
||||||
|
|
||||||
This software comes with ABSOLUTELY NO WARRANTY; see the file
|
|
||||||
COPYING for details. This is free software, and you are welcome
|
|
||||||
to distribute it under the conditions laid out in COPYING."""
|
|
||||||
|
|
||||||
__homepage__ = "http://github.com/nicolas33/offlineimap"
|
__homepage__ = "http://github.com/nicolas33/offlineimap"
|
||||||
__license__ = "Licensed under the GNU GPL v2+ (v2 or any later version)."
|
|
||||||
|
|
||||||
|
banner = __bigcopyright__
|
||||||
|
|
||||||
|
|
||||||
from offlineimap.error import OfflineImapError
|
from offlineimap.error import OfflineImapError
|
||||||
# put this last, so we don't run into circular dependencies using
|
# put this last, so we don't run into circular dependencies using
|
||||||
|
@ -23,7 +23,7 @@ import re
|
|||||||
import time
|
import time
|
||||||
from copy import copy
|
from copy import copy
|
||||||
from Base import BaseFolder
|
from Base import BaseFolder
|
||||||
from offlineimap import imaputil, imaplibutil
|
from offlineimap import imaputil, imaplibutil, OfflineImapError
|
||||||
|
|
||||||
class IMAPFolder(BaseFolder):
|
class IMAPFolder(BaseFolder):
|
||||||
def __init__(self, imapserver, name, visiblename, accountname, repository):
|
def __init__(self, imapserver, name, visiblename, accountname, repository):
|
||||||
@ -137,7 +137,7 @@ class IMAPFolder(BaseFolder):
|
|||||||
search_condition += date_search_str
|
search_condition += date_search_str
|
||||||
|
|
||||||
if(maxsize != -1):
|
if(maxsize != -1):
|
||||||
if(maxage != 1): #There are two conditions - add a space
|
if(maxage != -1): #There are two conditions - add a space
|
||||||
search_condition += " "
|
search_condition += " "
|
||||||
|
|
||||||
search_condition += "SMALLER " + self.config.getdefault("Account " + self.accountname, "maxsize", -1)
|
search_condition += "SMALLER " + self.config.getdefault("Account " + self.accountname, "maxsize", -1)
|
||||||
@ -195,13 +195,24 @@ class IMAPFolder(BaseFolder):
|
|||||||
def getmessage(self, uid):
|
def getmessage(self, uid):
|
||||||
"""Retrieve message with UID from the IMAP server (incl body)
|
"""Retrieve message with UID from the IMAP server (incl body)
|
||||||
|
|
||||||
:returns: the message body
|
:returns: the message body or throws and OfflineImapError
|
||||||
|
(probably severity MESSAGE) if e.g. no message with
|
||||||
|
this UID could be found.
|
||||||
"""
|
"""
|
||||||
imapobj = self.imapserver.acquireconnection()
|
imapobj = self.imapserver.acquireconnection()
|
||||||
try:
|
try:
|
||||||
imapobj.select(self.getfullname(), readonly = 1)
|
imapobj.select(self.getfullname(), readonly = 1)
|
||||||
res_type, data = imapobj.uid('fetch', '%d' % uid, '(BODY.PEEK[])')
|
res_type, data = imapobj.uid('fetch', str(uid), '(BODY.PEEK[])')
|
||||||
assert res_type == 'OK', "Fetching message with UID '%d' failed" % uid
|
if data == [None] or res_type != 'OK':
|
||||||
|
#IMAP server says bad request or UID does not exist
|
||||||
|
severity = OfflineImapError.ERROR.MESSAGE
|
||||||
|
reason = "IMAP server '%s' responded with '%s' to fetching "\
|
||||||
|
"message UID '%d'" % (self.getrepository(), res_type, uid)
|
||||||
|
if data == [None]:
|
||||||
|
#IMAP server did not find a message with this UID
|
||||||
|
reason = "IMAP server '%s' does not have a message "\
|
||||||
|
"with UID '%s'" % (self.getrepository(), uid)
|
||||||
|
raise OfflineImapError(reason, severity)
|
||||||
# data looks now e.g. [('320 (UID 17061 BODY[]
|
# data looks now e.g. [('320 (UID 17061 BODY[]
|
||||||
# {2565}','msgbody....')] we only asked for one message,
|
# {2565}','msgbody....')] we only asked for one message,
|
||||||
# and that msg is in data[0]. msbody is in [0][1]
|
# and that msg is in data[0]. msbody is in [0][1]
|
||||||
|
@ -20,8 +20,11 @@ from threading import *
|
|||||||
from IMAP import IMAPFolder
|
from IMAP import IMAPFolder
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
class MappingFolderMixIn:
|
class MappedIMAPFolder(IMAPFolder):
|
||||||
"""Helper class to map between Folder() instances where both side assign a uid
|
"""IMAP class to map between Folder() instances where both side assign a uid
|
||||||
|
|
||||||
|
This Folder is used on the local side, while the remote side should
|
||||||
|
be an IMAPFolder.
|
||||||
|
|
||||||
Instance variables (self.):
|
Instance variables (self.):
|
||||||
r2l: dict mapping message uids: self.r2l[remoteuid]=localuid
|
r2l: dict mapping message uids: self.r2l[remoteuid]=localuid
|
||||||
@ -29,10 +32,13 @@ class MappingFolderMixIn:
|
|||||||
#TODO: what is the difference, how are they used?
|
#TODO: what is the difference, how are they used?
|
||||||
diskr2l: dict mapping message uids: self.r2l[remoteuid]=localuid
|
diskr2l: dict mapping message uids: self.r2l[remoteuid]=localuid
|
||||||
diskl2r: dict mapping message uids: self.r2l[localuid]=remoteuid"""
|
diskl2r: dict mapping message uids: self.r2l[localuid]=remoteuid"""
|
||||||
def _initmapping(self):
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
IMAPFolder.__init__(self, *args, **kwargs)
|
||||||
self.maplock = Lock()
|
self.maplock = Lock()
|
||||||
(self.diskr2l, self.diskl2r) = self._loadmaps()
|
(self.diskr2l, self.diskl2r) = self._loadmaps()
|
||||||
self._mb = self.__class__.__bases__[1]
|
self._mb = IMAPFolder(*args, **kwargs)
|
||||||
|
"""Representing the local IMAP Folder using local UIDs"""
|
||||||
|
|
||||||
def _getmapfilename(self):
|
def _getmapfilename(self):
|
||||||
return os.path.join(self.repository.getmapdir(),
|
return os.path.join(self.repository.getmapdir(),
|
||||||
@ -81,8 +87,8 @@ class MappingFolderMixIn:
|
|||||||
return [mapping[x] for x in items]
|
return [mapping[x] for x in items]
|
||||||
|
|
||||||
def cachemessagelist(self):
|
def cachemessagelist(self):
|
||||||
self._mb.cachemessagelist(self)
|
self._mb.cachemessagelist()
|
||||||
reallist = self._mb.getmessagelist(self)
|
reallist = self._mb.getmessagelist()
|
||||||
|
|
||||||
self.maplock.acquire()
|
self.maplock.acquire()
|
||||||
try:
|
try:
|
||||||
@ -137,7 +143,7 @@ class MappingFolderMixIn:
|
|||||||
cachemessagelist() before calling this function!"""
|
cachemessagelist() before calling this function!"""
|
||||||
|
|
||||||
retval = {}
|
retval = {}
|
||||||
localhash = self._mb.getmessagelist(self)
|
localhash = self._mb.getmessagelist()
|
||||||
self.maplock.acquire()
|
self.maplock.acquire()
|
||||||
try:
|
try:
|
||||||
for key, value in localhash.items():
|
for key, value in localhash.items():
|
||||||
@ -158,7 +164,7 @@ class MappingFolderMixIn:
|
|||||||
|
|
||||||
def getmessage(self, uid):
|
def getmessage(self, uid):
|
||||||
"""Returns the content of the specified message."""
|
"""Returns the content of the specified message."""
|
||||||
return self._mb.getmessage(self, self.r2l[uid])
|
return self._mb.getmessage(self.r2l[uid])
|
||||||
|
|
||||||
def savemessage(self, uid, content, flags, rtime):
|
def savemessage(self, uid, content, flags, rtime):
|
||||||
"""Writes a new message, with the specified uid.
|
"""Writes a new message, with the specified uid.
|
||||||
@ -185,7 +191,7 @@ class MappingFolderMixIn:
|
|||||||
self.savemessageflags(uid, flags)
|
self.savemessageflags(uid, flags)
|
||||||
return uid
|
return uid
|
||||||
|
|
||||||
newluid = self._mb.savemessage(self, -1, content, flags, rtime)
|
newluid = self._mb.savemessage(-1, content, flags, rtime)
|
||||||
if newluid < 1:
|
if newluid < 1:
|
||||||
raise ValueError("Backend could not find uid for message")
|
raise ValueError("Backend could not find uid for message")
|
||||||
self.maplock.acquire()
|
self.maplock.acquire()
|
||||||
@ -197,21 +203,22 @@ class MappingFolderMixIn:
|
|||||||
self._savemaps(dolock = 0)
|
self._savemaps(dolock = 0)
|
||||||
finally:
|
finally:
|
||||||
self.maplock.release()
|
self.maplock.release()
|
||||||
|
return uid
|
||||||
|
|
||||||
def getmessageflags(self, uid):
|
def getmessageflags(self, uid):
|
||||||
return self._mb.getmessageflags(self, self.r2l[uid])
|
return self._mb.getmessageflags(self.r2l[uid])
|
||||||
|
|
||||||
def getmessagetime(self, uid):
|
def getmessagetime(self, uid):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def savemessageflags(self, uid, flags):
|
def savemessageflags(self, uid, flags):
|
||||||
self._mb.savemessageflags(self, self.r2l[uid], flags)
|
self._mb.savemessageflags(self.r2l[uid], flags)
|
||||||
|
|
||||||
def addmessageflags(self, uid, flags):
|
def addmessageflags(self, uid, flags):
|
||||||
self._mb.addmessageflags(self, self.r2l[uid], flags)
|
self._mb.addmessageflags(self.r2l[uid], flags)
|
||||||
|
|
||||||
def addmessagesflags(self, uidlist, flags):
|
def addmessagesflags(self, uidlist, flags):
|
||||||
self._mb.addmessagesflags(self, self._uidlist(self.r2l, uidlist),
|
self._mb.addmessagesflags(self._uidlist(self.r2l, uidlist),
|
||||||
flags)
|
flags)
|
||||||
|
|
||||||
def _mapped_delete(self, uidlist):
|
def _mapped_delete(self, uidlist):
|
||||||
@ -232,22 +239,16 @@ class MappingFolderMixIn:
|
|||||||
self.maplock.release()
|
self.maplock.release()
|
||||||
|
|
||||||
def deletemessageflags(self, uid, flags):
|
def deletemessageflags(self, uid, flags):
|
||||||
self._mb.deletemessageflags(self, self.r2l[uid], flags)
|
self._mb.deletemessageflags(self.r2l[uid], flags)
|
||||||
|
|
||||||
def deletemessagesflags(self, uidlist, flags):
|
def deletemessagesflags(self, uidlist, flags):
|
||||||
self._mb.deletemessagesflags(self, self._uidlist(self.r2l, uidlist),
|
self._mb.deletemessagesflags(self._uidlist(self.r2l, uidlist),
|
||||||
flags)
|
flags)
|
||||||
|
|
||||||
def deletemessage(self, uid):
|
def deletemessage(self, uid):
|
||||||
self._mb.deletemessage(self, self.r2l[uid])
|
self._mb.deletemessage(self.r2l[uid])
|
||||||
self._mapped_delete([uid])
|
self._mapped_delete([uid])
|
||||||
|
|
||||||
def deletemessages(self, uidlist):
|
def deletemessages(self, uidlist):
|
||||||
self._mb.deletemessages(self, self._uidlist(self.r2l, uidlist))
|
self._mb.deletemessages(self._uidlist(self.r2l, uidlist))
|
||||||
self._mapped_delete(uidlist)
|
self._mapped_delete(uidlist)
|
||||||
|
|
||||||
# Define a class for local part of IMAP.
|
|
||||||
class MappedIMAPFolder(MappingFolderMixIn, IMAPFolder):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
IMAPFolder.__init__(self, *args, **kwargs)
|
|
||||||
self._initmapping()
|
|
||||||
|
@ -33,11 +33,9 @@ except ImportError:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
class UsefulIMAPMixIn:
|
class UsefulIMAPMixIn:
|
||||||
def getstate(self):
|
|
||||||
return self.state
|
|
||||||
def getselectedfolder(self):
|
def getselectedfolder(self):
|
||||||
if self.getstate() == 'SELECTED':
|
if self.state == 'SELECTED':
|
||||||
return self.selectedfolder
|
return self.mailbox
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def select(self, mailbox='INBOX', readonly=None, force = 0):
|
def select(self, mailbox='INBOX', readonly=None, force = 0):
|
||||||
@ -58,10 +56,6 @@ class UsefulIMAPMixIn:
|
|||||||
(mailbox, result)
|
(mailbox, result)
|
||||||
severity = OfflineImapError.ERROR.FOLDER
|
severity = OfflineImapError.ERROR.FOLDER
|
||||||
raise OfflineImapError(errstr, severity)
|
raise OfflineImapError(errstr, severity)
|
||||||
if self.getstate() == 'SELECTED':
|
|
||||||
self.selectedfolder = mailbox
|
|
||||||
else:
|
|
||||||
self.selectedfolder = None
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _mesg(self, s, tn=None, secs=None):
|
def _mesg(self, s, tn=None, secs=None):
|
||||||
|
@ -24,7 +24,6 @@ import offlineimap.accounts
|
|||||||
import hmac
|
import hmac
|
||||||
import socket
|
import socket
|
||||||
import base64
|
import base64
|
||||||
import errno
|
|
||||||
|
|
||||||
from socket import gaierror
|
from socket import gaierror
|
||||||
try:
|
try:
|
||||||
@ -32,6 +31,7 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
# Protect against python<2.6, use dummy and won't get SSL errors.
|
# Protect against python<2.6, use dummy and won't get SSL errors.
|
||||||
SSLError = None
|
SSLError = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# do we have a recent pykerberos?
|
# do we have a recent pykerberos?
|
||||||
have_gss = False
|
have_gss = False
|
||||||
@ -219,6 +219,7 @@ class IMAPServer:
|
|||||||
try:
|
try:
|
||||||
# Try GSSAPI and continue if it fails
|
# Try GSSAPI and continue if it fails
|
||||||
if 'AUTH=GSSAPI' in imapobj.capabilities and have_gss:
|
if 'AUTH=GSSAPI' in imapobj.capabilities and have_gss:
|
||||||
|
self.connectionlock.acquire()
|
||||||
self.ui.debug('imap',
|
self.ui.debug('imap',
|
||||||
'Attempting GSSAPI authentication')
|
'Attempting GSSAPI authentication')
|
||||||
try:
|
try:
|
||||||
@ -229,15 +230,26 @@ class IMAPServer:
|
|||||||
'GSSAPI Authentication failed')
|
'GSSAPI Authentication failed')
|
||||||
else:
|
else:
|
||||||
self.gssapi = True
|
self.gssapi = True
|
||||||
|
kerberos.authGSSClientClean(self.gss_vc)
|
||||||
|
self.gss_vc = None
|
||||||
|
self.gss_step = self.GSS_STATE_STEP
|
||||||
#if we do self.password = None then the next attempt cannot try...
|
#if we do self.password = None then the next attempt cannot try...
|
||||||
#self.password = None
|
#self.password = None
|
||||||
|
self.connectionlock.release()
|
||||||
|
|
||||||
if not self.gssapi:
|
if not self.gssapi:
|
||||||
|
if 'STARTTLS' in imapobj.capabilities and not\
|
||||||
|
self.usessl:
|
||||||
|
self.ui.debug('imap',
|
||||||
|
'Using STARTTLS connection')
|
||||||
|
imapobj.starttls()
|
||||||
|
|
||||||
if 'AUTH=CRAM-MD5' in imapobj.capabilities:
|
if 'AUTH=CRAM-MD5' in imapobj.capabilities:
|
||||||
self.ui.debug('imap',
|
self.ui.debug('imap',
|
||||||
'Attempting CRAM-MD5 authentication')
|
'Attempting CRAM-MD5 authentication')
|
||||||
try:
|
try:
|
||||||
imapobj.authenticate('CRAM-MD5', self.md5handler)
|
imapobj.authenticate('CRAM-MD5',
|
||||||
|
self.md5handler)
|
||||||
except imapobj.error, val:
|
except imapobj.error, val:
|
||||||
self.plainauth(imapobj)
|
self.plainauth(imapobj)
|
||||||
else:
|
else:
|
||||||
@ -285,9 +297,8 @@ class IMAPServer:
|
|||||||
if(self.connectionlock.locked()):
|
if(self.connectionlock.locked()):
|
||||||
self.connectionlock.release()
|
self.connectionlock.release()
|
||||||
|
|
||||||
# now, check for known errors and throw OfflineImapErrors
|
|
||||||
severity = OfflineImapError.ERROR.REPO
|
severity = OfflineImapError.ERROR.REPO
|
||||||
if isinstance(e, gaierror):
|
if type(e) == gaierror:
|
||||||
#DNS related errors. Abort Repo sync
|
#DNS related errors. Abort Repo sync
|
||||||
#TODO: special error msg for e.errno == 2 "Name or service not known"?
|
#TODO: special error msg for e.errno == 2 "Name or service not known"?
|
||||||
reason = "Could not resolve name '%s' for repository "\
|
reason = "Could not resolve name '%s' for repository "\
|
||||||
@ -296,7 +307,7 @@ class IMAPServer:
|
|||||||
(self.hostname, self.reposname)
|
(self.hostname, self.reposname)
|
||||||
raise OfflineImapError(reason, severity)
|
raise OfflineImapError(reason, severity)
|
||||||
|
|
||||||
elif isinstance(e, SSLError) and e.errno == 1:
|
elif SSLError and isinstance(e, SSLError) and e.errno == 1:
|
||||||
# SSL unknown protocol error
|
# SSL unknown protocol error
|
||||||
# happens e.g. when connecting via SSL to a non-SSL service
|
# happens e.g. when connecting via SSL to a non-SSL service
|
||||||
if self.port != 443:
|
if self.port != 443:
|
||||||
@ -322,8 +333,7 @@ class IMAPServer:
|
|||||||
if str(e)[:24] == "can't open socket; error":
|
if str(e)[:24] == "can't open socket; error":
|
||||||
raise OfflineImapError("Could not connect to remote server '%s' "\
|
raise OfflineImapError("Could not connect to remote server '%s' "\
|
||||||
"for repository '%s'. Remote does not answer."
|
"for repository '%s'. Remote does not answer."
|
||||||
% (self.hostname, self.reposname),
|
% (self.hostname, self.reposname), severity)
|
||||||
OfflineImapError.ERROR.REPO)
|
|
||||||
else:
|
else:
|
||||||
# re-raise all other errors
|
# re-raise all other errors
|
||||||
raise
|
raise
|
||||||
|
@ -143,7 +143,6 @@ def imapsplit(imapstring):
|
|||||||
elif splitslen == 0:
|
elif splitslen == 0:
|
||||||
# There was not even an unquoted word.
|
# There was not even an unquoted word.
|
||||||
break
|
break
|
||||||
debug("imapsplit() returning:", retval)
|
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
flagmap = [('\\Seen', 'S'),
|
flagmap = [('\\Seen', 'S'),
|
||||||
|
@ -343,6 +343,7 @@ class OfflineImap:
|
|||||||
t.start()
|
t.start()
|
||||||
threadutil.exitnotifymonitorloop(threadutil.threadexited)
|
threadutil.exitnotifymonitorloop(threadutil.threadexited)
|
||||||
|
|
||||||
|
ui.terminate()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
ui.terminate(1, errormsg = 'CTRL-C pressed, aborting...')
|
ui.terminate(1, errormsg = 'CTRL-C pressed, aborting...')
|
||||||
return
|
return
|
||||||
|
@ -100,7 +100,7 @@ class MaildirRepository(BaseRepository):
|
|||||||
except OSError, e:
|
except OSError, e:
|
||||||
if e.errno == 17 and os.path.isdir(full_path):
|
if e.errno == 17 and os.path.isdir(full_path):
|
||||||
self.debug("makefolder: '%s' already has subdir %s" %
|
self.debug("makefolder: '%s' already has subdir %s" %
|
||||||
(foldername, sudir))
|
(foldername, subdir))
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
# Invalidate the folder cache
|
# Invalidate the folder cache
|
||||||
|
@ -21,7 +21,6 @@ import time
|
|||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
import threading
|
import threading
|
||||||
from StringIO import StringIO
|
|
||||||
import offlineimap
|
import offlineimap
|
||||||
|
|
||||||
debugtypes = {'':'Other offlineimap related sync messages',
|
debugtypes = {'':'Other offlineimap related sync messages',
|
||||||
@ -309,10 +308,8 @@ class UIBase:
|
|||||||
s.terminate(100)
|
s.terminate(100)
|
||||||
|
|
||||||
def getMainExceptionString(s):
|
def getMainExceptionString(s):
|
||||||
sbuf = StringIO()
|
return "Main program terminated with exception:\n%s\n" %\
|
||||||
traceback.print_exc(file = sbuf)
|
traceback.format_exc() + \
|
||||||
return "Main program terminated with exception:\n" + \
|
|
||||||
sbuf.getvalue() + "\n" + \
|
|
||||||
s.getThreadDebugLog(threading.currentThread())
|
s.getThreadDebugLog(threading.currentThread())
|
||||||
|
|
||||||
def mainException(s):
|
def mainException(s):
|
||||||
|
Loading…
Reference in New Issue
Block a user