From 73199ad7354bace212e025f800666d54d36af633 Mon Sep 17 00:00:00 2001 From: jgoerzen Date: Mon, 7 Oct 2002 21:59:02 +0100 Subject: [PATCH] /offlineimap/head: changeset 259 Initial commit on initialization reorganization --- offlineimap/head/bin/offlineimap | 311 +--------------- offlineimap/head/debian/changelog | 12 +- offlineimap/head/offlineimap/folder/Base.py | 12 +- offlineimap/head/offlineimap/folder/IMAP.py | 10 +- offlineimap/head/offlineimap/imaplib.py | 4 +- offlineimap/head/offlineimap/imapserver.py | 5 +- offlineimap/head/offlineimap/imaputil.py | 4 +- offlineimap/head/offlineimap/init.py | 335 ++++++++++++++++++ .../head/offlineimap/repository/Maildir.py | 4 +- offlineimap/head/offlineimap/ui/UIBase.py | 9 +- 10 files changed, 377 insertions(+), 329 deletions(-) create mode 100644 offlineimap/head/offlineimap/init.py diff --git a/offlineimap/head/bin/offlineimap b/offlineimap/head/bin/offlineimap index 3477d5a..a9d534e 100644 --- a/offlineimap/head/bin/offlineimap +++ b/offlineimap/head/bin/offlineimap @@ -17,311 +17,6 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -from offlineimap import imaplib, imapserver, repository, folder, mbnames, threadutil, version, localeval -from offlineimap.threadutil import InstanceLimitedThread, ExitNotifyThread -from offlineimap.ui import UIBase -import re, os, os.path, offlineimap, sys -from ConfigParser import ConfigParser -from threading import * -from getopt import getopt - -options = {} -if '--help' in sys.argv[1:]: - sys.stdout.write(version.cmdhelp + "\n") - sys.exit(0) - -for optlist in getopt(sys.argv[1:], 'P:1oa:c:d:u:h')[0]: - options[optlist[0]] = optlist[1] - -if '-h' in options: - sys.stdout.write(version.cmdhelp) - sys.stdout.write("\n") - sys.exit(0) -configfilename = os.path.expanduser("~/.offlineimaprc") -if '-c' in options: - configfilename = options['-c'] -if '-P' in options: - if not '-1' in options: - sys.stderr.write("FATAL: profile mode REQUIRES -1\n") - sys.exit(100) - profiledir = options['-P'] - os.mkdir(profiledir) - threadutil.setprofiledir(profiledir) - sys.stderr.write("WARNING: profile mode engaged;\nPotentially large data will be created in " + profiledir + "\n") - - - -config = ConfigParser() -if not os.path.exists(configfilename): - sys.stderr.write(" *** Config file %s does not exist; aborting!\n" % configfilename) - sys.exit(1) - -config.read(configfilename) - -if config.has_option("general", "pythonfile"): - path=os.path.expanduser(config.get("general", "pythonfile")) -else: - path=None -localeval = localeval.LocalEval(path) - -ui = offlineimap.ui.detector.findUI(config, localeval, options.get('-u')) -ui.init_banner() - - -if '-d' in options: - for debugtype in options['-d'].split(','): - ui.add_debug(debugtype.strip()) - if debugtype == 'imap': - imaplib.Debug = 5 - -if '-o' in options and config.has_option("general", "autorefresh"): - config.remove_option("general", "autorefresh") - -metadatadir = os.path.expanduser(config.get("general", "metadata")) -if not os.path.exists(metadatadir): - os.mkdir(metadatadir, 0700) - -accounts = config.get("general", "accounts") -if '-a' in options: - accounts = options['-a'] -accounts = accounts.replace(" ", "") -accounts = accounts.split(",") - -server = None -remoterepos = None -localrepos = None -passwords = {} -tunnels = {} - -if '-1' in options: - threadutil.initInstanceLimit("ACCOUNTLIMIT", 1) -else: - threadutil.initInstanceLimit("ACCOUNTLIMIT", - config.getint("general", "maxsyncaccounts")) - -# We have to gather passwords here -- don't want to have two threads -# asking for passwords simultaneously. - -for account in accounts: - #if '.' in account: - # raise ValueError, "Account '%s' contains a dot; dots are not " \ - # "allowed in account names." % account - if config.has_option(account, "preauthtunnel"): - tunnels[account] = config.get(account, "preauthtunnel") - elif config.has_option(account, "remotepass"): - passwords[account] = config.get(account, "remotepass") - elif config.has_option(account, "remotepassfile"): - passfile = open(os.path.expanduser(config.get(account, "remotepassfile"))) - passwords[account] = passfile.readline().strip() - passfile.close() - else: - passwords[account] = ui.getpass(account, config) - for instancename in ["FOLDER_" + account, "MSGCOPY_" + account]: - if '-1' in options: - threadutil.initInstanceLimit(instancename, 1) - else: - threadutil.initInstanceLimit(instancename, - config.getint(account, "maxconnections")) - -mailboxes = [] -servers = {} - -def syncaccount(accountname, *args): - # We don't need an account lock because syncitall() goes through - # each account once, then waits for all to finish. - try: - ui.acct(accountname) - accountmetadata = os.path.join(metadatadir, accountname) - if not os.path.exists(accountmetadata): - os.mkdir(accountmetadata, 0700) - - server = None - if accountname in servers: - server = servers[accountname] - else: - server = imapserver.ConfigedIMAPServer(config, accountname, passwords) - servers[accountname] = server - - remoterepos = repository.IMAP.IMAPRepository(config, localeval, accountname, server) - - # Connect to the Maildirs. - localrepos = repository.Maildir.MaildirRepository(os.path.expanduser(config.get(accountname, "localfolders")), accountname, config) - - # Connect to the local cache. - statusrepos = repository.LocalStatus.LocalStatusRepository(accountmetadata) - - ui.syncfolders(remoterepos, localrepos) - remoterepos.syncfoldersto(localrepos) - ui.acct(accountname) - - folderthreads = [] - for remotefolder in remoterepos.getfolders(): - thread = InstanceLimitedThread(\ - instancename = 'FOLDER_' + accountname, - target = syncfolder, - name = "Folder sync %s[%s]" % \ - (accountname, remotefolder.getvisiblename()), - args = (accountname, remoterepos, remotefolder, localrepos, - statusrepos)) - thread.setDaemon(1) - thread.start() - folderthreads.append(thread) - threadutil.threadsreset(folderthreads) - if not (config.has_option(accountname, 'holdconnectionopen') and \ - config.getboolean(accountname, 'holdconnectionopen')): - server.close() - finally: - pass - -def syncfolder(accountname, remoterepos, remotefolder, localrepos, - statusrepos): - # Load local folder. - localfolder = localrepos.\ - getfolder(remotefolder.getvisiblename().\ - replace(remoterepos.getsep(), localrepos.getsep())) - # Write the mailboxes - mailboxes.append({'accountname': accountname, - 'foldername': localfolder.getvisiblename()}) - # Load local folder - ui.syncingfolder(remoterepos, remotefolder, localrepos, localfolder) - ui.loadmessagelist(localrepos, localfolder) - localfolder.cachemessagelist() - ui.messagelistloaded(localrepos, localfolder, len(localfolder.getmessagelist().keys())) - - - # Load status folder. - statusfolder = statusrepos.getfolder(remotefolder.getvisiblename().\ - replace(remoterepos.getsep(), - statusrepos.getsep())) - if localfolder.getuidvalidity() == None: - # This is a new folder, so delete the status cache to be sure - # we don't have a conflict. - statusfolder.deletemessagelist() - - statusfolder.cachemessagelist() - - - # If either the local or the status folder has messages and - # there is a UID validity problem, warn and abort. - # If there are no messages, UW IMAPd loses UIDVALIDITY. - # But we don't really need it if both local folders are empty. - # So, in that case, save it off. - if (len(localfolder.getmessagelist()) or \ - len(statusfolder.getmessagelist())) and \ - not localfolder.isuidvalidityok(remotefolder): - ui.validityproblem(remotefolder) - return - else: - localfolder.saveuidvalidity(remotefolder.getuidvalidity()) - - # Load remote folder. - ui.loadmessagelist(remoterepos, remotefolder) - remotefolder.cachemessagelist() - ui.messagelistloaded(remoterepos, remotefolder, - len(remotefolder.getmessagelist().keys())) - - - # - - if not statusfolder.isnewfolder(): - # Delete local copies of remote messages. This way, - # if a message's flag is modified locally but it has been - # deleted remotely, we'll delete it locally. Otherwise, we - # try to modify a deleted message's flags! This step - # need only be taken if a statusfolder is present; otherwise, - # there is no action taken *to* the remote repository. - - remotefolder.syncmessagesto_delete(localfolder, [localfolder, - statusfolder]) - ui.syncingmessages(localrepos, localfolder, remoterepos, remotefolder) - localfolder.syncmessagesto(statusfolder, [remotefolder, statusfolder]) - - # Synchronize remote changes. - ui.syncingmessages(remoterepos, remotefolder, localrepos, localfolder) - remotefolder.syncmessagesto(localfolder) - - # Make sure the status folder is up-to-date. - ui.syncingmessages(localrepos, localfolder, statusrepos, statusfolder) - localfolder.syncmessagesto(statusfolder) - statusfolder.save() - - -def syncitall(): - global mailboxes - mailboxes = [] # Reset. - threads = [] - for accountname in accounts: - thread = InstanceLimitedThread(instancename = 'ACCOUNTLIMIT', - target = syncaccount, - name = "Account sync %s" % accountname, - args = (accountname,)) - thread.setDaemon(1) - thread.start() - threads.append(thread) - # Wait for the threads to finish. - threadutil.threadsreset(threads) - mbnames.genmbnames(config, localeval, mailboxes) - -def sync_with_timer(): - currentThread().setExitMessage('SYNC_WITH_TIMER_TERMINATE') - syncitall() - 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, - name = "Keep alive " + accountname, - 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): - if thread.getExitCause() == 'EXCEPTION': - if isinstance(thread.getExitException(), SystemExit): - # Bring a SystemExit into the main thread. - # Do not send it back to UI layer right now. - # Maybe later send it to ui.terminate? - raise SystemExit - ui.threadException(thread) # Expected to terminate - sys.exit(100) # Just in case... - os._exit(100) - elif thread.getExitMessage() == 'SYNC_WITH_TIMER_TERMINATE': - ui.terminate() - # Just in case... - sys.exit(100) - os._exit(100) - else: - ui.threadExited(thread) - -threadutil.initexitnotify() -t = ExitNotifyThread(target=sync_with_timer, - name='Sync Runner') -t.setDaemon(1) -t.start() -try: - threadutil.exitnotifymonitorloop(threadexited) -except SystemExit: - raise -except: - ui.mainException() # Also expected to terminate. +revno = long('$Rev: 252 $'[6:-2]) +from offlineimap import init +init.startup(revno) diff --git a/offlineimap/head/debian/changelog b/offlineimap/head/debian/changelog index f21a93d..0d1b3dd 100644 --- a/offlineimap/head/debian/changelog +++ b/offlineimap/head/debian/changelog @@ -1,10 +1,20 @@ -offlineimap (3.2.9) unstable; urgency=low +offlineimap (3.99.0) unstable; urgency=low + * The next few releases are adding features and reorganizing + code in preparation for 4.0.0. * imaputil.py now logs information with IMAP debugging is enabled. * Added folderfilter capability to mbnames recorder. You can now omit specified folders from the mbnames output. * Added a workaround to imaputil.py to deal with a bug in imaplib.py's tuple when a response contains a literal in certain cases. + * Split out the code in bin/offlineimap into offlineimap/init.py. + Retaining bin/offlineimap as a skeletal piece only. Contains + about three lines of code now. This will make many things + easier, including debugging. + * Added library version check to bin/offlineimap and + offlineimap/init.py. + * Moved __main__.ui to functions in UIBase: getglobalui() and + setglobalui(). -- John Goerzen Mon, 30 Sep 2002 12:08:08 -0500 diff --git a/offlineimap/head/offlineimap/folder/Base.py b/offlineimap/head/offlineimap/folder/Base.py index 6c1e577..8643eeb 100644 --- a/offlineimap/head/offlineimap/folder/Base.py +++ b/offlineimap/head/offlineimap/folder/Base.py @@ -16,10 +16,10 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import __main__ from threading import * from offlineimap import threadutil from offlineimap.threadutil import InstanceLimitedThread +from offlineimap.ui import UIBase class BaseFolder: def getname(self): @@ -161,7 +161,7 @@ class BaseFolder: for uid in self.getmessagelist().keys(): if uid >= 0: continue - __main__.ui.copyingmessage(uid, self, applyto) + UIBase.getglobalui().copyingmessage(uid, self, applyto) successobject = None successuid = None message = self.getmessage(uid) @@ -192,7 +192,7 @@ class BaseFolder: # synced to the status cache. This is only a problem with # self.getmessage(). So, don't call self.getmessage unless # really needed. - __main__.ui.copyingmessage(uid, self, applyto) + UIBase.getglobalui().copyingmessage(uid, self, applyto) message = '' # If any of the destinations actually stores the message body, # load it up. @@ -249,7 +249,7 @@ class BaseFolder: if not uid in self.getmessagelist(): deletelist.append(uid) if len(deletelist): - __main__.ui.deletingmessages(deletelist, applyto) + UIBase.getglobalui().deletingmessages(deletelist, applyto) for object in applyto: object.deletemessages(deletelist) @@ -266,13 +266,13 @@ class BaseFolder: addflags = [x for x in selfflags if x not in destflags] if len(addflags): - __main__.ui.addingflags(uid, addflags, applyto) + UIBase.getglobalui().addingflags(uid, addflags, applyto) for object in applyto: object.addmessageflags(uid, addflags) delflags = [x for x in destflags if x not in selfflags] if len(delflags): - __main__.ui.deletingflags(uid, delflags, applyto) + UIBase.getglobalui().deletingflags(uid, delflags, applyto) for object in applyto: object.deletemessageflags(uid, delflags) diff --git a/offlineimap/head/offlineimap/folder/IMAP.py b/offlineimap/head/offlineimap/folder/IMAP.py index c660436..943a2a0 100644 --- a/offlineimap/head/offlineimap/folder/IMAP.py +++ b/offlineimap/head/offlineimap/folder/IMAP.py @@ -18,11 +18,11 @@ from Base import BaseFolder from offlineimap import imaputil, imaplib +from offlineimap.ui import UIBase import rfc822, time, string from StringIO import StringIO from copy import copy -import __main__ class IMAPFolder(BaseFolder): def __init__(self, imapserver, name, visiblename, accountname, repository): @@ -102,7 +102,7 @@ class IMAPFolder(BaseFolder): try: imapobj.select(self.getfullname()) # Needed for search except imapobj.readonly: - __main__.ui.msgtoreadonly(self, uid, content, flags) + UIBase.getglobalui().msgtoreadonly(self, uid, content, flags) # Return indicating message taken, but no UID assigned. # Fudge it. return 0 @@ -168,7 +168,7 @@ class IMAPFolder(BaseFolder): try: imapobj.select(self.getfullname()) except imapobj.readonly: - __main__.ui.flagstoreadonly(self, [uid], flags) + UIBase.getglobalui().flagstoreadonly(self, [uid], flags) return result = imapobj.uid('store', '%d' % uid, 'FLAGS', imaputil.flagsmaildir2imap(flags)) @@ -191,7 +191,7 @@ class IMAPFolder(BaseFolder): try: imapobj.select(self.getfullname()) except imapobj.readonly: - __main__.ui.flagstoreadonly(self, uidlist, flags) + UIBase.getglobalui().flagstoreadonly(self, uidlist, flags) return r = imapobj.uid('store', imaputil.listjoin(uidlist), @@ -242,7 +242,7 @@ class IMAPFolder(BaseFolder): try: imapobj.select(self.getfullname()) except imapobj.readonly: - __main__.ui.deletereadonly(self, uidlist) + UIBase.getglobalui().deletereadonly(self, uidlist) return assert(imapobj.expunge()[0] == 'OK') finally: diff --git a/offlineimap/head/offlineimap/imaplib.py b/offlineimap/head/offlineimap/imaplib.py index 7768f6a..82e1894 100644 --- a/offlineimap/head/offlineimap/imaplib.py +++ b/offlineimap/head/offlineimap/imaplib.py @@ -22,7 +22,7 @@ Public functions: Internaldate2tuple __version__ = "2.52" import binascii, re, socket, time, random, sys, os -import __main__ +from offlineimap.ui import UIBase __all__ = ["IMAP4", "Internaldate2tuple", "Int2AP", "ParseFlags", "Time2Internaldate"] @@ -988,7 +988,7 @@ class IMAP4: if secs is None: secs = time.time() tm = time.strftime('%M:%S', time.localtime(secs)) - __main__.ui.debug('imap', ' %s.%02d %s' % (tm, (secs*100)%100, s)) + UIBase.getglobalui().debug('imap', ' %s.%02d %s' % (tm, (secs*100)%100, s)) def _dump_ur(self, dict): # Dump untagged responses (in `dict'). diff --git a/offlineimap/head/offlineimap/imapserver.py b/offlineimap/head/offlineimap/imapserver.py index 8c546c5..d6a03ce 100644 --- a/offlineimap/head/offlineimap/imapserver.py +++ b/offlineimap/head/offlineimap/imapserver.py @@ -17,9 +17,10 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from offlineimap import imaplib, imaputil, threadutil +from offlineimap.ui import UIBase from threading import * import thread -import __main__ + class UsefulIMAPMixIn: def getstate(self): @@ -121,7 +122,7 @@ class IMAPServer: self.connectionlock.release() # Release until need to modify data - __main__.ui.connecting(self.hostname, self.port) + UIBase.getglobalui().connecting(self.hostname, self.port) # Generate a new connection. if self.tunnel: diff --git a/offlineimap/head/offlineimap/imaputil.py b/offlineimap/head/offlineimap/imaputil.py index 0b8eb9c..062ea05 100644 --- a/offlineimap/head/offlineimap/imaputil.py +++ b/offlineimap/head/offlineimap/imaputil.py @@ -17,14 +17,14 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import re, string, types +from offlineimap.ui import UIBase quotere = re.compile('^("(?:[^"]|\\\\")*")') -import __main__ def debug(*args): msg = [] for arg in args: msg.append(str(arg)) - __main__.ui.debug('imap', " ".join(msg)) + UIBase.getglobalui().debug('imap', " ".join(msg)) def dequote(string): """Takes a string which may or may not be quoted and returns it, unquoted. diff --git a/offlineimap/head/offlineimap/init.py b/offlineimap/head/offlineimap/init.py new file mode 100644 index 0000000..8fa505a --- /dev/null +++ b/offlineimap/head/offlineimap/init.py @@ -0,0 +1,335 @@ +# OfflineIMAP initialization code +# Copyright (C) 2002 John Goerzen +# +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +from offlineimap import imaplib, imapserver, repository, folder, mbnames, threadutil, version +from offlineimap.localeval import LocalEval +from offlineimap.threadutil import InstanceLimitedThread, ExitNotifyThread +from offlineimap.ui import UIBase +import re, os, os.path, offlineimap, sys +from ConfigParser import ConfigParser +from threading import * +from getopt import getopt + +def startup(revno): + assert revno == version.revno, "Revision of main program (%d) does not match that of library (%d). Please double-check your PYTHONPATH and installation locations." % (revno, version.revno) + options = {} + if '--help' in sys.argv[1:]: + sys.stdout.write(version.cmdhelp + "\n") + sys.exit(0) + + for optlist in getopt(sys.argv[1:], 'P:1oa:c:d:u:h')[0]: + options[optlist[0]] = optlist[1] + + if '-h' in options: + sys.stdout.write(version.cmdhelp) + sys.stdout.write("\n") + sys.exit(0) + configfilename = os.path.expanduser("~/.offlineimaprc") + if '-c' in options: + configfilename = options['-c'] + if '-P' in options: + if not '-1' in options: + sys.stderr.write("FATAL: profile mode REQUIRES -1\n") + sys.exit(100) + profiledir = options['-P'] + os.mkdir(profiledir) + threadutil.setprofiledir(profiledir) + sys.stderr.write("WARNING: profile mode engaged;\nPotentially large data will be created in " + profiledir + "\n") + + config = ConfigParser() + if not os.path.exists(configfilename): + sys.stderr.write(" *** Config file %s does not exist; aborting!\n" % configfilename) + sys.exit(1) + + config.read(configfilename) + + if config.has_option("general", "pythonfile"): + path=os.path.expanduser(config.get("general", "pythonfile")) + else: + path=None + localeval = LocalEval(path) + + ui = offlineimap.ui.detector.findUI(config, localeval, options.get('-u')) + ui.init_banner() + UIBase.setglobalui(ui) + print "UI is", UIBase.getglobalui() + + if '-d' in options: + for debugtype in options['-d'].split(','): + ui.add_debug(debugtype.strip()) + if debugtype == 'imap': + imaplib.Debug = 5 + + if '-o' in options and config.has_option("general", "autorefresh"): + config.remove_option("general", "autorefresh") + + metadatadir = os.path.expanduser(config.get("general", "metadata")) + if not os.path.exists(metadatadir): + os.mkdir(metadatadir, 0700) + + accounts = config.get("general", "accounts") + if '-a' in options: + accounts = options['-a'] + accounts = accounts.replace(" ", "") + accounts = accounts.split(",") + + server = None + remoterepos = None + localrepos = None + passwords = {} + tunnels = {} + + if '-1' in options: + threadutil.initInstanceLimit("ACCOUNTLIMIT", 1) + else: + threadutil.initInstanceLimit("ACCOUNTLIMIT", + config.getint("general", "maxsyncaccounts")) + + # We have to gather passwords here -- don't want to have two threads + # asking for passwords simultaneously. + + for account in accounts: + print "Processing account", account + #if '.' in account: + # raise ValueError, "Account '%s' contains a dot; dots are not " \ + # "allowed in account names." % account + if config.has_option(account, "preauthtunnel"): + tunnels[account] = config.get(account, "preauthtunnel") + elif config.has_option(account, "remotepass"): + passwords[account] = config.get(account, "remotepass") + elif config.has_option(account, "remotepassfile"): + passfile = open(os.path.expanduser(config.get(account, "remotepassfile"))) + passwords[account] = passfile.readline().strip() + passfile.close() + else: + passwords[account] = ui.getpass(account, config) + for instancename in ["FOLDER_" + account, "MSGCOPY_" + account]: + if '-1' in options: + threadutil.initInstanceLimit(instancename, 1) + else: + threadutil.initInstanceLimit(instancename, + config.getint(account, "maxconnections")) + + mailboxes = [] + servers = {} + + threadutil.initexitnotify() + t = ExitNotifyThread(target=sync_with_timer, + name='Sync Runner') + t.setDaemon(1) + t.start() + try: + threadutil.exitnotifymonitorloop(threadexited) + except SystemExit: + raise + except: + ui.mainException() # Also expected to terminate. + +def syncaccount(accountname, *args): + ui = UIBase.getglobalui() + # We don't need an account lock because syncitall() goes through + # each account once, then waits for all to finish. + try: + ui.acct(accountname) + accountmetadata = os.path.join(metadatadir, accountname) + if not os.path.exists(accountmetadata): + os.mkdir(accountmetadata, 0700) + + server = None + if accountname in servers: + server = servers[accountname] + else: + server = imapserver.ConfigedIMAPServer(config, accountname, passwords) + servers[accountname] = server + + remoterepos = repository.IMAP.IMAPRepository(config, localeval, accountname, server) + + # Connect to the Maildirs. + localrepos = repository.Maildir.MaildirRepository(os.path.expanduser(config.get(accountname, "localfolders")), accountname, config) + + # Connect to the local cache. + statusrepos = repository.LocalStatus.LocalStatusRepository(accountmetadata) + + ui.syncfolders(remoterepos, localrepos) + remoterepos.syncfoldersto(localrepos) + ui.acct(accountname) + + folderthreads = [] + for remotefolder in remoterepos.getfolders(): + thread = InstanceLimitedThread(\ + instancename = 'FOLDER_' + accountname, + target = syncfolder, + name = "Folder sync %s[%s]" % \ + (accountname, remotefolder.getvisiblename()), + args = (accountname, remoterepos, remotefolder, localrepos, + statusrepos)) + thread.setDaemon(1) + thread.start() + folderthreads.append(thread) + threadutil.threadsreset(folderthreads) + if not (config.has_option(accountname, 'holdconnectionopen') and \ + config.getboolean(accountname, 'holdconnectionopen')): + server.close() + finally: + pass + +def syncfolder(accountname, remoterepos, remotefolder, localrepos, + statusrepos): + ui = UIBase.getglobalui() + # Load local folder. + localfolder = localrepos.\ + getfolder(remotefolder.getvisiblename().\ + replace(remoterepos.getsep(), localrepos.getsep())) + # Write the mailboxes + mailboxes.append({'accountname': accountname, + 'foldername': localfolder.getvisiblename()}) + # Load local folder + ui.syncingfolder(remoterepos, remotefolder, localrepos, localfolder) + ui.loadmessagelist(localrepos, localfolder) + localfolder.cachemessagelist() + ui.messagelistloaded(localrepos, localfolder, len(localfolder.getmessagelist().keys())) + + + # Load status folder. + statusfolder = statusrepos.getfolder(remotefolder.getvisiblename().\ + replace(remoterepos.getsep(), + statusrepos.getsep())) + if localfolder.getuidvalidity() == None: + # This is a new folder, so delete the status cache to be sure + # we don't have a conflict. + statusfolder.deletemessagelist() + + statusfolder.cachemessagelist() + + + # If either the local or the status folder has messages and + # there is a UID validity problem, warn and abort. + # If there are no messages, UW IMAPd loses UIDVALIDITY. + # But we don't really need it if both local folders are empty. + # So, in that case, save it off. + if (len(localfolder.getmessagelist()) or \ + len(statusfolder.getmessagelist())) and \ + not localfolder.isuidvalidityok(remotefolder): + ui.validityproblem(remotefolder) + return + else: + localfolder.saveuidvalidity(remotefolder.getuidvalidity()) + + # Load remote folder. + ui.loadmessagelist(remoterepos, remotefolder) + remotefolder.cachemessagelist() + ui.messagelistloaded(remoterepos, remotefolder, + len(remotefolder.getmessagelist().keys())) + + + # + + if not statusfolder.isnewfolder(): + # Delete local copies of remote messages. This way, + # if a message's flag is modified locally but it has been + # deleted remotely, we'll delete it locally. Otherwise, we + # try to modify a deleted message's flags! This step + # need only be taken if a statusfolder is present; otherwise, + # there is no action taken *to* the remote repository. + + remotefolder.syncmessagesto_delete(localfolder, [localfolder, + statusfolder]) + ui.syncingmessages(localrepos, localfolder, remoterepos, remotefolder) + localfolder.syncmessagesto(statusfolder, [remotefolder, statusfolder]) + + # Synchronize remote changes. + ui.syncingmessages(remoterepos, remotefolder, localrepos, localfolder) + remotefolder.syncmessagesto(localfolder) + + # Make sure the status folder is up-to-date. + ui.syncingmessages(localrepos, localfolder, statusrepos, statusfolder) + localfolder.syncmessagesto(statusfolder) + statusfolder.save() + + + +def syncitall(): + ui = UIBase.getglobalui() + global mailboxes + mailboxes = [] # Reset. + threads = [] + for accountname in accounts: + thread = InstanceLimitedThread(instancename = 'ACCOUNTLIMIT', + target = syncaccount, + name = "Account sync %s" % accountname, + args = (accountname,)) + thread.setDaemon(1) + thread.start() + threads.append(thread) + # Wait for the threads to finish. + threadutil.threadsreset(threads) + mbnames.genmbnames(config, localeval, mailboxes) + +def sync_with_timer(): + ui = UIBase.getglobalui() + currentThread().setExitMessage('SYNC_WITH_TIMER_TERMINATE') + syncitall() + 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, + name = "Keep alive " + accountname, + 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): + ui = UIBase.getglobalui() + if thread.getExitCause() == 'EXCEPTION': + if isinstance(thread.getExitException(), SystemExit): + # Bring a SystemExit into the main thread. + # Do not send it back to UI layer right now. + # Maybe later send it to ui.terminate? + raise SystemExit + ui.threadException(thread) # Expected to terminate + sys.exit(100) # Just in case... + os._exit(100) + elif thread.getExitMessage() == 'SYNC_WITH_TIMER_TERMINATE': + ui.terminate() + # Just in case... + sys.exit(100) + os._exit(100) + else: + ui.threadExited(thread) diff --git a/offlineimap/head/offlineimap/repository/Maildir.py b/offlineimap/head/offlineimap/repository/Maildir.py index af099bb..33b2ba8 100644 --- a/offlineimap/head/offlineimap/repository/Maildir.py +++ b/offlineimap/head/offlineimap/repository/Maildir.py @@ -18,9 +18,9 @@ from Base import BaseRepository from offlineimap import folder, imaputil +from offlineimap.ui import UIBase from mailbox import Maildir import os -import __main__ class MaildirRepository(BaseRepository): def __init__(self, root, accountname, config): @@ -31,7 +31,7 @@ class MaildirRepository(BaseRepository): self.folders = None self.accountname = accountname self.config = config - self.ui = __main__.ui + self.ui = UIBase.getglobalui() self.debug("MaildirRepository initialized, sep is " + repr(self.getsep())) def debug(self, msg): diff --git a/offlineimap/head/offlineimap/ui/UIBase.py b/offlineimap/head/offlineimap/ui/UIBase.py index e2550f1..27a820a 100644 --- a/offlineimap/head/offlineimap/ui/UIBase.py +++ b/offlineimap/head/offlineimap/ui/UIBase.py @@ -16,7 +16,6 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -from offlineimap import repository import offlineimap.version import re, time, sys, traceback from StringIO import StringIO @@ -24,6 +23,14 @@ from StringIO import StringIO debugtypes = {'imap': 'IMAP protocol debugging', 'maildir': 'Maildir repository debugging'} +globalui = None +def setglobalui(newui): + global globalui + globalui = newui +def getglobalui(): + global globalui + return globalui + class UIBase: def __init__(s, config, verbose = 0): s.verbose = verbose