Configurable thread status character for ui.Curses.Blinkenlights
This commit is contained in:
parent
e16881ca2a
commit
71c8b2e7c4
56
COPYRIGHT
56
COPYRIGHT
@ -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.
|
||||||
|
2
Makefile
2
Makefile
@ -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
|
||||||
|
@ -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
24
debian/changelog
vendored
@ -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
1
debian/control
vendored
@ -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
2
debian/copyright
vendored
@ -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
|
||||||
|
@ -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
|
||||||
##################################################
|
##################################################
|
||||||
|
@ -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')
|
||||||
|
@ -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>
|
||||||
|
@ -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:
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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
1453
offlineimap/imaplib.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -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)
|
|
@ -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
|
||||||
|
@ -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']
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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
543
offlineimap/ui/Tk.py
Normal 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()
|
||||||
|
|
||||||
|
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user