diff --git a/docs/doc-src/API.rst b/docs/doc-src/API.rst index c456435..1eb9adb 100644 --- a/docs/doc-src/API.rst +++ b/docs/doc-src/API.rst @@ -30,7 +30,7 @@ be merged into the main documentation. .. automethod:: run - .. automethod:: parse_cmd_options + .. automethod:: __parse_cmd_options .. .. autoattribute:: ui diff --git a/offlineimap/accounts.py b/offlineimap/accounts.py index 113d716..2c9551d 100644 --- a/offlineimap/accounts.py +++ b/offlineimap/accounts.py @@ -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 diff --git a/offlineimap/folder/Base.py b/offlineimap/folder/Base.py index e9d4a9c..0062dc2 100644 --- a/offlineimap/folder/Base.py +++ b/offlineimap/folder/Base.py @@ -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 diff --git a/offlineimap/folder/IMAP.py b/offlineimap/folder/IMAP.py index 1f3803c..d727b31 100644 --- a/offlineimap/folder/IMAP.py +++ b/offlineimap/folder/IMAP.py @@ -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("(?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] - - diff --git a/offlineimap/folder/LocalStatus.py b/offlineimap/folder/LocalStatus.py index 6f65f83..c3f24f6 100644 --- a/offlineimap/folder/LocalStatus.py +++ b/offlineimap/folder/LocalStatus.py @@ -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] diff --git a/offlineimap/folder/LocalStatusSQLite.py b/offlineimap/folder/LocalStatusSQLite.py index b624134..a22c68b 100644 --- a/offlineimap/folder/LocalStatusSQLite.py +++ b/offlineimap/folder/LocalStatusSQLite.py @@ -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]) diff --git a/offlineimap/folder/Maildir.py b/offlineimap/folder/Maildir.py index 3d6fd62..24d9ced 100644 --- a/offlineimap/folder/Maildir.py +++ b/offlineimap/folder/Maildir.py @@ -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]) - diff --git a/offlineimap/folder/UIDMaps.py b/offlineimap/folder/UIDMaps.py index 7dc43f3..10173f7 100644 --- a/offlineimap/folder/UIDMaps.py +++ b/offlineimap/folder/UIDMaps.py @@ -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) diff --git a/offlineimap/imaplibutil.py b/offlineimap/imaplibutil.py index 4290b2b..f8806dd 100644 --- a/offlineimap/imaplibutil.py +++ b/offlineimap/imaplibutil.py @@ -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) diff --git a/offlineimap/imapserver.py b/offlineimap/imapserver.py index d384bd2..ecd7602 100644 --- a/offlineimap/imapserver.py +++ b/offlineimap/imapserver.py @@ -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() diff --git a/offlineimap/imaputil.py b/offlineimap/imaputil.py index c6e660c..5d69f59 100644 --- a/offlineimap/imaputil.py +++ b/offlineimap/imaputil.py @@ -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 diff --git a/offlineimap/init.py b/offlineimap/init.py index ce24e48..d4c9a6d 100644 --- a/offlineimap/init.py +++ b/offlineimap/init.py @@ -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 diff --git a/offlineimap/mbnames.py b/offlineimap/mbnames.py index 39dcf03..2d9ab5e 100644 --- a/offlineimap/mbnames.py +++ b/offlineimap/mbnames.py @@ -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() diff --git a/test/tests/test_00_imaputil.py b/test/tests/test_00_imaputil.py index d1972da..4e9d142 100644 --- a/test/tests/test_00_imaputil.py +++ b/test/tests/test_00_imaputil.py @@ -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)')