Step 1 of converting tree to Arch layout
This commit is contained in:
@ -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.
|
@ -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.
|
@ -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
|
||||
|
||||
------------------------------------------------------------------------
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
@ -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')
|
@ -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:
|
@ -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.
|
@ -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
|
||||
|
@ -1,2 +0,0 @@
|
||||
usr/bin
|
||||
usr/sbin
|
@ -1,6 +0,0 @@
|
||||
manual.txt
|
||||
manual.ps
|
||||
manual.pdf
|
||||
manual.html
|
||||
README
|
||||
UPGRADING
|
@ -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
|
@ -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)
|
||||
|
@ -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
@ -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)
|
@ -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.
|
@ -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)
|
||||
|
@ -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
|
@ -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
@ -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)
|
||||
|
@ -1,3 +0,0 @@
|
||||
__all__ = ['ui', 'folder', 'repository', 'mbnames', 'threadutil', 'init']
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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()
|
@ -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])
|
||||
|
@ -1,2 +0,0 @@
|
||||
import Base, IMAP, Maildir, LocalStatus
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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)
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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)
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -1 +0,0 @@
|
||||
__all__ = ['IMAP', 'Base', 'Maildir', 'LocalStatus']
|
@ -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()
|
@ -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()
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
@ -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)
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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())))
|
||||
|
||||
|
@ -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
|
@ -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.
|
||||
|
||||
"""
|
@ -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"
|
||||
)
|
||||
|
Reference in New Issue
Block a user