[PATCH 1/4] Do away with UI plugins and simplify calling the GUI

Sebastian Spaeth Sebastian at SSpaeth.de
Wed Dec 22 14:27:17 UTC 2010


This change does away with the "dynamic" detection of GUIS and also
deletes the Curses (Blinkenlights) GUI in addition to given the GUIs
nicer names.

Blinkenlights was not optimal for various reasons, e.g. Exceptions
would be hidden as the screen immediately reset itself after exit. It
also frequently messed up the screen when offlineimap crashed.

Make the commonly called "get|setglobalui()" a function of
offlineimap.ui and not of the weird ui.UIBase. Save the ui instance in
various classes throughout the code rather than calling getglobalui()
again and again.

Simplify the names of the GUIs to something that a mortal can actually
remember, make them "TTY, Basic, Quiet, Machine" with TTY being the
default.

We will have to decide what to do with the remaining GUIs but for now
try to minimize the changes. This commit is huge enough.

Signed-off-by: Sebastian Spaeth <Sebastian at SSpaeth.de>
---
 offlineimap/accounts.py           |    6 +-
 offlineimap/folder/Base.py        |   28 +-
 offlineimap/folder/IMAP.py        |   56 ++--
 offlineimap/folder/Maildir.py     |   11 +-
 offlineimap/imaplibutil.py        |    4 +-
 offlineimap/imapserver.py         |   53 ++--
 offlineimap/imaputil.py           |    4 +-
 offlineimap/init.py               |   24 +-
 offlineimap/repository/Base.py    |    4 +-
 offlineimap/repository/Maildir.py |    4 +-
 offlineimap/threadutil.py         |    6 +-
 offlineimap/ui/Blinkenlights.py   |  147 ---------
 offlineimap/ui/Curses.py          |  595 -------------------------------------
 offlineimap/ui/TTY.py             |    2 +-
 offlineimap/ui/__init__.py        |   31 +--
 offlineimap/ui/detector.py        |   54 ----
 16 files changed, 110 insertions(+), 919 deletions(-)
 delete mode 100644 offlineimap/ui/Blinkenlights.py
 delete mode 100644 offlineimap/ui/Curses.py
 delete mode 100644 offlineimap/ui/detector.py

diff --git a/offlineimap/accounts.py b/offlineimap/accounts.py
index 667d2b2..236bba3 100644
--- a/offlineimap/accounts.py
+++ b/offlineimap/accounts.py
@@ -16,8 +16,8 @@
 #    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 
 from offlineimap import threadutil, mbnames, CustomConfig
+from offlineimap.ui import getglobalui
 import offlineimap.repository.Base, offlineimap.repository.LocalStatus
-from offlineimap.ui import UIBase
 from offlineimap.threadutil import InstanceLimitedThread, ExitNotifyThread
 from subprocess import Popen, PIPE
 from threading import Event, Lock
@@ -110,7 +110,7 @@ class Account(CustomConfig.ConfigHelperMixin):
         self.name = name
         self.metadatadir = config.getmetadatadir()
         self.localeval = config.getlocaleval()
-        self.ui = UIBase.getglobalui()
+        self.ui = getglobalui()
         self.refreshperiod = self.getconffloat('autorefresh', 0.0)
         self.quicknum = 0
         if self.refreshperiod == 0.0:
@@ -288,7 +288,7 @@ class SyncableAccount(Account, AccountSynchronizationMixin):
 def syncfolder(accountname, remoterepos, remotefolder, localrepos,
                statusrepos, quick):
     global mailboxes
-    ui = UIBase.getglobalui()
+    ui = getglobalui()
     ui.registerthread(accountname)
     try:
         # Load local folder.
diff --git a/offlineimap/folder/Base.py b/offlineimap/folder/Base.py
index 0f38bc3..a79dcf8 100644
--- a/offlineimap/folder/Base.py
+++ b/offlineimap/folder/Base.py
@@ -19,13 +19,15 @@
 from threading import *
 from offlineimap import threadutil
 from offlineimap.threadutil import InstanceLimitedThread
-from offlineimap.ui import UIBase
-import os.path, re
+from offlineimap.ui import getglobalui
+import os.path
+import re
 import sys
 
 class BaseFolder:
     def __init__(self):
         self.uidlock = Lock()
+        self.ui = getglobalui()
         
     def getname(self):
         """Returns name"""
@@ -202,8 +204,8 @@ class BaseFolder:
 
     def syncmessagesto_neguid_msg(self, uid, dest, applyto, register = 1):
         if register:
-            UIBase.getglobalui().registerthread(self.getaccountname())
-        UIBase.getglobalui().copyingmessage(uid, self, applyto)
+            self.ui.registerthread(self.getaccountname())
+        self.ui.copyingmessage(uid, self, applyto)
         successobject = None
         successuid = None
         message = self.getmessage(uid)
@@ -269,8 +271,8 @@ class BaseFolder:
         # really needed.
         try:
             if register:
-                UIBase.getglobalui().registerthread(self.getaccountname())
-            UIBase.getglobalui().copyingmessage(uid, self, applyto)
+                self.ui.registerthread(self.getaccountname())
+            self.ui.copyingmessage(uid, self, applyto)
             message = ''
             # If any of the destinations actually stores the message body,
             # load it up.
@@ -289,7 +291,7 @@ class BaseFolder:
                     self.deletemessage(uid)
                     uid = newuid
         except:
-            UIBase.getglobalui().warn("ERROR attempting to copy message " + str(uid) \
+            self.ui.warn("ERROR attempting to copy message " + str(uid) \
                  + " for account " + self.getaccountname() + ":" + str(sys.exc_info()[1]))
         
 
@@ -334,7 +336,7 @@ class BaseFolder:
             if not uid in self_messagelist:
                 deletelist.append(uid)
         if len(deletelist):
-            UIBase.getglobalui().deletingmessages(deletelist, applyto)
+            self.ui.deletingmessages(deletelist, applyto)
             for object in applyto:
                 object.deletemessages(deletelist)
 
@@ -375,10 +377,10 @@ class BaseFolder:
 
         for object in applyto:
             for flag in addflaglist.keys():
-                UIBase.getglobalui().addingflags(addflaglist[flag], flag, [object])
+                self.ui.addingflags(addflaglist[flag], flag, [object])
                 object.addmessagesflags(addflaglist[flag], [flag])
             for flag in delflaglist.keys():
-                UIBase.getglobalui().deletingflags(delflaglist[flag], flag, [object])
+                self.ui.deletingflags(delflaglist[flag], flag, [object])
                 object.deletemessagesflags(delflaglist[flag], [flag])
                 
     def syncmessagesto(self, dest, applyto = None):
@@ -394,7 +396,7 @@ class BaseFolder:
         try:
             self.syncmessagesto_neguid(dest, applyto)
         except:
-            UIBase.getglobalui().warn("ERROR attempting to handle negative uids " \
+            self.ui.warn("ERROR attempting to handle negative uids " \
                 + "for account " + self.getaccountname() + ":" + str(sys.exc_info()[1]))
 
         #all threads launched here are in try / except clauses when they copy anyway...
@@ -403,7 +405,7 @@ class BaseFolder:
         try:
             self.syncmessagesto_delete(dest, applyto)
         except:
-            UIBase.getglobalui().warn("ERROR attempting to delete messages " \
+            self.ui.warn("ERROR attempting to delete messages " \
                 + "for account " + self.getaccountname() + ":" + str(sys.exc_info()[1]))
 
         # Now, the message lists should be identical wrt the uids present.
@@ -413,7 +415,7 @@ class BaseFolder:
         try:
             self.syncmessagesto_flags(dest, applyto)
         except:
-            UIBase.getglobalui().warn("ERROR attempting to sync flags " \
+            self.ui.warn("ERROR attempting to sync flags " \
                 + "for account " + self.getaccountname() + ":" + str(sys.exc_info()[1]))
         
             
diff --git a/offlineimap/folder/IMAP.py b/offlineimap/folder/IMAP.py
index 927c5f4..2836bd3 100644
--- a/offlineimap/folder/IMAP.py
+++ b/offlineimap/folder/IMAP.py
@@ -19,7 +19,6 @@
 from Base import BaseFolder
 import imaplib
 from offlineimap import imaputil, imaplibutil
-from offlineimap.ui import UIBase
 from offlineimap.version import versionstr
 import rfc822, time, string, random, binascii, re
 from StringIO import StringIO
@@ -41,6 +40,7 @@ class IMAPFolder(BaseFolder):
         self.repository = repository
         self.randomgenerator = random.Random()
         BaseFolder.__init__(self)
+        #self.ui is set in BaseFolder
 
     def selectro(self, imapobj):
         """Select this folder when we do not need write access.
@@ -188,7 +188,7 @@ class IMAPFolder(BaseFolder):
             messagestr = string.split(messagestr, maxsplit = 1)[1]
             options = imaputil.flags2hash(messagestr)
             if not options.has_key('UID'):
-                UIBase.getglobalui().warn('No UID in message with options %s' %\
+                self.ui.warn('No UID in message with options %s' %\
                                           str(options),
                                           minor = 1)
             else:
@@ -201,12 +201,11 @@ class IMAPFolder(BaseFolder):
         return self.messagelist
 
     def getmessage(self, uid):
-        ui = UIBase.getglobalui()
         imapobj = self.imapserver.acquireconnection()
         try:
             imapobj.select(self.getfullname(), readonly = 1)
             initialresult = imapobj.uid('fetch', '%d' % uid, '(BODY.PEEK[])')
-            ui.debug('imap', 'Returned object from fetching %d: %s' % \
+            self.ui.debug('imap', 'Returned object from fetching %d: %s' % \
                      (uid, str(initialresult)))
             return initialresult[1][0][1].replace("\r\n", "\n")
                 
@@ -230,31 +229,29 @@ class IMAPFolder(BaseFolder):
         return (headername, headervalue)
 
     def savemessage_addheader(self, content, headername, headervalue):
-        ui = UIBase.getglobalui()
-        ui.debug('imap',
+        self.ui.debug('imap',
                  'savemessage_addheader: called to add %s: %s' % (headername,
                                                                   headervalue))
         insertionpoint = content.find("\r\n")
-        ui.debug('imap', 'savemessage_addheader: insertionpoint = %d' % insertionpoint)
+        self.ui.debug('imap', 'savemessage_addheader: insertionpoint = %d' % insertionpoint)
         leader = content[0:insertionpoint]
-        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)
-        ui.debug('imap', 'savemessage_addheader: newline = ' + repr(newline))
+        self.ui.debug('imap', 'savemessage_addheader: newline = ' + repr(newline))
         trailer = content[insertionpoint:]
-        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):
         if imapobj.untagged_responses.has_key('APPENDUID'):
             return long(imapobj.untagged_responses['APPENDUID'][-1].split(' ')[1])
 
-        ui = UIBase.getglobalui()
-        ui.debug('imap', 'savemessage_searchforheader called for %s: %s' % \
+        self.ui.debug('imap', 'savemessage_searchforheader called for %s: %s' % \
                  (headername, headervalue))
         # Now find the UID it got.
         headervalue = imapobj._quote(headervalue)
@@ -262,16 +259,16 @@ class IMAPFolder(BaseFolder):
             matchinguids = imapobj.uid('search', 'HEADER', headername, headervalue)[1][0]
         except imapobj.error, err:
             # IMAP server doesn't implement search or had a problem.
-            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
-        ui.debug('imap', 'savemessage_searchforheader got initial matchinguids: ' + repr(matchinguids))
+        self.ui.debug('imap', 'savemessage_searchforheader got initial matchinguids: ' + repr(matchinguids))
 
         if matchinguids == '':
-            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(' ')
-        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 header %s, got wrong-sized matchinguids of %s" % (headername, str(matchinguids))
@@ -280,13 +277,12 @@ class IMAPFolder(BaseFolder):
 
     def savemessage(self, uid, content, flags, rtime):
         imapobj = self.imapserver.acquireconnection()
-        ui = UIBase.getglobalui()
-        ui.debug('imap', 'savemessage: called')
+        self.ui.debug('imap', 'savemessage: called')
         try:
             try:
                 imapobj.select(self.getfullname()) # Needed for search
             except imapobj.readonly:
-                ui.msgtoreadonly(self, uid, content, flags)
+                self.ui.msgtoreadonly(self, uid, content, flags)
                 # Return indicating message taken, but no UID assigned.
                 # Fudge it.
                 return 0
@@ -324,17 +320,17 @@ class IMAPFolder(BaseFolder):
                 # but some IMAP servers nonetheless choke on 1902.
                 date = imaplib.Time2Internaldate(time.localtime())
 
-            ui.debug('imap', 'savemessage: using date ' + str(date))
+            self.ui.debug('imap', 'savemessage: using date ' + str(date))
             content = re.sub("(?<!\r)\n", "\r\n", content)
-            ui.debug('imap', 'savemessage: initial content is: ' + repr(content))
+            self.ui.debug('imap', 'savemessage: initial content is: ' + repr(content))
 
             (headername, headervalue) = self.savemessage_getnewheader(content)
-            ui.debug('imap', 'savemessage: new headers are: %s: %s' % \
+            self.ui.debug('imap', 'savemessage: new headers are: %s: %s' % \
                      (headername, headervalue))
             content = self.savemessage_addheader(content, headername,
                                                  headervalue)
-            ui.debug('imap', 'savemessage: new content is: ' + repr(content))
-            ui.debug('imap', 'savemessage: new content length is ' + \
+            self.ui.debug('imap', 'savemessage: new content is: ' + repr(content))
+            self.ui.debug('imap', 'savemessage: new content length is ' + \
                      str(len(content)))
 
             assert(imapobj.append(self.getfullname(),
@@ -345,12 +341,12 @@ class IMAPFolder(BaseFolder):
             assert(imapobj.check()[0] == 'OK')
 
             # Keep trying until we get the UID.
-            ui.debug('imap', 'savemessage: first attempt to get new UID')
+            self.ui.debug('imap', 'savemessage: first attempt to get new UID')
             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:
-                ui.debug('imap', 'savemessage: first attempt to get new UID failed.  Going to run a NOOP and try again.')
+                self.ui.debug('imap', 'savemessage: first attempt to get new UID failed.  Going to run a NOOP and try again.')
                 assert(imapobj.noop()[0] == 'OK')
                 uid = self.savemessage_searchforheader(imapobj, headername,
                                                        headervalue)
@@ -360,7 +356,7 @@ class IMAPFolder(BaseFolder):
         if uid: # avoid UID FETCH 0 crash happening later on
             self.messagelist[uid] = {'uid': uid, 'flags': flags}
 
-        ui.debug('imap', 'savemessage: returning %d' % uid)
+        self.ui.debug('imap', 'savemessage: returning %d' % uid)
         return uid
 
     def savemessageflags(self, uid, flags):
@@ -369,7 +365,7 @@ class IMAPFolder(BaseFolder):
             try:
                 imapobj.select(self.getfullname())
             except imapobj.readonly:
-                UIBase.getglobalui().flagstoreadonly(self, [uid], flags)
+                self.ui.flagstoreadonly(self, [uid], flags)
                 return
             result = imapobj.uid('store', '%d' % uid, 'FLAGS',
                                  imaputil.flagsmaildir2imap(flags))
@@ -413,7 +409,7 @@ class IMAPFolder(BaseFolder):
             try:
                 imapobj.select(self.getfullname())
             except imapobj.readonly:
-                UIBase.getglobalui().flagstoreadonly(self, uidlist, flags)
+                self.ui.flagstoreadonly(self, uidlist, flags)
                 return
             r = imapobj.uid('store',
                             imaputil.listjoin(uidlist),
@@ -472,7 +468,7 @@ class IMAPFolder(BaseFolder):
             try:
                 imapobj.select(self.getfullname())
             except imapobj.readonly:
-                UIBase.getglobalui().deletereadonly(self, uidlist)
+                self.ui.deletereadonly(self, uidlist)
                 return
             if self.expunge:
                 assert(imapobj.expunge()[0] == 'OK')
diff --git a/offlineimap/folder/Maildir.py b/offlineimap/folder/Maildir.py
index 49d4dae..a01d071 100644
--- a/offlineimap/folder/Maildir.py
+++ b/offlineimap/folder/Maildir.py
@@ -19,7 +19,6 @@
 import os.path, os, re, time, socket
 from Base import BaseFolder
 from offlineimap import imaputil
-from offlineimap.ui import UIBase
 from threading import Lock
 
 try:
@@ -61,6 +60,7 @@ class MaildirFolder(BaseFolder):
         self.repository = repository
         self.accountname = accountname
         BaseFolder.__init__(self)
+        #self.ui is set in BaseFolder.init()
 
     def getaccountname(self):
         return self.accountname
@@ -193,8 +193,7 @@ class MaildirFolder(BaseFolder):
     def savemessage(self, uid, content, flags, rtime):
         # This function only ever saves to tmp/,
         # but it calls savemessageflags() to actually save to cur/ or new/.
-        ui = UIBase.getglobalui()
-        ui.debug('maildir', 'savemessage: called to write with flags %s and content %s' % \
+        self.ui.debug('maildir', 'savemessage: called to write with flags %s and content %s' % \
                  (repr(flags), repr(content)))
         if uid < 0:
             # We cannot assign a new uid.
@@ -226,7 +225,7 @@ class MaildirFolder(BaseFolder):
             else:
                 break
         tmpmessagename = messagename.split(',')[0]
-        ui.debug('maildir', 'savemessage: using temporary name %s' % tmpmessagename)
+        self.ui.debug('maildir', 'savemessage: using temporary name %s' % tmpmessagename)
         file = open(os.path.join(tmpdir, tmpmessagename), "wt")
         file.write(content)
 
@@ -238,7 +237,7 @@ class MaildirFolder(BaseFolder):
         file.close()
         if rtime != None:
             os.utime(os.path.join(tmpdir,tmpmessagename), (rtime,rtime))
-        ui.debug('maildir', 'savemessage: moving from %s to %s' % \
+        self.ui.debug('maildir', 'savemessage: moving from %s to %s' % \
                  (tmpmessagename, messagename))
         if tmpmessagename != messagename: # then rename it
             os.rename(os.path.join(tmpdir, tmpmessagename),
@@ -256,7 +255,7 @@ class MaildirFolder(BaseFolder):
         self.messagelist[uid] = {'uid': uid, 'flags': [],
                                  'filename': os.path.join(tmpdir, messagename)}
         self.savemessageflags(uid, flags)
-        ui.debug('maildir', 'savemessage: returning uid %d' % uid)
+        self.ui.debug('maildir', 'savemessage: returning uid %d' % uid)
         return uid
         
     def getmessageflags(self, uid):
diff --git a/offlineimap/imaplibutil.py b/offlineimap/imaplibutil.py
index ba6a5bb..dc6940d 100644
--- a/offlineimap/imaplibutil.py
+++ b/offlineimap/imaplibutil.py
@@ -16,7 +16,7 @@
 #    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 
 import re, socket, time, subprocess
-from offlineimap.ui import UIBase
+from offlineimap.ui import getglobalui
 from imaplib import *
 
 # Import the symbols we need that aren't exported by default
@@ -67,7 +67,7 @@ def new_mesg(self, s, secs=None):
             if secs is None:
                 secs = time.time()
             tm = time.strftime('%M:%S', time.localtime(secs))
-            UIBase.getglobalui().debug('imap', '  %s.%02d %s' % (tm, (secs*100)%100, s))
+            getglobalui().debug('imap', '  %s.%02d %s' % (tm, (secs*100)%100, s))
 
 class WrappedIMAP4_SSL(IMAP4_SSL):
     """Provides an improved version of the standard IMAP4_SSL
diff --git a/offlineimap/imapserver.py b/offlineimap/imapserver.py
index 0029800..2a9f247 100644
--- a/offlineimap/imapserver.py
+++ b/offlineimap/imapserver.py
@@ -18,7 +18,7 @@
 
 import imaplib
 from offlineimap import imaplibutil, imaputil, threadutil
-from offlineimap.ui import UIBase
+from offlineimap.ui import getglobalui
 from threading import *
 import thread, hmac, os, time
 import base64
@@ -102,6 +102,7 @@ class IMAPServer:
                  port = None, ssl = 1, maxconnections = 1, tunnel = None,
                  reference = '""', sslclientcert = None, sslclientkey = None,
                  sslcacertfile= None):
+        self.ui = getglobalui()
         self.reposname = reposname
         self.config = config
         self.username = username
@@ -140,7 +141,7 @@ class IMAPServer:
         if self.password != None and self.passworderror == None:
             return self.password
 
-        self.password = UIBase.getglobalui().getpass(self.reposname,
+        self.password = self.ui.getpass(self.reposname,
                                                      self.config,
                                                      self.passworderror)
         self.passworderror = None
@@ -167,18 +168,16 @@ class IMAPServer:
         self.semaphore.release()
 
     def md5handler(self, response):
-        ui = UIBase.getglobalui()
         challenge = response.strip()
-        ui.debug('imap', 'md5handler: got challenge %s' % challenge)
+        self.ui.debug('imap', 'md5handler: got challenge %s' % challenge)
 
         passwd = self.getpassword()
         retval = self.username + ' ' + hmac.new(passwd, challenge).hexdigest()
-        ui.debug('imap', 'md5handler: returning %s' % retval)
+        self.ui.debug('imap', 'md5handler: returning %s' % retval)
         return retval
 
     def plainauth(self, imapobj):
-        UIBase.getglobalui().debug('imap',
-                                   'Attempting plain authentication')
+        self.ui.debug('imap', 'Attempting plain authentication')
         imapobj.login(self.username, self.getpassword())
 
     def gssauth(self, response):
@@ -201,8 +200,7 @@ class IMAPServer:
         except kerberos.GSSError, err:
             # Kerberos errored out on us, respond with None to cancel the
             # authentication
-            UIBase.getglobalui().debug('imap',
-                                       '%s: %s' % (err[0][0], err[1][0]))
+            self.ui.debug('imap', '%s: %s' % (err[0][0], err[1][0]))
             return None
 
         if not response:
@@ -249,16 +247,16 @@ class IMAPServer:
             while not success:
                 # Generate a new connection.
                 if self.tunnel:
-                    UIBase.getglobalui().connecting('tunnel', self.tunnel)
+                    self.ui.connecting('tunnel', self.tunnel)
                     imapobj = UsefulIMAP4_Tunnel(self.tunnel)
                     success = 1
                 elif self.usessl:
-                    UIBase.getglobalui().connecting(self.hostname, self.port)
+                    self.ui.connecting(self.hostname, self.port)
                     imapobj = UsefulIMAP4_SSL(self.hostname, self.port,
                                               self.sslclientkey, self.sslclientcert, 
                                               cacertfile = self.sslcacertfile)
                 else:
-                    UIBase.getglobalui().connecting(self.hostname, self.port)
+                    self.ui.connecting(self.hostname, self.port)
                     imapobj = UsefulIMAP4(self.hostname, self.port)
 
                 imapobj.mustquote = imaplibutil.mustquote
@@ -267,13 +265,13 @@ class IMAPServer:
                     try:
                         # Try GSSAPI and continue if it fails
                         if 'AUTH=GSSAPI' in imapobj.capabilities and have_gss:
-                            UIBase.getglobalui().debug('imap',
+                            self.ui.debug('imap',
                                 'Attempting GSSAPI authentication')
                             try:
                                 imapobj.authenticate('GSSAPI', self.gssauth)
                             except imapobj.error, val:
                                 self.gssapi = False
-                                UIBase.getglobalui().debug('imap',
+                                self.ui.debug('imap',
                                     'GSSAPI Authentication failed')
                             else:
                                 self.gssapi = True
@@ -282,7 +280,7 @@ class IMAPServer:
 
                         if not self.gssapi:
                             if 'AUTH=CRAM-MD5' in imapobj.capabilities:
-                                UIBase.getglobalui().debug('imap',
+                                self.ui.debug('imap',
                                                        'Attempting CRAM-MD5 authentication')
                                 try:
                                     imapobj.authenticate('CRAM-MD5', self.md5handler)
@@ -357,47 +355,46 @@ class IMAPServer:
         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."""
-        ui = UIBase.getglobalui()
-        ui.debug('imap', 'keepalive thread started')
+        self.ui.debug('imap', 'keepalive thread started')
         while 1:
-            ui.debug('imap', 'keepalive: top of loop')
+            self.ui.debug('imap', 'keepalive: top of loop')
             time.sleep(timeout)
-            ui.debug('imap', 'keepalive: after wait')
+            self.ui.debug('imap', 'keepalive: after wait')
             if event.isSet():
-                ui.debug('imap', 'keepalive: event is set; exiting')
+                self.ui.debug('imap', 'keepalive: event is set; exiting')
                 return
-            ui.debug('imap', 'keepalive: acquiring connectionlock')
+            self.ui.debug('imap', 'keepalive: acquiring connectionlock')
             self.connectionlock.acquire()
             numconnections = len(self.assignedconnections) + \
                              len(self.availableconnections)
             self.connectionlock.release()
-            ui.debug('imap', 'keepalive: connectionlock released')
+            self.ui.debug('imap', 'keepalive: connectionlock released')
             threads = []
             imapobjs = []
         
             for i in range(numconnections):
-                ui.debug('imap', 'keepalive: processing connection %d of %d' % (i, numconnections))
+                self.ui.debug('imap', 'keepalive: processing connection %d of %d' % (i, numconnections))
                 imapobj = self.acquireconnection()
-                ui.debug('imap', 'keepalive: connection %d acquired' % i)
+                self.ui.debug('imap', 'keepalive: connection %d acquired' % i)
                 imapobjs.append(imapobj)
                 thr = threadutil.ExitNotifyThread(target = imapobj.noop)
                 thr.setDaemon(1)
                 thr.start()
                 threads.append(thr)
-                ui.debug('imap', 'keepalive: thread started')
+                self.ui.debug('imap', 'keepalive: thread started')
 
-            ui.debug('imap', 'keepalive: joining threads')
+            self.ui.debug('imap', 'keepalive: joining threads')
 
             for thr in threads:
                 # Make sure all the commands have completed.
                 thr.join()
 
-            ui.debug('imap', 'keepalive: releasing connections')
+            self.ui.debug('imap', 'keepalive: releasing connections')
 
             for imapobj in imapobjs:
                 self.releaseconnection(imapobj)
 
-            ui.debug('imap', 'keepalive: bottom of loop')
+            self.ui.debug('imap', 'keepalive: bottom of loop')
 
 class ConfigedIMAPServer(IMAPServer):
     """This class is designed for easier initialization given a ConfigParser
diff --git a/offlineimap/imaputil.py b/offlineimap/imaputil.py
index 46a01d8..fe74854 100644
--- a/offlineimap/imaputil.py
+++ b/offlineimap/imaputil.py
@@ -17,14 +17,14 @@
 #    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 
 import re, string, types
-from offlineimap.ui import UIBase
+from offlineimap.ui import getglobalui
 quotere = re.compile('^("(?:[^"]|\\\\")*")')
 
 def debug(*args):
     msg = []
     for arg in args:
         msg.append(str(arg))
-    UIBase.getglobalui().debug('imap', " ".join(msg))
+    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/init.py b/offlineimap/init.py
index 4a7c67a..d895737 100644
--- a/offlineimap/init.py
+++ b/offlineimap/init.py
@@ -20,15 +20,14 @@ import imaplib
 from offlineimap import imapserver, threadutil, version, syncmaster, accounts
 from offlineimap.localeval import LocalEval
 from offlineimap.threadutil import InstanceLimitedThread, ExitNotifyThread
-import offlineimap.ui
 from offlineimap.CustomConfig import CustomConfigParser
-from offlineimap.ui.detector import DEFAULT_UI_LIST
 from optparse import OptionParser
 import re, os, sys
 from threading import *
 import threading, socket
 import signal
 import logging
+import offlineimap
 
 try:
     import fcntl
@@ -151,7 +150,7 @@ class OfflineImap:
               "configuration file. The UI specified with -u will "
               "be forced to be used, even if checks determine that it is "
               "not usable. Possible interface choices are: %s " %
-              ", ".join(DEFAULT_UI_LIST))
+              ", ".join(offlineimap.ui.UI_LIST.keys()))
 
         (options, args) = parser.parse_args()
 
@@ -187,10 +186,19 @@ class OfflineImap:
                     section = "general"
                 config.set(section, key, value)
 
-        #init the ui, and set up additional log files
-        ui = offlineimap.ui.detector.findUI(config, options.interface)
-        offlineimap.ui.UIBase.setglobalui(ui)
-    
+        #init the ui, cmd line option overrides config file
+        ui_type = config.getdefault('general','ui', None)
+        if options.interface != None:
+            ui_type = options.interface
+        try:
+            ui = offlineimap.ui.UI_LIST[ui_type](config)
+        except KeyError:
+            logging.error("UI '%s' does not exist, choose one of: %s" % \
+                              (ui_type,','.join(offlineimap.ui.UI_LIST.keys())))
+            sys.exit(1)
+        offlineimap.ui.setglobalui(ui)
+
+        #set up additional log files
         if options.logfile:
             ui.setlogfd(open(options.logfile, 'wt'))
     
@@ -236,7 +244,7 @@ class OfflineImap:
     
         def sigterm_handler(self, signum, frame):
             # die immediately
-            ui = BaseUI.getglobalui()
+            ui = offlineimap.ui.getglobalui()
             ui.terminate(errormsg="terminating...")
 
         signal.signal(signal.SIGTERM,sigterm_handler)
diff --git a/offlineimap/repository/Base.py b/offlineimap/repository/Base.py
index a9d98f1..0f4dd76 100644
--- a/offlineimap/repository/Base.py
+++ b/offlineimap/repository/Base.py
@@ -17,7 +17,7 @@
 #    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 
 from offlineimap import CustomConfig
-from offlineimap.ui import UIBase
+from offlineimap.ui import getglobalui
 import os.path
 import sys
 
@@ -164,7 +164,7 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
                     for copyfolder in copyfolders:
                         copyfolder.makefolder(key.replace(dest.getsep(), copyfolder.getsep()))
                 except:
-                    UIBase.getglobalui().warn("ERROR Attempting to make folder " \
+                    getglobalui().warn("ERROR Attempting to make folder " \
                         + key + ":"  +str(sys.exc_info()[1]))
                 
 
diff --git a/offlineimap/repository/Maildir.py b/offlineimap/repository/Maildir.py
index 53edd63..648440a 100644
--- a/offlineimap/repository/Maildir.py
+++ b/offlineimap/repository/Maildir.py
@@ -18,7 +18,7 @@
 
 from Base import BaseRepository
 from offlineimap import folder, imaputil
-from offlineimap.ui import UIBase
+from offlineimap.ui import getglobalui
 from mailbox import Maildir
 import os
 from stat import *
@@ -31,7 +31,7 @@ class MaildirRepository(BaseRepository):
 
         self.root = self.getlocalroot()
         self.folders = None
-        self.ui = UIBase.getglobalui()
+        self.ui = getglobalui()
         self.debug("MaildirRepository initialized, sep is " + repr(self.getsep()))
 	self.folder_atimes = []
 
diff --git a/offlineimap/threadutil.py b/offlineimap/threadutil.py
index da7bcf6..b24f2e1 100644
--- a/offlineimap/threadutil.py
+++ b/offlineimap/threadutil.py
@@ -20,7 +20,7 @@ from threading import *
 from StringIO import StringIO
 from Queue import Queue, Empty
 import sys, traceback, thread, time
-from offlineimap.ui import UIBase       # for getglobalui()
+from offlineimap.ui import getglobalui
 
 profiledir = None
 
@@ -129,7 +129,7 @@ def exitnotifymonitorloop(callback):
 
 def threadexited(thread):
     """Called when a thread exits."""
-    ui = UIBase.getglobalui()
+    ui = getglobalui()
     if thread.getExitCause() == 'EXCEPTION':
         if isinstance(thread.getExitException(), SystemExit):
             # Bring a SystemExit into the main thread.
@@ -297,5 +297,3 @@ class MultiLock:
                 self.lock.release()
         finally:
             self.statuslock.release()
-
-        
diff --git a/offlineimap/ui/Blinkenlights.py b/offlineimap/ui/Blinkenlights.py
deleted file mode 100644
index dcc4e01..0000000
--- a/offlineimap/ui/Blinkenlights.py
+++ /dev/null
@@ -1,147 +0,0 @@
-# Blinkenlights base classes
-# Copyright (C) 2003 John Goerzen
-# <jgoerzen at 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
-
-from threading import *
-from offlineimap.ui.UIBase import UIBase
-import thread
-from offlineimap.threadutil import MultiLock
-
-class BlinkenBase:
-    """This is a mix-in class that should be mixed in with either UIBase
-    or another appropriate base class.  The Tk interface, for instance,
-    will probably mix it in with VerboseUI."""
-
-    def acct(s, accountname):
-        s.gettf().setcolor('purple')
-        s.__class__.__bases__[-1].acct(s, accountname)
-
-    def connecting(s, hostname, port):
-        s.gettf().setcolor('gray')
-        s.__class__.__bases__[-1].connecting(s, hostname, port)
-
-    def syncfolders(s, srcrepos, destrepos):
-        s.gettf().setcolor('blue')
-        s.__class__.__bases__[-1].syncfolders(s, srcrepos, destrepos)
-
-    def syncingfolder(s, srcrepos, srcfolder, destrepos, destfolder):
-        s.gettf().setcolor('cyan')
-        s.__class__.__bases__[-1].syncingfolder(s, srcrepos, srcfolder, destrepos, destfolder)
-
-    def skippingfolder(s, folder):
-        s.gettf().setcolor('cyan')
-        s.__class__.__bases__[-1].skippingfolder(s, folder)
-
-    def loadmessagelist(s, repos, folder):
-        s.gettf().setcolor('green')
-        s._msg("Scanning folder [%s/%s]" % (s.getnicename(repos),
-                                            folder.getvisiblename()))
-
-    def syncingmessages(s, sr, sf, dr, df):
-        s.gettf().setcolor('blue')
-        s.__class__.__bases__[-1].syncingmessages(s, sr, sf, dr, df)
-
-    def copyingmessage(s, uid, src, destlist):
-        s.gettf().setcolor('orange')
-        s.__class__.__bases__[-1].copyingmessage(s, uid, src, destlist)
-
-    def deletingmessages(s, uidlist, destlist):
-        s.gettf().setcolor('red')
-        s.__class__.__bases__[-1].deletingmessages(s, uidlist, destlist)
-
-    def deletingmessage(s, uid, destlist):
-        s.gettf().setcolor('red')
-        s.__class__.__bases__[-1].deletingmessage(s, uid, destlist)
-
-    def addingflags(s, uidlist, flags, destlist):
-        s.gettf().setcolor('yellow')
-        s.__class__.__bases__[-1].addingflags(s, uidlist, flags, destlist)
-
-    def deletingflags(s, uidlist, flags, destlist):
-        s.gettf().setcolor('pink')
-        s.__class__.__bases__[-1].deletingflags(s, uidlist, flags, destlist)
-
-    def warn(s, msg, minor = 0):
-        if minor:
-            s.gettf().setcolor('pink')
-        else:
-            s.gettf().setcolor('red')
-        s.__class__.__bases__[-1].warn(s, msg, minor)
-
-    def init_banner(s):
-        s.availablethreadframes = {}
-        s.threadframes = {}
-        s.tflock = MultiLock()
-
-    def threadExited(s, thread):
-        threadid = thread.threadid
-        accountname = s.getthreadaccount(thread)
-        s.tflock.acquire()
-        try:
-            if threadid in s.threadframes[accountname]:
-                tf = s.threadframes[accountname][threadid]
-                del s.threadframes[accountname][threadid]
-                s.availablethreadframes[accountname].append(tf)
-                tf.setthread(None)
-        finally:
-            s.tflock.release()
-
-        UIBase.threadExited(s, thread)
-
-    def gettf(s):
-        threadid = thread.get_ident()
-        accountname = s.getthreadaccount()
-
-        s.tflock.acquire()
-
-        try:
-            if not accountname in s.threadframes:
-                s.threadframes[accountname] = {}
-                
-            if threadid in s.threadframes[accountname]:
-                return s.threadframes[accountname][threadid]
-
-            if not accountname in s.availablethreadframes:
-                s.availablethreadframes[accountname] = []
-
-            if len(s.availablethreadframes[accountname]):
-                tf = s.availablethreadframes[accountname].pop(0)
-                tf.setthread(currentThread())
-            else:
-                tf = s.getaccountframe().getnewthreadframe()
-            s.threadframes[accountname][threadid] = tf
-            return tf
-        finally:
-            s.tflock.release()
-
-    def callhook(s, msg):
-        s.gettf().setcolor('white')
-        s.__class__.__bases__[-1].callhook(s, msg)
-            
-    def sleep(s, sleepsecs, siglistener):
-        s.gettf().setcolor('red')
-        s.getaccountframe().startsleep(sleepsecs)
-        return UIBase.sleep(s, sleepsecs, siglistener)
-
-    def sleeping(s, sleepsecs, remainingsecs):
-        if remainingsecs and s.gettf().getcolor() == 'black':
-            s.gettf().setcolor('red')
-        else:
-            s.gettf().setcolor('black')
-        return s.getaccountframe().sleeping(sleepsecs, remainingsecs)
-
-    
diff --git a/offlineimap/ui/Curses.py b/offlineimap/ui/Curses.py
deleted file mode 100644
index 285d3af..0000000
--- a/offlineimap/ui/Curses.py
+++ /dev/null
@@ -1,595 +0,0 @@
-# Curses-based interfaces
-# Copyright (C) 2003 John Goerzen
-# <jgoerzen at 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
-
-from Blinkenlights import BlinkenBase
-from UIBase import UIBase
-from threading import *
-import thread, time, sys, os, signal, time
-from offlineimap import version, threadutil
-from offlineimap.threadutil import MultiLock
-
-import curses, curses.panel, curses.textpad, curses.wrapper
-
-acctkeys = '1234567890abcdefghijklmnoprstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-=;/.,'
-
-class CursesUtil:
-    def __init__(self):
-        self.pairlock = Lock()
-        self.iolock = MultiLock()
-        self.start()
-
-    def initpairs(self):
-        self.pairlock.acquire()
-        try:
-            self.pairs = {self._getpairindex(curses.COLOR_WHITE,
-                                             curses.COLOR_BLACK): 0}
-            self.nextpair = 1
-        finally:
-            self.pairlock.release()
-
-    def lock(self):
-        self.iolock.acquire()
-
-    def unlock(self):
-        self.iolock.release()
-        
-    def locked(self, target, *args, **kwargs):
-        """Perform an operation with full locking."""
-        self.lock()
-        try:
-            apply(target, args, kwargs)
-        finally:
-            self.unlock()
-
-    def refresh(self):
-        def lockedstuff():
-            curses.panel.update_panels()
-            curses.doupdate()
-        self.locked(lockedstuff)
-
-    def isactive(self):
-        return hasattr(self, 'stdscr')
-
-    def _getpairindex(self, fg, bg):
-        return '%d/%d' % (fg,bg)
-
-    def getpair(self, fg, bg):
-        if not self.has_color:
-            return 0
-        pindex = self._getpairindex(fg, bg)
-        self.pairlock.acquire()
-        try:
-            if self.pairs.has_key(pindex):
-                return curses.color_pair(self.pairs[pindex])
-            else:
-                self.pairs[pindex] = self.nextpair
-                curses.init_pair(self.nextpair, fg, bg)
-                self.nextpair += 1
-                return curses.color_pair(self.nextpair - 1)
-        finally:
-            self.pairlock.release()
-    
-    def start(self):
-        self.stdscr = curses.initscr()
-        curses.noecho()
-        curses.cbreak()
-        self.stdscr.keypad(1)
-        try:
-            curses.start_color()
-            self.has_color = curses.has_colors()
-        except:
-            self.has_color = 0
-
-        self.oldcursor = None
-        try:
-            self.oldcursor = curses.curs_set(0)
-        except:
-            pass
-        
-        self.stdscr.clear()
-        self.stdscr.refresh()
-        (self.height, self.width) = self.stdscr.getmaxyx()
-        self.initpairs()
-
-    def stop(self):
-        if not hasattr(self, 'stdscr'):
-            return
-        #self.stdscr.addstr(self.height - 1, 0, "\n",
-        #                   self.getpair(curses.COLOR_WHITE,
-        #                                curses.COLOR_BLACK))
-        if self.oldcursor != None:
-            curses.curs_set(self.oldcursor)
-        self.stdscr.refresh()
-        self.stdscr.keypad(0)
-        curses.nocbreak()
-        curses.echo()
-        curses.endwin()
-        del self.stdscr
-
-    def reset(self):
-        # dirty walkaround for bug http://bugs.python.org/issue7567 in python 2.6 to 2.6.5 (fixed since #83743)
-        if (sys.version_info[0:3] >= (2,6) and  sys.version_info[0:3] <= (2,6,5)): return
-        self.stop()
-        self.start()
-
-class CursesAccountFrame:
-    def __init__(s, master, accountname, ui):
-        s.c = master
-        s.children = []
-        s.accountname = accountname
-        s.ui = ui
-
-    def drawleadstr(s, secs = None):
-        if secs == None:
-            acctstr = '%s: [active] %13.13s: ' % (s.key, s.accountname)
-        else:
-            acctstr = '%s: [%3d:%02d] %13.13s: ' % (s.key,
-                                                    secs / 60, secs % 60,
-                                                    s.accountname)
-        s.c.locked(s.window.addstr, 0, 0, acctstr)
-        s.location = len(acctstr)
-
-    def setwindow(s, window, key):
-        s.window = window
-        s.key = key
-        s.drawleadstr()
-        for child in s.children:
-            child.update(window, 0, s.location)
-            s.location += 1
-
-    def getnewthreadframe(s):
-        tf = CursesThreadFrame(s.c, s.ui, s.window, 0, s.location)
-        s.location += 1
-        s.children.append(tf)
-        return tf
-
-    def startsleep(s, sleepsecs):
-        s.sleeping_abort = 0
-
-    def sleeping(s, sleepsecs, remainingsecs):
-        if remainingsecs:
-            s.c.lock()
-            try:
-                s.drawleadstr(remainingsecs)
-                s.window.refresh()
-            finally:
-                s.c.unlock()
-            time.sleep(sleepsecs)
-        else:
-            s.c.lock()
-            try:
-                s.drawleadstr()
-                s.window.refresh()
-            finally:
-                s.c.unlock()
-        return s.sleeping_abort
-
-    def syncnow(s):
-        s.sleeping_abort = 1
-
-class CursesThreadFrame:
-    def __init__(s, master, ui, window, y, x):
-        """master should be a CursesUtil object."""
-        s.c = master
-        s.ui = ui
-        s.window = window
-        s.x = x
-        s.y = y
-        s.colors = []
-        bg = curses.COLOR_BLACK
-        s.colormap = {'black': s.c.getpair(curses.COLOR_BLACK, bg),
-                         'gray': s.c.getpair(curses.COLOR_WHITE, bg),
-                         'white': curses.A_BOLD | s.c.getpair(curses.COLOR_WHITE, bg),
-                         'blue': s.c.getpair(curses.COLOR_BLUE, bg),
-                         'red': s.c.getpair(curses.COLOR_RED, bg),
-                         'purple': s.c.getpair(curses.COLOR_MAGENTA, bg),
-                         'cyan': s.c.getpair(curses.COLOR_CYAN, bg),
-                         'green': s.c.getpair(curses.COLOR_GREEN, bg),
-                         'orange': s.c.getpair(curses.COLOR_YELLOW, bg),
-                         'yellow': curses.A_BOLD | s.c.getpair(curses.COLOR_YELLOW, bg),
-                         'pink': curses.A_BOLD | s.c.getpair(curses.COLOR_RED, bg)}
-        #s.setcolor('gray')
-        s.setcolor('black')
-
-    def setcolor(self, color):
-        self.color = self.colormap[color]
-        self.colorname = color
-        self.display()
-
-    def display(self):
-        def lockedstuff():
-            if self.getcolor() == 'black':
-                self.window.addstr(self.y, self.x, ' ', self.color)
-            else:
-                self.window.addstr(self.y, self.x, self.ui.config.getdefault("ui.Curses.Blinkenlights", "statuschar", '.'), self.color)
-            self.c.stdscr.move(self.c.height - 1, self.c.width - 1)
-            self.window.refresh()
-        self.c.locked(lockedstuff)
-
-    def getcolor(self):
-        return self.colorname
-
-    def getcolorpair(self):
-        return self.color
-
-    def update(self, window, y, x):
-        self.window = window
-        self.y = y
-        self.x = x
-        self.display()
-
-    def setthread(self, newthread):
-        self.setcolor('black')
-        #if newthread:
-        #    self.setcolor('gray')
-        #else:
-        #    self.setcolor('black')
-
-class InputHandler:
-    def __init__(s, util):
-        s.c = util
-        s.bgchar = None
-        s.inputlock = Lock()
-        s.lockheld = 0
-        s.statuslock = Lock()
-        s.startup = Event()
-        s.startthread()
-
-    def startthread(s):
-        s.thread = threadutil.ExitNotifyThread(target = s.bgreaderloop,
-                                               name = "InputHandler loop")
-        s.thread.setDaemon(1)
-        s.thread.start()
-
-    def bgreaderloop(s):
-        while 1:
-            s.statuslock.acquire()
-            if s.lockheld or s.bgchar == None:
-                s.statuslock.release()
-                s.startup.wait()
-            else:
-                s.statuslock.release()
-                ch = s.c.stdscr.getch()
-                s.statuslock.acquire()
-                try:
-                    if s.lockheld or s.bgchar == None:
-                        curses.ungetch(ch)
-                    else:
-                        s.bgchar(ch)
-                finally:
-                    s.statuslock.release()
-
-    def set_bgchar(s, callback):
-        """Sets a "background" character handler.  If a key is pressed
-        while not doing anything else, it will be passed to this handler.
-
-        callback is a function taking a single arg -- the char pressed.
-
-        If callback is None, clears the request."""
-        s.statuslock.acquire()
-        oldhandler = s.bgchar
-        newhandler = callback
-        s.bgchar = callback
-
-        if oldhandler and not newhandler:
-            pass
-        if newhandler and not oldhandler:
-            s.startup.set()
-            
-        s.statuslock.release()
-
-    def input_acquire(s):
-        """Call this method when you want exclusive input control.
-        Make sure to call input_release afterwards!
-        """
-
-        s.inputlock.acquire()
-        s.statuslock.acquire()
-        s.lockheld = 1
-        s.statuslock.release()
-
-    def input_release(s):
-        """Call this method when you are done getting input."""
-        s.statuslock.acquire()
-        s.lockheld = 0
-        s.statuslock.release()
-        s.inputlock.release()
-        s.startup.set()
-        
-class Blinkenlights(BlinkenBase, UIBase):
-    def init_banner(s):
-        s.af = {}
-        s.aflock = Lock()
-        s.c = CursesUtil()
-        s.text = []
-        BlinkenBase.init_banner(s)
-        s.setupwindows()
-        s.inputhandler = InputHandler(s.c)
-        s.gettf().setcolor('red')
-        s._msg(version.banner)
-        s.inputhandler.set_bgchar(s.keypress)
-        signal.signal(signal.SIGWINCH, s.resizehandler)
-        s.resizelock = Lock()
-        s.resizecount = 0
-
-    def resizehandler(s, signum, frame):
-        s.resizeterm()
-
-    def resizeterm(s, dosleep = 1):
-        if not s.resizelock.acquire(0):
-            s.resizecount += 1
-            return
-        signal.signal(signal.SIGWINCH, signal.SIG_IGN)
-        s.aflock.acquire()
-        s.c.lock()
-        s.resizecount += 1
-        while s.resizecount:
-            s.c.reset()
-            s.setupwindows()
-            s.resizecount -= 1
-        s.c.unlock()
-        s.aflock.release()
-        s.resizelock.release()
-        signal.signal(signal.SIGWINCH, s.resizehandler)
-        if dosleep:
-            time.sleep(1)
-            s.resizeterm(0)
-
-    def isusable(s):
-        # Not a terminal?  Can't use curses.
-        if not sys.stdout.isatty() and sys.stdin.isatty():
-            return 0
-
-        # No TERM specified?  Can't use curses.
-        try:
-            if not len(os.environ['TERM']):
-                return 0
-        except: return 0
-
-        # ncurses doesn't want to start?  Can't use curses.
-        # This test is nasty because initscr() actually EXITS on error.
-        # grr.
-
-        pid = os.fork()
-        if pid:
-            # parent
-            return not os.WEXITSTATUS(os.waitpid(pid, 0)[1])
-        else:
-            # child
-            curses.initscr()
-            curses.endwin()
-            # If we didn't die by here, indicate success.
-            sys.exit(0)
-
-    def keypress(s, key):
-        if key < 1 or key > 255:
-            return
-        
-        if chr(key) == 'q':
-            # Request to quit.
-            s.terminate()
-        
-        try:
-            index = acctkeys.index(chr(key))
-        except ValueError:
-            # Key not a valid one: exit.
-            return
-
-        if index >= len(s.hotkeys):
-            # Not in our list of valid hotkeys.
-            return
-
-        # Trying to end sleep somewhere.
-
-        s.getaccountframe(s.hotkeys[index]).syncnow()
-
-    def getpass(s, accountname, config, errmsg = None):
-        s.inputhandler.input_acquire()
-
-        # See comment on _msg for info on why both locks are obtained.
-        
-        s.tflock.acquire()
-        s.c.lock()
-        try:
-            s.gettf().setcolor('white')
-            s._addline(" *** Input Required", s.gettf().getcolorpair())
-            s._addline(" *** Please enter password for account %s: " % accountname,
-                   s.gettf().getcolorpair())
-            s.logwindow.refresh()
-            password = s.logwindow.getstr()
-        finally:
-            s.tflock.release()
-            s.c.unlock()
-            s.inputhandler.input_release()
-        return password
-
-    def setupwindows(s):
-        s.c.lock()
-        try:
-            s.bannerwindow = curses.newwin(1, s.c.width, 0, 0)
-            s.setupwindow_drawbanner()
-            s.logheight = s.c.height - 1 - len(s.af.keys())
-            s.logwindow = curses.newwin(s.logheight, s.c.width, 1, 0)
-            s.logwindow.idlok(1)
-            s.logwindow.scrollok(1)
-            s.logwindow.move(s.logheight - 1, 0)
-            s.setupwindow_drawlog()
-            accounts = s.af.keys()
-            accounts.sort()
-            accounts.reverse()
-
-            pos = s.c.height - 1
-            index = 0
-            s.hotkeys = []
-            for account in accounts:
-                accountwindow = curses.newwin(1, s.c.width, pos, 0)
-                s.af[account].setwindow(accountwindow, acctkeys[index])
-                s.hotkeys.append(account)
-                index += 1
-                pos -= 1
-
-            curses.doupdate()
-        finally:
-            s.c.unlock()
-
-    def setupwindow_drawbanner(s):
-        if s.c.has_color:
-            color = s.c.getpair(curses.COLOR_WHITE, curses.COLOR_BLUE) | \
-                    curses.A_BOLD
-        else:
-            color = curses.A_REVERSE
-        s.bannerwindow.bkgd(' ', color) # Fill background with that color
-        s.bannerwindow.addstr("%s %s" % (version.productname,
-                                         version.versionstr))
-        s.bannerwindow.addstr(0, s.bannerwindow.getmaxyx()[1] - len(version.copyright) - 1,
-                              version.copyright)
-        
-        s.bannerwindow.noutrefresh()
-
-    def setupwindow_drawlog(s):
-        if s.c.has_color:
-            color = s.c.getpair(curses.COLOR_WHITE, curses.COLOR_BLACK)
-        else:
-            color = curses.A_NORMAL
-        s.logwindow.bkgd(' ', color)
-        for line, color in s.text:
-            s.logwindow.addstr("\n" + line, color)
-        s.logwindow.noutrefresh()
-
-    def getaccountframe(s, accountname = None):
-        if accountname == None:
-            accountname = s.getthreadaccount()
-        s.aflock.acquire()
-        try:
-            if accountname in s.af:
-                return s.af[accountname]
-
-            # New one.
-            s.af[accountname] = CursesAccountFrame(s.c, accountname, s)
-            s.c.lock()
-            try:
-                s.c.reset()
-                s.setupwindows()
-            finally:
-                s.c.unlock()
-        finally:
-            s.aflock.release()
-        return s.af[accountname]
-
-
-    def _display(s, msg, color = None):
-        if "\n" in msg:
-            for thisline in msg.split("\n"):
-                s._msg(thisline)
-            return
-
-        # We must acquire both locks.  Otherwise, deadlock can result.
-        # This can happen if one thread calls _msg (locking curses, then
-        # tf) and another tries to set the color (locking tf, then curses)
-        #
-        # By locking both up-front here, in this order, we prevent deadlock.
-        
-        s.tflock.acquire()
-        s.c.lock()
-        try:
-            if not s.c.isactive():
-                # For dumping out exceptions and stuff.
-                print msg
-                return
-            if color:
-                s.gettf().setcolor(color)
-            elif s.gettf().getcolor() == 'black':
-                s.gettf().setcolor('gray')
-            s._addline(msg, s.gettf().getcolorpair())
-            s.logwindow.refresh()
-        finally:
-            s.c.unlock()
-            s.tflock.release()
-
-    def _addline(s, msg, color):
-        s.c.lock()
-        try:
-            s.logwindow.addstr("\n" + msg, color)
-            s.text.append((msg, color))
-            while len(s.text) > s.logheight:
-                s.text = s.text[1:]
-        finally:
-            s.c.unlock()
-
-    def terminate(s, exitstatus = 0, errortitle = None, errormsg = None):
-        s.c.stop()
-        UIBase.terminate(s, exitstatus = exitstatus, errortitle = errortitle, errormsg = errormsg)
-
-    def threadException(s, thread):
-        s.c.stop()
-        UIBase.threadException(s, thread)
-
-    def mainException(s):
-        s.c.stop()
-        UIBase.mainException(s)
-
-    def sleep(s, sleepsecs, siglistener):
-        s.gettf().setcolor('red')
-        s._msg("Next sync in %d:%02d" % (sleepsecs / 60, sleepsecs % 60))
-        return BlinkenBase.sleep(s, sleepsecs, siglistener)
-            
-if __name__ == '__main__':
-    x = Blinkenlights(None)
-    x.init_banner()
-    import time
-    time.sleep(5)
-    x.c.stop()
-    fgs = {'black': curses.COLOR_BLACK, 'red': curses.COLOR_RED,
-           'green': curses.COLOR_GREEN, 'yellow': curses.COLOR_YELLOW,
-           'blue': curses.COLOR_BLUE, 'magenta': curses.COLOR_MAGENTA,
-           'cyan': curses.COLOR_CYAN, 'white': curses.COLOR_WHITE}
-    
-    x = CursesUtil()
-    win1 = curses.newwin(x.height, x.width / 4 - 1, 0, 0)
-    win1.addstr("Black/normal\n")
-    for name, fg in fgs.items():
-        win1.addstr("%s\n" % name, x.getpair(fg, curses.COLOR_BLACK))
-    win2 = curses.newwin(x.height, x.width / 4 - 1, 0, win1.getmaxyx()[1])
-    win2.addstr("Blue/normal\n")
-    for name, fg in fgs.items():
-        win2.addstr("%s\n" % name, x.getpair(fg, curses.COLOR_BLUE))
-    win3 = curses.newwin(x.height, x.width / 4 - 1, 0, win1.getmaxyx()[1] +
-                         win2.getmaxyx()[1])
-    win3.addstr("Black/bright\n")
-    for name, fg in fgs.items():
-        win3.addstr("%s\n" % name, x.getpair(fg, curses.COLOR_BLACK) | \
-                    curses.A_BOLD)
-    win4 = curses.newwin(x.height, x.width / 4 - 1, 0, win1.getmaxyx()[1] * 3)
-    win4.addstr("Blue/bright\n")
-    for name, fg in fgs.items():
-        win4.addstr("%s\n" % name, x.getpair(fg, curses.COLOR_BLUE) | \
-                    curses.A_BOLD)
-        
-        
-    win1.refresh()
-    win2.refresh()
-    win3.refresh()
-    win4.refresh()
-    x.stdscr.refresh()
-    import time
-    time.sleep(5)
-    x.stop()
-    print x.has_color
-    print x.height
-    print x.width
-
diff --git a/offlineimap/ui/TTY.py b/offlineimap/ui/TTY.py
index 4c574b2..9c11dda 100644
--- a/offlineimap/ui/TTY.py
+++ b/offlineimap/ui/TTY.py
@@ -16,7 +16,7 @@
 #    along with this program; if not, write to the Free Software
 #    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 
-from UIBase import UIBase
+from offlineimap.ui import UIBase
 from getpass import getpass
 import select, sys
 from threading import *
diff --git a/offlineimap/ui/__init__.py b/offlineimap/ui/__init__.py
index 0206ab4..720483c 100644
--- a/offlineimap/ui/__init__.py
+++ b/offlineimap/ui/__init__.py
@@ -1,6 +1,5 @@
-# UI module directory
-# Copyright (C) 2002 John Goerzen
-# <jgoerzen at complete.org>
+# UI module
+# Copyright (C) 2010 Sebastian Spaeth <Sebastian at SSpaeth.de>
 #
 #    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
@@ -16,23 +15,11 @@
 #    along with this program; if not, write to the Free Software
 #    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 
+from offlineimap.ui.UIBase import getglobalui, setglobalui
+from offlineimap.ui.UIBase import UIBase
+from offlineimap.ui import TTY, Noninteractive, Machine
 
-import UIBase, Blinkenlights
-try:
-    import TTY
-except ImportError:
-    pass
-
-try:
-    import curses
-except ImportError:
-    pass
-else:
-    import Curses
-
-import Noninteractive
-import Machine
-
-# Must be last
-import detector
-
+UI_LIST = {'TTY': TTY.TTYUI,
+           'Basic': Noninteractive.Basic,
+           'Quiet': Noninteractive.Quiet, 
+           'Machine': Machine.MachineUI}
diff --git a/offlineimap/ui/detector.py b/offlineimap/ui/detector.py
deleted file mode 100644
index 4ec7503..0000000
--- a/offlineimap/ui/detector.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# UI base class
-# Copyright (C) 2002 John Goerzen
-# <jgoerzen at 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
-
-import offlineimap.ui
-import sys
-
-DEFAULT_UI_LIST = ('Curses.Blinkenlights', 'TTY.TTYUI',
-                   'Noninteractive.Basic', 'Noninteractive.Quiet',
-                   'Machine.MachineUI')
-
-def findUI(config, chosenUI=None):
-    uistrlist = list(DEFAULT_UI_LIST)
-    namespace={}
-    for ui in dir(offlineimap.ui):
-        if ui.startswith('_') or ui in ('detector', 'UIBase'):
-            continue
-        namespace[ui]=getattr(offlineimap.ui, ui)
-
-    if chosenUI is not None:
-        uistrlist = [chosenUI]
-    elif config.has_option("general", "ui"):
-        uistrlist = config.get("general", "ui").replace(" ", "").split(",")
-
-    for uistr in uistrlist:
-        uimod = getUImod(uistr, config.getlocaleval(), namespace)
-        if uimod:
-            uiinstance = uimod(config)
-            if uiinstance.isusable():
-                return uiinstance
-    sys.stderr.write("ERROR: No UIs were found usable!\n")
-    sys.exit(200)
-    
-def getUImod(uistr, localeval, namespace):
-    try:
-        uimod = localeval.eval(uistr, namespace)
-    except (AttributeError, NameError), e:
-        #raise
-        return None
-    return uimod
-- 
1.7.1




More information about the OfflineIMAP-project mailing list