Configurable thread status character for ui.Curses.Blinkenlights

This commit is contained in:
aaron
2006-12-11 06:12:00 +01:00
parent e16881ca2a
commit 71c8b2e7c4
27 changed files with 2220 additions and 623 deletions

View File

@ -1,6 +1,8 @@
# Copyright (C) 2003 John Goerzen
# <jgoerzen@complete.org>
#
# Portions Copyright (C) 2007 David Favro <offlineimap@meta-dynamic.com>
#
# 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
@ -146,8 +148,6 @@ class AccountSynchronizationMixin:
folderthreads.append(thread)
threadutil.threadsreset(folderthreads)
mbnames.write()
localrepos.forgetfolders()
remoterepos.forgetfolders()
localrepos.holdordropconnections()
remoterepos.holdordropconnections()
finally:

View File

@ -1,5 +1,5 @@
# IMAP folder support
# Copyright (C) 2002-2007 John Goerzen
# Copyright (C) 2002-2004 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
@ -17,8 +17,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
from Base import BaseFolder
import imaplib
from offlineimap import imaputil, imaplibutil
from offlineimap import imaputil, imaplib
from offlineimap.ui import UIBase
from offlineimap.version import versionstr
import rfc822, time, string, random, binascii, re
@ -99,7 +98,7 @@ class IMAPFolder(BaseFolder):
else:
uid = long(options['UID'])
flags = imaputil.flagsimap2maildir(options['FLAGS'])
rtime = imaplibutil.Internaldate2epoch(messagestr)
rtime = imaplib.Internaldate2epoch(messagestr)
self.messagelist[uid] = {'uid': uid, 'flags': flags, 'time': rtime}
def getmessagelist(self):
@ -271,7 +270,7 @@ class IMAPFolder(BaseFolder):
return
result = imapobj.uid('store', '%d' % uid, 'FLAGS',
imaputil.flagsmaildir2imap(flags))
assert result[0] == 'OK', 'Error with store: ' + '. '.join(r[1])
assert result[0] == 'OK', 'Error with store: ' + r[1]
finally:
self.imapserver.releaseconnection(imapobj)
result = result[1][0]
@ -317,7 +316,7 @@ class IMAPFolder(BaseFolder):
imaputil.listjoin(uidlist),
operation + 'FLAGS',
imaputil.flagsmaildir2imap(flags))
assert r[0] == 'OK', 'Error with store: ' + '. '.join(r[1])
assert r[0] == 'OK', 'Error with store: ' + r[1]
r = r[1]
finally:
self.imapserver.releaseconnection(imapobj)

View File

@ -1,5 +1,5 @@
# Local status cache virtual folder
# Copyright (C) 2002 - 2007 John Goerzen
# Copyright (C) 2002 - 2003 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
@ -90,18 +90,8 @@ class LocalStatusFolder(BaseFolder):
flags.sort()
flags = ''.join(flags)
file.write("%s:%s\n" % (msg['uid'], flags))
file.flush()
os.fsync(file.fileno())
file.close()
os.rename(self.filename + ".tmp", self.filename)
try:
fd = os.open(os.path.dirname(self.filename), os.O_RDONLY)
os.fsync(fd)
os.close(fd)
except:
pass
finally:
self.savelock.release()

View File

@ -1,5 +1,5 @@
# Maildir folder support
# Copyright (C) 2002 - 2007 John Goerzen
# Copyright (C) 2002 - 2006 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
@ -130,8 +130,6 @@ class MaildirFolder(BaseFolder):
return st.st_mtime
def savemessage(self, uid, content, flags, rtime):
# This function only ever saves to tmp/,
# but it calls savemessageflags() to actually save to cur/ or new/.
ui = UIBase.getglobalui()
ui.debug('maildir', 'savemessage: called to write with flags %s and content %s' % \
(repr(flags), repr(content)))
@ -142,9 +140,12 @@ class MaildirFolder(BaseFolder):
# We already have it.
self.savemessageflags(uid, flags)
return uid
# Otherwise, save the message in tmp/ and then call savemessageflags()
# to give it a permanent home.
if 'S' in flags:
# If a message has been seen, it goes into the cur
# directory. CR debian#152482, [complete.org #4]
newdir = os.path.join(self.getfullname(), 'cur')
else:
newdir = os.path.join(self.getfullname(), 'new')
tmpdir = os.path.join(self.getfullname(), 'tmp')
messagename = None
attempts = 0
@ -168,31 +169,16 @@ class MaildirFolder(BaseFolder):
ui.debug('maildir', 'savemessage: using temporary name %s' % tmpmessagename)
file = open(os.path.join(tmpdir, tmpmessagename), "wt")
file.write(content)
# Make sure the data hits the disk
file.flush()
os.fsync(file.fileno())
file.close()
if rtime != None:
os.utime(os.path.join(tmpdir,tmpmessagename), (rtime,rtime))
ui.debug('maildir', 'savemessage: moving from %s to %s' % \
(tmpmessagename, messagename))
if tmpmessagename != messagename: # then rename it
os.link(os.path.join(tmpdir, tmpmessagename),
os.path.join(tmpdir, messagename))
os.unlink(os.path.join(tmpdir, tmpmessagename))
try:
# fsync the directory (safer semantics in Linux)
fd = os.open(tmpdir, os.O_RDONLY)
os.fsync(fd)
os.close(fd)
except:
pass
os.link(os.path.join(tmpdir, tmpmessagename),
os.path.join(newdir, messagename))
os.unlink(os.path.join(tmpdir, tmpmessagename))
self.messagelist[uid] = {'uid': uid, 'flags': [],
'filename': os.path.join(tmpdir, messagename)}
'filename': os.path.join(newdir, messagename)}
self.savemessageflags(uid, flags)
ui.debug('maildir', 'savemessage: returning uid %d' % uid)
return uid
@ -203,7 +189,6 @@ class MaildirFolder(BaseFolder):
def savemessageflags(self, uid, flags):
oldfilename = self.messagelist[uid]['filename']
newpath, newname = os.path.split(oldfilename)
tmpdir = os.path.join(self.getfullname(), 'tmp')
if 'S' in flags:
# If a message has been seen, it goes into the cur
# directory. CR debian#152482, [complete.org #4]
@ -226,10 +211,6 @@ class MaildirFolder(BaseFolder):
self.messagelist[uid]['flags'] = flags
self.messagelist[uid]['filename'] = newfilename
# By now, the message had better not be in tmp/ land!
final_dir, final_name = os.path.split(self.messagelist[uid]['filename'])
assert final_dir != tmpdir
def deletemessage(self, uid):
if not uid in self.messagelist:
return

1453
offlineimap/imaplib.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,199 +0,0 @@
# imaplib utilities
# Copyright (C) 2002-2007 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import re, string, types, binascii, socket, time, random, subprocess, sys, os
from offlineimap.ui import UIBase
from imaplib import *
# Import the symbols we need that aren't exported by default
from imaplib import IMAP4_PORT, IMAP4_SSL_PORT, InternalDate, Mon2num
class IMAP4_Tunnel(IMAP4):
"""IMAP4 client class over a tunnel
Instantiate with: IMAP4_Tunnel(tunnelcmd)
tunnelcmd -- shell command to generate the tunnel.
The result will be in PREAUTH stage."""
def __init__(self, tunnelcmd):
IMAP4.__init__(self, tunnelcmd)
def open(self, host, port):
"""The tunnelcmd comes in on host!"""
self.process = subprocess.Popen(host, shell=True, close_fds=True,
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
(self.outfd, self.infd) = (self.process.stdin, self.process.stdout)
def read(self, size):
retval = ''
while len(retval) < size:
retval += self.infd.read(size - len(retval))
return retval
def readline(self):
return self.infd.readline()
def send(self, data):
self.outfd.write(data)
def shutdown(self):
self.infd.close()
self.outfd.close()
self.process.wait()
class sslwrapper:
def __init__(self, sslsock):
self.sslsock = sslsock
self.readbuf = ''
def write(self, s):
return self.sslsock.write(s)
def _read(self, n):
return self.sslsock.read(n)
def read(self, n):
if len(self.readbuf):
# Return the stuff in readbuf, even if less than n.
# It might contain the rest of the line, and if we try to
# read more, might block waiting for data that is not
# coming to arrive.
bytesfrombuf = min(n, len(self.readbuf))
retval = self.readbuf[:bytesfrombuf]
self.readbuf = self.readbuf[bytesfrombuf:]
return retval
retval = self._read(n)
if len(retval) > n:
self.readbuf = retval[n:]
return retval[:n]
return retval
def readline(self):
retval = ''
while 1:
linebuf = self.read(1024)
nlindex = linebuf.find("\n")
if nlindex != -1:
retval += linebuf[:nlindex + 1]
self.readbuf = linebuf[nlindex + 1:] + self.readbuf
return retval
else:
retval += linebuf
def new_mesg(self, s, secs=None):
if secs is None:
secs = time.time()
tm = time.strftime('%M:%S', time.localtime(secs))
UIBase.getglobalui().debug('imap', ' %s.%02d %s' % (tm, (secs*100)%100, s))
def new_open(self, host = '', port = IMAP4_PORT):
"""Setup connection to remote server on "host:port"
(default: localhost:standard IMAP4 port).
This connection will be used by the routines:
read, readline, send, shutdown.
"""
self.host = host
self.port = port
res = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
socket.SOCK_STREAM)
self.sock = socket.socket(af, socktype, proto)
# Try each address returned by getaddrinfo in turn until we
# manage to connect to one.
# Try all the addresses in turn until we connect()
last_error = 0
for remote in res:
af, socktype, proto, canonname, sa = remote
self.sock = socket.socket(af, socktype, proto)
last_error = self.sock.connect_ex(sa)
if last_error == 0:
break
else:
self.sock.close()
if last_error != 0:
# FIXME
raise socket.error(last_error)
self.file = self.sock.makefile('rb')
def new_open_ssl(self, host = '', port = IMAP4_SSL_PORT):
"""Setup connection to remote server on "host:port".
(default: localhost:standard IMAP4 SSL port).
This connection will be used by the routines:
read, readline, send, shutdown.
"""
self.host = host
self.port = port
#This connects to the first ip found ipv4/ipv6
#Added by Adriaan Peeters <apeeters@lashout.net> based on a socket
#example from the python documentation:
#http://www.python.org/doc/lib/socket-example.html
res = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
socket.SOCK_STREAM)
# Try all the addresses in turn until we connect()
last_error = 0
for remote in res:
af, socktype, proto, canonname, sa = remote
self.sock = socket.socket(af, socktype, proto)
last_error = self.sock.connect_ex(sa)
if last_error == 0:
break
else:
self.sock.close()
if last_error != 0:
# FIXME
raise socket.error(last_error)
if sys.version_info[0] <= 2 and sys.version_info[1] <= 2:
self.sslobj = socket.ssl(self.sock, self.keyfile, self.certfile)
else:
self.sslobj = socket.ssl(self.sock._sock, self.keyfile, self.certfile)
self.sslobj = sslwrapper(self.sslobj)
mustquote = re.compile(r"[^\w!#$%&'+,.:;<=>?^`|~-]")
def Internaldate2epoch(resp):
"""Convert IMAP4 INTERNALDATE to UT.
Returns seconds since the epoch.
"""
mo = InternalDate.match(resp)
if not mo:
return None
mon = Mon2num[mo.group('mon')]
zonen = mo.group('zonen')
day = int(mo.group('day'))
year = int(mo.group('year'))
hour = int(mo.group('hour'))
min = int(mo.group('min'))
sec = int(mo.group('sec'))
zoneh = int(mo.group('zoneh'))
zonem = int(mo.group('zonem'))
# INTERNALDATE timezone must be subtracted to get UT
zone = (zoneh*60 + zonem)*60
if zonen == '-':
zone = -zone
tt = (year, mon, day, hour, min, sec, -1, -1, -1)
return time.mktime(tt)

View File

@ -1,5 +1,5 @@
# IMAP server support
# Copyright (C) 2002 - 2007 John Goerzen
# Copyright (C) 2002, 2003 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
@ -16,8 +16,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import imaplib
from offlineimap import imaplibutil, imaputil, threadutil
from offlineimap import imaplib, imaputil, threadutil
from offlineimap.ui import UIBase
from threading import *
import thread, hmac, os
@ -32,8 +31,8 @@ class UsefulIMAPMixIn:
return None
def select(self, mailbox='INBOX', readonly=None, force = 0):
if (not force) and self.getselectedfolder() == mailbox \
and self.is_readonly == readonly:
if (not force) and self.getselectedfolder() == mailbox:
self.is_readonly = readonly
# No change; return.
return
result = self.__class__.__bases__[1].select(self, mailbox, readonly)
@ -44,18 +43,9 @@ class UsefulIMAPMixIn:
else:
self.selectedfolder = None
def _mesg(self, s, secs=None):
imaplibutil.new_mesg(self, s, secs)
class UsefulIMAP4(UsefulIMAPMixIn, imaplib.IMAP4):
def open(self, host = '', port = imaplib.IMAP4_PORT):
imaplibutil.new_open(self, host, port)
class UsefulIMAP4_SSL(UsefulIMAPMixIn, imaplib.IMAP4_SSL):
def open(self, host = '', port = imaplib.IMAP4_SSL_PORT):
imaplibutil.new_open_ssl(self, host, port)
class UsefulIMAP4_Tunnel(UsefulIMAPMixIn, imaplibutil.IMAP4_Tunnel): pass
class UsefulIMAP4(UsefulIMAPMixIn, imaplib.IMAP4): pass
class UsefulIMAP4_SSL(UsefulIMAPMixIn, imaplib.IMAP4_SSL): pass
class UsefulIMAP4_Tunnel(UsefulIMAPMixIn, imaplib.IMAP4_Tunnel): pass
class IMAPServer:
def __init__(self, config, reposname,
@ -67,7 +57,6 @@ class IMAPServer:
self.username = username
self.password = password
self.passworderror = None
self.goodpassword = None
self.hostname = hostname
self.tunnel = tunnel
self.port = port
@ -88,9 +77,6 @@ class IMAPServer:
self.reference = reference
def getpassword(self):
if self.goodpassword != None:
return self.goodpassword
if self.password != None and self.passworderror == None:
return self.password
@ -113,7 +99,6 @@ class IMAPServer:
def releaseconnection(self, connection):
"""Releases a connection, returning it to the pool."""
self.connectionlock.acquire()
self.assignedconnections.remove(connection)
self.availableconnections.append(connection)
@ -182,8 +167,6 @@ class IMAPServer:
UIBase.getglobalui().connecting(self.hostname, self.port)
imapobj = UsefulIMAP4(self.hostname, self.port)
imapobj.mustquote = imaplibutil.mustquote
if not self.tunnel:
try:
if 'AUTH=CRAM-MD5' in imapobj.capabilities:
@ -197,7 +180,6 @@ class IMAPServer:
self.plainauth(imapobj)
# Would bail by here if there was a failure.
success = 1
self.goodpassword = self.password
except imapobj.error, val:
self.passworderror = str(val)
self.password = None

View File

@ -1,5 +1,5 @@
# OfflineIMAP initialization code
# Copyright (C) 2002-2007 John Goerzen
# Copyright (C) 2002, 2003 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
@ -16,15 +16,14 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import imaplib
from offlineimap import imapserver, repository, folder, mbnames, threadutil, version, syncmaster, accounts
from offlineimap import imaplib, imapserver, repository, folder, mbnames, threadutil, version, syncmaster, accounts
from offlineimap.localeval import LocalEval
from offlineimap.threadutil import InstanceLimitedThread, ExitNotifyThread
from offlineimap.ui import UIBase
import re, os, os.path, offlineimap, sys
from offlineimap.CustomConfig import CustomConfigParser
from threading import *
import threading, socket
import threading
from getopt import getopt
try:
@ -50,14 +49,14 @@ def startup(versionno):
assert versionno == version.versionstr, "Revision of main program (%s) does not match that of library (%s). Please double-check your PYTHONPATH and installation locations." % (versionno, version.versionstr)
options = {}
if '--help' in sys.argv[1:]:
sys.stdout.write(version.getcmdhelp() + "\n")
sys.stdout.write(version.cmdhelp + "\n")
sys.exit(0)
for optlist in getopt(sys.argv[1:], 'P:1oa:c:d:l:u:h')[0]:
options[optlist[0]] = optlist[1]
if options.has_key('-h'):
sys.stdout.write(version.getcmdhelp())
sys.stdout.write(version.cmdhelp)
sys.stdout.write("\n")
sys.exit(0)
configfilename = os.path.expanduser("~/.offlineimaprc")
@ -102,21 +101,10 @@ def startup(versionno):
lock(config, ui)
try:
pidfd = open(config.getmetadatadir() + "/pid", "w")
pidfd.write(os.getpid())
pidfd.close()
except:
pass
try:
if options.has_key('-l'):
sys.stderr = ui.logfile
socktimeout = config.getdefaultint("general", "socktimeout", 0)
if socktimeout > 0:
socket.setdefaulttimeout(socktimeout)
activeaccounts = config.get("general", "accounts")
if options.has_key('-a'):
activeaccounts = options['-a']

View File

@ -1,5 +1,5 @@
# Base repository support
# Copyright (C) 2002-2007 John Goerzen
# Copyright (C) 2002, 2003, 2006 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
@ -63,15 +63,6 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
return self.restore_folder_atimes()
def connect(self):
"""Establish a connection to the remote, if necessary. This exists
so that IMAP connections can all be established up front, gathering
passwords as needed. It was added in order to support the
error recovery -- we need to connect first outside of the error
trap in order to validate the password, and that's the point of
this function."""
pass
def holdordropconnections(self):
pass
@ -106,11 +97,6 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
"""Returns a list of ALL folders on this server."""
return []
def forgetfolders(self):
"""Forgets the cached list of folders, if any. Useful to run
after a sync run."""
pass
def getsep(self):
raise NotImplementedError

View File

@ -85,30 +85,30 @@ class IMAPRepository(BaseRepository):
return self.imapserver.delim
def gethost(self):
host = None
host = None
localeval = self.localeval
if self.config.has_option(self.getsection(), 'remotehosteval'):
host = self.getconf('remotehosteval')
if host != None:
return localeval.eval(host)
host = self.getconf('remotehosteval')
if host != None:
return localeval.eval(host)
host = self.getconf('remotehost')
if host != None:
return host
host = self.getconf('remotehost')
if host != None:
return host
def getuser(self):
user = None
user = None
localeval = self.localeval
if self.config.has_option(self.getsection(), 'remoteusereval'):
user = self.getconf('remoteusereval')
if user != None:
return localeval.eval(user)
user = self.getconf('remoteusereval')
if user != None:
return localeval.eval(user)
user = self.getconf('remoteuser')
if user != None:
return user
user = self.getconf('remoteuser')
if user != None:
return user
def getport(self):
return self.getconfint('remoteport', None)
@ -129,13 +129,13 @@ class IMAPRepository(BaseRepository):
return self.getconfboolean('expunge', 1)
def getpassword(self):
passwd = None
passwd = None
localeval = self.localeval
if self.config.has_option(self.getsection(), 'remotepasseval'):
passwd = self.getconf('remotepasseval')
if passwd != None:
return localeval.eval(passwd)
passwd = self.getconf('remotepasseval')
if passwd != None:
return localeval.eval(passwd)
password = self.getconf('remotepass', None)
if password != None:
@ -145,7 +145,7 @@ class IMAPRepository(BaseRepository):
fd = open(os.path.expanduser(passfile))
password = fd.readline().strip()
fd.close()
return password
return password
return None
def getfolder(self, foldername):
@ -156,13 +156,6 @@ class IMAPRepository(BaseRepository):
def getfoldertype(self):
return folder.IMAP.IMAPFolder
def connect(self):
imapobj = self.imapserver.acquireconnection()
self.imapserver.releaseconnection(imapobj)
def forgetfolders(self):
self.folders = None
def getfolders(self):
if self.folders != None:
return self.folders

View File

@ -1,5 +1,5 @@
# OfflineIMAP synchronization master code
# Copyright (C) 2002-2007 John Goerzen
# Copyright (C) 2002 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
@ -16,8 +16,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import imaplib
from offlineimap import imapserver, repository, folder, mbnames, threadutil, version
from offlineimap import imaplib, imapserver, repository, folder, mbnames, threadutil, version
from offlineimap.threadutil import InstanceLimitedThread, ExitNotifyThread
import offlineimap.accounts
from offlineimap.accounts import SyncableAccount

View File

@ -126,10 +126,11 @@ class CursesUtil:
self.start()
class CursesAccountFrame:
def __init__(s, master, accountname):
def __init__(s, master, accountname, ui):
s.c = master
s.children = []
s.accountname = accountname
s.ui = ui
def drawleadstr(s, secs = None):
if secs == None:
@ -150,7 +151,7 @@ class CursesAccountFrame:
s.location += 1
def getnewthreadframe(s):
tf = CursesThreadFrame(s.c, s.window, 0, s.location)
tf = CursesThreadFrame(s.c, s.ui, s.window, 0, s.location)
s.location += 1
s.children.append(tf)
return tf
@ -180,9 +181,10 @@ class CursesAccountFrame:
s.sleeping_abort = 1
class CursesThreadFrame:
def __init__(s, master, window, y, x):
def __init__(s, master, ui, window, y, x):
"""master should be a CursesUtil object."""
s.c = master
s.ui = ui
s.window = window
s.x = x
s.y = y
@ -212,7 +214,7 @@ class CursesThreadFrame:
if self.getcolor() == 'black':
self.window.addstr(self.y, self.x, ' ', self.color)
else:
self.window.addstr(self.y, self.x, '.', self.color)
self.window.addstr(self.y, self.x, self.ui.config.getdefault("ui.Curses.Blinkenlights", "statuschar", '.'), self.color)
self.c.stdscr.move(self.c.height - 1, self.c.width - 1)
self.window.refresh()
self.c.locked(lockedstuff)
@ -476,7 +478,7 @@ class Blinkenlights(BlinkenBase, UIBase):
return s.af[accountname]
# New one.
s.af[accountname] = CursesAccountFrame(s.c, accountname)
s.af[accountname] = CursesAccountFrame(s.c, accountname, s)
s.c.lock()
try:
s.c.reset()

View File

@ -1,177 +0,0 @@
# Copyright (C) 2007 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import offlineimap.version
import urllib, sys, re, time, traceback, threading, thread
from UIBase import UIBase
from threading import *
protocol = '6.0.0'
class MachineUI(UIBase):
def __init__(s, config, verbose = 0):
UIBase.__init__(s, config, verbose)
s.safechars=" ;,./-_=+()[]"
s.iswaiting = 0
s.outputlock = Lock()
s._printData('__init__', protocol)
def isusable(s):
return True
def _printData(s, command, data, dolock = True):
s._printDataOut('msg', command, data, dolock)
def _printWarn(s, command, data, dolock = True):
s._printDataOut('warn', command, data, dolock)
def _printDataOut(s, datatype, command, data, dolock = True):
if dolock:
s.outputlock.acquire()
try:
print "%s:%s:%s:%s" % \
(datatype,
urllib.quote(command, s.safechars),
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),
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.getvisiblename()))
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 = s.folderlist(destlist)
s._printData('copyingmessage', "%d\n%s\n%s\n%s" % \
(uid, s.getnicename(src), src.getname(), ds))
def folderlist(s, list):
return ("\f".join(["%s\t%s" % (s.getnicename(x), x.getname()) for 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 = s.folderlist(destlist)
s._printData('deletingmessages', "%s\n%s" % (s.uidlist(uidlist), ds))
def addingflags(s, uidlist, flags, destlist):
ds = 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 = 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(thread)))
s.delThreadDebugLog(thread)
s.terminate(100)
def terminate(s, exitstatus = 0, errortitle = '', errormsg = ''):
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 = None):
s.outputlock.acquire()
try:
if errmsg:
s._printData('getpasserror', "%s\n%s" % (accountname, errmsg),
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)

543
offlineimap/ui/Tk.py Normal file
View File

@ -0,0 +1,543 @@
# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 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 terminate(s, exitstatus = 0, errortitle = None, errormsg = None):
if errormsg <> None:
if errortitle == None:
errortitle = "Error"
TextOKDialog(errortitle, errormsg)
UIBase.terminate(s, exitstatus = exitstatus, errortitle = None, errormsg = None)
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()

View File

@ -151,17 +151,17 @@ class UIBase:
################################################## WARNINGS
def msgtoreadonly(s, destfolder, uid, content, flags):
if not (s.config.has_option('general', 'ignore-readonly') and s.config.getboolean("general", "ignore-readonly")):
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 (s.config.has_option('general', 'ignore-readonly') and s.config.getboolean("general", "ignore-readonly")):
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 (s.config.has_option('general', 'ignore-readonly') and s.config.getboolean("general", "ignore-readonly")):
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()))
@ -208,10 +208,9 @@ class UIBase:
s.getnicename(srcrepos),
s.getnicename(destrepos)))
def validityproblem(s, folder):
s.warn("UID validity problem for folder %s (repo %s) (saved %d; got %d); skipping it" % \
(folder.getname(), folder.getrepository().getname(),
folder.getsaveduidvalidity(), folder.getuidvalidity()))
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:

View File

@ -23,6 +23,13 @@ try:
except ImportError:
pass
try:
import Tkinter
except ImportError:
pass
else:
import Tk
try:
import curses
except ImportError:
@ -31,7 +38,6 @@ else:
import Curses
import Noninteractive
import Machine
# Must be last
import detector

View File

@ -19,9 +19,9 @@
import offlineimap.ui
import sys
DEFAULT_UI_LIST = ('Curses.Blinkenlights', 'TTY.TTYUI',
'Noninteractive.Basic', 'Noninteractive.Quiet',
'Machine.MachineUI')
DEFAULT_UI_LIST = ('Tk.Blinkenlights', 'Tk.VerboseUI',
'Curses.Blinkenlights', 'TTY.TTYUI',
'Noninteractive.Basic', 'Noninteractive.Quiet')
def findUI(config, chosenUI=None):
uistrlist = list(DEFAULT_UI_LIST)

View File

@ -1,11 +1,11 @@
productname = 'OfflineIMAP'
versionstr = "5.99.0"
versionstr = "4.0.16"
versionlist = versionstr.split(".")
major = versionlist[0]
minor = versionlist[1]
patch = versionlist[2]
copyright = "Copyright (C) 2002 - 2007 John Goerzen"
copyright = "Copyright (C) 2002 - 2006 John Goerzen"
author = "John Goerzen"
author_email = "jgoerzen@complete.org"
description = "Disconnected Universal IMAP Mail Synchronization/Reader Support"
@ -18,7 +18,7 @@ COPYING for details. This is free software, and you are welcome
to distribute it under the conditions laid out in COPYING."""
homepage = "http://software.complete.org/offlineimap/"
license = """Copyright (C) 2002 - 2007 John Goerzen <jgoerzen@complete.org>
license = """Copyright (C) 2002 - 2006 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
@ -34,13 +34,7 @@ You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA"""
def getcmdhelp():
from offlineimap.ui import detector
import os
uilist = ""
for ui in detector.DEFAULT_UI_LIST:
uilist += " " + ui + os.linesep
return """
cmdhelp = """
offlineimap [ -1 ] [ -P profiledir ] [ -a accountlist ] [
-c configfile ] [ -d debugtype[,debugtype...] ] [ -o ] [
-u interface ]
@ -108,4 +102,8 @@ def getcmdhelp():
states that it cannot be. Use this option with
care. The pre-defined options, described in the
USER INTERFACES section of the man page, are:
""" + uilist
"""
from offlineimap.ui import detector
import os
for ui in detector.DEFAULT_UI_LIST:
cmdhelp += " " + ui + os.linesep