Step 1 of converting tree to Arch layout

This commit is contained in:
John Goerzen
2005-04-16 20:32:25 +01:00
parent e774e38cc2
commit 3673e4c5d4
56 changed files with 0 additions and 0 deletions

View File

@ -1,340 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.

View File

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

View File

@ -1,693 +0,0 @@
------------------------------------------------------------------------
r590 | jgoerzen | 2004-07-26 10:47:36 -0500 (Mon, 26 Jul 2004) | 1 line
Changed paths:
M /offlineimap/head/ChangeLog
M /offlineimap/head/bin/offlineimap
M /offlineimap/head/debian/changelog
M /offlineimap/head/debian/control
M /offlineimap/head/manual.html
M /offlineimap/head/manual.pdf
M /offlineimap/head/manual.ps
M /offlineimap/head/manual.txt
M /offlineimap/head/offlineimap/version.py
M /offlineimap/head/offlineimap.1
M /offlineimap/head/offlineimap.py
Preparing 4.0.6
------------------------------------------------------------------------
r589 | jgoerzen | 2004-07-26 10:37:45 -0500 (Mon, 26 Jul 2004) | 1 line
Changed paths:
M /offlineimap/head/ChangeLog
M /offlineimap/head/debian/changelog
M /offlineimap/head/offlineimap/folder/IMAP.py
M /offlineimap/head/offlineimap/ui/Curses.py
M /offlineimap/head/offlineimap/ui/Tk.py
M /offlineimap/head/offlineimap.sgml
Various bug fixes and enhancements
------------------------------------------------------------------------
r588 | jgoerzen | 2004-07-13 10:25:27 -0500 (Tue, 13 Jul 2004) | 1 line
Changed paths:
M /offlineimap/head/bin/offlineimap
M /offlineimap/head/offlineimap/version.py
M /offlineimap/head/offlineimap.py
Updated for 4.0.5
------------------------------------------------------------------------
r587 | jgoerzen | 2004-07-13 10:24:46 -0500 (Tue, 13 Jul 2004) | 1 line
Changed paths:
M /offlineimap/head/ChangeLog
Updated ChangeLog
------------------------------------------------------------------------
r586 | jgoerzen | 2004-07-13 10:22:41 -0500 (Tue, 13 Jul 2004) | 1 line
Changed paths:
M /offlineimap/head/debian/changelog
M /offlineimap/head/debian/control
Added python, python-dev to build-deps.
------------------------------------------------------------------------
r585 | jgoerzen | 2004-06-15 04:45:13 -0500 (Tue, 15 Jun 2004) | 1 line
Changed paths:
M /offlineimap/head/debian/changelog
Updated changelog
------------------------------------------------------------------------
r584 | jgoerzen | 2004-06-15 04:44:05 -0500 (Tue, 15 Jun 2004) | 1 line
Changed paths:
M /offlineimap/head/bin/offlineimap
M /offlineimap/head/offlineimap/version.py
M /offlineimap/head/offlineimap.py
Preparing 4.0.4
------------------------------------------------------------------------
r583 | jgoerzen | 2004-06-15 04:43:14 -0500 (Tue, 15 Jun 2004) | 1 line
Changed paths:
M /offlineimap/head/ChangeLog
M /offlineimap/head/debian/changelog
M /offlineimap/head/offlineimap/folder/IMAP.py
Reverted patch from Daniel James and updated Changelog.
------------------------------------------------------------------------
r582 | jgoerzen | 2004-06-04 11:04:29 -0500 (Fri, 04 Jun 2004) | 1 line
Changed paths:
M /offlineimap/head/COPYRIGHT
M /offlineimap/head/debian/changelog
M /offlineimap/head/debian/copyright
M /offlineimap/head/offlineimap/version.py
Updated copyright info
------------------------------------------------------------------------
r581 | jgoerzen | 2004-06-04 10:50:57 -0500 (Fri, 04 Jun 2004) | 1 line
Changed paths:
M /offlineimap/head/debian/changelog
M /offlineimap/head/debian/rules
M /offlineimap/head/offlineimap/version.py
Use dh_python
------------------------------------------------------------------------
r580 | jgoerzen | 2004-06-04 10:47:21 -0500 (Fri, 04 Jun 2004) | 1 line
Changed paths:
M /offlineimap/head/Makefile
M /offlineimap/head/debian/changelog
M /offlineimap/head/debian/control
M /offlineimap/head/debian/rules
M /offlineimap/head/offlineimap/version.py
Final changes before 4.0.3
------------------------------------------------------------------------
r579 | jgoerzen | 2004-06-04 10:42:52 -0500 (Fri, 04 Jun 2004) | 1 line
Changed paths:
M /offlineimap/head/ChangeLog
M /offlineimap/head/Makefile
Preparing 4.0.3
------------------------------------------------------------------------
r578 | jgoerzen | 2004-06-04 10:29:24 -0500 (Fri, 04 Jun 2004) | 2 lines
Changed paths:
M /offlineimap/head/debian/changelog
M /offlineimap/head/offlineimap/folder/IMAP.py
Applied patch from Daniel James to adjust the insertion point
for a new header.
------------------------------------------------------------------------
r577 | jgoerzen | 2004-06-04 10:26:30 -0500 (Fri, 04 Jun 2004) | 1 line
Changed paths:
M /offlineimap/head/debian/changelog
M /offlineimap/head/debian/control
M /offlineimap/head/manual.html
M /offlineimap/head/manual.pdf
M /offlineimap/head/manual.ps
M /offlineimap/head/manual.txt
M /offlineimap/head/offlineimap.1
M /offlineimap/head/offlineimap.conf
M /offlineimap/head/offlineimap.sgml
Fixed various doc bugs
------------------------------------------------------------------------
r576 | jgoerzen | 2004-06-04 10:13:11 -0500 (Fri, 04 Jun 2004) | 1 line
Changed paths:
M /offlineimap/head/bin/offlineimap
M /offlineimap/head/debian/changelog
M /offlineimap/head/debian/control
M /offlineimap/head/debian/rules
M /offlineimap/head/offlineimap/version.py
M /offlineimap/head/offlineimap.py
M /offlineimap/head/setup.py
Moved to Python2.3 and bumped version number
------------------------------------------------------------------------
r575 | jgoerzen | 2003-10-31 15:18:56 -0600 (Fri, 31 Oct 2003) | 2 lines
Changed paths:
M /offlineimap/head/debian/changelog
M /offlineimap/head/offlineimap/ui/Noninteractive.py
Fixed missing _display in Noninteractive.
------------------------------------------------------------------------
r574 | jgoerzen | 2003-10-10 23:23:47 -0500 (Fri, 10 Oct 2003) | 1 line
Changed paths:
M /offlineimap/head/debian/changelog
Slight changelog fix for 4.0.1
------------------------------------------------------------------------
r521 | jgoerzen | 2003-07-25 16:01:25 -0500 (Fri, 25 Jul 2003) | 2 lines
Changed paths:
M /offlineimap/head/offlineimap/__init__.py
M /offlineimap/head/offlineimap/init.py
M /offlineimap/head/offlineimap/localeval.py
A few fixes for Jython compatibility.
------------------------------------------------------------------------
r520 | jgoerzen | 2003-07-25 15:47:18 -0500 (Fri, 25 Jul 2003) | 2 lines
Changed paths:
M /offlineimap/head/offlineimap.1
Updated docs
------------------------------------------------------------------------
r519 | jgoerzen | 2003-07-25 15:41:35 -0500 (Fri, 25 Jul 2003) | 2 lines
Changed paths:
M /offlineimap/head/offlineimap.sgml
Updated docs with some history
------------------------------------------------------------------------
r518 | jgoerzen | 2003-07-24 16:15:27 -0500 (Thu, 24 Jul 2003) | 2 lines
Changed paths:
M /offlineimap/head/offlineimap/init.py
Fixed a problem with the version number printout routine.
------------------------------------------------------------------------
r517 | jgoerzen | 2003-07-24 15:58:20 -0500 (Thu, 24 Jul 2003) | 3 lines
Changed paths:
M /offlineimap/head/offlineimap/__init__.py
M /offlineimap/head/offlineimap/accounts.py
M /offlineimap/head/offlineimap/repository/__init__.py
Adjusted __init__ code to use __all__ to provide better compatibility with
jython.
------------------------------------------------------------------------
r515 | jgoerzen | 2003-07-18 16:13:04 -0500 (Fri, 18 Jul 2003) | 2 lines
Changed paths:
M /offlineimap/head/debian/changelog
M /offlineimap/head/debian/docs
Included more docs in Debian package.
------------------------------------------------------------------------
r514 | jgoerzen | 2003-07-18 15:59:56 -0500 (Fri, 18 Jul 2003) | 5 lines
Changed paths:
M /offlineimap/head/debian/changelog
M /offlineimap/head/offlineimap.1
M /offlineimap/head/offlineimap.sgml
Fixed a manpage typo.
Closes: [debian.org #201497]
Notify: bk@bk.cx
------------------------------------------------------------------------
r511 | jgoerzen | 2003-07-18 13:56:15 -0500 (Fri, 18 Jul 2003) | 2 lines
Changed paths:
M /offlineimap/head/bin/offlineimap
M /offlineimap/head/offlineimap/version.py
M /offlineimap/head/offlineimap.py
Final version number updates
------------------------------------------------------------------------
r510 | jgoerzen | 2003-07-18 13:54:18 -0500 (Fri, 18 Jul 2003) | 3 lines
Changed paths:
M /offlineimap/head/ChangeLog
M /offlineimap/head/manual.html
M /offlineimap/head/manual.pdf
M /offlineimap/head/manual.ps
M /offlineimap/head/manual.txt
Final commits before 4.0. This is the re-built manual and updated
ChangeLog.
------------------------------------------------------------------------
r509 | jgoerzen | 2003-07-18 13:49:13 -0500 (Fri, 18 Jul 2003) | 3 lines
Changed paths:
A /offlineimap/head/UPGRADING
M /offlineimap/head/debian/changelog
M /offlineimap/head/offlineimap.1
M /offlineimap/head/offlineimap.sgml
Added a section on upgrading to the documentation
------------------------------------------------------------------------
r487 | jgoerzen | 2003-06-26 14:03:07 -0500 (Thu, 26 Jun 2003) | 2 lines
Changed paths:
M /offlineimap/head/bin/offlineimap
M /offlineimap/head/debian/changelog
M /offlineimap/head/offlineimap/folder/Maildir.py
M /offlineimap/head/offlineimap/version.py
M /offlineimap/head/offlineimap.py
Fixed version confusion
------------------------------------------------------------------------
r486 | jgoerzen | 2003-06-26 13:38:47 -0500 (Thu, 26 Jun 2003) | 2 lines
Changed paths:
M /offlineimap/head/bin/offlineimap
M /offlineimap/head/debian/changelog
M /offlineimap/head/offlineimap/version.py
M /offlineimap/head/offlineimap.py
Prep for 3.99.19
------------------------------------------------------------------------
r485 | jgoerzen | 2003-06-26 13:28:54 -0500 (Thu, 26 Jun 2003) | 5 lines
Changed paths:
M /offlineimap/head/debian/changelog
M /offlineimap/head/offlineimap/folder/IMAP.py
* Applied patch from Joerg Wendland <joergland@debian.org> to use
APPENDUID result from mail servers that provide it. Closes: #198772.
Resolves: [debian.org #198772]
------------------------------------------------------------------------
r484 | jgoerzen | 2003-06-02 11:17:29 -0500 (Mon, 02 Jun 2003) | 7 lines
Changed paths:
M /offlineimap/head/debian/changelog
M /offlineimap/head/offlineimap/folder/IMAP.py
M /offlineimap/head/offlineimap/imapserver.py
* Added a "force" option to imapserver/select to force a reloading of a
folder. Per [complete.org #67], when cachemessagelist() was called
on an object that was cached from a previous run, it would not
re-issue the select().
Closes: [complete.org #67]
------------------------------------------------------------------------
r482 | jgoerzen | 2003-06-02 11:11:51 -0500 (Mon, 02 Jun 2003) | 1 line
Changed paths:
M /offlineimap/head/debian/control
------------------------------------------------------------------------
r481 | jgoerzen | 2003-06-02 11:09:57 -0500 (Mon, 02 Jun 2003) | 2 lines
Changed paths:
M /offlineimap/head/Makefile
M /offlineimap/head/bin/offlineimap
M /offlineimap/head/debian/changelog
M /offlineimap/head/debian/rules
M /offlineimap/head/offlineimap.py
M /offlineimap/head/setup.py
Updated
------------------------------------------------------------------------
r480 | jgoerzen | 2003-06-02 09:52:33 -0500 (Mon, 02 Jun 2003) | 3 lines
Changed paths:
M /offlineimap/head/offlineimap/init.py
M /offlineimap/head/offlineimap/ui/UIBase.py
Fixed the -l option
------------------------------------------------------------------------
r479 | jgoerzen | 2003-06-02 09:07:30 -0500 (Mon, 02 Jun 2003) | 2 lines
Changed paths:
M /offlineimap/head/offlineimap/init.py
Made -d recognized
------------------------------------------------------------------------
r478 | jgoerzen | 2003-06-02 09:06:18 -0500 (Mon, 02 Jun 2003) | 4 lines
Changed paths:
M /offlineimap/head/Makefile
M /offlineimap/head/bin/offlineimap
M /offlineimap/head/debian/changelog
M /offlineimap/head/debian/control
M /offlineimap/head/debian/rules
M /offlineimap/head/manual.html
M /offlineimap/head/manual.pdf
M /offlineimap/head/manual.ps
M /offlineimap/head/manual.txt
M /offlineimap/head/offlineimap/imaplib.py
M /offlineimap/head/offlineimap/imapserver.py
M /offlineimap/head/offlineimap/init.py
M /offlineimap/head/offlineimap/ui/Curses.py
M /offlineimap/head/offlineimap/ui/TTY.py
M /offlineimap/head/offlineimap/ui/Tk.py
M /offlineimap/head/offlineimap/ui/UIBase.py
M /offlineimap/head/offlineimap.1
M /offlineimap/head/offlineimap.py
M /offlineimap/head/offlineimap.sgml
M /offlineimap/head/setup.py
Added -l option. Updated documentation for it. Changed _msg to _display
override in UI modules. Renamed "doc" to "docs" target in Makefile to avoid
conflicting with a subdir.
------------------------------------------------------------------------
r477 | jgoerzen | 2003-05-27 17:01:27 -0500 (Tue, 27 May 2003) | 2 lines
Changed paths:
M /offlineimap/head/debian/changelog
M /offlineimap/head/offlineimap/folder/IMAP.py
M /offlineimap/head/offlineimap/imaplib.py
Fixed SSL for Python2.3.
------------------------------------------------------------------------
r475 | jgoerzen | 2003-05-06 09:27:36 -0500 (Tue, 06 May 2003) | 2 lines
Changed paths:
M /offlineimap/head/ChangeLog
M /offlineimap/head/bin/offlineimap
M /offlineimap/head/offlineimap/version.py
M /offlineimap/head/offlineimap.py
Preparing for 3.99.17
------------------------------------------------------------------------
r474 | jgoerzen | 2003-05-06 09:26:12 -0500 (Tue, 06 May 2003) | 17 lines
Changed paths:
M /offlineimap/head/debian/changelog
M /offlineimap/head/offlineimap/folder/Maildir.py
offlineimap (3.99.17) unstable; urgency=low
* Fixed two potential obscure race conditions in folder/Maildir.py.
+ Condition 1 involved the gettimeseq() function. This function
accesses per-module variables but does not have a lock. It may have
been possible for this to have been called in such a way that timeseq
was not properly updated.
+ Condition 2 involved the call to gettimeseq(). Since the timeseq is
based on the system clock, we now use the time as reported inside
timeseq() rather than outside. This way, we can be assured that the
same value is in use both places.
* Added debug code to savemessage in folder/Maildir.py to try to track
down a mysterious 0-length file bug.
-- John Goerzen <jgoerzen@complete.org> Tue, 6 May 2003 09:21:38 -0500
------------------------------------------------------------------------
r472 | jgoerzen | 2003-05-06 08:50:01 -0500 (Tue, 06 May 2003) | 2 lines
Changed paths:
M /offlineimap/head/bin/offlineimap
M /offlineimap/head/offlineimap/version.py
M /offlineimap/head/offlineimap.py
Prepping for 3.99.16
------------------------------------------------------------------------
r471 | jgoerzen | 2003-05-06 08:41:13 -0500 (Tue, 06 May 2003) | 8 lines
Changed paths:
M /offlineimap/head/debian/changelog
M /offlineimap/head/offlineimap/folder/IMAP.py
* Added some significant debug code to folder/IMAP.py when saving a new
message with APPEND. This should make it easier to track down bugs both
in OfflineIMAP and in mail servers that implement this poorly.
* Fixed adding of X-OfflineIMAP header when the message starts out with
no headers. (This should not generally occur.) This should help
with some "invalid literal for long()" problems.
------------------------------------------------------------------------
r470 | jgoerzen | 2003-04-29 16:54:07 -0500 (Tue, 29 Apr 2003) | 2 lines
Changed paths:
M /offlineimap/head/debian/changelog
Added a note about the Debian bug this closes.
------------------------------------------------------------------------
r469 | jgoerzen | 2003-04-29 15:18:17 -0500 (Tue, 29 Apr 2003) | 2 lines
Changed paths:
A /offlineimap/head/docs/sgml-common
A /offlineimap/head/docs/sgml-common/Makefile.common
A /offlineimap/head/docs/sgml-common/ps2epsi
Added from rev 5
------------------------------------------------------------------------
r468 | jgoerzen | 2003-04-29 14:44:17 -0500 (Tue, 29 Apr 2003) | 2 lines
Changed paths:
A /offlineimap/head/docs
Added
------------------------------------------------------------------------
r467 | jgoerzen | 2003-04-29 11:30:26 -0500 (Tue, 29 Apr 2003) | 13 lines
Changed paths:
M /offlineimap/head/debian/changelog
M /offlineimap/head/offlineimap/repository/Maildir.py
* When sep was /, the new Maildir support code would recursively try to
scan ., resulting in huge paths and an eventual crash. Fixed with
a one-line patch to Maildir.py.
Closes: [complete.org #60]
Sergei,
The below diff is going into 3.99.16. You can apply it to 3.99.15 and it
should work for you now. Please let me know. (Ignore any patch errors for
debian/changelog). Thanks for the report.
------------------------------------------------------------------------
r466 | jgoerzen | 2003-04-29 10:48:52 -0500 (Tue, 29 Apr 2003) | 1 line
Changed paths:
M /offlineimap/head/README
Testing bug mailing
------------------------------------------------------------------------
r465 | jgoerzen | 2003-04-29 10:46:05 -0500 (Tue, 29 Apr 2003) | 4 lines
Changed paths:
M /offlineimap/head/README
This is more testing.
Closes: [complete.org #62]
------------------------------------------------------------------------
r464 | jgoerzen | 2003-04-29 10:38:05 -0500 (Tue, 29 Apr 2003) | 5 lines
Changed paths:
M /offlineimap/head/README
Testing BTS actions.
Notify: jglt@complete.org
Notify: [complete.org #62], [debian.org #154165]
------------------------------------------------------------------------
r463 | jgoerzen | 2003-04-29 08:13:52 -0500 (Tue, 29 Apr 2003) | 2 lines
Changed paths:
M /offlineimap/head/debian/changelog
Readying to accept changes, captain
------------------------------------------------------------------------
r462 | jgoerzen | 2003-04-28 22:38:20 -0500 (Mon, 28 Apr 2003) | 4 lines
Changed paths:
M /offlineimap/head/README
Updated copyright date.
Notify: jgoerzen@complete.org, jgoerzen@debian.org
------------------------------------------------------------------------
r461 | jgoerzen | 2003-04-28 22:35:50 -0500 (Mon, 28 Apr 2003) | 2 lines
Changed paths:
M /offlineimap/head/ChangeLog
Updated the ChangeLog
------------------------------------------------------------------------
r459 | jgoerzen | 2003-04-28 16:41:50 -0500 (Mon, 28 Apr 2003) | 1 line
Changed paths:
M /offlineimap/head/ChangeLog
M /offlineimap/head/bin/offlineimap
M /offlineimap/head/debian/changelog
M /offlineimap/head/offlineimap/version.py
M /offlineimap/head/offlineimap.py
Preparing for 3.99.15
------------------------------------------------------------------------
r458 | jgoerzen | 2003-04-28 16:25:42 -0500 (Mon, 28 Apr 2003) | 3 lines
Changed paths:
M /offlineimap/head/debian/changelog
M /offlineimap/head/offlineimap/CustomConfig.py
M /offlineimap/head/offlineimap/accounts.py
M /offlineimap/head/offlineimap.conf
* autorefresh may now be a floating-point value. Closes: #190060.
------------------------------------------------------------------------
r457 | jgoerzen | 2003-04-28 16:17:30 -0500 (Mon, 28 Apr 2003) | 5 lines
Changed paths:
M /offlineimap/head/debian/changelog
M /offlineimap/head/offlineimap/imaplib.py
* Made OfflineIMAP IPv6-aware. Used the short patch from
Adriaan Peeters <apeeters@lashout.net> in Debian bug report 186636.
Closes: #186636.
------------------------------------------------------------------------
r456 | jgoerzen | 2003-04-28 15:52:03 -0500 (Mon, 28 Apr 2003) | 4 lines
Changed paths:
M /offlineimap/head/debian/changelog
M /offlineimap/head/offlineimap/repository/IMAP.py
* Fixed a silly error relating to handling of the remotepassfile.
Closes: #189935.
------------------------------------------------------------------------
r455 | jgoerzen | 2003-04-28 15:48:55 -0500 (Mon, 28 Apr 2003) | 7 lines
Changed paths:
M /offlineimap/head/debian/changelog
M /offlineimap/head/offlineimap/ui/UIBase.py
* Raise an exception when the status area is locked. This will cause UIs
to go through their normal exception handling code. In particular, for
the Curses.Blinkenlights interface, the Curses module will be stopped
and the error message will be printed on the console. Previously, this
error message would not have been visible. Closes: #185709.
------------------------------------------------------------------------
r454 | jgoerzen | 2003-04-28 15:43:41 -0500 (Mon, 28 Apr 2003) | 2 lines
Changed paths:
M /offlineimap/head/debian/changelog
Updated bug closing number
------------------------------------------------------------------------
r453 | jgoerzen | 2003-04-28 14:04:22 -0500 (Mon, 28 Apr 2003) | 4 lines
Changed paths:
M /offlineimap/head/debian/changelog
M /offlineimap/head/offlineimap/syncmaster.py
M /offlineimap/head/offlineimap/threadutil.py
* Backed out removal of SYNC_WITH_TIMER_TERMINATE code to deal with
completed syncs. Without this code, -o broke because the app would
never terminate.
------------------------------------------------------------------------
r451 | jgoerzen | 2003-04-28 09:16:58 -0500 (Mon, 28 Apr 2003) | 2 lines
Changed paths:
M /offlineimap/head/ChangeLog
Updated for 3.99.14
------------------------------------------------------------------------
r450 | jgoerzen | 2003-04-28 09:16:30 -0500 (Mon, 28 Apr 2003) | 2 lines
Changed paths:
M /offlineimap/head/bin/offlineimap
M /offlineimap/head/debian/changelog
M /offlineimap/head/offlineimap/version.py
M /offlineimap/head/offlineimap.py
Preparing for 3.99.14
------------------------------------------------------------------------
r449 | jgoerzen | 2003-04-22 10:47:25 -0500 (Tue, 22 Apr 2003) | 11 lines
Changed paths:
M /offlineimap/head/debian/changelog
M /offlineimap/head/manual.html
M /offlineimap/head/manual.pdf
M /offlineimap/head/manual.ps
M /offlineimap/head/manual.txt
M /offlineimap/head/offlineimap.1
M /offlineimap/head/offlineimap.conf.minimal
M /offlineimap/head/offlineimap.sgml
* Slight renaming in offlineimap.conf.minimal to clarify things.
* Documentation updated with information about new features.
Closes: #189771.
+ Described IMAP-IMAP syncing
+ Updated minimal example with new offlineimap.conf.minimal
+ Updated UID information. Added link to recent mailing list
discussion.
+ Described KMail syncing, which now works.
+ Added link to mailing list archives.
------------------------------------------------------------------------
r448 | jgoerzen | 2003-04-18 15:44:10 -0500 (Fri, 18 Apr 2003) | 4 lines
Changed paths:
M /offlineimap/head/debian/changelog
M /offlineimap/head/offlineimap/folder/IMAP.py
* Fixed the regular expression that fixes line endings to make sure to
deal with \n\n properly.
------------------------------------------------------------------------
r447 | jgoerzen | 2003-04-17 21:14:45 -0500 (Thu, 17 Apr 2003) | 4 lines
Changed paths:
M /offlineimap/head/debian/changelog
M /offlineimap/head/offlineimap/repository/Maildir.py
* Added the ability to use the top level of a Maildir as folder named
".". Useful for generating Maildir trees for a Courier server.
------------------------------------------------------------------------
r446 | jgoerzen | 2003-04-17 21:06:04 -0500 (Thu, 17 Apr 2003) | 2 lines
Changed paths:
M /offlineimap/head/bin/offlineimap
M /offlineimap/head/offlineimap/folder/IMAP.py
M /offlineimap/head/offlineimap/repository/IMAP.py
M /offlineimap/head/offlineimap/version.py
M /offlineimap/head/offlineimap.py
Prepping for 0.99.13 -- fixed some niggling bugs
------------------------------------------------------------------------
r445 | jgoerzen | 2003-04-17 18:31:25 -0500 (Thu, 17 Apr 2003) | 6 lines
Changed paths:
M /offlineimap/head/debian/changelog
M /offlineimap/head/offlineimap/folder/IMAP.py
M /offlineimap/head/offlineimap/repository/IMAP.py
* Now checks that SELECT succeeded when entering a folder.
* Verifies that folders listed on folderincludes actually exist by trying
to enter them. Thus, if they do not exist, they can be created on the
first run.
------------------------------------------------------------------------
r444 | jgoerzen | 2003-04-17 18:18:54 -0500 (Thu, 17 Apr 2003) | 5 lines
Changed paths:
M /offlineimap/head/debian/changelog
M /offlineimap/head/offlineimap/folder/IMAP.py
* Fixed line-ending code to deal with files with mixed \n and \r\n
codes. This is a rare case, but now is more onerous because we now
have to find headers.
------------------------------------------------------------------------
r443 | jgoerzen | 2003-04-17 16:43:54 -0500 (Thu, 17 Apr 2003) | 3 lines
Changed paths:
M /offlineimap/head/offlineimap/ui/TTY.py
M /offlineimap/head/offlineimap/ui/Tk.py
Fixed account names in password prompts
------------------------------------------------------------------------
r442 | jgoerzen | 2003-04-17 16:18:34 -0500 (Thu, 17 Apr 2003) | 2 lines
Changed paths:
D /offlineimap/branches/account-sep
A /offlineimap/head (from /offlineimap/branches/account-sep:441)
Moved account-sep branch to head
------------------------------------------------------------------------

View File

@ -1,46 +0,0 @@
# Copyright (C) 2002 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
SHELL=/bin/bash
clean:
-python2.3 setup.py clean --all
-rm -f `find . -name "*~"`
-rm -f `find . -name "*.tmp"`
-rm -f bin/offlineimapc
-rm -f `find . -name "*.pyc"`
-rm -f `find . -name "*.pygc"`
-rm -f `find . -name "*.class"`
-rm -f `find . -name "*.bak"`
-rm -f `find . -name ".cache*"`
-rm manpage.links
-rm manpage.refs
-find . -name auth -exec rm -vf {}/password {}/username \;
-svn cleanup
changelog:
svn log -v --stop-on-copy > ChangeLog
doc:
docbook2man offlineimap.sgml
docbook2man offlineimap.sgml
docbook2html -u offlineimap.sgml
mv offlineimap.html manual.html
man -t -l offlineimap.1 > manual.ps
ps2pdf manual.ps
groff -Tascii -man offlineimap.1 | sed $$'s/.\b//g' > manual.txt
-rm manpage.links manpage.refs

View File

@ -1,12 +0,0 @@
OfflineIMAP
Copyright (C) 2002, 2003 John Goerzen <jgoerzen@complete.org>
This software comes with ABSOLUTELY NO WARRANTY; see the file
COPYING for details. This is free software, and you are welcome
to distribute it under the conditions laid out in COPYING.
gopher://quux.org/1/devel/offlineimap
http://quux.org/devel/offlineimap
Please see manual.txt; the information previously in README has been moved
there.

View File

@ -1,9 +0,0 @@
NOTE:
The configuration file has changed in incompatile ways with 4.0. If you are
upgrading from a previous version, please read the UPGRADING section in the
manual before invoking OfflineIMAP.
-- John

View File

@ -1,21 +0,0 @@
#!/usr/bin/env python2.3
# Startup from system-wide installation
# Copyright (C) 2002, 2003 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from offlineimap import init
init.startup('4.0.7')

View File

@ -1,737 +0,0 @@
offlineimap (4.0.7) unstable; urgency=low
* Added additional debug logging when downloading messages in an attempt to
get the info needed to address #199452.
-- John Goerzen <jgoerzen@complete.org> Sun, 1 Aug 2004 16:48:15 -0500
offlineimap (4.0.6) unstable; urgency=low
* Corrected check for Curses.Blinkenlights hotkey press. Closes: #256336.
* Added version to generated header. Closes: #257893.
* Fixed warn() in Tk (reported by Martin Pool).
* Added Windows documentation from Chris Walker to the FAQ.
* Added Build-Conflicts for docbook-ebnf; it can mess up the doc
building.
-- John Goerzen <jgoerzen@complete.org> Mon, 26 Jul 2004 10:04:06 -0500
offlineimap (4.0.5) unstable; urgency=low
* Added python to build-deps. Closes: #259118.
-- John Goerzen <jgoerzen@complete.org> Tue, 13 Jul 2004 10:20:59 -0500
offlineimap (4.0.4) unstable; urgency=low
* Reverted the header insertion point patch applied in 4.0.3. It
has apparently caused header corruption. Closes: #254261.
-- John Goerzen <jgoerzen@complete.org> Tue, 15 Jun 2004 04:41:36 -0500
offlineimap (4.0.3) unstable; urgency=low
* Fixed version numbers to read 4.0.3. Closes: #220536.
* Switched defaults from Python 2.2 to Python 2.3.
Closes: #237560, #239018.
* Fixed description typo. Closes: #211251.
* Fixed nametrans example. Closes: #252644.
* Applied patch from Johannes Berg for mycmp example in manual.
Closes: #252645.
* Fixed typos in manual. Closes: #252646.
* Regenerated docs.
* Applied patch from Daniel James to adjust the insertion point
for a new header.
* Now calls dh_python and build-deps on newer debhelper.
* Updated copyright information.
-- John Goerzen <jgoerzen@complete.org> Fri, 4 Jun 2004 07:10:00 -0500
offlineimap (4.0.2) unstable; urgency=low
* Fixed missing definition of _display in Noninteractive.
-- John Goerzen <jgoerzen@complete.org> Fri, 31 Oct 2003 15:18:05 -0600
offlineimap (4.0.1) unstable; urgency=low
* Fixed a typo. Closes: #201497.
* Include UPGRADING in Debian package. Closes: #208650.
-- John Goerzen <jgoerzen@complete.org> Fri, 10 Oct 2003 23:21:38 -0500
offlineimap (4.0.0) unstable; urgency=low
* Poof, this is 4.0.
* Added UPGRADING file and UPGRADING instructions.
-- John Goerzen <jgoerzen@complete.org> Fri, 18 Jul 2003 17:31:14 -0500
offlineimap (3.99.20) unstable; urgency=low
* OfflineIMAP now moves messages between new and cur in Maildir when flags
have changed on the server.
* Applied patch from Joerg Wendland <joergland@debian.org> to use
APPENDUID result from mail servers that provide it. Closes: #198772.
-- John Goerzen <jgoerzen@complete.org> Thu, 27 Jun 2003 07:28:33 -0500
offlineimap (3.99.19) unstable; urgency=low
* Added a "force" option to imapserver/select to force a reloading of a
folder. Per [complete.org #67], when cachemessagelist() was called
on an object that was cached from a previous run, it would not
re-issue the select().
-- John Goerzen <jgoerzen@complete.org> Mon, 2 Jun 2003 07:04:53 -0500
offlineimap (3.99.18) unstable; urgency=low
* Made a fix for Python2.3 compatibility.
* Removed warning when thread debug is specified. Closes: #195739.
* New option -l to log debug info to a file without having it spew
on-screen.
* New debug type "thread".
-- John Goerzen <jgoerzen@complete.org> Tue, 27 May 2003 16:58:54 -0500
offlineimap (3.99.17) unstable; urgency=low
* Fixed two potential obscure race conditions in folder/Maildir.py.
+ Condition 1 involved the gettimeseq() function. This function
accesses per-module variables but does not have a lock. It may have
been possible for this to have been called in such a way that timeseq
was not properly updated.
+ Condition 2 involved the call to gettimeseq(). Since the timeseq is
based on the system clock, we now use the time as reported inside
timeseq() rather than outside. This way, we can be assured that the
same value is in use both places.
* Added debug code to savemessage in folder/Maildir.py to try to track
down a mysterious 0-length file bug.
-- John Goerzen <jgoerzen@complete.org> Tue, 6 May 2003 07:25:38 -0500
offlineimap (3.99.16) unstable; urgency=low
* This is a 4.0 TRACK release, and may be unstable or in flux!
* When sep was /, the new Maildir support code would recursively try to
scan ., resulting in huge paths and an eventual crash. Fixed with
a one-line patch to Maildir.py. Fixes [complete.org #60].
Closes: #191318.
* Added some significant debug code to folder/IMAP.py when saving a new
message with APPEND. This should make it easier to track down bugs both
in OfflineIMAP and in mail servers that implement this poorly.
* Fixed adding of X-OfflineIMAP header when the message starts out with
no headers. (This should not generally occur.) This should help
with some "invalid literal for long()" problems.
-- John Goerzen <jgoerzen@complete.org> Tue, 6 May 2003 07:10:17 -0500
offlineimap (3.99.15) unstable; urgency=low
* This is a 4.0 TRACK release, and may be unstable or in flux!
* Backed out removal of SYNC_WITH_TIMER_TERMINATE code to deal with
completed syncs. Without this code, -o broke because the app would
never terminate. Closes: #190063.
* Raise an exception when the status area is locked. This will cause UIs
to go through their normal exception handling code. In particular, for
the Curses.Blinkenlights interface, the Curses module will be stopped
and the error message will be printed on the console. Previously, this
error message would not have been visible. Closes: #185709.
* Fixed a silly error relating to handling of the remotepassfile.
Closes: #189935.
* Made OfflineIMAP IPv6-aware. Used the short patch from
Adriaan Peeters <apeeters@lashout.net> in Debian bug report 186636.
Closes: #186636.
* autorefresh may now be a floating-point value. Closes: #190060.
-- John Goerzen <jgoerzen@complete.org> Mon, 28 Apr 2003 17:30:32 -0500
offlineimap (3.99.14) unstable; urgency=low
* This is a 4.0 TRACK release, and may be unstable or in flux!
* Added the ability to use the top level of a Maildir as folder named
".". Useful for generating Maildir trees for a Courier server.
* Fixed the regular expression that fixes line endings to make sure to
deal with \n\n properly.
* Slight renaming in offlineimap.conf.minimal to clarify things.
* Documentation updated with information about new features.
Closes: #189771.
+ Described IMAP-IMAP syncing
+ Updated minimal example with new offlineimap.conf.minimal
+ Updated UID information. Added link to recent mailing list
discussion.
+ Described KMail syncing, which now works.
+ Added link to mailing list archives.
-- John Goerzen <jgoerzen@complete.org> Mon, 28 Apr 2003 07:13:23 -0500
offlineimap (3.99.13) unstable; urgency=low
* This is a 4.0 TRACK release, and may be unstable or in flux!
* Fixed password prompting for non-Curses UIs.
* Fixed line-ending code to deal with files with mixed \n and \r\n
codes. This is a rare case, but now is more onerous because we now
have to find headers.
* Now checks that SELECT succeeded when entering a folder.
* Verifies that folders listed on folderincludes actually exist by trying
to enter them. Thus, if they do not exist, they can be created on the
first run.
-- John Goerzen <jgoerzen@complete.org> Thu, 17 Apr 2003 18:02:13 -0500
offlineimap (3.99.12) unstable; urgency=low
* This is a 4.0 TRACK release, and may be unstable or in flux!
* Big news: OfflineIMAP can now sync two remote IMAP servers to each
other, with no need to write a Maildir at all.
* WARNING: the format of the configuration file *AND* the local
status area changes with this release!
* Major reworking of internal management of accounts. Previously, the
account defined a local Maildir and a remote IMAP server. Now, the
account is simply a connection between two repositories. For
traditional ones, an account basically specifies a refresh interval,
a Maildir repository, and an IMAP repository.
* Added a notion of a repository to the configuration file. Repositories
currently available are IMAP and Maildir. Combined with the new account
system, this lets the user define powerful combinations without
duplicating information.
* When uploading messages to an IMAP server, OfflineIMAP generates its
own X-OfflineIMAP header rather than trying to guess the new message UID
based on the Message-Id header. This leads to greater reliability when
uploading messages, especially when dealing with duplicate messages.
This change was required to permit reliable IMAP-to-IMAP syncing, but
helps with regular IMAP-to-Maildir syncing as well.
* Local status area under ~/.offlineimap revamped. It now contains
separate subdirectories for each account and repository, and they
contain UID validity information, UID mapping (for IMAP-to-IMAP
syncing). UID validity information is no longer stored in the Maildir
itself.
* New debug type: "thread" to debug multithreading.
* preauth tunnels no longer require remoteuser, remotepass, host,
or port in the configuration file.
* Logging for preauth tunnels is more sensible.
* Fixed a logic error for syncs with a reference that returns no folders.
-- John Goerzen <jgoerzen@complete.org> Thu, 17 Apr 2003 17:20:08 -0500
offlineimap (3.99.11) unstable; urgency=low
* Curses interface can now be resized. Closes: #176342.
-- John Goerzen <jgoerzen@wile.excelhustler.com> Thu, 13 Mar 2003 11:48:36 -0600
offlineimap (3.99.10) unstable; urgency=low
* Always do a flush in Noninteractive when writing out data.
* Fixed a bug in folder/Base.py relating to threads in
syncmessagesto_neguid.
-- John Goerzen <jgoerzen@complete.org> Fri, 7 Feb 2003 14:12:17 -0600
offlineimap (3.99.9) unstable; urgency=low
* Added check to make sure that two processes do not run in the same
directory at once. Closes: #178939.
-- John Goerzen <jgoerzen@complete.org> Wed, 29 Jan 2003 13:19:14 -0600
offlineimap (3.99.8) unstable; urgency=low
* This is a 4.0 TRACK release, and may be unstable or in flux!
* Fixed several problems with the Curses interface: colors showing
up very weird on FreeBSD and exceptions when running on non-color
terminals.
-- John Goerzen <jgoerzen@complete.org> Fri, 10 Jan 2003 11:46:24 -0600
offlineimap (3.99.7) unstable; urgency=low
* This is a 4.0 TRACK release, and may be unstable or in flux!
* Converted entire manual to DocBook SGML so it will be easier to
expand in the future. The HTML manual, also, looks far nicer now
than it did before.
* Fixed the Tk.VerboseUI -- small, silly error introduced in 3.99.6.
* Multiple performance and reliability enhancements to syncing
algorithms, as described below.
* The process of uploading new messages from local folders to the IMAP
server was not internally multi-threaded previously. Now it is.
This means that if you have a single folder with lots of new messages
locally, the processing time should be dramatically sped up. Moreover,
the process should be more reliable because we do not risk connections
going dead.
* The process of synchronizing flags has been overhauled and optimized.
Previously, for each message where a flag (seen, replied, etc.) was
changed, we'd issue a separate command to the IMAP server to adjust
things. Now, we issue one command for each flag. In other words,
instead of seing 45 messages saying something like "Adding flag S to
message 1421", you now see one message saying "Adding flag S to 45
messages" -- and the interaction with the IMAP server may well be
almost 45 times faster on this. We will now issue at most four
commands per flag operation (add or remove) per folder, where before,
we may have issued as many as two per message. Should be a
large speedup in most cases, but a small slowdown in a few.
* Potentially increased the reliability of writing files to the Maildir.
-- John Goerzen <jgoerzen@complete.org> Wed, 8 Jan 2003 19:30:11 -0600
offlineimap (3.99.6) unstable; urgency=low
* This a 4.0 TRACK release, and may be unstable or in flux!
* Completed work to make both graphical interfaces work
with a threaded Tcl/Tk Tkinter. A threaded Tcl/Tk actually makes
life MORE difficult for multi-threaded Python programs, argh.
* Now properly handles folder names that contain parenthesis. Used
patch from Kyler Laird in
http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=173895.
Closes: #173895.
* Changed to a more account-centric behavior. The refresh time is now
a per-account variable. Implemented new account classes. User
interfaces must now be updated to take advantage of this.
*** NOTE *** THIS CHANGE BREAKS COMPATIBILITY WITH OLDER VERSIONS.
* New color console interface: Curses.Blinkenlights! Try it out!
Closes: #167357.
* Due to possibly having one account sleep while another is reading a
password, and other tricky situations, support for nice updating and
cancelling of a sleep in TTY.TTYUI has been removed. (Sleep will
still work; there is just no way to cancel it early.)
.
However, this is not going to be a huge problem because the new Curses
Blinkenlights interface has this support, and does it a lot better than
TTY.TTYUI ever could have.
* Provided config file default for many more options. Closes: #168219.
* New example file offlineimap.conf.minimal showing you how to get started
fast. It uses only 6 lines to set up an account.
-- John Goerzen <jgoerzen@complete.org> Mon, 6 Jan 2003 06:59:44 -0600
offlineimap (3.99.5) unstable; urgency=low
* Added ability to disable expunging on the server.
* Fixed infinite loop with preauth. Closes: #169514, #171485.
-- John Goerzen <jgoerzen@complete.org> Tue, 03 Dec 2002 06:22:37 -0600
offlineimap (3.99.4) unstable; urgency=low
* Fixed setup.py installation instructions.
* Added more debugging to the CRAM-MD5 authentication module.
* CRAM-MD5 *really* fixed this time. Thanks to MJ for the patch.
* Adding missing import os to imapserver.py. Thanks to John Wiegley
for catching that.
-- John Goerzen <jgoerzen@complete.org> Tue, 5 Nov 2002 08:06:45 -0600
offlineimap (3.99.3) unstable; urgency=low
* Moved password promting into imapserver.py. Passwords are now asked
for on-demand and typos will no longer crash the program (the user
will be re-prompted). Closes: #162672.
* Falls back to plain-text auth if CRAM-MD5 fails. Fixes [complete.org #42]
* Fixed CRAM-MD5 auth so it actually works now.
-- John Goerzen <jgoerzen@complete.org> Mon, 4 Nov 2002 06:16:11 -0600
offlineimap (3.99.2) unstable; urgency=low
* Further attempts to fix imapsplit problems.
* When an exception occurs, OfflineIMAP will attempt to print the last
50 debug messages, whether or not debugging was enabled for this
session. This way, even unexpected and non-repeatable errors stand
a chance of getting a more detailed log.
* Handle uidvalidity file in an atomic fashion. CLoses: #165600.
* Supports CRAM-MD5 authentication. Fixes [complete.org #38], and for
Debian, Closes: #154165.
* Noted CRAM-MD5 support in the "CONFORMING TO" section of the manual.
* Certain servers may not always return the UID flag for new messages.
This causes an OfflineIMAP exception, though rerunning will fix it.
Now, OfflineIMAP will detect the condition and ignore the offending
messages (without an exception) until the next sync.
-- John Goerzen <jgoerzen@complete.org> Sat, 02 Nov 2002 10:23:10 -0600
offlineimap (3.99.1) unstable; urgency=low
* Fixed some syntax errors in imaputil.py
* Fixed a syntax error with mbnames
-- John Goerzen <jgoerzen@complete.org> Wed, 9 Oct 2002 19:34:37 -0500
offlineimap (3.99.0) unstable; urgency=low
* The next few releases are adding features and reorganizing
code in preparation for 4.0.0.
* imaputil.py now logs information with IMAP debugging is enabled.
* Added folderfilter capability to mbnames recorder. You can now omit
specified folders from the mbnames output.
* Added a workaround to imaputil.py to deal with a bug in imaplib.py's
tuple when a response contains a literal in certain cases.
* Split out the code in bin/offlineimap into offlineimap/init.py.
Retaining bin/offlineimap as a skeletal piece only. Contains
about three lines of code now. This will make many things
easier, including debugging.
* Added library version check to bin/offlineimap and
offlineimap/init.py.
* Moved __main__.ui to functions in UIBase: getglobalui() and
setglobalui().
* Added license comments to some source files that were missing them.
* Moved some code from offlineimap/init.py to new file
offlineimap/syncmaster.py to help dileneate between code that
performs different functions.
* Moved threadexited from offlineimap/init.py to
offlineimap/threadutil.py.
* offlineimap.py is back to ease the use of OfflineIMAP in single-user
installations.
-- John Goerzen <jgoerzen@complete.org> Mon, 07 Oct 2002 05:08:08 -0500
offlineimap (3.2.8) unstable; urgency=low
* Added a work-around for some IMAP servers that respond poorly
to LIST "" "". It will now do LIST "" "*", for them only.
-- John Goerzen <jgoerzen@complete.org> Mon, 30 Sep 2002 10:48:01 -0500
offlineimap (3.2.7) unstable; urgency=low
* Moved executable to bin/offlineimap. This will allow setup.py to
properly install it as offlineimap instead of offlineimap.py.
* Made sure executables use /usr/bin/env in bangpath.
* Font size for Blinkenlights interface is now configurable.
-- John Goerzen <jgoerzen@complete.org> Thu, 19 Sep 2002 06:46:56 -0500
offlineimap (3.2.6) unstable; urgency=low
* Changed indentation in debian/control. Closes: #156327.
* Removed calls to folder object deletions. None have been implemented
anyway.
* folder/Maildir.py: unlink throws OSError, not IOError; fixed.
Now handles message deleting race condition properly.
Closes: #154497.
-- John Goerzen <jgoerzen@complete.org> Fri, 16 Aug 2002 17:43:19 -0500
offlineimap (3.2.5) unstable; urgency=low
* Now handles uploading messages without Message-Id headers.
Closes: #156022.
* Applied patch from Tommi Virtanen that adds two new config file
options: pythonfile and foldersort. Fixes [complete.org #29], and
for Debian, Closes: #155637.
* Added documentation for the above features.
* Even more resiliant in the face of invalid Date headers. Closes: #155994.
-- John Goerzen <jgoerzen@complete.org> Fri, 9 Aug 2002 17:54:01 -0500
offlineimap (3.2.4) unstable; urgency=low
* When using nested folders, the Maildir repository handler now properly
deals with folders that are nested inside "noselect" folders -- ones
that do not actually contain messages and are not provided in the
server's LIST response. Fixes [complete.org #32] and, for Debian,
Closes: #155866.
-- John Goerzen <jgoerzen@complete.org> Thu, 8 Aug 2002 17:54:44 -0500
offlineimap (3.2.3) unstable; urgency=low
* -d now takes a parameter: imap or maildir (or both) to specify
what type of debugging to do.
-- John Goerzen <jgoerzen@complete.org> Thu, 8 Aug 2002 10:02:36 -0500
offlineimap (3.2.2) unstable; urgency=low
* Updated manual to show new Gray color.
* Scrolling behavior is better now; sometimes, with fast-scrolling text,
the log would stop scrolling.
* Better handling of read-only folders. We will now warn if there is
a change, but not propogate it. New config variable ignore-readonly
can suppress the warnings. This fixes [complete.org #10] and,
for Debian, Closes: #154769.
* If a given Maildir folder is new, remove the associated local status
cache file, if any. That way, there will not be any chance of
propogating hordes of deletes and adds based on old status data.
* Added support for /-separated Maildirs -- that is, hierarchical
Maildir trees. Fixes [complete.org #28] and, for Debian,
Closes: #155460.
* Preventitive security: Folder names may not contain ./ or start with /.
-- John Goerzen <jgoerzen@complete.org> Wed, 07 Aug 2002 20:22:25 -0500
offlineimap (3.2.1) unstable; urgency=low
* There is a new "connecting" event that will appear in all but the
Quiet UIs. It has a gray color in Blinkenlights. This event indicates
the the program is connecting to a remote server.
* Blinkenlights UI log window is now scrolled and has a new
config file option "bufferlines" to specify the size of the scroll
buffer.
* The Blinkenlights window is now non-resizable when the log is disabled.
When the log is enabled, the window is resizable, and the changes in
size are reflected in the log widget. Therefore, the Bigger Log
and Smaller Log items can disappear, and the Log menu now becomes
a Show Log or a Hide Log menu option. No sub-menus necessary anymore.
This presents a much cleaner feel, more intuitive operation, and
faster navigation.
* Fix for account name interpolation in dot warning from 3.2.0 from
Martijn Pieters.
* Backed out check for . in account names for now. Will put it back in
when we have a consensus on what exactly to do. Doubt that anyone
has a foldername that would conflict with Blinkenlights anyway.
* Fix reading the ui.Tk.Blinkenlights bufferlines option.
-- John Goerzen <jgoerzen@complete.org> Wed, 24 Jul 2002 17:04:04 -0500
offlineimap (3.2.0) unstable; urgency=low
* New BLINKENLIGHTS interface! Mesmerising, isn't it?
* New ui.Tk.Blinkenlights section in offlineimap.conf.
* New USER INTERFACES section in the manual.
* TTYUI isusable() now checks to see if stdout and stdin are TTYs.
* Added build-dependency on python2.2-dev. Closes: #154167.
-- John Goerzen <jgoerzen@complete.org> Wed, 24 Jul 2002 17:53:20 -0500
offlineimap (3.1.1) unstable; urgency=low
* Modified imaputil.py and folder/Maildir.py to run faster. Eliminated
many regular expressions; pre-compiled many others.
* Fixed threadutil's exitnotifyloop to always handle threads in the order
they exited, rather than sometimes in the inverse order. This way,
make sure to handle thread's exception messages before a thread exited.
* Replaced imaplib.py's braindead readline() with a more efficient one.
* More optimizations to imaputil and folders for faster operation.
* These optimizations, all together, have resulted in OfflineIMAP
using approximately half the CPU time of previous versions, fixing
[complete.org #6], and... Closes: #153503.
-- John Goerzen <jgoerzen@complete.org> Wed, 24 Jul 2002 06:53:16 -0500
offlineimap (3.1.0) unstable; urgency=low
* When uploading messages from a Maildir, now convert \r\n to \n in case
the message is stored weirdly. That way, everything is uniform.
Fixes [complete.org #11].
* Manual: added UW IMAPD example with references from docwhat@gerf.org.
* New UI modules: Noninteractive.Basic and Noninteractive.Quiet.
Fixes [complete.org #14].
* Added per-thread profiling support to aid in debugging.
-- John Goerzen <jgoerzen@complete.org> Sun, 21 Jul 2002 16:09:42 -0500
offlineimap (3.0.3) unstable; urgency=low
* No longer throws an exception when updating messages with strange
Date headers; will just set IMAP Internaldate to the current date.
Closes: #153425.
* No longer doubles-up reference names for mailboxes. Closes: #153515.
* Noted new bug-tracking system in manual and rebuilt manual files.
* Now stores incoming messages in 'cur' instead of 'new' if they have
the S flag. Closes: #152482.
-- John Goerzen <jgoerzen@complete.org> Sun, 21 Jul 2002 13:46:13 -0500
offlineimap (3.0.2) unstable; urgency=low
* Fixed mailbox name recorder to use localfolder.getvisiblename() rather
than remotefolder.getvisiblename()
* Fixed remotepassfile option. Closes: #153119. Used 1-line patch from
Tommi Virtanen.
* Now handles cases of not being able to get UID for an uploaded message
more gracefully. This could occur if the server doesn't support
SEARCH, can't find the message ID, or finds multiple message IDs.
Closes: #153241.
* Now source is in Subversion. Make version.py log the Subversion
revision number.
-- John Goerzen <jgoerzen@complete.org> Wed, 15 Jul 2002 06:43:36 -0500
offlineimap (3.0.1) unstable; urgency=low
* Detabified the source.
* Added UI list to the manpage.
* Added -o (run only once) option with patch sent in by Martijn Pieters.
* Optimized folder/IMAP.py addmessagesflags() with new listjoin() in
imaputil. Now, send the server 1:5,7 instead of 1,2,3,4,5,7.
* Made folder/Maildir.py/deletemessage() more tolerant if a message
asked to be deleted already has been.
* In Base.py/copymessageto(), no longer bother calling getmessage()
unless a folder's storemessages() returns true. This will also help
with syncing to LocalStatus if the user deleted messages in the
Maildir since the cachemessagelist() was called.
-- John Goerzen <jgoerzen@complete.org> Fri, 12 Jul 2002 07:28:24 -0500
offlineimap (3.0.0) unstable; urgency=low
* Introduced a new graphical user interface written with Tkinter.
It features a nice view of multi-threaded displays.
* The TTY user interface now also displays thread names.
* Program-wide, new threads are given descriptive names to aid in
debugging and status messages.
* Added new module offlineimap/ui/detect.py that is used to detect
which user interface to select for a given session. Its operation
is governed by the ui config option and the -u command-line option.
* Made IMAP folder addmessagesflags() resiliant to a server refusing
to return a full set of new message flags. Closes: #152587.
* Completely rewrote documentation. OfflineIMAP now has an
exhaustive manpage, which is really a manual. It is also shipped
in plain text, HTML, PDF, and PostScript formats.
* New command-line options:
-1 to force no multi-threaded operation
-u to force a particular UI
-a to specify which accounts to sync
-h to print help
-c to specify an alternate config file
* Added a workaround for UW IMAP problem wherein the server loses
uidvalidity whenever a folder is emptied. Now, the program
will not consider it a problem if uidvalidity is lost when a folder
and the local status cache are both completely empty, since we do
not really need to preserve uidvalidity in that case anyway.
Closes: #152079.
-- John Goerzen <jgoerzen@complete.org> Thu, 11 Jul 2002 22:35:42 -0500
offlineimap (2.0.8) unstable; urgency=low
* Modified the IMAP folder to use SELECT rather than STATUS more often.
Makes the code more robust; handles better with read-only folders;
and runs faster, especially for non-threaded useres, where it
may eliminate up to 2-3 commands per folder.
* Made sure IMAP folder savemessage() does a select. This was a possible
bug.
* Modified Maildir folder to unlink messages with T flag in
cachemessagelist()
* My own box now syncs in 3 seconds.
* Optimized acquireconnection() to try to give a thread back the
connection that it last used, if possible.
-- John Goerzen <jgoerzen@complete.org> Tue, 9 Jul 2002 23:29:30 -0500
offlineimap (2.0.7) unstable; urgency=low
* Fixed imaplib.py to work better with read-only folders.
-- John Goerzen <jgoerzen@complete.org> Tue, 9 Jul 2002 20:24:21 -0500
offlineimap (2.0.6) unstable; urgency=low
* Added support for holdconnectionopen and keepalive. This feature
allows for an IMAP server connection(s) to be held open until
the next sync process, permitting faster restart times.
* Another try at read-only folder support. This is nasty because I
have no way to test it and imaplib's read-only support is weird.
* Closing out old bug; fixed in 1.0.2. Closes: #150803.
* Optimized algorithm so that a SELECT is never issued for folders
that contain no messages.
-- John Goerzen <jgoerzen@complete.org> Tue, 9 Jul 2002 20:05:24 -0500
offlineimap (2.0.5) unstable; urgency=low
* Fixed a folderfilter example. Partially fixes #152079.
* Added folderincludes capability. Partially fixes #152079.
* More fixes for read-only folders.
-- John Goerzen <jgoerzen@complete.org> Fri, 5 Jul 2002 09:21:52 -0500
offlineimap (2.0.4) unstable; urgency=low
* Made OfflineIMAP at least rudimentarily compatible with read-only
folders. It will still fail if they get modified locally, though.
* Flags are handled case-insensitively. Closes: #151993.
-- John Goerzen <jgoerzen@complete.org> Fri, 5 Jul 2002 09:10:29 -0500
offlineimap (2.0.3) unstable; urgency=low
* Added support for specifying references. Closes: #151960.
* Added -d command-line option to enable imaplib debugging.
-- John Goerzen <jgoerzen@complete.org> Thu, 4 Jul 2002 20:39:29 -0500
offlineimap (2.0.2) unstable; urgency=low
* Added support for remotepassfile. Closes: #151943.
* Added support for preauth tunnels.
-- John Goerzen <jgoerzen@complete.org> Thu, 4 Jul 2002 14:46:23 -0500
offlineimap (2.0.1) unstable; urgency=low
* Fixed a bug with not properly propogating foldersep changes.
Now, local folders and status folders properly use the foldersep
mechanism. This corrects a problem with Exchange servers.
* Wrote a major new thread montiring subsystem, defined a new
ExitNotifyThread. Handling of Ctrl-C now occurs within 1 second
rather than after the whole program terminates. Exceptions that
occur in a thread are now caught by the main thread and marshalled
over into the UI side of things for dispatch. The entire program will
now abort when one thread dies with an exception.
-- John Goerzen <jgoerzen@complete.org> Thu, 4 Jul 2002 09:07:06 -0500
offlineimap (2.0.0) unstable; urgency=low
* This code is now multithreaded. New config file options control the
behavior. This can make synchronizing several times faster.
* Fixed the STATUS call to be compatible with Exchange.
* Added the ability to exclude folders.
* If upgrading from 1.0.x, you will need to add maxsyncaccounts to the
general section and maxconnections to each account sections.
There is also a new folderfilter option.
You can find examples of all of these in the new offlineimap.conf
example file packaged with the distribution.
* The Debian package now properly installs the example offlineimap.conf
file.
* There is a new mailing list available. To join, send SUBSCRIBE
to offlineimap-request@complete.org. The posting address is
offlineimap@complete.org.
-- John Goerzen <jgoerzen@complete.org> Wed, 3 Jul 2002 19:21:32 -0500
offlineimap (1.0.4) unstable; urgency=low
* Deletion of more than one message has been optimized. This could make
deleting large numbers of messages far faster -- several orders of
magnitude.
* Moved more sleep code into ui layer. Fancier sleep actions are now
possible. Better handling of Ctrl-C in TTY handler.
-- John Goerzen <jgoerzen@complete.org> Tue, 2 Jul 2002 19:16:04 -0500
offlineimap (1.0.3) unstable; urgency=low
* Fixed a bug when a message was deleted on the IMAP side and modified
on the local side.
-- John Goerzen <jgoerzen@complete.org> Mon, 24 Jun 2002 19:08:21 -0500
offlineimap (1.0.2) unstable; urgency=low
* Made sure that LocalStatus does writing atomically. If the program
is interrupted during save(), there will always be a complete copy of
either the old or the new data.
-- John Goerzen <jgoerzen@complete.org> Mon, 24 Jun 2002 06:57:28 -0500
offlineimap (1.0.1) unstable; urgency=low
* Fixed a bug with writing messages to some IMAP servers. Turns
out we need to issue CHECK between APPEND and SEARCH for some.
Thanks to Donovan Lange for reporting this bug and helping track it
down.
-- John Goerzen <jgoerzen@complete.org> Fri, 21 Jun 2002 22:03:12 -0500
offlineimap (1.0.0) unstable; urgency=low
* Initial Release. Closes: #150571.
-- John Goerzen <jgoerzen@complete.org> Fri, 21 Jun 2002 18:54:56 -0500
Local variables:
mode: debian-changelog
End:

View File

@ -1,40 +0,0 @@
Source: offlineimap
Section: mail
Priority: optional
Maintainer: John Goerzen <jgoerzen@complete.org>
Build-Depends-Indep: debhelper (>> 4.2.0), python2.3, python2.3-dev (>= 2.2.2), groff, docbook-utils, python (>= 2.3), python-dev (>= 2.3)
Build-Conflicts-Indep: docbook-ebnf
Standards-Version: 3.5.2
Package: offlineimap
Architecture: all
Depends: python2.3
Suggests: python2.3-tk
Description: IMAP/Maildir synchronization and reader support
OfflineIMAP is a tool to simplify your e-mail reading. With
OfflineIMAP, you can:
.
* Read the same mailbox from multiple computers, and have your
changes (deletions, etc.) be automatically reflected on
all computers
.
* Use various mail clients to read a single mail box
.
* Read mail while offline (on a laptop) and have all changes
synchronized when you get connected again
.
* Read IMAP mail with mail readers that do not support IMAP
.
* Use SSL (secure connections) to read IMAP mail even if your reader
doesn't support SSL
.
* Synchronize your mail using a completely safe and fault-tolerant
algorithm. (At least I think it is!)
.
* Customize which mailboxes to synchronize with regular expressions
or lists.
.
* Synchronize your mail two to four times faster than with other tools
or other mail readers' internal IMAP support.
.
In short, OfflineIMAP is a tool to let you read mail how YOU want to.

View File

@ -1,23 +0,0 @@
This is offlineimap, written and maintained by John Goerzen <jgoerzen@complete.org>
on Fri, 21 Jun 2002 14:54:56 -0500.
The original source can always be found at:
ftp://ftp.debian.org/dists/unstable/main/source/
Copyright (C) 2002 - 2004 John Goerzen
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 with
the Debian GNU/Linux distribution in file /usr/share/common-licenses/GPL-2;
if not, write to the Free Software Foundation, Inc., 59 Temple Place,
Suite 330, Boston, MA 02111-1307 USA

View File

@ -1,2 +0,0 @@
usr/bin
usr/sbin

View File

@ -1,6 +0,0 @@
manual.txt
manual.ps
manual.pdf
manual.html
README
UPGRADING

View File

@ -1,100 +0,0 @@
#!/usr/bin/make -f
# Sample debian/rules that uses debhelper.
# GNU copyright 1997 to 1999 by Joey Hess.
# Modified by John Goerzen
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
# This is the debhelper compatibility version to use.
export DH_COMPAT=3
PYTHON=python2.3
PACKAGE=offlineimap
ifneq (,$(findstring debug,$(DEB_BUILD_OPTIONS)))
CFLAGS += -g
endif
ifeq (,$(findstring nostrip,$(DEB_BUILD_OPTIONS)))
INSTALL_PROGRAM += -s
endif
configure: configure-stamp
configure-stamp:
dh_testdir
# Add here commands to configure the package.
#$(PYTHON) setup.py configure
touch configure-stamp
build: build-stamp
build-stamp: configure-stamp
dh_testdir
# Add here commands to compile the package.
$(PYTHON) setup.py build
#/usr/bin/docbook-to-man debian/pygopherd.sgml > pygopherd.1
touch build-stamp
clean:
dh_testdir
dh_testroot
rm -f build-stamp configure-stamp
# Add here commands to clean up after the build process.
-$(MAKE) clean
dh_clean
install: build
dh_testdir
dh_testroot
dh_clean -k
dh_installdirs
# Add here commands to install the package into debian/pygopherd.
#$(MAKE) install DESTDIR=$(CURDIR)/debian/pygopherd
$(PYTHON) setup.py install --root=`pwd`/debian/$(PACKAGE) --no-compile
# Build architecture-dependent files here.
binary-arch: build install
# We have nothing to do by default.
# Build architecture-independent files here.
binary-indep: build install
dh_testdir
dh_testroot
# dh_installdebconf
dh_installdocs
dh_installexamples offlineimap.conf offlineimap.conf.minimal
dh_installmenu
# dh_installlogrotate
# dh_installemacsen
# dh_installpam
# dh_installmime
dh_installinit
dh_installcron
dh_installman offlineimap.1
dh_installinfo
# dh_undocumented
dh_installchangelogs
dh_link
dh_strip
dh_compress
dh_fixperms
# dh_perl
dh_python
# dh_makeshlibs
dh_installdeb
dh_shlibdeps
dh_gencontrol
dh_md5sums
dh_builddeb
binary: binary-indep binary-arch
.PHONY: build clean binary-indep binary-arch binary install configure

View File

@ -1,187 +0,0 @@
# -*- Mode: makefile; -*-
#
# Common Makefile for SGML documents
#
# Copyright (C) 2002, 2003 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# The following variables should be set:
# MASTERBASE -- basename of master file -- example: linux-guide
# BASICDEPS -- various dependencies of the master file. For instance,
# this might include files included in the SGML. It could also be empty.
# TOPNODE -- Basename of top id for HTML link.
MASTER := $(MASTERBASE).sgml
FIGUREDIRS := $(wildcard figures/*)
DOINDEX ?= yes
######################################################################
# Index generation
######################################################################
ifeq ($(DOINDEX), yes)
INDEXSGMLFILE := index/index.sgml
INDEXDATAFILE := index/HTML.index
$(INDEXSGMLFILE): $(INDEXDATAFILE)
@echo " *** Generating SGML index from index list"
collateindex.pl -i ch.index -g -o index/index.sgml index/HTML.index
$(INDEXDATAFILE): $(MASTER) $(BASICDEPS)
# jade -t sgml -d docbook.dsl -V html-index $(MASTER)
# jade -t sgml -V html-index $(MASTER)
@echo " *** Generating index list from document"
-rm -r index
mkdir index
collateindex.pl -i ch.index -N -o index/index.sgml
#mkdir html-temp
#docbook2html --output html-temp -V html-index $(MASTER)
docbook-2-html -O -V -O html-index $(HTMLARGS) $(MASTER)
mv $(MASTERBASE)-html/HTML.index index/
rm -r $(MASTERBASE)-html
endif # DOINDEX
######################################################################
# PostScript generation
######################################################################
$(MASTERBASE).ps: $(MASTER) $(BASICDEPS) $(INDEXSGMLFILE) $(EPSFILES)
@echo " *** Generating PostScript output"
# This works too: docbook2ps -V paper-size=Letter $(MASTER)
docbook-2-ps -q -O -V -O paper-size=Letter $(PSARGS) $(MASTER)
######################################################################
# Figure generation
######################################################################
%_1.epi: %.ps
$(get-epi)
%_2.epi: %.ps
$(get-epi)
%_3.epi: %.ps
$(get-epi)
%_4.epi: %.ps
$(get-epi)
%_5.epi: %.ps
$(get-epi)
%_6.epi: %.ps
$(get-epi)
%_7.epi: %.ps
$(get-epi)
%_8.epi: %.ps
$(get-epi)
%_9.epi: %.ps
$(get-epi)
%_10.epi: %.ps
$(get-epi)
%_11.epi: %.ps
$(get-epi)
%_12.epi: %.ps
$(get-epi)
%.png: %.epi
@echo " *** Generating PNG image for $<"
gs -q -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -r90 -dBATCH -dNOPAUSE \
-dSAFER -sOutputFile=$@ -sDEVICE=png16m $< -c showpage
%.ps: %.pdf
pdftops $<
######################################################################
# HTML generation
######################################################################
define copy-figures-worker
mkdir html/figures
for DIRECTORY in $(FIGUREDIRS); do mkdir html/$$DIRECTORY; cp -v $$DIRECTORY/*.png html/$$DIRECTORY/; done
endef
define copy-figures
$(if $(FIGUREDIRS),$(copy-figures-worker))
endef
html/index.html: $(MASTER) $(BASICDEPS) $(INDEXSGMLFILE) $(PNGFILES)
@echo " *** Generating HTML output"
-rm -r html
mkdir html
#docbook2html --output html $(MASTER)
docbook-2-html $(HTMLARGS) $(MASTER)
mv $(MASTERBASE)-html/* html/
rmdir $(MASTERBASE)-html
$(copy-figures)
# tidy -m html/*.html
ln -s $(TOPNODE).html html/index.html
-cp -v /usr/share/gtk-doc/*.png html/
######################################################################
# Cleaning
######################################################################
clean:
-rm -f `find . -name "*~"` `find . -name "*.png"` `find . -name "*.epi"`
-rm -r html-temp linux-guide-html html index
-rm *.aux *.log *.dvi *.tex *.jtex *.ps *.html *.log *.out jadetex.cfg
-rm *.ps html/*.html figures/topology/*.epi figures/topology/*.png
-rm *.log *.pdb
-rm `find . -name ".ps"` `find . -name "*.epi"` *.pdf
-rm `find . -name "*.png"`
######################################################################
# Utility functions
######################################################################
GETPAGE=$(shell echo $(1) | sed -e "s/^.*_\([0-9]*\).epi/\\1/g")
define get-epi
@echo " *** Generating EPI image for $<"
psselect -q $(call GETPAGE,$@) $< temp.ps
psresize -w 6.375in -h 8.25in temp.ps temp2.ps
../sgml-common/ps2epsi temp2.ps $@
rm temp.ps temp2.ps
endef
pdf: $(MASTERBASE).pdf
$(MASTERBASE).pdf: $(MASTERBASE).ps
ps2pdf14 $(MASTERBASE).ps
plucker: $(MASTERBASE).pdb
$(MASTERBASE).pdb: html
plucker-build --bpp=4 --compression=zlib --doc-name="$(MASTERBASE)" \
-H file:`pwd`/html/index.html -M 5 \
--maxheight=320 --maxwidth=310 \
--staybelow=file:`pwd`/html --title="$(MASTERBASE)" -p . \
-f $(MASTERBASE)
###########################################################################
# These are obsolete but should still work.
###########################################################################
$(MASTERBASE).dvi: $(MASTERBASE).tex
@echo " *** Generating DVI file."
jadetex unix-guide.tex
jadetex unix-guide.tex
jadetex unix-guide.tex
$(MASTERBASE).tex: $(MASTER) $(BASICDEPS) $(INDEXSGMLFILE)
@echo " *** Generating TeX files."
docbook2tex -V paper-size=Letter $(MASTER)
# jade -t tex -V tex-backend -d \
# /usr/share/sgml/docbook/stylesheet/dsssl/modular/print/docbook.dsl \
# $(MASTER)

View File

@ -1,76 +0,0 @@
#!/bin/sh
# $RCSfile: ps2epsi,v $ $Revision: 1.4.2.2 $
tmpfile=/tmp/ps2epsi$$
export outfile
if [ $# -lt 1 -o $# -gt 2 ]; then
echo "Usage: `basename $0` file.ps [file.epsi]" 1>&2
exit 1
fi
infile=$1;
if [ $# -eq 1 ]
then
case "${infile}" in
*.ps) base=`basename ${infile} .ps` ;;
*.cps) base=`basename ${infile} .cps` ;;
*.eps) base=`basename ${infile} .eps` ;;
*.epsf) base=`basename ${infile} .epsf` ;;
*) base=`basename ${infile}` ;;
esac
outfile=${base}.epsi
else
outfile=$2
fi
ls -l ${infile} |
awk 'F==1 {
cd="%%CreationDate: " $6 " " $7 " " $8;
t="%%Title: " $9;
f="%%For:" U " " $3;
c="%%Creator: Ghostscript ps2epsi from " $9;
next;
}
/^%!/ {next;}
/^%%Title:/ {t=$0; next;}
/^%%Creator:/ {c=$0; next;}
/^%%CreationDate:/ {cd=$0; next;}
/^%%For:/ {f=$0; next;}
!/^%/ {
print "/ps2edict 30 dict def";
print "ps2edict begin";
print "/epsititle (" t "\\n) def";
print "/epsicreator (" c "\\n) def";
print "/epsicrdt (" cd "\\n) def";
print "/epsifor (" f "\\n) def";
print "end";
exit(0);
}
' U="$USERNAME$LOGNAME" F=1 - F=2 ${infile} >$tmpfile
gs -q -dNOPAUSE -dSAFER -dDELAYSAFER -r72 -sDEVICE=bit -sOutputFile=/dev/null $tmpfile ps2epsi.ps $tmpfile <${infile} 1>&2
rm -f $tmpfile
(
cat << BEGINEPS
save countdictstack mark newpath /showpage {} def /setpagedevice {pop} def
%%EndProlog
%%Page 1 1
BEGINEPS
cat ${infile} |
sed -e '/^%%BeginPreview:/,/^%%EndPreview[^!-~]*$/d' -e '/^%!PS-Adobe/d'\
-e '/^%%[A-Za-z][A-Za-z]*[^!-~]*$/d' -e '/^%%[A-Za-z][A-Za-z]*: /d'
cat << ENDEPS
%%Trailer
cleartomark countdictstack exch sub { end } repeat restore
%%EOF
ENDEPS
) >> ${outfile}
exit 0

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -1,850 +0,0 @@
OFFLINEIMAP(1) OfflineIMAP Manual OFFLINEIMAP(1)
NAME
OfflineIMAP - Powerful IMAP/Maildir synchronization and reader support
SYNOPSIS
offlineimap [ -1 ] [ -P profiledir ] [ -a accountlist ] [ -c configfile
] [ -d debugtype[,...] ] [ -l filename ] [ -o ] [ -u interface ]
offlineimap -h | --help
DESCRIPTION
OfflineIMAP is a tool to simplify your e-mail reading. With
OfflineIMAP, you can read the same mailbox from multiple computers.
You get a current copy of your messages on each computer, and changes
you make one place will be visible on all other systems. For instance,
you can delete a message on your home computer, and it will appear
deleted on your work computer as well. OfflineIMAP is also useful if
you want to use a mail reader that does not have IMAP support, has poor
IMAP support, or does not provide disconnected operation.
OfflineIMAP is FAST; it synchronizes my two accounts with over 50 fold-
ers in 3 seconds. Other similar tools might take over a minute, and
achieve a less-reliable result. Some mail readers can take over 10
minutes to do the same thing, and some don't even support it at all.
Unlike other mail tools, OfflineIMAP features a multi-threaded synchro-
nization algorithm that can dramatically speed up performance in many
situations by synchronizing several different things simultaneously.
OfflineIMAP is FLEXIBLE; you can customize which folders are synced via
regular expressions, lists, or Python expressions; a versatile and com-
prehensive configuration file is used to control behavior; two user
interfaces are built-in; fine-tuning of synchronization performance is
possible; internal or external automation is supported; SSL and PREAUTH
tunnels are both supported; offline (or "unplugged") reading is sup-
ported; and esoteric IMAP features are supported to ensure compatibil-
ity with the widest variety of IMAP servers.
OfflineIMAP is SAFE; it uses an algorithm designed to prevent mail loss
at all costs. Because of the design of this algorithm, even program-
ming errors should not result in loss of mail. I am so confident in
the algorithm that I use my own personal and work accounts for testing
of OfflineIMAP pre-release, development, and beta releases. Of course,
legally speaking, OfflineIMAP comes with no warranty, so I am not
responsible if this turns out to be wrong.
METHOD OF OPERATION
OfflineIMAP traditionally operates by maintaining a hierarchy of mail
folders in Maildir format locally. Your own mail reader will read mail
from this tree, and need never know that the mail comes from IMAP.
OfflineIMAP will detect changes to the mail folders on your IMAP server
and your own computer and bi-directionally synchronize them, copying,
marking, and deleting messages as necessary.
With OfflineIMAP 4.0, a powerful new ability has been introduced -- the
program can now synchronize two IMAP servers with each other, with no
need to have a Maildir layer in-between. Many people use this if they
use a mail reader on their local machine that does not support
Maildirs. People may install an IMAP server on their local machine,
and point both OfflineIMAP and their mail reader of choice at it. This
is often preferable to the mail reader's own IMAP support since
OfflineIMAP supports many features (offline reading, for one) that most
IMAP-aware readers don't. However, this feature is not as time-tested
as traditional syncing, so my advice is to stick with normal methods of
operation for the time being.
QUICK START
If you have already installed OfflineIMAP system-wide, or your system
administrator has done that for you, your task for setting up
OfflineIMAP for the first time is quite simple. You just need to set
up your configuration file, make your folder directory, and run it!
You can quickly set up your configuration file. The distribution
includes a file offlineimap.conf.minimal (Debian users may find this at
/usr/share/doc/offlineimap/examples/offlineimap.conf.minimal) that is a
basic example of setting of OfflineIMAP. You can simply copy this file
into your home directory and name it .offlineimaprc (note the leading
period). A command such as cp offlineimap.conf.minimal
~/.offlineimaprc will do it. Or, if you prefer, you can just copy this
text to ~/.offlineimaprc:
[general]
accounts = Test
[Account Test]
localrepository = Local
remoterepository = Remote
[Repository Local]
type = Maildir
localfolders = ~/Test
[Repository Remote]
type = IMAP
remotehost = examplehost
remoteuser = jgoerzen
Now, edit the ~/.offlineimaprc file with your favorite editor. All you
have to do is specify a directory for your folders to be in (on the
localfolders line), the host name of your IMAP server (on the remote-
host line), and your login name on the remote (on the remoteuser line).
That's it!
To run OfflineIMAP, you just have to say offlineimap -- it will fire
up, ask you for a login password if necessary, synchronize your fold-
ers, and exit. See? You can just throw away the rest of this finely-
crafted, perfectly-honed manual! Of course, if you want to see how you
can make OfflineIMAP FIVE TIMES FASTER FOR JUST $19.95 (err, well, $0),
you have to read on!
INSTALLATION
If you are reading this document via the "man" command, it is likely
that you have no installation tasks to perform; your system administra-
tor has already installed it. If you need to install it yourself, you
have three options: a system-wide installation with Debian, system-wide
installation with other systems, and a single-user installation. You
can download the latest version of OfflineIMAP from the OfflineIMAP
website <URL:http://quux.org/devel/offlineimap/>.
PREREQUISITES
In order to use OfflineIMAP, you need to have these conditions satis-
fied:
o Your mail server must support IMAP. Most Internet Service Providers
and corporate networks do, and most operating systems have an IMAP
implementation readily available.
o You must have Python version 2.2.1 or above installed. If you are
running on Debian GNU/Linux, this requirement will automatically be
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 the Python website <URL:http://www.python.org/>. 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.
o Have a mail reader that supports the Maildir mailbox format. Most
modern mail readers have this support built-in, so you can choose
from a wide variety of mail servers. This format is also known as
the "qmail" format, so any mail reader compatible with it will work
with OfflineIMAP. If you do not have a mail reader that supports
Maildir, you can often install a local IMAP server and point both
OfflineIMAP and your mail reader at it.
SYSTEM-WIDE INSTALLATION, DEBIAN
If you are tracking Debian unstable, you may install OfflineIMAP by
simply running the following command as root:
apt-get install offlineimap
If you are not tracking Debian unstable, download the Debian .deb pack-
age from the OfflineIMAP website
<URL:http://quux.org/devel/offlineimap/> and then run dpkg -i to
install the downloaded package. Then, skip to [XRef to CONFIGURATION]
below. You will type offlineimap to invoke the program.
SYSTEM-WIDE INSTALLATION, OTHER
Download the tar.gz version of the package from the website
<URL:http://quux.org/devel/offlineimap/>. Then run these commands,
making sure that you are the "root" user first:
tar -zxvf offlineimap_x.y.z.tar.gz
cd offlineimap-x.y.z
python2.2 setup.py install
On some systems, you will need to use python instead of python2.2.
Next, proceed to [XRef to CONFIGURATION] below. You will type
offlineimap to invoke the program.
SINGLE-ACCOUNT INSTALLATION
Download the tar.gz version of the package from the website
<URL:http://quux.org/devel/offlineimap/>. Then run these commands:
tar -zxvf offlineimap_x.y.z.tar.gz
cd offlineimap-x.y.z
When you want to run OfflineIMAP, you will issue the cd command as
above and then type ./offlineimap.py; there is no installation step
necessary.
CONFIGURATION
OfflineIMAP is regulated by a configuration file that is normally
stored in ~/.offlineimaprc. OfflineIMAP ships with a file named
offlineimap.conf that you should copy to that location and then edit.
This file is vital to proper operation of the system; it sets every-
thing you need to run OfflineIMAP. Full documentation for the configu-
ration file is included within the sample file.
OfflineIMAP also ships a file named offlineimap.conf.minimal that you
can also try. It's useful if you want to get started with the most
basic feature set, and you can read about other features later with
offlineimap.conf.
OPTIONS
Most configuration is done via the configuration file. Nevertheless,
there are a few command-line options that you may set for OfflineIMAP.
-1 Disable most multithreading operations and use solely a single-
connection sync. This effectively sets the maxsyncaccounts and
all maxconnections configuration file variables to 1.
-P profiledir
Sets OfflineIMAP into profile mode. The program will create
profiledir (it must not already exist). As it runs, Python pro-
filing information about each thread is logged into profiledir.
Please note: This option is present for debugging and optimiza-
tion only, and should NOT be used unless you have a specific
reason to do so. It will significantly slow program perfor-
mance, may reduce reliability, and can generate huge amounts of
data. You must use the -1 option when you use -P.
-a accountlist
Overrides the accounts option in the general section of the con-
figuration file. You might use this to exclude certain
accounts, or to sync some accounts that you normally prefer not
to. Separate the accounts by commas, and use no embedded
spaces.
-c configfile
Specifies a configuration file to use in lieu of the default,
~/.offlineimaprc.
-d debugtype[,...]
Enables debugging for OfflineIMAP. This is useful if you are
trying to track down a malfunction or figure out what is going
on under the hood. I suggest that you use this with -1 to make
the results more sensible.
-d requires one or more debugtypes, separated by commas. These
define what exactly will be debugged, and include three options:
imap, maildir, and thread. The imap option will enable IMAP
protocol stream and parsing debugging. Note that the output may
contain passwords, so take care to remove that from the debug-
ging output before sending it to anyone else. The maildir
option will enable debugging for certain Maildir operations.
And thread will debug the threading model.
-l filename
Enables logging to filename. This will log everything that goes
to the screen to the specified file. Additionally, if any
debugging is specified with -d, then debug messages will not go
to the screen, but instead to the logfile only.
-o Run only once, ignoring all autorefresh settings in the configu-
ration file.
-h
--help Show summary of options.
-u interface
Specifies an alternative user interface module to use. This
overrides the default specified in the configuration file. The
pre-defined options are listed in the User Interfaces section.
USER INTERFACES
OfflineIMAP has a pluggable user interface system that lets you choose
how the program communicates information to you. There are two graphi-
cal interfaces, two terminal interfaces, and two noninteractive inter-
faces suitable for scripting or logging purposes. The ui option in the
configuration file specifies user interface preferences. The -u com-
mand-line option can override the configuration file setting. The
available values for the configuration file or command-line are
described in this section.
TK.BLINKENLIGHTS
Tk.Blinkenlights is an interface designed to be sleek, fun to watch,
and informative of the overall picture of what OfflineIMAP is doing. I
consider it to be the best general-purpose interface in OfflineIMAP.
Tk.Blinkenlights contains, by default, a small window with a row of
LEDs, a small log, and a row of command buttons. 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 of the lights.
Tk.Blinkenlights is the only user interface that has configurable
parameters; see the example offlineimap.conf for more details.
Each light in the Blinkenlights interface represents a thread of execu-
tion -- that is, a particular task that OfflineIMAP is performing right
now. The colors indicate what task the particular thread is perform-
ing, and are as follows:
Black indicates that this light's thread has terminated; it will light
up again later when new threads start up. So, black indicates
no activity.
Red (Meaning 1)
is the color of the main program's thread, which basically does
nothing but monitor the others. It might remind you of HAL 9000
in 2001.
Gray indicates that the thread is establishing a new connection to
the IMAP server.
Purple is the color of an account synchronization thread that is moni-
toring the progress of the folders in that account (not generat-
ing any I/O).
Cyan indicates that the thread is syncing a folder.
Green means that a folder's message list is being loaded.
Blue is the color of a message synchronization controller thread.
Orange indicates that an actual message is being copied. (We use fuch-
sia for fake messages.)
Red (meaning 2)
indicates that a message is being deleted.
Yellow / bright orange
indicates that message flags are being added.
Pink / bright red
indicates that message flags are being removed.
Red / Black Flashing
corresponds to the countdown timer that runs between synchro-
nizations.
The name of this interfaces derives from a bit of computer history.
Eric Raymond's Jargon File defines blinkenlights, in part, as:
Front-panel diagnostic lights on a computer, esp. a dinosaur.
Now that dinosaurs are rare, this term usually refers to status
lights on a modem, network hub, or the like.
This term derives from the last word of the famous blackletter-
Gothic sign in mangled pseudo-German that once graced about half
the computer rooms in the English-speaking world. One version
ran in its entirety as follows:
ACHTUNG! ALLES LOOKENSPEEPERS!
Das computermachine ist nicht fuer gefingerpoken und mitten-
grabben. Ist easy schnappen der springenwerk, blowenfusen und
poppencorken mit spitzensparken. Ist nicht fuer gewerken bei
das dumpkopfen. Das rubbernecken sichtseeren keepen das cotten-
pickenen hans in das pockets muss; relaxen und watchen das
blinkenlichten.
CURSES.BLINKENLIGHTS
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.
Please see the Tk.Blinkenlights section above for more information
about the colors used in this interface.
TK.VERBOSEUI
Tk.VerboseUI (formerly known as Tk.TkUI) is a graphical interface that
presents a variable-sized window. In the window, each currently-exe-
cuting thread has a section where its name and current status are dis-
played. This interface is best suited to people running on slower con-
nections, as you get a lot of detail, but for fast connections, the
detail may go by too quickly to be useful. People with fast connec-
tions may wish to use Tk.Blinkenlights instead.
TTY.TTYUI
TTY.TTYUI interface is for people running in basic, non-color termi-
nals. It prints out basic status messages and is generally friendly to
use on a console or xterm.
NONINTERACTIVE.BASIC
Noninteractive.Basic is designed for situations in which OfflineIMAP
will be run non-attended and the status of its execution will be
logged. You might use it, for instance, to have the system run auto-
matically and e-mail you the results of the synchronization. This user
interface is not capable of reading a password from the keyboard;
account passwords must be specified using one of the configuration file
options.
NONINTERACTIVE.QUIET
Noninteractive.Quiet is designed for non-attended running in situations
where normal status messages are not desired. It will output nothing
except errors and serious warnings. Like Noninteractive.Basic, this
user interface is not capable of reading a password from the keyboard;
account passwords must be specified using one of the configuration file
options.
EXAMPLES
Here are some example configurations for various situations. Please e-
mail any other examples you have that may be useful to me.
MULTIPLE ACCOUNTS WITH MUTT
This example shows you how to set up OfflineIMAP to synchronize multi-
ple accounts with the mutt mail reader.
Start by creating a directory to hold your folders by running mkdir
~/Mail. Then, in your ~/.offlineimaprc, specify:
accounts = Personal, Work
Make sure that you have both an [Account Personal] and an [Account
Work] section. The local repository for each account must have differ-
ent localfolder path names. Also, make sure to enable [mbnames].
In each local repository section, write something like this:
localfolders = ~/Mail/Personal
Finally, add these lines to your ~/.muttrc:
source ~/path-to-mbnames-muttrc-mailboxes
folder-hook Personal set from="youremail@personal.com"
folder-hook Work set from="youremail@work.com"
set mbox_type=Maildir
set folder=$HOME/Mail
spoolfile=+Personal/INBOX
That's it!
UW-IMAPD AND REFERENCES
Some users with a UW-IMAPD server need to use OfflineIMAP's "reference"
feature to get at their mailboxes, specifying a reference of "~/Mail"
or "#mh/" depending on the configuration. The below configuration from
(originally from docwhat@gerf.org) shows using a reference of Mail, a
nametrans that strips the leading Mail/ off incoming folder names, and
a folderfilter that limits the folders synced to just three.
[Account Gerf]
localrepository = GerfLocal
remoterepository = GerfRemote
[Repository GerfLocal]
type = Maildir
localfolders = ~/Mail
[Repository GerfRemote]
type = IMAP
remotehost = gerf.org
ssl = yes
remoteuser = docwhat
reference = Mail
# Trims off the preceeding Mail on all the folder names.
nametrans = lambda foldername: \
re.sub('^Mail/', '', foldername)
# Yeah, you have to mention the Mail dir, even though it
# would seem intuitive that reference would trim it.
folderfilter = lambda foldername: foldername in [
'Mail/INBOX',
'Mail/list/zaurus-general',
'Mail/list/zaurus-dev',
]
maxconnections = 1
holdconnectionopen = no
PYTHONFILE CONFIGURATION FILE OPTION
You can have OfflineIMAP load up a Python file before evaluating the
configuration file options that are Python expressions. This example
is based on one supplied by Tommi Virtanen for this feature.
In ~/.offlineimap.rc, he adds these options:
[general]
pythonfile=~/.offlineimap.py
[Repository foo]
foldersort=mycmp
Then, the ~/.offlineimap.py file will contain:
prioritized = ['INBOX', 'personal', 'announce', 'list']
def mycmp(x, y):
for prefix in prioritized:
xsw = x.startswith(prefix)
ysw = y.startswith(prefix)
if xsw and ysw:
return cmp(x, y)
elif xsw:
return -1
elif ysw:
return +1
return cmp(x, y)
def test_mycmp():
import os, os.path
folders=os.listdir(os.path.expanduser('~/data/mail/tv@hq.yok.utu.fi'))
folders.sort(mycmp)
print folders
This code snippet illustrates how the foldersort option can be cus-
tomized with a Python function from the pythonfile to always synchro-
nize certain folders first.
ERRORS
If you get one of some frequently-encountered or confusing errors,
please check this section.
UID VALIDITY PROBLEM FOR FOLDER
IMAP servers use a unique ID (UID) to refer to a specific message.
This number is guaranteed to be unique to a particular message forever.
No other message in the same folder will ever get the same UID. UIDs
are an integral part of OfflineIMAP's synchronization scheme; they are
used to match up messages on your computer to messages on the server.
Sometimes, the UIDs on the server might get reset. Usually this will
happen if you delete and then recreate a folder. When you create a
folder, the server will often start the UID back from 1. But
OfflineIMAP might still have the UIDs from the previous folder by the
same name stored. OfflineIMAP will detect this condition and skip the
folder. This is GOOD, because it prevents data loss.
You can fix it by removing your local folder and cache data. For
instance, if your folders are under ~/Folders and the folder with the
problem is INBOX, you'd type this:
rm -r ~/Folders/INBOX
rm -r ~/.offlineimap/Account-AccountName
rm -r ~/.offlineimap/Repository-RepositoryName
(Of course, replace AccountName and RepositoryName with the names as
specified in ~/.offlineimaprc).
Next time you run OfflineIMAP, it will re-download the folder with the
new UIDs. Note that the procedure specified above will lose any local
changes made to the folder.
Some IMAP servers are broken and do not support UIDs properly. If you
continue to get this error for all your folders even after performing
the above procedure, it is likely that your IMAP server falls into this
category. OfflineIMAP is incompatible with such servers. Using
OfflineIMAP with them will not destroy any mail, but at the same time,
it will not actually synchronize it either. (OfflineIMAP will detect
this condition and abort prior to synchronization.)
This question comes up frequently on the OfflineIMAP mailing list
<URL:http://lists.complete.org/offlineimap@complete.org/>. You can
find a detailed discussion <URL:http://lists.com-
plete.org/offlineimap@complete.org/2003/04/msg00012.html.gz> of the
problem there.
OTHER FREQUENTLY ASKED QUESTIONS
There are some other FAQs that might not fit into another section of
the document, so they are discussed here.
What platforms does OfflineIMAP run on?
It should run on most platforms supported by Python, which are
quite a few. I do not support Windows myself, but some have
made it work there; see the FAQ entry for that platform.
I'm using Mutt. Other IMAP sync programs require me to use "set
maildir_trash=yes". Do I need to do that with OfflineIMAP?
No. OfflineIMAP is smart enough to figure out message deletion
without this extra crutch. You'll get the best results if you
don't use this setting, in fact.
I've upgraded and now OfflineIMAP crashes when I start it up! Why?
You need to upgrade your configuration file. See [XRef to
UPGRADING.4.0] at the end of this manual.
How do I specify the names of my folders?
You do not need to. OfflineIMAP is smart enough to automati-
cally figure out what folders are present on the IMAP server and
synchronize them. You can use the folderfilter and foldertrans
configuration file options to request certain folders and rename
them as they come in if you like.
How can I prevent certain folders from being synced?
Use the folderfilter option in the configuration file.
How can I add or delete a folder?
OfflineIMAP does not currently provide this feature, but if you
create a new folder on the IMAP server, it will be created
locally automatically.
Are there any other warnings that I should be aware of?
Yes; see the Notes section below.
What is the mailbox name recorder (mbnames) for?
Some mail readers, such as Mutt, are not capable of automati-
cally determining the names of your mailboxes. OfflineIMAP can
help these programs by writing the names of the folders in a
format you specify. See the example offlineimap.conf for
details.
Can I synchronize multiple accounts with OfflineIMAP?
Sure. Just name them all in the accounts line in the general
section of the configuration file, and add a per-account section
for each one.
Does OfflineIMAP support POP?
No. POP is not robust enough to do a completely reliable multi-
machine synchronization like OfflineIMAP can do. OfflineIMAP
will not support it.
Does OfflineIMAP support mailbox formats other than Maildir?
Not at present. There is no technical reason not to; just no
demand yet. Maildir is a superior format anyway. However,
OfflineIMAP can sync between two IMAP servers, and some IMAP
servers support other formats. You could install an IMAP server
on your local machine and have OfflineIMAP sync to that.
[technical] Why are your Maildir message filenames so huge?
OfflineIMAP has two relevant principles: 1) never modifying your
messages in any way and 2) ensuring 100% reliable
synchronizations. In order to do a reliable sync, OfflineIMAP
must have a way to uniquely identify each e-mail. Three pieces
of information are required to do this: your account name, the
folder name, and the message UID. The account name can be cal-
culated from the path in which your messages are. The folder
name can usually be as well, BUT some mail clients move messages
between folders by simply moving the file, leaving the name
intact.
So, OfflineIMAP must store both a UID folder ID. The folder ID
is necessary so OfflineIMAP can detect a message moved to a dif-
ferent folder. OfflineIMAP stores the UID (U= number) and an
md5sum of the foldername (FMD5= number) to facilitate this.
What is the speed of OfflineIMAP's sync?
OfflineIMAP versions 2.0 and above contain a multithreaded sys-
tem. A good way to experiment is by setting maxsyncaccounts to
3 and maxconnections to 3 in each account clause.
This lets OfflineIMAP open up multiple connections simultane-
ously. That will let it process multiple folders and messages
at once. In most cases, this will increase performance of the
sync.
Don't set the number too high. If you do that, things might
actually slow down as your link gets saturated. Also, too many
connections can cause mail servers to have excessive load.
Administrators might take unkindly to this, and the server might
bog down. There are many variables in the optimal setting;
experimentation may help.
An informal benchmark yields these results for my setup:
o 10 minutes with MacOS X Mail.app "manual cache"
o 5 minutes with GNUS agent sync
o 20 seconds with OfflineIMAP 1.x
o 9 seconds with OfflineIMAP 2.x
o 3 seconds with OfflineIMAP 3.x "cold start"
o 2 seconds with OfflineIMAP 3.x "held connection"
Can I use OfflineIMAP on Windows?
These answers have been reported by OfflineIMAP users. I do not
run OfflineIMAP on Windows myself, so I can't directly address
their accuracy.
The basic answer is that it's possible and doesn't require hack-
ing OfflineIMAP source code. However, it's not necessarily
trivial. The information below is based in instructions submit-
ted by Chris Walker.
First, you must run OfflineIMAP in the Cygwin
<URL:http://www.cygwin.com/> environment.
Next, you'll need to mount your Maildir directory in a special
way. There is information for doing that at
<URL:http://barnson.org/node/view/295>. That site gives this
example:
mount -f -s -b -o managed "d:/tmp/mail" "/home/of/mail"
That URL also has more details on making OfflineIMAP work with
Windows.
CONFORMING TO
o Internet Message Access Protocol version 4rev1 (IMAP 4rev1) as speci-
fied in RFC2060 and RFC3501
o CRAM-MD5 as specified in RFC2195
o Maildir as specified in the Maildir manpage
<URL:http://www.qmail.org/qmail-manual-html/man5/maildir.html> and
the qmail website <URL:http://cr.yp.to/proto/maildir.html>.
o Standard Python 2.2.1 as implemented on POSIX-compliant systems.
NOTES
DELETING LOCAL FOLDERS
OfflineIMAP does a two-way synchronization. That is, if you make a
change to the mail on the server, it will be propagated to your local
copy, and vise-versa. Some people might think that it would be wise to
just delete all their local mail folders periodically. If you do this
with OfflineIMAP, remember to also remove your local status cache
(~/.offlineimap by default). Otherwise, OfflineIMAP will take this as
an intentional deletion of many messages and will interpret your action
as requesting them to be deleted from the server as well. (If you
don't understand this, don't worry; you probably won't encounter this
situation)
MULTIPLE INSTANCES
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 abort if
another OfflineIMAP is already running. If you need to schedule syn-
chronizations, please use the autorefresh settings rather than cron.
Alternatively, you can set a separate metadata directory for each
instance.
COPYING MESSAGES BETWEEN FOLDERS
Normally, when you copy a message between folders or add a new message
to a folder locally, OfflineIMAP will just do the right thing. How-
ever, sometimes this can be tricky -- if your IMAP server does not pro-
vide the SEARCH command, or does not return something useful,
OfflineIMAP cannot determine the new UID of the message. So, in these
rare instances, OfflineIMAP will upload the message to the IMAP server
and delete it from your local folder. Then, on your next sync, the
message will be re-downloaded with the proper UID. OfflineIMAP makes
sure that the message was properly uploaded before deleting it, so
there should be no risk of data loss.
USE WITH EVOLUTION
OfflineIMAP can work with Evolution. To do so, first configure your
OfflineIMAP account to have sep = / in its configuration. Then, con-
figure Evolution with the "Maildir-format mail directories" server
type. For the path, you will need to specify the name of the top-level
folder inside your OfflineIMAP storage location. You're now set!
USE WITH KMAIL
At this time, I believe that OfflineIMAP with Maildirs is not compati-
ble with KMail. KMail cannot work in any mode other than to move all
messages out of all folders immediately, which (besides being annoying
and fundamentally broken) is incompatible with OfflineIMAP.
However, I have made KMail version 3 work well with OfflineIMAP by
installing an IMAP server on my local machine, having OfflineIMAP sync
to that, and pointing KMail at the same server.
MAILING LIST
There is an OfflineIMAP mailing list available. To subscribe, send the
text "Subscribe" in the subject of a mail to offlineimap-request@com-
plete.org. To post, send the message to offlineimap@complete.org.
Archives are available at
<URL:http://lists.complete.org/offlineimap@complete.org/>.
BUGS
Reports of bugs should be sent via e-mail to the OfflineIMAP bug-track-
ing system (BTS) at offlineimap@bugs.complete.org or submitted online
using the web interface <URL:http://bugs.complete.org/>.
The Web site also lists all current bugs, where you can check their
status or contribute to fixing them.
UPGRADING TO 4.0
If you are upgrading from a version of OfflineIMAP prior to 3.99.12,
you will find that you will get errors when OfflineIMAP starts up
(relating to ConfigParser or AccountHashGenerator) and the configura-
tion file. This is because the config file format had to change to
accommodate new features in 4.0. Fortunately, it's not difficult to
adjust it to suit.
First thing you need to do is stop any running OfflineIMAP instance,
making sure first that it's synced all your mail. Then, modify your
~/.offlineimaprc file. You'll need to split up each account section
(make sure that it now starts with "Account ") into two Repository sec-
tions (one for the local side and another for the remote side.) See
the files offlineimap.conf.minimal and offlineimap.conf in the distri-
bution if you need more assistance.
OfflineIMAP's status directory area has also changed. Therefore, you
should delete everything in ~/.offlineimap as well as your local mail
folders.
When you start up OfflineIMAP 4.0, it will re-download all your mail
from the server and then you can continue using it like normal.
COPYRIGHT
OfflineIMAP, and this manual, are Copyright (C) 2002, 2003 John
Goerzen.
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 MER-
CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
imaplib.py comes from the Python dev tree and is licensed under the
GPL-compatible PSF license as stated in the file COPYRIGHT in the
OfflineIMAP distribution.
AUTHOR
OfflineIMAP, its libraries, documentation, and all included files,
except where noted, was written by John Goerzen <jgoerzen@complete.org>
and copyright is held as stated in the COPYRIGHT section.
OfflineIMAP may be downloaded, and information found, from its homepage
via either Gopher <URL:gopher://quux.org/1/devel/offlineimap> or HTTP
<URL:http://quux.org/devel/offlineimap>.
OfflineIMAP may also be downloaded using Subversion. Additionally, the
distributed tar.gz may be updated with a simple "svn update" command;
it is ready to go. For information on getting OfflineIMAP with Subver-
sion, please visit the complete.org Subversion page
<URL:http://svn.complete.org/>.
SEE ALSO
mutt(1), python(1)
HISTORY
Detailed history may be found in the file ChangeLog in the OfflineIMAP
distribution. Feature and bug histories may be found in the file
debian/changelog which, despite its name, is not really Debian-spe-
cific. This section provides a large overview.
Development on OfflineIMAP began on June 18, 2002. Version 1.0.0 was
released three days later on June 21, 2002. Point releases followed,
including speed optimizations and some compatibility fixes.
Version 2.0.0 was released on July 3, 2002, and represented the first
time the synchronization became multithreaded and, to the best of my
knowledge, the first multithreaded IMAP syncrhonizing application in
existance. The last 2.0.x release, 2.0.8, was made on July 9.
Version 3.0.0 was released on July 11, 2002, and introduced modular
user interfaces and the first GUI interface for OfflineIMAP. This man-
ual also was introduced with 3.0.0, along with many command-line
options. Version 3.1.0 was released on July 21, adding the Noninterac-
tive user interfaces, profiling support, and several bugfixes. 3.2.0
was released on July 24, adding support for the Blinkenlights GUI
interface. OfflineIMAP entered maintenance mode for awhile, as it had
reached a feature-complete milestone in my mind.
The 3.99.x branch began in on October 7, 2002, to begin work for 4.0.
The Curses.Blinkenlights interface was added in 3.99.6, and many archi-
tectural changes were made.
4.0.0 was released on July 18, 2003, including the ability to synchro-
nize directly between two IMAP servers, the first re-architecting of
the configuration file to refine the notion of an account, and the new
Curses interface.
John Goerzen 26 July 2004 OFFLINEIMAP(1)

View File

@ -1,979 +0,0 @@
.\" This manpage has been automatically generated by docbook2man
.\" from a DocBook document. This tool can be found at:
.\" <http://shell.ipoline.com/~elmert/comp/docbook2X/>
.\" Please send any bug reports, improvements, comments, patches,
.\" etc. to Steve Cheng <steve@ggi-project.org>.
.TH "OFFLINEIMAP" "1" "26 July 2004" "John Goerzen" "OfflineIMAP Manual"
.SH NAME
OfflineIMAP \- Powerful IMAP/Maildir synchronization and reader support
.SH SYNOPSIS
\fBofflineimap\fR [ \fB-1\fR ] [ \fB-P \fIprofiledir\fB\fR ] [ \fB-a \fIaccountlist\fB\fR ] [ \fB-c \fIconfigfile\fB\fR ] [ \fB-d \fIdebugtype[,...]\fB\fR ] [ \fB-l \fIfilename\fB\fR ] [ \fB-o\fR ] [ \fB-u \fIinterface\fB\fR ]
\fBofflineimap\fR \fB-h\fR | \fB--help\fR
.SH "DESCRIPTION"
.PP
\fBOfflineIMAP\fR is a tool to simplify your e-mail
reading. With \fBOfflineIMAP\fR, you can read the same mailbox
from multiple computers. You get a current copy of your
messages on each computer, and changes you make one place will be
visible on all other systems. For instance, you can delete a message
on your home computer, and it will appear deleted on your work
computer as well. \fBOfflineIMAP\fR is also useful if you want to
use a mail reader that does not have IMAP support, has poor IMAP
support, or does not provide disconnected operation.
.PP
\fBOfflineIMAP\fR is \fBFAST\fR; it synchronizes
my two accounts with over 50 folders in 3 seconds. Other
similar tools might take over a minute, and achieve a
less-reliable result. Some mail readers can take over 10
minutes to do the same thing, and some don't even support it
at all. Unlike other mail tools, \fBOfflineIMAP\fR features a
multi-threaded synchronization algorithm that can dramatically
speed up performance in many situations by synchronizing
several different things simultaneously.
.PP
\fBOfflineIMAP\fR is \fBFLEXIBLE\fR; you can
customize which folders are synced via regular expressions,
lists, or Python expressions; a versatile and comprehensive
configuration file is used to control behavior; two user
interfaces are built-in; fine-tuning of synchronization
performance is possible; internal or external automation is
supported; SSL and PREAUTH tunnels are both supported; offline
(or "unplugged") reading is supported; and esoteric IMAP
features are supported to ensure compatibility with the widest
variety of IMAP servers.
.PP
\fBOfflineIMAP\fR is \fBSAFE\fR; it uses an
algorithm designed to prevent mail loss at all costs. Because
of the design of this algorithm, even programming errors
should not result in loss of mail. I am so confident in the
algorithm that I use my own personal and work accounts for
testing of \fBOfflineIMAP\fR pre-release, development, and beta
releases. Of course, legally speaking, \fBOfflineIMAP\fR comes
with no warranty, so I am not responsible if this turns out
to be wrong.
.SS "METHOD OF OPERATION"
.PP
\fBOfflineIMAP\fR traditionally
operates by maintaining a hierarchy of
mail folders in Maildir format locally. Your own mail
reader will read mail from this tree, and need never know
that the mail comes from IMAP. \fBOfflineIMAP\fR will detect
changes to the mail folders on your IMAP server and your own
computer and bi-directionally synchronize them, copying,
marking, and deleting messages as necessary.
.PP
With \fBOfflineIMAP\fR 4.0, a powerful new ability has been
introduced -- the program can now synchronize two IMAP
servers with each other, with no need to have a Maildir
layer in-between. Many people use this if they use a mail
reader on their local machine that does not support
Maildirs. People may install an IMAP server on their local
machine, and point both \fBOfflineIMAP\fR and their mail reader
of choice at it. This is often preferable to the mail
reader's own IMAP support since \fBOfflineIMAP\fR supports many
features (offline reading, for one) that most IMAP-aware
readers don't. However, this feature is not as time-tested
as traditional syncing, so my advice is to stick with normal
methods of operation for the time being.
.SH "QUICK START"
.PP
If you have already installed \fBOfflineIMAP\fR system-wide,
or your system administrator has done that for you, your task
for setting up \fBOfflineIMAP\fR for the first time is quite
simple. You just need to set up your configuration file, make
your folder directory, and run it!
.PP
You can quickly set up your configuration file. The distribution
includes a file \fIofflineimap.conf.minimal\fR
(Debian users
may find this at
\fI/usr/share/doc/offlineimap/examples/offlineimap.conf.minimal\fR) that is a basic example of setting of \fBOfflineIMAP\fR. You can
simply copy this file into your home directory and name it
\fI.offlineimaprc\fR (note the leading period). A
command such as \fBcp offlineimap.conf.minimal ~/.offlineimaprc\fR will do it. Or, if you prefer, you can just copy this text to
\fI~/.offlineimaprc\fR:
.nf
[general]
accounts = Test
[Account Test]
localrepository = Local
remoterepository = Remote
[Repository Local]
type = Maildir
localfolders = ~/Test
[Repository Remote]
type = IMAP
remotehost = examplehost
remoteuser = jgoerzen
.fi
.PP
Now, edit the \fI~/.offlineimaprc\fR file with
your favorite editor. All you have to do is specify a directory
for your folders to be in (on the \fIlocalfolders\fR
line), the host name of your IMAP server (on the
\fIremotehost\fR line), and your login name on
the remote (on the \fIremoteuser\fR line). That's
it!
.PP
To run \fBOfflineIMAP\fR, you just have to say
\fBofflineimap\fR -- it will fire up, ask you for
a login password if necessary, synchronize your folders, and exit.
See? You can just throw away the rest of this finely-crafted,
perfectly-honed manual! Of course, if you want to see how you can
make \fBOfflineIMAP\fR FIVE TIMES FASTER FOR JUST $19.95 (err, well,
$0), you have to read on!
.SH "INSTALLATION"
.PP
If you are reading this document via the "man" command, it is
likely
that you have no installation tasks to perform; your system
administrator has already installed it. If you need to install it
yourself, you have three options: a system-wide installation with
Debian, system-wide installation with other systems, and a single-user
installation. You can download the latest version of \fBOfflineIMAP\fR from
the \fBOfflineIMAP\fR
website <URL:http://quux.org/devel/offlineimap/>.
.SS "PREREQUISITES"
.PP
In order to use \fBOfflineIMAP\fR, you need to have these conditions
satisfied:
.TP 0.2i
\(bu
Your mail server must support IMAP. Most Internet Service
Providers
and corporate networks do, and most operating systems
have an IMAP
implementation readily available.
.TP 0.2i
\(bu
You must have Python version 2.2.1 or above installed.
If you are
running on Debian GNU/Linux, this requirement will automatically be
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
the Python website <URL:http://www.python.org/>.
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.
.TP 0.2i
\(bu
Have a mail reader that supports the Maildir mailbox
format. Most modern mail readers have this support
built-in, so you can choose from a wide variety of mail
servers. This format is also known as the "qmail"
format, so any mail reader compatible with it will work
with \fBOfflineIMAP\fR. If you do not have a mail reader
that supports Maildir, you can often install a local
IMAP server and point both \fBOfflineIMAP\fR and your mail
reader at it.
.SS "SYSTEM-WIDE INSTALLATION, DEBIAN"
.PP
If you are tracking Debian unstable, you may install
\fBOfflineIMAP\fR by simply running the following command as root:
.PP
\fBapt-get install offlineimap\fR
.PP
If you are not tracking Debian unstable, download the Debian .deb
package from the \fBOfflineIMAP\fR website <URL:http://quux.org/devel/offlineimap/>
and then run \fBdpkg -i\fR to install the downloaded
package. Then, skip to [XRef to CONFIGURATION] below. You will type \fBofflineimap\fR to
invoke the program.
.SS "SYSTEM-WIDE INSTALLATION, OTHER"
.PP
Download the tar.gz version of the package from the
website <URL:http://quux.org/devel/offlineimap/>.
Then run
these commands, making sure that you are the "root" user first:
.nf
tar -zxvf offlineimap_x.y.z.tar.gz
cd offlineimap-x.y.z
python2.2 setup.py install
.fi
.PP
On some systems, you will need to use
\fBpython\fR instead of \fBpython2.2\fR.
Next, proceed to [XRef to CONFIGURATION] below. You will type \fBofflineimap\fR to
invoke the program.
.SS "SINGLE-ACCOUNT INSTALLATION"
.PP
Download the tar.gz version of the package from the
website <URL:http://quux.org/devel/offlineimap/>.
Then run these commands:
.nf
tar -zxvf offlineimap_x.y.z.tar.gz
cd offlineimap-x.y.z
.fi
.PP
When you want to run \fBOfflineIMAP\fR, you will issue the
\fBcd\fR command as above and then type
\fB./offlineimap.py\fR; there is no installation
step necessary.
.SH "CONFIGURATION"
.PP
\fBOfflineIMAP\fR is regulated by a configuration file that is normally
stored in \fI~/.offlineimaprc\fR. \fBOfflineIMAP\fR
ships with a file named \fIofflineimap.conf\fR
that you should copy to that location and then edit. This file is
vital to proper operation of the system; it sets everything you need
to run \fBOfflineIMAP\fR. Full documentation for the configuration file
is included within the sample file.
.PP
\fBOfflineIMAP\fR also ships a file named
\fIofflineimap.conf.minimal\fR that you can also try.
It's useful if you want to get started with
the most basic feature set, and you can read about other features
later with \fIofflineimap.conf\fR.
.SH "OPTIONS"
.PP
Most configuration is done via the configuration file. Nevertheless,
there are a few command-line options that you may set for
\fBOfflineIMAP\fR.
.TP
\fB-1\fR
Disable most multithreading operations and use
solely a single-connection
sync. This effectively sets the
\fImaxsyncaccounts\fR
and all \fImaxconnections\fR configuration file
variables to 1.
.TP
\fB-P \fIprofiledir\fB\fR
Sets \fBOfflineIMAP\fR into profile mode. The program
will create \fIprofiledir\fR
(it must not already exist). As it runs, Python profiling
information
about each thread is logged into profiledir. Please note: This option
is present for debugging and optimization only, and should NOT be used
unless you have a specific reason to do so. It will significantly
slow program performance, may reduce reliability, and can generate
huge amounts of data. You must use the \fB-1\fR option when
you use \fB-P\fR.
.TP
\fB-a \fIaccountlist\fB\fR
Overrides the \fIaccounts\fR option
in the \fIgeneral\fR section of the configuration
file. You might use this to exclude certain accounts, or to sync
some accounts that you normally prefer not to. Separate the
accounts by commas, and use no embedded spaces.
.TP
\fB-c \fIconfigfile\fB\fR
Specifies a configuration file to use in lieu of
the default, \fI~/.offlineimaprc\fR.
.TP
\fB-d \fIdebugtype[,...]\fB\fR
Enables debugging for OfflineIMAP. This is useful if
you are trying to track down a malfunction or figure out what is going
on under the hood. I suggest that you use this with
\fB-1\fR to make the results more sensible.
\fB-d\fR requires one or more debugtypes,
separated by commas. These define what exactly will be
debugged, and include three options: \fIimap\fR,
\fImaildir\fR, and \fIthread\fR.
The \fIimap\fR
option will enable IMAP protocol stream and parsing debugging. Note
that the output may contain passwords, so take care to remove that
from the debugging output before sending it to anyone else. The
\fImaildir\fR option will enable debugging for
certain Maildir operations. And \fIthread\fR
will debug the threading model.
.TP
\fB-l \fIfilename\fB\fR
Enables logging to filename. This will log everything
that goes to the screen to the specified file.
Additionally, if any debugging is specified with -d,
then debug messages will not go to the screen, but
instead to the logfile only.
.TP
\fB-o\fR
Run only once, ignoring all
\fIautorefresh\fR settings in the configuration
file.
.TP
\fB-h\fR
.TP
\fB--help\fR
Show summary of options.
.TP
\fB-u \fIinterface\fB\fR
Specifies an alternative user interface module
to use. This overrides the default specified in the
configuration file. The pre-defined options are listed in
the User Interfaces section.
.SH "USER INTERFACES"
.PP
\fBOfflineIMAP\fR has a pluggable user interface system that lets you choose how the
program communicates information to you. There are two graphical
interfaces, two terminal interfaces, and two noninteractive interfaces
suitable for scripting or logging purposes. The
\fIui\fR option in the configuration file specifies
user interface preferences. The \fB-u\fR command-line
option can override the configuration file setting. The available
values for the configuration file or command-line are described
in this section.
.SS "TK.BLINKENLIGHTS"
.PP
Tk.Blinkenlights is an interface designed to be sleek, fun to watch, and
informative of the overall picture of what \fBOfflineIMAP\fR
is doing. I consider it to be the best general-purpose interface in
\fBOfflineIMAP\fR.
.PP
Tk.Blinkenlights contains, by default, a small window with a row of
LEDs, a small log, and a row of command buttons.
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
of the lights.
.PP
Tk.Blinkenlights is the only user interface that has configurable
parameters; see the example \fIofflineimap.conf\fR
for more details.
.PP
Each light in the Blinkenlights interface represents a thread
of execution -- that is, a particular task that \fBOfflineIMAP\fR
is performing right now. The colors indicate what task
the particular thread is performing, and are as follows:
.TP
\fBBlack\fR
indicates that this light's thread has terminated; it will light up
again later when new threads start up. So, black indicates no
activity.
.TP
\fBRed (Meaning 1)\fR
is the color of the main program's thread, which basically does
nothing but monitor the others. It might remind you of HAL 9000 in
2001.
.TP
\fBGray\fR
indicates that the thread is establishing a new connection to the IMAP
server.
.TP
\fBPurple\fR
is the color of an account synchronization thread that is monitoring
the progress of the folders in that account (not generating any I/O).
.TP
\fBCyan\fR
indicates that the thread is syncing a folder.
.TP
\fBGreen\fR
means that a folder's message list is being loaded.
.TP
\fBBlue\fR
is the color of a message synchronization controller thread.
.TP
\fBOrange\fR
indicates that an actual message is being copied.
(We use fuchsia for fake messages.)
.TP
\fBRed (meaning 2)\fR
indicates that a message is being deleted.
.TP
\fBYellow / bright orange\fR
indicates that message flags are being added.
.TP
\fBPink / bright red\fR
indicates that message flags are being removed.
.TP
\fBRed / Black Flashing\fR
corresponds to the countdown timer that runs between
synchronizations.
.PP
The name of this interfaces derives from a bit of computer
history. Eric Raymond's Jargon File defines
\fIblinkenlights\fR, in part, as:
.sp
.RS
.PP
Front-panel diagnostic
lights on a computer, esp. a dinosaur. Now that dinosaurs are rare,
this term usually refers to status lights on a modem, network hub, or
the like.
.PP
This term derives from the last word of the famous blackletter-Gothic
sign in mangled pseudo-German that once graced about half the computer
rooms in the English-speaking world. One version ran in its entirety as
follows:
.PP
\fBACHTUNG! ALLES LOOKENSPEEPERS!\fR
.PP
Das computermachine ist nicht fuer gefingerpoken und mittengrabben.
Ist easy schnappen der springenwerk, blowenfusen und poppencorken
mit spitzensparken. Ist nicht fuer gewerken bei das dumpkopfen.
Das rubbernecken sichtseeren keepen das cotten-pickenen hans in das
pockets muss; relaxen und watchen das blinkenlichten.
.RE
.SS "CURSES.BLINKENLIGHTS"
.PP
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.
.PP
Please see the Tk.Blinkenlights section above for more
information about the colors used in this interface.
.SS "TK.VERBOSEUI"
.PP
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.
.SS "TTY.TTYUI"
.PP
TTY.TTYUI interface is for people running in basic, non-color terminals. It
prints out basic status messages and is generally friendly to use on a console
or xterm.
.SS "NONINTERACTIVE.BASIC"
.PP
Noninteractive.Basic is designed for situations in which \fBOfflineIMAP\fR
will be run non-attended and the status of its execution will be
logged. You might use it, for instance, to have the system run
automatically and
e-mail you the results of the synchronization. This user interface
is not capable of reading a password from the keyboard; account
passwords must be specified using one of the configuration file options.
.SS "NONINTERACTIVE.QUIET"
.PP
Noninteractive.Quiet is designed for non-attended running in situations
where normal status messages are not desired. It will output nothing
except errors and serious warnings. Like Noninteractive.Basic,
this user interface
is not capable of reading a password from the keyboard; account
passwords must be specified using one of the configuration file options.
.SH "EXAMPLES"
.PP
Here are some example configurations for various situations.
Please e-mail any other examples you have that may be useful to
me.
.SS "MULTIPLE ACCOUNTS WITH MUTT"
.PP
This example shows you how to set up \fBOfflineIMAP\fR to
synchronize multiple accounts with the mutt mail reader.
.PP
Start by creating a directory to hold your folders by running
\fBmkdir ~/Mail\fR. Then, in your
\fI~/.offlineimaprc\fR, specify:
.nf
accounts = Personal, Work
.fi
.PP
Make sure that you have both an
\fI[Account Personal]\fR
and an \fI[Account Work]\fR section. The
local repository for each account must have different
\fIlocalfolder\fR path names.
Also, make sure
to enable \fI[mbnames]\fR.
.PP
In each local repository section, write something like this:
.nf
localfolders = ~/Mail/Personal
.fi
.PP
Finally, add these lines to your \fI~/.muttrc\fR:
.nf
source ~/path-to-mbnames-muttrc-mailboxes
folder-hook Personal set from="youremail@personal.com"
folder-hook Work set from="youremail@work.com"
set mbox_type=Maildir
set folder=$HOME/Mail
spoolfile=+Personal/INBOX
.fi
.PP
That's it!
.SS "UW-IMAPD AND REFERENCES"
.PP
Some users with a UW-IMAPD server need to use \fBOfflineIMAP\fR's
"reference" feature to get at their mailboxes, specifying a reference
of "~/Mail" or "#mh/" depending on the configuration. The below
configuration from (originally from docwhat@gerf.org)
shows using a \fIreference\fR of Mail, a \fInametrans\fR
that strips
the leading Mail/ off incoming folder names, and a
\fIfolderfilter\fR that
limits the folders synced to just three.
.nf
[Account Gerf]
localrepository = GerfLocal
remoterepository = GerfRemote
[Repository GerfLocal]
type = Maildir
localfolders = ~/Mail
[Repository GerfRemote]
type = IMAP
remotehost = gerf.org
ssl = yes
remoteuser = docwhat
reference = Mail
# Trims off the preceeding Mail on all the folder names.
nametrans = lambda foldername: \\
re.sub('^Mail/', '', foldername)
# Yeah, you have to mention the Mail dir, even though it
# would seem intuitive that reference would trim it.
folderfilter = lambda foldername: foldername in [
'Mail/INBOX',
'Mail/list/zaurus-general',
'Mail/list/zaurus-dev',
]
maxconnections = 1
holdconnectionopen = no
.fi
.SS "PYTHONFILE CONFIGURATION FILE OPTION"
.PP
You can have \fBOfflineIMAP\fR
load up a Python file before evaluating the
configuration file options that are Python expressions. This example
is based on one supplied by Tommi Virtanen for this feature.
.PP
In \fI~/.offlineimap.rc\fR, he adds these options:
.nf
[general]
pythonfile=~/.offlineimap.py
[Repository foo]
foldersort=mycmp
.fi
.PP
Then, the \fI~/.offlineimap.py\fR file will
contain:
.nf
prioritized = ['INBOX', 'personal', 'announce', 'list']
def mycmp(x, y):
for prefix in prioritized:
xsw = x.startswith(prefix)
ysw = y.startswith(prefix)
if xsw and ysw:
return cmp(x, y)
elif xsw:
return -1
elif ysw:
return +1
return cmp(x, y)
def test_mycmp():
import os, os.path
folders=os.listdir(os.path.expanduser('~/data/mail/tv@hq.yok.utu.fi'))
folders.sort(mycmp)
print folders
.fi
.PP
This code snippet illustrates how the \fIfoldersort\fR
option can be customized with a Python function from the
\fIpythonfile\fR to always synchronize certain
folders first.
.SH "ERRORS"
.PP
If you get one of some frequently-encountered or confusing errors,
please check this section.
.SS "UID VALIDITY PROBLEM FOR FOLDER"
.PP
IMAP servers use a unique ID (UID) to refer to a specific message.
This number is guaranteed to be unique to a particular message
\fBforever\fR.
No other message in the same folder will ever get the same
UID. UIDs are an integral part of \fBOfflineIMAP\fR's synchronization
scheme; they are used to match up messages on your computer to
messages on the server.
.PP
Sometimes, the UIDs on the server might get reset. Usually this will
happen if you delete and then recreate a folder. When you create a
folder, the server will often start the UID back from 1. But
\fBOfflineIMAP\fR might still have the UIDs from the previous folder by the
same name stored. \fBOfflineIMAP\fR will detect this condition and skip the
folder. This is GOOD, because it prevents data loss.
.PP
You can fix it by removing your local folder and cache data. For
instance, if your folders are under \fI~/Folders\fR
and the folder with the problem is INBOX, you'd type this:
.nf
rm -r ~/Folders/INBOX
rm -r ~/.offlineimap/Account-\fIAccountName\fR
rm -r ~/.offlineimap/Repository-\fIRepositoryName\fR
.fi
.PP
(Of course, replace AccountName and RepositoryName
with the names as specified
in \fI~/.offlineimaprc\fR).
.PP
Next time you run \fBOfflineIMAP\fR, it will re-download
the folder with the
new UIDs. Note that the procedure specified above will lose any local
changes made to the folder.
.PP
Some IMAP servers are broken and do not support UIDs properly. If you
continue to get this error for all your folders even after performing
the above procedure, it is likely that your IMAP server falls into
this category. \fBOfflineIMAP\fR is incompatible with such servers.
Using \fBOfflineIMAP\fR with them will not destroy any mail, but at the same time,
it will not actually synchronize it either. (\fBOfflineIMAP\fR will detect
this condition and abort prior to synchronization.)
.PP
This question comes up frequently on the
\fBOfflineIMAP\fR
mailing list <URL:http://lists.complete.org/offlineimap@complete.org/>. You can find a
detailed
discussion <URL:http://lists.complete.org/offlineimap@complete.org/2003/04/msg00012.html.gz> of the problem there.
.SH "OTHER FREQUENTLY ASKED QUESTIONS"
.PP
There are some other FAQs that might not fit into another section
of the document, so they are discussed here.
.TP
\fBWhat platforms does OfflineIMAP run on?\fR
It should run on most platforms supported by Python, which are quite a
few. I do not support Windows myself, but some have made
it work there; see the FAQ entry for that platform.
.TP
\fBI'm using Mutt. Other IMAP sync programs require me to use "set maildir_trash=yes". Do I need to do that with OfflineIMAP?\fR
No. \fBOfflineIMAP\fR is smart enough to figure out message deletion without this extra
crutch. You'll get the best results if you don't use this setting, in
fact.
.TP
\fBI've upgraded and now OfflineIMAP crashes when I start it up! Why?\fR
You need to upgrade your configuration
file. See [XRef to UPGRADING.4.0] at the end of this
manual.
.TP
\fBHow do I specify the names of my folders?\fR
You do not need to. \fBOfflineIMAP\fR is smart
enough to automatically figure out what folders are present
on the IMAP server and synchronize them. You can use the
\fIfolderfilter\fR and \fIfoldertrans\fR
configuration file options to request certain folders and rename them
as they come in if you like.
.TP
\fBHow can I prevent certain folders from being synced?\fR
Use the \fIfolderfilter\fR option in the configuration file.
.TP
\fBHow can I add or delete a folder?\fR
\fBOfflineIMAP\fR does not currently provide this feature, but if you create a new
folder on the IMAP server, it will be created locally automatically.
.TP
\fBAre there any other warnings that I should be aware of?\fR
Yes; see the Notes section below.
.TP
\fBWhat is the mailbox name recorder (mbnames) for?\fR
Some mail readers, such as Mutt, are not capable
of automatically determining the names of your mailboxes.
\fBOfflineIMAP\fR can help these programs by writing the names
of the folders in a format you specify. See the example
\fIofflineimap.conf\fR for details.
.TP
\fBCan I synchronize multiple accounts with OfflineIMAP?\fR
Sure. Just name them all in the
\fIaccounts\fR line in the \fIgeneral\fR
section of the configuration file, and add a per-account section
for each one.
.TP
\fBDoes OfflineIMAP support POP?\fR
No. POP is not robust enough to do a completely reliable
multi-machine synchronization like \fBOfflineIMAP\fR can do. \fBOfflineIMAP\fR
will not support it.
.TP
\fBDoes OfflineIMAP support mailbox formats other than Maildir?\fR
Not at present. There is no technical reason not to; just no
demand yet. Maildir is a superior format anyway.
However, \fBOfflineIMAP\fR can sync between two IMAP
servers, and some IMAP servers support other formats. You
could install an IMAP server on your local machine and have
\fBOfflineIMAP\fR sync to that.
.TP
\fB[technical] Why are your Maildir message filenames so huge?\fR
\fBOfflineIMAP\fR has two relevant principles: 1) never modifying your
messages in any way and 2) ensuring 100% reliable synchronizations.
In order to do a reliable sync, \fBOfflineIMAP\fR
must have a way to
uniquely identify each e-mail. Three pieces of information are
required to do this: your account name, the folder name, and the
message UID. The account name can be calculated from the path in
which your messages are. The folder name can usually be as well, BUT
some mail clients move messages between folders by simply moving the
file, leaving the name intact.
So, \fBOfflineIMAP\fR must store both a UID folder ID. The folder ID is
necessary so \fBOfflineIMAP\fR can detect a message moved to a different
folder. \fBOfflineIMAP\fR stores the UID (U= number) and an md5sum of the
foldername (FMD5= number) to facilitate this.
.TP
\fBWhat is the speed of OfflineIMAP's sync?\fR
OfflineIMAP
versions 2.0 and above contain a multithreaded system. A good way to
experiment is by setting \fImaxsyncaccounts\fR to 3 and \fImaxconnections\fR to 3
in each account clause.
This lets OfflineIMAP open up multiple connections simultaneously.
That will let it process multiple folders and messages at once. In
most cases, this will increase performance of the sync.
Don't set the number too high. If you do that, things might actually
slow down as your link gets saturated. Also, too many connections can
cause mail servers to have excessive load. Administrators might take
unkindly to this, and the server might bog down. There are many
variables in the optimal setting; experimentation may help.
An informal benchmark yields these results for my setup:
.RS
.TP 0.2i
\(bu
10 minutes with MacOS X Mail.app "manual cache"
.TP 0.2i
\(bu
5 minutes with GNUS agent sync
.TP 0.2i
\(bu
20 seconds with OfflineIMAP 1.x
.TP 0.2i
\(bu
9 seconds with OfflineIMAP 2.x
.TP 0.2i
\(bu
3 seconds with OfflineIMAP 3.x "cold start"
.TP 0.2i
\(bu
2 seconds with OfflineIMAP 3.x "held connection"
.RE
.TP
\fBCan I use OfflineIMAP on Windows?\fR
These answers have been reported by \fBOfflineIMAP\fR
users. I do not run \fBOfflineIMAP\fR on Windows myself, so
I can't directly address their accuracy.
The basic answer is that it's possible and doesn't
require hacking \fBOfflineIMAP\fR source code. However,
it's not necessarily trivial. The information below is
based in instructions submitted by Chris Walker.
First, you must run \fBOfflineIMAP\fR in the Cygwin <URL:http://www.cygwin.com/>
environment.
Next, you'll need to mount your Maildir directory in a
special way. There is information for doing that at
<URL:http://barnson.org/node/view/295>.
That site gives this example:
.nf
mount -f -s -b -o managed "d:/tmp/mail" "/home/of/mail"
.fi
That URL also has more details on making OfflineIMAP
work with Windows.
.SH "CONFORMING TO"
.TP 0.2i
\(bu
Internet Message Access Protocol version 4rev1 (IMAP 4rev1) as
specified in RFC2060 and RFC3501
.TP 0.2i
\(bu
CRAM-MD5 as specified in RFC2195
.TP 0.2i
\(bu
Maildir as specified in
the Maildir manpage <URL:http://www.qmail.org/qmail-manual-html/man5/maildir.html> and
the qmail website <URL:http://cr.yp.to/proto/maildir.html>.
.TP 0.2i
\(bu
Standard Python 2.2.1 as implemented on POSIX-compliant systems.
.SH "NOTES"
.SS "DELETING LOCAL FOLDERS"
.PP
\fBOfflineIMAP\fR does a two-way synchronization. That is, if you
make a change to the mail on the server, it will be propagated to your
local copy, and vise-versa. Some people might think that it would be
wise to just delete all their local mail folders periodically. If you
do this with \fBOfflineIMAP\fR, remember to also remove your local status
cache (\fI~/.offlineimap\fR by default). Otherwise, \fBOfflineIMAP\fR will take
this as an intentional deletion of many messages and will interpret
your action as requesting them to be deleted from the server as well.
(If you don't understand this, don't worry; you probably won't
encounter this situation)
.SS "MULTIPLE INSTANCES"
.PP
\fBOfflineIMAP\fR 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
abort if another \fBOfflineIMAP\fR is already running. If you need
to schedule synchronizations, please use the
\fIautorefresh\fR settings rather than cron.
Alternatively, you can set a separate \fImetadata\fR
directory for each instance.
.SS "COPYING MESSAGES BETWEEN FOLDERS"
.PP
Normally, when you copy a message between folders or add a new message
to a folder locally, \fBOfflineIMAP\fR
will just do the right thing. However, sometimes this can be tricky
-- if your IMAP server does not provide the SEARCH command, or does
not return something useful, \fBOfflineIMAP\fR
cannot determine the new UID of the message. So, in these rare
instances, OfflineIMAP will upload the message to the IMAP server and
delete it from your local folder. Then, on your next sync, the
message will be re-downloaded with the proper UID.
\fBOfflineIMAP\fR makes sure that the message was properly uploaded before deleting it,
so there should be no risk of data loss.
.SS "USE WITH EVOLUTION"
.PP
\fBOfflineIMAP\fR can work with Evolution. To do so, first configure
your \fBOfflineIMAP\fR account to have
\fBsep = /\fR in its configuration. Then, configure
Evolution with the
"Maildir-format mail directories" server type. For the path, you will need to
specify the name of the top-level folder
\fBinside\fR your \fBOfflineIMAP\fR storage location.
You're now set!
.SS "USE WITH KMAIL"
.PP
At this time, I believe that \fBOfflineIMAP\fR with Maildirs
is not compatible
with KMail. KMail cannot work in any mode other than to move
all messages out of all folders immediately, which (besides being annoying
and fundamentally broken) is incompatible with
\fBOfflineIMAP\fR.
.PP
However, I have made KMail version 3 work well with
\fBOfflineIMAP\fR by installing an IMAP server on my local
machine, having \fBOfflineIMAP\fR sync to that, and pointing
KMail at the same server.
.SS "MAILING LIST"
.PP
There is an OfflineIMAP mailing list available.
To subscribe, send the text "Subscribe" in the subject of a mail to
offlineimap-request@complete.org. To post, send the message to
offlineimap@complete.org. Archives are available at
<URL:http://lists.complete.org/offlineimap@complete.org/>.
.SS "BUGS"
.PP
Reports of bugs should be sent via e-mail to the
\fBOfflineIMAP\fR bug-tracking system (BTS) at
offlineimap@bugs.complete.org or submitted online using
the web interface <URL:http://bugs.complete.org/>.
.PP
The Web site also lists all current bugs, where you can check their
status or contribute to fixing them.
.SH "UPGRADING TO 4.0"
.PP
If you are upgrading from a version of \fBOfflineIMAP\fR prior to
3.99.12, you will find that you will get errors when
\fBOfflineIMAP\fR starts up (relating to ConfigParser or
AccountHashGenerator) and the
configuration file. This is because the config file format
had to change to accommodate new features in 4.0. Fortunately,
it's not difficult to adjust it to suit.
.PP
First thing you need to do is stop any running \fBOfflineIMAP\fR
instance, making sure first that it's synced all your mail.
Then, modify your
\fI~/.offlineimaprc\fR file. You'll need to
split up each account section (make sure that it now starts
with "Account ") into two Repository sections (one for the
local side and another for the remote side.) See the files
\fIofflineimap.conf.minimal\fR and
\fIofflineimap.conf\fR in the distribution if
you need more assistance.
.PP
\fBOfflineIMAP\fR's status directory area has also changed.
Therefore, you should delete everything in ~/.offlineimap as
well as your local mail folders.
.PP
When you start up \fBOfflineIMAP\fR 4.0, it will re-download all
your mail from the server and then you can continue using it
like normal.
.SH "COPYRIGHT"
.PP
OfflineIMAP, and this manual, are Copyright (C) 2002, 2003 John Goerzen.
.PP
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.
.PP
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.
.PP
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
.PP
imaplib.py comes from the Python dev tree and is licensed under
the GPL-compatible PSF license as stated in the file
\fICOPYRIGHT\fR in the \fBOfflineIMAP\fR
distribution.
.SH "AUTHOR"
.PP
\fBOfflineIMAP\fR, its libraries, documentation, and all included files, except where
noted, was written by John Goerzen <jgoerzen@complete.org> and
copyright is held as stated in the COPYRIGHT section.
.PP
\fBOfflineIMAP\fR may be downloaded, and information found, from its
homepage via either Gopher <URL:gopher://quux.org/1/devel/offlineimap>
or HTTP <URL:http://quux.org/devel/offlineimap>.
.PP
\fBOfflineIMAP\fR may also be downloaded using Subversion. Additionally,
the distributed tar.gz may be updated with a simple "svn update"
command; it is ready to go. For information on getting OfflineIMAP
with Subversion, please visit the
complete.org Subversion page <URL:http://svn.complete.org/>.
.SH "SEE ALSO"
.PP
\fBmutt\fR(1),
\fBpython\fR(1)
.SH "HISTORY"
.PP
Detailed history may be found in the file ChangeLog in the
\fBOfflineIMAP\fR distribution. Feature and bug histories may be
found in the file debian/changelog which, despite its name, is
not really Debian-specific. This section provides a large
overview.
.PP
Development on \fBOfflineIMAP\fR began on June 18, 2002. Version
1.0.0 was released three days later on June 21, 2002. Point
releases followed, including speed optimizations and some
compatibility fixes.
.PP
Version 2.0.0 was released on July 3, 2002, and
represented the first time the synchronization became
multithreaded and, to the best of my knowledge, the first
multithreaded IMAP syncrhonizing application in existance.
The last 2.0.x release, 2.0.8, was made on July 9.
.PP
Version 3.0.0 was released on July 11, 2002, and introduced
modular user interfaces and the first GUI interface for
\fBOfflineIMAP\fR. This manual also was introduced with 3.0.0,
along with many command-line options. Version 3.1.0 was
released on July 21, adding the Noninteractive user
interfaces, profiling support, and several bugfixes. 3.2.0
was released on July 24, adding support for the Blinkenlights
GUI interface. \fBOfflineIMAP\fR entered maintenance mode for
awhile, as it had reached a feature-complete milestone in my
mind.
.PP
The 3.99.x branch began in on October 7, 2002, to begin work
for 4.0. The Curses.Blinkenlights interface was added in
3.99.6, and many architectural changes were made.
.PP
4.0.0 was released on July 18, 2003, including the ability to
synchronize directly between two IMAP servers, the first
re-architecting of the configuration file to refine the
notion of an account, and the new Curses interface.

View File

@ -1,356 +0,0 @@
# Sample configuration file
# Copyright (C) 2002 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# Looking for a quick start? Take a look at offlineimap.conf.minimal.
##################################################
# General definitions
##################################################
[general]
# This specifies where offlineimap is to store its metadata.
# This directory will be created if it does not already exist.
metadata = ~/.offlineimap
# This variable specifies which accounts are defined. Separate them
# with commas. Account names should be alphanumeric only.
# You will need to specify one section per account below. You may
# not use "general" for an account name.
#
accounts = Test
# Offlineimap can synchronize more the one account at a time. If you
# want to enable this feature, set the below value to something
# greater than 1. To force it to synchronize only one account at a
# time, set it to 1.
#
maxsyncaccounts = 1
# You can specify one or more user interface modules for OfflineIMAP
# to use. OfflineIMAP will try the first in the list, and if it
# fails, the second, and so forth.
#
# 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
# Tk.Blinkenlights
# TTY.TTYUI -- a text-based (terminal) interface
# Noninteractive.Basic -- Noninteractive interface suitable for cronning
# Noninteractive.Quiet -- Noninteractive interface, generates no output
# except for errors.
#
# You can override this with a command-line option -u.
ui = Tk.Blinkenlights, Tk.VerboseUI, Curses.Blinkenlights, TTY.TTYUI,
Noninteractive.Basic, Noninteractive.Quiet
# If you try to synchronize messages to a read-only folder,
# OfflineIMAP will generate a warning. If you want to suppress these
# warnings, set ignore-readonly to yes. Read-only IMAP folders allow
# reading but not modification, so if you try to change messages in
# the local copy of such a folder, the IMAP server will prevent
# OfflineIMAP from propogating those changes to the IMAP server.
ignore-readonly = no
########## Advanced settings
# You can give a Python source filename here and all config file
# python snippets will be evaluated in the context of that file.
# This allows you to e.g. define helper functions in the Python
# source file and call them from this config file. You can find
# an example of this in the manual.
#
# pythonfile = ~/.offlineimap.py
#
##################################################
# Mailbox name recorder
##################################################
[mbnames]
# offlineimap can record your mailbox names in a format you specify.
# You can define the header, each mailbox item, the separator,
# and the footer. Here is an example for Mutt.
# If enabled is yes, all six setting must be specified, even if they
# are just the empty string "".
#
# The header, peritem, sep, and footer are all Python expressions passed
# through eval, so you can (and must) use Python quoting.
enabled = no
filename = ~/Mutt/muttrc.mailboxes
header = "mailboxes "
peritem = "+%(accountname)s/%(foldername)s"
sep = " "
footer = "\n"
# You can also specify a folderfilter. It will apply to the
# *translated* folder name here, and it takes TWO arguments:
# accountname and foldername. In all other ways, it will
# behave identically to the folderfilter for accounts. Please see
# that section for more information and examples.
#
# Note that this filter can be used only to further restrict mbnames
# 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
##################################################
# Accounts
##################################################
# This is an account definition clause. You'll have one of these
# for each account listed in general/accounts above.
[Account Test]
########## Basic settings
# These settings specify the two folders that you will be syncing.
# You'll need to have a "Repository ..." section for each one.
localrepository = LocalExample
remoterepository = RemoteExample
########## Advanced settings
# You can have offlineimap continue running indefinately, automatically
# syncing your mail periodically. If you want that, specify how
# frequently to do that (in minutes) here. You can also specify
# fractional minutes (ie, 3.25).
# autorefresh = 5
[Repository LocalExample]
# This is one of the two repositories that you'll work with given the
# above example. Each repository requires a "type" declaration.
#
# The types supported are Maildir and IMAP.
#
type = Maildir
# Specify local repository. Your IMAP folders will be synchronized
# to maildirs created under this path. OfflineIMAP will create the
# maildirs for you as needed.
localfolders = ~/Test
# You can specify the "path separator character" used for your Maildir
# folders. This is inserted in-between the components of the tree.
# It defaults to ".". If you want your Maildir folders to be nested,
# set it to "/".
sep = .
[Repository RemoteExample]
# And this is the remote repository. For now, we only support IMAP here.
type = IMAP
# Specify the remote hostname.
remotehost = examplehost
# Whether or not to use SSL.
ssl = yes
# Specify the port. If not specified, use a default port.
# remoteport = 993
# Specify the remote user name.
remoteuser = username
# There are three ways to specify the password for the remote IMAP
# server:
#
# 1. No password at all specified in the config file. You will
# be prompted for the password when OfflineIMAP starts.
#
# 2. The remote password stored in this file with the remotepass
# option. Example:
#
# remotepass = mypassword
#
# 3. The remote password stored as a single line in an external
# file, which is referenced by the remotefile option. Example:
#
# remotepassfile = ~/Password.IMAP.Account1
#
# 4. With a preauth tunnel. With this method, you invoke an external
# program that is guaranteed *NOT* to ask for a password, but rather
# to read from stdin and write to stdout an IMAP procotol stream
# that begins life in the PREAUTH state. When you use a tunnel,
# you do NOT specify a user or password (if you do, they'll be
# ignored.) Instead, you specify a preauthtunnel, as this
# example illustrates for Courier IMAP on Debian:
#
# preauthtunnel = ssh -q imaphost '/usr/bin/imapd ./Maildir'
#
########## Advanced settings
# Some IMAP servers need a "reference" which often refers to the
# "folder root". This is most commonly needed with UW IMAP, where
# you might need to specify the directory in which your mail is
# stored. Most users will not need this.
#
# reference = Mail
# OfflineIMAP can use multiple connections to the server in order
# to perform multiple synchronization actions simultaneously.
# This may place a higher burden on the server. In most cases,
# setting this value to 2 or 3 will speed up the sync, but in some
# cases, it may slow things down. The safe answer is 1. You should
# probably never set it to a value more than 5.
maxconnections = 1
# OfflineIMAP normally closes IMAP server connections between refreshes if
# the global option autorefresh is specified. If you wish it to keep the
# connection open, set this to true. If not specified, the default is
# false. Keeping the connection open means a faster sync start the
# next time and may use fewer server resources on connection, but uses
# more server memory. This setting has no effect if autorefresh is not set.
holdconnectionopen = no
# If you want to have "keepalives" sent while waiting between syncs,
# specify the amount of time IN SECONDS between keepalives here. Note that
# sometimes more than this amount of time might pass, so don't make it
# tight. This setting has no effect if autorefresh and holdconnectionopen
# are not both set.
# keepalive = 60
# Normally, OfflineIMAP will expunge deleted messages from the server.
# You can disable that if you wish. This means that OfflineIMAP will
# mark them deleted on the server, but not actually delete them.
# You must use some other IMAP client to delete them if you use this
# setting; otherwise, the messgaes will just pile up there forever.
# Therefore, this setting is definately NOT recommended.
#
# expunge = no
# You can specify a folder translator. This must be a eval-able
# Python expression that takes a foldername arg and returns the new
# value. I suggest a lambda. This example below will remove "INBOX." from
# the leading edge of folders (great for Courier IMAP users)
#
# WARNING: you MUST construct this such that it NEVER returns
# the same value for two folders, UNLESS the second values are
# filtered out by folderfilter below. Failure to follow this rule
# will result in undefined behavior
#
# nametrans = lambda foldername: re.sub('^INBOX\.', '', foldername)
# You can specify which folders to sync. You can do it several ways.
# I'll provide some examples. The folderfilter operates on the
# *UNTRANSLATED* name, if you specify nametrans. It should return
# true if the folder is to be included; false otherwise.
#
# Example 1: synchronizing only INBOX and Sent.
#
# folderfilter = lambda foldername: foldername in ['INBOX', 'Sent']
#
# Example 2: synchronizing everything except Trash.
#
# folderfilter = lambda foldername: foldername not in ['Trash']
#
# Example 3: Using a regular expression to exclude Trash and all folders
# containing the characters "Del".
#
# folderfilter = lambda foldername: not re.search('(^Trash$|Del)', foldername)
#
# If folderfilter is not specified, ALL remote folders will be
# synchronized.
#
# You can span multiple lines by indenting the others. (Use backslashes
# at the end when required by Python syntax) For instance:
#
# folderfilter = lambda foldername: foldername in
# ['INBOX', 'Sent Mail', 'Deleted Items',
# 'Received']
#
# FYI, you could also include every folder with:
#
# folderfilter = lambda foldername: 1
#
# And exclude every folder with:
#
# folderfilter = lambda foldername: 0
# You can specify folderincludes to include additional folders.
# It should return a Python list. This might be used to include a
# folder that was excluded by your folderfilter rule, to include a
# folder that your server does not specify with its LIST option, or
# to include a folder that is outside your basic reference. Some examples:
#
# To include debian.user and debian.personal:
#
# folderincludes = ['debian.user', 'debian.personal']
#
# To include your INBOX (UW IMAPd users will find this useful if they
# specify a reference):
#
# folderincludes = ['INBOX']
#
# To specify a long list:
#
# folderincludes = ['box1', 'box2', 'box3', 'box4',
# 'box5', 'box6']
# You can specify foldersort to determine how folders are sorted.
# This affects order of synchronization and mbnames. The expression
# should return -1, 0, or 1, as the default Python cmp() does. The
# two arguments, x and y, are strings representing the names of the folders
# to be sorted. The sorting is applied *AFTER* nametrans, if any.
#
# To reverse the sort:
#
# foldersort = lambda x, y: -cmp(x, y)

View File

@ -1,18 +0,0 @@
# Sample minimal config file. Copy this to ~/.offlineimaprc and edit to
# suit to get started fast.
[general]
accounts = Test
[Account Test]
localrepository = Local
remoterepository = Remote
[Repository Local]
type = Maildir
localfolders = ~/Test
[Repository Remote]
type = IMAP
remotehost = examplehost
remoteuser = jgoerzen

View File

@ -1,21 +0,0 @@
#!/usr/bin/env python2.3
# Startup from single-user installation
# Copyright (C) 2002, 2003, 2004 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from offlineimap import init
init.startup('4.0.7')

File diff suppressed because it is too large Load Diff

View File

@ -1,104 +0,0 @@
# Copyright (C) 2003 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from ConfigParser import ConfigParser
from offlineimap.localeval import LocalEval
import os
class CustomConfigParser(ConfigParser):
def getdefault(self, section, option, default, *args, **kwargs):
"""Same as config.get, but returns the "default" option if there
is no such option specified."""
if self.has_option(section, option):
return apply(self.get, [section, option] + list(args), kwargs)
else:
return default
def getdefaultint(self, section, option, default, *args, **kwargs):
if self.has_option(section, option):
return apply(self.getint, [section, option] + list(args), kwargs)
else:
return default
def getdefaultfloat(self, section, option, default, *args, **kwargs):
if self.has_option(section, option):
return apply(self.getfloat, [section, option] + list(args), kwargs)
else:
return default
def getdefaultboolean(self, section, option, default, *args, **kwargs):
if self.has_option(section, option):
return apply(self.getboolean, [section, option] + list(args),
kwargs)
else:
return default
def getmetadatadir(self):
metadatadir = os.path.expanduser(self.getdefault("general", "metadata", "~/.offlineimap"))
if not os.path.exists(metadatadir):
os.mkdir(metadatadir, 0700)
return metadatadir
def getlocaleval(self):
if self.has_option("general", "pythonfile"):
path = os.path.expanduser(self.get("general", "pythonfile"))
else:
path = None
return LocalEval(path)
def getsectionlist(self, key):
"""Returns a list of sections that start with key + " ". That is,
if key is "Account", returns all section names that start with
"Account ", but strips off the "Account ". For instance, for
"Account Test", returns "Test"."""
key = key + ' '
return [x[len(key):] for x in self.sections() \
if x.startswith(key)]
def CustomConfigDefault():
"""Just a sample constant that won't occur anywhere else to use for the
default."""
pass
class ConfigHelperMixin:
def _confighelper_runner(self, option, default, defaultfunc, mainfunc):
if default != CustomConfigDefault:
return apply(defaultfunc, [self.getsection(), option, default])
else:
return apply(mainfunc, [self.getsection(), option])
def getconf(self, option, default = CustomConfigDefault):
return self._confighelper_runner(option, default,
self.getconfig().getdefault,
self.getconfig().get)
def getconfboolean(self, option, default = CustomConfigDefault):
return self._confighelper_runner(option, default,
self.getconfig().getdefaultboolean,
self.getconfig().getboolean)
def getconfint(self, option, default = CustomConfigDefault):
return self._confighelper_runner(option, default,
self.getconfig().getdefaultint,
self.getconfig().getint)
def getconffloat(self, option, default = CustomConfigDefault):
return self._confighelper_runner(option, default,
self.getconfig().getdefaultfloat,
self.getconfig().getfloat)

View File

@ -1,3 +0,0 @@
__all__ = ['ui', 'folder', 'repository', 'mbnames', 'threadutil', 'init']

View File

@ -1,233 +0,0 @@
# Copyright (C) 2003 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from offlineimap import threadutil, mbnames, CustomConfig
import offlineimap.repository.Base, offlineimap.repository.LocalStatus
from offlineimap.ui import UIBase
from offlineimap.threadutil import InstanceLimitedThread, ExitNotifyThread
from threading import Event
import os
def getaccountlist(customconfig):
return customconfig.getsectionlist('Account')
def AccountListGenerator(customconfig):
return [Account(customconfig, accountname)
for accountname in getaccountlist(customconfig)]
def AccountHashGenerator(customconfig):
retval = {}
for item in AccountListGenerator(customconfig):
retval[item.getname()] = item
return retval
mailboxes = []
class Account(CustomConfig.ConfigHelperMixin):
def __init__(self, config, name):
self.config = config
self.name = name
self.metadatadir = config.getmetadatadir()
self.localeval = config.getlocaleval()
self.ui = UIBase.getglobalui()
self.refreshperiod = self.getconffloat('autorefresh', 0.0)
if self.refreshperiod == 0.0:
self.refreshperiod = None
def getlocaleval(self):
return self.localeval
def getconfig(self):
return self.config
def getname(self):
return self.name
def getsection(self):
return 'Account ' + self.getname()
def sleeper(self):
"""Sleep handler. Returns same value as UIBase.sleep:
0 if timeout expired, 1 if there was a request to cancel the timer,
and 2 if there is a request to abort the program.
Also, returns 100 if configured to not sleep at all."""
if not self.refreshperiod:
return 100
kaobjs = []
if hasattr(self, 'localrepos'):
kaobjs.append(self.localrepos)
if hasattr(self, 'remoterepos'):
kaobjs.append(self.remoterepos)
for item in kaobjs:
item.startkeepalive()
refreshperiod = int(self.refreshperiod * 60)
sleepresult = self.ui.sleep(refreshperiod)
if sleepresult == 2:
# Cancel keep-alive, but don't bother terminating threads
for item in kaobjs:
item.stopkeepalive(abrupt = 1)
return sleepresult
else:
# Cancel keep-alive and wait for thread to terminate.
for item in kaobjs:
item.stopkeepalive(abrupt = 0)
return sleepresult
class AccountSynchronizationMixin:
def syncrunner(self):
self.ui.registerthread(self.name)
self.ui.acct(self.name)
accountmetadata = self.getaccountmeta()
if not os.path.exists(accountmetadata):
os.mkdir(accountmetadata, 0700)
self.remoterepos = offlineimap.repository.Base.LoadRepository(self.getconf('remoterepository'), self, 'remote')
# Connect to the local repository.
self.localrepos = offlineimap.repository.Base.LoadRepository(self.getconf('localrepository'), self, 'local')
# Connect to the local cache.
self.statusrepos = offlineimap.repository.LocalStatus.LocalStatusRepository(self.getconf('localrepository'), self)
if not self.refreshperiod:
self.sync()
self.ui.acctdone(self.name)
return
looping = 1
while looping:
self.sync()
looping = self.sleeper() != 2
self.ui.acctdone(self.name)
def getaccountmeta(self):
return os.path.join(self.metadatadir, 'Account-' + self.name)
def sync(self):
# We don't need an account lock because syncitall() goes through
# each account once, then waits for all to finish.
try:
remoterepos = self.remoterepos
localrepos = self.localrepos
statusrepos = self.statusrepos
self.ui.syncfolders(remoterepos, localrepos)
remoterepos.syncfoldersto(localrepos)
folderthreads = []
for remotefolder in remoterepos.getfolders():
thread = InstanceLimitedThread(\
instancename = 'FOLDER_' + self.remoterepos.getname(),
target = syncfolder,
name = "Folder sync %s[%s]" % \
(self.name, remotefolder.getvisiblename()),
args = (self.name, remoterepos, remotefolder, localrepos,
statusrepos))
thread.setDaemon(1)
thread.start()
folderthreads.append(thread)
threadutil.threadsreset(folderthreads)
mbnames.write()
localrepos.holdordropconnections()
remoterepos.holdordropconnections()
finally:
pass
class SyncableAccount(Account, AccountSynchronizationMixin):
pass
def syncfolder(accountname, remoterepos, remotefolder, localrepos,
statusrepos):
global mailboxes
ui = UIBase.getglobalui()
ui.registerthread(accountname)
# Load local folder.
localfolder = localrepos.\
getfolder(remotefolder.getvisiblename().\
replace(remoterepos.getsep(), localrepos.getsep()))
# Write the mailboxes
mbnames.add(accountname, localfolder.getvisiblename())
# Load local folder
ui.syncingfolder(remoterepos, remotefolder, localrepos, localfolder)
ui.loadmessagelist(localrepos, localfolder)
localfolder.cachemessagelist()
ui.messagelistloaded(localrepos, localfolder, len(localfolder.getmessagelist().keys()))
# Load status folder.
statusfolder = statusrepos.getfolder(remotefolder.getvisiblename().\
replace(remoterepos.getsep(),
statusrepos.getsep()))
if localfolder.getuidvalidity() == None:
# This is a new folder, so delete the status cache to be sure
# we don't have a conflict.
statusfolder.deletemessagelist()
statusfolder.cachemessagelist()
# If either the local or the status folder has messages and there is a UID
# validity problem, warn and abort. If there are no messages, UW IMAPd
# loses UIDVALIDITY. But we don't really need it if both local folders are
# empty. So, in that case, just save it off.
if len(localfolder.getmessagelist()) or len(statusfolder.getmessagelist()):
if not localfolder.isuidvalidityok():
ui.validityproblem(localfolder, localfolder.getsaveduidvalidity(),
localfolder.getuidvalidity())
return
if not remotefolder.isuidvalidityok():
ui.validityproblem(remotefolder, remotefolder.getsaveduidvalidity(),
remotefolder.getuidvalidity())
return
else:
localfolder.saveuidvalidity()
remotefolder.saveuidvalidity()
# Load remote folder.
ui.loadmessagelist(remoterepos, remotefolder)
remotefolder.cachemessagelist()
ui.messagelistloaded(remoterepos, remotefolder,
len(remotefolder.getmessagelist().keys()))
#
if not statusfolder.isnewfolder():
# Delete local copies of remote messages. This way,
# if a message's flag is modified locally but it has been
# deleted remotely, we'll delete it locally. Otherwise, we
# try to modify a deleted message's flags! This step
# need only be taken if a statusfolder is present; otherwise,
# there is no action taken *to* the remote repository.
remotefolder.syncmessagesto_delete(localfolder, [localfolder,
statusfolder])
ui.syncingmessages(localrepos, localfolder, remoterepos, remotefolder)
localfolder.syncmessagesto(statusfolder, [remotefolder, statusfolder])
# Synchronize remote changes.
ui.syncingmessages(remoterepos, remotefolder, localrepos, localfolder)
remotefolder.syncmessagesto(localfolder, [localfolder, statusfolder])
# Make sure the status folder is up-to-date.
ui.syncingmessages(localrepos, localfolder, statusrepos, statusfolder)
localfolder.syncmessagesto(statusfolder)
statusfolder.save()

View File

@ -1,390 +0,0 @@
# Base folder support
# Copyright (C) 2002 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from threading import *
from offlineimap import threadutil
from offlineimap.threadutil import InstanceLimitedThread
from offlineimap.ui import UIBase
import os.path, re
class BaseFolder:
def __init__(self):
self.uidlock = Lock()
def getname(self):
"""Returns name"""
return self.name
def suggeststhreads(self):
"""Returns true if this folder suggests using threads for actions;
false otherwise. Probably only IMAP will return true."""
return 0
def waitforthread(self):
"""For threading folders, waits until there is a resource available
before firing off a thread. For all others, returns immediately."""
pass
def getcopyinstancelimit(self):
"""For threading folders, returns the instancelimitname for
InstanceLimitedThreads."""
raise NotImplementedException
def storesmessages(self):
"""Should be true for any backend that actually saves message bodies.
(Almost all of them). False for the LocalStatus backend. Saves
us from having to slurp up messages just for localstatus purposes."""
return 1
def getvisiblename(self):
return self.name
def getrepository(self):
"""Returns the repository object that this folder is within."""
return self.repository
def getroot(self):
"""Returns the root of the folder, in a folder-specific fashion."""
return self.root
def getsep(self):
"""Returns the separator for this folder type."""
return self.sep
def getfullname(self):
if self.getroot():
return self.getroot() + self.getsep() + self.getname()
else:
return self.getname()
def getfolderbasename(self):
foldername = self.getname()
foldername = foldername.replace(self.repository.getsep(), '.')
foldername = re.sub('/\.$', '/dot', foldername)
foldername = re.sub('^\.$', 'dot', foldername)
return foldername
def isuidvalidityok(self):
if self.getsaveduidvalidity() != None:
return self.getsaveduidvalidity() == self.getuidvalidity()
else:
self.saveuidvalidity()
return 1
def _getuidfilename(self):
return os.path.join(self.repository.getuiddir(),
self.getfolderbasename())
def getsaveduidvalidity(self):
if hasattr(self, '_base_saved_uidvalidity'):
return self._base_saved_uidvalidity
uidfilename = self._getuidfilename()
if not os.path.exists(uidfilename):
self._base_saved_uidvalidity = None
else:
file = open(uidfilename, "rt")
self._base_saved_uidvalidity = long(file.readline().strip())
file.close()
return self._base_saved_uidvalidity
def saveuidvalidity(self):
newval = self.getuidvalidity()
uidfilename = self._getuidfilename()
self.uidlock.acquire()
try:
file = open(uidfilename + ".tmp", "wt")
file.write("%d\n" % newval)
file.close()
os.rename(uidfilename + ".tmp", uidfilename)
self._base_saved_uidvalidity = newval
finally:
self.uidlock.release()
def getuidvalidity(self):
raise NotImplementedException
def cachemessagelist(self):
"""Reads the message list from disk or network and stores it in
memory for later use. This list will not be re-read from disk or
memory unless this function is called again."""
raise NotImplementedException
def getmessagelist(self):
"""Gets the current message list.
You must call cachemessagelist() before calling this function!"""
raise NotImplementedException
def getmessage(self, uid):
"""Returns the content of the specified message."""
raise NotImplementedException
def savemessage(self, uid, content, flags):
"""Writes a new message, with the specified uid.
If the uid is < 0, the backend should assign a new uid and return it.
If the backend cannot assign a new uid, it returns the uid passed in
WITHOUT saving the message.
If the backend CAN assign a new uid, but cannot find out what this UID
is (as is the case with many IMAP servers), it returns 0 but DOES save
the message.
IMAP backend should be the only one that can assign a new uid.
If the uid is > 0, the backend should set the uid to this, if it can.
If it cannot set the uid to that, it will save it anyway.
It will return the uid assigned in any case.
"""
raise NotImplementedException
def getmessageflags(self, uid):
"""Returns the flags for the specified message."""
raise NotImplementedException
def savemessageflags(self, uid, flags):
"""Sets the specified message's flags to the given set."""
raise NotImplementedException
def addmessageflags(self, uid, flags):
"""Adds the specified flags to the message's flag set. If a given
flag is already present, it will not be duplicated."""
newflags = self.getmessageflags(uid)
for flag in flags:
if not flag in newflags:
newflags.append(flag)
newflags.sort()
self.savemessageflags(uid, newflags)
def addmessagesflags(self, uidlist, flags):
for uid in uidlist:
self.addmessageflags(uid, flags)
def deletemessageflags(self, uid, flags):
"""Removes each flag given from the message's flag set. If a given
flag is already removed, no action will be taken for that flag."""
newflags = self.getmessageflags(uid)
for flag in flags:
if flag in newflags:
newflags.remove(flag)
newflags.sort()
self.savemessageflags(uid, newflags)
def deletemessagesflags(self, uidlist, flags):
for uid in uidlist:
self.deletemessageflags(uid, flags)
def deletemessage(self, uid):
raise NotImplementedException
def deletemessages(self, uidlist):
for uid in uidlist:
self.deletemessage(uid)
def syncmessagesto_neguid_msg(self, uid, dest, applyto, register = 1):
if register:
UIBase.getglobalui().registerthread(self.getaccountname())
UIBase.getglobalui().copyingmessage(uid, self, applyto)
successobject = None
successuid = None
message = self.getmessage(uid)
flags = self.getmessageflags(uid)
for tryappend in applyto:
successuid = tryappend.savemessage(uid, message, flags)
if successuid >= 0:
successobject = tryappend
break
# Did we succeed?
if successobject != None:
if successuid: # Only if IMAP actually assigned a UID
# Copy the message to the other remote servers.
for appendserver in \
[x for x in applyto if x != successobject]:
appendserver.savemessage(successuid, message, flags)
# Copy to its new name on the local server and delete
# the one without a UID.
self.savemessage(successuid, message, flags)
self.deletemessage(uid) # It'll be re-downloaded.
else:
# Did not find any server to take this message. Ignore.
pass
def syncmessagesto_neguid(self, dest, applyto):
"""Pass 1 of folder synchronization.
Look for messages in self with a negative uid. These are messages in
Maildirs that were not added by us. Try to add them to the dests,
and once that succeeds, get the UID, add it to the others for real,
add it to local for real, and delete the fake one."""
uidlist = [uid for uid in self.getmessagelist().keys() if uid < 0]
threads = []
usethread = None
if applyto != None:
usethread = applyto[0]
for uid in uidlist:
if usethread and usethread.suggeststhreads():
usethread.waitforthread()
thread = InstanceLimitedThread(\
usethread.getcopyinstancelimit(),
target = self.syncmessagesto_neguid_msg,
name = "New msg sync from %s" % self.getvisiblename(),
args = (uid, dest, applyto))
thread.setDaemon(1)
thread.start()
threads.append(thread)
else:
self.syncmessagesto_neguid_msg(uid, dest, applyto, register = 0)
for thread in threads:
thread.join()
def copymessageto(self, uid, applyto, register = 1):
# Sometimes, it could be the case that if a sync takes awhile,
# a message might be deleted from the maildir before it can be
# synced to the status cache. This is only a problem with
# self.getmessage(). So, don't call self.getmessage unless
# really needed.
if register:
UIBase.getglobalui().registerthread(self.getaccountname())
UIBase.getglobalui().copyingmessage(uid, self, applyto)
message = ''
# If any of the destinations actually stores the message body,
# load it up.
for object in applyto:
if object.storesmessages():
message = self.getmessage(uid)
break
flags = self.getmessageflags(uid)
for object in applyto:
newuid = object.savemessage(uid, message, flags)
if newuid > 0 and newuid != uid:
# Change the local uid.
self.savemessage(newuid, message, flags)
self.deletemessage(uid)
uid = newuid
def syncmessagesto_copy(self, dest, applyto):
"""Pass 2 of folder synchronization.
Look for messages present in self but not in dest. If any, add
them to dest."""
threads = []
for uid in self.getmessagelist().keys():
if uid < 0: # Ignore messages that pass 1 missed.
continue
if not uid in dest.getmessagelist():
if self.suggeststhreads():
self.waitforthread()
thread = InstanceLimitedThread(\
self.getcopyinstancelimit(),
target = self.copymessageto,
name = "Copy message %d from %s" % (uid,
self.getvisiblename()),
args = (uid, applyto))
thread.setDaemon(1)
thread.start()
threads.append(thread)
else:
self.copymessageto(uid, applyto, register = 0)
for thread in threads:
thread.join()
def syncmessagesto_delete(self, dest, applyto):
"""Pass 3 of folder synchronization.
Look for message present in dest but not in self.
If any, delete them."""
deletelist = []
for uid in dest.getmessagelist().keys():
if uid < 0:
continue
if not uid in self.getmessagelist():
deletelist.append(uid)
if len(deletelist):
UIBase.getglobalui().deletingmessages(deletelist, applyto)
for object in applyto:
object.deletemessages(deletelist)
def syncmessagesto_flags(self, dest, applyto):
"""Pass 4 of folder synchronization.
Look for any flag matching issues -- set dest message to have the
same flags that we have."""
# As an optimization over previous versions, we store up which flags
# are being used for an add or a delete. For each flag, we store
# a list of uids to which it should be added. Then, we can call
# addmessagesflags() to apply them in bulk, rather than one
# call per message as before. This should result in some significant
# performance improvements.
addflaglist = {}
delflaglist = {}
for uid in self.getmessagelist().keys():
if uid < 0: # Ignore messages missed by pass 1
continue
selfflags = self.getmessageflags(uid)
destflags = dest.getmessageflags(uid)
addflags = [x for x in selfflags if x not in destflags]
for flag in addflags:
if not flag in addflaglist:
addflaglist[flag] = []
addflaglist[flag].append(uid)
delflags = [x for x in destflags if x not in selfflags]
for flag in delflags:
if not flag in delflaglist:
delflaglist[flag] = []
delflaglist[flag].append(uid)
for object in applyto:
for flag in addflaglist.keys():
UIBase.getglobalui().addingflags(addflaglist[flag], flag, [object])
object.addmessagesflags(addflaglist[flag], [flag])
for flag in delflaglist.keys():
UIBase.getglobalui().deletingflags(delflaglist[flag], flag, [object])
object.deletemessagesflags(delflaglist[flag], [flag])
def syncmessagesto(self, dest, applyto = None):
"""Syncs messages in this folder to the destination.
If applyto is specified, it should be a list of folders (don't forget
to include dest!) to which all write actions should be applied.
It defaults to [dest] if not specified. It is important that
the UID generator be listed first in applyto; that is, the other
applyto ones should be the ones that "copy" the main action."""
if applyto == None:
applyto = [dest]
self.syncmessagesto_neguid(dest, applyto)
self.syncmessagesto_copy(dest, applyto)
self.syncmessagesto_delete(dest, applyto)
# Now, the message lists should be identical wrt the uids present.
# (except for potential negative uids that couldn't be placed
# anywhere)
self.syncmessagesto_flags(dest, applyto)

View File

@ -1,358 +0,0 @@
# IMAP folder support
# Copyright (C) 2002-2004 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from Base import BaseFolder
from offlineimap import imaputil, imaplib
from offlineimap.ui import UIBase
from offlineimap.version import versionstr
import rfc822, time, string, random, binascii, re
from StringIO import StringIO
from copy import copy
class IMAPFolder(BaseFolder):
def __init__(self, imapserver, name, visiblename, accountname, repository):
self.config = imapserver.config
self.expunge = repository.getexpunge()
self.name = imaputil.dequote(name)
self.root = None # imapserver.root
self.sep = imapserver.delim
self.imapserver = imapserver
self.messagelist = None
self.visiblename = visiblename
self.accountname = accountname
self.repository = repository
self.randomgenerator = random.Random()
BaseFolder.__init__(self)
def getaccountname(self):
return self.accountname
def suggeststhreads(self):
return 1
def waitforthread(self):
self.imapserver.connectionwait()
def getcopyinstancelimit(self):
return 'MSGCOPY_' + self.repository.getname()
def getvisiblename(self):
return self.visiblename
def getuidvalidity(self):
imapobj = self.imapserver.acquireconnection()
try:
# Primes untagged_responses
imapobj.select(self.getfullname(), readonly = 1)
return long(imapobj.untagged_responses['UIDVALIDITY'][0])
finally:
self.imapserver.releaseconnection(imapobj)
def cachemessagelist(self):
imapobj = self.imapserver.acquireconnection()
self.messagelist = {}
try:
# Primes untagged_responses
imapobj.select(self.getfullname(), readonly = 1, force = 1)
try:
# Some mail servers do not return an EXISTS response if
# the folder is empty.
maxmsgid = long(imapobj.untagged_responses['EXISTS'][0])
except KeyError:
return
if maxmsgid < 1:
# No messages; return
return
# Now, get the flags and UIDs for these.
# We could conceivably get rid of maxmsgid and just say
# '1:*' here.
response = imapobj.fetch('1:%d' % maxmsgid, '(FLAGS UID)')[1]
finally:
self.imapserver.releaseconnection(imapobj)
for messagestr in response:
# Discard the message number.
messagestr = string.split(messagestr, maxsplit = 1)[1]
options = imaputil.flags2hash(messagestr)
if not options.has_key('UID'):
UIBase.getglobalui().warn('No UID in message with options %s' %\
str(options),
minor = 1)
else:
uid = long(options['UID'])
flags = imaputil.flagsimap2maildir(options['FLAGS'])
self.messagelist[uid] = {'uid': uid, 'flags': flags}
def getmessagelist(self):
return self.messagelist
def getmessage(self, uid):
ui = UIBase.getglobalui()
imapobj = self.imapserver.acquireconnection()
try:
imapobj.select(self.getfullname(), readonly = 1)
initialresult = imapobj.uid('fetch', '%d' % uid, '(BODY.PEEK[])')
ui.debug('imap', 'Returned object from fetching %d: %s' % \
(uid, str(initialresult)))
return initialresult[1][0][1].replace("\r\n", "\n")
finally:
self.imapserver.releaseconnection(imapobj)
def getmessageflags(self, uid):
return self.messagelist[uid]['flags']
def savemessage_getnewheader(self, content):
headername = 'X-OfflineIMAP-%s-' % str(binascii.crc32(content)).replace('-', 'x')
headername += binascii.hexlify(self.repository.getname()) + '-'
headername += binascii.hexlify(self.getname())
headervalue= '%d-' % long(time.time())
headervalue += str(self.randomgenerator.random()).replace('.', '')
headervalue += '-v' + versionstr
return (headername, headervalue)
def savemessage_addheader(self, content, headername, headervalue):
ui = UIBase.getglobalui()
ui.debug('imap',
'savemessage_addheader: called to add %s: %s' % (headername,
headervalue))
insertionpoint = content.find("\r\n")
ui.debug('imap', 'savemessage_addheader: insertionpoint = %d' % insertionpoint)
leader = content[0:insertionpoint]
ui.debug('imap', 'savemessage_addheader: leader = %s' % repr(leader))
if insertionpoint == 0 or insertionpoint == -1:
newline = ''
insertionpoint = 0
else:
newline = "\r\n"
newline += "%s: %s" % (headername, headervalue)
ui.debug('imap', 'savemessage_addheader: newline = ' + repr(newline))
trailer = content[insertionpoint:]
ui.debug('imap', 'savemessage_addheader: trailer = ' + repr(trailer))
return leader + newline + trailer
def savemessage_searchforheader(self, imapobj, headername, headervalue):
if imapobj.untagged_responses.has_key('APPENDUID'):
return long(imapobj.untagged_responses['APPENDUID'][0].split(' ')[1])
ui = UIBase.getglobalui()
ui.debug('imap', 'savemessage_searchforheader called for %s: %s' % \
(headername, headervalue))
# Now find the UID it got.
headervalue = imapobj._quote(headervalue)
try:
matchinguids = imapobj.uid('search', None,
'(HEADER %s %s)' % (headername, headervalue))[1][0]
except imapobj.error:
# IMAP server doesn't implement search or had a problem.
return 0
ui.debug('imap', 'savemessage_searchforheader got initial matchinguids: ' + repr(matchinguids))
matchinguids = matchinguids.split(' ')
ui.debug('imap', 'savemessage_searchforheader: matchinguids now ' + \
repr(matchinguids))
if len(matchinguids) != 1 or matchinguids[0] == None:
raise ValueError, "While attempting to find UID for message with header %s, got wrong-sized matchinguids of %s" % (headername, str(matchinguids))
matchinguids.sort()
return long(matchinguids[0])
def savemessage(self, uid, content, flags):
imapobj = self.imapserver.acquireconnection()
ui = UIBase.getglobalui()
ui.debug('imap', 'savemessage: called')
try:
try:
imapobj.select(self.getfullname()) # Needed for search
except imapobj.readonly:
ui.msgtoreadonly(self, uid, content, flags)
# Return indicating message taken, but no UID assigned.
# Fudge it.
return 0
# This backend always assigns a new uid, so the uid arg is ignored.
# In order to get the new uid, we need to save off the message ID.
message = rfc822.Message(StringIO(content))
datetuple = rfc822.parsedate(message.getheader('Date'))
# Will be None if missing or not in a valid format.
if datetuple == None:
datetuple = time.localtime()
try:
if datetuple[0] < 1981:
raise ValueError
# This could raise a value error if it's not a valid format.
date = imaplib.Time2Internaldate(datetuple)
except ValueError:
# Argh, sometimes it's a valid format but year is 0102
# or something. Argh. It seems that Time2Internaldate
# will rause a ValueError if the year is 0102 but not 1902,
# but some IMAP servers nonetheless choke on 1902.
date = imaplib.Time2Internaldate(time.localtime())
ui.debug('imap', 'savemessage: using date ' + str(date))
content = re.sub("(?<!\r)\n", "\r\n", content)
ui.debug('imap', 'savemessage: initial content is: ' + repr(content))
(headername, headervalue) = self.savemessage_getnewheader(content)
ui.debug('imap', 'savemessage: new headers are: %s: %s' % \
(headername, headervalue))
content = self.savemessage_addheader(content, headername,
headervalue)
ui.debug('imap', 'savemessage: new content is: ' + repr(content))
ui.debug('imap', 'savemessage: new content length is ' + \
str(len(content)))
assert(imapobj.append(self.getfullname(),
imaputil.flagsmaildir2imap(flags),
date, content)[0] == 'OK')
# Checkpoint. Let it write out the messages, etc.
assert(imapobj.check()[0] == 'OK')
# Keep trying until we get the UID.
try:
ui.debug('imap', 'savemessage: first attempt to get new UID')
uid = self.savemessage_searchforheader(imapobj, headername,
headervalue)
except ValueError:
ui.debug('imap', 'savemessage: first attempt to get new UID failed. Going to run a NOOP and try again.')
assert(imapobj.noop()[0] == 'OK')
uid = self.savemessage_searchforheader(imapobj, headername,
headervalue)
finally:
self.imapserver.releaseconnection(imapobj)
self.messagelist[uid] = {'uid': uid, 'flags': flags}
ui.debug('imap', 'savemessage: returning %d' % uid)
return uid
def savemessageflags(self, uid, flags):
imapobj = self.imapserver.acquireconnection()
try:
try:
imapobj.select(self.getfullname())
except imapobj.readonly:
UIBase.getglobalui().flagstoreadonly(self, [uid], flags)
return
result = imapobj.uid('store', '%d' % uid, 'FLAGS',
imaputil.flagsmaildir2imap(flags))
assert result[0] == 'OK', 'Error with store: ' + r[1]
finally:
self.imapserver.releaseconnection(imapobj)
result = result[1][0]
if not result:
self.messagelist[uid]['flags'] = flags
else:
flags = imaputil.flags2hash(imaputil.imapsplit(result)[1])['FLAGS']
self.messagelist[uid]['flags'] = imaputil.flagsimap2maildir(flags)
def addmessageflags(self, uid, flags):
self.addmessagesflags([uid], flags)
def addmessagesflags_noconvert(self, uidlist, flags):
self.processmessagesflags('+', uidlist, flags)
def addmessagesflags(self, uidlist, flags):
"""This is here for the sake of UIDMaps.py -- deletemessages must
add flags and get a converted UID, and if we don't have noconvert,
then UIDMaps will try to convert it twice."""
self.addmessagesflags_noconvert(uidlist, flags)
def deletemessageflags(self, uid, flags):
self.deletemessagesflags([uid], flags)
def deletemessagesflags(self, uidlist, flags):
self.processmessagesflags('-', uidlist, flags)
def processmessagesflags(self, operation, uidlist, flags):
imapobj = self.imapserver.acquireconnection()
try:
try:
imapobj.select(self.getfullname())
except imapobj.readonly:
UIBase.getglobalui().flagstoreadonly(self, uidlist, flags)
return
r = imapobj.uid('store',
imaputil.listjoin(uidlist),
operation + 'FLAGS',
imaputil.flagsmaildir2imap(flags))
assert r[0] == 'OK', 'Error with store: ' + r[1]
r = r[1]
finally:
self.imapserver.releaseconnection(imapobj)
# Some IMAP servers do not always return a result. Therefore,
# only update the ones that it talks about, and manually fix
# the others.
needupdate = copy(uidlist)
for result in r:
if result == None:
# Compensate for servers that don't return anything from
# STORE.
continue
attributehash = imaputil.flags2hash(imaputil.imapsplit(result)[1])
if not ('UID' in attributehash and 'FLAGS' in attributehash):
# Compensate for servers that don't return a UID attribute.
continue
flags = attributehash['FLAGS']
uid = long(attributehash['UID'])
self.messagelist[uid]['flags'] = imaputil.flagsimap2maildir(flags)
try:
needupdate.remove(uid)
except ValueError: # Let it slide if it's not in the list
pass
for uid in needupdate:
if operation == '+':
for flag in flags:
if not flag in self.messagelist[uid]['flags']:
self.messagelist[uid]['flags'].append(flag)
self.messagelist[uid]['flags'].sort()
elif operation == '-':
for flag in flags:
if flag in self.messagelist[uid]['flags']:
self.messagelist[uid]['flags'].remove(flag)
def deletemessage(self, uid):
self.deletemessages_noconvert([uid])
def deletemessages(self, uidlist):
self.deletemessages_noconvert(uidlist)
def deletemessages_noconvert(self, uidlist):
# Weed out ones not in self.messagelist
uidlist = [uid for uid in uidlist if uid in self.messagelist]
if not len(uidlist):
return
self.addmessagesflags_noconvert(uidlist, ['T'])
imapobj = self.imapserver.acquireconnection()
try:
try:
imapobj.select(self.getfullname())
except imapobj.readonly:
UIBase.getglobalui().deletereadonly(self, uidlist)
return
if self.expunge:
assert(imapobj.expunge()[0] == 'OK')
finally:
self.imapserver.releaseconnection(imapobj)
for uid in uidlist:
del self.messagelist[uid]

View File

@ -1,125 +0,0 @@
# Local status cache virtual folder
# Copyright (C) 2002 - 2003 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from Base import BaseFolder
import os, threading
magicline = "OFFLINEIMAP LocalStatus CACHE DATA - DO NOT MODIFY - FORMAT 1"
class LocalStatusFolder(BaseFolder):
def __init__(self, root, name, repository, accountname):
self.name = name
self.root = root
self.sep = '.'
self.filename = os.path.join(root, name)
self.filename = repository.getfolderfilename(name)
self.messagelist = None
self.repository = repository
self.savelock = threading.Lock()
self.doautosave = 1
self.accountname = accountname
BaseFolder.__init__(self)
def getaccountname(self):
return self.accountname
def storesmessages(self):
return 0
def isnewfolder(self):
return not os.path.exists(self.filename)
def getname(self):
return self.name
def getroot(self):
return self.root
def getsep(self):
return self.sep
def getfullname(self):
return self.filename
def deletemessagelist(self):
if not self.isnewfolder():
os.unlink(self.filename)
def cachemessagelist(self):
if self.isnewfolder():
self.messagelist = {}
return
file = open(self.filename, "rt")
self.messagelist = {}
line = file.readline().strip()
assert(line == magicline)
for line in file.xreadlines():
line = line.strip()
uid, flags = line.split(':')
uid = long(uid)
flags = [x for x in flags]
self.messagelist[uid] = {'uid': uid, 'flags': flags}
file.close()
def autosave(self):
if self.doautosave:
self.save()
def save(self):
self.savelock.acquire()
try:
file = open(self.filename + ".tmp", "wt")
file.write(magicline + "\n")
for msg in self.messagelist.values():
flags = msg['flags']
flags.sort()
flags = ''.join(flags)
file.write("%s:%s\n" % (msg['uid'], flags))
file.close()
os.rename(self.filename + ".tmp", self.filename)
finally:
self.savelock.release()
def getmessagelist(self):
return self.messagelist
def savemessage(self, uid, content, flags):
if uid < 0:
# We cannot assign a uid.
return uid
if uid in self.messagelist: # already have it
self.savemessageflags(uid, flags)
return uid
self.messagelist[uid] = {'uid': uid, 'flags': flags}
self.autosave()
return uid
def getmessageflags(self, uid):
return self.messagelist[uid]['flags']
def savemessageflags(self, uid, flags):
self.messagelist[uid]['flags'] = flags
self.autosave()
def deletemessage(self, uid):
if not uid in self.messagelist:
return
del(self.messagelist[uid])
self.autosave()

View File

@ -1,220 +0,0 @@
# Maildir folder support
# Copyright (C) 2002 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from Base import BaseFolder
from offlineimap import imaputil
from offlineimap.ui import UIBase
from threading import Lock
import os.path, os, re, time, socket, md5
foldermatchre = re.compile(',FMD5=([0-9a-f]{32})')
uidmatchre = re.compile(',U=(\d+)')
flagmatchre = re.compile(':.*2,([A-Z]+)')
timeseq = 0
lasttime = long(0)
timelock = Lock()
def gettimeseq():
global lasttime, timeseq, timelock
timelock.acquire()
try:
thistime = long(time.time())
if thistime == lasttime:
timeseq += 1
return (thistime, timeseq)
else:
lasttime = thistime
timeseq = 0
return (thistime, timeseq)
finally:
timelock.release()
class MaildirFolder(BaseFolder):
def __init__(self, root, name, sep, repository, accountname):
self.name = name
self.root = root
self.sep = sep
self.messagelist = None
self.repository = repository
self.accountname = accountname
BaseFolder.__init__(self)
def getaccountname(self):
return self.accountname
def getfullname(self):
return os.path.join(self.getroot(), self.getname())
def getuidvalidity(self):
"""Maildirs have no notion of uidvalidity, so we just return a magic
token."""
return 42
def _scanfolder(self):
"""Cache the message list. Maildir flags are:
R (replied)
S (seen)
T (trashed)
D (draft)
F (flagged)
and must occur in ASCII order."""
retval = {}
files = []
nouidcounter = -1 # Messages without UIDs get
# negative UID numbers.
for dirannex in ['new', 'cur']:
fulldirname = os.path.join(self.getfullname(), dirannex)
files.extend([os.path.join(fulldirname, filename) for
filename in os.listdir(fulldirname)])
for file in files:
messagename = os.path.basename(file)
foldermatch = foldermatchre.search(messagename)
if (not foldermatch) or \
md5.new(self.getvisiblename()).hexdigest() \
!= foldermatch.group(1):
# If there is no folder MD5 specified, or if it mismatches,
# assume it is a foreign (new) message and generate a
# negative uid for it
uid = nouidcounter
nouidcounter -= 1
else: # It comes from our folder.
uidmatch = uidmatchre.search(messagename)
uid = None
if not uidmatch:
uid = nouidcounter
nouidcounter -= 1
else:
uid = long(uidmatch.group(1))
flagmatch = flagmatchre.search(messagename)
flags = []
if flagmatch:
flags = [x for x in flagmatch.group(1)]
flags.sort()
retval[uid] = {'uid': uid,
'flags': flags,
'filename': file}
return retval
def cachemessagelist(self):
self.messagelist = self._scanfolder()
def getmessagelist(self):
return self.messagelist
def getmessage(self, uid):
filename = self.messagelist[uid]['filename']
file = open(filename, 'rt')
retval = file.read()
file.close()
return retval.replace("\r\n", "\n")
def savemessage(self, uid, content, flags):
ui = UIBase.getglobalui()
ui.debug('maildir', 'savemessage: called to write with flags %s and content %s' % \
(repr(flags), repr(content)))
if uid < 0:
# We cannot assign a new uid.
return uid
if uid in self.messagelist:
# We already have it.
self.savemessageflags(uid, flags)
return uid
if 'S' in flags:
# If a message has been seen, it goes into the cur
# directory. CR debian#152482, [complete.org #4]
newdir = os.path.join(self.getfullname(), 'cur')
else:
newdir = os.path.join(self.getfullname(), 'new')
tmpdir = os.path.join(self.getfullname(), 'tmp')
messagename = None
attempts = 0
while 1:
if attempts > 15:
raise IOError, "Couldn't write to file %s" % messagename
timeval, timeseq = gettimeseq()
messagename = '%d_%d.%d.%s,U=%d,FMD5=%s' % \
(timeval,
timeseq,
os.getpid(),
socket.gethostname(),
uid,
md5.new(self.getvisiblename()).hexdigest())
if os.path.exists(os.path.join(tmpdir, messagename)):
time.sleep(2)
attempts += 1
else:
break
tmpmessagename = messagename.split(',')[0]
ui.debug('maildir', 'savemessage: using temporary name %s' % tmpmessagename)
file = open(os.path.join(tmpdir, tmpmessagename), "wt")
file.write(content)
file.close()
ui.debug('maildir', 'savemessage: moving from %s to %s' % \
(tmpmessagename, messagename))
os.link(os.path.join(tmpdir, tmpmessagename),
os.path.join(newdir, messagename))
os.unlink(os.path.join(tmpdir, tmpmessagename))
self.messagelist[uid] = {'uid': uid, 'flags': [],
'filename': os.path.join(newdir, messagename)}
self.savemessageflags(uid, flags)
ui.debug('maildir', 'savemessage: returning uid %d' % uid)
return uid
def getmessageflags(self, uid):
return self.messagelist[uid]['flags']
def savemessageflags(self, uid, flags):
oldfilename = self.messagelist[uid]['filename']
newpath, newname = os.path.split(oldfilename)
if 'S' in flags:
# If a message has been seen, it goes into the cur
# directory. CR debian#152482, [complete.org #4]
newpath = os.path.join(self.getfullname(), 'cur')
else:
newpath = os.path.join(self.getfullname(), 'new')
infostr = ':'
infomatch = re.search('(:.*)$', newname)
if infomatch: # If the info string is present..
infostr = infomatch.group(1)
newname = newname.split(':')[0] # Strip off the info string.
infostr = re.sub('2,[A-Z]*', '', infostr)
flags.sort()
infostr += '2,' + ''.join(flags)
newname += infostr
newfilename = os.path.join(newpath, newname)
if (newfilename != oldfilename):
os.rename(oldfilename, newfilename)
self.messagelist[uid]['flags'] = flags
self.messagelist[uid]['filename'] = newfilename
def deletemessage(self, uid):
if not uid in self.messagelist:
return
filename = self.messagelist[uid]['filename']
try:
os.unlink(filename)
except OSError:
# Can't find the file -- maybe already deleted?
newmsglist = self._scanfolder()
if uid in newmsglist: # Nope, try new filename.
os.unlink(newmsglist[uid]['filename'])
# Yep -- return.
del(self.messagelist[uid])

View File

@ -1,2 +0,0 @@
import Base, IMAP, Maildir, LocalStatus

File diff suppressed because it is too large Load Diff

View File

@ -1,309 +0,0 @@
# IMAP server support
# Copyright (C) 2002, 2003 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from offlineimap import imaplib, imaputil, threadutil
from offlineimap.ui import UIBase
from threading import *
import thread, hmac, os
class UsefulIMAPMixIn:
def getstate(self):
return self.state
def getselectedfolder(self):
if self.getstate() == 'SELECTED':
return self.selectedfolder
return None
def select(self, mailbox='INBOX', readonly=None, force = 0):
if (not force) and self.getselectedfolder() == mailbox:
self.is_readonly = readonly
# No change; return.
return
result = self.__class__.__bases__[1].select(self, mailbox, readonly)
if result[0] != 'OK':
raise ValueError, "Error from select: %s" % str(result)
if self.getstate() == 'SELECTED':
self.selectedfolder = mailbox
else:
self.selectedfolder = None
class UsefulIMAP4(UsefulIMAPMixIn, imaplib.IMAP4): pass
class UsefulIMAP4_SSL(UsefulIMAPMixIn, imaplib.IMAP4_SSL): pass
class UsefulIMAP4_Tunnel(UsefulIMAPMixIn, imaplib.IMAP4_Tunnel): pass
class IMAPServer:
def __init__(self, config, reposname,
username = None, password = None, hostname = None,
port = None, ssl = 1, maxconnections = 1, tunnel = None,
reference = '""'):
self.reposname = reposname
self.config = config
self.username = username
self.password = password
self.passworderror = None
self.hostname = hostname
self.tunnel = tunnel
self.port = port
self.usessl = ssl
self.delim = None
self.root = None
if port == None:
if ssl:
self.port = 993
else:
self.port = 143
self.maxconnections = maxconnections
self.availableconnections = []
self.assignedconnections = []
self.lastowner = {}
self.semaphore = BoundedSemaphore(self.maxconnections)
self.connectionlock = Lock()
self.reference = reference
def getpassword(self):
if self.password != None and self.passworderror == None:
return self.password
self.password = UIBase.getglobalui().getpass(self.reposname,
self.config,
self.passworderror)
self.passworderror = None
return self.password
def getdelim(self):
"""Returns this server's folder delimiter. Can only be called
after one or more calls to acquireconnection."""
return self.delim
def getroot(self):
"""Returns this server's folder root. Can only be called after one
or more calls to acquireconnection."""
return self.root
def releaseconnection(self, connection):
self.connectionlock.acquire()
self.assignedconnections.remove(connection)
self.availableconnections.append(connection)
self.connectionlock.release()
self.semaphore.release()
def md5handler(self, response):
ui = UIBase.getglobalui()
challenge = response.strip()
ui.debug('imap', 'md5handler: got challenge %s' % challenge)
passwd = self.getpassword()
retval = self.username + ' ' + hmac.new(passwd, challenge).hexdigest()
ui.debug('imap', 'md5handler: returning %s' % retval)
return retval
def plainauth(self, imapobj):
UIBase.getglobalui().debug('imap',
'Attempting plain authentication')
imapobj.login(self.username, self.getpassword())
def acquireconnection(self):
"""Fetches a connection from the pool, making sure to create a new one
if needed, to obey the maximum connection limits, etc.
Opens a connection to the server and returns an appropriate
object."""
self.semaphore.acquire()
self.connectionlock.acquire()
imapobj = None
if len(self.availableconnections): # One is available.
# Try to find one that previously belonged to this thread
# as an optimization. Start from the back since that's where
# they're popped on.
threadid = thread.get_ident()
imapobj = None
for i in range(len(self.availableconnections) - 1, -1, -1):
tryobj = self.availableconnections[i]
if self.lastowner[tryobj] == threadid:
imapobj = tryobj
del(self.availableconnections[i])
break
if not imapobj:
imapobj = self.availableconnections[0]
del(self.availableconnections[0])
self.assignedconnections.append(imapobj)
self.lastowner[imapobj] = thread.get_ident()
self.connectionlock.release()
return imapobj
self.connectionlock.release() # Release until need to modify data
success = 0
while not success:
# Generate a new connection.
if self.tunnel:
UIBase.getglobalui().connecting('tunnel', self.tunnel)
imapobj = UsefulIMAP4_Tunnel(self.tunnel)
success = 1
elif self.usessl:
UIBase.getglobalui().connecting(self.hostname, self.port)
imapobj = UsefulIMAP4_SSL(self.hostname, self.port)
else:
UIBase.getglobalui().connecting(self.hostname, self.port)
imapobj = UsefulIMAP4(self.hostname, self.port)
if not self.tunnel:
try:
if 'AUTH=CRAM-MD5' in imapobj.capabilities:
UIBase.getglobalui().debug('imap',
'Attempting CRAM-MD5 authentication')
try:
imapobj.authenticate('CRAM-MD5', self.md5handler)
except imapobj.error, val:
self.plainauth(imapobj)
else:
self.plainauth(imapobj)
# Would bail by here if there was a failure.
success = 1
except imapobj.error, val:
self.passworderror = str(val)
self.password = None
if self.delim == None:
listres = imapobj.list(self.reference, '""')[1]
if listres == [None] or listres == None:
# Some buggy IMAP servers do not respond well to LIST "" ""
# Work around them.
listres = imapobj.list(self.reference, '"*"')[1]
self.delim, self.root = \
imaputil.imapsplit(listres[0])[1:]
self.delim = imaputil.dequote(self.delim)
self.root = imaputil.dequote(self.root)
self.connectionlock.acquire()
self.assignedconnections.append(imapobj)
self.lastowner[imapobj] = thread.get_ident()
self.connectionlock.release()
return imapobj
def connectionwait(self):
"""Waits until there is a connection available. Note that between
the time that a connection becomes available and the time it is
requested, another thread may have grabbed it. This function is
mainly present as a way to avoid spawning thousands of threads
to copy messages, then have them all wait for 3 available connections.
It's OK if we have maxconnections + 1 or 2 threads, which is what
this will help us do."""
threadutil.semaphorewait(self.semaphore)
def close(self):
# Make sure I own all the semaphores. Let the threads finish
# their stuff. This is a blocking method.
self.connectionlock.acquire()
threadutil.semaphorereset(self.semaphore, self.maxconnections)
for imapobj in self.assignedconnections + self.availableconnections:
imapobj.logout()
self.assignedconnections = []
self.availableconnections = []
self.lastowner = {}
self.connectionlock.release()
def keepalive(self, timeout, event):
"""Sends a NOOP to each connection recorded. It will wait a maximum
of timeout seconds between doing this, and will continue to do so
until the Event object as passed is true. This method is expected
to be invoked in a separate thread, which should be join()'d after
the event is set."""
ui = UIBase.getglobalui()
ui.debug('imap', 'keepalive thread started')
while 1:
ui.debug('imap', 'keepalive: top of loop')
event.wait(timeout)
ui.debug('imap', 'keepalive: after wait')
if event.isSet():
ui.debug('imap', 'keepalive: event is set; exiting')
return
ui.debug('imap', 'keepalive: acquiring connectionlock')
self.connectionlock.acquire()
numconnections = len(self.assignedconnections) + \
len(self.availableconnections)
self.connectionlock.release()
ui.debug('imap', 'keepalive: connectionlock released')
threads = []
imapobjs = []
for i in range(numconnections):
ui.debug('imap', 'keepalive: processing connection %d of %d' % (i, numconnections))
imapobj = self.acquireconnection()
ui.debug('imap', 'keepalive: connection %d acquired' % i)
imapobjs.append(imapobj)
thr = threadutil.ExitNotifyThread(target = imapobj.noop)
thr.setDaemon(1)
thr.start()
threads.append(thr)
ui.debug('imap', 'keepalive: thread started')
ui.debug('imap', 'keepalive: joining threads')
for thr in threads:
# Make sure all the commands have completed.
thr.join()
ui.debug('imap', 'keepalive: releasing connections')
for imapobj in imapobjs:
self.releaseconnection(imapobj)
ui.debug('imap', 'keepalive: bottom of loop')
class ConfigedIMAPServer(IMAPServer):
"""This class is designed for easier initialization given a ConfigParser
object and an account name. The passwordhash is used if
passwords for certain accounts are known. If the password for this
account is listed, it will be obtained from there."""
def __init__(self, repository, passwordhash = {}):
"""Initialize the object. If the account is not a tunnel,
the password is required."""
self.repos = repository
self.config = self.repos.getconfig()
usetunnel = self.repos.getpreauthtunnel()
if not usetunnel:
host = self.repos.gethost()
user = self.repos.getuser()
port = self.repos.getport()
ssl = self.repos.getssl()
reference = self.repos.getreference()
server = None
password = None
if repository.getname() in passwordhash:
password = passwordhash[repository.getname()]
# Connect to the remote server.
if usetunnel:
IMAPServer.__init__(self, self.config, self.repos.getname(),
tunnel = usetunnel,
reference = reference,
maxconnections = self.repos.getmaxconnections())
else:
if not password:
password = self.repos.getpassword()
IMAPServer.__init__(self, self.config, self.repos.getname(),
user, password, host, port, ssl,
self.repos.getmaxconnections(),
reference = reference)

View File

@ -1,211 +0,0 @@
# IMAP utility module
# Copyright (C) 2002 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import re, string, types
from offlineimap.ui import UIBase
quotere = re.compile('^("(?:[^"]|\\\\")*")')
def debug(*args):
msg = []
for arg in args:
msg.append(str(arg))
UIBase.getglobalui().debug('imap', " ".join(msg))
def dequote(string):
"""Takes a string which may or may not be quoted and returns it, unquoted.
This function does NOT consider parenthised lists to be quoted.
"""
debug("dequote() called with input:", string)
if not (string[0] == '"' and string[-1] == '"'):
return string
string = string[1:-1] # Strip off quotes.
string = string.replace('\\"', '"')
string = string.replace('\\\\', '\\')
debug("dequote() returning:", string)
return string
def flagsplit(string):
if string[0] != '(' or string[-1] != ')':
raise ValueError, "Passed string '%s' is not a flag list" % string
return imapsplit(string[1:-1])
def options2hash(list):
debug("options2hash called with input:", list)
retval = {}
counter = 0
while (counter < len(list)):
retval[list[counter]] = list[counter + 1]
counter += 2
debug("options2hash returning:", retval)
return retval
def flags2hash(string):
return options2hash(flagsplit(string))
def imapsplit(imapstring):
"""Takes a string from an IMAP conversation and returns a list containing
its components. One example string is:
(\\HasNoChildren) "." "INBOX.Sent"
The result from parsing this will be:
['(\\HasNoChildren)', '"."', '"INBOX.Sent"']"""
debug("imapsplit() called with input:", imapstring)
if type(imapstring) != types.StringType:
debug("imapsplit() got a non-string input; working around.")
# Sometimes, imaplib will throw us a tuple if the input
# contains a literal. See Python bug
# #619732 at https://sourceforge.net/tracker/index.php?func=detail&aid=619732&group_id=5470&atid=105470
# One example is:
# result[0] = '() "\\\\" Admin'
# result[1] = ('() "\\\\" {19}', 'Folder\\2')
#
# This function will effectively get result[0] or result[1], so
# if we get the result[1] version, we need to parse apart the tuple
# and figure out what to do with it. Each even-numbered
# part of it should end with the {} number, and each odd-numbered
# part should be directly a part of the result. We'll
# artificially quote it to help out.
retval = []
for i in range(len(imapstring)):
if i % 2: # Odd: quote then append.
arg = imapstring[i]
# Quote code lifted from imaplib
arg = arg.replace('\\', '\\\\')
arg = arg.replace('"', '\\"')
arg = '"%s"' % arg
debug("imapsplit() non-string [%d]: Appending %s" %\
(i, arg))
retval.append(arg)
else:
# Even -- we have a string that ends with a literal
# size specifier. We need to strip off that, then run
# what remains through the regular imapsplit parser.
# Recursion to the rescue.
arg = imapstring[i]
arg = re.sub('\{\d+\}$', '', arg)
debug("imapsplit() non-string [%d]: Feeding %s to recursion" %\
(i, arg))
retval.extend(imapsplit(arg))
debug("imapsplit() non-string: returning %s" % str(retval))
return retval
workstr = imapstring.strip()
retval = []
while len(workstr):
if workstr[0] == '(':
rparenc = 1 # count of right parenthesis to match
rpareni = 1 # position to examine
while rparenc: # Find the end of the group.
if workstr[rpareni] == ')': # end of a group
rparenc -= 1
elif workstr[rpareni] == '(': # start of a group
rparenc += 1
rpareni += 1 # Move to next character.
parenlist = workstr[0:rpareni]
workstr = workstr[rpareni:].lstrip()
retval.append(parenlist)
elif workstr[0] == '"':
quotelist = quotere.search(workstr).group(1)
workstr = workstr[len(quotelist):].lstrip()
retval.append(quotelist)
else:
splits = string.split(workstr, maxsplit = 1)
splitslen = len(splits)
# The unquoted word is splits[0]; the remainder is splits[1]
if splitslen == 2:
# There's an unquoted word, and more string follows.
retval.append(splits[0])
workstr = splits[1] # split will have already lstripped it
continue
elif splitslen == 1:
# We got a last unquoted word, but nothing else
retval.append(splits[0])
# Nothing remains. workstr would be ''
break
elif splitslen == 0:
# There was not even an unquoted word.
break
debug("imapsplit() returning:", retval)
return retval
def flagsimap2maildir(flagstring):
flagmap = {'\\seen': 'S',
'\\answered': 'R',
'\\flagged': 'F',
'\\deleted': 'T',
'\\draft': 'D'}
retval = []
imapflaglist = [x.lower() for x in flagstring[1:-1].split()]
for imapflag in imapflaglist:
if flagmap.has_key(imapflag):
retval.append(flagmap[imapflag])
retval.sort()
return retval
def flagsmaildir2imap(list):
flagmap = {'S': '\\Seen',
'R': '\\Answered',
'F': '\\Flagged',
'T': '\\Deleted',
'D': '\\Draft'}
retval = []
for mdflag in list:
if flagmap.has_key(mdflag):
retval.append(flagmap[mdflag])
retval.sort()
return '(' + ' '.join(retval) + ')'
def listjoin(list):
start = None
end = None
retval = []
def getlist(start, end):
if start == end:
return(str(start))
else:
return(str(start) + ":" + str(end))
for item in list:
if start == None:
# First item.
start = item
end = item
elif item == end + 1:
# An addition to the list.
end = item
else:
# Here on: starting a new list.
retval.append(getlist(start, end))
start = item
end = item
if start != None:
retval.append(getlist(start, end))
return ",".join(retval)

View File

@ -1,151 +0,0 @@
# OfflineIMAP initialization code
# Copyright (C) 2002, 2003 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from offlineimap import imaplib, imapserver, repository, folder, mbnames, threadutil, version, syncmaster, accounts
from offlineimap.localeval import LocalEval
from offlineimap.threadutil import InstanceLimitedThread, ExitNotifyThread
from offlineimap.ui import UIBase
import re, os, os.path, offlineimap, sys
from offlineimap.CustomConfig import CustomConfigParser
from threading import *
import threading
from getopt import getopt
try:
import fcntl
hasfcntl = 1
except:
hasfcntl = 0
lockfd = None
def lock(config, ui):
global lockfd, hasfcntl
if not hasfcntl:
return
lockfd = open(config.getmetadatadir() + "/lock", "w")
try:
fcntl.flock(lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except IOError:
ui.locked()
ui.terminate(1)
def startup(versionno):
assert versionno == version.versionstr, "Revision of main program (%s) does not match that of library (%s). Please double-check your PYTHONPATH and installation locations." % (versionno, version.versionstr)
options = {}
if '--help' in sys.argv[1:]:
sys.stdout.write(version.cmdhelp + "\n")
sys.exit(0)
for optlist in getopt(sys.argv[1:], 'P:1oa:c:d:l:u:h')[0]:
options[optlist[0]] = optlist[1]
if options.has_key('-h'):
sys.stdout.write(version.cmdhelp)
sys.stdout.write("\n")
sys.exit(0)
configfilename = os.path.expanduser("~/.offlineimaprc")
if options.has_key('-c'):
configfilename = options['-c']
if options.has_key('-P'):
if not options.has_key('-1'):
sys.stderr.write("FATAL: profile mode REQUIRES -1\n")
sys.exit(100)
profiledir = options['-P']
os.mkdir(profiledir)
threadutil.setprofiledir(profiledir)
sys.stderr.write("WARNING: profile mode engaged;\nPotentially large data will be created in " + profiledir + "\n")
config = CustomConfigParser()
if not os.path.exists(configfilename):
sys.stderr.write(" *** Config file %s does not exist; aborting!\n" % configfilename)
sys.exit(1)
config.read(configfilename)
ui = offlineimap.ui.detector.findUI(config, options.get('-u'))
UIBase.setglobalui(ui)
if options.has_key('-l'):
ui.setlogfd(open(options['-l'], 'wt'))
ui.init_banner()
if options.has_key('-d'):
for debugtype in options['-d'].split(','):
ui.add_debug(debugtype.strip())
if debugtype == 'imap':
imaplib.Debug = 5
if debugtype == 'thread':
threading._VERBOSE = 1
if options.has_key('-o'):
# FIXME: maybe need a better
for section in accounts.getaccountlist(config):
config.remove_option('Account ' + section, "autorefresh")
lock(config, ui)
if options.has_key('-l'):
sys.stderr = ui.logfile
activeaccounts = config.get("general", "accounts")
if options.has_key('-a'):
activeaccounts = options['-a']
activeaccounts = activeaccounts.replace(" ", "")
activeaccounts = activeaccounts.split(",")
allaccounts = accounts.AccountHashGenerator(config)
syncaccounts = {}
for account in activeaccounts:
syncaccounts[account] = allaccounts[account]
server = None
remoterepos = None
localrepos = None
if options.has_key('-1'):
threadutil.initInstanceLimit("ACCOUNTLIMIT", 1)
else:
threadutil.initInstanceLimit("ACCOUNTLIMIT",
config.getdefaultint("general", "maxsyncaccounts", 1))
for reposname in config.getsectionlist('Repository'):
for instancename in ["FOLDER_" + reposname,
"MSGCOPY_" + reposname]:
if options.has_key('-1'):
threadutil.initInstanceLimit(instancename, 1)
else:
threadutil.initInstanceLimit(instancename,
config.getdefaultint('Repository ' + reposname, "maxconnections", 1))
threadutil.initexitnotify()
t = ExitNotifyThread(target=syncmaster.syncitall,
name='Sync Runner',
kwargs = {'accounts': syncaccounts,
'config': config})
t.setDaemon(1)
t.start()
try:
threadutil.exitnotifymonitorloop(threadutil.threadexited)
except SystemExit:
raise
except:
ui.mainException() # Also expected to terminate.

View File

@ -1,45 +0,0 @@
"""Eval python code with global namespace of a python source file."""
# Copyright (C) 2002 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import imp
try:
import errno
except:
pass
class LocalEval:
def __init__(self, path=None):
self.namespace={}
if path is not None:
file=open(path, 'r')
module=imp.load_module(
'<none>',
file,
path,
('', 'r', imp.PY_SOURCE))
for attr in dir(module):
self.namespace[attr]=getattr(module, attr)
def eval(self, text, namespace=None):
names = {}
names.update(self.namespace)
if namespace is not None:
names.update(namespace)
return eval(text, names)

View File

@ -1,74 +0,0 @@
# Mailbox name generator
# Copyright (C) 2002 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import os.path
import re # for folderfilter
from threading import *
boxes = {}
config = None
accounts = None
mblock = Lock()
def init(conf, accts):
global config, accounts
config = conf
accounts = accts
def add(accountname, foldername):
if not accountname in boxes:
boxes[accountname] = []
if not foldername in boxes[accountname]:
boxes[accountname].append(foldername)
def write():
# See if we're ready to write it out.
for account in accounts:
if account not in boxes:
return
genmbnames()
def genmbnames():
"""Takes a configparser object and a boxlist, which is a list of hashes
containing 'accountname' and 'foldername' keys."""
mblock.acquire()
try:
localeval = config.getlocaleval()
if not config.getdefaultboolean("mbnames", "enabled", 0):
return
file = open(os.path.expanduser(config.get("mbnames", "filename")), "wt")
file.write(localeval.eval(config.get("mbnames", "header")))
folderfilter = lambda accountname, foldername: 1
if config.has_option("mbnames", "folderfilter"):
folderfilter = localeval.eval(config.get("mbnames", "folderfilter"),
{'re': re})
itemlist = []
for accountname in boxes.keys():
for foldername in boxes[accountname]:
if folderfilter(accountname, foldername):
itemlist.append(config.get("mbnames", "peritem", raw=1) % \
{'accountname': accountname,
'foldername': foldername})
file.write(localeval.eval(config.get("mbnames", "sep")).join(itemlist))
file.write(localeval.eval(config.get("mbnames", "footer")))
file.close()
finally:
mblock.release()

View File

@ -1,145 +0,0 @@
# Base repository support
# Copyright (C) 2002, 2003 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from offlineimap import CustomConfig
import os.path
def LoadRepository(name, account, reqtype):
from offlineimap.repository.IMAP import IMAPRepository, MappedIMAPRepository
from offlineimap.repository.Maildir import MaildirRepository
if reqtype == 'remote':
# For now, we don't support Maildirs on the remote side.
typemap = {'IMAP': IMAPRepository}
elif reqtype == 'local':
typemap = {'IMAP': MappedIMAPRepository,
'Maildir': MaildirRepository}
else:
raise ValueError, "Request type %s not supported" % reqtype
config = account.getconfig()
repostype = config.get('Repository ' + name, 'type').strip()
return typemap[repostype](name, account)
class BaseRepository(CustomConfig.ConfigHelperMixin):
def __init__(self, reposname, account):
self.account = account
self.config = account.getconfig()
self.name = reposname
self.localeval = account.getlocaleval()
self.accountname = self.account.getname()
self.uiddir = os.path.join(self.config.getmetadatadir(), 'Repository-' + self.name)
if not os.path.exists(self.uiddir):
os.mkdir(self.uiddir, 0700)
self.mapdir = os.path.join(self.uiddir, 'UIDMapping')
if not os.path.exists(self.mapdir):
os.mkdir(self.mapdir, 0700)
self.uiddir = os.path.join(self.uiddir, 'FolderValidity')
if not os.path.exists(self.uiddir):
os.mkdir(self.uiddir, 0700)
def holdordropconnections(self):
pass
def dropconnections(self):
pass
def getaccount(self):
return self.account
def getname(self):
return self.name
def getuiddir(self):
return self.uiddir
def getmapdir(self):
return self.mapdir
def getaccountname(self):
return self.accountname
def getsection(self):
return 'Repository ' + self.name
def getconfig(self):
return self.config
def getlocaleval(self):
return self.account.getlocaleval()
def getfolders(self):
"""Returns a list of ALL folders on this server."""
return []
def getsep(self):
raise NotImplementedError
def makefolder(self, foldername):
raise NotImplementedError
def deletefolder(self, foldername):
raise NotImplementedError
def getfolder(self, foldername):
raise NotImplementedError
def syncfoldersto(self, dest):
"""Syncs the folders in this repository to those in dest.
It does NOT sync the contents of those folders."""
src = self
srcfolders = src.getfolders()
destfolders = dest.getfolders()
# Create hashes with the names, but convert the source folders
# to the dest folder's sep.
srchash = {}
for folder in srcfolders:
srchash[folder.getvisiblename().replace(src.getsep(), dest.getsep())] = \
folder
desthash = {}
for folder in destfolders:
desthash[folder.getvisiblename()] = folder
#
# Find new folders.
#
for key in srchash.keys():
if not key in desthash:
dest.makefolder(key)
#
# Find deleted folders.
#
# We don't delete folders right now.
#for key in desthash.keys():
# if not key in srchash:
# dest.deletefolder(key)
##### Keepalive
def startkeepalive(self):
"""The default implementation will do nothing."""
pass
def stopkeepalive(self, abrupt = 0):
"""Stop keep alive. If abrupt is 1, stop it but don't bother waiting
for the threads to terminate."""
pass

View File

@ -1,191 +0,0 @@
# IMAP repository support
# Copyright (C) 2002 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from Base import BaseRepository
from offlineimap import folder, imaputil, imapserver
from offlineimap.folder.UIDMaps import MappedIMAPFolder
from offlineimap.threadutil import ExitNotifyThread
import re, types, os
from threading import *
class IMAPRepository(BaseRepository):
def __init__(self, reposname, account):
"""Initialize an IMAPRepository object."""
BaseRepository.__init__(self, reposname, account)
self.imapserver = imapserver.ConfigedIMAPServer(self)
self.folders = None
self.nametrans = lambda foldername: foldername
self.folderfilter = lambda foldername: 1
self.folderincludes = []
self.foldersort = cmp
localeval = self.localeval
if self.config.has_option(self.getsection(), 'nametrans'):
self.nametrans = localeval.eval(self.getconf('nametrans'),
{'re': re})
if self.config.has_option(self.getsection(), 'folderfilter'):
self.folderfilter = localeval.eval(self.getconf('folderfilter'),
{'re': re})
if self.config.has_option(self.getsection(), 'folderincludes'):
self.folderincludes = localeval.eval(self.getconf('folderincludes'),
{'re': re})
if self.config.has_option(self.getsection(), 'foldersort'):
self.foldersort = localeval.eval(self.getconf('foldersort'),
{'re': re})
def startkeepalive(self):
keepalivetime = self.getkeepalive()
if not keepalivetime: return
self.kaevent = Event()
self.kathread = ExitNotifyThread(target = self.imapserver.keepalive,
name = "Keep alive " + self.getname(),
args = (keepalivetime, self.kaevent))
self.kathread.setDaemon(1)
self.kathread.start()
def stopkeepalive(self, abrupt = 0):
if not hasattr(self, 'kaevent'):
# Keepalive is not active.
return
self.kaevent.set()
if not abrupt:
self.kathread.join()
del self.kathread
del self.kaevent
def holdordropconnections(self):
if not self.getholdconnectionopen():
self.dropconnections()
def dropconnections(self):
self.imapserver.close()
def getholdconnectionopen(self):
return self.getconfboolean("holdconnectionopen", 0)
def getkeepalive(self):
return self.getconfint("keepalive", 0)
def getsep(self):
return self.imapserver.delim
def gethost(self):
return self.getconf('remotehost')
def getuser(self):
return self.getconf('remoteuser')
def getport(self):
return self.getconfint('remoteport', None)
def getssl(self):
return self.getconfboolean('ssl', 0)
def getpreauthtunnel(self):
return self.getconf('preauthtunnel', None)
def getreference(self):
return self.getconf('reference', '""')
def getmaxconnections(self):
return self.getconfint('maxconnections', 1)
def getexpunge(self):
return self.getconfboolean('expunge', 1)
def getpassword(self):
password = self.getconf('remotepass', None)
if password != None:
return password
passfile = self.getconf('remotepassfile', None)
if passfile != None:
fd = open(os.path.expanduser(passfile))
password = fd.readline().strip()
fd.close()
return password
return None
def getfolder(self, foldername):
return self.getfoldertype()(self.imapserver, foldername,
self.nametrans(foldername),
self.accountname, self)
def getfoldertype(self):
return folder.IMAP.IMAPFolder
def getfolders(self):
if self.folders != None:
return self.folders
retval = []
imapobj = self.imapserver.acquireconnection()
try:
listresult = imapobj.list(directory = self.imapserver.reference)[1]
finally:
self.imapserver.releaseconnection(imapobj)
for string in listresult:
if string == None or \
(type(string) == types.StringType and string == ''):
# Bug in imaplib: empty strings in results from
# literals.
continue
flags, delim, name = imaputil.imapsplit(string)
flaglist = [x.lower() for x in imaputil.flagsplit(flags)]
if '\\noselect' in flaglist:
continue
foldername = imaputil.dequote(name)
if not self.folderfilter(foldername):
continue
retval.append(self.getfoldertype()(self.imapserver, foldername,
self.nametrans(foldername),
self.accountname, self))
if len(self.folderincludes):
imapobj = self.imapserver.acquireconnection()
try:
for foldername in self.folderincludes:
try:
imapobj.select(foldername, readonly = 1)
except ValueError:
continue
retval.append(self.getfoldertype()(self.imapserver,
foldername,
self.nametrans(foldername),
self.accountname, self))
finally:
self.imapserver.releaseconnection(imapobj)
retval.sort(lambda x, y: self.foldersort(x.getvisiblename(), y.getvisiblename()))
self.folders = retval
return retval
def makefolder(self, foldername):
#if self.getreference() != '""':
# newname = self.getreference() + self.getsep() + foldername
#else:
# newname = foldername
newname = foldername
imapobj = self.imapserver.acquireconnection()
try:
result = imapobj.create(newname)
if result[0] != 'OK':
raise RuntimeError, "Repository %s could not create folder %s: %s" % (self.getname(), foldername, str(result))
finally:
self.imapserver.releaseconnection(imapobj)
class MappedIMAPRepository(IMAPRepository):
def getfoldertype(self):
return MappedIMAPFolder

View File

@ -1,60 +0,0 @@
# Local status cache repository support
# Copyright (C) 2002 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from Base import BaseRepository
from offlineimap import folder
import os, re
class LocalStatusRepository(BaseRepository):
def __init__(self, reposname, account):
BaseRepository.__init__(self, reposname, account)
self.directory = os.path.join(account.getaccountmeta(), 'LocalStatus')
if not os.path.exists(self.directory):
os.mkdir(self.directory, 0700)
self.folders = None
def getsep(self):
return '.'
def getfolderfilename(self, foldername):
foldername = re.sub('/\.$', '/dot', foldername)
foldername = re.sub('^\.$', 'dot', foldername)
return os.path.join(self.directory, foldername)
def makefolder(self, foldername):
# "touch" the file.
file = open(self.getfolderfilename(foldername), "ab")
file.close()
# Invalidate the cache.
self.folders = None
def getfolders(self):
retval = []
for folder in os.listdir(self.directory):
retval.append(folder.LocalStatus.LocalStatusFolder(self.directory,
folder, self, self.accountname))
return retval
def getfolder(self, foldername):
return folder.LocalStatus.LocalStatusFolder(self.directory, foldername,
self, self.accountname)

View File

@ -1,146 +0,0 @@
# Maildir repository support
# Copyright (C) 2002 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from Base import BaseRepository
from offlineimap import folder, imaputil
from offlineimap.ui import UIBase
from mailbox import Maildir
import os
class MaildirRepository(BaseRepository):
def __init__(self, reposname, account):
"""Initialize a MaildirRepository object. Takes a path name
to the directory holding all the Maildir directories."""
BaseRepository.__init__(self, reposname, account)
self.root = self.getlocalroot()
self.folders = None
self.ui = UIBase.getglobalui()
self.debug("MaildirRepository initialized, sep is " + repr(self.getsep()))
def getlocalroot(self):
return os.path.expanduser(self.getconf('localfolders'))
def debug(self, msg):
self.ui.debug('maildir', msg)
def getsep(self):
return self.getconf('sep', '.').strip()
def makefolder(self, foldername):
self.debug("makefolder called with arg " + repr(foldername))
# Do the chdir thing so the call to makedirs does not make the
# self.root directory (we'd prefer to raise an error in that case),
# but will make the (relative) paths underneath it. Need to use
# makedirs to support a / separator.
if self.getsep() == '/':
for invalid in ['new', 'cur', 'tmp', 'offlineimap.uidvalidity']:
for component in foldername.split('/'):
assert component != invalid, "When using nested folders (/ as a separator in the account config), your folder names may not contain 'new', 'cur', 'tmp', or 'offlineimap.uidvalidity'."
assert foldername.find('./') == -1, "Folder names may not contain ../"
assert not foldername.startswith('/'), "Folder names may not begin with /"
oldcwd = os.getcwd()
os.chdir(self.root)
# If we're using hierarchical folders, it's possible that sub-folders
# may be created before higher-up ones. If this is the case,
# makedirs will fail because the higher-up dir already exists.
# So, check to see if this is indeed the case.
if (self.getsep() == '/' or self.getconfboolean('existsok', 0)) \
and os.path.isdir(foldername):
self.debug("makefolder: %s already is a directory" % foldername)
# Already exists. Sanity-check that it's not a Maildir.
for subdir in ['cur', 'new', 'tmp']:
assert not os.path.isdir(os.path.join(foldername, subdir)), \
"Tried to create folder %s but it already had dir %s" %\
(foldername, subdir)
else:
self.debug("makefolder: calling makedirs %s" % foldername)
os.makedirs(foldername, 0700)
self.debug("makefolder: creating cur, new, tmp")
for subdir in ['cur', 'new', 'tmp']:
os.mkdir(os.path.join(foldername, subdir), 0700)
# Invalidate the cache
self.folders = None
os.chdir(oldcwd)
def deletefolder(self, foldername):
self.ui.warn("NOT YET IMPLEMENTED: DELETE FOLDER %s" % foldername)
def getfolder(self, foldername):
return folder.Maildir.MaildirFolder(self.root, foldername,
self.getsep(), self, self.accountname)
def _getfolders_scandir(self, root, extension = None):
self.debug("_GETFOLDERS_SCANDIR STARTING. root = %s, extension = %s" \
% (root, extension))
# extension willl only be non-None when called recursively when
# getsep() returns '/'.
retval = []
# Configure the full path to this repository -- "toppath"
if extension == None:
toppath = root
else:
toppath = os.path.join(root, extension)
self.debug(" toppath = %s" % toppath)
# Iterate over directories in top.
for dirname in os.listdir(toppath) + ['.']:
self.debug(" *** top of loop")
self.debug(" dirname = %s" % dirname)
if dirname in ['cur', 'new', 'tmp', 'offlineimap.uidvalidity']:
self.debug(" skipping this dir (Maildir special)")
# Bypass special files.
continue
fullname = os.path.join(toppath, dirname)
self.debug(" fullname = %s" % fullname)
if not os.path.isdir(fullname):
self.debug(" skipping this entry (not a directory)")
# Not a directory -- not a folder.
continue
foldername = dirname
if extension != None:
foldername = os.path.join(extension, dirname)
if (os.path.isdir(os.path.join(fullname, 'cur')) and
os.path.isdir(os.path.join(fullname, 'new')) and
os.path.isdir(os.path.join(fullname, 'tmp'))):
# This directory has maildir stuff -- process
self.debug(" This is a maildir folder.")
self.debug(" foldername = %s" % foldername)
retval.append(folder.Maildir.MaildirFolder(self.root, foldername,
self.getsep(), self, self.accountname))
if self.getsep() == '/' and dirname != '.':
# Check sub-directories for folders.
retval.extend(self._getfolders_scandir(root, foldername))
self.debug("_GETFOLDERS_SCANDIR RETURNING %s" % \
repr([x.getname() for x in retval]))
return retval
def getfolders(self):
if self.folders == None:
self.folders = self._getfolders_scandir(self.root)
return self.folders

View File

@ -1 +0,0 @@
__all__ = ['IMAP', 'Base', 'Maildir', 'LocalStatus']

View File

@ -1,45 +0,0 @@
# OfflineIMAP synchronization master code
# Copyright (C) 2002 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from offlineimap import imaplib, imapserver, repository, folder, mbnames, threadutil, version
from offlineimap.threadutil import InstanceLimitedThread, ExitNotifyThread
import offlineimap.accounts
from offlineimap.accounts import SyncableAccount
from offlineimap.ui import UIBase
import re, os, os.path, offlineimap, sys
from ConfigParser import ConfigParser
from threading import *
def syncaccount(threads, config, accountname):
account = SyncableAccount(config, accountname)
thread = InstanceLimitedThread(instancename = 'ACCOUNTLIMIT',
target = account.syncrunner,
name = "Account sync %s" % accountname)
thread.setDaemon(1)
thread.start()
threads.add(thread)
def syncitall(accounts, config):
currentThread().setExitMessage('SYNC_WITH_TIMER_TERMINATE')
ui = UIBase.getglobalui()
threads = threadutil.threadlist()
mbnames.init(config, accounts)
for accountname in accounts:
syncaccount(threads, config, accountname)
# Wait for the threads to finish.
threads.reset()

View File

@ -1,291 +0,0 @@
# Copyright (C) 2002, 2003 John Goerzen
# Thread support module
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from threading import *
from StringIO import StringIO
import sys, traceback, thread, profile
from offlineimap.ui import UIBase # for getglobalui()
profiledir = None
def setprofiledir(newdir):
global profiledir
profiledir = newdir
######################################################################
# General utilities
######################################################################
def semaphorereset(semaphore, originalstate):
"""Wait until the semaphore gets back to its original state -- all acquired
resources released."""
for i in range(originalstate):
semaphore.acquire()
# Now release these.
for i in range(originalstate):
semaphore.release()
def semaphorewait(semaphore):
semaphore.acquire()
semaphore.release()
def threadsreset(threadlist):
for thr in threadlist:
thr.join()
class threadlist:
def __init__(self):
self.lock = Lock()
self.list = []
def add(self, thread):
self.lock.acquire()
try:
self.list.append(thread)
finally:
self.lock.release()
def remove(self, thread):
self.lock.acquire()
try:
self.list.remove(thread)
finally:
self.lock.release()
def pop(self):
self.lock.acquire()
try:
if not len(self.list):
return None
return self.list.pop()
finally:
self.lock.release()
def reset(self):
while 1:
thread = self.pop()
if not thread:
return
thread.join()
######################################################################
# Exit-notify threads
######################################################################
exitcondition = Condition(Lock())
exitthreads = []
inited = 0
def initexitnotify():
"""Initialize the exit notify system. This MUST be called from the
SAME THREAD that will call monitorloop BEFORE it calls monitorloop.
This SHOULD be called before the main thread starts any other
ExitNotifyThreads, or else it may miss the ability to catch the exit
status from them!"""
pass
def exitnotifymonitorloop(callback):
"""Enter an infinite "monitoring" loop. The argument, callback,
defines the function to call when an ExitNotifyThread has terminated.
That function is called with a single argument -- the ExitNotifyThread
that has terminated. The monitor will not continue to monitor for
other threads until the function returns, so if it intends to perform
long calculations, it should start a new thread itself -- but NOT
an ExitNotifyThread, or else an infinite loop may result. Furthermore,
the monitor will hold the lock all the while the other thread is waiting.
"""
global exitcondition, exitthreads
while 1: # Loop forever.
exitcondition.acquire()
try:
while not len(exitthreads):
exitcondition.wait(1)
while len(exitthreads):
callback(exitthreads.pop(0)) # Pull off in order added!
finally:
exitcondition.release()
def threadexited(thread):
"""Called when a thread exits."""
ui = UIBase.getglobalui()
if thread.getExitCause() == 'EXCEPTION':
if isinstance(thread.getExitException(), SystemExit):
# Bring a SystemExit into the main thread.
# Do not send it back to UI layer right now.
# Maybe later send it to ui.terminate?
raise SystemExit
ui.threadException(thread) # Expected to terminate
sys.exit(100) # Just in case...
os._exit(100)
elif thread.getExitMessage() == 'SYNC_WITH_TIMER_TERMINATE':
ui.terminate()
# Just in case...
sys.exit(100)
os._exit(100)
else:
ui.threadExited(thread)
class ExitNotifyThread(Thread):
"""This class is designed to alert a "monitor" to the fact that a thread has
exited and to provide for the ability for it to find out why."""
def run(self):
global exitcondition, exitthreads, profiledir
self.threadid = thread.get_ident()
try:
if not profiledir: # normal case
Thread.run(self)
else:
prof = profile.Profile()
try:
prof = prof.runctx("Thread.run(self)", globals(), locals())
except SystemExit:
pass
prof.dump_stats( \
profiledir + "/" + str(self.threadid) + "_" + \
self.getName() + ".prof")
except:
self.setExitCause('EXCEPTION')
self.setExitException(sys.exc_info()[1])
sbuf = StringIO()
traceback.print_exc(file = sbuf)
self.setExitStackTrace(sbuf.getvalue())
else:
self.setExitCause('NORMAL')
if not hasattr(self, 'exitmessage'):
self.setExitMessage(None)
exitcondition.acquire()
exitthreads.append(self)
exitcondition.notify()
exitcondition.release()
def setExitCause(self, cause):
self.exitcause = cause
def getExitCause(self):
"""Returns the cause of the exit, one of:
'EXCEPTION' -- the thread aborted because of an exception
'NORMAL' -- normal termination."""
return self.exitcause
def setExitException(self, exc):
self.exitexception = exc
def getExitException(self):
"""If getExitCause() is 'EXCEPTION', holds the value from
sys.exc_info()[1] for this exception."""
return self.exitexception
def setExitStackTrace(self, st):
self.exitstacktrace = st
def getExitStackTrace(self):
"""If getExitCause() is 'EXCEPTION', returns a string representing
the stack trace for this exception."""
return self.exitstacktrace
def setExitMessage(self, msg):
"""Sets the exit message to be fetched by a subsequent call to
getExitMessage. This message may be any object or type except
None."""
self.exitmessage = msg
def getExitMessage(self):
"""For any exit cause, returns the message previously set by
a call to setExitMessage(), or None if there was no such message
set."""
return self.exitmessage
######################################################################
# Instance-limited threads
######################################################################
instancelimitedsems = {}
instancelimitedlock = Lock()
def initInstanceLimit(instancename, instancemax):
"""Initialize the instance-limited thread implementation to permit
up to intancemax threads with the given instancename."""
instancelimitedlock.acquire()
if not instancelimitedsems.has_key(instancename):
instancelimitedsems[instancename] = BoundedSemaphore(instancemax)
instancelimitedlock.release()
class InstanceLimitedThread(ExitNotifyThread):
def __init__(self, instancename, *args, **kwargs):
self.instancename = instancename
apply(ExitNotifyThread.__init__, (self,) + args, kwargs)
def start(self):
instancelimitedsems[self.instancename].acquire()
ExitNotifyThread.start(self)
def run(self):
try:
ExitNotifyThread.run(self)
finally:
instancelimitedsems[self.instancename].release()
######################################################################
# Multi-lock -- capable of handling a single thread requesting a lock
# multiple times
######################################################################
class MultiLock:
def __init__(self):
self.lock = Lock()
self.statuslock = Lock()
self.locksheld = {}
def acquire(self):
"""Obtain a lock. Provides nice support for a single
thread trying to lock it several times -- as may be the case
if one I/O-using object calls others, while wanting to make it all
an atomic operation. Keeps a "lock request count" for the current
thread, and acquires the lock when it goes above zero, releases when
it goes below one.
This call is always blocking."""
# First, check to see if this thread already has a lock.
# If so, increment the lock count and just return.
self.statuslock.acquire()
try:
threadid = thread.get_ident()
if threadid in self.locksheld:
self.locksheld[threadid] += 1
return
else:
# This is safe because it is a per-thread structure
self.locksheld[threadid] = 1
finally:
self.statuslock.release()
self.lock.acquire()
def release(self):
self.statuslock.acquire()
try:
threadid = thread.get_ident()
if self.locksheld[threadid] > 1:
self.locksheld[threadid] -= 1
return
else:
del self.locksheld[threadid]
self.lock.release()
finally:
self.statuslock.release()

View File

@ -1,132 +0,0 @@
# Blinkenlights base classes
# Copyright (C) 2003 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from threading import *
from offlineimap.ui.UIBase import UIBase
import thread
from offlineimap.threadutil import MultiLock
class BlinkenBase:
"""This is a mix-in class that should be mixed in with either UIBase
or another appropriate base class. The Tk interface, for instance,
will probably mix it in with VerboseUI."""
def acct(s, accountname):
s.gettf().setcolor('purple')
s.__class__.__bases__[-1].acct(s, accountname)
def connecting(s, hostname, port):
s.gettf().setcolor('gray')
s.__class__.__bases__[-1].connecting(s, hostname, port)
def syncfolders(s, srcrepos, destrepos):
s.gettf().setcolor('blue')
s.__class__.__bases__[-1].syncfolders(s, srcrepos, destrepos)
def syncingfolder(s, srcrepos, srcfolder, destrepos, destfolder):
s.gettf().setcolor('cyan')
s.__class__.__bases__[-1].syncingfolder(s, srcrepos, srcfolder, destrepos, destfolder)
def loadmessagelist(s, repos, folder):
s.gettf().setcolor('green')
s._msg("Scanning folder [%s/%s]" % (s.getnicename(repos),
folder.getvisiblename()))
def syncingmessages(s, sr, sf, dr, df):
s.gettf().setcolor('blue')
s.__class__.__bases__[-1].syncingmessages(s, sr, sf, dr, df)
def copyingmessage(s, uid, src, destlist):
s.gettf().setcolor('orange')
s.__class__.__bases__[-1].copyingmessage(s, uid, src, destlist)
def deletingmessages(s, uidlist, destlist):
s.gettf().setcolor('red')
s.__class__.__bases__[-1].deletingmessages(s, uidlist, destlist)
def deletingmessage(s, uid, destlist):
s.gettf().setcolor('red')
s.__class__.__bases__[-1].deletingmessage(s, uid, destlist)
def addingflags(s, uidlist, flags, destlist):
s.gettf().setcolor('yellow')
s.__class__.__bases__[-1].addingflags(s, uidlist, flags, destlist)
def deletingflags(s, uidlist, flags, destlist):
s.gettf().setcolor('pink')
s.__class__.__bases__[-1].deletingflags(s, uidlist, flags, destlist)
def init_banner(s):
s.availablethreadframes = {}
s.threadframes = {}
s.tflock = MultiLock()
def threadExited(s, thread):
threadid = thread.threadid
accountname = s.getthreadaccount(thread)
s.tflock.acquire()
try:
if threadid in s.threadframes[accountname]:
tf = s.threadframes[accountname][threadid]
del s.threadframes[accountname][threadid]
s.availablethreadframes[accountname].append(tf)
tf.setthread(None)
finally:
s.tflock.release()
UIBase.threadExited(s, thread)
def gettf(s):
threadid = thread.get_ident()
accountname = s.getthreadaccount()
s.tflock.acquire()
try:
if not accountname in s.threadframes:
s.threadframes[accountname] = {}
if threadid in s.threadframes[accountname]:
return s.threadframes[accountname][threadid]
if not accountname in s.availablethreadframes:
s.availablethreadframes[accountname] = []
if len(s.availablethreadframes[accountname]):
tf = s.availablethreadframes[accountname].pop(0)
tf.setthread(currentThread())
else:
tf = s.getaccountframe().getnewthreadframe()
s.threadframes[accountname][threadid] = tf
return tf
finally:
s.tflock.release()
def sleep(s, sleepsecs):
s.gettf().setcolor('red')
s.getaccountframe().startsleep(sleepsecs)
UIBase.sleep(s, sleepsecs)
def sleeping(s, sleepsecs, remainingsecs):
if remainingsecs and s.gettf().getcolor() == 'black':
s.gettf().setcolor('red')
else:
s.gettf().setcolor('black')
return s.getaccountframe().sleeping(sleepsecs, remainingsecs)

View File

@ -1,589 +0,0 @@
# Curses-based interfaces
# Copyright (C) 2003 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from Blinkenlights import BlinkenBase
from UIBase import UIBase
from threading import *
import thread, time, sys, os, signal, time
from offlineimap import version, threadutil
from offlineimap.threadutil import MultiLock
import curses, curses.panel, curses.textpad, curses.wrapper
acctkeys = '1234567890abcdefghijklmnoprstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-=;/.,'
class CursesUtil:
def __init__(self):
self.pairlock = Lock()
self.iolock = MultiLock()
self.start()
def initpairs(self):
self.pairlock.acquire()
try:
self.pairs = {self._getpairindex(curses.COLOR_WHITE,
curses.COLOR_BLACK): 0}
self.nextpair = 1
finally:
self.pairlock.release()
def lock(self):
self.iolock.acquire()
def unlock(self):
self.iolock.release()
def locked(self, target, *args, **kwargs):
"""Perform an operation with full locking."""
self.lock()
try:
apply(target, args, kwargs)
finally:
self.unlock()
def refresh(self):
def lockedstuff():
curses.panel.update_panels()
curses.doupdate()
self.locked(lockedstuff)
def isactive(self):
return hasattr(self, 'stdscr')
def _getpairindex(self, fg, bg):
return '%d/%d' % (fg,bg)
def getpair(self, fg, bg):
if not self.has_color:
return 0
pindex = self._getpairindex(fg, bg)
self.pairlock.acquire()
try:
if self.pairs.has_key(pindex):
return curses.color_pair(self.pairs[pindex])
else:
self.pairs[pindex] = self.nextpair
curses.init_pair(self.nextpair, fg, bg)
self.nextpair += 1
return curses.color_pair(self.nextpair - 1)
finally:
self.pairlock.release()
def start(self):
self.stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
self.stdscr.keypad(1)
try:
curses.start_color()
self.has_color = curses.has_colors()
except:
self.has_color = 0
self.oldcursor = None
try:
self.oldcursor = curses.curs_set(0)
except:
pass
self.stdscr.clear()
self.stdscr.refresh()
(self.height, self.width) = self.stdscr.getmaxyx()
self.initpairs()
def stop(self):
if not hasattr(self, 'stdscr'):
return
#self.stdscr.addstr(self.height - 1, 0, "\n",
# self.getpair(curses.COLOR_WHITE,
# curses.COLOR_BLACK))
if self.oldcursor != None:
curses.curs_set(self.oldcursor)
self.stdscr.refresh()
self.stdscr.keypad(0)
curses.nocbreak()
curses.echo()
curses.endwin()
del self.stdscr
def reset(self):
self.stop()
self.start()
class CursesAccountFrame:
def __init__(s, master, accountname):
s.c = master
s.children = []
s.accountname = accountname
def drawleadstr(s, secs = None):
if secs == None:
acctstr = '%s: [active] %13.13s: ' % (s.key, s.accountname)
else:
acctstr = '%s: [%3d:%02d] %13.13s: ' % (s.key,
secs / 60, secs % 60,
s.accountname)
s.c.locked(s.window.addstr, 0, 0, acctstr)
s.location = len(acctstr)
def setwindow(s, window, key):
s.window = window
s.key = key
s.drawleadstr()
for child in s.children:
child.update(window, 0, s.location)
s.location += 1
def getnewthreadframe(s):
tf = CursesThreadFrame(s.c, s.window, 0, s.location)
s.location += 1
s.children.append(tf)
return tf
def startsleep(s, sleepsecs):
s.sleeping_abort = 0
def sleeping(s, sleepsecs, remainingsecs):
if remainingsecs:
s.c.lock()
try:
s.drawleadstr(remainingsecs)
s.window.refresh()
finally:
s.c.unlock()
time.sleep(sleepsecs)
else:
s.c.lock()
try:
s.drawleadstr()
s.window.refresh()
finally:
s.c.unlock()
return s.sleeping_abort
def syncnow(s):
s.sleeping_abort = 1
class CursesThreadFrame:
def __init__(s, master, window, y, x):
"""master should be a CursesUtil object."""
s.c = master
s.window = window
s.x = x
s.y = y
s.colors = []
bg = curses.COLOR_BLACK
s.colormap = {'black': s.c.getpair(curses.COLOR_BLACK, bg),
'gray': s.c.getpair(curses.COLOR_WHITE, bg),
'white': curses.A_BOLD | s.c.getpair(curses.COLOR_WHITE, bg),
'blue': s.c.getpair(curses.COLOR_BLUE, bg),
'red': s.c.getpair(curses.COLOR_RED, bg),
'purple': s.c.getpair(curses.COLOR_MAGENTA, bg),
'cyan': s.c.getpair(curses.COLOR_CYAN, bg),
'green': s.c.getpair(curses.COLOR_GREEN, bg),
'orange': s.c.getpair(curses.COLOR_YELLOW, bg),
'yellow': curses.A_BOLD | s.c.getpair(curses.COLOR_YELLOW, bg),
'pink': curses.A_BOLD | s.c.getpair(curses.COLOR_RED, bg)}
#s.setcolor('gray')
s.setcolor('black')
def setcolor(self, color):
self.color = self.colormap[color]
self.colorname = color
self.display()
def display(self):
def lockedstuff():
if self.getcolor() == 'black':
self.window.addstr(self.y, self.x, ' ', self.color)
else:
self.window.addstr(self.y, self.x, '.', self.color)
self.c.stdscr.move(self.c.height - 1, self.c.width - 1)
self.window.refresh()
self.c.locked(lockedstuff)
def getcolor(self):
return self.colorname
def getcolorpair(self):
return self.color
def update(self, window, y, x):
self.window = window
self.y = y
self.x = x
self.display()
def setthread(self, newthread):
self.setcolor('black')
#if newthread:
# self.setcolor('gray')
#else:
# self.setcolor('black')
class InputHandler:
def __init__(s, util):
s.c = util
s.bgchar = None
s.inputlock = Lock()
s.lockheld = 0
s.statuslock = Lock()
s.startup = Event()
s.startthread()
def startthread(s):
s.thread = threadutil.ExitNotifyThread(target = s.bgreaderloop,
name = "InputHandler loop")
s.thread.setDaemon(1)
s.thread.start()
def bgreaderloop(s):
while 1:
s.statuslock.acquire()
if s.lockheld or s.bgchar == None:
s.statuslock.release()
s.startup.wait()
else:
s.statuslock.release()
ch = s.c.stdscr.getch()
s.statuslock.acquire()
try:
if s.lockheld or s.bgchar == None:
curses.ungetch(ch)
else:
s.bgchar(ch)
finally:
s.statuslock.release()
def set_bgchar(s, callback):
"""Sets a "background" character handler. If a key is pressed
while not doing anything else, it will be passed to this handler.
callback is a function taking a single arg -- the char pressed.
If callback is None, clears the request."""
s.statuslock.acquire()
oldhandler = s.bgchar
newhandler = callback
s.bgchar = callback
if oldhandler and not newhandler:
pass
if newhandler and not oldhandler:
s.startup.set()
s.statuslock.release()
def input_acquire(s):
"""Call this method when you want exclusive input control.
Make sure to call input_release afterwards!
"""
s.inputlock.acquire()
s.statuslock.acquire()
s.lockheld = 1
s.statuslock.release()
def input_release(s):
"""Call this method when you are done getting input."""
s.statuslock.acquire()
s.lockheld = 0
s.statuslock.release()
s.inputlock.release()
s.startup.set()
class Blinkenlights(BlinkenBase, UIBase):
def init_banner(s):
s.af = {}
s.aflock = Lock()
s.c = CursesUtil()
s.text = []
BlinkenBase.init_banner(s)
s.setupwindows()
s.inputhandler = InputHandler(s.c)
s.gettf().setcolor('red')
s._msg(version.banner)
s.inputhandler.set_bgchar(s.keypress)
signal.signal(signal.SIGWINCH, s.resizehandler)
s.resizelock = Lock()
s.resizecount = 0
def resizehandler(s, signum, frame):
s.resizeterm()
def resizeterm(s, dosleep = 1):
if not s.resizelock.acquire(0):
s.resizecount += 1
return
signal.signal(signal.SIGWINCH, signal.SIG_IGN)
s.aflock.acquire()
s.c.lock()
s.resizecount += 1
while s.resizecount:
s.c.reset()
s.setupwindows()
s.resizecount -= 1
s.c.unlock()
s.aflock.release()
s.resizelock.release()
signal.signal(signal.SIGWINCH, s.resizehandler)
if dosleep:
time.sleep(1)
s.resizeterm(0)
def isusable(s):
# Not a terminal? Can't use curses.
if not sys.stdout.isatty() and sys.stdin.isatty():
return 0
# No TERM specified? Can't use curses.
try:
if not len(os.environ['TERM']):
return 0
except: return 0
# ncurses doesn't want to start? Can't use curses.
# This test is nasty because initscr() actually EXITS on error.
# grr.
pid = os.fork()
if pid:
# parent
return not os.WEXITSTATUS(os.waitpid(pid, 0)[1])
else:
# child
curses.initscr()
curses.endwin()
# If we didn't die by here, indicate success.
sys.exit(0)
def keypress(s, key):
if key > 255:
return
if chr(key) == 'q':
# Request to quit.
s.terminate()
try:
index = acctkeys.index(chr(key))
except ValueError:
# Key not a valid one: exit.
return
if index >= len(s.hotkeys):
# Not in our list of valid hotkeys.
return
# Trying to end sleep somewhere.
s.getaccountframe(s.hotkeys[index]).syncnow()
def getpass(s, accountname, config, errmsg = None):
s.inputhandler.input_acquire()
# See comment on _msg for info on why both locks are obtained.
s.tflock.acquire()
s.c.lock()
try:
s.gettf().setcolor('white')
s._addline(" *** Input Required", s.gettf().getcolorpair())
s._addline(" *** Please enter password for account %s: " % accountname,
s.gettf().getcolorpair())
s.logwindow.refresh()
password = s.logwindow.getstr()
finally:
s.tflock.release()
s.c.unlock()
s.inputhandler.input_release()
return password
def setupwindows(s):
s.c.lock()
try:
s.bannerwindow = curses.newwin(1, s.c.width, 0, 0)
s.setupwindow_drawbanner()
s.logheight = s.c.height - 1 - len(s.af.keys())
s.logwindow = curses.newwin(s.logheight, s.c.width, 1, 0)
s.logwindow.idlok(1)
s.logwindow.scrollok(1)
s.logwindow.move(s.logheight - 1, 0)
s.setupwindow_drawlog()
accounts = s.af.keys()
accounts.sort()
accounts.reverse()
pos = s.c.height - 1
index = 0
s.hotkeys = []
for account in accounts:
accountwindow = curses.newwin(1, s.c.width, pos, 0)
s.af[account].setwindow(accountwindow, acctkeys[index])
s.hotkeys.append(account)
index += 1
pos -= 1
curses.doupdate()
finally:
s.c.unlock()
def setupwindow_drawbanner(s):
if s.c.has_color:
color = s.c.getpair(curses.COLOR_WHITE, curses.COLOR_BLUE) | \
curses.A_BOLD
else:
color = curses.A_REVERSE
s.bannerwindow.bkgd(' ', color) # Fill background with that color
s.bannerwindow.addstr("%s %s" % (version.productname,
version.versionstr))
s.bannerwindow.addstr(0, s.bannerwindow.getmaxyx()[1] - len(version.copyright) - 1,
version.copyright)
s.bannerwindow.noutrefresh()
def setupwindow_drawlog(s):
if s.c.has_color:
color = s.c.getpair(curses.COLOR_WHITE, curses.COLOR_BLACK)
else:
color = curses.A_NORMAL
s.logwindow.bkgd(' ', color)
for line, color in s.text:
s.logwindow.addstr("\n" + line, color)
s.logwindow.noutrefresh()
def getaccountframe(s, accountname = None):
if accountname == None:
accountname = s.getthreadaccount()
s.aflock.acquire()
try:
if accountname in s.af:
return s.af[accountname]
# New one.
s.af[accountname] = CursesAccountFrame(s.c, accountname)
s.c.lock()
try:
s.c.reset()
s.setupwindows()
finally:
s.c.unlock()
finally:
s.aflock.release()
return s.af[accountname]
def _display(s, msg, color = None):
if "\n" in msg:
for thisline in msg.split("\n"):
s._msg(thisline)
return
# We must acquire both locks. Otherwise, deadlock can result.
# This can happen if one thread calls _msg (locking curses, then
# tf) and another tries to set the color (locking tf, then curses)
#
# By locking both up-front here, in this order, we prevent deadlock.
s.tflock.acquire()
s.c.lock()
try:
if not s.c.isactive():
# For dumping out exceptions and stuff.
print msg
return
if color:
s.gettf().setcolor(color)
s._addline(msg, s.gettf().getcolorpair())
s.logwindow.refresh()
finally:
s.c.unlock()
s.tflock.release()
def _addline(s, msg, color):
s.c.lock()
try:
s.logwindow.addstr("\n" + msg, color)
s.text.append((msg, color))
while len(s.text) > s.logheight:
s.text = s.text[1:]
finally:
s.c.unlock()
def terminate(s, exitstatus = 0):
s.c.stop()
UIBase.terminate(s, exitstatus)
def threadException(s, thread):
s.c.stop()
UIBase.threadException(s, thread)
def mainException(s):
s.c.stop()
UIBase.mainException(s)
def sleep(s, sleepsecs):
s.gettf().setcolor('red')
s._msg("Next sync in %d:%02d" % (sleepsecs / 60, sleepsecs % 60))
BlinkenBase.sleep(s, sleepsecs)
if __name__ == '__main__':
x = Blinkenlights(None)
x.init_banner()
import time
time.sleep(5)
x.c.stop()
fgs = {'black': curses.COLOR_BLACK, 'red': curses.COLOR_RED,
'green': curses.COLOR_GREEN, 'yellow': curses.COLOR_YELLOW,
'blue': curses.COLOR_BLUE, 'magenta': curses.COLOR_MAGENTA,
'cyan': curses.COLOR_CYAN, 'white': curses.COLOR_WHITE}
x = CursesUtil()
win1 = curses.newwin(x.height, x.width / 4 - 1, 0, 0)
win1.addstr("Black/normal\n")
for name, fg in fgs.items():
win1.addstr("%s\n" % name, x.getpair(fg, curses.COLOR_BLACK))
win2 = curses.newwin(x.height, x.width / 4 - 1, 0, win1.getmaxyx()[1])
win2.addstr("Blue/normal\n")
for name, fg in fgs.items():
win2.addstr("%s\n" % name, x.getpair(fg, curses.COLOR_BLUE))
win3 = curses.newwin(x.height, x.width / 4 - 1, 0, win1.getmaxyx()[1] +
win2.getmaxyx()[1])
win3.addstr("Black/bright\n")
for name, fg in fgs.items():
win3.addstr("%s\n" % name, x.getpair(fg, curses.COLOR_BLACK) | \
curses.A_BOLD)
win4 = curses.newwin(x.height, x.width / 4 - 1, 0, win1.getmaxyx()[1] * 3)
win4.addstr("Blue/bright\n")
for name, fg in fgs.items():
win4.addstr("%s\n" % name, x.getpair(fg, curses.COLOR_BLUE) | \
curses.A_BOLD)
win1.refresh()
win2.refresh()
win3.refresh()
win4.refresh()
x.stdscr.refresh()
import time
time.sleep(5)
x.stop()
print x.has_color
print x.height
print x.width

View File

@ -1,48 +0,0 @@
# Noninteractive UI
# Copyright (C) 2002 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import sys, time
from UIBase import UIBase
class Basic(UIBase):
def getpass(s, accountname, config, errmsg = None):
raise NotImplementedError, "Prompting for a password is not supported in noninteractive mode."
def _display(s, msg):
print msg
sys.stdout.flush()
def warn(s, msg, minor = 0):
warntxt = 'WARNING'
if minor:
warntxt = 'warning'
sys.stderr.write(warntxt + ": " + str(msg) + "\n")
def sleep(s, sleepsecs):
if s.verbose >= 0:
s._msg("Sleeping for %d:%02d" % (sleepsecs / 60, sleepsecs % 60))
UIBase.sleep(s, sleepsecs)
def sleeping(s, sleepsecs, remainingsecs):
if sleepsecs > 0:
time.sleep(sleepsecs)
return 0
class Quiet(Basic):
def __init__(s, config, verbose = -1):
Basic.__init__(s, config, verbose)

View File

@ -1,60 +0,0 @@
# TTY UI
# Copyright (C) 2002 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from UIBase import UIBase
from getpass import getpass
import select, sys
from threading import *
class TTYUI(UIBase):
def __init__(s, config, verbose = 0):
UIBase.__init__(s, config, verbose)
s.iswaiting = 0
s.outputlock = Lock()
def isusable(s):
return sys.stdout.isatty() and sys.stdin.isatty()
def _display(s, msg):
s.outputlock.acquire()
try:
if (currentThread().getName() == 'MainThread'):
print msg
else:
print "%s:\n %s" % (currentThread().getName(), msg)
sys.stdout.flush()
finally:
s.outputlock.release()
def getpass(s, accountname, config, errmsg = None):
if errmsg:
s._msg("%s: %s" % (accountname, errmsg))
s.outputlock.acquire()
try:
return getpass("%s: Enter password: " % accountname)
finally:
s.outputlock.release()
def mainException(s):
if isinstance(sys.exc_info()[1], KeyboardInterrupt) and \
s.iswaiting:
sys.stdout.write("Timer interrupted at user request; program terminating. \n")
s.terminate()
else:
UIBase.mainException(s)

View File

@ -1,536 +0,0 @@
# Tk UI
# Copyright (C) 2002, 2003 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from __future__ import nested_scopes
from Tkinter import *
import tkFont
from threading import *
import thread, traceback, time, threading
from StringIO import StringIO
from ScrolledText import ScrolledText
from offlineimap import threadutil, version
from Queue import Queue
from UIBase import UIBase
from offlineimap.ui.Blinkenlights import BlinkenBase
usabletest = None
class PasswordDialog:
def __init__(self, accountname, config, master=None, errmsg = None):
self.top = Toplevel(master)
self.top.title(version.productname + " Password Entry")
text = ''
if errmsg:
text = '%s: %s\n' % (accountname, errmsg)
text += "%s: Enter password: " % accountname
self.label = Label(self.top, text = text)
self.label.pack()
self.entry = Entry(self.top, show='*')
self.entry.bind("<Return>", self.ok)
self.entry.pack()
self.entry.focus_force()
self.button = Button(self.top, text = "OK", command=self.ok)
self.button.pack()
self.entry.focus_force()
self.top.wait_window(self.label)
def ok(self, args = None):
self.password = self.entry.get()
self.top.destroy()
def getpassword(self):
return self.password
class TextOKDialog:
def __init__(self, title, message, blocking = 1, master = None):
if not master:
self.top = Tk()
else:
self.top = Toplevel(master)
self.top.title(title)
self.text = ScrolledText(self.top, font = "Courier 10")
self.text.pack()
self.text.insert(END, message)
self.text['state'] = DISABLED
self.button = Button(self.top, text = "OK", command=self.ok)
self.button.pack()
if blocking:
self.top.wait_window(self.button)
def ok(self):
self.top.destroy()
class ThreadFrame(Frame):
def __init__(self, master=None):
self.threadextraframe = None
self.thread = currentThread()
self.threadid = thread.get_ident()
Frame.__init__(self, master, relief = RIDGE, borderwidth = 2)
self.pack(fill = 'x')
self.threadlabel = Label(self, foreground = '#FF0000',
text ="Thread %d (%s)" % (self.threadid,
self.thread.getName()))
self.threadlabel.pack()
self.setthread(currentThread())
self.account = "Unknown"
self.mailbox = "Unknown"
self.loclabel = Label(self,
text = "Account/mailbox information unknown")
#self.loclabel.pack()
self.updateloclabel()
self.message = Label(self, text="Messages will appear here.\n",
foreground = '#0000FF')
self.message.pack(fill = 'x')
def setthread(self, newthread):
if newthread:
self.threadlabel['text'] = newthread.getName()
else:
self.threadlabel['text'] = "No thread"
self.destroythreadextraframe()
def destroythreadextraframe(self):
if self.threadextraframe:
self.threadextraframe.destroy()
self.threadextraframe = None
def getthreadextraframe(self):
if self.threadextraframe:
return self.threadextraframe
self.threadextraframe = Frame(self)
self.threadextraframe.pack(fill = 'x')
return self.threadextraframe
def setaccount(self, account):
self.account = account
self.mailbox = "Unknown"
self.updateloclabel()
def setmailbox(self, mailbox):
self.mailbox = mailbox
self.updateloclabel()
def updateloclabel(self):
self.loclabel['text'] = "Processing %s: %s" % (self.account,
self.mailbox)
def appendmessage(self, newtext):
self.message['text'] += "\n" + newtext
def setmessage(self, newtext):
self.message['text'] = newtext
class VerboseUI(UIBase):
def isusable(s):
global usabletest
if usabletest != None:
return usabletest
try:
Tk().destroy()
usabletest = 1
except TclError:
usabletest = 0
return usabletest
def _createTopWindow(self, doidlevac = 1):
self.notdeleted = 1
self.created = threading.Event()
self.af = {}
self.aflock = Lock()
t = threadutil.ExitNotifyThread(target = self._runmainloop,
name = "Tk Mainloop")
t.setDaemon(1)
t.start()
self.created.wait()
del self.created
if doidlevac:
t = threadutil.ExitNotifyThread(target = self.idlevacuum,
name = "Tk idle vacuum")
t.setDaemon(1)
t.start()
def _runmainloop(s):
s.top = Tk()
s.top.title(version.productname + " " + version.versionstr)
s.top.after_idle(s.created.set)
s.top.mainloop()
s.notdeleted = 0
def getaccountframe(s):
accountname = s.getthreadaccount()
s.aflock.acquire()
try:
if accountname in s.af:
return s.af[accountname]
s.af[accountname] = LEDAccountFrame(s.top, accountname,
s.fontfamily, s.fontsize)
finally:
s.aflock.release()
return s.af[accountname]
def getpass(s, accountname, config, errmsg = None):
pd = PasswordDialog(accountname, config, errmsg = errmsg)
return pd.getpassword()
def gettf(s, newtype=ThreadFrame, master = None):
if master == None:
master = s.top
threadid = thread.get_ident()
s.tflock.acquire()
try:
if threadid in s.threadframes:
return s.threadframes[threadid]
if len(s.availablethreadframes):
tf = s.availablethreadframes.pop(0)
tf.setthread(currentThread())
else:
tf = newtype(master)
s.threadframes[threadid] = tf
return tf
finally:
s.tflock.release()
def _display(s, msg):
s.gettf().setmessage(msg)
def threadExited(s, thread):
threadid = thread.threadid
s.tflock.acquire()
if threadid in s.threadframes:
tf = s.threadframes[threadid]
tf.setthread(None)
tf.setaccount("Unknown")
tf.setmessage("Idle")
s.availablethreadframes.append(tf)
del s.threadframes[threadid]
s.tflock.release()
UIBase.threadExited(s, thread)
def idlevacuum(s):
while s.notdeleted:
time.sleep(10)
s.tflock.acquire()
while len(s.availablethreadframes):
tf = s.availablethreadframes.pop()
tf.destroy()
s.tflock.release()
def threadException(s, thread):
exceptionstr = s.getThreadExceptionString(thread)
print exceptionstr
s.top.destroy()
s.top = None
TextOKDialog("Thread Exception", exceptionstr)
s.delThreadDebugLog(thread)
s.terminate(100)
def mainException(s):
exceptionstr = s.getMainExceptionString()
print exceptionstr
s.top.destroy()
s.top = None
TextOKDialog("Main Program Exception", exceptionstr)
def warn(s, msg, minor = 0):
if minor:
# Just let the default handler catch it
UIBase.warn(s, msg, minor)
else:
TextOKDialog("OfflineIMAP Warning", msg)
def showlicense(s):
TextOKDialog(version.productname + " License",
version.bigcopyright + "\n" +
version.homepage + "\n\n" + version.license,
blocking = 0, master = s.top)
def init_banner(s):
s.threadframes = {}
s.availablethreadframes = []
s.tflock = Lock()
s._createTopWindow()
s._msg(version.productname + " " + version.versionstr + ", " +\
version.copyright)
tf = s.gettf().getthreadextraframe()
b = Button(tf, text = "About", command = s.showlicense)
b.pack(side = LEFT)
b = Button(tf, text = "Exit", command = s.terminate)
b.pack(side = RIGHT)
s.sleeping_abort = {}
def deletingmessages(s, uidlist, destlist):
ds = s.folderlist(destlist)
s._msg("Deleting %d messages in %s" % (len(uidlist), ds))
def _sleep_cancel(s, args = None):
s.sleeping_abort[thread.get_ident()] = 1
def sleep(s, sleepsecs):
threadid = thread.get_ident()
s.sleeping_abort[threadid] = 0
tf = s.gettf().getthreadextraframe()
def sleep_cancel():
s.sleeping_abort[threadid] = 1
sleepbut = Button(tf, text = 'Sync immediately',
command = sleep_cancel)
sleepbut.pack()
UIBase.sleep(s, sleepsecs)
def sleeping(s, sleepsecs, remainingsecs):
retval = s.sleeping_abort[thread.get_ident()]
if remainingsecs:
s._msg("Next sync in %d:%02d" % (remainingsecs / 60,
remainingsecs % 60))
else:
s._msg("Wait done; synchronizing now.")
s.gettf().destroythreadextraframe()
del s.sleeping_abort[thread.get_ident()]
time.sleep(sleepsecs)
return retval
TkUI = VerboseUI
################################################## Blinkenlights
class LEDAccountFrame:
def __init__(self, top, accountname, fontfamily, fontsize):
self.top = top
self.accountname = accountname
self.fontfamily = fontfamily
self.fontsize = fontsize
self.frame = Frame(self.top, background = 'black')
self.frame.pack(side = BOTTOM, expand = 1, fill = X)
self._createcanvas(self.frame)
self.label = Label(self.frame, text = accountname,
background = "black", foreground = "blue",
font = (self.fontfamily, self.fontsize))
self.label.grid(sticky = E, row = 0, column = 1)
def getnewthreadframe(s):
return LEDThreadFrame(s.canvas)
def _createcanvas(self, parent):
c = LEDFrame(parent)
self.canvas = c
c.grid(sticky = E, row = 0, column = 0)
parent.grid_columnconfigure(1, weight = 1)
#c.pack(side = LEFT, expand = 0, fill = X)
def startsleep(s, sleepsecs):
s.sleeping_abort = 0
s.button = Button(s.frame, text = "Sync now", command = s.syncnow,
background = "black", activebackground = "black",
activeforeground = "white",
foreground = "blue", highlightthickness = 0,
padx = 0, pady = 0,
font = (s.fontfamily, s.fontsize), borderwidth = 0,
relief = 'solid')
s.button.grid(sticky = E, row = 0, column = 2)
def syncnow(s):
s.sleeping_abort = 1
def sleeping(s, sleepsecs, remainingsecs):
if remainingsecs:
s.button.config(text = 'Sync now (%d:%02d remain)' % \
(remainingsecs / 60, remainingsecs % 60))
time.sleep(sleepsecs)
else:
s.button.destroy()
del s.button
return s.sleeping_abort
class LEDFrame(Frame):
"""This holds the different lights."""
def getnewobj(self):
retval = Canvas(self, background = 'black', height = 20, bd = 0,
highlightthickness = 0, width = 10)
retval.pack(side = LEFT, padx = 0, pady = 0, ipadx = 0, ipady = 0)
return retval
class LEDThreadFrame:
"""There is one of these for each little light."""
def __init__(self, master):
self.canvas = master.getnewobj()
self.color = ''
self.ovalid = self.canvas.create_oval(4, 4, 9,
9, fill = 'gray',
outline = '#303030')
def setcolor(self, newcolor):
if newcolor != self.color:
self.canvas.itemconfigure(self.ovalid, fill = newcolor)
self.color = newcolor
def getcolor(self):
return self.color
def setthread(self, newthread):
if newthread:
self.setcolor('gray')
else:
self.setcolor('black')
class Blinkenlights(BlinkenBase, VerboseUI):
def __init__(s, config, verbose = 0):
VerboseUI.__init__(s, config, verbose)
s.fontfamily = 'Helvetica'
s.fontsize = 8
if config.has_option('ui.Tk.Blinkenlights', 'fontfamily'):
s.fontfamily = config.get('ui.Tk.Blinkenlights', 'fontfamily')
if config.has_option('ui.Tk.Blinkenlights', 'fontsize'):
s.fontsize = config.getint('ui.Tk.Blinkenlights', 'fontsize')
def isusable(s):
return VerboseUI.isusable(s)
def _createTopWindow(self):
VerboseUI._createTopWindow(self, 0)
#self.top.resizable(width = 0, height = 0)
self.top.configure(background = 'black', bd = 0)
widthmetric = tkFont.Font(family = self.fontfamily, size = self.fontsize).measure("0")
self.loglines = self.config.getdefaultint("ui.Tk.Blinkenlights",
"loglines", 5)
self.bufferlines = self.config.getdefaultint("ui.Tk.Blinkenlights",
"bufferlines", 500)
self.text = ScrolledText(self.top, bg = 'black', #scrollbar = 'y',
font = (self.fontfamily, self.fontsize),
bd = 0, highlightthickness = 0, setgrid = 0,
state = DISABLED, height = self.loglines,
wrap = NONE, width = 60)
self.text.vbar.configure(background = '#000050',
activebackground = 'blue',
highlightbackground = 'black',
troughcolor = "black", bd = 0,
elementborderwidth = 2)
self.textenabled = 0
self.tags = []
self.textlock = Lock()
def init_banner(s):
BlinkenBase.init_banner(s)
s._createTopWindow()
menubar = Menu(s.top, activebackground = "black",
activeforeground = "white",
activeborderwidth = 0,
background = "black", foreground = "blue",
font = (s.fontfamily, s.fontsize), bd = 0)
menubar.add_command(label = "About", command = s.showlicense)
menubar.add_command(label = "Show Log", command = s._togglelog)
menubar.add_command(label = "Exit", command = s.terminate)
s.top.config(menu = menubar)
s.menubar = menubar
s.text.see(END)
if s.config.getdefaultboolean("ui.Tk.Blinkenlights", "showlog", 1):
s._togglelog()
s.gettf().setcolor('red')
s.top.resizable(width = 0, height = 0)
s._msg(version.banner)
def _togglelog(s):
if s.textenabled:
s.oldtextheight = s.text.winfo_height()
s.text.pack_forget()
s.textenabled = 0
s.menubar.entryconfig('Hide Log', label = 'Show Log')
s.top.update()
s.top.geometry("")
s.top.update()
s.top.resizable(width = 0, height = 0)
s.top.update()
else:
s.text.pack(side = TOP, expand = 1, fill = BOTH)
s.textenabled = 1
s.top.update()
s.top.geometry("")
s.menubar.entryconfig('Show Log', label = 'Hide Log')
s._rescroll()
s.top.resizable(width = 1, height = 1)
def sleep(s, sleepsecs):
s.gettf().setcolor('red')
s._msg("Next sync in %d:%02d" % (sleepsecs / 60, sleepsecs % 60))
BlinkenBase.sleep(s, sleepsecs)
def sleeping(s, sleepsecs, remainingsecs):
return BlinkenBase.sleeping(s, sleepsecs, remainingsecs)
def _rescroll(s):
s.text.see(END)
lo, hi = s.text.vbar.get()
s.text.vbar.set(1.0 - (hi - lo), 1.0)
def _display(s, msg):
if "\n" in msg:
for thisline in msg.split("\n"):
s._msg(thisline)
return
#VerboseUI._msg(s, msg)
color = s.gettf().getcolor()
rescroll = 1
s.textlock.acquire()
try:
if s.text.vbar.get()[1] != 1.0:
rescroll = 0
s.text.config(state = NORMAL)
if not color in s.tags:
s.text.tag_config(color, foreground = color)
s.tags.append(color)
s.text.insert(END, "\n" + msg, color)
# Trim down. Not quite sure why I have to say 7 instead of 5,
# but so it is.
while float(s.text.index(END)) > s.bufferlines + 2.0:
s.text.delete(1.0, 2.0)
if rescroll:
s._rescroll()
finally:
s.text.config(state = DISABLED)
s.textlock.release()

View File

@ -1,341 +0,0 @@
# UI base class
# Copyright (C) 2002 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import offlineimap.version
import re, time, sys, traceback, threading, thread
from StringIO import StringIO
debugtypes = {'imap': 'IMAP protocol debugging',
'maildir': 'Maildir repository debugging',
'thread': 'Threading debugging'}
globalui = None
def setglobalui(newui):
global globalui
globalui = newui
def getglobalui():
global globalui
return globalui
class UIBase:
def __init__(s, config, verbose = 0):
s.verbose = verbose
s.config = config
s.debuglist = []
s.debugmessages = {}
s.debugmsglen = 50
s.threadaccounts = {}
s.logfile = None
################################################## UTILS
def _msg(s, msg):
"""Generic tool called when no other works."""
s._log(msg)
s._display(msg)
def _log(s, msg):
"""Log it to disk. Returns true if it wrote something; false
otherwise."""
if s.logfile:
s.logfile.write("%s: %s\n" % (threading.currentThread().getName(),
msg))
return 1
return 0
def setlogfd(s, logfd):
s.logfile = logfd
logfd.write("This is %s %s %s\n" % \
(offlineimap.version.productname,
offlineimap.version.versionstr,
offlineimap.version.revstr))
logfd.write("Python: %s\n" % sys.version)
logfd.write("Platform: %s\n" % sys.platform)
logfd.write("Args: %s\n" % sys.argv)
def _display(s, msg):
"""Display a message."""
raise NotImplementedError
def warn(s, msg, minor = 0):
if minor:
s._msg("warning: " + msg)
else:
s._msg("WARNING: " + msg)
def registerthread(s, account):
"""Provides a hint to UIs about which account this particular
thread is processing."""
if s.threadaccounts.has_key(threading.currentThread()):
raise ValueError, "Thread %s already registered (old %s, new %s)" %\
(threading.currentThread().getName(),
s.getthreadaccount(s), account)
s.threadaccounts[threading.currentThread()] = account
def unregisterthread(s, thr):
"""Recognizes a thread has exited."""
if s.threadaccounts.has_key(thr):
del s.threadaccounts[thr]
def getthreadaccount(s, thr = None):
if not thr:
thr = threading.currentThread()
if s.threadaccounts.has_key(thr):
return s.threadaccounts[thr]
return '*Control'
def debug(s, debugtype, msg):
thisthread = threading.currentThread()
if s.debugmessages.has_key(thisthread):
s.debugmessages[thisthread].append("%s: %s" % (debugtype, msg))
else:
s.debugmessages[thisthread] = ["%s: %s" % (debugtype, msg)]
while len(s.debugmessages[thisthread]) > s.debugmsglen:
s.debugmessages[thisthread] = s.debugmessages[thisthread][1:]
if debugtype in s.debuglist:
if not s._log("DEBUG[%s]: %s" % (debugtype, msg)):
s._display("DEBUG[%s]: %s" % (debugtype, msg))
def add_debug(s, debugtype):
global debugtypes
if debugtype in debugtypes:
if not debugtype in s.debuglist:
s.debuglist.append(debugtype)
s.debugging(debugtype)
else:
s.invaliddebug(debugtype)
def debugging(s, debugtype):
global debugtypes
s._msg("Now debugging for %s: %s" % (debugtype, debugtypes[debugtype]))
def invaliddebug(s, debugtype):
s.warn("Invalid debug type: %s" % debugtype)
def locked(s):
raise Exception, "Another OfflineIMAP is running with the same metadatadir; exiting."
def getnicename(s, object):
prelimname = str(object.__class__).split('.')[-1]
# Strip off extra stuff.
return re.sub('(Folder|Repository)', '', prelimname)
def isusable(s):
"""Returns true if this UI object is usable in the current
environment. For instance, an X GUI would return true if it's
being run in X with a valid DISPLAY setting, and false otherwise."""
return 1
################################################## INPUT
def getpass(s, accountname, config, errmsg = None):
raise NotImplementedError
def folderlist(s, list):
return ', '.join(["%s[%s]" % (s.getnicename(x), x.getname()) for x in list])
################################################## WARNINGS
def msgtoreadonly(s, destfolder, uid, content, flags):
if not (config.has_option('general', 'ignore-readonly') and config.getboolean("general", "ignore-readonly")):
s.warn("Attempted to synchronize message %d to folder %s[%s], but that folder is read-only. The message will not be copied to that folder." % \
(uid, s.getnicename(destfolder), destfolder.getname()))
def flagstoreadonly(s, destfolder, uidlist, flags):
if not (config.has_option('general', 'ignore-readonly') and config.getboolean("general", "ignore-readonly")):
s.warn("Attempted to modify flags for messages %s in folder %s[%s], but that folder is read-only. No flags have been modified for that message." % \
(str(uidlist), s.getnicename(destfolder), destfolder.getname()))
def deletereadonly(s, destfolder, uidlist):
if not (config.has_option('general', 'ignore-readonly') and config.getboolean("general", "ignore-readonly")):
s.warn("Attempted to delete messages %s in folder %s[%s], but that folder is read-only. No messages have been deleted in that folder." % \
(str(uidlist), s.getnicename(destfolder), destfolder.getname()))
################################################## MESSAGES
def init_banner(s):
"""Called when the UI starts. Must be called before any other UI
call except isusable(). Displays the copyright banner. This is
where the UI should do its setup -- TK, for instance, would
create the application window here."""
if s.verbose >= 0:
s._msg(offlineimap.version.banner)
def connecting(s, hostname, port):
if s.verbose < 0:
return
if hostname == None:
hostname = ''
if port != None:
port = ":%s" % str(port)
displaystr = ' to %s%s.' % (hostname, port)
if hostname == '' and port == None:
displaystr = '.'
s._msg("Establishing connection" + displaystr)
def acct(s, accountname):
if s.verbose >= 0:
s._msg("***** Processing account %s" % accountname)
def acctdone(s, accountname):
if s.verbose >= 0:
s._msg("***** Finished processing account " + accountname)
def syncfolders(s, srcrepos, destrepos):
if s.verbose >= 0:
s._msg("Copying folder structure from %s to %s" % \
(s.getnicename(srcrepos), s.getnicename(destrepos)))
############################## Folder syncing
def syncingfolder(s, srcrepos, srcfolder, destrepos, destfolder):
"""Called when a folder sync operation is started."""
if s.verbose >= 0:
s._msg("Syncing %s: %s -> %s" % (srcfolder.getname(),
s.getnicename(srcrepos),
s.getnicename(destrepos)))
def validityproblem(s, folder, saved, new):
s.warn("UID validity problem for folder %s (saved %d; got %d); skipping it" % \
(folder.getname(), saved, new))
def loadmessagelist(s, repos, folder):
if s.verbose > 0:
s._msg("Loading message list for %s[%s]" % (s.getnicename(repos),
folder.getname()))
def messagelistloaded(s, repos, folder, count):
if s.verbose > 0:
s._msg("Message list for %s[%s] loaded: %d messages" % \
(s.getnicename(repos), folder.getname(), count))
############################## Message syncing
def syncingmessages(s, sr, sf, dr, df):
if s.verbose > 0:
s._msg("Syncing messages %s[%s] -> %s[%s]" % (s.getnicename(sr),
sf.getname(),
s.getnicename(dr),
df.getname()))
def copyingmessage(s, uid, src, destlist):
if s.verbose >= 0:
ds = s.folderlist(destlist)
s._msg("Copy message %d %s[%s] -> %s" % (uid, s.getnicename(src),
src.getname(), ds))
def deletingmessage(s, uid, destlist):
if s.verbose >= 0:
ds = s.folderlist(destlist)
s._msg("Deleting message %d in %s" % (uid, ds))
def deletingmessages(s, uidlist, destlist):
if s.verbose >= 0:
ds = s.folderlist(destlist)
s._msg("Deleting %d messages (%s) in %s" % \
(len(uidlist),
", ".join([str(u) for u in uidlist]),
ds))
def addingflags(s, uidlist, flags, destlist):
if s.verbose >= 0:
ds = s.folderlist(destlist)
s._msg("Adding flags %s to %d messages on %s" % \
(", ".join(flags), len(uidlist), ds))
def deletingflags(s, uidlist, flags, destlist):
if s.verbose >= 0:
ds = s.folderlist(destlist)
s._msg("Deleting flags %s to %d messages on %s" % \
(", ".join(flags), len(uidlist), ds))
################################################## Threads
def getThreadDebugLog(s, thread):
if s.debugmessages.has_key(thread):
message = "\nLast %d debug messages logged for %s prior to exception:\n"\
% (len(s.debugmessages[thread]), thread.getName())
message += "\n".join(s.debugmessages[thread])
else:
message = "\nNo debug messages were logged for %s." % \
thread.getName()
return message
def delThreadDebugLog(s, thread):
if s.debugmessages.has_key(thread):
del s.debugmessages[thread]
def getThreadExceptionString(s, thread):
message = "Thread '%s' terminated with exception:\n%s" % \
(thread.getName(), thread.getExitStackTrace())
message += "\n" + s.getThreadDebugLog(thread)
return message
def threadException(s, thread):
"""Called when a thread has terminated with an exception.
The argument is the ExitNotifyThread that has so terminated."""
s._msg(s.getThreadExceptionString(thread))
s.delThreadDebugLog(thread)
s.terminate(100)
def getMainExceptionString(s):
sbuf = StringIO()
traceback.print_exc(file = sbuf)
return "Main program terminated with exception:\n" + \
sbuf.getvalue() + "\n" + \
s.getThreadDebugLog(threading.currentThread())
def mainException(s):
s._msg(s.getMainExceptionString())
def terminate(s, exitstatus = 0):
"""Called to terminate the application."""
sys.exit(exitstatus)
def threadExited(s, thread):
"""Called when a thread has exited normally. Many UIs will
just ignore this."""
s.delThreadDebugLog(thread)
s.unregisterthread(thread)
################################################## Other
def sleep(s, sleepsecs):
"""This function does not actually output anything, but handles
the overall sleep, dealing with updates as necessary. It will,
however, call sleeping() which DOES output something.
Returns 0 if timeout expired, 1 if there is a request to cancel
the timer, and 2 if there is a request to abort the program."""
abortsleep = 0
while sleepsecs > 0 and not abortsleep:
abortsleep = s.sleeping(1, sleepsecs)
sleepsecs -= 1
s.sleeping(0, 0) # Done sleeping.
return abortsleep
def sleeping(s, sleepsecs, remainingsecs):
"""Sleep for sleepsecs, remainingsecs to go.
If sleepsecs is 0, indicates we're done sleeping.
Return 0 for normal sleep, or 1 to indicate a request
to sync immediately."""
s._msg("Next refresh in %d seconds" % remainingsecs)
if sleepsecs > 0:
time.sleep(sleepsecs)
return 0

View File

@ -1,44 +0,0 @@
# UI module directory
# Copyright (C) 2002 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import UIBase, Blinkenlights
try:
import TTY
except ImportError:
pass
try:
import Tkinter
except ImportError:
pass
else:
import Tk
try:
import curses
except ImportError:
pass
else:
import Curses
import Noninteractive
# Must be last
import detector

View File

@ -1,49 +0,0 @@
# Locking debugging code -- temporary
# Copyright (C) 2003 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from threading import *
import traceback
logfile = open("/tmp/logfile", "wt")
loglock = Lock()
class DebuggingLock:
def __init__(self, name):
self.lock = Lock()
self.name = name
def acquire(self, blocking = 1):
self.print_tb("Acquire lock")
self.lock.acquire(blocking)
self.logmsg("===== %s: Thread %s acquired lock\n" % (self.name, currentThread().getName()))
def release(self):
self.print_tb("Release lock")
self.lock.release()
def logmsg(self, msg):
loglock.acquire()
logfile.write(msg + "\n")
logfile.flush()
loglock.release()
def print_tb(self, msg):
self.logmsg(".... %s: Thread %s attempting to %s\n" % \
(self.name, currentThread().getName(), msg) + \
"\n".join(traceback.format_list(traceback.extract_stack())))

View File

@ -1,52 +0,0 @@
# UI base class
# Copyright (C) 2002 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import offlineimap.ui
import sys
def findUI(config, chosenUI=None):
uistrlist = ['Tk.Blinkenlights', 'Tk.VerboseUI',
'Curses.Blinkenlights', 'TTY.TTYUI',
'Noninteractive.Basic', 'Noninteractive.Quiet']
namespace={}
for ui in dir(offlineimap.ui):
if ui.startswith('_') or ui=='detector':
continue
namespace[ui]=getattr(offlineimap.ui, ui)
if chosenUI is not None:
uistrlist = [chosenUI]
elif config.has_option("general", "ui"):
uistrlist = config.get("general", "ui").replace(" ", "").split(",")
for uistr in uistrlist:
uimod = getUImod(uistr, config.getlocaleval(), namespace)
if uimod:
uiinstance = uimod(config)
if uiinstance.isusable():
return uiinstance
sys.stderr.write("ERROR: No UIs were found usable!\n")
sys.exit(200)
def getUImod(uistr, localeval, namespace):
try:
uimod = localeval.eval(uistr, namespace)
except (AttributeError, NameError), e:
#raise
return None
return uimod

View File

@ -1,110 +0,0 @@
productname = 'OfflineIMAP'
versionstr = "4.0.7"
revno = long('$Rev: 592 $'[6:-2])
revstr = "Rev %d" % revno
datestr = '$Date: 2004-08-01 16:50:23 -0500 (Sun, 01 Aug 2004) $'
versionlist = versionstr.split(".")
major = versionlist[0]
minor = versionlist[1]
patch = versionlist[2]
copyright = "Copyright (C) 2002 - 2004 John Goerzen"
author = "John Goerzen"
author_email = "jgoerzen@complete.org"
description = "Disconnected Universal IMAP Mail Synchronization/Reader Support"
bigcopyright = """%(productname)s %(versionstr)s (%(revstr)s)
%(copyright)s <%(author_email)s>""" % locals()
banner = bigcopyright + """
This software comes with ABSOLUTELY NO WARRANTY; see the file
COPYING for details. This is free software, and you are welcome
to distribute it under the conditions laid out in COPYING."""
homepage = "http://www.quux.org/devel/offlineimap"
homegopher = "gopher://quux.org/1/devel/offlineimap"
license = """Copyright (C) 2002 - 2004 John Goerzen <jgoerzen@complete.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA"""
cmdhelp = """
offlineimap [ -1 ] [ -P profiledir ] [ -a accountlist ] [
-c configfile ] [ -d debugtype[,debugtype...] ] [ -o ] [
-u interface ]
offlineimap -h | --help
-1 Disable all multithreading operations and use
solely a single-thread sync. This effectively sets
the maxsyncaccounts and all maxconnections configu-
ration file variables to 1.
-P profiledir
Sets OfflineIMAP into profile mode. The program
will create profiledir (it must not already exist).
As it runs, Python profiling information about each
thread is logged into profiledir. Please note:
This option is present for debugging and optimiza-
tion only, and should NOT be used unless you have a
specific reason to do so. It will significantly
slow program performance, may reduce reliability,
and can generate huge amounts of data. You must
use the -1 option when you use -P.
-a accountlist
Overrides the accounts section in the config file.
Lets you specify a particular account or set of
accounts to sync without having to edit the config
file. You might use this to exclude certain
accounts, or to sync some accounts that you nor-
mally prefer not to.
-c configfile
Specifies a configuration file to use in lieu of
the default, ~/.offlineimaprc.
-d debugtype[,debugtype...]
Enables debugging for OfflineIMAP. This is useful
if you are trying to track down a malfunction or
figure out what is going on under the hood. I sug-
gest that you use this with -1 in order to make the
results more sensible.
-d now requires one or more debugtypes, separated
by commas. These define what exactly will be
debugged, and so far include two options: imap and
maildir. The imap option will enable IMAP protocol
stream and parsing debugging. Note that the output
may contain passwords, so take care to remove that
from the debugging output before sending it to any-
one else. The maildir option will enable debugging
for certain Maildir operations.
-o Run only once, ignoring any autorefresh setting in
the config file.
-h, --help
Show summary of options.
-u interface
Specifies an alternative user interface module to
use. This overrides the default specified in the
configuration file. The UI specified with -u will
be forced to be used, even if its isuable() method
states that it cannot be. Use this option with
care. The pre-defined options are listed in the
USER INTERFACES section.
"""

View File

@ -1,41 +0,0 @@
#!/usr/bin/env python2.3
# $Id: setup.py,v 1.1 2002/06/21 18:10:49 jgoerzen Exp $
# IMAP synchronization
# Module: installer
# COPYRIGHT #
# Copyright (C) 2002 John Goerzen
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# END OF COPYRIGHT #
from distutils.core import setup
import offlineimap.version
setup(name = "offlineimap",
version = offlineimap.version.versionstr,
description = offlineimap.version.description,
author = offlineimap.version.author,
author_email = offlineimap.version.author_email,
url = offlineimap.version.homepage,
packages = ['offlineimap', 'offlineimap.folder',
'offlineimap.repository', 'offlineimap.ui'],
scripts = ['bin/offlineimap'],
license = offlineimap.version.copyright + \
", Licensed under the GPL version 2"
)