Complete.Org: Mailing Lists: Archives: offlineimap: July 2009:
[PATCH] allow dynamic addition of ui plugins
Home

[PATCH] allow dynamic addition of ui plugins

[Top] [All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
Subject: [PATCH] allow dynamic addition of ui plugins
From: =3D?utf-8?q?Christoph=3D20H=3DC3=3DB6ger?=3D <choeger@xxxxx-berlin.=
Date: Fri, 24 Jul 2009 14:21:01 +0200

Hi,

this is more a RFC to my last mail. I wanted to be able to add UI plugins=
 dynamically mostly because they can have dependencies that offlineimap i=
tself should not have (e.g. GTK or Curses). So I moved all ui plugins to =
the offlineimap.ui.plugins package. The invocation with this patch is off=
lineimap -u <MODULENAME> and not offlineimap -u <MODULENAME>.<CLASSNAME> =
as the concret class is defined by the plugins getUIClass() method.
Although this patch is rather invasive it allows to easily create and dis=
tribute new UI modules (think of the Gnome branch) without changing any f=
ile in the rest of the distribution.

Signed-off-by: Christoph H=F6ger <choeger@xxxxxxxxxxxxxxx>
---
 offlineimap/ui/Blinkenlights.py  |  147 ----------
 offlineimap/ui/Curses.py         |  593 --------------------------------=
------
 offlineimap/ui/Machine.py        |  179 ------------
 offlineimap/ui/Noninteractive.py |   51 ----
 offlineimap/ui/TTY.py            |   60 ----
 offlineimap/ui/__init__.py       |   19 +--
 offlineimap/ui/detector.py       |   23 +-
 setup.py                         |    3 +-
 8 files changed, 12 insertions(+), 1063 deletions(-)
 delete mode 100644 offlineimap/ui/Blinkenlights.py
 delete mode 100644 offlineimap/ui/Curses.py
 delete mode 100644 offlineimap/ui/Machine.py
 delete mode 100644 offlineimap/ui/Noninteractive.py
 delete mode 100644 offlineimap/ui/TTY.py

diff --git a/offlineimap/ui/Blinkenlights.py b/offlineimap/ui/Blinkenligh=
ts.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@xxxxxxxxxxxx>
-#
-#    This program is free software; you can redistribute it and/or modif=
y
-#    it under the terms of the GNU General Public License as published b=
y
-#    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-13=
01 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, destlis=
t)
-
-    def deletingflags(s, uidlist, flags, destlist):
-        s.gettf().setcolor('pink')
-        s.__class__.__bases__[-1].deletingflags(s, uidlist, flags, destl=
ist)
-
-    def warn(s, msg, minor =3D 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 =3D {}
-        s.threadframes =3D {}
-        s.tflock =3D MultiLock()
-
-    def threadExited(s, thread):
-        threadid =3D thread.threadid
-        accountname =3D s.getthreadaccount(thread)
-        s.tflock.acquire()
-        try:
-            if threadid in s.threadframes[accountname]:
-                tf =3D 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 =3D thread.get_ident()
-        accountname =3D s.getthreadaccount()
-
-        s.tflock.acquire()
-
-        try:
-            if not accountname in s.threadframes:
-                s.threadframes[accountname] =3D {}
-               =20
-            if threadid in s.threadframes[accountname]:
-                return s.threadframes[accountname][threadid]
-
-            if not accountname in s.availablethreadframes:
-                s.availablethreadframes[accountname] =3D []
-
-            if len(s.availablethreadframes[accountname]):
-                tf =3D s.availablethreadframes[accountname].pop(0)
-                tf.setthread(currentThread())
-            else:
-                tf =3D s.getaccountframe().getnewthreadframe()
-            s.threadframes[accountname][threadid] =3D tf
-            return tf
-        finally:
-            s.tflock.release()
-
-    def callhook(s, msg):
-        s.gettf().setcolor('white')
-        s.__class__.__bases__[-1].callhook(s, msg)
-           =20
-    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() =3D=3D 'black':
-            s.gettf().setcolor('red')
-        else:
-            s.gettf().setcolor('black')
-        return s.getaccountframe().sleeping(sleepsecs, remainingsecs)
-
-   =20
diff --git a/offlineimap/ui/Curses.py b/offlineimap/ui/Curses.py
deleted file mode 100644
index 8eb709f..0000000
--- a/offlineimap/ui/Curses.py
+++ /dev/null
@@ -1,593 +0,0 @@
-# Curses-based interfaces
-# Copyright (C) 2003 John Goerzen
-# <jgoerzen@xxxxxxxxxxxx>
-#
-#    This program is free software; you can redistribute it and/or modif=
y
-#    it under the terms of the GNU General Public License as published b=
y
-#    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-13=
01 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 =3D '1234567890abcdefghijklmnoprstuvwxyzABCDEFGHIJKLMNOPQRSTUVW=
XYZ-=3D;/.,'
-
-class CursesUtil:
-    def __init__(self):
-        self.pairlock =3D Lock()
-        self.iolock =3D MultiLock()
-        self.start()
-
-    def initpairs(self):
-        self.pairlock.acquire()
-        try:
-            self.pairs =3D {self._getpairindex(curses.COLOR_WHITE,
-                                             curses.COLOR_BLACK): 0}
-            self.nextpair =3D 1
-        finally:
-            self.pairlock.release()
-
-    def lock(self):
-        self.iolock.acquire()
-
-    def unlock(self):
-        self.iolock.release()
-       =20
-    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 =3D 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] =3D self.nextpair
-                curses.init_pair(self.nextpair, fg, bg)
-                self.nextpair +=3D 1
-                return curses.color_pair(self.nextpair - 1)
-        finally:
-            self.pairlock.release()
-   =20
-    def start(self):
-        self.stdscr =3D curses.initscr()
-        curses.noecho()
-        curses.cbreak()
-        self.stdscr.keypad(1)
-        try:
-            curses.start_color()
-            self.has_color =3D curses.has_colors()
-        except:
-            self.has_color =3D 0
-
-        self.oldcursor =3D None
-        try:
-            self.oldcursor =3D curses.curs_set(0)
-        except:
-            pass
-       =20
-        self.stdscr.clear()
-        self.stdscr.refresh()
-        (self.height, self.width) =3D 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 !=3D 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):
-        self.stop()
-        self.start()
-
-class CursesAccountFrame:
-    def __init__(s, master, accountname, ui):
-        s.c =3D master
-        s.children =3D []
-        s.accountname =3D accountname
-        s.ui =3D ui
-
-    def drawleadstr(s, secs =3D None):
-        if secs =3D=3D None:
-            acctstr =3D '%s: [active] %13.13s: ' % (s.key, s.accountname=
)
-        else:
-            acctstr =3D '%s: [%3d:%02d] %13.13s: ' % (s.key,
-                                                    secs / 60, secs % 60=
,
-                                                    s.accountname)
-        s.c.locked(s.window.addstr, 0, 0, acctstr)
-        s.location =3D len(acctstr)
-
-    def setwindow(s, window, key):
-        s.window =3D window
-        s.key =3D key
-        s.drawleadstr()
-        for child in s.children:
-            child.update(window, 0, s.location)
-            s.location +=3D 1
-
-    def getnewthreadframe(s):
-        tf =3D CursesThreadFrame(s.c, s.ui, s.window, 0, s.location)
-        s.location +=3D 1
-        s.children.append(tf)
-        return tf
-
-    def startsleep(s, sleepsecs):
-        s.sleeping_abort =3D 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 =3D 1
-
-class CursesThreadFrame:
-    def __init__(s, master, ui, window, y, x):
-        """master should be a CursesUtil object."""
-        s.c =3D master
-        s.ui =3D ui
-        s.window =3D window
-        s.x =3D x
-        s.y =3D y
-        s.colors =3D []
-        bg =3D curses.COLOR_BLACK
-        s.colormap =3D {'black': s.c.getpair(curses.COLOR_BLACK, bg),
-                         'gray': s.c.getpair(curses.COLOR_WHITE, bg),
-                         'white': curses.A_BOLD | s.c.getpair(curses.COL=
OR_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.CO=
LOR_YELLOW, bg),
-                         'pink': curses.A_BOLD | s.c.getpair(curses.COLO=
R_RED, bg)}
-        #s.setcolor('gray')
-        s.setcolor('black')
-
-    def setcolor(self, color):
-        self.color =3D self.colormap[color]
-        self.colorname =3D color
-        self.display()
-
-    def display(self):
-        def lockedstuff():
-            if self.getcolor() =3D=3D 'black':
-                self.window.addstr(self.y, self.x, ' ', self.color)
-            else:
-                self.window.addstr(self.y, self.x, self.ui.config.getdef=
ault("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 =3D window
-        self.y =3D y
-        self.x =3D 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 =3D util
-        s.bgchar =3D None
-        s.inputlock =3D Lock()
-        s.lockheld =3D 0
-        s.statuslock =3D Lock()
-        s.startup =3D Event()
-        s.startthread()
-
-    def startthread(s):
-        s.thread =3D threadutil.ExitNotifyThread(target =3D s.bgreaderlo=
op,
-                                               name =3D "InputHandler lo=
op")
-        s.thread.setDaemon(1)
-        s.thread.start()
-
-    def bgreaderloop(s):
-        while 1:
-            s.statuslock.acquire()
-            if s.lockheld or s.bgchar =3D=3D None:
-                s.statuslock.release()
-                s.startup.wait()
-            else:
-                s.statuslock.release()
-                ch =3D s.c.stdscr.getch()
-                s.statuslock.acquire()
-                try:
-                    if s.lockheld or s.bgchar =3D=3D 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 =3D s.bgchar
-        newhandler =3D callback
-        s.bgchar =3D callback
-
-        if oldhandler and not newhandler:
-            pass
-        if newhandler and not oldhandler:
-            s.startup.set()
-           =20
-        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 =3D 1
-        s.statuslock.release()
-
-    def input_release(s):
-        """Call this method when you are done getting input."""
-        s.statuslock.acquire()
-        s.lockheld =3D 0
-        s.statuslock.release()
-        s.inputlock.release()
-        s.startup.set()
-       =20
-class Blinkenlights(BlinkenBase, UIBase):
-    def init_banner(s):
-        s.af =3D {}
-        s.aflock =3D Lock()
-        s.c =3D CursesUtil()
-        s.text =3D []
-        BlinkenBase.init_banner(s)
-        s.setupwindows()
-        s.inputhandler =3D 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 =3D Lock()
-        s.resizecount =3D 0
-
-    def resizehandler(s, signum, frame):
-        s.resizeterm()
-
-    def resizeterm(s, dosleep =3D 1):
-        if not s.resizelock.acquire(0):
-            s.resizecount +=3D 1
-            return
-        signal.signal(signal.SIGWINCH, signal.SIG_IGN)
-        s.aflock.acquire()
-        s.c.lock()
-        s.resizecount +=3D 1
-        while s.resizecount:
-            s.c.reset()
-            s.setupwindows()
-            s.resizecount -=3D 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 =3D 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
-       =20
-        if chr(key) =3D=3D 'q':
-            # Request to quit.
-            s.terminate()
-       =20
-        try:
-            index =3D acctkeys.index(chr(key))
-        except ValueError:
-            # Key not a valid one: exit.
-            return
-
-        if index >=3D 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 =3D None):
-        s.inputhandler.input_acquire()
-
-        # See comment on _msg for info on why both locks are obtained.
-       =20
-        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: " % a=
ccountname,
-                   s.gettf().getcolorpair())
-            s.logwindow.refresh()
-            password =3D 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 =3D curses.newwin(1, s.c.width, 0, 0)
-            s.setupwindow_drawbanner()
-            s.logheight =3D s.c.height - 1 - len(s.af.keys())
-            s.logwindow =3D 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 =3D s.af.keys()
-            accounts.sort()
-            accounts.reverse()
-
-            pos =3D s.c.height - 1
-            index =3D 0
-            s.hotkeys =3D []
-            for account in accounts:
-                accountwindow =3D curses.newwin(1, s.c.width, pos, 0)
-                s.af[account].setwindow(accountwindow, acctkeys[index])
-                s.hotkeys.append(account)
-                index +=3D 1
-                pos -=3D 1
-
-            curses.doupdate()
-        finally:
-            s.c.unlock()
-
-    def setupwindow_drawbanner(s):
-        if s.c.has_color:
-            color =3D s.c.getpair(curses.COLOR_WHITE, curses.COLOR_BLUE)=
 | \
-                    curses.A_BOLD
-        else:
-            color =3D curses.A_REVERSE
-        s.bannerwindow.bkgd(' ', color) # Fill background with that colo=
r
-        s.bannerwindow.addstr("%s %s" % (version.productname,
-                                         version.versionstr))
-        s.bannerwindow.addstr(0, s.bannerwindow.getmaxyx()[1] - len(vers=
ion.copyright) - 1,
-                              version.copyright)
-       =20
-        s.bannerwindow.noutrefresh()
-
-    def setupwindow_drawlog(s):
-        if s.c.has_color:
-            color =3D s.c.getpair(curses.COLOR_WHITE, curses.COLOR_BLACK=
)
-        else:
-            color =3D 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 =3D None):
-        if accountname =3D=3D None:
-            accountname =3D s.getthreadaccount()
-        s.aflock.acquire()
-        try:
-            if accountname in s.af:
-                return s.af[accountname]
-
-            # New one.
-            s.af[accountname] =3D 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 =3D 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 curse=
s)
-        #
-        # By locking both up-front here, in this order, we prevent deadl=
ock.
-       =20
-        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() =3D=3D '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 =3D s.text[1:]
-        finally:
-            s.c.unlock()
-
-    def terminate(s, exitstatus =3D 0, errortitle =3D None, errormsg =3D=
 None):
-        s.c.stop()
-        UIBase.terminate(s, exitstatus =3D exitstatus, errortitle =3D er=
rortitle, errormsg =3D 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)
-           =20
-if __name__ =3D=3D '__main__':
-    x =3D Blinkenlights(None)
-    x.init_banner()
-    import time
-    time.sleep(5)
-    x.c.stop()
-    fgs =3D {'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}
-   =20
-    x =3D CursesUtil()
-    win1 =3D 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 =3D 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 =3D 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 =3D 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)
-       =20
-       =20
-    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/Machine.py b/offlineimap/ui/Machine.py
deleted file mode 100644
index 0a07e3e..0000000
--- a/offlineimap/ui/Machine.py
+++ /dev/null
@@ -1,179 +0,0 @@
-# Copyright (C) 2007 John Goerzen
-# <jgoerzen@xxxxxxxxxxxx>
-#
-#    This program is free software; you can redistribute it and/or modif=
y
-#    it under the terms of the GNU General Public License as published b=
y
-#    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-13=
01 USA
-
-import offlineimap.version
-import urllib, sys, re, time, traceback, threading, thread
-from UIBase import UIBase
-from threading import *
-
-protocol =3D '6.0.0'
-
-class MachineUI(UIBase):
-    def __init__(s, config, verbose =3D 0):
-        UIBase.__init__(s, config, verbose)
-        s.safechars=3D" ;,./-_=3D+()[]"
-        s.iswaiting =3D 0
-        s.outputlock =3D Lock()
-        s._printData('__init__', protocol)
-
-    def isusable(s):
-        return True
-
-    def _printData(s, command, data, dolock =3D True):
-        s._printDataOut('msg', command, data, dolock)
-
-    def _printWarn(s, command, data, dolock =3D True):
-        s._printDataOut('warn', command, data, dolock)
-
-    def _printDataOut(s, datatype, command, data, dolock =3D True):
-        if dolock:
-            s.outputlock.acquire()
-        try:
-            print "%s:%s:%s:%s" % \
-                    (datatype,
-                     urllib.quote(command, s.safechars),=20
-                     urllib.quote(currentThread().getName(), s.safechars=
),
-                     urllib.quote(data, s.safechars))
-            sys.stdout.flush()
-        finally:
-            if dolock:
-                s.outputlock.release()
-
-    def _display(s, msg):
-        s._printData('_display', msg)
-
-    def warn(s, msg, minor):
-        s._printData('warn', '%s\n%d' % (msg, int(minor)))
-
-    def registerthread(s, account):
-        UIBase.registerthread(s, account)
-        s._printData('registerthread', account)
-
-    def unregisterthread(s, thread):
-        UIBase.unregisterthread(s, thread)
-        s._printData('unregisterthread', thread.getName())
-
-    def debugging(s, debugtype):
-        s._printData('debugging', debugtype)
-
-    def acct(s, accountname):
-        s._printData('acct', accountname)
-
-    def acctdone(s, accountname):
-        s._printData('acctdone', accountname)
-
-    def validityproblem(s, folder):
-        s._printData('validityproblem', "%s\n%s\n%s\n%s" % \
-                (folder.getname(), folder.getrepository().getname(),
-                 folder.getsaveduidvalidity(), folder.getuidvalidity()))
-
-    def connecting(s, hostname, port):
-        s._printData('connecting', "%s\n%s" % (hostname, str(port)))
-
-    def syncfolders(s, srcrepos, destrepos):
-        s._printData('syncfolders', "%s\n%s" % (s.getnicename(srcrepos),=
=20
-                                                s.getnicename(destrepos)=
))
-
-    def syncingfolder(s, srcrepos, srcfolder, destrepos, destfolder):
-        s._printData('syncingfolder', "%s\n%s\n%s\n%s\n" % \
-                (s.getnicename(srcrepos), srcfolder.getname(),
-                 s.getnicename(destrepos), destfolder.getname()))
-
-    def loadmessagelist(s, repos, folder):
-        s._printData('loadmessagelist', "%s\n%s" % (s.getnicename(repos)=
,
-                                                    folder.getvisiblenam=
e()))
-
-    def messagelistloaded(s, repos, folder, count):
-        s._printData('messagelistloaded', "%s\n%s\n%d" % \
-                (s.getnicename(repos), folder.getname(), count))
-
-    def syncingmessages(s, sr, sf, dr, df):
-        s._printData('syncingmessages', "%s\n%s\n%s\n%s\n" % \
-                (s.getnicename(sr), sf.getname(), s.getnicename(dr),
-                 df.getname()))
-
-    def copyingmessage(s, uid, src, destlist):
-        ds =3D s.folderlist(destlist)
-        s._printData('copyingmessage', "%d\n%s\n%s\n%s"  % \
-                (uid, s.getnicename(src), src.getname(), ds))
-       =20
-    def folderlist(s, list):
-        return ("\f".join(["%s\t%s" % (s.getnicename(x), x.getname()) fo=
r x in list]))
-
-    def deletingmessage(s, uid, destlist):
-        s.deletingmessages(s, [uid], destlist)
-
-    def uidlist(s, list):
-        return ("\f".join([str(u) for u in list]))
-
-    def deletingmessages(s, uidlist, destlist):
-        ds =3D s.folderlist(destlist)
-        s._printData('deletingmessages', "%s\n%s" % (s.uidlist(uidlist),=
 ds))
-
-    def addingflags(s, uidlist, flags, destlist):
-        ds =3D s.folderlist(destlist)
-        s._printData("addingflags", "%s\n%s\n%s" % (s.uidlist(uidlist),
-                                                    "\f".join(flags),
-                                                    ds))
-
-    def deletingflags(s, uidlist, flags, destlist):
-        ds =3D s.folderlist(destlist)
-        s._printData('deletingflags', "%s\n%s\n%s" % (s.uidlist(uidlist)=
,
-                                                      "\f".join(flags),
-                                                      ds))
-
-    def threadException(s, thread):
-        print s.getThreadExceptionString(thread)
-        s._printData('threadException', "%s\n%s" % \
-                     (thread.getName(), s.getThreadExceptionString(threa=
d)))
-        s.delThreadDebugLog(thread)
-        s.terminate(100)
-
-    def terminate(s, exitstatus =3D 0, errortitle =3D '', errormsg =3D '=
'):
-        s._printData('terminate', "%d\n%s\n%s" % (exitstatus, errortitle=
, errormsg))
-        sys.exit(exitstatus)
-
-    def mainException(s):
-        s._printData('mainException', s.getMainExceptionString())
-
-    def threadExited(s, thread):
-        s._printData('threadExited', thread.getName())
-        UIBase.threadExited(s, thread)
-
-    def sleeping(s, sleepsecs, remainingsecs):
-        s._printData('sleeping', "%d\n%d" % (sleepsecs, remainingsecs))
-        if sleepsecs > 0:
-            time.sleep(sleepsecs)
-        return 0
-
-
-    def getpass(s, accountname, config, errmsg =3D None):
-        s.outputlock.acquire()
-        try:
-            if errmsg:
-                s._printData('getpasserror', "%s\n%s" % (accountname, er=
rmsg),
-                             False)
-            s._printData('getpass', accountname, False)
-            return (sys.stdin.readline()[:-1])
-        finally:
-            s.outputlock.release()
-
-    def init_banner(s):
-        s._printData('initbanner', offlineimap.version.banner)
-
-    def callhook(s, msg):
-        s._printData('callhook', msg)
diff --git a/offlineimap/ui/Noninteractive.py b/offlineimap/ui/Noninterac=
tive.py
deleted file mode 100644
index 9cd5eca..0000000
--- a/offlineimap/ui/Noninteractive.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# Noninteractive UI
-# Copyright (C) 2002 John Goerzen
-# <jgoerzen@xxxxxxxxxxxx>
-#
-#    This program is free software; you can redistribute it and/or modif=
y
-#    it under the terms of the GNU General Public License as published b=
y
-#    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-13=
01 USA
-
-import sys, time
-from UIBase import UIBase
-
-class Basic(UIBase):
-    def getpass(s, accountname, config, errmsg =3D None):
-        raise NotImplementedError, "Prompting for a password is not supp=
orted in noninteractive mode."
-
-    def _display(s, msg):
-        print msg
-        sys.stdout.flush()
-
-    def warn(s, msg, minor =3D 0):
-        warntxt =3D 'WARNING'
-        if minor:
-            warntxt =3D 'warning'
-        sys.stderr.write(warntxt + ": " + str(msg) + "\n")
-
-    def sleep(s, sleepsecs, siglistener):
-        if s.verbose >=3D 0:
-            s._msg("Sleeping for %d:%02d" % (sleepsecs / 60, sleepsecs %=
 60))
-        return UIBase.sleep(s, sleepsecs, siglistener)
-
-    def sleeping(s, sleepsecs, remainingsecs):
-        if sleepsecs > 0:
-            time.sleep(sleepsecs)
-        return 0
-
-    def locked(s):
-        s.warn("Another OfflineIMAP is running with the same metadatadir=
; exiting.")
-
-class Quiet(Basic):
-    def __init__(s, config, verbose =3D -1):
-        Basic.__init__(s, config, verbose)
diff --git a/offlineimap/ui/TTY.py b/offlineimap/ui/TTY.py
deleted file mode 100644
index 99c46d4..0000000
--- a/offlineimap/ui/TTY.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# TTY UI
-# Copyright (C) 2002 John Goerzen
-# <jgoerzen@xxxxxxxxxxxx>
-#
-#    This program is free software; you can redistribute it and/or modif=
y
-#    it under the terms of the GNU General Public License as published b=
y
-#    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-13=
01 USA
-
-from UIBase import UIBase
-from getpass import getpass
-import select, sys
-from threading import *
-
-class TTYUI(UIBase):
-    def __init__(s, config, verbose =3D 0):
-        UIBase.__init__(s, config, verbose)
-        s.iswaiting =3D 0
-        s.outputlock =3D Lock()
-
-    def isusable(s):
-        return sys.stdout.isatty() and sys.stdin.isatty()
-       =20
-    def _display(s, msg):
-        s.outputlock.acquire()
-        try:
-            if (currentThread().getName() =3D=3D 'MainThread'):
-                print msg
-            else:
-                print "%s:\n   %s" % (currentThread().getName(), msg)
-            sys.stdout.flush()
-        finally:
-            s.outputlock.release()
-
-    def getpass(s, accountname, config, errmsg =3D None):
-        if errmsg:
-            s._msg("%s: %s" % (accountname, errmsg))
-        s.outputlock.acquire()
-        try:
-            return getpass("%s: Enter password: " % accountname)
-        finally:
-            s.outputlock.release()
-
-    def mainException(s):
-        if isinstance(sys.exc_info()[1], KeyboardInterrupt) and \
-           s.iswaiting:
-            sys.stdout.write("Timer interrupted at user request; program=
 terminating.             \n")
-            s.terminate()
-        else:
-            UIBase.mainException(s)
-
diff --git a/offlineimap/ui/__init__.py b/offlineimap/ui/__init__.py
index 0206ab4..fa08df3 100644
--- a/offlineimap/ui/__init__.py
+++ b/offlineimap/ui/__init__.py
@@ -16,23 +16,8 @@
 #    along with this program; if not, write to the Free Software
 #    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-13=
01 USA
=20
-
-import UIBase, Blinkenlights
-try:
-    import TTY
-except ImportError:
-    pass
-
-try:
-    import curses
-except ImportError:
-    pass
-else:
-    import Curses
-
-import Noninteractive
-import Machine
-
+import plugins
+from plugins import *
 # Must be last
 import detector
=20
diff --git a/offlineimap/ui/detector.py b/offlineimap/ui/detector.py
index 4ec7503..571bb01 100644
--- a/offlineimap/ui/detector.py
+++ b/offlineimap/ui/detector.py
@@ -19,17 +19,8 @@
 import offlineimap.ui
 import sys
=20
-DEFAULT_UI_LIST =3D ('Curses.Blinkenlights', 'TTY.TTYUI',
-                   'Noninteractive.Basic', 'Noninteractive.Quiet',
-                   'Machine.MachineUI')
-
 def findUI(config, chosenUI=3DNone):
-    uistrlist =3D list(DEFAULT_UI_LIST)
-    namespace=3D{}
-    for ui in dir(offlineimap.ui):
-        if ui.startswith('_') or ui in ('detector', 'UIBase'):
-            continue
-        namespace[ui]=3Dgetattr(offlineimap.ui, ui)
+    uistrlist =3D []
=20
     if chosenUI is not None:
         uistrlist =3D [chosenUI]
@@ -37,7 +28,7 @@ def findUI(config, chosenUI=3DNone):
         uistrlist =3D config.get("general", "ui").replace(" ", "").split=
(",")
=20
     for uistr in uistrlist:
-        uimod =3D getUImod(uistr, config.getlocaleval(), namespace)
+        uimod =3D getUImod(uistr)
         if uimod:
             uiinstance =3D uimod(config)
             if uiinstance.isusable():
@@ -45,10 +36,12 @@ def findUI(config, chosenUI=3DNone):
     sys.stderr.write("ERROR: No UIs were found usable!\n")
     sys.exit(200)
    =20
-def getUImod(uistr, localeval, namespace):
+def getUImod(uistr):
     try:
-        uimod =3D localeval.eval(uistr, namespace)
+        uimod =3D __import__("offlineimap.ui.plugins." + uistr,[],[],[ui=
str])
+        uiClass =3D uimod.getUIClass()
     except (AttributeError, NameError), e:
-        #raise
+        print e
+       #raise
         return None
-    return uimod
+    return uiClass
diff --git a/setup.py b/setup.py
index 92fb1fd..fe3c53d 100644
--- a/setup.py
+++ b/setup.py
@@ -33,7 +33,8 @@ setup(name =3D "offlineimap",
       author_email =3D offlineimap.version.author_email,
       url =3D offlineimap.version.homepage,
       packages =3D ['offlineimap', 'offlineimap.folder',
-                  'offlineimap.repository', 'offlineimap.ui'],
+                  'offlineimap.repository',
+                  'offlineimap.ui','offlineimap.ui.plugins'],
       scripts =3D ['bin/offlineimap'],
       license =3D offlineimap.version.copyright + \
                 ", Licensed under the GPL version 2"
--=20
1.6.2.5




[Prev in Thread] Current Thread [Next in Thread]