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:: parse_cmd_options
.. automethod:: __parse_cmd_options
.. .. autoattribute:: ui

View File

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

View File

@ -96,6 +96,22 @@ class BaseFolder(object):
false otherwise. Probably only IMAP will return true."""
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):
"""For threading folders, returns the instancelimitname for
InstanceLimitedThreads."""
@ -328,7 +344,7 @@ class BaseFolder(object):
for uid in uidlist:
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
Note that this function does not check against dryrun settings,
@ -403,14 +419,14 @@ class BaseFolder(object):
(uid, self.accountname))
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
This will copy messages to dstfolder that exist locally but are
not in the statusfolder yet. The strategy is:
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.
- Update statusfolder
@ -431,23 +447,23 @@ class BaseFolder(object):
if offlineimap.accounts.Account.abort_NOW_signal.is_set():
break
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:
self.waitforthread()
thread = threadutil.InstanceLimitedThread(\
self.getcopyinstancelimit(),
target = self.copymessageto,
target = self.__copymessageto,
name = "Copy message from %s:%s" % (self.repository, self),
args = (uid, dstfolder, statusfolder))
thread.start()
threads.append(thread)
else:
self.copymessageto(uid, dstfolder, statusfolder,
self.__copymessageto(uid, dstfolder, statusfolder,
register = 0)
for thread in threads:
thread.join()
def syncmessagesto_delete(self, dstfolder, statusfolder):
def __syncmessagesto_delete(self, dstfolder, statusfolder):
"""Pass 2: Remove locally deleted messages on dst
Get all UIDS in statusfolder but not self. These are messages
@ -468,7 +484,7 @@ class BaseFolder(object):
for folder in [statusfolder, dstfolder]:
folder.deletemessages(deletelist)
def syncmessagesto_flags(self, dstfolder, statusfolder):
def __syncmessagesto_flags(self, dstfolder, statusfolder):
"""Pass 3: Flag synchronization
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 statusfolder: LocalStatus instance to sync against.
"""
passes = [('copying messages' , self.syncmessagesto_copy),
('deleting messages' , self.syncmessagesto_delete),
('syncing flags' , self.syncmessagesto_flags)]
passes = [('copying messages' , self.__syncmessagesto_copy),
('deleting messages' , self.__syncmessagesto_delete),
('syncing flags' , self.__syncmessagesto_flags)]
for (passdesc, action) in passes:
# bail out on CTRL-C or SIGTERM

View File

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

View File

@ -33,21 +33,26 @@ class LocalStatusFolder(BaseFolder):
False)
"""Should we perform fsyncs as often as possible?"""
# Interface from BaseFolder
def storesmessages(self):
return 0
def isnewfolder(self):
return not os.path.exists(self.filename)
# Interface from BaseFolder
def getname(self):
return self.name
# Interface from BaseFolder
def getroot(self):
return self.repository.root
# Interface from BaseFolder
def getsep(self):
return self.sep
# Interface from BaseFolder
def getfullname(self):
return self.filename
@ -55,6 +60,7 @@ class LocalStatusFolder(BaseFolder):
if not self.isnewfolder():
os.unlink(self.filename)
# Interface from BaseFolder
def cachemessagelist(self):
if self.isnewfolder():
self.messagelist = {}
@ -103,9 +109,11 @@ class LocalStatusFolder(BaseFolder):
os.fsync(fd)
os.close(fd)
# Interface from BaseFolder
def getmessagelist(self):
return self.messagelist
# Interface from BaseFolder
def savemessage(self, uid, content, flags, rtime):
"""Writes a new message, with the specified uid.
@ -124,19 +132,24 @@ class LocalStatusFolder(BaseFolder):
self.save()
return uid
# Interface from BaseFolder
def getmessageflags(self, uid):
return self.messagelist[uid]['flags']
# Interface from BaseFolder
def getmessagetime(self, uid):
return self.messagelist[uid]['time']
# Interface from BaseFolder
def savemessageflags(self, uid, flags):
self.messagelist[uid]['flags'] = flags
self.save()
# Interface from BaseFolder
def deletemessage(self, uid):
self.deletemessages([uid])
# Interface from BaseFolder
def deletemessages(self, uidlist):
# Weed out ones not 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'")
except sqlite.DatabaseError:
#db file missing or corrupt, recreate it.
self.upgrade_db(0)
self.__upgrade_db(0)
else:
# fetch db version and upgrade if needed
version = int(cursor.fetchone()[0])
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.
:param sql: the SQL string passed to execute()
@ -106,7 +106,7 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
self._dblock.release()
return cursor
def upgrade_db(self, from_ver):
def __upgrade_db(self, from_ver):
"""Upgrade the sqlite format from version 'from_ver' to current"""
if hasattr(self, 'connection'):
@ -116,7 +116,7 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
if from_ver == 0:
# from_ver==0: no db existent: plain text migration?
self.create_db()
self.__create_db()
# below was derived from repository.getfolderfilename() logic
plaintextfilename = os.path.join(
self.repository.account.getaccountmeta(),
@ -144,7 +144,7 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
# if from_ver <= 1: ... #upgrade from 1 to 2
# if from_ver <= 2: ... #upgrade from 2 to 3
def create_db(self):
def __create_db(self):
"""Create a new db file"""
self.ui._msg('Creating new Local Status db for %s:%s' \
% (self.repository, self))
@ -158,16 +158,19 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
""")
self.connection.commit()
# Interface from LocalStatusFolder
def isnewfolder(self):
# 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
# new folder when there are no messages at all recorded in it.
return self.getmessagecount() > 0
# Interface from LocalStatusFolder
def deletemessagelist(self):
"""delete all messages in the db"""
self.sql_write('DELETE FROM status')
self.__sql_write('DELETE FROM status')
# Interface from BaseFolder
def cachemessagelist(self):
self.messagelist = {}
cursor = self.connection.execute('SELECT id,flags from status')
@ -175,6 +178,7 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
flags = set(row[1])
self.messagelist[row[0]] = {'uid': row[0], 'flags': flags}
# Interface from LocalStatusFolder
def save(self):
#Noop in this backend
pass
@ -215,6 +219,7 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
# return flags
# assert False,"getmessageflags() called on non-existing message"
# Interface from BaseFolder
def savemessage(self, uid, content, flags, rtime):
"""Writes a new message, with the specified uid.
@ -231,21 +236,24 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
self.messagelist[uid] = {'uid': uid, 'flags': flags, 'time': rtime}
flags = ''.join(sorted(flags))
self.sql_write('INSERT INTO status (id,flags) VALUES (?,?)',
self.__sql_write('INSERT INTO status (id,flags) VALUES (?,?)',
(uid,flags))
return uid
# Interface from BaseFolder
def savemessageflags(self, uid, flags):
self.messagelist[uid] = {'uid': uid, 'flags': 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):
if not uid in self.messagelist:
return
self.sql_write('DELETE FROM status WHERE id=?', (uid, ))
self.__sql_write('DELETE FROM status WHERE id=?', (uid, ))
del(self.messagelist[uid])
# Interface from BaseFolder
def deletemessages(self, uidlist):
"""Delete list of UIDs from status cache
@ -257,6 +265,6 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
if not len(uidlist):
return
# 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:
del(self.messagelist[uid])

View File

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

View File

@ -91,6 +91,7 @@ class MappedIMAPFolder(IMAPFolder):
"iling list.".format(e.args[0], self),
OfflineImapError.ERROR.MESSAGE)
# Interface from BaseFolder
def cachemessagelist(self):
self._mb.cachemessagelist()
reallist = self._mb.getmessagelist()
@ -122,12 +123,14 @@ class MappedIMAPFolder(IMAPFolder):
finally:
self.maplock.release()
# Interface from BaseFolder
def uidexists(self, ruid):
"""Checks if the (remote) UID exists in this Folder"""
# This implementation overrides the one in BaseFolder, as it is
# much more efficient for the mapped case.
return ruid in self.r2l
# Interface from BaseFolder
def getmessageuidlist(self):
"""Gets a list of (remote) UIDs.
You may have to call cachemessagelist() before calling this function!"""
@ -135,6 +138,7 @@ class MappedIMAPFolder(IMAPFolder):
# much more efficient for the mapped case.
return self.r2l.keys()
# Interface from BaseFolder
def getmessagecount(self):
"""Gets the number of messages in this folder.
You may have to call cachemessagelist() before calling this function!"""
@ -142,6 +146,7 @@ class MappedIMAPFolder(IMAPFolder):
# much more efficient for the mapped case.
return len(self.r2l)
# Interface from BaseFolder
def getmessagelist(self):
"""Gets the current message list. This function's implementation
is quite expensive for the mapped UID case. You must call
@ -167,10 +172,12 @@ class MappedIMAPFolder(IMAPFolder):
finally:
self.maplock.release()
# Interface from BaseFolder
def getmessage(self, uid):
"""Returns the content of the specified message."""
return self._mb.getmessage(self.r2l[uid])
# Interface from BaseFolder
def savemessage(self, uid, content, flags, rtime):
"""Writes a new message, with the specified uid.
@ -216,12 +223,15 @@ class MappedIMAPFolder(IMAPFolder):
self.maplock.release()
return uid
# Interface from BaseFolder
def getmessageflags(self, uid):
return self._mb.getmessageflags(self.r2l[uid])
# Interface from BaseFolder
def getmessagetime(self, uid):
return None
# Interface from BaseFolder
def savemessageflags(self, uid, flags):
"""
@ -230,13 +240,16 @@ class MappedIMAPFolder(IMAPFolder):
dryrun mode."""
self._mb.savemessageflags(self.r2l[uid], flags)
# Interface from BaseFolder
def addmessageflags(self, uid, flags):
self._mb.addmessageflags(self.r2l[uid], flags)
# Interface from BaseFolder
def addmessagesflags(self, uidlist, flags):
self._mb.addmessagesflags(self._uidlist(self.r2l, uidlist),
flags)
# Interface from BaseFolder
def change_message_uid(self, ruid, new_ruid):
"""Change the message from existing ruid to new_ruid
@ -279,17 +292,21 @@ class MappedIMAPFolder(IMAPFolder):
finally:
self.maplock.release()
# Interface from BaseFolder
def deletemessageflags(self, uid, flags):
self._mb.deletemessageflags(self.r2l[uid], flags)
# Interface from BaseFolder
def deletemessagesflags(self, uidlist, flags):
self._mb.deletemessagesflags(self._uidlist(self.r2l, uidlist),
flags)
# Interface from BaseFolder
def deletemessage(self, uid):
self._mb.deletemessage(self.r2l[uid])
self._mapped_delete([uid])
# Interface from BaseFolder
def deletemessages(self, uidlist):
self._mb.deletemessages(self._uidlist(self.r2l, 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):
def getselectedfolder(self):
def __getselectedfolder(self):
if self.state == 'SELECTED':
return self.mailbox
return None
@ -41,7 +41,7 @@ class UsefulIMAPMixIn(object):
:returns: 'OK' on success, nothing if the folder was already
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:
# No change; return.
return
@ -66,6 +66,7 @@ class UsefulIMAPMixIn(object):
raise OfflineImapError(errstr, severity)
return result
# Overrides private function from IMAP4 (@imaplib2)
def _mesg(self, s, tn=None, secs=None):
new_mesg(self, s, tn, secs)

View File

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

View File

@ -21,7 +21,7 @@ import string
from offlineimap.ui import getglobalui
def debug(*args):
def __debug(*args):
msg = []
for arg in args:
msg.append(str(arg))
@ -50,7 +50,7 @@ def flagsplit(string):
raise ValueError("Passed string '%s' is not a flag list" % string)
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}"""
# effectively this does dict(zip(l[::2],l[1::2])), however
# measurements seemed to have indicated that the manual variant is
@ -60,7 +60,7 @@ def options2hash(list):
while (counter < len(list)):
retval[list[counter]] = list[counter + 1]
counter += 2
debug("options2hash returning:", retval)
__debug("__options2hash returning:", retval)
return retval
def flags2hash(flags):
@ -68,7 +68,7 @@ def flags2hash(flags):
E.g. '(FLAGS (\\Seen Old) UID 4807)' leads to
{'FLAGS': '(\\Seen Old)', 'UID': '4807'}"""
return options2hash(flagsplit(flags))
return __options2hash(flagsplit(flags))
def imapsplit(imapstring):
"""Takes a string from an IMAP conversation and returns a list containing
@ -81,7 +81,7 @@ def imapsplit(imapstring):
['(\\HasNoChildren)', '"."', '"INBOX.Sent"']"""
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
# contains a literal. See Python bug
# #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 = '"%s"' % arg
debug("imapsplit() non-string [%d]: Appending %s" %\
__debug("imapsplit() non-string [%d]: Appending %s" %\
(i, arg))
retval.append(arg)
else:
@ -113,10 +113,10 @@ def imapsplit(imapstring):
# Recursion to the rescue.
arg = imapstring[i]
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))
retval.extend(imapsplit(arg))
debug("imapsplit() non-string: returning %s" % str(retval))
__debug("imapsplit() non-string: returning %s" % str(retval))
return retval
workstr = imapstring.strip()
@ -137,7 +137,7 @@ def imapsplit(imapstring):
retval.append(parenlist)
elif workstr[0] == '"':
# quoted fragments '"...\"..."'
(quoted, rest) = _split_quoted(workstr)
(quoted, rest) = __split_quoted(workstr)
retval.append(quoted)
workstr = rest
else:
@ -213,7 +213,7 @@ def uid_sequence(uidlist):
return ",".join(retval)
def _split_quoted(string):
def __split_quoted(string):
"""
Looks for the ending quote character in the string that starts
with quote character, splitting out quoted component and the

View File

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

View File

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

View File

@ -70,11 +70,6 @@ class TestInternalFunctions(unittest.TestCase):
res = imaputil.flagsplit(b'(FLAGS (\\Seen Old) UID 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):
"""Test imaputil.flags2hash()"""
res = imaputil.flags2hash(b'(FLAGS (\\Seen Old) UID 4807)')