Step 2 of SVN to arch tree conversion
This commit is contained in:
132
offlineimap/ui/Blinkenlights.py
Normal file
132
offlineimap/ui/Blinkenlights.py
Normal file
@ -0,0 +1,132 @@
|
||||
# Blinkenlights base classes
|
||||
# Copyright (C) 2003 John Goerzen
|
||||
# <jgoerzen@complete.org>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
from 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 loadmessagelist(s, repos, folder):
|
||||
s.gettf().setcolor('green')
|
||||
s._msg("Scanning folder [%s/%s]" % (s.getnicename(repos),
|
||||
folder.getvisiblename()))
|
||||
|
||||
def syncingmessages(s, sr, sf, dr, df):
|
||||
s.gettf().setcolor('blue')
|
||||
s.__class__.__bases__[-1].syncingmessages(s, sr, sf, dr, df)
|
||||
|
||||
def copyingmessage(s, uid, src, destlist):
|
||||
s.gettf().setcolor('orange')
|
||||
s.__class__.__bases__[-1].copyingmessage(s, uid, src, destlist)
|
||||
|
||||
def deletingmessages(s, uidlist, destlist):
|
||||
s.gettf().setcolor('red')
|
||||
s.__class__.__bases__[-1].deletingmessages(s, uidlist, destlist)
|
||||
|
||||
def deletingmessage(s, uid, destlist):
|
||||
s.gettf().setcolor('red')
|
||||
s.__class__.__bases__[-1].deletingmessage(s, uid, destlist)
|
||||
|
||||
def addingflags(s, uidlist, flags, destlist):
|
||||
s.gettf().setcolor('yellow')
|
||||
s.__class__.__bases__[-1].addingflags(s, uidlist, flags, destlist)
|
||||
|
||||
def deletingflags(s, uidlist, flags, destlist):
|
||||
s.gettf().setcolor('pink')
|
||||
s.__class__.__bases__[-1].deletingflags(s, uidlist, flags, destlist)
|
||||
|
||||
def init_banner(s):
|
||||
s.availablethreadframes = {}
|
||||
s.threadframes = {}
|
||||
s.tflock = MultiLock()
|
||||
|
||||
def threadExited(s, thread):
|
||||
threadid = thread.threadid
|
||||
accountname = s.getthreadaccount(thread)
|
||||
s.tflock.acquire()
|
||||
try:
|
||||
if threadid in s.threadframes[accountname]:
|
||||
tf = s.threadframes[accountname][threadid]
|
||||
del s.threadframes[accountname][threadid]
|
||||
s.availablethreadframes[accountname].append(tf)
|
||||
tf.setthread(None)
|
||||
finally:
|
||||
s.tflock.release()
|
||||
|
||||
UIBase.threadExited(s, thread)
|
||||
|
||||
def gettf(s):
|
||||
threadid = thread.get_ident()
|
||||
accountname = s.getthreadaccount()
|
||||
|
||||
s.tflock.acquire()
|
||||
|
||||
try:
|
||||
if not accountname in s.threadframes:
|
||||
s.threadframes[accountname] = {}
|
||||
|
||||
if threadid in s.threadframes[accountname]:
|
||||
return s.threadframes[accountname][threadid]
|
||||
|
||||
if not accountname in s.availablethreadframes:
|
||||
s.availablethreadframes[accountname] = []
|
||||
|
||||
if len(s.availablethreadframes[accountname]):
|
||||
tf = s.availablethreadframes[accountname].pop(0)
|
||||
tf.setthread(currentThread())
|
||||
else:
|
||||
tf = s.getaccountframe().getnewthreadframe()
|
||||
s.threadframes[accountname][threadid] = tf
|
||||
return tf
|
||||
finally:
|
||||
s.tflock.release()
|
||||
|
||||
def 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() == 'black':
|
||||
s.gettf().setcolor('red')
|
||||
else:
|
||||
s.gettf().setcolor('black')
|
||||
return s.getaccountframe().sleeping(sleepsecs, remainingsecs)
|
||||
|
||||
|
589
offlineimap/ui/Curses.py
Normal file
589
offlineimap/ui/Curses.py
Normal file
@ -0,0 +1,589 @@
|
||||
# Curses-based interfaces
|
||||
# Copyright (C) 2003 John Goerzen
|
||||
# <jgoerzen@complete.org>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
from Blinkenlights import BlinkenBase
|
||||
from UIBase import UIBase
|
||||
from threading import *
|
||||
import thread, time, sys, os, signal, time
|
||||
from offlineimap import version, threadutil
|
||||
from offlineimap.threadutil import MultiLock
|
||||
|
||||
import curses, curses.panel, curses.textpad, curses.wrapper
|
||||
|
||||
acctkeys = '1234567890abcdefghijklmnoprstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-=;/.,'
|
||||
|
||||
class CursesUtil:
|
||||
def __init__(self):
|
||||
self.pairlock = Lock()
|
||||
self.iolock = MultiLock()
|
||||
self.start()
|
||||
|
||||
def initpairs(self):
|
||||
self.pairlock.acquire()
|
||||
try:
|
||||
self.pairs = {self._getpairindex(curses.COLOR_WHITE,
|
||||
curses.COLOR_BLACK): 0}
|
||||
self.nextpair = 1
|
||||
finally:
|
||||
self.pairlock.release()
|
||||
|
||||
def lock(self):
|
||||
self.iolock.acquire()
|
||||
|
||||
def unlock(self):
|
||||
self.iolock.release()
|
||||
|
||||
def locked(self, target, *args, **kwargs):
|
||||
"""Perform an operation with full locking."""
|
||||
self.lock()
|
||||
try:
|
||||
apply(target, args, kwargs)
|
||||
finally:
|
||||
self.unlock()
|
||||
|
||||
def refresh(self):
|
||||
def lockedstuff():
|
||||
curses.panel.update_panels()
|
||||
curses.doupdate()
|
||||
self.locked(lockedstuff)
|
||||
|
||||
def isactive(self):
|
||||
return hasattr(self, 'stdscr')
|
||||
|
||||
def _getpairindex(self, fg, bg):
|
||||
return '%d/%d' % (fg,bg)
|
||||
|
||||
def getpair(self, fg, bg):
|
||||
if not self.has_color:
|
||||
return 0
|
||||
pindex = self._getpairindex(fg, bg)
|
||||
self.pairlock.acquire()
|
||||
try:
|
||||
if self.pairs.has_key(pindex):
|
||||
return curses.color_pair(self.pairs[pindex])
|
||||
else:
|
||||
self.pairs[pindex] = self.nextpair
|
||||
curses.init_pair(self.nextpair, fg, bg)
|
||||
self.nextpair += 1
|
||||
return curses.color_pair(self.nextpair - 1)
|
||||
finally:
|
||||
self.pairlock.release()
|
||||
|
||||
def start(self):
|
||||
self.stdscr = curses.initscr()
|
||||
curses.noecho()
|
||||
curses.cbreak()
|
||||
self.stdscr.keypad(1)
|
||||
try:
|
||||
curses.start_color()
|
||||
self.has_color = curses.has_colors()
|
||||
except:
|
||||
self.has_color = 0
|
||||
|
||||
self.oldcursor = None
|
||||
try:
|
||||
self.oldcursor = curses.curs_set(0)
|
||||
except:
|
||||
pass
|
||||
|
||||
self.stdscr.clear()
|
||||
self.stdscr.refresh()
|
||||
(self.height, self.width) = self.stdscr.getmaxyx()
|
||||
self.initpairs()
|
||||
|
||||
def stop(self):
|
||||
if not hasattr(self, 'stdscr'):
|
||||
return
|
||||
#self.stdscr.addstr(self.height - 1, 0, "\n",
|
||||
# self.getpair(curses.COLOR_WHITE,
|
||||
# curses.COLOR_BLACK))
|
||||
if self.oldcursor != None:
|
||||
curses.curs_set(self.oldcursor)
|
||||
self.stdscr.refresh()
|
||||
self.stdscr.keypad(0)
|
||||
curses.nocbreak()
|
||||
curses.echo()
|
||||
curses.endwin()
|
||||
del self.stdscr
|
||||
|
||||
def reset(self):
|
||||
self.stop()
|
||||
self.start()
|
||||
|
||||
class CursesAccountFrame:
|
||||
def __init__(s, master, accountname):
|
||||
s.c = master
|
||||
s.children = []
|
||||
s.accountname = accountname
|
||||
|
||||
def drawleadstr(s, secs = None):
|
||||
if secs == None:
|
||||
acctstr = '%s: [active] %13.13s: ' % (s.key, s.accountname)
|
||||
else:
|
||||
acctstr = '%s: [%3d:%02d] %13.13s: ' % (s.key,
|
||||
secs / 60, secs % 60,
|
||||
s.accountname)
|
||||
s.c.locked(s.window.addstr, 0, 0, acctstr)
|
||||
s.location = len(acctstr)
|
||||
|
||||
def setwindow(s, window, key):
|
||||
s.window = window
|
||||
s.key = key
|
||||
s.drawleadstr()
|
||||
for child in s.children:
|
||||
child.update(window, 0, s.location)
|
||||
s.location += 1
|
||||
|
||||
def getnewthreadframe(s):
|
||||
tf = CursesThreadFrame(s.c, s.window, 0, s.location)
|
||||
s.location += 1
|
||||
s.children.append(tf)
|
||||
return tf
|
||||
|
||||
def startsleep(s, sleepsecs):
|
||||
s.sleeping_abort = 0
|
||||
|
||||
def sleeping(s, sleepsecs, remainingsecs):
|
||||
if remainingsecs:
|
||||
s.c.lock()
|
||||
try:
|
||||
s.drawleadstr(remainingsecs)
|
||||
s.window.refresh()
|
||||
finally:
|
||||
s.c.unlock()
|
||||
time.sleep(sleepsecs)
|
||||
else:
|
||||
s.c.lock()
|
||||
try:
|
||||
s.drawleadstr()
|
||||
s.window.refresh()
|
||||
finally:
|
||||
s.c.unlock()
|
||||
return s.sleeping_abort
|
||||
|
||||
def syncnow(s):
|
||||
s.sleeping_abort = 1
|
||||
|
||||
class CursesThreadFrame:
|
||||
def __init__(s, master, window, y, x):
|
||||
"""master should be a CursesUtil object."""
|
||||
s.c = master
|
||||
s.window = window
|
||||
s.x = x
|
||||
s.y = y
|
||||
s.colors = []
|
||||
bg = curses.COLOR_BLACK
|
||||
s.colormap = {'black': s.c.getpair(curses.COLOR_BLACK, bg),
|
||||
'gray': s.c.getpair(curses.COLOR_WHITE, bg),
|
||||
'white': curses.A_BOLD | s.c.getpair(curses.COLOR_WHITE, bg),
|
||||
'blue': s.c.getpair(curses.COLOR_BLUE, bg),
|
||||
'red': s.c.getpair(curses.COLOR_RED, bg),
|
||||
'purple': s.c.getpair(curses.COLOR_MAGENTA, bg),
|
||||
'cyan': s.c.getpair(curses.COLOR_CYAN, bg),
|
||||
'green': s.c.getpair(curses.COLOR_GREEN, bg),
|
||||
'orange': s.c.getpair(curses.COLOR_YELLOW, bg),
|
||||
'yellow': curses.A_BOLD | s.c.getpair(curses.COLOR_YELLOW, bg),
|
||||
'pink': curses.A_BOLD | s.c.getpair(curses.COLOR_RED, bg)}
|
||||
#s.setcolor('gray')
|
||||
s.setcolor('black')
|
||||
|
||||
def setcolor(self, color):
|
||||
self.color = self.colormap[color]
|
||||
self.colorname = color
|
||||
self.display()
|
||||
|
||||
def display(self):
|
||||
def lockedstuff():
|
||||
if self.getcolor() == 'black':
|
||||
self.window.addstr(self.y, self.x, ' ', self.color)
|
||||
else:
|
||||
self.window.addstr(self.y, self.x, '.', self.color)
|
||||
self.c.stdscr.move(self.c.height - 1, self.c.width - 1)
|
||||
self.window.refresh()
|
||||
self.c.locked(lockedstuff)
|
||||
|
||||
def getcolor(self):
|
||||
return self.colorname
|
||||
|
||||
def getcolorpair(self):
|
||||
return self.color
|
||||
|
||||
def update(self, window, y, x):
|
||||
self.window = window
|
||||
self.y = y
|
||||
self.x = x
|
||||
self.display()
|
||||
|
||||
def setthread(self, newthread):
|
||||
self.setcolor('black')
|
||||
#if newthread:
|
||||
# self.setcolor('gray')
|
||||
#else:
|
||||
# self.setcolor('black')
|
||||
|
||||
class InputHandler:
|
||||
def __init__(s, util):
|
||||
s.c = util
|
||||
s.bgchar = None
|
||||
s.inputlock = Lock()
|
||||
s.lockheld = 0
|
||||
s.statuslock = Lock()
|
||||
s.startup = Event()
|
||||
s.startthread()
|
||||
|
||||
def startthread(s):
|
||||
s.thread = threadutil.ExitNotifyThread(target = s.bgreaderloop,
|
||||
name = "InputHandler loop")
|
||||
s.thread.setDaemon(1)
|
||||
s.thread.start()
|
||||
|
||||
def bgreaderloop(s):
|
||||
while 1:
|
||||
s.statuslock.acquire()
|
||||
if s.lockheld or s.bgchar == None:
|
||||
s.statuslock.release()
|
||||
s.startup.wait()
|
||||
else:
|
||||
s.statuslock.release()
|
||||
ch = s.c.stdscr.getch()
|
||||
s.statuslock.acquire()
|
||||
try:
|
||||
if s.lockheld or s.bgchar == None:
|
||||
curses.ungetch(ch)
|
||||
else:
|
||||
s.bgchar(ch)
|
||||
finally:
|
||||
s.statuslock.release()
|
||||
|
||||
def set_bgchar(s, callback):
|
||||
"""Sets a "background" character handler. If a key is pressed
|
||||
while not doing anything else, it will be passed to this handler.
|
||||
|
||||
callback is a function taking a single arg -- the char pressed.
|
||||
|
||||
If callback is None, clears the request."""
|
||||
s.statuslock.acquire()
|
||||
oldhandler = s.bgchar
|
||||
newhandler = callback
|
||||
s.bgchar = callback
|
||||
|
||||
if oldhandler and not newhandler:
|
||||
pass
|
||||
if newhandler and not oldhandler:
|
||||
s.startup.set()
|
||||
|
||||
s.statuslock.release()
|
||||
|
||||
def input_acquire(s):
|
||||
"""Call this method when you want exclusive input control.
|
||||
Make sure to call input_release afterwards!
|
||||
"""
|
||||
|
||||
s.inputlock.acquire()
|
||||
s.statuslock.acquire()
|
||||
s.lockheld = 1
|
||||
s.statuslock.release()
|
||||
|
||||
def input_release(s):
|
||||
"""Call this method when you are done getting input."""
|
||||
s.statuslock.acquire()
|
||||
s.lockheld = 0
|
||||
s.statuslock.release()
|
||||
s.inputlock.release()
|
||||
s.startup.set()
|
||||
|
||||
class Blinkenlights(BlinkenBase, UIBase):
|
||||
def init_banner(s):
|
||||
s.af = {}
|
||||
s.aflock = Lock()
|
||||
s.c = CursesUtil()
|
||||
s.text = []
|
||||
BlinkenBase.init_banner(s)
|
||||
s.setupwindows()
|
||||
s.inputhandler = InputHandler(s.c)
|
||||
s.gettf().setcolor('red')
|
||||
s._msg(version.banner)
|
||||
s.inputhandler.set_bgchar(s.keypress)
|
||||
signal.signal(signal.SIGWINCH, s.resizehandler)
|
||||
s.resizelock = Lock()
|
||||
s.resizecount = 0
|
||||
|
||||
def resizehandler(s, signum, frame):
|
||||
s.resizeterm()
|
||||
|
||||
def resizeterm(s, dosleep = 1):
|
||||
if not s.resizelock.acquire(0):
|
||||
s.resizecount += 1
|
||||
return
|
||||
signal.signal(signal.SIGWINCH, signal.SIG_IGN)
|
||||
s.aflock.acquire()
|
||||
s.c.lock()
|
||||
s.resizecount += 1
|
||||
while s.resizecount:
|
||||
s.c.reset()
|
||||
s.setupwindows()
|
||||
s.resizecount -= 1
|
||||
s.c.unlock()
|
||||
s.aflock.release()
|
||||
s.resizelock.release()
|
||||
signal.signal(signal.SIGWINCH, s.resizehandler)
|
||||
if dosleep:
|
||||
time.sleep(1)
|
||||
s.resizeterm(0)
|
||||
|
||||
def isusable(s):
|
||||
# Not a terminal? Can't use curses.
|
||||
if not sys.stdout.isatty() and sys.stdin.isatty():
|
||||
return 0
|
||||
|
||||
# No TERM specified? Can't use curses.
|
||||
try:
|
||||
if not len(os.environ['TERM']):
|
||||
return 0
|
||||
except: return 0
|
||||
|
||||
# ncurses doesn't want to start? Can't use curses.
|
||||
# This test is nasty because initscr() actually EXITS on error.
|
||||
# grr.
|
||||
|
||||
pid = os.fork()
|
||||
if pid:
|
||||
# parent
|
||||
return not os.WEXITSTATUS(os.waitpid(pid, 0)[1])
|
||||
else:
|
||||
# child
|
||||
curses.initscr()
|
||||
curses.endwin()
|
||||
# If we didn't die by here, indicate success.
|
||||
sys.exit(0)
|
||||
|
||||
def keypress(s, key):
|
||||
if key > 255:
|
||||
return
|
||||
|
||||
if chr(key) == 'q':
|
||||
# Request to quit.
|
||||
s.terminate()
|
||||
|
||||
try:
|
||||
index = acctkeys.index(chr(key))
|
||||
except ValueError:
|
||||
# Key not a valid one: exit.
|
||||
return
|
||||
|
||||
if index >= len(s.hotkeys):
|
||||
# Not in our list of valid hotkeys.
|
||||
return
|
||||
|
||||
# Trying to end sleep somewhere.
|
||||
|
||||
s.getaccountframe(s.hotkeys[index]).syncnow()
|
||||
|
||||
def getpass(s, accountname, config, errmsg = None):
|
||||
s.inputhandler.input_acquire()
|
||||
|
||||
# See comment on _msg for info on why both locks are obtained.
|
||||
|
||||
s.tflock.acquire()
|
||||
s.c.lock()
|
||||
try:
|
||||
s.gettf().setcolor('white')
|
||||
s._addline(" *** Input Required", s.gettf().getcolorpair())
|
||||
s._addline(" *** Please enter password for account %s: " % accountname,
|
||||
s.gettf().getcolorpair())
|
||||
s.logwindow.refresh()
|
||||
password = s.logwindow.getstr()
|
||||
finally:
|
||||
s.tflock.release()
|
||||
s.c.unlock()
|
||||
s.inputhandler.input_release()
|
||||
return password
|
||||
|
||||
def setupwindows(s):
|
||||
s.c.lock()
|
||||
try:
|
||||
s.bannerwindow = curses.newwin(1, s.c.width, 0, 0)
|
||||
s.setupwindow_drawbanner()
|
||||
s.logheight = s.c.height - 1 - len(s.af.keys())
|
||||
s.logwindow = curses.newwin(s.logheight, s.c.width, 1, 0)
|
||||
s.logwindow.idlok(1)
|
||||
s.logwindow.scrollok(1)
|
||||
s.logwindow.move(s.logheight - 1, 0)
|
||||
s.setupwindow_drawlog()
|
||||
accounts = s.af.keys()
|
||||
accounts.sort()
|
||||
accounts.reverse()
|
||||
|
||||
pos = s.c.height - 1
|
||||
index = 0
|
||||
s.hotkeys = []
|
||||
for account in accounts:
|
||||
accountwindow = curses.newwin(1, s.c.width, pos, 0)
|
||||
s.af[account].setwindow(accountwindow, acctkeys[index])
|
||||
s.hotkeys.append(account)
|
||||
index += 1
|
||||
pos -= 1
|
||||
|
||||
curses.doupdate()
|
||||
finally:
|
||||
s.c.unlock()
|
||||
|
||||
def setupwindow_drawbanner(s):
|
||||
if s.c.has_color:
|
||||
color = s.c.getpair(curses.COLOR_WHITE, curses.COLOR_BLUE) | \
|
||||
curses.A_BOLD
|
||||
else:
|
||||
color = curses.A_REVERSE
|
||||
s.bannerwindow.bkgd(' ', color) # Fill background with that color
|
||||
s.bannerwindow.addstr("%s %s" % (version.productname,
|
||||
version.versionstr))
|
||||
s.bannerwindow.addstr(0, s.bannerwindow.getmaxyx()[1] - len(version.copyright) - 1,
|
||||
version.copyright)
|
||||
|
||||
s.bannerwindow.noutrefresh()
|
||||
|
||||
def setupwindow_drawlog(s):
|
||||
if s.c.has_color:
|
||||
color = s.c.getpair(curses.COLOR_WHITE, curses.COLOR_BLACK)
|
||||
else:
|
||||
color = curses.A_NORMAL
|
||||
s.logwindow.bkgd(' ', color)
|
||||
for line, color in s.text:
|
||||
s.logwindow.addstr("\n" + line, color)
|
||||
s.logwindow.noutrefresh()
|
||||
|
||||
def getaccountframe(s, accountname = None):
|
||||
if accountname == None:
|
||||
accountname = s.getthreadaccount()
|
||||
s.aflock.acquire()
|
||||
try:
|
||||
if accountname in s.af:
|
||||
return s.af[accountname]
|
||||
|
||||
# New one.
|
||||
s.af[accountname] = CursesAccountFrame(s.c, accountname)
|
||||
s.c.lock()
|
||||
try:
|
||||
s.c.reset()
|
||||
s.setupwindows()
|
||||
finally:
|
||||
s.c.unlock()
|
||||
finally:
|
||||
s.aflock.release()
|
||||
return s.af[accountname]
|
||||
|
||||
|
||||
def _display(s, msg, color = None):
|
||||
if "\n" in msg:
|
||||
for thisline in msg.split("\n"):
|
||||
s._msg(thisline)
|
||||
return
|
||||
|
||||
# We must acquire both locks. Otherwise, deadlock can result.
|
||||
# This can happen if one thread calls _msg (locking curses, then
|
||||
# tf) and another tries to set the color (locking tf, then curses)
|
||||
#
|
||||
# By locking both up-front here, in this order, we prevent deadlock.
|
||||
|
||||
s.tflock.acquire()
|
||||
s.c.lock()
|
||||
try:
|
||||
if not s.c.isactive():
|
||||
# For dumping out exceptions and stuff.
|
||||
print msg
|
||||
return
|
||||
if color:
|
||||
s.gettf().setcolor(color)
|
||||
s._addline(msg, s.gettf().getcolorpair())
|
||||
s.logwindow.refresh()
|
||||
finally:
|
||||
s.c.unlock()
|
||||
s.tflock.release()
|
||||
|
||||
def _addline(s, msg, color):
|
||||
s.c.lock()
|
||||
try:
|
||||
s.logwindow.addstr("\n" + msg, color)
|
||||
s.text.append((msg, color))
|
||||
while len(s.text) > s.logheight:
|
||||
s.text = s.text[1:]
|
||||
finally:
|
||||
s.c.unlock()
|
||||
|
||||
def terminate(s, exitstatus = 0):
|
||||
s.c.stop()
|
||||
UIBase.terminate(s, exitstatus)
|
||||
|
||||
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)
|
||||
|
||||
if __name__ == '__main__':
|
||||
x = Blinkenlights(None)
|
||||
x.init_banner()
|
||||
import time
|
||||
time.sleep(5)
|
||||
x.c.stop()
|
||||
fgs = {'black': curses.COLOR_BLACK, 'red': curses.COLOR_RED,
|
||||
'green': curses.COLOR_GREEN, 'yellow': curses.COLOR_YELLOW,
|
||||
'blue': curses.COLOR_BLUE, 'magenta': curses.COLOR_MAGENTA,
|
||||
'cyan': curses.COLOR_CYAN, 'white': curses.COLOR_WHITE}
|
||||
|
||||
x = CursesUtil()
|
||||
win1 = curses.newwin(x.height, x.width / 4 - 1, 0, 0)
|
||||
win1.addstr("Black/normal\n")
|
||||
for name, fg in fgs.items():
|
||||
win1.addstr("%s\n" % name, x.getpair(fg, curses.COLOR_BLACK))
|
||||
win2 = curses.newwin(x.height, x.width / 4 - 1, 0, win1.getmaxyx()[1])
|
||||
win2.addstr("Blue/normal\n")
|
||||
for name, fg in fgs.items():
|
||||
win2.addstr("%s\n" % name, x.getpair(fg, curses.COLOR_BLUE))
|
||||
win3 = curses.newwin(x.height, x.width / 4 - 1, 0, win1.getmaxyx()[1] +
|
||||
win2.getmaxyx()[1])
|
||||
win3.addstr("Black/bright\n")
|
||||
for name, fg in fgs.items():
|
||||
win3.addstr("%s\n" % name, x.getpair(fg, curses.COLOR_BLACK) | \
|
||||
curses.A_BOLD)
|
||||
win4 = curses.newwin(x.height, x.width / 4 - 1, 0, win1.getmaxyx()[1] * 3)
|
||||
win4.addstr("Blue/bright\n")
|
||||
for name, fg in fgs.items():
|
||||
win4.addstr("%s\n" % name, x.getpair(fg, curses.COLOR_BLUE) | \
|
||||
curses.A_BOLD)
|
||||
|
||||
|
||||
win1.refresh()
|
||||
win2.refresh()
|
||||
win3.refresh()
|
||||
win4.refresh()
|
||||
x.stdscr.refresh()
|
||||
import time
|
||||
time.sleep(5)
|
||||
x.stop()
|
||||
print x.has_color
|
||||
print x.height
|
||||
print x.width
|
||||
|
48
offlineimap/ui/Noninteractive.py
Normal file
48
offlineimap/ui/Noninteractive.py
Normal file
@ -0,0 +1,48 @@
|
||||
# Noninteractive UI
|
||||
# Copyright (C) 2002 John Goerzen
|
||||
# <jgoerzen@complete.org>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
import sys, time
|
||||
from UIBase import UIBase
|
||||
|
||||
class Basic(UIBase):
|
||||
def getpass(s, accountname, config, errmsg = None):
|
||||
raise NotImplementedError, "Prompting for a password is not supported in noninteractive mode."
|
||||
|
||||
def _display(s, msg):
|
||||
print msg
|
||||
sys.stdout.flush()
|
||||
|
||||
def warn(s, msg, minor = 0):
|
||||
warntxt = 'WARNING'
|
||||
if minor:
|
||||
warntxt = 'warning'
|
||||
sys.stderr.write(warntxt + ": " + str(msg) + "\n")
|
||||
|
||||
def sleep(s, sleepsecs):
|
||||
if s.verbose >= 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
|
||||
|
||||
class Quiet(Basic):
|
||||
def __init__(s, config, verbose = -1):
|
||||
Basic.__init__(s, config, verbose)
|
60
offlineimap/ui/TTY.py
Normal file
60
offlineimap/ui/TTY.py
Normal file
@ -0,0 +1,60 @@
|
||||
# TTY UI
|
||||
# Copyright (C) 2002 John Goerzen
|
||||
# <jgoerzen@complete.org>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
from UIBase import UIBase
|
||||
from getpass import getpass
|
||||
import select, sys
|
||||
from threading import *
|
||||
|
||||
class TTYUI(UIBase):
|
||||
def __init__(s, config, verbose = 0):
|
||||
UIBase.__init__(s, config, verbose)
|
||||
s.iswaiting = 0
|
||||
s.outputlock = Lock()
|
||||
|
||||
def isusable(s):
|
||||
return sys.stdout.isatty() and sys.stdin.isatty()
|
||||
|
||||
def _display(s, msg):
|
||||
s.outputlock.acquire()
|
||||
try:
|
||||
if (currentThread().getName() == 'MainThread'):
|
||||
print msg
|
||||
else:
|
||||
print "%s:\n %s" % (currentThread().getName(), msg)
|
||||
sys.stdout.flush()
|
||||
finally:
|
||||
s.outputlock.release()
|
||||
|
||||
def getpass(s, accountname, config, errmsg = 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)
|
||||
|
536
offlineimap/ui/Tk.py
Normal file
536
offlineimap/ui/Tk.py
Normal file
@ -0,0 +1,536 @@
|
||||
# Tk UI
|
||||
# Copyright (C) 2002, 2003 John Goerzen
|
||||
# <jgoerzen@complete.org>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
from __future__ import nested_scopes
|
||||
|
||||
from Tkinter import *
|
||||
import tkFont
|
||||
from threading import *
|
||||
import thread, traceback, time, threading
|
||||
from StringIO import StringIO
|
||||
from ScrolledText import ScrolledText
|
||||
from offlineimap import threadutil, version
|
||||
from Queue import Queue
|
||||
from UIBase import UIBase
|
||||
from offlineimap.ui.Blinkenlights import BlinkenBase
|
||||
|
||||
usabletest = None
|
||||
|
||||
class PasswordDialog:
|
||||
def __init__(self, accountname, config, master=None, errmsg = None):
|
||||
self.top = Toplevel(master)
|
||||
self.top.title(version.productname + " Password Entry")
|
||||
text = ''
|
||||
if errmsg:
|
||||
text = '%s: %s\n' % (accountname, errmsg)
|
||||
text += "%s: Enter password: " % accountname
|
||||
self.label = Label(self.top, text = text)
|
||||
self.label.pack()
|
||||
|
||||
self.entry = Entry(self.top, show='*')
|
||||
self.entry.bind("<Return>", self.ok)
|
||||
self.entry.pack()
|
||||
self.entry.focus_force()
|
||||
|
||||
self.button = Button(self.top, text = "OK", command=self.ok)
|
||||
self.button.pack()
|
||||
|
||||
self.entry.focus_force()
|
||||
self.top.wait_window(self.label)
|
||||
|
||||
def ok(self, args = None):
|
||||
self.password = self.entry.get()
|
||||
self.top.destroy()
|
||||
|
||||
def getpassword(self):
|
||||
return self.password
|
||||
|
||||
class TextOKDialog:
|
||||
def __init__(self, title, message, blocking = 1, master = None):
|
||||
if not master:
|
||||
self.top = Tk()
|
||||
else:
|
||||
self.top = Toplevel(master)
|
||||
self.top.title(title)
|
||||
self.text = ScrolledText(self.top, font = "Courier 10")
|
||||
self.text.pack()
|
||||
self.text.insert(END, message)
|
||||
self.text['state'] = DISABLED
|
||||
self.button = Button(self.top, text = "OK", command=self.ok)
|
||||
self.button.pack()
|
||||
|
||||
if blocking:
|
||||
self.top.wait_window(self.button)
|
||||
|
||||
def ok(self):
|
||||
self.top.destroy()
|
||||
|
||||
|
||||
|
||||
class ThreadFrame(Frame):
|
||||
def __init__(self, master=None):
|
||||
self.threadextraframe = None
|
||||
self.thread = currentThread()
|
||||
self.threadid = thread.get_ident()
|
||||
Frame.__init__(self, master, relief = RIDGE, borderwidth = 2)
|
||||
self.pack(fill = 'x')
|
||||
self.threadlabel = Label(self, foreground = '#FF0000',
|
||||
text ="Thread %d (%s)" % (self.threadid,
|
||||
self.thread.getName()))
|
||||
self.threadlabel.pack()
|
||||
self.setthread(currentThread())
|
||||
|
||||
self.account = "Unknown"
|
||||
self.mailbox = "Unknown"
|
||||
self.loclabel = Label(self,
|
||||
text = "Account/mailbox information unknown")
|
||||
#self.loclabel.pack()
|
||||
|
||||
self.updateloclabel()
|
||||
|
||||
self.message = Label(self, text="Messages will appear here.\n",
|
||||
foreground = '#0000FF')
|
||||
self.message.pack(fill = 'x')
|
||||
|
||||
def setthread(self, newthread):
|
||||
if newthread:
|
||||
self.threadlabel['text'] = newthread.getName()
|
||||
else:
|
||||
self.threadlabel['text'] = "No thread"
|
||||
self.destroythreadextraframe()
|
||||
|
||||
def destroythreadextraframe(self):
|
||||
if self.threadextraframe:
|
||||
self.threadextraframe.destroy()
|
||||
self.threadextraframe = None
|
||||
|
||||
def getthreadextraframe(self):
|
||||
if self.threadextraframe:
|
||||
return self.threadextraframe
|
||||
self.threadextraframe = Frame(self)
|
||||
self.threadextraframe.pack(fill = 'x')
|
||||
return self.threadextraframe
|
||||
|
||||
def setaccount(self, account):
|
||||
self.account = account
|
||||
self.mailbox = "Unknown"
|
||||
self.updateloclabel()
|
||||
|
||||
def setmailbox(self, mailbox):
|
||||
self.mailbox = mailbox
|
||||
self.updateloclabel()
|
||||
|
||||
def updateloclabel(self):
|
||||
self.loclabel['text'] = "Processing %s: %s" % (self.account,
|
||||
self.mailbox)
|
||||
|
||||
def appendmessage(self, newtext):
|
||||
self.message['text'] += "\n" + newtext
|
||||
|
||||
def setmessage(self, newtext):
|
||||
self.message['text'] = newtext
|
||||
|
||||
|
||||
class VerboseUI(UIBase):
|
||||
def isusable(s):
|
||||
global usabletest
|
||||
if usabletest != None:
|
||||
return usabletest
|
||||
|
||||
try:
|
||||
Tk().destroy()
|
||||
usabletest = 1
|
||||
except TclError:
|
||||
usabletest = 0
|
||||
return usabletest
|
||||
|
||||
def _createTopWindow(self, doidlevac = 1):
|
||||
self.notdeleted = 1
|
||||
self.created = threading.Event()
|
||||
|
||||
self.af = {}
|
||||
self.aflock = Lock()
|
||||
|
||||
t = threadutil.ExitNotifyThread(target = self._runmainloop,
|
||||
name = "Tk Mainloop")
|
||||
t.setDaemon(1)
|
||||
t.start()
|
||||
|
||||
self.created.wait()
|
||||
del self.created
|
||||
|
||||
if doidlevac:
|
||||
t = threadutil.ExitNotifyThread(target = self.idlevacuum,
|
||||
name = "Tk idle vacuum")
|
||||
t.setDaemon(1)
|
||||
t.start()
|
||||
|
||||
def _runmainloop(s):
|
||||
s.top = Tk()
|
||||
s.top.title(version.productname + " " + version.versionstr)
|
||||
s.top.after_idle(s.created.set)
|
||||
s.top.mainloop()
|
||||
s.notdeleted = 0
|
||||
|
||||
def getaccountframe(s):
|
||||
accountname = s.getthreadaccount()
|
||||
s.aflock.acquire()
|
||||
try:
|
||||
if accountname in s.af:
|
||||
return s.af[accountname]
|
||||
|
||||
s.af[accountname] = LEDAccountFrame(s.top, accountname,
|
||||
s.fontfamily, s.fontsize)
|
||||
finally:
|
||||
s.aflock.release()
|
||||
return s.af[accountname]
|
||||
|
||||
def getpass(s, accountname, config, errmsg = None):
|
||||
pd = PasswordDialog(accountname, config, errmsg = errmsg)
|
||||
return pd.getpassword()
|
||||
|
||||
def gettf(s, newtype=ThreadFrame, master = None):
|
||||
if master == None:
|
||||
master = s.top
|
||||
threadid = thread.get_ident()
|
||||
s.tflock.acquire()
|
||||
try:
|
||||
if threadid in s.threadframes:
|
||||
return s.threadframes[threadid]
|
||||
if len(s.availablethreadframes):
|
||||
tf = s.availablethreadframes.pop(0)
|
||||
tf.setthread(currentThread())
|
||||
else:
|
||||
tf = newtype(master)
|
||||
s.threadframes[threadid] = tf
|
||||
return tf
|
||||
finally:
|
||||
s.tflock.release()
|
||||
|
||||
def _display(s, msg):
|
||||
s.gettf().setmessage(msg)
|
||||
|
||||
def threadExited(s, thread):
|
||||
threadid = thread.threadid
|
||||
s.tflock.acquire()
|
||||
if threadid in s.threadframes:
|
||||
tf = s.threadframes[threadid]
|
||||
tf.setthread(None)
|
||||
tf.setaccount("Unknown")
|
||||
tf.setmessage("Idle")
|
||||
s.availablethreadframes.append(tf)
|
||||
del s.threadframes[threadid]
|
||||
s.tflock.release()
|
||||
UIBase.threadExited(s, thread)
|
||||
|
||||
def idlevacuum(s):
|
||||
while s.notdeleted:
|
||||
time.sleep(10)
|
||||
s.tflock.acquire()
|
||||
while len(s.availablethreadframes):
|
||||
tf = s.availablethreadframes.pop()
|
||||
tf.destroy()
|
||||
s.tflock.release()
|
||||
|
||||
def threadException(s, thread):
|
||||
exceptionstr = s.getThreadExceptionString(thread)
|
||||
print exceptionstr
|
||||
|
||||
s.top.destroy()
|
||||
s.top = None
|
||||
TextOKDialog("Thread Exception", exceptionstr)
|
||||
s.delThreadDebugLog(thread)
|
||||
s.terminate(100)
|
||||
|
||||
def mainException(s):
|
||||
exceptionstr = s.getMainExceptionString()
|
||||
print exceptionstr
|
||||
|
||||
s.top.destroy()
|
||||
s.top = None
|
||||
TextOKDialog("Main Program Exception", exceptionstr)
|
||||
|
||||
def warn(s, msg, minor = 0):
|
||||
if minor:
|
||||
# Just let the default handler catch it
|
||||
UIBase.warn(s, msg, minor)
|
||||
else:
|
||||
TextOKDialog("OfflineIMAP Warning", msg)
|
||||
|
||||
def showlicense(s):
|
||||
TextOKDialog(version.productname + " License",
|
||||
version.bigcopyright + "\n" +
|
||||
version.homepage + "\n\n" + version.license,
|
||||
blocking = 0, master = s.top)
|
||||
|
||||
|
||||
def init_banner(s):
|
||||
s.threadframes = {}
|
||||
s.availablethreadframes = []
|
||||
s.tflock = Lock()
|
||||
s._createTopWindow()
|
||||
s._msg(version.productname + " " + version.versionstr + ", " +\
|
||||
version.copyright)
|
||||
tf = s.gettf().getthreadextraframe()
|
||||
|
||||
b = Button(tf, text = "About", command = s.showlicense)
|
||||
b.pack(side = LEFT)
|
||||
|
||||
b = Button(tf, text = "Exit", command = s.terminate)
|
||||
b.pack(side = RIGHT)
|
||||
s.sleeping_abort = {}
|
||||
|
||||
def deletingmessages(s, uidlist, destlist):
|
||||
ds = s.folderlist(destlist)
|
||||
s._msg("Deleting %d messages in %s" % (len(uidlist), ds))
|
||||
|
||||
def _sleep_cancel(s, args = None):
|
||||
s.sleeping_abort[thread.get_ident()] = 1
|
||||
|
||||
def sleep(s, sleepsecs):
|
||||
threadid = thread.get_ident()
|
||||
s.sleeping_abort[threadid] = 0
|
||||
tf = s.gettf().getthreadextraframe()
|
||||
|
||||
def sleep_cancel():
|
||||
s.sleeping_abort[threadid] = 1
|
||||
|
||||
sleepbut = Button(tf, text = 'Sync immediately',
|
||||
command = sleep_cancel)
|
||||
sleepbut.pack()
|
||||
UIBase.sleep(s, sleepsecs)
|
||||
|
||||
def sleeping(s, sleepsecs, remainingsecs):
|
||||
retval = s.sleeping_abort[thread.get_ident()]
|
||||
if remainingsecs:
|
||||
s._msg("Next sync in %d:%02d" % (remainingsecs / 60,
|
||||
remainingsecs % 60))
|
||||
else:
|
||||
s._msg("Wait done; synchronizing now.")
|
||||
s.gettf().destroythreadextraframe()
|
||||
del s.sleeping_abort[thread.get_ident()]
|
||||
time.sleep(sleepsecs)
|
||||
return retval
|
||||
|
||||
TkUI = VerboseUI
|
||||
|
||||
################################################## Blinkenlights
|
||||
|
||||
class LEDAccountFrame:
|
||||
def __init__(self, top, accountname, fontfamily, fontsize):
|
||||
self.top = top
|
||||
self.accountname = accountname
|
||||
self.fontfamily = fontfamily
|
||||
self.fontsize = fontsize
|
||||
self.frame = Frame(self.top, background = 'black')
|
||||
self.frame.pack(side = BOTTOM, expand = 1, fill = X)
|
||||
self._createcanvas(self.frame)
|
||||
|
||||
self.label = Label(self.frame, text = accountname,
|
||||
background = "black", foreground = "blue",
|
||||
font = (self.fontfamily, self.fontsize))
|
||||
self.label.grid(sticky = E, row = 0, column = 1)
|
||||
|
||||
def getnewthreadframe(s):
|
||||
return LEDThreadFrame(s.canvas)
|
||||
|
||||
def _createcanvas(self, parent):
|
||||
c = LEDFrame(parent)
|
||||
self.canvas = c
|
||||
c.grid(sticky = E, row = 0, column = 0)
|
||||
parent.grid_columnconfigure(1, weight = 1)
|
||||
#c.pack(side = LEFT, expand = 0, fill = X)
|
||||
|
||||
def startsleep(s, sleepsecs):
|
||||
s.sleeping_abort = 0
|
||||
s.button = Button(s.frame, text = "Sync now", command = s.syncnow,
|
||||
background = "black", activebackground = "black",
|
||||
activeforeground = "white",
|
||||
foreground = "blue", highlightthickness = 0,
|
||||
padx = 0, pady = 0,
|
||||
font = (s.fontfamily, s.fontsize), borderwidth = 0,
|
||||
relief = 'solid')
|
||||
s.button.grid(sticky = E, row = 0, column = 2)
|
||||
|
||||
def syncnow(s):
|
||||
s.sleeping_abort = 1
|
||||
|
||||
def sleeping(s, sleepsecs, remainingsecs):
|
||||
if remainingsecs:
|
||||
s.button.config(text = 'Sync now (%d:%02d remain)' % \
|
||||
(remainingsecs / 60, remainingsecs % 60))
|
||||
time.sleep(sleepsecs)
|
||||
else:
|
||||
s.button.destroy()
|
||||
del s.button
|
||||
return s.sleeping_abort
|
||||
|
||||
class LEDFrame(Frame):
|
||||
"""This holds the different lights."""
|
||||
def getnewobj(self):
|
||||
retval = Canvas(self, background = 'black', height = 20, bd = 0,
|
||||
highlightthickness = 0, width = 10)
|
||||
retval.pack(side = LEFT, padx = 0, pady = 0, ipadx = 0, ipady = 0)
|
||||
return retval
|
||||
|
||||
class LEDThreadFrame:
|
||||
"""There is one of these for each little light."""
|
||||
def __init__(self, master):
|
||||
self.canvas = master.getnewobj()
|
||||
self.color = ''
|
||||
self.ovalid = self.canvas.create_oval(4, 4, 9,
|
||||
9, fill = 'gray',
|
||||
outline = '#303030')
|
||||
|
||||
def setcolor(self, newcolor):
|
||||
if newcolor != self.color:
|
||||
self.canvas.itemconfigure(self.ovalid, fill = newcolor)
|
||||
self.color = newcolor
|
||||
|
||||
def getcolor(self):
|
||||
return self.color
|
||||
|
||||
def setthread(self, newthread):
|
||||
if newthread:
|
||||
self.setcolor('gray')
|
||||
else:
|
||||
self.setcolor('black')
|
||||
|
||||
|
||||
class Blinkenlights(BlinkenBase, VerboseUI):
|
||||
def __init__(s, config, verbose = 0):
|
||||
VerboseUI.__init__(s, config, verbose)
|
||||
s.fontfamily = 'Helvetica'
|
||||
s.fontsize = 8
|
||||
if config.has_option('ui.Tk.Blinkenlights', 'fontfamily'):
|
||||
s.fontfamily = config.get('ui.Tk.Blinkenlights', 'fontfamily')
|
||||
if config.has_option('ui.Tk.Blinkenlights', 'fontsize'):
|
||||
s.fontsize = config.getint('ui.Tk.Blinkenlights', 'fontsize')
|
||||
|
||||
def isusable(s):
|
||||
return VerboseUI.isusable(s)
|
||||
|
||||
def _createTopWindow(self):
|
||||
VerboseUI._createTopWindow(self, 0)
|
||||
#self.top.resizable(width = 0, height = 0)
|
||||
self.top.configure(background = 'black', bd = 0)
|
||||
|
||||
widthmetric = tkFont.Font(family = self.fontfamily, size = self.fontsize).measure("0")
|
||||
self.loglines = self.config.getdefaultint("ui.Tk.Blinkenlights",
|
||||
"loglines", 5)
|
||||
self.bufferlines = self.config.getdefaultint("ui.Tk.Blinkenlights",
|
||||
"bufferlines", 500)
|
||||
self.text = ScrolledText(self.top, bg = 'black', #scrollbar = 'y',
|
||||
font = (self.fontfamily, self.fontsize),
|
||||
bd = 0, highlightthickness = 0, setgrid = 0,
|
||||
state = DISABLED, height = self.loglines,
|
||||
wrap = NONE, width = 60)
|
||||
self.text.vbar.configure(background = '#000050',
|
||||
activebackground = 'blue',
|
||||
highlightbackground = 'black',
|
||||
troughcolor = "black", bd = 0,
|
||||
elementborderwidth = 2)
|
||||
|
||||
self.textenabled = 0
|
||||
self.tags = []
|
||||
self.textlock = Lock()
|
||||
|
||||
def init_banner(s):
|
||||
BlinkenBase.init_banner(s)
|
||||
s._createTopWindow()
|
||||
menubar = Menu(s.top, activebackground = "black",
|
||||
activeforeground = "white",
|
||||
activeborderwidth = 0,
|
||||
background = "black", foreground = "blue",
|
||||
font = (s.fontfamily, s.fontsize), bd = 0)
|
||||
menubar.add_command(label = "About", command = s.showlicense)
|
||||
menubar.add_command(label = "Show Log", command = s._togglelog)
|
||||
menubar.add_command(label = "Exit", command = s.terminate)
|
||||
s.top.config(menu = menubar)
|
||||
s.menubar = menubar
|
||||
s.text.see(END)
|
||||
if s.config.getdefaultboolean("ui.Tk.Blinkenlights", "showlog", 1):
|
||||
s._togglelog()
|
||||
s.gettf().setcolor('red')
|
||||
s.top.resizable(width = 0, height = 0)
|
||||
s._msg(version.banner)
|
||||
|
||||
def _togglelog(s):
|
||||
if s.textenabled:
|
||||
s.oldtextheight = s.text.winfo_height()
|
||||
s.text.pack_forget()
|
||||
s.textenabled = 0
|
||||
s.menubar.entryconfig('Hide Log', label = 'Show Log')
|
||||
s.top.update()
|
||||
s.top.geometry("")
|
||||
s.top.update()
|
||||
s.top.resizable(width = 0, height = 0)
|
||||
s.top.update()
|
||||
|
||||
else:
|
||||
s.text.pack(side = TOP, expand = 1, fill = BOTH)
|
||||
s.textenabled = 1
|
||||
s.top.update()
|
||||
s.top.geometry("")
|
||||
s.menubar.entryconfig('Show Log', label = 'Hide Log')
|
||||
s._rescroll()
|
||||
s.top.resizable(width = 1, height = 1)
|
||||
|
||||
def sleep(s, sleepsecs):
|
||||
s.gettf().setcolor('red')
|
||||
s._msg("Next sync in %d:%02d" % (sleepsecs / 60, sleepsecs % 60))
|
||||
BlinkenBase.sleep(s, sleepsecs)
|
||||
|
||||
def sleeping(s, sleepsecs, remainingsecs):
|
||||
return BlinkenBase.sleeping(s, sleepsecs, remainingsecs)
|
||||
|
||||
def _rescroll(s):
|
||||
s.text.see(END)
|
||||
lo, hi = s.text.vbar.get()
|
||||
s.text.vbar.set(1.0 - (hi - lo), 1.0)
|
||||
|
||||
def _display(s, msg):
|
||||
if "\n" in msg:
|
||||
for thisline in msg.split("\n"):
|
||||
s._msg(thisline)
|
||||
return
|
||||
#VerboseUI._msg(s, msg)
|
||||
color = s.gettf().getcolor()
|
||||
rescroll = 1
|
||||
s.textlock.acquire()
|
||||
try:
|
||||
if s.text.vbar.get()[1] != 1.0:
|
||||
rescroll = 0
|
||||
s.text.config(state = NORMAL)
|
||||
if not color in s.tags:
|
||||
s.text.tag_config(color, foreground = color)
|
||||
s.tags.append(color)
|
||||
s.text.insert(END, "\n" + msg, color)
|
||||
|
||||
# Trim down. Not quite sure why I have to say 7 instead of 5,
|
||||
# but so it is.
|
||||
while float(s.text.index(END)) > s.bufferlines + 2.0:
|
||||
s.text.delete(1.0, 2.0)
|
||||
|
||||
if rescroll:
|
||||
s._rescroll()
|
||||
finally:
|
||||
s.text.config(state = DISABLED)
|
||||
s.textlock.release()
|
||||
|
||||
|
341
offlineimap/ui/UIBase.py
Normal file
341
offlineimap/ui/UIBase.py
Normal file
@ -0,0 +1,341 @@
|
||||
# UI base class
|
||||
# Copyright (C) 2002 John Goerzen
|
||||
# <jgoerzen@complete.org>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
import offlineimap.version
|
||||
import re, time, sys, traceback, threading, thread
|
||||
from StringIO import StringIO
|
||||
|
||||
debugtypes = {'imap': 'IMAP protocol debugging',
|
||||
'maildir': 'Maildir repository debugging',
|
||||
'thread': 'Threading debugging'}
|
||||
|
||||
globalui = None
|
||||
def setglobalui(newui):
|
||||
global globalui
|
||||
globalui = newui
|
||||
def getglobalui():
|
||||
global globalui
|
||||
return globalui
|
||||
|
||||
class UIBase:
|
||||
def __init__(s, config, verbose = 0):
|
||||
s.verbose = verbose
|
||||
s.config = config
|
||||
s.debuglist = []
|
||||
s.debugmessages = {}
|
||||
s.debugmsglen = 50
|
||||
s.threadaccounts = {}
|
||||
s.logfile = None
|
||||
|
||||
################################################## UTILS
|
||||
def _msg(s, msg):
|
||||
"""Generic tool called when no other works."""
|
||||
s._log(msg)
|
||||
s._display(msg)
|
||||
|
||||
def _log(s, msg):
|
||||
"""Log it to disk. Returns true if it wrote something; false
|
||||
otherwise."""
|
||||
if s.logfile:
|
||||
s.logfile.write("%s: %s\n" % (threading.currentThread().getName(),
|
||||
msg))
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def setlogfd(s, logfd):
|
||||
s.logfile = logfd
|
||||
logfd.write("This is %s %s %s\n" % \
|
||||
(offlineimap.version.productname,
|
||||
offlineimap.version.versionstr,
|
||||
offlineimap.version.revstr))
|
||||
logfd.write("Python: %s\n" % sys.version)
|
||||
logfd.write("Platform: %s\n" % sys.platform)
|
||||
logfd.write("Args: %s\n" % sys.argv)
|
||||
|
||||
def _display(s, msg):
|
||||
"""Display a message."""
|
||||
raise NotImplementedError
|
||||
|
||||
def warn(s, msg, minor = 0):
|
||||
if minor:
|
||||
s._msg("warning: " + msg)
|
||||
else:
|
||||
s._msg("WARNING: " + msg)
|
||||
|
||||
def registerthread(s, account):
|
||||
"""Provides a hint to UIs about which account this particular
|
||||
thread is processing."""
|
||||
if s.threadaccounts.has_key(threading.currentThread()):
|
||||
raise ValueError, "Thread %s already registered (old %s, new %s)" %\
|
||||
(threading.currentThread().getName(),
|
||||
s.getthreadaccount(s), account)
|
||||
s.threadaccounts[threading.currentThread()] = account
|
||||
|
||||
def unregisterthread(s, thr):
|
||||
"""Recognizes a thread has exited."""
|
||||
if s.threadaccounts.has_key(thr):
|
||||
del s.threadaccounts[thr]
|
||||
|
||||
def getthreadaccount(s, thr = None):
|
||||
if not thr:
|
||||
thr = threading.currentThread()
|
||||
if s.threadaccounts.has_key(thr):
|
||||
return s.threadaccounts[thr]
|
||||
return '*Control'
|
||||
|
||||
def debug(s, debugtype, msg):
|
||||
thisthread = threading.currentThread()
|
||||
if s.debugmessages.has_key(thisthread):
|
||||
s.debugmessages[thisthread].append("%s: %s" % (debugtype, msg))
|
||||
else:
|
||||
s.debugmessages[thisthread] = ["%s: %s" % (debugtype, msg)]
|
||||
|
||||
while len(s.debugmessages[thisthread]) > s.debugmsglen:
|
||||
s.debugmessages[thisthread] = s.debugmessages[thisthread][1:]
|
||||
|
||||
if debugtype in s.debuglist:
|
||||
if not s._log("DEBUG[%s]: %s" % (debugtype, msg)):
|
||||
s._display("DEBUG[%s]: %s" % (debugtype, msg))
|
||||
|
||||
def add_debug(s, debugtype):
|
||||
global debugtypes
|
||||
if debugtype in debugtypes:
|
||||
if not debugtype in s.debuglist:
|
||||
s.debuglist.append(debugtype)
|
||||
s.debugging(debugtype)
|
||||
else:
|
||||
s.invaliddebug(debugtype)
|
||||
|
||||
def debugging(s, debugtype):
|
||||
global debugtypes
|
||||
s._msg("Now debugging for %s: %s" % (debugtype, debugtypes[debugtype]))
|
||||
|
||||
def invaliddebug(s, debugtype):
|
||||
s.warn("Invalid debug type: %s" % debugtype)
|
||||
|
||||
def locked(s):
|
||||
raise Exception, "Another OfflineIMAP is running with the same metadatadir; exiting."
|
||||
|
||||
def getnicename(s, object):
|
||||
prelimname = str(object.__class__).split('.')[-1]
|
||||
# Strip off extra stuff.
|
||||
return re.sub('(Folder|Repository)', '', prelimname)
|
||||
|
||||
def isusable(s):
|
||||
"""Returns true if this UI object is usable in the current
|
||||
environment. For instance, an X GUI would return true if it's
|
||||
being run in X with a valid DISPLAY setting, and false otherwise."""
|
||||
return 1
|
||||
|
||||
################################################## INPUT
|
||||
|
||||
def getpass(s, accountname, config, errmsg = None):
|
||||
raise NotImplementedError
|
||||
|
||||
def folderlist(s, list):
|
||||
return ', '.join(["%s[%s]" % (s.getnicename(x), x.getname()) for x in list])
|
||||
|
||||
################################################## WARNINGS
|
||||
def msgtoreadonly(s, destfolder, uid, content, flags):
|
||||
if not (config.has_option('general', 'ignore-readonly') and config.getboolean("general", "ignore-readonly")):
|
||||
s.warn("Attempted to synchronize message %d to folder %s[%s], but that folder is read-only. The message will not be copied to that folder." % \
|
||||
(uid, s.getnicename(destfolder), destfolder.getname()))
|
||||
|
||||
def flagstoreadonly(s, destfolder, uidlist, flags):
|
||||
if not (config.has_option('general', 'ignore-readonly') and config.getboolean("general", "ignore-readonly")):
|
||||
s.warn("Attempted to modify flags for messages %s in folder %s[%s], but that folder is read-only. No flags have been modified for that message." % \
|
||||
(str(uidlist), s.getnicename(destfolder), destfolder.getname()))
|
||||
|
||||
def deletereadonly(s, destfolder, uidlist):
|
||||
if not (config.has_option('general', 'ignore-readonly') and config.getboolean("general", "ignore-readonly")):
|
||||
s.warn("Attempted to delete messages %s in folder %s[%s], but that folder is read-only. No messages have been deleted in that folder." % \
|
||||
(str(uidlist), s.getnicename(destfolder), destfolder.getname()))
|
||||
|
||||
################################################## MESSAGES
|
||||
|
||||
def init_banner(s):
|
||||
"""Called when the UI starts. Must be called before any other UI
|
||||
call except isusable(). Displays the copyright banner. This is
|
||||
where the UI should do its setup -- TK, for instance, would
|
||||
create the application window here."""
|
||||
if s.verbose >= 0:
|
||||
s._msg(offlineimap.version.banner)
|
||||
|
||||
def connecting(s, hostname, port):
|
||||
if s.verbose < 0:
|
||||
return
|
||||
if hostname == None:
|
||||
hostname = ''
|
||||
if port != None:
|
||||
port = ":%s" % str(port)
|
||||
displaystr = ' to %s%s.' % (hostname, port)
|
||||
if hostname == '' and port == None:
|
||||
displaystr = '.'
|
||||
s._msg("Establishing connection" + displaystr)
|
||||
|
||||
def acct(s, accountname):
|
||||
if s.verbose >= 0:
|
||||
s._msg("***** Processing account %s" % accountname)
|
||||
|
||||
def acctdone(s, accountname):
|
||||
if s.verbose >= 0:
|
||||
s._msg("***** Finished processing account " + accountname)
|
||||
|
||||
def syncfolders(s, srcrepos, destrepos):
|
||||
if s.verbose >= 0:
|
||||
s._msg("Copying folder structure from %s to %s" % \
|
||||
(s.getnicename(srcrepos), s.getnicename(destrepos)))
|
||||
|
||||
############################## Folder syncing
|
||||
def syncingfolder(s, srcrepos, srcfolder, destrepos, destfolder):
|
||||
"""Called when a folder sync operation is started."""
|
||||
if s.verbose >= 0:
|
||||
s._msg("Syncing %s: %s -> %s" % (srcfolder.getname(),
|
||||
s.getnicename(srcrepos),
|
||||
s.getnicename(destrepos)))
|
||||
|
||||
def validityproblem(s, folder, saved, new):
|
||||
s.warn("UID validity problem for folder %s (saved %d; got %d); skipping it" % \
|
||||
(folder.getname(), saved, new))
|
||||
|
||||
def loadmessagelist(s, repos, folder):
|
||||
if s.verbose > 0:
|
||||
s._msg("Loading message list for %s[%s]" % (s.getnicename(repos),
|
||||
folder.getname()))
|
||||
|
||||
def messagelistloaded(s, repos, folder, count):
|
||||
if s.verbose > 0:
|
||||
s._msg("Message list for %s[%s] loaded: %d messages" % \
|
||||
(s.getnicename(repos), folder.getname(), count))
|
||||
|
||||
############################## Message syncing
|
||||
|
||||
def syncingmessages(s, sr, sf, dr, df):
|
||||
if s.verbose > 0:
|
||||
s._msg("Syncing messages %s[%s] -> %s[%s]" % (s.getnicename(sr),
|
||||
sf.getname(),
|
||||
s.getnicename(dr),
|
||||
df.getname()))
|
||||
|
||||
def copyingmessage(s, uid, src, destlist):
|
||||
if s.verbose >= 0:
|
||||
ds = s.folderlist(destlist)
|
||||
s._msg("Copy message %d %s[%s] -> %s" % (uid, s.getnicename(src),
|
||||
src.getname(), ds))
|
||||
|
||||
def deletingmessage(s, uid, destlist):
|
||||
if s.verbose >= 0:
|
||||
ds = s.folderlist(destlist)
|
||||
s._msg("Deleting message %d in %s" % (uid, ds))
|
||||
|
||||
def deletingmessages(s, uidlist, destlist):
|
||||
if s.verbose >= 0:
|
||||
ds = s.folderlist(destlist)
|
||||
s._msg("Deleting %d messages (%s) in %s" % \
|
||||
(len(uidlist),
|
||||
", ".join([str(u) for u in uidlist]),
|
||||
ds))
|
||||
|
||||
def addingflags(s, uidlist, flags, destlist):
|
||||
if s.verbose >= 0:
|
||||
ds = s.folderlist(destlist)
|
||||
s._msg("Adding flags %s to %d messages on %s" % \
|
||||
(", ".join(flags), len(uidlist), ds))
|
||||
|
||||
def deletingflags(s, uidlist, flags, destlist):
|
||||
if s.verbose >= 0:
|
||||
ds = s.folderlist(destlist)
|
||||
s._msg("Deleting flags %s to %d messages on %s" % \
|
||||
(", ".join(flags), len(uidlist), ds))
|
||||
|
||||
################################################## Threads
|
||||
|
||||
def getThreadDebugLog(s, thread):
|
||||
if s.debugmessages.has_key(thread):
|
||||
message = "\nLast %d debug messages logged for %s prior to exception:\n"\
|
||||
% (len(s.debugmessages[thread]), thread.getName())
|
||||
message += "\n".join(s.debugmessages[thread])
|
||||
else:
|
||||
message = "\nNo debug messages were logged for %s." % \
|
||||
thread.getName()
|
||||
return message
|
||||
|
||||
def delThreadDebugLog(s, thread):
|
||||
if s.debugmessages.has_key(thread):
|
||||
del s.debugmessages[thread]
|
||||
|
||||
def getThreadExceptionString(s, thread):
|
||||
message = "Thread '%s' terminated with exception:\n%s" % \
|
||||
(thread.getName(), thread.getExitStackTrace())
|
||||
message += "\n" + s.getThreadDebugLog(thread)
|
||||
return message
|
||||
|
||||
def threadException(s, thread):
|
||||
"""Called when a thread has terminated with an exception.
|
||||
The argument is the ExitNotifyThread that has so terminated."""
|
||||
s._msg(s.getThreadExceptionString(thread))
|
||||
s.delThreadDebugLog(thread)
|
||||
s.terminate(100)
|
||||
|
||||
def getMainExceptionString(s):
|
||||
sbuf = StringIO()
|
||||
traceback.print_exc(file = sbuf)
|
||||
return "Main program terminated with exception:\n" + \
|
||||
sbuf.getvalue() + "\n" + \
|
||||
s.getThreadDebugLog(threading.currentThread())
|
||||
|
||||
def mainException(s):
|
||||
s._msg(s.getMainExceptionString())
|
||||
|
||||
def terminate(s, exitstatus = 0):
|
||||
"""Called to terminate the application."""
|
||||
sys.exit(exitstatus)
|
||||
|
||||
def threadExited(s, thread):
|
||||
"""Called when a thread has exited normally. Many UIs will
|
||||
just ignore this."""
|
||||
s.delThreadDebugLog(thread)
|
||||
s.unregisterthread(thread)
|
||||
|
||||
################################################## Other
|
||||
|
||||
def sleep(s, sleepsecs):
|
||||
"""This function does not actually output anything, but handles
|
||||
the overall sleep, dealing with updates as necessary. It will,
|
||||
however, call sleeping() which DOES output something.
|
||||
|
||||
Returns 0 if timeout expired, 1 if there is a request to cancel
|
||||
the timer, and 2 if there is a request to abort the program."""
|
||||
|
||||
abortsleep = 0
|
||||
while sleepsecs > 0 and not abortsleep:
|
||||
abortsleep = s.sleeping(1, sleepsecs)
|
||||
sleepsecs -= 1
|
||||
s.sleeping(0, 0) # Done sleeping.
|
||||
return abortsleep
|
||||
|
||||
def sleeping(s, sleepsecs, remainingsecs):
|
||||
"""Sleep for sleepsecs, remainingsecs to go.
|
||||
If sleepsecs is 0, indicates we're done sleeping.
|
||||
|
||||
Return 0 for normal sleep, or 1 to indicate a request
|
||||
to sync immediately."""
|
||||
s._msg("Next refresh in %d seconds" % remainingsecs)
|
||||
if sleepsecs > 0:
|
||||
time.sleep(sleepsecs)
|
||||
return 0
|
44
offlineimap/ui/__init__.py
Normal file
44
offlineimap/ui/__init__.py
Normal file
@ -0,0 +1,44 @@
|
||||
# UI module directory
|
||||
# Copyright (C) 2002 John Goerzen
|
||||
# <jgoerzen@complete.org>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
|
||||
import UIBase, Blinkenlights
|
||||
try:
|
||||
import TTY
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
import Tkinter
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
import Tk
|
||||
|
||||
try:
|
||||
import curses
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
import Curses
|
||||
|
||||
import Noninteractive
|
||||
|
||||
# Must be last
|
||||
import detector
|
||||
|
49
offlineimap/ui/debuglock.py
Normal file
49
offlineimap/ui/debuglock.py
Normal file
@ -0,0 +1,49 @@
|
||||
# Locking debugging code -- temporary
|
||||
# Copyright (C) 2003 John Goerzen
|
||||
# <jgoerzen@complete.org>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
from threading import *
|
||||
import traceback
|
||||
logfile = open("/tmp/logfile", "wt")
|
||||
loglock = Lock()
|
||||
|
||||
class DebuggingLock:
|
||||
def __init__(self, name):
|
||||
self.lock = Lock()
|
||||
self.name = name
|
||||
|
||||
def acquire(self, blocking = 1):
|
||||
self.print_tb("Acquire lock")
|
||||
self.lock.acquire(blocking)
|
||||
self.logmsg("===== %s: Thread %s acquired lock\n" % (self.name, currentThread().getName()))
|
||||
|
||||
def release(self):
|
||||
self.print_tb("Release lock")
|
||||
self.lock.release()
|
||||
|
||||
def logmsg(self, msg):
|
||||
loglock.acquire()
|
||||
logfile.write(msg + "\n")
|
||||
logfile.flush()
|
||||
loglock.release()
|
||||
|
||||
def print_tb(self, msg):
|
||||
self.logmsg(".... %s: Thread %s attempting to %s\n" % \
|
||||
(self.name, currentThread().getName(), msg) + \
|
||||
"\n".join(traceback.format_list(traceback.extract_stack())))
|
||||
|
||||
|
52
offlineimap/ui/detector.py
Normal file
52
offlineimap/ui/detector.py
Normal file
@ -0,0 +1,52 @@
|
||||
# UI base class
|
||||
# Copyright (C) 2002 John Goerzen
|
||||
# <jgoerzen@complete.org>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
import offlineimap.ui
|
||||
import sys
|
||||
|
||||
def findUI(config, chosenUI=None):
|
||||
uistrlist = ['Tk.Blinkenlights', 'Tk.VerboseUI',
|
||||
'Curses.Blinkenlights', 'TTY.TTYUI',
|
||||
'Noninteractive.Basic', 'Noninteractive.Quiet']
|
||||
namespace={}
|
||||
for ui in dir(offlineimap.ui):
|
||||
if ui.startswith('_') or ui=='detector':
|
||||
continue
|
||||
namespace[ui]=getattr(offlineimap.ui, ui)
|
||||
|
||||
if chosenUI is not None:
|
||||
uistrlist = [chosenUI]
|
||||
elif config.has_option("general", "ui"):
|
||||
uistrlist = config.get("general", "ui").replace(" ", "").split(",")
|
||||
|
||||
for uistr in uistrlist:
|
||||
uimod = getUImod(uistr, config.getlocaleval(), namespace)
|
||||
if uimod:
|
||||
uiinstance = uimod(config)
|
||||
if uiinstance.isusable():
|
||||
return uiinstance
|
||||
sys.stderr.write("ERROR: No UIs were found usable!\n")
|
||||
sys.exit(200)
|
||||
|
||||
def getUImod(uistr, localeval, namespace):
|
||||
try:
|
||||
uimod = localeval.eval(uistr, namespace)
|
||||
except (AttributeError, NameError), e:
|
||||
#raise
|
||||
return None
|
||||
return uimod
|
Reference in New Issue
Block a user