diff --git a/head/ChangeLog b/head/ChangeLog index 4586f7c..479fec2 100644 --- a/head/ChangeLog +++ b/head/ChangeLog @@ -1,3 +1,22 @@ +2002-07-09 20:17 jgoerzen + + * offlineimap.conf, offlineimap.py, debian/changelog, + offlineimap/imapserver.py, offlineimap/version.py, + offlineimap/folder/IMAP.py: Commited changes for 2.0.6 + +2002-07-09 13:13 jgoerzen + + * offlineimap/folder/IMAP.py: Another attempt at the read-only bug + +2002-07-08 12:05 jgoerzen + + * offlineimap/folder/IMAP.py: Another attempt at fixing read-only + folders + +2002-07-08 11:38 jgoerzen + + * ChangeLog: Updated for 2.0.5 + 2002-07-08 11:32 jgoerzen * debian/changelog, offlineimap/folder/IMAP.py, diff --git a/head/debian/changelog b/head/debian/changelog index cb7aa5e..3ffad6f 100644 --- a/head/debian/changelog +++ b/head/debian/changelog @@ -1,3 +1,16 @@ +offlineimap (2.0.6) unstable; urgency=low + + * Added support for holdconnectionopen and keepalive. This feature + allows for an IMAP server connection(s) to be held open until + the next sync process, permitting faster restart times. + * Another try at read-only folder support. This is nasty because I + have no way to test it and imaplib's read-only support is weird. + * Closing out old bug; fixed in 1.0.2. Closes: #150803. + * Optimized algorithm so that a SELECT is never issued for folders + that contain no messages. + + -- John Goerzen Tue, 9 Jul 2002 20:05:24 -0500 + offlineimap (2.0.5) unstable; urgency=low * Fixed a folderfilter example. Partially fixes #152079. diff --git a/head/offlineimap.conf b/head/offlineimap.conf index 50fca86..7cd3219 100644 --- a/head/offlineimap.conf +++ b/head/offlineimap.conf @@ -215,3 +215,19 @@ remoteuser = username maxconnections = 1 +# OfflineIMAP normally closes IMAP server connections between refreshes if +# the global option autorefresh is specified. If you wish it to keep the +# connection open, set this to true. If not specified, the default is +# false. Keeping the connection open means a faster sync start the +# next time and may use fewer server resources on connection, but uses +# more server memory. This setting has no effect if autorefresh is not set. + +holdconnectionopen = no + +# If you want to have "keepalives" sent while waiting between syncs, +# specify the amount of time IN SECONDS between keepalives here. Note that +# sometimes more than this amount of time might pass, so don't make it +# tight. This setting has no effect if autorefresh and holdconnectionopen +# are not both set. + +# keepalive = 60 diff --git a/head/offlineimap.py b/head/offlineimap.py index 1cb95c9..d6666fa 100644 --- a/head/offlineimap.py +++ b/head/offlineimap.py @@ -74,6 +74,8 @@ for account in accounts: mailboxes = [] mailboxlock = Lock() +servers = {} + def addmailbox(accountname, remotefolder): mailboxlock.acquire() mailboxes.append({'accountname' : accountname, @@ -88,28 +90,14 @@ def syncaccount(accountname, *args): accountmetadata = os.path.join(metadatadir, accountname) if not os.path.exists(accountmetadata): os.mkdir(accountmetadata, 0700) - host = config.get(accountname, "remotehost") - user = config.get(accountname, "remoteuser") - port = None - if config.has_option(accountname, "remoteport"): - port = config.getint(accountname, "remoteport") - ssl = config.getboolean(accountname, "ssl") - usetunnel = config.has_option(accountname, "preauthtunnel") - reference = '""' - if config.has_option(accountname, "reference"): - reference = config.get(accountname, "reference") server = None - # Connect to the remote server. - if usetunnel: - server = imapserver.IMAPServer(tunnel = tunnels[accountname], - reference = reference, - maxconnections = config.getint(accountname, "maxconnections")) + if accountname in servers: + server = servers[accountname] else: - server = imapserver.IMAPServer(user, passwords[accountname], - host, port, ssl, - config.getint(accountname, "maxconnections"), - reference = reference) + server = imapserver.ConfigedIMAPServer(config, accountname, passwords) + servers[accountname] = server + remoterepos = repository.IMAP.IMAPRepository(config, accountname, server) # Connect to the Maildirs. @@ -134,7 +122,9 @@ def syncaccount(accountname, *args): thread.start() folderthreads.append(thread) threadutil.threadsreset(folderthreads) - server.close() + if not (config.has_option(accountname, 'holdconnectionopen') and \ + config.getboolean(accountname, 'holdconnectionopen')): + server.close() finally: pass @@ -213,9 +203,31 @@ def sync_with_timer(): if config.has_option('general', 'autorefresh'): refreshperiod = config.getint('general', 'autorefresh') * 60 while 1: + # Set up keep-alives. + kaevents = {} + kathreads = {} + for accountname in accounts: + if config.has_option(accountname, 'holdconnectionopen') and \ + config.getboolean(accountname, 'holdconnectionopen') and \ + config.has_option(accountname, 'keepalive'): + event = Event() + kaevents[accountname] = event + thread = ExitNotifyThread(target = servers[accountname].keepalive, + args = (config.getint(accountname, 'keepalive'), event)) + thread.setDaemon(1) + thread.start() + kathreads[accountname] = thread if ui.sleep(refreshperiod) == 2: + # Cancel keep-alives, but don't bother terminating threads + for event in kaevents.values(): + event.set() break else: + # Cancel keep-alives and wait for threads to terminate. + for event in kaevents.values(): + event.set() + for thread in kathreads.values(): + thread.join() syncitall() def threadexited(thread): diff --git a/head/offlineimap/folder/IMAP.py b/head/offlineimap/folder/IMAP.py index 08a9924..44f309f 100644 --- a/head/offlineimap/folder/IMAP.py +++ b/head/offlineimap/folder/IMAP.py @@ -59,8 +59,6 @@ class IMAPFolder(BaseFolder): def cachemessagelist(self): imapobj = self.imapserver.acquireconnection() try: - # Needed for fetch below - imapobj.select(self.getfullname(), readonly = 1) self.messagelist = {} response = imapobj.status(self.getfullname(), '(MESSAGES)')[1][0] result = imaputil.imapsplit(response)[1] @@ -69,6 +67,8 @@ class IMAPFolder(BaseFolder): # No messages? return. return + # Needed for fetch below + imapobj.select(self.getfullname(), readonly = 1) # Now, get the flags and UIDs for these. response = imapobj.fetch('1:%d' % maxmsgid, '(FLAGS UID)')[1] finally: diff --git a/head/offlineimap/imapserver.py b/head/offlineimap/imapserver.py index 0931f04..ae311f6 100644 --- a/head/offlineimap/imapserver.py +++ b/head/offlineimap/imapserver.py @@ -28,13 +28,14 @@ class UsefulIMAPMixIn: return None def select(self, mailbox='INBOX', readonly=None): - if self.getselectedfolder() == mailbox and not readonly: + if self.getselectedfolder() == mailbox: + self.is_readonly = readonly # No change; return. return result = self.__class__.__bases__[1].select(self, mailbox, readonly) if result[0] != 'OK': raise ValueError, "Error from select: %s" % str(result) - if self.getstate() == 'SELECTED' and not readonly: + if self.getstate() == 'SELECTED': self.selectedfolder = mailbox else: self.selectedfolder = None @@ -146,5 +147,71 @@ class IMAPServer: self.assignedconnections = [] self.availableconnections = [] self.connectionlock.release() - + def keepalive(self, timeout, event): + """Sends a NOOP to each connection recorded. It will wait a maximum + of timeout seconds between doing this, and will continue to do so + until the Event object as passed is true. This method is expected + to be invoked in a separate thread, which should be join()'d after + the event is set.""" + while 1: + event.wait(timeout) + if event.isSet(): + return + self.connectionlock.acquire() + numconnections = len(self.assignedconnections) + \ + len(self.availableconnections) + self.connectionlock.release() + threads = [] + imapobjs = [] + + for i in range(numconnections): + imapobj = self.acquireconnection() + imapobjs.append(imapobj) + thread = threadutil.ExitNotifyThread(target = imapobj.noop) + thread.setDaemon(1) + thread.start() + threads.append(thread) + + for thread in threads: + # Make sure all the commands have completed. + thread.join() + + for imapobj in imapobjs: + self.releaseconnection(imapobj) + +class ConfigedIMAPServer(IMAPServer): + """This class is designed for easier initialization given a ConfigParser + object and an account name. The passwordhash is used if + passwords for certain accounts are known. If the password for this + account is listed, it will be obtained from there.""" + def __init__(self, config, accountname, passwordhash = {}): + """Initialize the object. If the account is not a tunnel, + the password is required.""" + host = config.get(accountname, "remotehost") + user = config.get(accountname, "remoteuser") + port = None + if config.has_option(accountname, "remoteport"): + port = config.getint(accountname, "remoteport") + ssl = config.getboolean(accountname, "ssl") + usetunnel = config.has_option(accountname, "preauthtunnel") + reference = '""' + if config.has_option(accountname, "reference"): + reference = config.get(accountname, "reference") + server = None + password = None + if accountname in passwordhash: + password = passwordhash[accountname] + + # Connect to the remote server. + if usetunnel: + IMAPServer.__init__(self, + tunnel = config.get(accountname, "preauthtunnel"), + reference = reference, + maxconnections = config.getint(accountname, "maxconnections")) + else: + if not password: + password = config.get(accountname, 'remotepass') + IMAPServer.__init__(self, user, password, host, port, ssl, + config.getint(accountname, "maxconnections"), + reference = reference) diff --git a/head/offlineimap/version.py b/head/offlineimap/version.py index 04c6f9c..16a13f0 100644 --- a/head/offlineimap/version.py +++ b/head/offlineimap/version.py @@ -1,5 +1,5 @@ productname = 'OfflineIMAP' -versionstr = "2.0.5" +versionstr = "2.0.6" versionlist = versionstr.split(".") major = versionlist[0]