Refactoring: make functions to be private if we can

Make external API of class/module to be smaller, explicitely mark
all internal functions.  Also annotate methods that are implemented
as the part of the parent class interface.

Signed-off-by: Eygene Ryabinkin <rea@codelabs.ru>
This commit is contained in:
Eygene Ryabinkin 2014-03-16 16:27:35 +04:00
parent af2d6dc5e1
commit 6cbd2498ae
14 changed files with 218 additions and 136 deletions

View File

@ -30,7 +30,7 @@ be merged into the main documentation.
.. automethod:: run .. automethod:: run
.. automethod:: parse_cmd_options .. automethod:: __parse_cmd_options
.. .. autoattribute:: ui .. .. autoattribute:: ui

View File

@ -134,7 +134,7 @@ class Account(CustomConfig.ConfigHelperMixin):
return skipsleep or Account.abort_soon_signal.is_set() or \ return skipsleep or Account.abort_soon_signal.is_set() or \
Account.abort_NOW_signal.is_set() Account.abort_NOW_signal.is_set()
def sleeper(self): def _sleeper(self):
"""Sleep if the account is set to autorefresh """Sleep if the account is set to autorefresh
:returns: 0:timeout expired, 1: canceled the timer, :returns: 0:timeout expired, 1: canceled the timer,
@ -192,7 +192,7 @@ class SyncableAccount(Account):
self._lockfilepath = os.path.join(self.config.getmetadatadir(), self._lockfilepath = os.path.join(self.config.getmetadatadir(),
"%s.lock" % self) "%s.lock" % self)
def lock(self): def __lock(self):
"""Lock the account, throwing an exception if it is locked already""" """Lock the account, throwing an exception if it is locked already"""
self._lockfd = open(self._lockfilepath, 'w') self._lockfd = open(self._lockfilepath, 'w')
try: try:
@ -206,7 +206,7 @@ class SyncableAccount(Account):
"instance using this account?" % self, "instance using this account?" % self,
OfflineImapError.ERROR.REPO) OfflineImapError.ERROR.REPO)
def unlock(self): def _unlock(self):
"""Unlock the account, deleting the lock file""" """Unlock the account, deleting the lock file"""
#If we own the lock file, delete it #If we own the lock file, delete it
if self._lockfd and not self._lockfd.closed: if self._lockfd and not self._lockfd.closed:
@ -237,8 +237,8 @@ class SyncableAccount(Account):
while looping: while looping:
self.ui.acct(self) self.ui.acct(self)
try: try:
self.lock() self.__lock()
self.sync() self.__sync()
except (KeyboardInterrupt, SystemExit): except (KeyboardInterrupt, SystemExit):
raise raise
except OfflineImapError as e: except OfflineImapError as e:
@ -258,8 +258,8 @@ class SyncableAccount(Account):
looping = 3 looping = 3
finally: finally:
self.ui.acctdone(self) self.ui.acctdone(self)
self.unlock() self._unlock()
if looping and self.sleeper() >= 2: if looping and self._sleeper() >= 2:
looping = 0 looping = 0
def get_local_folder(self, remotefolder): def get_local_folder(self, remotefolder):
@ -268,7 +268,7 @@ class SyncableAccount(Account):
remotefolder.getvisiblename(). remotefolder.getvisiblename().
replace(self.remoterepos.getsep(), self.localrepos.getsep())) replace(self.remoterepos.getsep(), self.localrepos.getsep()))
def sync(self): def __sync(self):
"""Synchronize the account once, then return """Synchronize the account once, then return
Assumes that `self.remoterepos`, `self.localrepos`, and Assumes that `self.remoterepos`, `self.localrepos`, and

View File

@ -96,6 +96,22 @@ class BaseFolder(object):
false otherwise. Probably only IMAP will return true.""" false otherwise. Probably only IMAP will return true."""
return 0 return 0
def waitforthread(self):
"""Implements method that waits for thread to be usable.
Should be implemented only for folders that suggest threads."""
raise NotImplementedException
# XXX: we may need someting like supports_quickstatus() to check
# XXX: if user specifies 'quick' flag for folder that doesn't
# XXX: support quick status queries, so one believes that quick
# XXX: status checks will be done, but it won't really be so.
def quickchanged(self, statusfolder):
""" Runs quick check for folder changes and returns changed
status: True -- changed, False -- not changed.
:param statusfolder: keeps track of the last known folder state.
"""
return True
def getcopyinstancelimit(self): def getcopyinstancelimit(self):
"""For threading folders, returns the instancelimitname for """For threading folders, returns the instancelimitname for
InstanceLimitedThreads.""" InstanceLimitedThreads."""
@ -328,7 +344,7 @@ class BaseFolder(object):
for uid in uidlist: for uid in uidlist:
self.deletemessage(uid) self.deletemessage(uid)
def copymessageto(self, uid, dstfolder, statusfolder, register = 1): def __copymessageto(self, uid, dstfolder, statusfolder, register = 1):
"""Copies a message from self to dst if needed, updating the status """Copies a message from self to dst if needed, updating the status
Note that this function does not check against dryrun settings, Note that this function does not check against dryrun settings,
@ -403,14 +419,14 @@ class BaseFolder(object):
(uid, self.accountname)) (uid, self.accountname))
raise #raise on unknown errors, so we can fix those raise #raise on unknown errors, so we can fix those
def syncmessagesto_copy(self, dstfolder, statusfolder): def __syncmessagesto_copy(self, dstfolder, statusfolder):
"""Pass1: Copy locally existing messages not on the other side """Pass1: Copy locally existing messages not on the other side
This will copy messages to dstfolder that exist locally but are This will copy messages to dstfolder that exist locally but are
not in the statusfolder yet. The strategy is: not in the statusfolder yet. The strategy is:
1) Look for messages present in self but not in statusfolder. 1) Look for messages present in self but not in statusfolder.
2) invoke copymessageto() on those which: 2) invoke __copymessageto() on those which:
- If dstfolder doesn't have it yet, add them to dstfolder. - If dstfolder doesn't have it yet, add them to dstfolder.
- Update statusfolder - Update statusfolder
@ -431,23 +447,23 @@ class BaseFolder(object):
if offlineimap.accounts.Account.abort_NOW_signal.is_set(): if offlineimap.accounts.Account.abort_NOW_signal.is_set():
break break
self.ui.copyingmessage(uid, num+1, num_to_copy, self, dstfolder) self.ui.copyingmessage(uid, num+1, num_to_copy, self, dstfolder)
# exceptions are caught in copymessageto() # exceptions are caught in __copymessageto()
if self.suggeststhreads() and not globals.options.singlethreading: if self.suggeststhreads() and not globals.options.singlethreading:
self.waitforthread() self.waitforthread()
thread = threadutil.InstanceLimitedThread(\ thread = threadutil.InstanceLimitedThread(\
self.getcopyinstancelimit(), self.getcopyinstancelimit(),
target = self.copymessageto, target = self.__copymessageto,
name = "Copy message from %s:%s" % (self.repository, self), name = "Copy message from %s:%s" % (self.repository, self),
args = (uid, dstfolder, statusfolder)) args = (uid, dstfolder, statusfolder))
thread.start() thread.start()
threads.append(thread) threads.append(thread)
else: else:
self.copymessageto(uid, dstfolder, statusfolder, self.__copymessageto(uid, dstfolder, statusfolder,
register = 0) register = 0)
for thread in threads: for thread in threads:
thread.join() thread.join()
def syncmessagesto_delete(self, dstfolder, statusfolder): def __syncmessagesto_delete(self, dstfolder, statusfolder):
"""Pass 2: Remove locally deleted messages on dst """Pass 2: Remove locally deleted messages on dst
Get all UIDS in statusfolder but not self. These are messages Get all UIDS in statusfolder but not self. These are messages
@ -468,7 +484,7 @@ class BaseFolder(object):
for folder in [statusfolder, dstfolder]: for folder in [statusfolder, dstfolder]:
folder.deletemessages(deletelist) folder.deletemessages(deletelist)
def syncmessagesto_flags(self, dstfolder, statusfolder): def __syncmessagesto_flags(self, dstfolder, statusfolder):
"""Pass 3: Flag synchronization """Pass 3: Flag synchronization
Compare flag mismatches in self with those in statusfolder. If Compare flag mismatches in self with those in statusfolder. If
@ -551,9 +567,9 @@ class BaseFolder(object):
:param dstfolder: Folderinstance to sync the msgs to. :param dstfolder: Folderinstance to sync the msgs to.
:param statusfolder: LocalStatus instance to sync against. :param statusfolder: LocalStatus instance to sync against.
""" """
passes = [('copying messages' , self.syncmessagesto_copy), passes = [('copying messages' , self.__syncmessagesto_copy),
('deleting messages' , self.syncmessagesto_delete), ('deleting messages' , self.__syncmessagesto_delete),
('syncing flags' , self.syncmessagesto_flags)] ('syncing flags' , self.__syncmessagesto_flags)]
for (passdesc, action) in passes: for (passdesc, action) in passes:
# bail out on CTRL-C or SIGTERM # bail out on CTRL-C or SIGTERM

View File

@ -38,7 +38,7 @@ class IMAPFolder(BaseFolder):
self.randomgenerator = random.Random() self.randomgenerator = random.Random()
#self.ui is set in BaseFolder #self.ui is set in BaseFolder
def selectro(self, imapobj, force = False): def __selectro(self, imapobj, force = False):
"""Select this folder when we do not need write access. """Select this folder when we do not need write access.
Prefer SELECT to EXAMINE if we can, since some servers Prefer SELECT to EXAMINE if we can, since some servers
@ -52,15 +52,19 @@ class IMAPFolder(BaseFolder):
except imapobj.readonly: except imapobj.readonly:
imapobj.select(self.getfullname(), readonly = True, force = force) imapobj.select(self.getfullname(), readonly = True, force = force)
# Interface from BaseFolder
def suggeststhreads(self): def suggeststhreads(self):
return not globals.options.singlethreading return not globals.options.singlethreading
# Interface from BaseFolder
def waitforthread(self): def waitforthread(self):
self.imapserver.connectionwait() self.imapserver.connectionwait()
# Interface from BaseFolder
def getcopyinstancelimit(self): def getcopyinstancelimit(self):
return 'MSGCOPY_' + self.repository.getname() return 'MSGCOPY_' + self.repository.getname()
# Interface from BaseFolder
def get_uidvalidity(self): def get_uidvalidity(self):
"""Retrieve the current connections UIDVALIDITY value """Retrieve the current connections UIDVALIDITY value
@ -72,7 +76,7 @@ class IMAPFolder(BaseFolder):
imapobj = self.imapserver.acquireconnection() imapobj = self.imapserver.acquireconnection()
try: try:
# SELECT (if not already done) and get current UIDVALIDITY # SELECT (if not already done) and get current UIDVALIDITY
self.selectro(imapobj) self.__selectro(imapobj)
typ, uidval = imapobj.response('UIDVALIDITY') typ, uidval = imapobj.response('UIDVALIDITY')
assert uidval != [None] and uidval != None, \ assert uidval != [None] and uidval != None, \
"response('UIDVALIDITY') returned [None]!" "response('UIDVALIDITY') returned [None]!"
@ -81,6 +85,7 @@ class IMAPFolder(BaseFolder):
finally: finally:
self.imapserver.releaseconnection(imapobj) self.imapserver.releaseconnection(imapobj)
# Interface from BaseFolder
def quickchanged(self, statusfolder): def quickchanged(self, statusfolder):
# An IMAP folder has definitely changed if the number of # An IMAP folder has definitely changed if the number of
# messages or the UID of the last message have changed. Otherwise # messages or the UID of the last message have changed. Otherwise
@ -118,6 +123,7 @@ class IMAPFolder(BaseFolder):
return True return True
return False return False
# Interface from BaseFolder
def cachemessagelist(self): def cachemessagelist(self):
maxage = self.config.getdefaultint("Account %s" % self.accountname, maxage = self.config.getdefaultint("Account %s" % self.accountname,
"maxage", -1) "maxage", -1)
@ -199,9 +205,11 @@ class IMAPFolder(BaseFolder):
rtime = imaplibutil.Internaldate2epoch(messagestr) rtime = imaplibutil.Internaldate2epoch(messagestr)
self.messagelist[uid] = {'uid': uid, 'flags': flags, 'time': rtime} self.messagelist[uid] = {'uid': uid, 'flags': flags, 'time': rtime}
# Interface from BaseFolder
def getmessagelist(self): def getmessagelist(self):
return self.messagelist return self.messagelist
# Interface from 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)
@ -253,13 +261,15 @@ class IMAPFolder(BaseFolder):
self.imapserver.releaseconnection(imapobj) self.imapserver.releaseconnection(imapobj)
return data return data
# Interface from BaseFolder
def getmessagetime(self, uid): def getmessagetime(self, uid):
return self.messagelist[uid]['time'] return self.messagelist[uid]['time']
# Interface from BaseFolder
def getmessageflags(self, uid): def getmessageflags(self, uid):
return self.messagelist[uid]['flags'] return self.messagelist[uid]['flags']
def generate_randomheader(self, content): def __generate_randomheader(self, content):
"""Returns a unique X-OfflineIMAP header """Returns a unique X-OfflineIMAP header
Generate an 'X-OfflineIMAP' mail header which contains a random Generate an 'X-OfflineIMAP' mail header which contains a random
@ -286,28 +296,28 @@ class IMAPFolder(BaseFolder):
return (headername, headervalue) return (headername, headervalue)
def savemessage_addheader(self, content, headername, headervalue): def __savemessage_addheader(self, content, headername, headervalue):
self.ui.debug('imap', self.ui.debug('imap',
'savemessage_addheader: called to add %s: %s' % (headername, '__savemessage_addheader: called to add %s: %s' % (headername,
headervalue)) headervalue))
insertionpoint = content.find("\r\n\r\n") insertionpoint = content.find("\r\n\r\n")
self.ui.debug('imap', 'savemessage_addheader: insertionpoint = %d' % insertionpoint) self.ui.debug('imap', '__savemessage_addheader: insertionpoint = %d' % insertionpoint)
leader = content[0:insertionpoint] leader = content[0:insertionpoint]
self.ui.debug('imap', 'savemessage_addheader: leader = %s' % repr(leader)) self.ui.debug('imap', '__savemessage_addheader: leader = %s' % repr(leader))
if insertionpoint == 0 or insertionpoint == -1: if insertionpoint == 0 or insertionpoint == -1:
newline = '' newline = ''
insertionpoint = 0 insertionpoint = 0
else: else:
newline = "\r\n" newline = "\r\n"
newline += "%s: %s" % (headername, headervalue) newline += "%s: %s" % (headername, headervalue)
self.ui.debug('imap', 'savemessage_addheader: newline = ' + repr(newline)) self.ui.debug('imap', '__savemessage_addheader: newline = ' + repr(newline))
trailer = content[insertionpoint:] trailer = content[insertionpoint:]
self.ui.debug('imap', 'savemessage_addheader: trailer = ' + repr(trailer)) self.ui.debug('imap', '__savemessage_addheader: trailer = ' + repr(trailer))
return leader + newline + trailer return leader + newline + trailer
def savemessage_searchforheader(self, imapobj, headername, headervalue): def __savemessage_searchforheader(self, imapobj, headername, headervalue):
self.ui.debug('imap', 'savemessage_searchforheader called for %s: %s' % \ self.ui.debug('imap', '__savemessage_searchforheader called for %s: %s' % \
(headername, headervalue)) (headername, headervalue))
# Now find the UID it got. # Now find the UID it got.
headervalue = imapobj._quote(headervalue) headervalue = imapobj._quote(headervalue)
@ -315,16 +325,16 @@ class IMAPFolder(BaseFolder):
matchinguids = imapobj.uid('search', 'HEADER', headername, headervalue)[1][0] matchinguids = imapobj.uid('search', 'HEADER', headername, headervalue)[1][0]
except imapobj.error as err: except imapobj.error as err:
# IMAP server doesn't implement search or had a problem. # IMAP server doesn't implement search or had a problem.
self.ui.debug('imap', "savemessage_searchforheader: got IMAP error '%s' while attempting to UID SEARCH for message with header %s" % (err, headername)) self.ui.debug('imap', "__savemessage_searchforheader: got IMAP error '%s' while attempting to UID SEARCH for message with header %s" % (err, headername))
return 0 return 0
self.ui.debug('imap', 'savemessage_searchforheader got initial matchinguids: ' + repr(matchinguids)) self.ui.debug('imap', '__savemessage_searchforheader got initial matchinguids: ' + repr(matchinguids))
if matchinguids == '': if matchinguids == '':
self.ui.debug('imap', "savemessage_searchforheader: UID SEARCH for message with header %s yielded no results" % headername) self.ui.debug('imap', "__savemessage_searchforheader: UID SEARCH for message with header %s yielded no results" % headername)
return 0 return 0
matchinguids = matchinguids.split(' ') matchinguids = matchinguids.split(' ')
self.ui.debug('imap', 'savemessage_searchforheader: matchinguids now ' + \ self.ui.debug('imap', '__savemessage_searchforheader: matchinguids now ' + \
repr(matchinguids)) repr(matchinguids))
if len(matchinguids) != 1 or matchinguids[0] == None: if len(matchinguids) != 1 or matchinguids[0] == None:
raise ValueError("While attempting to find UID for message with " raise ValueError("While attempting to find UID for message with "
@ -332,7 +342,7 @@ class IMAPFolder(BaseFolder):
(headername, str(matchinguids))) (headername, str(matchinguids)))
return long(matchinguids[0]) return long(matchinguids[0])
def savemessage_fetchheaders(self, imapobj, headername, headervalue): def __savemessage_fetchheaders(self, imapobj, headername, headervalue):
""" We fetch all new mail headers and search for the right """ We fetch all new mail headers and search for the right
X-OfflineImap line by hand. The response from the server has form: X-OfflineImap line by hand. The response from the server has form:
( (
@ -355,7 +365,7 @@ class IMAPFolder(BaseFolder):
Returns UID when found, 0 when not found. Returns UID when found, 0 when not found.
""" """
self.ui.debug('imap', 'savemessage_fetchheaders called for %s: %s' % \ self.ui.debug('imap', '__savemessage_fetchheaders called for %s: %s' % \
(headername, headervalue)) (headername, headervalue))
# run "fetch X:* rfc822.header" # run "fetch X:* rfc822.header"
@ -401,7 +411,7 @@ class IMAPFolder(BaseFolder):
return 0 return 0
def getmessageinternaldate(self, content, rtime=None): def __getmessageinternaldate(self, content, rtime=None):
"""Parses mail and returns an INTERNALDATE string """Parses mail and returns an INTERNALDATE string
It will use information in the following order, falling back as an attempt fails: It will use information in the following order, falling back as an attempt fails:
@ -474,6 +484,7 @@ class IMAPFolder(BaseFolder):
return internaldate return internaldate
# Interface from BaseFolder
def savemessage(self, uid, content, flags, rtime): def savemessage(self, uid, content, flags, rtime):
"""Save the message on the Server """Save the message on the Server
@ -511,16 +522,16 @@ class IMAPFolder(BaseFolder):
use_uidplus = 'UIDPLUS' in imapobj.capabilities use_uidplus = 'UIDPLUS' in imapobj.capabilities
# get the date of the message, so we can pass it to the server. # get the date of the message, so we can pass it to the server.
date = self.getmessageinternaldate(content, rtime) date = self.__getmessageinternaldate(content, rtime)
content = re.sub("(?<!\r)\n", "\r\n", content) content = re.sub("(?<!\r)\n", "\r\n", content)
if not use_uidplus: if not use_uidplus:
# insert a random unique header that we can fetch later # insert a random unique header that we can fetch later
(headername, headervalue) = self.generate_randomheader( (headername, headervalue) = self.__generate_randomheader(
content) content)
self.ui.debug('imap', 'savemessage: header is: %s: %s' %\ self.ui.debug('imap', 'savemessage: header is: %s: %s' %\
(headername, headervalue)) (headername, headervalue))
content = self.savemessage_addheader(content, headername, content = self.__savemessage_addheader(content, headername,
headervalue) headervalue)
if len(content)>200: if len(content)>200:
dbg_output = "%s...%s" % (content[:150], content[-50:]) dbg_output = "%s...%s" % (content[:150], content[-50:])
@ -605,14 +616,14 @@ class IMAPFolder(BaseFolder):
"'%s'" % str(resp)) "'%s'" % str(resp))
else: else:
# we don't support UIDPLUS # we don't support UIDPLUS
uid = self.savemessage_searchforheader(imapobj, headername, uid = self.__savemessage_searchforheader(imapobj, headername,
headervalue) headervalue)
# See docs for savemessage in Base.py for explanation # See docs for savemessage in Base.py for explanation
# of this and other return values # of this and other return values
if uid == 0: if uid == 0:
self.ui.debug('imap', 'savemessage: attempt to get new UID ' self.ui.debug('imap', 'savemessage: attempt to get new UID '
'UID failed. Search headers manually.') 'UID failed. Search headers manually.')
uid = self.savemessage_fetchheaders(imapobj, headername, uid = self.__savemessage_fetchheaders(imapobj, headername,
headervalue) headervalue)
self.ui.warn('imap', "savemessage: Searching mails for new " self.ui.warn('imap', "savemessage: Searching mails for new "
"Message-ID failed. Could not determine new UID.") "Message-ID failed. Could not determine new UID.")
@ -625,6 +636,7 @@ class IMAPFolder(BaseFolder):
self.ui.debug('imap', 'savemessage: returning new UID %d' % uid) self.ui.debug('imap', 'savemessage: returning new UID %d' % uid)
return uid return uid
# Interface from BaseFolder
def savemessageflags(self, uid, flags): def savemessageflags(self, uid, flags):
"""Change a message's flags to `flags`. """Change a message's flags to `flags`.
@ -650,29 +662,34 @@ class IMAPFolder(BaseFolder):
flags = imaputil.flags2hash(imaputil.imapsplit(result)[1])['FLAGS'] flags = imaputil.flags2hash(imaputil.imapsplit(result)[1])['FLAGS']
self.messagelist[uid]['flags'] = imaputil.flagsimap2maildir(flags) self.messagelist[uid]['flags'] = imaputil.flagsimap2maildir(flags)
# Interface from BaseFolder
def addmessageflags(self, uid, flags): def addmessageflags(self, uid, flags):
self.addmessagesflags([uid], flags) self.addmessagesflags([uid], flags)
def addmessagesflags_noconvert(self, uidlist, flags): def __addmessagesflags_noconvert(self, uidlist, flags):
self.processmessagesflags('+', uidlist, flags) self.__processmessagesflags('+', uidlist, flags)
# Interface from BaseFolder
def addmessagesflags(self, uidlist, flags): def addmessagesflags(self, uidlist, flags):
"""This is here for the sake of UIDMaps.py -- deletemessages must """This is here for the sake of UIDMaps.py -- deletemessages must
add flags and get a converted UID, and if we don't have noconvert, add flags and get a converted UID, and if we don't have noconvert,
then UIDMaps will try to convert it twice.""" then UIDMaps will try to convert it twice."""
self.addmessagesflags_noconvert(uidlist, flags) self.__addmessagesflags_noconvert(uidlist, flags)
# Interface from BaseFolder
def deletemessageflags(self, uid, flags): def deletemessageflags(self, uid, flags):
self.deletemessagesflags([uid], flags) self.deletemessagesflags([uid], flags)
# Interface from BaseFolder
def deletemessagesflags(self, uidlist, flags): def deletemessagesflags(self, uidlist, flags):
self.processmessagesflags('-', uidlist, flags) self.__processmessagesflags('-', uidlist, flags)
def processmessagesflags(self, operation, uidlist, flags): def __processmessagesflags(self, operation, uidlist, flags):
# XXX: should really iterate over batches of 100 UIDs
if len(uidlist) > 101: if len(uidlist) > 101:
# Hack for those IMAP ervers with a limited line length # Hack for those IMAP servers with a limited line length
self.processmessagesflags(operation, uidlist[:100], flags) self.__processmessagesflags(operation, uidlist[:100], flags)
self.processmessagesflags(operation, uidlist[100:], flags) self.__processmessagesflags(operation, uidlist[100:], flags)
return return
imapobj = self.imapserver.acquireconnection() imapobj = self.imapserver.acquireconnection()
@ -716,6 +733,7 @@ class IMAPFolder(BaseFolder):
elif operation == '-': elif operation == '-':
self.messagelist[uid]['flags'] -= flags self.messagelist[uid]['flags'] -= flags
# Interface from BaseFolder
def change_message_uid(self, uid, new_uid): def change_message_uid(self, uid, new_uid):
"""Change the message from existing uid to new_uid """Change the message from existing uid to new_uid
@ -724,19 +742,21 @@ class IMAPFolder(BaseFolder):
'%d to %d' % (uid, new_uid), '%d to %d' % (uid, new_uid),
OfflineImapError.ERROR.MESSAGE) OfflineImapError.ERROR.MESSAGE)
# Interface from BaseFolder
def deletemessage(self, uid): def deletemessage(self, uid):
self.deletemessages_noconvert([uid]) self.__deletemessages_noconvert([uid])
# Interface from BaseFolder
def deletemessages(self, uidlist): def deletemessages(self, uidlist):
self.deletemessages_noconvert(uidlist) self.__deletemessages_noconvert(uidlist)
def deletemessages_noconvert(self, uidlist): def __deletemessages_noconvert(self, uidlist):
# Weed out ones not in self.messagelist # Weed out ones not in self.messagelist
uidlist = [uid for uid in uidlist if self.uidexists(uid)] uidlist = [uid for uid in uidlist if self.uidexists(uid)]
if not len(uidlist): if not len(uidlist):
return return
self.addmessagesflags_noconvert(uidlist, set('T')) self.__addmessagesflags_noconvert(uidlist, set('T'))
imapobj = self.imapserver.acquireconnection() imapobj = self.imapserver.acquireconnection()
try: try:
try: try:
@ -750,5 +770,3 @@ class IMAPFolder(BaseFolder):
self.imapserver.releaseconnection(imapobj) self.imapserver.releaseconnection(imapobj)
for uid in uidlist: for uid in uidlist:
del self.messagelist[uid] del self.messagelist[uid]

View File

@ -33,21 +33,26 @@ class LocalStatusFolder(BaseFolder):
False) False)
"""Should we perform fsyncs as often as possible?""" """Should we perform fsyncs as often as possible?"""
# Interface from BaseFolder
def storesmessages(self): def storesmessages(self):
return 0 return 0
def isnewfolder(self): def isnewfolder(self):
return not os.path.exists(self.filename) return not os.path.exists(self.filename)
# Interface from BaseFolder
def getname(self): def getname(self):
return self.name return self.name
# Interface from BaseFolder
def getroot(self): def getroot(self):
return self.repository.root return self.repository.root
# Interface from BaseFolder
def getsep(self): def getsep(self):
return self.sep return self.sep
# Interface from BaseFolder
def getfullname(self): def getfullname(self):
return self.filename return self.filename
@ -55,6 +60,7 @@ class LocalStatusFolder(BaseFolder):
if not self.isnewfolder(): if not self.isnewfolder():
os.unlink(self.filename) os.unlink(self.filename)
# Interface from BaseFolder
def cachemessagelist(self): def cachemessagelist(self):
if self.isnewfolder(): if self.isnewfolder():
self.messagelist = {} self.messagelist = {}
@ -103,9 +109,11 @@ class LocalStatusFolder(BaseFolder):
os.fsync(fd) os.fsync(fd)
os.close(fd) os.close(fd)
# Interface from BaseFolder
def getmessagelist(self): def getmessagelist(self):
return self.messagelist return self.messagelist
# Interface from BaseFolder
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.
@ -124,19 +132,24 @@ class LocalStatusFolder(BaseFolder):
self.save() self.save()
return uid return uid
# Interface from BaseFolder
def getmessageflags(self, uid): def getmessageflags(self, uid):
return self.messagelist[uid]['flags'] return self.messagelist[uid]['flags']
# Interface from BaseFolder
def getmessagetime(self, uid): def getmessagetime(self, uid):
return self.messagelist[uid]['time'] return self.messagelist[uid]['time']
# Interface from BaseFolder
def savemessageflags(self, uid, flags): def savemessageflags(self, uid, flags):
self.messagelist[uid]['flags'] = flags self.messagelist[uid]['flags'] = flags
self.save() self.save()
# Interface from BaseFolder
def deletemessage(self, uid): def deletemessage(self, uid):
self.deletemessages([uid]) self.deletemessages([uid])
# Interface from BaseFolder
def deletemessages(self, uidlist): def deletemessages(self, uidlist):
# Weed out ones not in self.messagelist # Weed out ones not in self.messagelist
uidlist = [uid for uid in uidlist if uid in self.messagelist] uidlist = [uid for uid in uidlist if uid in self.messagelist]

View File

@ -62,14 +62,14 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
cursor = self.connection.execute("SELECT value from metadata WHERE key='db_version'") cursor = self.connection.execute("SELECT value from metadata WHERE key='db_version'")
except sqlite.DatabaseError: except sqlite.DatabaseError:
#db file missing or corrupt, recreate it. #db file missing or corrupt, recreate it.
self.upgrade_db(0) self.__upgrade_db(0)
else: else:
# fetch db version and upgrade if needed # fetch db version and upgrade if needed
version = int(cursor.fetchone()[0]) version = int(cursor.fetchone()[0])
if version < LocalStatusSQLiteFolder.cur_version: if version < LocalStatusSQLiteFolder.cur_version:
self.upgrade_db(version) self.__upgrade_db(version)
def sql_write(self, sql, vars=None, executemany=False): def __sql_write(self, sql, vars=None, executemany=False):
"""Execute some SQL, retrying if the db was locked. """Execute some SQL, retrying if the db was locked.
:param sql: the SQL string passed to execute() :param sql: the SQL string passed to execute()
@ -106,7 +106,7 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
self._dblock.release() self._dblock.release()
return cursor return cursor
def upgrade_db(self, from_ver): def __upgrade_db(self, from_ver):
"""Upgrade the sqlite format from version 'from_ver' to current""" """Upgrade the sqlite format from version 'from_ver' to current"""
if hasattr(self, 'connection'): if hasattr(self, 'connection'):
@ -116,7 +116,7 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
if from_ver == 0: if from_ver == 0:
# from_ver==0: no db existent: plain text migration? # from_ver==0: no db existent: plain text migration?
self.create_db() self.__create_db()
# below was derived from repository.getfolderfilename() logic # below was derived from repository.getfolderfilename() logic
plaintextfilename = os.path.join( plaintextfilename = os.path.join(
self.repository.account.getaccountmeta(), self.repository.account.getaccountmeta(),
@ -144,7 +144,7 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
# if from_ver <= 1: ... #upgrade from 1 to 2 # if from_ver <= 1: ... #upgrade from 1 to 2
# if from_ver <= 2: ... #upgrade from 2 to 3 # if from_ver <= 2: ... #upgrade from 2 to 3
def create_db(self): def __create_db(self):
"""Create a new db file""" """Create a new db file"""
self.ui._msg('Creating new Local Status db for %s:%s' \ self.ui._msg('Creating new Local Status db for %s:%s' \
% (self.repository, self)) % (self.repository, self))
@ -158,16 +158,19 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
""") """)
self.connection.commit() self.connection.commit()
# Interface from LocalStatusFolder
def isnewfolder(self): def isnewfolder(self):
# testing the existence of the db file won't work. It is created # testing the existence of the db file won't work. It is created
# as soon as this class instance was intitiated. So say it is a # as soon as this class instance was intitiated. So say it is a
# new folder when there are no messages at all recorded in it. # new folder when there are no messages at all recorded in it.
return self.getmessagecount() > 0 return self.getmessagecount() > 0
# Interface from LocalStatusFolder
def deletemessagelist(self): def deletemessagelist(self):
"""delete all messages in the db""" """delete all messages in the db"""
self.sql_write('DELETE FROM status') self.__sql_write('DELETE FROM status')
# Interface from BaseFolder
def cachemessagelist(self): def cachemessagelist(self):
self.messagelist = {} self.messagelist = {}
cursor = self.connection.execute('SELECT id,flags from status') cursor = self.connection.execute('SELECT id,flags from status')
@ -175,6 +178,7 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
flags = set(row[1]) flags = set(row[1])
self.messagelist[row[0]] = {'uid': row[0], 'flags': flags} self.messagelist[row[0]] = {'uid': row[0], 'flags': flags}
# Interface from LocalStatusFolder
def save(self): def save(self):
#Noop in this backend #Noop in this backend
pass pass
@ -215,6 +219,7 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
# return flags # return flags
# assert False,"getmessageflags() called on non-existing message" # assert False,"getmessageflags() called on non-existing message"
# Interface from BaseFolder
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.
@ -231,21 +236,24 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
self.messagelist[uid] = {'uid': uid, 'flags': flags, 'time': rtime} self.messagelist[uid] = {'uid': uid, 'flags': flags, 'time': rtime}
flags = ''.join(sorted(flags)) flags = ''.join(sorted(flags))
self.sql_write('INSERT INTO status (id,flags) VALUES (?,?)', self.__sql_write('INSERT INTO status (id,flags) VALUES (?,?)',
(uid,flags)) (uid,flags))
return uid return uid
# Interface from BaseFolder
def savemessageflags(self, uid, flags): def savemessageflags(self, uid, flags):
self.messagelist[uid] = {'uid': uid, 'flags': flags} self.messagelist[uid] = {'uid': uid, 'flags': flags}
flags = ''.join(sorted(flags)) flags = ''.join(sorted(flags))
self.sql_write('UPDATE status SET flags=? WHERE id=?',(flags,uid)) self.__sql_write('UPDATE status SET flags=? WHERE id=?',(flags,uid))
# Interface from BaseFolder
def deletemessage(self, uid): def deletemessage(self, uid):
if not uid in self.messagelist: if not uid in self.messagelist:
return return
self.sql_write('DELETE FROM status WHERE id=?', (uid, )) self.__sql_write('DELETE FROM status WHERE id=?', (uid, ))
del(self.messagelist[uid]) del(self.messagelist[uid])
# Interface from BaseFolder
def deletemessages(self, uidlist): def deletemessages(self, uidlist):
"""Delete list of UIDs from status cache """Delete list of UIDs from status cache
@ -257,6 +265,6 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
if not len(uidlist): if not len(uidlist):
return return
# arg2 needs to be an iterable of 1-tuples [(1,),(2,),...] # arg2 needs to be an iterable of 1-tuples [(1,),(2,),...]
self.sql_write('DELETE FROM status WHERE id=?', zip(uidlist, ), True) self.__sql_write('DELETE FROM status WHERE id=?', zip(uidlist, ), True)
for uid in uidlist: for uid in uidlist:
del(self.messagelist[uid]) del(self.messagelist[uid])

View File

@ -43,7 +43,7 @@ timeseq = 0
lasttime = 0 lasttime = 0
timelock = Lock() timelock = Lock()
def gettimeseq(): def _gettimeseq():
global lasttime, timeseq, timelock global lasttime, timeseq, timelock
timelock.acquire() timelock.acquire()
try: try:
@ -79,10 +79,12 @@ class MaildirFolder(BaseFolder):
# Cache the full folder path, as we use getfullname() very often # Cache the full folder path, as we use getfullname() very often
self._fullname = os.path.join(self.getroot(), self.getname()) self._fullname = os.path.join(self.getroot(), self.getname())
# Interface from BaseFolder
def getfullname(self): def getfullname(self):
"""Return the absolute file path to the Maildir folder (sans cur|new)""" """Return the absolute file path to the Maildir folder (sans cur|new)"""
return self._fullname return self._fullname
# Interface from BaseFolder
def get_uidvalidity(self): def get_uidvalidity(self):
"""Retrieve the current connections UIDVALIDITY value """Retrieve the current connections UIDVALIDITY value
@ -191,6 +193,7 @@ class MaildirFolder(BaseFolder):
retval[uid] = {'flags': flags, 'filename': filepath} retval[uid] = {'flags': flags, 'filename': filepath}
return retval return retval
# Interface from BaseFolder
def quickchanged(self, statusfolder): def quickchanged(self, statusfolder):
"""Returns True if the Maildir has changed""" """Returns True if the Maildir has changed"""
self.cachemessagelist() self.cachemessagelist()
@ -204,13 +207,16 @@ class MaildirFolder(BaseFolder):
return True return True
return False #Nope, nothing changed return False #Nope, nothing changed
# Interface from BaseFolder
def cachemessagelist(self): def cachemessagelist(self):
if self.messagelist is None: if self.messagelist is None:
self.messagelist = self._scanfolder() self.messagelist = self._scanfolder()
# Interface from BaseFolder
def getmessagelist(self): def getmessagelist(self):
return self.messagelist return self.messagelist
# Interface from BaseFolder
def getmessage(self, uid): def getmessage(self, uid):
"""Return the content of the message""" """Return the content of the message"""
filename = self.messagelist[uid]['filename'] filename = self.messagelist[uid]['filename']
@ -222,22 +228,24 @@ class MaildirFolder(BaseFolder):
# read it as text? # read it as text?
return retval.replace("\r\n", "\n") return retval.replace("\r\n", "\n")
# Interface from BaseFolder
def getmessagetime(self, uid): def getmessagetime(self, uid):
filename = self.messagelist[uid]['filename'] filename = self.messagelist[uid]['filename']
filepath = os.path.join(self.getfullname(), filename) filepath = os.path.join(self.getfullname(), filename)
return os.path.getmtime(filepath) return os.path.getmtime(filepath)
def new_message_filename(self, uid, flags=set()): def __new_message_filename(self, uid, flags=set()):
"""Creates a new unique Maildir filename """Creates a new unique Maildir filename
:param uid: The UID`None`, or a set of maildir flags :param uid: The UID`None`, or a set of maildir flags
:param flags: A set of maildir flags :param flags: A set of maildir flags
:returns: String containing unique message filename""" :returns: String containing unique message filename"""
timeval, timeseq = gettimeseq() timeval, timeseq = _gettimeseq()
return '%d_%d.%d.%s,U=%d,FMD5=%s%s2,%s' % \ return '%d_%d.%d.%s,U=%d,FMD5=%s%s2,%s' % \
(timeval, timeseq, os.getpid(), socket.gethostname(), (timeval, timeseq, os.getpid(), socket.gethostname(),
uid, self._foldermd5, self.infosep, ''.join(sorted(flags))) uid, self._foldermd5, self.infosep, ''.join(sorted(flags)))
# Interface from BaseFolder
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.
@ -259,7 +267,7 @@ class MaildirFolder(BaseFolder):
# Otherwise, save the message in tmp/ and then call savemessageflags() # Otherwise, save the message in tmp/ and then call savemessageflags()
# to give it a permanent home. # to give it a permanent home.
tmpdir = os.path.join(self.getfullname(), 'tmp') tmpdir = os.path.join(self.getfullname(), 'tmp')
messagename = self.new_message_filename(uid, flags) messagename = self.__new_message_filename(uid, flags)
# open file and write it out # open file and write it out
try: try:
fd = os.open(os.path.join(tmpdir, messagename), fd = os.open(os.path.join(tmpdir, messagename),
@ -291,9 +299,11 @@ class MaildirFolder(BaseFolder):
self.ui.debug('maildir', 'savemessage: returning uid %d' % uid) self.ui.debug('maildir', 'savemessage: returning uid %d' % uid)
return uid return uid
# Interface from BaseFolder
def getmessageflags(self, uid): def getmessageflags(self, uid):
return self.messagelist[uid]['flags'] return self.messagelist[uid]['flags']
# Interface from BaseFolder
def savemessageflags(self, uid, flags): def savemessageflags(self, uid, flags):
"""Sets the specified message's flags to the given set. """Sets the specified message's flags to the given set.
@ -331,6 +341,7 @@ class MaildirFolder(BaseFolder):
self.messagelist[uid]['flags'] = flags self.messagelist[uid]['flags'] = flags
self.messagelist[uid]['filename'] = newfilename self.messagelist[uid]['filename'] = newfilename
# Interface from BaseFolder
def change_message_uid(self, uid, new_uid): def change_message_uid(self, uid, new_uid):
"""Change the message from existing uid to new_uid """Change the message from existing uid to new_uid
@ -345,12 +356,13 @@ class MaildirFolder(BaseFolder):
oldfilename = self.messagelist[uid]['filename'] oldfilename = self.messagelist[uid]['filename']
dir_prefix, filename = os.path.split(oldfilename) dir_prefix, filename = os.path.split(oldfilename)
flags = self.getmessageflags(uid) flags = self.getmessageflags(uid)
filename = self.new_message_filename(new_uid, flags) filename = self.__new_message_filename(new_uid, flags)
os.rename(os.path.join(self.getfullname(), oldfilename), os.rename(os.path.join(self.getfullname(), oldfilename),
os.path.join(self.getfullname(), dir_prefix, filename)) os.path.join(self.getfullname(), dir_prefix, filename))
self.messagelist[new_uid] = self.messagelist[uid] self.messagelist[new_uid] = self.messagelist[uid]
del self.messagelist[uid] del self.messagelist[uid]
# Interface from BaseFolder
def deletemessage(self, uid): def deletemessage(self, uid):
"""Unlinks a message file from the Maildir. """Unlinks a message file from the Maildir.
@ -375,4 +387,3 @@ class MaildirFolder(BaseFolder):
os.unlink(filepath) os.unlink(filepath)
# Yep -- return. # Yep -- return.
del(self.messagelist[uid]) del(self.messagelist[uid])

View File

@ -91,6 +91,7 @@ class MappedIMAPFolder(IMAPFolder):
"iling list.".format(e.args[0], self), "iling list.".format(e.args[0], self),
OfflineImapError.ERROR.MESSAGE) OfflineImapError.ERROR.MESSAGE)
# Interface from BaseFolder
def cachemessagelist(self): def cachemessagelist(self):
self._mb.cachemessagelist() self._mb.cachemessagelist()
reallist = self._mb.getmessagelist() reallist = self._mb.getmessagelist()
@ -122,12 +123,14 @@ class MappedIMAPFolder(IMAPFolder):
finally: finally:
self.maplock.release() self.maplock.release()
# Interface from BaseFolder
def uidexists(self, ruid): def uidexists(self, ruid):
"""Checks if the (remote) UID exists in this Folder""" """Checks if the (remote) UID exists in this Folder"""
# This implementation overrides the one in BaseFolder, as it is # This implementation overrides the one in BaseFolder, as it is
# much more efficient for the mapped case. # much more efficient for the mapped case.
return ruid in self.r2l return ruid in self.r2l
# Interface from BaseFolder
def getmessageuidlist(self): def getmessageuidlist(self):
"""Gets a list of (remote) UIDs. """Gets a list of (remote) UIDs.
You may have to call cachemessagelist() before calling this function!""" You may have to call cachemessagelist() before calling this function!"""
@ -135,6 +138,7 @@ class MappedIMAPFolder(IMAPFolder):
# much more efficient for the mapped case. # much more efficient for the mapped case.
return self.r2l.keys() return self.r2l.keys()
# Interface from BaseFolder
def getmessagecount(self): def getmessagecount(self):
"""Gets the number of messages in this folder. """Gets the number of messages in this folder.
You may have to call cachemessagelist() before calling this function!""" You may have to call cachemessagelist() before calling this function!"""
@ -142,6 +146,7 @@ class MappedIMAPFolder(IMAPFolder):
# much more efficient for the mapped case. # much more efficient for the mapped case.
return len(self.r2l) return len(self.r2l)
# Interface from BaseFolder
def getmessagelist(self): def getmessagelist(self):
"""Gets the current message list. This function's implementation """Gets the current message list. This function's implementation
is quite expensive for the mapped UID case. You must call is quite expensive for the mapped UID case. You must call
@ -167,10 +172,12 @@ class MappedIMAPFolder(IMAPFolder):
finally: finally:
self.maplock.release() self.maplock.release()
# Interface from BaseFolder
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.r2l[uid]) return self._mb.getmessage(self.r2l[uid])
# Interface from BaseFolder
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.
@ -216,12 +223,15 @@ class MappedIMAPFolder(IMAPFolder):
self.maplock.release() self.maplock.release()
return uid return uid
# Interface from BaseFolder
def getmessageflags(self, uid): def getmessageflags(self, uid):
return self._mb.getmessageflags(self.r2l[uid]) return self._mb.getmessageflags(self.r2l[uid])
# Interface from BaseFolder
def getmessagetime(self, uid): def getmessagetime(self, uid):
return None return None
# Interface from BaseFolder
def savemessageflags(self, uid, flags): def savemessageflags(self, uid, flags):
""" """
@ -230,13 +240,16 @@ class MappedIMAPFolder(IMAPFolder):
dryrun mode.""" dryrun mode."""
self._mb.savemessageflags(self.r2l[uid], flags) self._mb.savemessageflags(self.r2l[uid], flags)
# Interface from BaseFolder
def addmessageflags(self, uid, flags): def addmessageflags(self, uid, flags):
self._mb.addmessageflags(self.r2l[uid], flags) self._mb.addmessageflags(self.r2l[uid], flags)
# Interface from BaseFolder
def addmessagesflags(self, uidlist, flags): def addmessagesflags(self, uidlist, flags):
self._mb.addmessagesflags(self._uidlist(self.r2l, uidlist), self._mb.addmessagesflags(self._uidlist(self.r2l, uidlist),
flags) flags)
# Interface from BaseFolder
def change_message_uid(self, ruid, new_ruid): def change_message_uid(self, ruid, new_ruid):
"""Change the message from existing ruid to new_ruid """Change the message from existing ruid to new_ruid
@ -279,17 +292,21 @@ class MappedIMAPFolder(IMAPFolder):
finally: finally:
self.maplock.release() self.maplock.release()
# Interface from BaseFolder
def deletemessageflags(self, uid, flags): def deletemessageflags(self, uid, flags):
self._mb.deletemessageflags(self.r2l[uid], flags) self._mb.deletemessageflags(self.r2l[uid], flags)
# Interface from BaseFolder
def deletemessagesflags(self, uidlist, flags): def deletemessagesflags(self, uidlist, flags):
self._mb.deletemessagesflags(self._uidlist(self.r2l, uidlist), self._mb.deletemessagesflags(self._uidlist(self.r2l, uidlist),
flags) flags)
# Interface from BaseFolder
def deletemessage(self, uid): def deletemessage(self, uid):
self._mb.deletemessage(self.r2l[uid]) self._mb.deletemessage(self.r2l[uid])
self._mapped_delete([uid]) self._mapped_delete([uid])
# Interface from BaseFolder
def deletemessages(self, uidlist): def deletemessages(self, uidlist):
self._mb.deletemessages(self._uidlist(self.r2l, uidlist)) self._mb.deletemessages(self._uidlist(self.r2l, uidlist))
self._mapped_delete(uidlist) self._mapped_delete(uidlist)

View File

@ -31,7 +31,7 @@ from offlineimap.imaplib2 import IMAP4, IMAP4_SSL, zlib, IMAP4_PORT, InternalDat
class UsefulIMAPMixIn(object): class UsefulIMAPMixIn(object):
def getselectedfolder(self): def __getselectedfolder(self):
if self.state == 'SELECTED': if self.state == 'SELECTED':
return self.mailbox return self.mailbox
return None return None
@ -41,7 +41,7 @@ class UsefulIMAPMixIn(object):
:returns: 'OK' on success, nothing if the folder was already :returns: 'OK' on success, nothing if the folder was already
selected or raises an :exc:`OfflineImapError`""" selected or raises an :exc:`OfflineImapError`"""
if self.getselectedfolder() == mailbox and self.is_readonly == readonly \ if self.__getselectedfolder() == mailbox and self.is_readonly == readonly \
and not force: and not force:
# No change; return. # No change; return.
return return
@ -66,6 +66,7 @@ class UsefulIMAPMixIn(object):
raise OfflineImapError(errstr, severity) raise OfflineImapError(errstr, severity)
return result return result
# Overrides private function from IMAP4 (@imaplib2)
def _mesg(self, s, tn=None, secs=None): def _mesg(self, s, tn=None, secs=None):
new_mesg(self, s, tn, secs) new_mesg(self, s, tn, secs)

View File

@ -83,7 +83,7 @@ class IMAPServer:
self.sslcacertfile = repos.getsslcacertfile() self.sslcacertfile = repos.getsslcacertfile()
self.sslversion = repos.getsslversion() self.sslversion = repos.getsslversion()
if self.sslcacertfile is None: if self.sslcacertfile is None:
self.verifycert = None # disable cert verification self.__verifycert = None # disable cert verification
self.delim = None self.delim = None
self.root = None self.root = None
@ -99,7 +99,7 @@ class IMAPServer:
self.gss_vc = None self.gss_vc = None
self.gssapi = False self.gssapi = False
def getpassword(self): def __getpassword(self):
"""Returns the server password or None""" """Returns the server password or None"""
if self.goodpassword != None: # use cached good one first if self.goodpassword != None: # use cached good one first
return self.goodpassword return self.goodpassword
@ -114,6 +114,7 @@ class IMAPServer:
self.passworderror = None self.passworderror = None
return self.password return self.password
# XXX: is this function used anywhere?
def getroot(self): def getroot(self):
"""Returns this server's folder root. Can only be called after one """Returns this server's folder root. Can only be called after one
or more calls to acquireconnection.""" or more calls to acquireconnection."""
@ -136,39 +137,40 @@ class IMAPServer:
self.connectionlock.release() self.connectionlock.release()
self.semaphore.release() self.semaphore.release()
def md5handler(self, response): def __md5handler(self, response):
challenge = response.strip() challenge = response.strip()
self.ui.debug('imap', 'md5handler: got challenge %s' % challenge) self.ui.debug('imap', '__md5handler: got challenge %s' % challenge)
passwd = self.getpassword() passwd = self.__getpassword()
retval = self.username + ' ' + hmac.new(passwd, challenge).hexdigest() retval = self.username + ' ' + hmac.new(passwd, challenge).hexdigest()
self.ui.debug('imap', 'md5handler: returning %s' % retval) self.ui.debug('imap', '__md5handler: returning %s' % retval)
return retval return retval
def loginauth(self, imapobj): def __loginauth(self, imapobj):
""" Basic authentication via LOGIN command """ """ Basic authentication via LOGIN command """
self.ui.debug('imap', 'Attempting IMAP LOGIN authentication') self.ui.debug('imap', 'Attempting IMAP LOGIN authentication')
imapobj.login(self.username, self.getpassword()) imapobj.login(self.username, self.__getpassword())
def plainhandler(self, response): def __plainhandler(self, response):
""" """
Implements SASL PLAIN authentication, RFC 4616, Implements SASL PLAIN authentication, RFC 4616,
http://tools.ietf.org/html/rfc4616 http://tools.ietf.org/html/rfc4616
""" """
authc = self.username authc = self.username
passwd = self.getpassword() passwd = self.__getpassword()
authz = '' authz = ''
if self.user_identity != None: if self.user_identity != None:
authz = self.user_identity authz = self.user_identity
NULL = u'\x00' NULL = u'\x00'
retval = NULL.join((authz, authc, passwd)).encode('utf-8') retval = NULL.join((authz, authc, passwd)).encode('utf-8')
self.ui.debug('imap', 'plainhandler: returning %s' % retval) self.ui.debug('imap', '__plainhandler: returning %s' % retval)
return retval return retval
def gssauth(self, response): # XXX: describe function
def __gssauth(self, response):
data = base64.b64encode(response) data = base64.b64encode(response)
try: try:
if self.gss_step == self.GSS_STATE_STEP: if self.gss_step == self.GSS_STATE_STEP:
@ -196,7 +198,7 @@ class IMAPServer:
return base64.b64decode(response) return base64.b64decode(response)
def _start_tls(self, imapobj): def __start_tls(self, imapobj):
if 'STARTTLS' in imapobj.capabilities and not self.usessl: if 'STARTTLS' in imapobj.capabilities and not self.usessl:
self.ui.debug('imap', 'Using STARTTLS connection') self.ui.debug('imap', 'Using STARTTLS connection')
try: try:
@ -207,7 +209,7 @@ class IMAPServer:
OfflineImapError.ERROR.REPO) OfflineImapError.ERROR.REPO)
## All _authn_* procedures are helpers that do authentication. ## All __authn_* procedures are helpers that do authentication.
## They are class methods that take one parameter, IMAP object. ## They are class methods that take one parameter, IMAP object.
## ##
## Each function should return True if authentication was ## Each function should return True if authentication was
@ -224,13 +226,13 @@ class IMAPServer:
## - OfflineImapError means that function detected some ## - OfflineImapError means that function detected some
## problem by itself. ## problem by itself.
def _authn_gssapi(self, imapobj): def __authn_gssapi(self, imapobj):
if not have_gss: if not have_gss:
return False return False
self.connectionlock.acquire() self.connectionlock.acquire()
try: try:
imapobj.authenticate('GSSAPI', self.gssauth) imapobj.authenticate('GSSAPI', self.__gssauth)
return True return True
except imapobj.error as e: except imapobj.error as e:
self.gssapi = False self.gssapi = False
@ -243,15 +245,15 @@ class IMAPServer:
finally: finally:
self.connectionlock.release() self.connectionlock.release()
def _authn_cram_md5(self, imapobj): def __authn_cram_md5(self, imapobj):
imapobj.authenticate('CRAM-MD5', self.md5handler) imapobj.authenticate('CRAM-MD5', self.__md5handler)
return True return True
def _authn_plain(self, imapobj): def __authn_plain(self, imapobj):
imapobj.authenticate('PLAIN', self.plainhandler) imapobj.authenticate('PLAIN', self.__plainhandler)
return True return True
def _authn_login(self, imapobj): def __authn_login(self, imapobj):
# Use LOGIN command, unless LOGINDISABLED is advertized # Use LOGIN command, unless LOGINDISABLED is advertized
# (per RFC 2595) # (per RFC 2595)
if 'LOGINDISABLED' in imapobj.capabilities: if 'LOGINDISABLED' in imapobj.capabilities:
@ -259,11 +261,11 @@ class IMAPServer:
"disabled by server. Need to use SSL?", "disabled by server. Need to use SSL?",
OfflineImapError.ERROR.REPO) OfflineImapError.ERROR.REPO)
else: else:
self.loginauth(imapobj) self.__loginauth(imapobj)
return True return True
def _authn_helper(self, imapobj): def __authn_helper(self, imapobj):
""" """
Authentication machinery for self.acquireconnection(). Authentication machinery for self.acquireconnection().
@ -283,10 +285,10 @@ class IMAPServer:
# - tryTLS flag, # - tryTLS flag,
# - check IMAP capability flag. # - check IMAP capability flag.
auth_methods = { auth_methods = {
"GSSAPI": (self._authn_gssapi, False, True), "GSSAPI": (self.__authn_gssapi, False, True),
"CRAM-MD5": (self._authn_cram_md5, True, True), "CRAM-MD5": (self.__authn_cram_md5, True, True),
"PLAIN": (self._authn_plain, True, True), "PLAIN": (self.__authn_plain, True, True),
"LOGIN": (self._authn_login, True, False), "LOGIN": (self.__authn_login, True, False),
} }
# Stack stores pairs of (method name, exception) # Stack stores pairs of (method name, exception)
exc_stack = [] exc_stack = []
@ -311,7 +313,7 @@ class IMAPServer:
# they could have been changed after STARTTLS. # they could have been changed after STARTTLS.
if tryTLS and not tried_tls: if tryTLS and not tried_tls:
tried_tls = True tried_tls = True
self._start_tls(imapobj) self.__start_tls(imapobj)
if check_cap: if check_cap:
cap = "AUTH=" + m cap = "AUTH=" + m
@ -348,6 +350,7 @@ class IMAPServer:
OfflineImapError.ERROR.REPO) OfflineImapError.ERROR.REPO)
# XXX: move above, closer to releaseconnection()
def acquireconnection(self): def acquireconnection(self):
"""Fetches a connection from the pool, making sure to create a new one """Fetches a connection from the pool, making sure to create a new one
if needed, to obey the maximum connection limits, etc. if needed, to obey the maximum connection limits, etc.
@ -400,7 +403,7 @@ class IMAPServer:
self.sslclientkey, self.sslclientkey,
self.sslclientcert, self.sslclientcert,
self.sslcacertfile, self.sslcacertfile,
self.verifycert, self.__verifycert,
self.sslversion, self.sslversion,
timeout=socket.getdefaulttimeout(), timeout=socket.getdefaulttimeout(),
fingerprint=fingerprint fingerprint=fingerprint
@ -412,7 +415,7 @@ class IMAPServer:
if not self.preauth_tunnel: if not self.preauth_tunnel:
try: try:
self._authn_helper(imapobj) self.__authn_helper(imapobj)
self.goodpassword = self.password self.goodpassword = self.password
success = 1 success = 1
except OfflineImapError as e: except OfflineImapError as e:
@ -562,7 +565,7 @@ class IMAPServer:
self.ui.debug('imap', 'keepalive: event is set; exiting') self.ui.debug('imap', 'keepalive: event is set; exiting')
return return
def verifycert(self, cert, hostname): def __verifycert(self, cert, hostname):
'''Verify that cert (in socket.getpeercert() format) matches hostname. '''Verify that cert (in socket.getpeercert() format) matches hostname.
CRLs are not handled. CRLs are not handled.
@ -646,7 +649,7 @@ class IdleThread(object):
self.parent.releaseconnection(imapobj) self.parent.releaseconnection(imapobj)
self.stop_sig.wait() # wait until we are supposed to quit self.stop_sig.wait() # wait until we are supposed to quit
def dosync(self): def __dosync(self):
remoterepos = self.parent.repos remoterepos = self.parent.repos
account = remoterepos.account account = remoterepos.account
localrepos = account.localrepos localrepos = account.localrepos
@ -663,7 +666,7 @@ class IdleThread(object):
ui = getglobalui() ui = getglobalui()
ui.unregisterthread(currentThread()) #syncfolder registered the thread ui.unregisterthread(currentThread()) #syncfolder registered the thread
def idle(self): def __idle(self):
"""Invoke IDLE mode until timeout or self.stop() is invoked""" """Invoke IDLE mode until timeout or self.stop() is invoked"""
def callback(args): def callback(args):
"""IDLE callback function invoked by imaplib2 """IDLE callback function invoked by imaplib2
@ -696,7 +699,7 @@ class IdleThread(object):
else: else:
success = True success = True
if "IDLE" in imapobj.capabilities: if "IDLE" in imapobj.capabilities:
imapobj.idle(callback=callback) imapobj.__idle(callback=callback)
else: else:
self.ui.warn("IMAP IDLE not supported on server '%s'." self.ui.warn("IMAP IDLE not supported on server '%s'."
"Sleep until next refresh cycle." % imapobj.identifier) "Sleep until next refresh cycle." % imapobj.identifier)
@ -716,4 +719,4 @@ class IdleThread(object):
# here not via self.stop, but because IDLE responded. Do # here not via self.stop, but because IDLE responded. Do
# another round and invoke actual syncing. # another round and invoke actual syncing.
self.stop_sig.clear() self.stop_sig.clear()
self.dosync() self.__dosync()

View File

@ -21,7 +21,7 @@ import string
from offlineimap.ui import getglobalui from offlineimap.ui import getglobalui
def debug(*args): def __debug(*args):
msg = [] msg = []
for arg in args: for arg in args:
msg.append(str(arg)) msg.append(str(arg))
@ -50,7 +50,7 @@ def flagsplit(string):
raise ValueError("Passed string '%s' is not a flag list" % string) raise ValueError("Passed string '%s' is not a flag list" % string)
return imapsplit(string[1:-1]) return imapsplit(string[1:-1])
def options2hash(list): def __options2hash(list):
"""convert list [1,2,3,4,5,6] to {1:2, 3:4, 5:6}""" """convert list [1,2,3,4,5,6] to {1:2, 3:4, 5:6}"""
# effectively this does dict(zip(l[::2],l[1::2])), however # effectively this does dict(zip(l[::2],l[1::2])), however
# measurements seemed to have indicated that the manual variant is # measurements seemed to have indicated that the manual variant is
@ -60,7 +60,7 @@ def options2hash(list):
while (counter < len(list)): while (counter < len(list)):
retval[list[counter]] = list[counter + 1] retval[list[counter]] = list[counter + 1]
counter += 2 counter += 2
debug("options2hash returning:", retval) __debug("__options2hash returning:", retval)
return retval return retval
def flags2hash(flags): def flags2hash(flags):
@ -68,7 +68,7 @@ def flags2hash(flags):
E.g. '(FLAGS (\\Seen Old) UID 4807)' leads to E.g. '(FLAGS (\\Seen Old) UID 4807)' leads to
{'FLAGS': '(\\Seen Old)', 'UID': '4807'}""" {'FLAGS': '(\\Seen Old)', 'UID': '4807'}"""
return options2hash(flagsplit(flags)) return __options2hash(flagsplit(flags))
def imapsplit(imapstring): def imapsplit(imapstring):
"""Takes a string from an IMAP conversation and returns a list containing """Takes a string from an IMAP conversation and returns a list containing
@ -81,7 +81,7 @@ def imapsplit(imapstring):
['(\\HasNoChildren)', '"."', '"INBOX.Sent"']""" ['(\\HasNoChildren)', '"."', '"INBOX.Sent"']"""
if not isinstance(imapstring, basestring): if not isinstance(imapstring, basestring):
debug("imapsplit() got a non-string input; working around.") __debug("imapsplit() got a non-string input; working around.")
# Sometimes, imaplib will throw us a tuple if the input # Sometimes, imaplib will throw us a tuple if the input
# contains a literal. See Python bug # contains a literal. See Python bug
# #619732 at https://sourceforge.net/tracker/index.php?func=detail&aid=619732&group_id=5470&atid=105470 # #619732 at https://sourceforge.net/tracker/index.php?func=detail&aid=619732&group_id=5470&atid=105470
@ -103,7 +103,7 @@ def imapsplit(imapstring):
arg = arg.replace('\\', '\\\\') arg = arg.replace('\\', '\\\\')
arg = arg.replace('"', '\\"') arg = arg.replace('"', '\\"')
arg = '"%s"' % arg arg = '"%s"' % arg
debug("imapsplit() non-string [%d]: Appending %s" %\ __debug("imapsplit() non-string [%d]: Appending %s" %\
(i, arg)) (i, arg))
retval.append(arg) retval.append(arg)
else: else:
@ -113,10 +113,10 @@ def imapsplit(imapstring):
# Recursion to the rescue. # Recursion to the rescue.
arg = imapstring[i] arg = imapstring[i]
arg = re.sub('\{\d+\}$', '', arg) arg = re.sub('\{\d+\}$', '', arg)
debug("imapsplit() non-string [%d]: Feeding %s to recursion" %\ __debug("imapsplit() non-string [%d]: Feeding %s to recursion" %\
(i, arg)) (i, arg))
retval.extend(imapsplit(arg)) retval.extend(imapsplit(arg))
debug("imapsplit() non-string: returning %s" % str(retval)) __debug("imapsplit() non-string: returning %s" % str(retval))
return retval return retval
workstr = imapstring.strip() workstr = imapstring.strip()
@ -137,7 +137,7 @@ def imapsplit(imapstring):
retval.append(parenlist) retval.append(parenlist)
elif workstr[0] == '"': elif workstr[0] == '"':
# quoted fragments '"...\"..."' # quoted fragments '"...\"..."'
(quoted, rest) = _split_quoted(workstr) (quoted, rest) = __split_quoted(workstr)
retval.append(quoted) retval.append(quoted)
workstr = rest workstr = rest
else: else:
@ -213,7 +213,7 @@ def uid_sequence(uidlist):
return ",".join(retval) return ",".join(retval)
def _split_quoted(string): def __split_quoted(string):
""" """
Looks for the ending quote character in the string that starts Looks for the ending quote character in the string that starts
with quote character, splitting out quoted component and the with quote character, splitting out quoted component and the

View File

@ -43,13 +43,13 @@ class OfflineImap:
def run(self): def run(self):
"""Parse the commandline and invoke everything""" """Parse the commandline and invoke everything"""
# next line also sets self.config and self.ui # next line also sets self.config and self.ui
options, args = self.parse_cmd_options() options, args = self.__parse_cmd_options()
if options.diagnostics: if options.diagnostics:
self.serverdiagnostics(options) self.__serverdiagnostics(options)
else: else:
self.sync(options) self.__sync(options)
def parse_cmd_options(self): def __parse_cmd_options(self):
parser = OptionParser(version=offlineimap.__version__, parser = OptionParser(version=offlineimap.__version__,
description="%s.\n\n%s" % description="%s.\n\n%s" %
(offlineimap.__copyright__, (offlineimap.__copyright__,
@ -297,7 +297,7 @@ class OfflineImap:
self.config = config self.config = config
return (options, args) return (options, args)
def sync(self, options): def __sync(self, options):
"""Invoke the correct single/multithread syncing """Invoke the correct single/multithread syncing
self.config is supposed to have been correctly initialized self.config is supposed to have been correctly initialized
@ -360,7 +360,7 @@ class OfflineImap:
if options.singlethreading: if options.singlethreading:
#singlethreaded #singlethreaded
self.sync_singlethreaded(syncaccounts) self.__sync_singlethreaded(syncaccounts)
else: else:
# multithreaded # multithreaded
t = threadutil.ExitNotifyThread(target=syncmaster.syncitall, t = threadutil.ExitNotifyThread(target=syncmaster.syncitall,
@ -376,7 +376,7 @@ class OfflineImap:
self.ui.error(e) self.ui.error(e)
self.ui.terminate() self.ui.terminate()
def sync_singlethreaded(self, accs): def __sync_singlethreaded(self, accs):
"""Executed if we do not want a separate syncmaster thread """Executed if we do not want a separate syncmaster thread
:param accs: A list of accounts that should be synced :param accs: A list of accounts that should be synced
@ -387,7 +387,7 @@ class OfflineImap:
threading.currentThread().name = "Account sync %s" % accountname threading.currentThread().name = "Account sync %s" % accountname
account.syncrunner() account.syncrunner()
def serverdiagnostics(self, options): def __serverdiagnostics(self, options):
activeaccounts = self.config.get("general", "accounts") activeaccounts = self.config.get("general", "accounts")
if options.accounts: if options.accounts:
activeaccounts = options.accounts activeaccounts = options.accounts

View File

@ -44,9 +44,9 @@ def write():
if account not in boxes: if account not in boxes:
return return
genmbnames() __genmbnames()
def genmbnames(): def __genmbnames():
"""Takes a configparser object and a boxlist, which is a list of hashes """Takes a configparser object and a boxlist, which is a list of hashes
containing 'accountname' and 'foldername' keys.""" containing 'accountname' and 'foldername' keys."""
mblock.acquire() mblock.acquire()

View File

@ -70,11 +70,6 @@ class TestInternalFunctions(unittest.TestCase):
res = imaputil.flagsplit(b'(FLAGS (\\Seen Old) UID 4807)') res = imaputil.flagsplit(b'(FLAGS (\\Seen Old) UID 4807)')
self.assertEqual(res, [b'FLAGS', b'(\\Seen Old)', b'UID', b'4807']) self.assertEqual(res, [b'FLAGS', b'(\\Seen Old)', b'UID', b'4807'])
def test_03_options2hash(self):
"""Test imaputil.options2hash()"""
res = imaputil.options2hash([1,2,3,4,5,6])
self.assertEqual(res, {1:2, 3:4, 5:6})
def test_04_flags2hash(self): def test_04_flags2hash(self):
"""Test imaputil.flags2hash()""" """Test imaputil.flags2hash()"""
res = imaputil.flags2hash(b'(FLAGS (\\Seen Old) UID 4807)') res = imaputil.flags2hash(b'(FLAGS (\\Seen Old) UID 4807)')