[PATCH] furbished dynamic ui plugin selection
[Top] [All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
Here we go again, this time a lot better:
This patch allows ui plugins to be dropped only
inside the ui/plugins folder and work without modifying
any other file. Good for packagers.
* config:
Old config options like "Curses.Blinkenlights" still work
New ui plugins can use Modulename only if they provide a
getUIClass() method, I think thats a compromise
* default ui:
I have made Curses.Blinkenlights the default ui, but
perhaps TTY.TTYUI is a better choice for that.
* testing:
I tested against Blinkenlights and Gnome ui (which is not
yet in the mainline branch)
Signed-off-by: Christoph H=C3=B6ger <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 | 17 -
offlineimap/ui/detector.py | 35 +-
offlineimap/ui/plugins/Blinkenlights.py | 143 ++++++
offlineimap/ui/plugins/Curses.py | 593 +++++++++++++++++++++++
offlineimap/ui/plugins/Gnome.py | 769 ++++++++++++++++++++++++=
++++++
offlineimap/ui/plugins/Machine.py | 177 +++++++
offlineimap/ui/plugins/Noninteractive.py | 51 ++
offlineimap/ui/plugins/TTY.py | 60 +++
offlineimap/ui/plugins/__init__.py | 18 +
14 files changed, 1831 insertions(+), 1062 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
create mode 100644 offlineimap/ui/plugins/Blinkenlights.py
create mode 100644 offlineimap/ui/plugins/Curses.py
create mode 100644 offlineimap/ui/plugins/Gnome.py
create mode 100644 offlineimap/ui/plugins/Machine.py
create mode 100644 offlineimap/ui/plugins/Noninteractive.py
create mode 100644 offlineimap/ui/plugins/TTY.py
create mode 100644 offlineimap/ui/plugins/__init__.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..081c1fc 100644
--- a/offlineimap/ui/__init__.py
+++ b/offlineimap/ui/__init__.py
@@ -16,23 +16,6 @@
# 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
-
# Must be last
import detector
=20
diff --git a/offlineimap/ui/detector.py b/offlineimap/ui/detector.py
index 4ec7503..d73c9d0 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 ["Curses.Blinkenlights"]
=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,24 @@ 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):
+ # ensure backwards compatibility with configs from <=3D 6.1
+ # uiClassName and dot will be empty if no . is found
+ uimodName,dot,uiClassName=3Duistr.partition(".")
+ =20
+ # this asserts that all elements are in place
try:
- uimod =3D localeval.eval(uistr, namespace)
+ # get the module from the plugin path
+ uimod =3D __import__("offlineimap.ui.plugins." + uimodName,[],[]=
,[uimodName])
+ if not uiClassName =3D=3D "":
+ # if uiClassName is set, we use the old method (introspectio=
n
+ # rocks!)
+ uiClass=3Duimod.__dict__[uiClassName]
+ else:
+ # asking the plugin for the class name is less error prone
+ uiClass =3D uimod.getUIClass()
except (AttributeError, NameError), e:
- #raise
+ print e
+ #raise
return None
- return uimod
+ return uiClass
diff --git a/offlineimap/ui/plugins/Blinkenlights.py b/offlineimap/ui/plu=
gins/Blinkenlights.py
new file mode 100644
index 0000000..6982351
--- /dev/null
+++ b/offlineimap/ui/plugins/Blinkenlights.py
@@ -0,0 +1,143 @@
+# 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()
+ =20
+ def sleep(s, sleepsecs):
+ s.gettf().setcolor('red')
+ s.getaccountframe().startsleep(sleepsecs)
+ UIBase.sleep(s, sleepsecs)
+
+ 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/plugins/Curses.py b/offlineimap/ui/plugins/Cu=
rses.py
new file mode 100644
index 0000000..949edb8
--- /dev/null
+++ b/offlineimap/ui/plugins/Curses.py
@@ -0,0 +1,593 @@
+# 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 offlineimap.ui.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):
+ s.gettf().setcolor('red')
+ s._msg("Next sync in %d:%02d" % (sleepsecs / 60, sleepsecs % 60)=
)
+ BlinkenBase.sleep(s, sleepsecs)
+ =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/plugins/Gnome.py b/offlineimap/ui/plugins/Gno=
me.py
new file mode 100644
index 0000000..369d68c
--- /dev/null
+++ b/offlineimap/ui/plugins/Gnome.py
@@ -0,0 +1,769 @@
+# -*- mode: python; coding: utf-8 -*-
+# Gnome UI
+# Copyright (C) 2008 David H=E4rdeman <david@xxxxxxxxxxx>
+#
+# 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, thread, threading
+if __name__ =3D=3D '__main__':
+ from offlineimap import imapserver, repository, folder, mbnames, thread=
util, version, syncmaster, accounts
+ from offlineimap.threadutil import MultiLock
+from offlineimap import version, threadutil
+from offlineimap.ui.UIBase import UIBase
+
+# Temporarily make gtk emit exceptions insted of warnings
+import warnings
+warnings.filterwarnings('error', module=3D'gtk')
+try:
+ import pygtk
+ pygtk.require('2.0')
+ import gobject
+ import gtk
+ import gnome
+ import gnomekeyring as gkey
+ if gtk.pygtk_version[0] !=3D 2 or gtk.pygtk_version[1] < 10:
+ raise Exception, 'Invalid pygtk version'
+ gtk.gdk.threads_init()
+ usable =3D True
+except:
+ usable =3D False
+=09
+warnings.resetwarnings()
+
+# FIXME: Custom icons
+GNOME_UI_ICON =3D 'stock_mail-send-receive'
+GNOME_UI_ACTIVE_ICONS =3D [ 'stock_mail-forward', 'stock_mail-reply' ]
+
+class GnomeUIAboutDialog(gtk.AboutDialog):
+ def __init__(self, ui):
+ self.ui =3D ui
+ self.closed =3D threading.Event()
+ self.closed.set()
+ gtk.AboutDialog.__init__(self)
+ gtk.about_dialog_set_url_hook(lambda dialog,url: \
+ gnome.url_show(url))
+ gtk.about_dialog_set_email_hook(lambda dialog,url: \
+ gnome.url_show("mailto:" + url))
+
+ self.connect('delete-event', self.delete_cb)
+ self.connect('response', self.response_cb)
+ self.set_name(version.productname)
+ self.set_version(version.versionstr)
+ self.set_copyright(version.copyright)
+ self.set_license(version.license)
+ self.set_wrap_license(True)
+ self.set_website(version.homepage)
+ self.set_website_label(version.homepage)
+ author =3D "%s <%s>" % (version.author,
+ version.author_email)
+ self.set_authors([author])
+ self.set_logo_icon_name(GNOME_UI_ICON)
+ self.set_program_name(version.productname)
+
+ def response_cb(self, arg =3D None, argb =3D None):
+ self.ui.debug("GnomeUIAboutDialog.response_cb()")
+ self.close()
+ return False
+
+ def delete_cb(self, widget, event):
+ self.ui.debug("GnomeUIAboutDialog.delete_cb()")
+ # Prevents widget destruction
+ return True
+
+ def close(self):
+ self.ui.debug("GnomeUIAboutDialog.close()")
+ self.hide_all()
+ self.closed.set()
+
+ def open(self, parent=3DNone):
+ self.ui.debug("GnomeUIAboutDialog.open()")
+ self.show_all()
+ self.closed.clear()
+
+
+class GnomeUIStatusIcon(gtk.StatusIcon):
+ STATE_ACTIVE =3D 1
+ STATE_IDLE =3D 2
+
+ def __init__(self, ui):
+ self.ui =3D ui
+ self.closed =3D threading.Event()
+ gtk.StatusIcon.__init__(self)
+ self.connect('activate', self.ui.log.open)
+ self.connect('popup-menu', self.popup_cb)
+ self.animate_counter =3D 0
+ self.state =3D None
+
+ self.menu =3D gtk.Menu()
+
+ self.mstart =3D gtk.ImageMenuItem("_Start Sync")
+ img =3D gtk.image_new_from_stock(gtk.STOCK_EXECUTE,
gtk.ICON_SIZE_MENU=
)
+ self.mstart.set_image(img)
+ self.mstart.connect('activate', self.ui.stopsleep_cb)
+ self.mstart.set_sensitive(False)
+ self.menu.append(self.mstart)
+
+ mlog =3D gtk.ImageMenuItem("Show _Log")
+ img =3D gtk.image_new_from_stock(gtk.STOCK_INFO,
gtk.ICON_SIZE_MENU)
+ mlog.set_image(img)
+ mlog.connect('activate', self.ui.log.open)
+ self.menu.append(mlog)
+
+ sep =3D gtk.SeparatorMenuItem()
+ self.menu.append(sep)
+
+ mabout =3D gtk.ImageMenuItem(gtk.STOCK_ABOUT)
+ mabout.connect('activate', self.ui.about.open)
+ self.menu.append(mabout)
+
+ mquit =3D gtk.ImageMenuItem(gtk.STOCK_QUIT)
+ mquit.connect('activate', self.quit_cb)
+ self.menu.append(mquit)
+
+ self.set_state(GnomeUIStatusIcon.STATE_ACTIVE)
+ self.set_visible(True)
+
+ def set_title(self, title):
+ self.ui.debug("GnomeUIStatusIcon.set_title(%s)" % title)
+ self.set_tooltip(version.productname + ": " + title)
+
+ def set_state(self, state):
+ self.ui.debug("GnomeUIStatusIcon.set_state(%i)" % state)
+ if self.state =3D=3D state:
+ return
+
+ if state =3D=3D GnomeUIStatusIcon.STATE_ACTIVE:
+ gobject.timeout_add(500, self.animate_cb)
+ self.set_title("active")
+ self.mstart.set_sensitive(False)
+ self.state =3D state
+ elif state =3D=3D GnomeUIStatusIcon.STATE_IDLE:
+ self.set_from_icon_name(GNOME_UI_ICON)
+ self.set_title("idle")
+ self.mstart.set_sensitive(True)
+ self.state =3D state
+
+ def quit_cb(self, notused =3D None):
+ self.ui.debug("GnomeUIStatusIcon.quit_cb()")
+ self.close()
+ self.ui.uiexit =3D True
+ self.ui.quit_cb()
+
+ def animate_cb(self):
+ self.ui.debug("GnomeUIStatusIcon.animate_cb()")
+ if self.state !=3D GnomeUIStatusIcon.STATE_ACTIVE:
+ return False
+
+ icon =3D GNOME_UI_ACTIVE_ICONS[self.animate_counter %
len(GNOME_UI_ACT=
IVE_ICONS)]
+ self.set_from_icon_name(icon)
+ self.animate_counter +=3D 1
+ return True
+
+ def popup_cb(self, widget, button, time, data =3D None):
+ self.ui.debug("GnomeUIStatusIcon.popup_cb()")
+ if button !=3D 3:
+ return
+ self.menu.show_all()
+ self.menu.popup(None, None, None, 3, time)
+ return False
+
+ def close(self):
+ self.ui.debug("GnomeUIStatusIcon.close()")
+ self.set_visible(False)
+ self.closed.set()
+
+ def open(self):
+ self.ui.debug("GnomeUIStatusIcon.open()")
+ self.set_visible(True)
+ self.closed.clear()
+
+
+
+class GnomeUIPasswordDialog(gtk.Dialog):
+ def __init__(self, ui):
+ # DIALOG =3D +------+---------+
+ # | ICON | MESSAGE |
+ # | | |
+ # | | INPUT |
+ # +------+---------+
+ # |KEYRING QESTION |
+ # +----------------+
+ # | BUTTONS |
+ # +----------------+
+ self.ui =3D ui
+ self.closed =3D threading.Event()
+ self.closed.set()
+ self.pw =3D None
+ self.pwready =3D threading.Event()
+ gtk.Dialog.__init__(self,
+ version.productname + \
+ " password prompt",
+ None,
+ gtk.DIALOG_NO_SEPARATOR,
+ # BUTTONS
+ (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+ gtk.STOCK_OK, gtk.RESPONSE_OK))
+ self.set_decorated(False)
+ self.set_resizable(False)
+ self.set_position(gtk.WIN_POS_CENTER_ALWAYS)
+ self.set_border_width(16)
+ self.connect('response', self.done_cb)
+
+ # Split between ICON and MESSAGE + INPUT
+ mainsplit =3D gtk.HBox(False, 8)
+ self.vbox.pack_start(mainsplit, False, False, 0)
+
+ # ICON
+ icon =3D
gtk.image_new_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION,
+ gtk.ICON_SIZE_DIALOG)
+ valign =3D gtk.VBox(False, 0) # to top-align the icon
+ valign.pack_start(icon, False, False, 0)
+ mainsplit.pack_start(valign, False, False, 0)
+
+ # Split between MESSAGE and INPUT
+ split =3D gtk.VBox(False, 0)
+ mainsplit.pack_start(split, True, True, 0)
+
+ # MESSAGE
+ self.msg =3D gtk.Label()
+ lalign =3D gtk.HBox(False, 0) # to left-align the label
+ lalign.pack_start(self.msg, False, False, 0)
+ split.pack_start(lalign, False, False, 0)
+
+ # INPUT
+ input =3D gtk.HBox(False, 16)
+ split.pack_start(input, True, True, 32)
+
+ pwlabel =3D gtk.Label("Password:")
+ input.pack_start(pwlabel, False, False, 0)
+
+ # KEYRING QUESTION
+ keyring =3D gtk.HBox(False, 16)
+ split.pack_start(keyring, True, True, 0)
+ self.save_button =3D gtk.CheckButton("Store password in
keyring")
+ keyring.pack_start(self.save_button, False, False, 0)
+
+ self.pwentry =3D gtk.Entry()
+ self.pwentry.set_visibility(False)
+ self.pwentry.set_activates_default(True)
+ input.pack_start(self.pwentry, True, True, 0)
+
+ def done_cb(self, dialog, response):
+ self.ui.debug("GnomeUIPasswordDialog.done_cb()")
+ self.pw =3D None
+ self.save_pw =3D False
+ if response =3D=3D gtk.RESPONSE_OK:
+ self.save_pw =3D self.save_button.get_active()
+ self.pw =3D self.pwentry.get_text()
+ self.pwready.set()
+ self.close()
+ return False
+
+ def close(self):
+ self.ui.debug("GnomeUIPasswordDialog.close()")
+ self.hide_all()
+ self.closed.set()
+
+ def open(self, markup =3D ''):
+ self.ui.debug("GnomeUIPasswordDialog.open()")
+ self.pwentry.set_text('')
+ self.pwready.clear()
+ self.msg.set_markup(markup)
+ self.set_default_response(gtk.RESPONSE_OK)
+ self.show_all()
+ self.closed.clear()
+
+
+class GnomeUILogWindow(gtk.Dialog):
+ def __init__(self, ui):
+ self.ui =3D ui
+ self.closed =3D threading.Event()
+ self.closed.set()
+ self.max_lines =3D 64 * 1024
+ self.autoscroll =3D True
+ gtk.Dialog.__init__(self,
+ version.productname + " log",
+ None,
+ 0,
+ #gtk.DIALOG_NO_SEPARATOR,
+ (gtk.STOCK_CLEAR, gtk.RESPONSE_CANCEL,
+ gtk.STOCK_OK, gtk.RESPONSE_OK))
+ self.set_default_size(640, 480)
+ self.connect('response', self.response_cb)
+ self.connect('delete-event',self.close_on_delete_cb)
+
+ box =3D gtk.VBox(False, 8)
+ self.vbox.pack_start(box, True, True, 0)
+
+ title =3D gtk.HBox(False, 8)
+ img =3D gtk.image_new_from_icon_name(GNOME_UI_ICON,
+ gtk.ICON_SIZE_DIALOG)
+ label =3D gtk.Label()
+ label.set_markup("<big><b>" + \
+ version.productname + " Log" + \
+ "</b></big>\n" + \
+ "Recent log messages:")
+ title.pack_start(img, False, False, 0)
+ title.pack_start(label, False, False, 0)
+ box.pack_start(title, False, False, 8)
+
+ self.sw =3D gtk.ScrolledWindow()
+ self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
+ self.tv =3D gtk.TextView()
+ self.tv.set_wrap_mode(gtk.WRAP_WORD)
+ self.tv.set_pixels_below_lines(6)
+ self.tv.set_pixels_inside_wrap(0)
+ self.tv.set_editable(False)
+ self.tv.set_cursor_visible(False)
+ self.sw.add(self.tv)
+ self.tb =3D self.tv.get_buffer()
+ self.tb.connect('insert-text', self.insert_cb)
+ box.pack_start(self.sw, True, True, 8)
+
+ abox =3D gtk.HBox(False, 8)
+ box.pack_start(abox, False, False, 16)
+
+ self.entry_autoscroll =3D gtk.CheckButton("_Autoscroll")
+ self.entry_autoscroll.set_active(self.autoscroll)
+ self.entry_autoscroll.connect('toggled', self.toggle_scroll_cb)
+ abox.pack_start(self.entry_autoscroll, True, True, 0)
+
+ label =3D gtk.Label("Max Lines:")
+ abox.pack_start(label, False, False, 0)
+
+ adj =3D gtk.Adjustment(self.max_lines, 1, 65536, 1, 1024, 1024)
+ self.entry_lines =3D gtk.SpinButton(adj, 1.0, 0)
+ self.entry_lines.set_numeric(True)
+ self.entry_lines.set_wrap(False)
+ self.entry_lines.set_snap_to_ticks(True)
+ self.entry_lines.connect('value-changed', self.line_change_cb)
+ abox.pack_start(self.entry_lines, False, False, 0)
+
+ def scroll(self):
+ self.ui.debug("GnomeUILogWindow.scroll()")
+ if self.autoscroll:
+ end =3D self.tb.get_end_iter()
+ self.tv.scroll_to_iter(end, 0.0, True, 1.0, 1.0)
+
+ def delete_lines(self):
+ self.ui.debug("GnomeUILogWindow.delete_lines()")
+ lines =3D self.tb.get_line_count()
+ diff =3D lines - self.max_lines - 1
+ if diff > 0:
+ start =3D self.tb.get_start_iter()
+ end =3D self.tb.get_iter_at_line(diff)
+ self.tb.delete(start, end)
+
+ def line_change_cb(self, spinbutton):
+ self.ui.debug("GnomeUILogWindow.line_change_cb()")
+ self.max_lines =3D self.entry_lines.get_value_as_int()
+ self.delete_lines()
+
+ def toggle_scroll_cb(self, togglebutton):
+ self.ui.debug("GnomeUILogWindow.toggle_scroll_cb()")
+ self.autoscroll =3D togglebutton.get_active()
+ self.scroll()
+
+ def insert_cb(self, textbuffer, iter, text, length):
+ self.ui.debug("GnomeUILogWindow.insert_cb()")
+ self.scroll()
+
+ def add_msg(self, msg):
+ self.ui.debug("GnomeUILogWindow.add_msg()")
+ gtk.gdk.threads_enter()
+ end =3D self.tb.get_end_iter()
+ self.tb.insert(end, msg.strip() + "\n")
+ self.delete_lines()
+ # the insert-text signal will take care of scrolling...
+ gtk.gdk.threads_leave()
+ return False
+
+ def close_on_delete_cb(self, widget, event):
+ # response_cb will handle everything
+ return True
+
+ def response_cb(self, widget, response):
+ self.ui.debug("GnomeUILogWindow.response_cb()")
+ if response =3D=3D gtk.RESPONSE_CANCEL:
+ self.tb.set_text('')
+ return False
+ else:
+ self.close()
+ # Prevent widget destruction
+ return True
+
+ def close(self):
+ self.ui.debug("GnomeUILogWindow.close()")
+ self.hide_all()
+ self.closed.set()
+
+ def open(self, notused =3D None):
+ self.ui.debug("GnomeUILogWindow.open()")
+ self.show_all()
+ self.closed.clear()
+
+
+class GnomeUIWarningWindow(gtk.Dialog):
+ def __init__(self, ui):
+ self.ui =3D ui
+ self.closed =3D threading.Event()
+ self.closed.set()
+ gtk.Dialog.__init__(self,
+ version.productname + " WARNING",
+ None,
+ 0,
+ #gtk.DIALOG_NO_SEPARATOR,
+ (gtk.STOCK_OK, gtk.RESPONSE_OK))
+ self.set_default_size(640, 480)
+ self.connect('response', self.response_cb)
+
+ box =3D gtk.VBox(False, 8)
+ self.vbox.pack_start(box, True, True, 0)
+
+ title =3D gtk.HBox(False, 8)
+ img =3D gtk.image_new_from_stock(gtk.STOCK_DIALOG_WARNING,
+ gtk.ICON_SIZE_DIALOG)
+ label =3D gtk.Label()
+ label.set_markup("<big><b>" + \
+ version.productname + " Warning" + \
+ "</b></big>\n" + \
+ "The following errors have occurred:")
+ title.pack_start(img, False, False, 0)
+ title.pack_start(label, False, False, 0)
+ box.pack_start(title, False, False, 8)
+
+ self.sw =3D gtk.ScrolledWindow()
+ self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
+ self.tv =3D gtk.TextView()
+ self.tv.set_wrap_mode(gtk.WRAP_WORD)
+ self.tv.set_pixels_below_lines(6)
+ self.tv.set_pixels_inside_wrap(0)
+ self.tv.set_editable(False)
+ self.tv.set_cursor_visible(False)
+ self.sw.add(self.tv)
+ self.tb =3D self.tv.get_buffer()
+ self.tb.connect('insert-text', self.insert_cb)
+ box.pack_start(self.sw, True, True, 8)
+
+ def insert_cb(self, textbuffer, iter, text, length):
+ self.ui.debug("GnomeUIWarningWindow.insert_cb()")
+ end =3D self.tb.get_end_iter()
+ self.tv.scroll_to_iter(end, 0.0, True, 1.0, 1.0)
+
+ def add_msg(self, msg):
+ self.ui.debug("GnomeUIWarningWindow.add_msg()")
+ end =3D self.tb.get_end_iter()
+ self.tb.insert(end, msg.strip() + "\n")
+ self.open()
+ return False
+
+ def response_cb(self, widget =3D None, event =3D None):
+ self.ui.debug("GnomeUIWarningWindow.response_cb()")
+ self.close()
+ # Prevent widget destruction
+ return True
+
+ def close(self):
+ self.ui.debug("GnomeUIWarningWindow.close()")
+ self.hide_all()
+ self.tb.set_text('')
+ self.closed.set()
+
+ def open(self):
+ self.ui.debug("GnomeUILogWindow.open()")
+ self.show_all()
+ self.closed.clear()
+
+
+class GnomeUIThread:
+ def __init__(self):
+ # General
+ self.dbg =3D False
+ self.lock =3D threading.Lock()
+ self.thread =3D None
+ self.uiexit =3D False # Was an exit action initiated by the
UI?
+ self.stopsleep =3D threading.Event()
+ self.exitsync =3D threading.Event()
+ gtk.window_set_default_icon_name(GNOME_UI_ICON)
+
+ # Widgets
+ self.about =3D GnomeUIAboutDialog(self)
+ self.log =3D GnomeUILogWindow(self)
+ self.warn =3D GnomeUIWarningWindow(self)
+ self.pwdialog =3D GnomeUIPasswordDialog(self)
+ self.icon =3D GnomeUIStatusIcon(self)
+
+ def start(self):
+ self.debug("GnomeUIThread.start()")
+ self.lock.acquire()
+ if self.thread is None:
+ self.exitsync.clear()
+ self.thread =3D threadutil.ExitNotifyThread(target =3D
self.uiloop,
+ name =3D
"GnomeUIThread")
+ self.thread.setDaemon(True)
+ self.thread.start()
+ self.lock.release()
+
+ def stop(self):
+ self.debug("GnomeUIThread.stop()")
+ self.lock.acquire()
+ if self.thread is not None:
+ gobject.idle_add(self.quit_cb, None)
+ # self.thread.join won't work for some reason...
+ self.exitsync.wait()
+ self.thread =3D None
+ self.lock.release()
+=09
+ def get_keyring_pw(self, accountname):
+ server =3D accountname
+ protocol =3D "offlineimap"
+ try:
+ gkey.get_default_keyring_sync()
+ except (gkey.NoKeyringDaemonError):
+ return None
+ try:
+ attrs =3D {"server": server, "protocol": protocol}
+ items =3D
gkey.find_items_sync(gkey.ITEM_NETWORK_PASSWORD, attrs)
+ if len(items) > 0:
+ return items[0].secret
+ else:
+ return None
+ except (gkey.DeniedError, gkey.NoMatchError):
+ return None
+
+ def set_keyring_pw(self, accountname, pw):
+ name =3D "offlineimap"
+ server =3D accountname
+ protocol =3D "offlineimap"
+ attrs =3D {
+ "user": name,
+ "server": server,
+ "protocol": protocol,
+ }
+ gkey.item_create_sync(gkey.get_default_keyring_sync(),
+ gkey.ITEM_NETWORK_PASSWORD, "offlineimap", attrs, pw, Tr=
ue)=09
+
+
+ def getpass(self, accountname, config, errmsg =3D None):
+ self.debug("GnomeUIThread.getpass()")
+ pw =3D None
+ self.lock.acquire()
+ if self.thread is not None:
+ pw =3D self.get_keyring_pw(accountname)
+ if pw is None: =20
+ gobject.idle_add(self.pw_cb, accountname,
config, errmsg)
+ self.pwdialog.pwready.wait()
+ self.pwdialog.pwready.clear()
+ pw =3D self.pwdialog.pw
+ if self.pwdialog.save_pw:
+ self.set_keyring_pw(accountname, pw)
+ self.pwdialog.pw =3D None
+ =09
+ self.lock.release()
+ return pw
+
+ def add_msg(self, msg):
+ self.debug("GnomeUIThread.add_msg(%s)" % msg)
+ self.lock.acquire()
+ if self.thread is not None:
+ gobject.idle_add(self.log.add_msg, msg)
+ self.lock.release()
+
+ def add_warning(self, msg):
+ self.debug("GnomeUIThread.add_warning(%s)" % msg)
+ self.add_msg('WARNING: ' + msg)
+ self.lock.acquire()
+ if self.thread is not None:
+ gobject.idle_add(self.warn.add_msg, msg)
+ self.lock.release()
+
+ # Internal methods follow
+ def enable_debug(self, enable =3D True):
+ self.dbg =3D enable
+
+ def debug(self, msg):
+ if self.dbg:
+ sys.stderr.write("GnomeUI-DEBUG: " + msg + "\n")
+
+ def stopsleep_cb(self, data =3D None):
+ self.debug("GnomeUIThread.stopsleep_cb()")
+ self.stopsleep.set()
+ return False
+
+ basemarkup =3D "<big><b>Enter %s password</b></big>\n\n" + \
+ "A password is needed to access account \"%s\"."
+
+ basemarkuperr =3D basemarkup + "\n\n" + \
+ "<span foreground=3D\"red\">%s</span>"
+
+ def pw_cb(self, accountname, config =3D None, errmsg =3D None):
+ self.debug("GnomeUIThread.pw_cb()")
+
+ if errmsg is None:
+ markup =3D self.basemarkup % \
+ (version.productname,
+ accountname)
+ else:
+ markup =3D self.basemarkuperr % \
+ (version.productname,
+ accountname,
+ errmsg)
+
+ self.pw =3D self.pwdialog.open(markup)
+ return False
+
+ def quit_cb(self, data =3D None):
+ self.debug("GnomeUIThread.quit_cb()")
+ self.about.close()
+ self.log.close()
+ self.warn.close()
+ self.pwdialog.close()
+ self.icon.close()
+ gtk.main_quit()
+ return False
+
+ def uiloop(self):
+ self.debug("GnomeUIThread.uiloop() - begin")
+ gtk.main()
+ self.debug("GnomeUIThread.uiloop() - end (%s)" %
repr(self.uiexit))
+ if self.uiexit:
+ thread.interrupt_main()
+ self.exitsync.set()
+ #thread.exit()
+
+
+class GnomeUI(UIBase):
+ def __init__(self, config, verbose =3D 0):
+ UIBase.__init__(self, config, verbose)
+
+ def isusable(self):
+ return usable
+
+ def init_banner(self):
+ self.ui =3D GnomeUIThread()
+ self.ui.start()
+ UIBase.init_banner(self)
+
+ def getpass(self, accountname, config, errmsg =3D None):
+ return self.ui.getpass(accountname, config, errmsg)
+
+ def _display(self, msg):
+ self.ui.add_msg(msg)
+
+ def warn(self, msg, minor =3D 0):
+ if minor:
+ self.ui.add_msg('warning: ' + str(msg))
+ else:
+ self.ui.add_warning(str(msg))
+
+ def sleep(self, secs):
+ self.ui.debug("GnomeUI.sleep(%i)" % secs)
+ msg =3D "Sleeping for %d minutes" % (secs / 60)
+ if secs % 60 > 0:
+ msg +=3D " and %02d seconds" % (secs % 60)
+ self.ui.add_msg(msg)
+ self.ui.icon.set_state(GnomeUIStatusIcon.STATE_IDLE)
+ self.ui.stopsleep.clear()
+ UIBase.sleep(self, secs)
+
+ def sleeping(self, secs, remainingsecs):
+ # Return values
+ # 0 if timeout expired
+ # 1 if there is a request to cancel the timer
+ # 2 if there is a request to abort the program
+ self.ui.icon.set_title("sleeping for %dm%02ds" % \
+ (remainingsecs / 60, remainingsecs % 60))
+ if secs > 0:
+ self.ui.stopsleep.wait(secs)
+ if self.ui.stopsleep.isSet():
+ return 1
+ else:
+ self.ui.icon.set_state(GnomeUIStatusIcon.STATE_ACTIVE)
+ return 0
+
+ def terminate(self, exitstatus =3D 0, errortitle =3D None, errormsg =3D=
None):
+ self.ui.debug("Terminating")
+ if not self.ui.uiexit and errormsg is not None:
+ if errortitle is not None:
+ self.ui.add_warning('ERROR:
%s\n\n%s\n'%(errortitle, errormsg))
+ else:
+ self.ui.add_warning('%s\n' % errormsg)
+
+ # Give the user a chance to read any warnings
+ self.ui.warn.closed.wait()
+ if not self.ui.uiexit:
+ self.ui.stop()
+ UIBase.terminate(self, exitstatus, errortitle, errormsg)
+
+ def locked(self):
+ self.warn("Another OfflineIMAP is running with the same
metadatadir; e=
xiting.", 1)
+
+def getUIClass() : return GnomeUI
+
+if __name__ =3D=3D '__main__':
+ import time
+ print "MAIN: Begin tests"
+
+
+ x =3D GnomeUI(None)
+ x.init_banner()
+ x.ui.enable_debug(True)
+
+ warnings =3D 5
+ messages =3D 5
+
+ x.warn("Printing %i Warnings" % warnings)
+ for i in range(warnings):
+ x.warn("Warning " + str(i + 1))
+ time.sleep(1)
+=09
+ x.warn("Sleeping 999 secs")
+ x.warn("Select \"Start Sync\" from status icon context menu to continue=
")
+ x.sleep(999)
+
+ x.warn("Showing log window")
+ x.ui.log.open()
+
+ x.warn("Printing %i Log Messages" % messages)
+ for i in range(messages):
+ x.ui.add_msg("Line " + str(i + 1) + " of log text")
+ time.sleep(1)
+ x.warn("Sleeping 10 secs")
+ x.sleep(10)
+
+ x.warn("Showing about - close to continue")
+ x.ui.about.open()
+ x.ui.about.closed.wait()
+
+ x.warn("Getting a password")
+ password =3D x.ui.getpass("TEST", None, None)
+ if password is not None:
+ x.warn("Password was " + password)
+ else:
+ x.warn("No password given")
+
+ x.warn("Sleeping 3 secs")
+ x.sleep(3)
+ x.warn("Trying a password again with error message")
+ password =3D x.ui.getpass("Test", None, "Error message")
+ if password is not None:
+ x.warn("Password was " + password)
+ else:
+ x.warn("No password given")
+
+ x.warn("Terminating, close warning window to exit")
+ x.terminate()
+
+
diff --git a/offlineimap/ui/plugins/Machine.py b/offlineimap/ui/plugins/M=
achine.py
new file mode 100644
index 0000000..c77a3c5
--- /dev/null
+++ b/offlineimap/ui/plugins/Machine.py
@@ -0,0 +1,177 @@
+# 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 offlineimap.ui.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)
+
diff --git a/offlineimap/ui/plugins/Noninteractive.py b/offlineimap/ui/pl=
ugins/Noninteractive.py
new file mode 100644
index 0000000..ca8ff5a
--- /dev/null
+++ b/offlineimap/ui/plugins/Noninteractive.py
@@ -0,0 +1,51 @@
+# 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 offlineimap.ui.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):
+ if s.verbose >=3D 0:
+ s._msg("Sleeping for %d:%02d" % (sleepsecs / 60, sleepsecs %=
60))
+ UIBase.sleep(s, sleepsecs)
+
+ 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/plugins/TTY.py b/offlineimap/ui/plugins/TTY.p=
y
new file mode 100644
index 0000000..32d6279
--- /dev/null
+++ b/offlineimap/ui/plugins/TTY.py
@@ -0,0 +1,60 @@
+# 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 offlineimap.ui.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/plugins/__init__.py b/offlineimap/ui/plugins/=
__init__.py
new file mode 100644
index 0000000..1b42165
--- /dev/null
+++ b/offlineimap/ui/plugins/__init__.py
@@ -0,0 +1,18 @@
+# UI module directory
+# 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
+
--=20
1.6.2.5
- [PATCH] furbished dynamic ui plugin selection,
Christoph Höger <=
|
|