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,5 +1,5 @@
offlineimap Mail syncing software offlineimap Mail syncing software
Copyright (C) 2002 - 2007 John Goerzen Copyright (C) 2002 - 2006 John Goerzen
<jgoerzen@complete.org> <jgoerzen@complete.org>
This program is free software; you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
@ -15,3 +15,57 @@ Copyright (C) 2002 - 2007 John Goerzen
along with this program; if not, write to the Free Software along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
ONLY imaplib.py is Copyright (c) 2001 Python Software Foundation;
All Rights Reserved
imaplib.py comes from Python dev tree and is licensed under the
GPL-compatible PSF license as follows:
PSF LICENSE AGREEMENT FOR PYTHON 2.2
------------------------------------
1. This LICENSE AGREEMENT is between the Python Software Foundation
("PSF"), and the Individual or Organization ("Licensee") accessing and
otherwise using Python 2.2 software in source or binary form and its
associated documentation.
2. Subject to the terms and conditions of this License Agreement, PSF
hereby grants Licensee a nonexclusive, royalty-free, world-wide
license to reproduce, analyze, test, perform and/or display publicly,
prepare derivative works, distribute, and otherwise use Python 2.2
alone or in any derivative version, provided, however, that PSF's
License Agreement and PSF's notice of copyright, i.e., "Copyright (c)
2001 Python Software Foundation; All Rights Reserved" are retained in
Python 2.2 alone or in any derivative version prepared by Licensee.
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python 2.2 or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python 2.2.
4. PSF is making Python 2.2 available to Licensee on an "AS IS"
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 2.2 WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
2.2 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 2.2,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. Nothing in this License Agreement shall be deemed to create any
relationship of agency, partnership, or joint venture between PSF and
Licensee. This License Agreement does not grant permission to use PSF
trademarks or trade name in a trademark sense to endorse or promote
products or services of Licensee, or any third party.
8. By copying, installing or otherwise using Python 2.2, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.

View File

@ -33,7 +33,7 @@ clean:
-find . -name auth -exec rm -vf {}/password {}/username \; -find . -name auth -exec rm -vf {}/password {}/username \;
-rm -f manual.html manual.pdf manual.txt offlineimap.1 -rm -f manual.html manual.pdf manual.txt offlineimap.1
doc: doc: faq
docbook2man offlineimap.sgml docbook2man offlineimap.sgml
docbook2man offlineimap.sgml docbook2man offlineimap.sgml
docbook2html -u offlineimap.sgml docbook2html -u offlineimap.sgml

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# Startup from system-wide installation # Startup from system-wide installation
# Copyright (C) 2002 - 2007 John Goerzen # Copyright (C) 2002 - 2006 John Goerzen
# <jgoerzen@complete.org> # <jgoerzen@complete.org>
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
@ -18,4 +18,4 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
from offlineimap import init from offlineimap import init
init.startup('5.99.0') init.startup('4.0.16')

24
debian/changelog vendored
View File

@ -1,27 +1,3 @@
offlineimap (5.99.0) unstable; urgency=low
* Re-scan remote folder names at the start of each sync run.
Closes: #329000, #396772.
* Drop internal imaplib.py in favor of default Python one.
* New user interface: Machine.MachineUI. Machine-parsable.
* Drop all the Tk interfaces.
Closes: #265088.
* Now supports specifying timeouts for socket operations.
* Updated copyright files.
* Improved interaction with Dovecot maildirs. Patch thanks to Asheesh
Laroia.
* Improved filesystem syncing semantics, which should reduce duplication
in the event of hardware failure.
* UID validity diagnostics improvement. Patch from David Favro.
* No longer leave preauthtunnel zombies with autorefresh.
Patch from Peter Colberg. Closes: #410730.
* --help now shows available UIs. Patch from Daniel Rall.
* Check all resolved addresses. Patch from Mark Brown. Closes: #413030.
* Removed todo directory from tree, moved to BTS.
* New PID file to enable third-party "kill offlineimap" tools.
-- John Goerzen <jgoerzen@complete.org> Tue, 10 Jul 2007 04:10:26 -0500
offlineimap (4.0.16) unstable; urgency=low offlineimap (4.0.16) unstable; urgency=low
* Apply patches from Danial Burrows to improve situation when * Apply patches from Danial Burrows to improve situation when

1
debian/control vendored
View File

@ -10,6 +10,7 @@ Standards-Version: 3.7.2
Package: offlineimap Package: offlineimap
Architecture: all Architecture: all
Depends: ${python:Depends} Depends: ${python:Depends}
Suggests: python-tk
Description: IMAP/Maildir synchronization and reader support Description: IMAP/Maildir synchronization and reader support
OfflineIMAP is a tool to simplify your e-mail reading. With OfflineIMAP is a tool to simplify your e-mail reading. With
OfflineIMAP, you can: OfflineIMAP, you can:

2
debian/copyright vendored
View File

@ -4,7 +4,7 @@ on Fri, 21 Jun 2002 14:54:56 -0500.
The original source can always be found at: The original source can always be found at:
http://software.complete.org/offlineimap/ http://software.complete.org/offlineimap/
Copyright (C) 2002 - 2007 John Goerzen Copyright (C) 2002 - 2006 John Goerzen
This program is free software; you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by

View File

@ -51,18 +51,18 @@ maxsyncaccounts = 1
# fails, the second, and so forth. # fails, the second, and so forth.
# #
# The pre-defined options are: # The pre-defined options are:
# Tk.Blinkenlights -- A graphical interface, shows LEDs and a single log
# Tk.VerboseUI -- A graphical interface, shows logs per thread
# Curses.Blinkenlights -- A text-based (terminal) interface similar to # Curses.Blinkenlights -- A text-based (terminal) interface similar to
# Tk.Blinkenlights # Tk.Blinkenlights
# TTY.TTYUI -- a text-based (terminal) interface # TTY.TTYUI -- a text-based (terminal) interface
# Noninteractive.Basic -- Noninteractive interface suitable for cronning # Noninteractive.Basic -- Noninteractive interface suitable for cronning
# Noninteractive.Quiet -- Noninteractive interface, generates no output # Noninteractive.Quiet -- Noninteractive interface, generates no output
# except for errors. # except for errors.
# Machine.MachineUI -- Interactive interface suitable for machine
# parsing.
# #
# You can override this with a command-line option -u. # You can override this with a command-line option -u.
ui = Curses.Blinkenlights, TTY.TTYUI, ui = Tk.Blinkenlights, Tk.VerboseUI, Curses.Blinkenlights, TTY.TTYUI,
Noninteractive.Basic, Noninteractive.Quiet Noninteractive.Basic, Noninteractive.Quiet
# If you try to synchronize messages to a read-only folder, # If you try to synchronize messages to a read-only folder,
@ -85,19 +85,6 @@ ignore-readonly = no
# pythonfile = ~/.offlineimap.py # pythonfile = ~/.offlineimap.py
# #
# By default, OfflineIMAP will not exit due to a network error until
# the operating system returns an error code. Operating systems can sometimes
# take forever to notice this. Here you can activate a timeout on the
# socket. This timeout applies to individual socket reads and writes,
# not to an overall sync operation. You could perfectly well have a 30s
# timeout here and your sync still take minutes.
#
# Values in the 30-120 second range are reasonable.
#
# The default is to have no timeout beyond the OS. Times are given in seconds.
#
# socktimeout = 60
################################################## ##################################################
# Mailbox name recorder # Mailbox name recorder
################################################## ##################################################
@ -129,6 +116,37 @@ footer = "\n"
# Note that this filter can be used only to further restrict mbnames # Note that this filter can be used only to further restrict mbnames
# to a subset of folders that pass the account's folderfilter. # to a subset of folders that pass the account's folderfilter.
##################################################
# Blinkenlights configuration
##################################################
[ui.Tk.Blinkenlights]
# Specifies the default number of lines in the log.
loglines = 5
# Specifies how many lines are in the scrollback log buffer.
bufferlines = 500
# If true, says that the log should be enabled by default.
# Otherwise, you have to click "Show Log" to enable the log.
showlog = false
# Sets the font information.
fontfamily = Helvetica
fontsize = 8
[ui.Curses.Blinkenlights]
# Character used to indicate thread status, in place of
# the LEDs from Tk.Blinkenlights
statuschar = .
################################################## ##################################################
# Accounts # Accounts
################################################## ##################################################

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# Startup from single-user installation # Startup from single-user installation
# Copyright (C) 2002 - 2007 John Goerzen # Copyright (C) 2002 - 2006 John Goerzen
# <jgoerzen@complete.org> # <jgoerzen@complete.org>
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
@ -18,4 +18,4 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from offlineimap import init from offlineimap import init
init.startup('5.99.0') init.startup('4.0.16')

View File

@ -208,13 +208,14 @@ remoteuser = jgoerzen
</listitem> </listitem>
<listitem> <listitem>
<para> <para>
You must have Python version 2.4 or above installed. You must have Python version 2.2.1 or above installed.
If you are If you are
running on Debian GNU/Linux, this requirement will automatically be running on Debian GNU/Linux, this requirement will automatically be
taken care of for you. If you do not have Python already, check with taken care of for you. If you do not have Python already, check with
your system administrator or operating system vendor; or, download it from your system administrator or operating system vendor; or, download it from
<ulink url="http://www.python.org/">the Python website</ulink>. <ulink url="http://www.python.org/">the Python website</ulink>.
If you intend to use the SSL interface, your If you intend to use the Tk interface, you must have Tkinter
(python-tk) installed. If you intend to use the SSL interface, your
Python must have been built with SSL support. Python must have been built with SSL support.
</para> </para>
</listitem> </listitem>
@ -411,22 +412,27 @@ cd offlineimap-x.y.z</ProgramListing>
option can override the configuration file setting. The available option can override the configuration file setting. The available
values for the configuration file or command-line are described values for the configuration file or command-line are described
in this section.</para> in this section.</para>
<refsect2> <refsect2>
<title>Curses.Blinkenlights</title> <title>Tk.Blinkenlights</title>
<para> <para>Tk.Blinkenlights is an interface designed to be sleek, fun to watch, and
Curses.Blinkenlights is an interface designed to be sleek, fun to watch, and
informative of the overall picture of what &OfflineIMAP; informative of the overall picture of what &OfflineIMAP;
is doing. I consider it to be the best general-purpose interface in is doing. I consider it to be the best general-purpose interface in
&OfflineIMAP;. &OfflineIMAP;.
</para> </para>
<para> <para>
Curses.Blinkenlights contains a row of Tk.Blinkenlights contains, by default, a small window with a row of
"LEDs" with command buttons and a log. LEDs, a small log, and a row of command buttons.
The log shows more The total size of the window is
very small, so it uses little desktop space, yet it is quite
functional. The optional, toggleable, log shows more
detail about what is happening and is color-coded to match the color detail about what is happening and is color-coded to match the color
of the lights. of the lights.
</para> </para>
<para>
Tk.Blinkenlights is the only user interface that has configurable
parameters; see the example <filename>offlineimap.conf</filename>
for more details.
</para>
<para> <para>
Each light in the Blinkenlights interface represents a thread Each light in the Blinkenlights interface represents a thread
of execution -- that is, a particular task that &OfflineIMAP; of execution -- that is, a particular task that &OfflineIMAP;
@ -532,6 +538,32 @@ cd offlineimap-x.y.z</ProgramListing>
</blockquote> </blockquote>
</refsect2> </refsect2>
<refsect2>
<title>Curses.Blinkenlights</title>
<para>
Curses.Blinkenlights is an interface very similar to Tk.Blinkenlights,
but is designed to be run in a console window (an xterm, Linux virtual
terminal, etc.) Since it doesn't have access to graphics, it isn't
quite as pretty, but it still gets the job done.
</para>
<para>Please see the Tk.Blinkenlights section above for more
information about the colors used in this interface.
</para>
</refsect2>
<refsect2>
<title>Tk.VerboseUI</title>
<para>
Tk.VerboseUI (formerly known as Tk.TkUI) is a graphical interface
that presents a variable-sized window. In the window, each
currently-executing thread has a section where its name and current
status are displayed. This interface is best suited to people running
on slower connections, as you get a lot of detail, but for fast
connections, the detail may go by too quickly to be useful. People
with fast connections may wish to use Tk.Blinkenlights instead.
</para>
</refsect2>
<refsect2> <refsect2>
<title>TTY.TTYUI</title> <title>TTY.TTYUI</title>
<para> <para>
@ -566,15 +598,6 @@ cd offlineimap-x.y.z</ProgramListing>
</para> </para>
</refsect2> </refsect2>
<refsect2>
<title>Machine.MachineUI</title>
<para>
Machine.MachineUI generates output in a machine-parsable format.
It is designed for other programs that will interface
to OfflineIMAP.
</para>
</refsect2>
</refsect1> </refsect1>
<refsect1> <refsect1>
@ -708,24 +731,6 @@ def test_mycmp():
</para> </para>
</refsect2> </refsect2>
</refsect1> </refsect1>
<refsect1>
<title>Signals</title>
<para>
OfflineIMAP writes its current PID into
<filename>~/.offlineimap/pid</filename> when it is
running. It is not guaranteed that this file will
not exist when OfflineIMAP is not running.
</para>
<!-- not done yet
<para>
You can send SIGINT to OfflineIMAP using this file to
kill it. SIGUSR1 will force an immediate resync of
all accounts. This will be ignored for all accounts
for which a resync is already in progress.
</para>
-->
</refsect1>
<refsect1> <refsect1>
<title>Errors</title> <title>Errors</title>
@ -831,8 +836,8 @@ rm -r ~/.offlineimap/Repository-<replaceable>RepositoryName</></programlisting>
<para>&OfflineIMAP; is not designed to have several instances (for instance, a cron job and an interactive invocation) run over the same <para>&OfflineIMAP; is not designed to have several instances (for instance, a cron job and an interactive invocation) run over the same
mailbox simultaneously. It will perform a check on startup and mailbox simultaneously. It will perform a check on startup and
abort if another &OfflineIMAP; is already running. If you need abort if another &OfflineIMAP; is already running. If you need
to schedule synchronizations, you'll probably find to schedule synchronizations, please use the
<property>autorefresh</property> settings more convenient than cron. <property>autorefresh</property> settings rather than cron.
Alternatively, you can set a separate <property>metadata</property> Alternatively, you can set a separate <property>metadata</property>
directory for each instance. directory for each instance.
</para> </para>

View File

@ -1,6 +1,8 @@
# Copyright (C) 2003 John Goerzen # Copyright (C) 2003 John Goerzen
# <jgoerzen@complete.org> # <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 # 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 # it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or # the Free Software Foundation; either version 2 of the License, or
@ -146,8 +148,6 @@ class AccountSynchronizationMixin:
folderthreads.append(thread) folderthreads.append(thread)
threadutil.threadsreset(folderthreads) threadutil.threadsreset(folderthreads)
mbnames.write() mbnames.write()
localrepos.forgetfolders()
remoterepos.forgetfolders()
localrepos.holdordropconnections() localrepos.holdordropconnections()
remoterepos.holdordropconnections() remoterepos.holdordropconnections()
finally: finally:

View File

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

View File

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

View File

@ -1,5 +1,5 @@
# Maildir folder support # Maildir folder support
# Copyright (C) 2002 - 2007 John Goerzen # Copyright (C) 2002 - 2006 John Goerzen
# <jgoerzen@complete.org> # <jgoerzen@complete.org>
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
@ -130,8 +130,6 @@ class MaildirFolder(BaseFolder):
return st.st_mtime return st.st_mtime
def savemessage(self, uid, content, flags, rtime): 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 = UIBase.getglobalui()
ui.debug('maildir', 'savemessage: called to write with flags %s and content %s' % \ ui.debug('maildir', 'savemessage: called to write with flags %s and content %s' % \
(repr(flags), repr(content))) (repr(flags), repr(content)))
@ -142,9 +140,12 @@ class MaildirFolder(BaseFolder):
# We already have it. # We already have it.
self.savemessageflags(uid, flags) self.savemessageflags(uid, flags)
return uid return uid
if 'S' in flags:
# Otherwise, save the message in tmp/ and then call savemessageflags() # If a message has been seen, it goes into the cur
# to give it a permanent home. # 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') tmpdir = os.path.join(self.getfullname(), 'tmp')
messagename = None messagename = None
attempts = 0 attempts = 0
@ -168,31 +169,16 @@ class MaildirFolder(BaseFolder):
ui.debug('maildir', 'savemessage: using temporary name %s' % tmpmessagename) ui.debug('maildir', 'savemessage: using temporary name %s' % tmpmessagename)
file = open(os.path.join(tmpdir, tmpmessagename), "wt") file = open(os.path.join(tmpdir, tmpmessagename), "wt")
file.write(content) file.write(content)
# Make sure the data hits the disk
file.flush()
os.fsync(file.fileno())
file.close() file.close()
if rtime != None: if rtime != None:
os.utime(os.path.join(tmpdir,tmpmessagename), (rtime,rtime)) os.utime(os.path.join(tmpdir,tmpmessagename), (rtime,rtime))
ui.debug('maildir', 'savemessage: moving from %s to %s' % \ ui.debug('maildir', 'savemessage: moving from %s to %s' % \
(tmpmessagename, messagename)) (tmpmessagename, messagename))
if tmpmessagename != messagename: # then rename it os.link(os.path.join(tmpdir, tmpmessagename),
os.link(os.path.join(tmpdir, tmpmessagename), os.path.join(newdir, messagename))
os.path.join(tmpdir, messagename)) os.unlink(os.path.join(tmpdir, tmpmessagename))
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
self.messagelist[uid] = {'uid': uid, 'flags': [], self.messagelist[uid] = {'uid': uid, 'flags': [],
'filename': os.path.join(tmpdir, messagename)} 'filename': os.path.join(newdir, messagename)}
self.savemessageflags(uid, flags) self.savemessageflags(uid, flags)
ui.debug('maildir', 'savemessage: returning uid %d' % uid) ui.debug('maildir', 'savemessage: returning uid %d' % uid)
return uid return uid
@ -203,7 +189,6 @@ class MaildirFolder(BaseFolder):
def savemessageflags(self, uid, flags): def savemessageflags(self, uid, flags):
oldfilename = self.messagelist[uid]['filename'] oldfilename = self.messagelist[uid]['filename']
newpath, newname = os.path.split(oldfilename) newpath, newname = os.path.split(oldfilename)
tmpdir = os.path.join(self.getfullname(), 'tmp')
if 'S' in flags: if 'S' in flags:
# If a message has been seen, it goes into the cur # If a message has been seen, it goes into the cur
# directory. CR debian#152482, [complete.org #4] # directory. CR debian#152482, [complete.org #4]
@ -226,10 +211,6 @@ class MaildirFolder(BaseFolder):
self.messagelist[uid]['flags'] = flags self.messagelist[uid]['flags'] = flags
self.messagelist[uid]['filename'] = newfilename 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): def deletemessage(self, uid):
if not uid in self.messagelist: if not uid in self.messagelist:
return 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 # IMAP server support
# Copyright (C) 2002 - 2007 John Goerzen # Copyright (C) 2002, 2003 John Goerzen
# <jgoerzen@complete.org> # <jgoerzen@complete.org>
# #
# This program is free software; you can redistribute it and/or modify # 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 # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import imaplib from offlineimap import imaplib, imaputil, threadutil
from offlineimap import imaplibutil, imaputil, threadutil
from offlineimap.ui import UIBase from offlineimap.ui import UIBase
from threading import * from threading import *
import thread, hmac, os import thread, hmac, os
@ -32,8 +31,8 @@ class UsefulIMAPMixIn:
return None return None
def select(self, mailbox='INBOX', readonly=None, force = 0): def select(self, mailbox='INBOX', readonly=None, force = 0):
if (not force) and self.getselectedfolder() == mailbox \ if (not force) and self.getselectedfolder() == mailbox:
and self.is_readonly == readonly: self.is_readonly = readonly
# No change; return. # No change; return.
return return
result = self.__class__.__bases__[1].select(self, mailbox, readonly) result = self.__class__.__bases__[1].select(self, mailbox, readonly)
@ -44,18 +43,9 @@ class UsefulIMAPMixIn:
else: else:
self.selectedfolder = None self.selectedfolder = None
def _mesg(self, s, secs=None): class UsefulIMAP4(UsefulIMAPMixIn, imaplib.IMAP4): pass
imaplibutil.new_mesg(self, s, secs) class UsefulIMAP4_SSL(UsefulIMAPMixIn, imaplib.IMAP4_SSL): pass
class UsefulIMAP4_Tunnel(UsefulIMAPMixIn, imaplib.IMAP4_Tunnel): pass
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 IMAPServer: class IMAPServer:
def __init__(self, config, reposname, def __init__(self, config, reposname,
@ -67,7 +57,6 @@ class IMAPServer:
self.username = username self.username = username
self.password = password self.password = password
self.passworderror = None self.passworderror = None
self.goodpassword = None
self.hostname = hostname self.hostname = hostname
self.tunnel = tunnel self.tunnel = tunnel
self.port = port self.port = port
@ -88,9 +77,6 @@ class IMAPServer:
self.reference = reference self.reference = reference
def getpassword(self): def getpassword(self):
if self.goodpassword != None:
return self.goodpassword
if self.password != None and self.passworderror == None: if self.password != None and self.passworderror == None:
return self.password return self.password
@ -113,7 +99,6 @@ class IMAPServer:
def releaseconnection(self, connection): def releaseconnection(self, connection):
"""Releases a connection, returning it to the pool."""
self.connectionlock.acquire() self.connectionlock.acquire()
self.assignedconnections.remove(connection) self.assignedconnections.remove(connection)
self.availableconnections.append(connection) self.availableconnections.append(connection)
@ -182,8 +167,6 @@ class IMAPServer:
UIBase.getglobalui().connecting(self.hostname, self.port) UIBase.getglobalui().connecting(self.hostname, self.port)
imapobj = UsefulIMAP4(self.hostname, self.port) imapobj = UsefulIMAP4(self.hostname, self.port)
imapobj.mustquote = imaplibutil.mustquote
if not self.tunnel: if not self.tunnel:
try: try:
if 'AUTH=CRAM-MD5' in imapobj.capabilities: if 'AUTH=CRAM-MD5' in imapobj.capabilities:
@ -197,7 +180,6 @@ class IMAPServer:
self.plainauth(imapobj) self.plainauth(imapobj)
# Would bail by here if there was a failure. # Would bail by here if there was a failure.
success = 1 success = 1
self.goodpassword = self.password
except imapobj.error, val: except imapobj.error, val:
self.passworderror = str(val) self.passworderror = str(val)
self.password = None self.password = None

View File

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

View File

@ -1,5 +1,5 @@
# Base repository support # Base repository support
# Copyright (C) 2002-2007 John Goerzen # Copyright (C) 2002, 2003, 2006 John Goerzen
# <jgoerzen@complete.org> # <jgoerzen@complete.org>
# #
# This program is free software; you can redistribute it and/or modify # 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() 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): def holdordropconnections(self):
pass pass
@ -106,11 +97,6 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
"""Returns a list of ALL folders on this server.""" """Returns a list of ALL folders on this server."""
return [] return []
def forgetfolders(self):
"""Forgets the cached list of folders, if any. Useful to run
after a sync run."""
pass
def getsep(self): def getsep(self):
raise NotImplementedError raise NotImplementedError

View File

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

View File

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

View File

@ -126,10 +126,11 @@ class CursesUtil:
self.start() self.start()
class CursesAccountFrame: class CursesAccountFrame:
def __init__(s, master, accountname): def __init__(s, master, accountname, ui):
s.c = master s.c = master
s.children = [] s.children = []
s.accountname = accountname s.accountname = accountname
s.ui = ui
def drawleadstr(s, secs = None): def drawleadstr(s, secs = None):
if secs == None: if secs == None:
@ -150,7 +151,7 @@ class CursesAccountFrame:
s.location += 1 s.location += 1
def getnewthreadframe(s): 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.location += 1
s.children.append(tf) s.children.append(tf)
return tf return tf
@ -180,9 +181,10 @@ class CursesAccountFrame:
s.sleeping_abort = 1 s.sleeping_abort = 1
class CursesThreadFrame: class CursesThreadFrame:
def __init__(s, master, window, y, x): def __init__(s, master, ui, window, y, x):
"""master should be a CursesUtil object.""" """master should be a CursesUtil object."""
s.c = master s.c = master
s.ui = ui
s.window = window s.window = window
s.x = x s.x = x
s.y = y s.y = y
@ -212,7 +214,7 @@ class CursesThreadFrame:
if self.getcolor() == 'black': if self.getcolor() == 'black':
self.window.addstr(self.y, self.x, ' ', self.color) self.window.addstr(self.y, self.x, ' ', self.color)
else: 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.c.stdscr.move(self.c.height - 1, self.c.width - 1)
self.window.refresh() self.window.refresh()
self.c.locked(lockedstuff) self.c.locked(lockedstuff)
@ -476,7 +478,7 @@ class Blinkenlights(BlinkenBase, UIBase):
return s.af[accountname] return s.af[accountname]
# New one. # New one.
s.af[accountname] = CursesAccountFrame(s.c, accountname) s.af[accountname] = CursesAccountFrame(s.c, accountname, s)
s.c.lock() s.c.lock()
try: try:
s.c.reset() 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 ################################################## WARNINGS
def msgtoreadonly(s, destfolder, uid, content, flags): 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." % \ 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())) (uid, s.getnicename(destfolder), destfolder.getname()))
def flagstoreadonly(s, destfolder, uidlist, flags): 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." % \ 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())) (str(uidlist), s.getnicename(destfolder), destfolder.getname()))
def deletereadonly(s, destfolder, uidlist): 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." % \ 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())) (str(uidlist), s.getnicename(destfolder), destfolder.getname()))
@ -208,10 +208,9 @@ class UIBase:
s.getnicename(srcrepos), s.getnicename(srcrepos),
s.getnicename(destrepos))) s.getnicename(destrepos)))
def validityproblem(s, folder): def validityproblem(s, folder, saved, new):
s.warn("UID validity problem for folder %s (repo %s) (saved %d; got %d); skipping it" % \ s.warn("UID validity problem for folder %s (saved %d; got %d); skipping it" % \
(folder.getname(), folder.getrepository().getname(), (folder.getname(), saved, new))
folder.getsaveduidvalidity(), folder.getuidvalidity()))
def loadmessagelist(s, repos, folder): def loadmessagelist(s, repos, folder):
if s.verbose > 0: if s.verbose > 0:

View File

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

View File

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

View File

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