/offlineimap/head: changeset 259

Initial commit on initialization reorganization
This commit is contained in:
jgoerzen 2002-10-07 21:59:02 +01:00
parent ca56d8c899
commit 73199ad735
10 changed files with 377 additions and 329 deletions

View File

@ -17,311 +17,6 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from offlineimap import imaplib, imapserver, repository, folder, mbnames, threadutil, version, localeval revno = long('$Rev: 252 $'[6:-2])
from offlineimap.threadutil import InstanceLimitedThread, ExitNotifyThread from offlineimap import init
from offlineimap.ui import UIBase init.startup(revno)
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.

View File

@ -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. * imaputil.py now logs information with IMAP debugging is enabled.
* Added folderfilter capability to mbnames recorder. You can now omit * Added folderfilter capability to mbnames recorder. You can now omit
specified folders from the mbnames output. specified folders from the mbnames output.
* Added a workaround to imaputil.py to deal with a bug in imaplib.py's * 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. 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 <jgoerzen@complete.org> Mon, 30 Sep 2002 12:08:08 -0500 -- John Goerzen <jgoerzen@complete.org> Mon, 30 Sep 2002 12:08:08 -0500

View File

@ -16,10 +16,10 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import __main__
from threading import * from threading import *
from offlineimap import threadutil from offlineimap import threadutil
from offlineimap.threadutil import InstanceLimitedThread from offlineimap.threadutil import InstanceLimitedThread
from offlineimap.ui import UIBase
class BaseFolder: class BaseFolder:
def getname(self): def getname(self):
@ -161,7 +161,7 @@ class BaseFolder:
for uid in self.getmessagelist().keys(): for uid in self.getmessagelist().keys():
if uid >= 0: if uid >= 0:
continue continue
__main__.ui.copyingmessage(uid, self, applyto) UIBase.getglobalui().copyingmessage(uid, self, applyto)
successobject = None successobject = None
successuid = None successuid = None
message = self.getmessage(uid) message = self.getmessage(uid)
@ -192,7 +192,7 @@ class BaseFolder:
# synced to the status cache. This is only a problem with # synced to the status cache. This is only a problem with
# self.getmessage(). So, don't call self.getmessage unless # self.getmessage(). So, don't call self.getmessage unless
# really needed. # really needed.
__main__.ui.copyingmessage(uid, self, applyto) UIBase.getglobalui().copyingmessage(uid, self, applyto)
message = '' message = ''
# If any of the destinations actually stores the message body, # If any of the destinations actually stores the message body,
# load it up. # load it up.
@ -249,7 +249,7 @@ class BaseFolder:
if not uid in self.getmessagelist(): if not uid in self.getmessagelist():
deletelist.append(uid) deletelist.append(uid)
if len(deletelist): if len(deletelist):
__main__.ui.deletingmessages(deletelist, applyto) UIBase.getglobalui().deletingmessages(deletelist, applyto)
for object in applyto: for object in applyto:
object.deletemessages(deletelist) object.deletemessages(deletelist)
@ -266,13 +266,13 @@ class BaseFolder:
addflags = [x for x in selfflags if x not in destflags] addflags = [x for x in selfflags if x not in destflags]
if len(addflags): if len(addflags):
__main__.ui.addingflags(uid, addflags, applyto) UIBase.getglobalui().addingflags(uid, addflags, applyto)
for object in applyto: for object in applyto:
object.addmessageflags(uid, addflags) object.addmessageflags(uid, addflags)
delflags = [x for x in destflags if x not in selfflags] delflags = [x for x in destflags if x not in selfflags]
if len(delflags): if len(delflags):
__main__.ui.deletingflags(uid, delflags, applyto) UIBase.getglobalui().deletingflags(uid, delflags, applyto)
for object in applyto: for object in applyto:
object.deletemessageflags(uid, delflags) object.deletemessageflags(uid, delflags)

View File

@ -18,11 +18,11 @@
from Base import BaseFolder from Base import BaseFolder
from offlineimap import imaputil, imaplib from offlineimap import imaputil, imaplib
from offlineimap.ui import UIBase
import rfc822, time, string import rfc822, time, string
from StringIO import StringIO from StringIO import StringIO
from copy import copy from copy import copy
import __main__
class IMAPFolder(BaseFolder): class IMAPFolder(BaseFolder):
def __init__(self, imapserver, name, visiblename, accountname, repository): def __init__(self, imapserver, name, visiblename, accountname, repository):
@ -102,7 +102,7 @@ class IMAPFolder(BaseFolder):
try: try:
imapobj.select(self.getfullname()) # Needed for search imapobj.select(self.getfullname()) # Needed for search
except imapobj.readonly: 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. # Return indicating message taken, but no UID assigned.
# Fudge it. # Fudge it.
return 0 return 0
@ -168,7 +168,7 @@ class IMAPFolder(BaseFolder):
try: try:
imapobj.select(self.getfullname()) imapobj.select(self.getfullname())
except imapobj.readonly: except imapobj.readonly:
__main__.ui.flagstoreadonly(self, [uid], flags) UIBase.getglobalui().flagstoreadonly(self, [uid], flags)
return return
result = imapobj.uid('store', '%d' % uid, 'FLAGS', result = imapobj.uid('store', '%d' % uid, 'FLAGS',
imaputil.flagsmaildir2imap(flags)) imaputil.flagsmaildir2imap(flags))
@ -191,7 +191,7 @@ class IMAPFolder(BaseFolder):
try: try:
imapobj.select(self.getfullname()) imapobj.select(self.getfullname())
except imapobj.readonly: except imapobj.readonly:
__main__.ui.flagstoreadonly(self, uidlist, flags) UIBase.getglobalui().flagstoreadonly(self, uidlist, flags)
return return
r = imapobj.uid('store', r = imapobj.uid('store',
imaputil.listjoin(uidlist), imaputil.listjoin(uidlist),
@ -242,7 +242,7 @@ class IMAPFolder(BaseFolder):
try: try:
imapobj.select(self.getfullname()) imapobj.select(self.getfullname())
except imapobj.readonly: except imapobj.readonly:
__main__.ui.deletereadonly(self, uidlist) UIBase.getglobalui().deletereadonly(self, uidlist)
return return
assert(imapobj.expunge()[0] == 'OK') assert(imapobj.expunge()[0] == 'OK')
finally: finally:

View File

@ -22,7 +22,7 @@ Public functions: Internaldate2tuple
__version__ = "2.52" __version__ = "2.52"
import binascii, re, socket, time, random, sys, os import binascii, re, socket, time, random, sys, os
import __main__ from offlineimap.ui import UIBase
__all__ = ["IMAP4", "Internaldate2tuple", __all__ = ["IMAP4", "Internaldate2tuple",
"Int2AP", "ParseFlags", "Time2Internaldate"] "Int2AP", "ParseFlags", "Time2Internaldate"]
@ -988,7 +988,7 @@ class IMAP4:
if secs is None: if secs is None:
secs = time.time() secs = time.time()
tm = time.strftime('%M:%S', time.localtime(secs)) 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): def _dump_ur(self, dict):
# Dump untagged responses (in `dict'). # Dump untagged responses (in `dict').

View File

@ -17,9 +17,10 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from offlineimap import imaplib, imaputil, threadutil from offlineimap import imaplib, imaputil, threadutil
from offlineimap.ui import UIBase
from threading import * from threading import *
import thread import thread
import __main__
class UsefulIMAPMixIn: class UsefulIMAPMixIn:
def getstate(self): def getstate(self):
@ -121,7 +122,7 @@ class IMAPServer:
self.connectionlock.release() # Release until need to modify data 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. # Generate a new connection.
if self.tunnel: if self.tunnel:

View File

@ -17,14 +17,14 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import re, string, types import re, string, types
from offlineimap.ui import UIBase
quotere = re.compile('^("(?:[^"]|\\\\")*")') quotere = re.compile('^("(?:[^"]|\\\\")*")')
import __main__
def debug(*args): def debug(*args):
msg = [] msg = []
for arg in args: for arg in args:
msg.append(str(arg)) msg.append(str(arg))
__main__.ui.debug('imap', " ".join(msg)) UIBase.getglobalui().debug('imap', " ".join(msg))
def dequote(string): def dequote(string):
"""Takes a string which may or may not be quoted and returns it, unquoted. """Takes a string which may or may not be quoted and returns it, unquoted.

View File

@ -0,0 +1,335 @@
# OfflineIMAP initialization code
# Copyright (C) 2002 John Goerzen
# <jgoerzen@complete.org>
#
# 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)

View File

@ -18,9 +18,9 @@
from Base import BaseRepository from Base import BaseRepository
from offlineimap import folder, imaputil from offlineimap import folder, imaputil
from offlineimap.ui import UIBase
from mailbox import Maildir from mailbox import Maildir
import os import os
import __main__
class MaildirRepository(BaseRepository): class MaildirRepository(BaseRepository):
def __init__(self, root, accountname, config): def __init__(self, root, accountname, config):
@ -31,7 +31,7 @@ class MaildirRepository(BaseRepository):
self.folders = None self.folders = None
self.accountname = accountname self.accountname = accountname
self.config = config self.config = config
self.ui = __main__.ui self.ui = UIBase.getglobalui()
self.debug("MaildirRepository initialized, sep is " + repr(self.getsep())) self.debug("MaildirRepository initialized, sep is " + repr(self.getsep()))
def debug(self, msg): def debug(self, msg):

View File

@ -16,7 +16,6 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from offlineimap import repository
import offlineimap.version import offlineimap.version
import re, time, sys, traceback import re, time, sys, traceback
from StringIO import StringIO from StringIO import StringIO
@ -24,6 +23,14 @@ from StringIO import StringIO
debugtypes = {'imap': 'IMAP protocol debugging', debugtypes = {'imap': 'IMAP protocol debugging',
'maildir': 'Maildir repository debugging'} 'maildir': 'Maildir repository debugging'}
globalui = None
def setglobalui(newui):
global globalui
globalui = newui
def getglobalui():
global globalui
return globalui
class UIBase: class UIBase:
def __init__(s, config, verbose = 0): def __init__(s, config, verbose = 0):
s.verbose = verbose s.verbose = verbose