mirror of
https://git.code.sf.net/p/archivemail/code
synced 2024-12-21 15:22:59 +00:00
Initial revision
This commit is contained in:
parent
e7b0a0331f
commit
902a81b4bc
5 changed files with 1592 additions and 0 deletions
341
COPYING
Normal file
341
COPYING
Normal file
|
@ -0,0 +1,341 @@
|
|||
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.
|
||||
|
21
README
Normal file
21
README
Normal file
|
@ -0,0 +1,21 @@
|
|||
|
||||
archivemail - archive and compress old mail in your mailbox
|
||||
|
||||
'archivemail' is a tool written in Python for organising and storing old
|
||||
email choking any of your mailboxes. It can move messages older than a
|
||||
certain number of days to a separate 'archive' mailbox which can be
|
||||
compressed with bzip2, gzip or compress.
|
||||
|
||||
For example, have you been subscribing to the 'linux-kernel' mailing list
|
||||
for the last 6 years and ended up with an 160-meg mailbox that 'mutt' is
|
||||
taking a long time to load? 'archivemail' can move all messages that are
|
||||
older than 6 months to a separate compressed mailbox, and leave you with
|
||||
just the most recent messages.
|
||||
|
||||
'archivemail' can save a lot of disk space and will significantly reduce
|
||||
overhead on your mail reader. The number of days before mail is considered
|
||||
'old' is up to you, but the default is 180 days.
|
||||
|
||||
'archivemail' currently works on mbox-format mailboxes, and requires python
|
||||
v2.0 or greater. It also supports deleting old mail instead of archiving
|
||||
it. It currently only works on Unix platforms.
|
26
TODO
Normal file
26
TODO
Normal file
|
@ -0,0 +1,26 @@
|
|||
|
||||
add Maildir support
|
||||
|
||||
add MH support
|
||||
|
||||
start using private variables?
|
||||
|
||||
finish man page
|
||||
|
||||
add option to archive depending on mailbox size threshold
|
||||
+ is this a good idea?
|
||||
|
||||
perserve atime of mailbox properly
|
||||
|
||||
lock any original .gz files (?)
|
||||
|
||||
check for symlink attacks for tempfiles (although we don't use /var/tmp)
|
||||
|
||||
test for write permission before doing anything
|
||||
|
||||
test for missing compression programs
|
||||
+ is this a waste of time?
|
||||
|
||||
add option - do not compress (?)
|
||||
|
||||
Add Makefile with "make install" target ?
|
635
archivemail.1
Normal file
635
archivemail.1
Normal file
|
@ -0,0 +1,635 @@
|
|||
.\" archivemail man page
|
||||
.if !\n(.g \{\
|
||||
. if !\w|\*(lq| \{\
|
||||
. ds lq ``
|
||||
. if \w'\(lq' .ds lq "\(lq
|
||||
. \}
|
||||
. if !\w|\*(rq| \{\
|
||||
. ds rq ''
|
||||
. if \w'\(rq' .ds rq "\(rq
|
||||
. \}
|
||||
.\}
|
||||
.de Id
|
||||
.ds Dt \\$4
|
||||
..
|
||||
.TH archivemail 1 \*(Dt "GNU Project"
|
||||
.SH NAME
|
||||
archivemail \- archive and compress old email
|
||||
.SH SYNOPSIS
|
||||
.B archivemail
|
||||
.RI [ options ]
|
||||
.I FILE
|
||||
.RI [ FILE .\|.\|.]
|
||||
.br
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
.B archivemail
|
||||
archives and compresses and
|
||||
.IR FILE s
|
||||
|
||||
|
||||
|
||||
.IR PATTERN .
|
||||
By default,
|
||||
.B grep
|
||||
prints the matching lines.
|
||||
.PP
|
||||
In addition, two variant programs
|
||||
.B egrep
|
||||
and
|
||||
.B fgrep
|
||||
are available.
|
||||
.B Egrep
|
||||
is the same as
|
||||
.BR "grep\ \-E" .
|
||||
.B Fgrep
|
||||
is the same as
|
||||
.BR "grep\ \-F" .
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BI \-A " NUM" "\fR,\fP \-\^\-after-context=" NUM
|
||||
Print
|
||||
.I NUM
|
||||
lines of trailing context after matching lines.
|
||||
.TP
|
||||
.BR \-a ", " \-\^\-text
|
||||
Process a binary file as if it were text; this is equivalent to the
|
||||
.B \-\^\-binary-files=text
|
||||
option.
|
||||
.TP
|
||||
.BI \-B " NUM" "\fR,\fP \-\^\-before-context=" NUM
|
||||
Print
|
||||
.I NUM
|
||||
lines of leading context before matching lines.
|
||||
.TP
|
||||
\fB\-C\fP [\fINUM\fP], \fB\-\fP\fINUM\fP, \fB\-\^\-context\fP[\fB=\fP\fINUM\fP]
|
||||
Print
|
||||
.I NUM
|
||||
lines (default 2) of output context.
|
||||
.TP
|
||||
.BR \-b ", " \-\^\-byte-offset
|
||||
Print the byte offset within the input file before
|
||||
each line of output.
|
||||
.TP
|
||||
.BI \-\^\-binary-files= TYPE
|
||||
If the first few bytes of a file indicate that the file contains binary
|
||||
data, assume that the file is of type
|
||||
.IR TYPE .
|
||||
By default,
|
||||
.I TYPE
|
||||
is
|
||||
.BR binary ,
|
||||
and
|
||||
.B grep
|
||||
normally outputs either
|
||||
a one-line message saying that a binary file matches, or no message if
|
||||
there is no match.
|
||||
If
|
||||
.I TYPE
|
||||
is
|
||||
.BR without-match ,
|
||||
.B grep
|
||||
assumes that a binary file does not match; this is equivalent to the
|
||||
.B \-I
|
||||
option.
|
||||
If
|
||||
.I TYPE
|
||||
is
|
||||
.BR text ,
|
||||
.B grep
|
||||
processes a binary file as if it were text; this is equivalent to the
|
||||
.B \-a
|
||||
option.
|
||||
.I Warning:
|
||||
.B "grep \-\^\-binary-files=text"
|
||||
might output binary garbage,
|
||||
which can have nasty side effects if the output is a terminal and if the
|
||||
terminal driver interprets some of it as commands.
|
||||
.TP
|
||||
.BR \-c ", " \-\^\-count
|
||||
Suppress normal output; instead print a count of
|
||||
matching lines for each input file.
|
||||
With the
|
||||
.BR \-v ", " \-\^\-invert-match
|
||||
option (see below), count non-matching lines.
|
||||
.TP
|
||||
.BI \-d " ACTION" "\fR,\fP \-\^\-directories=" ACTION
|
||||
If an input file is a directory, use
|
||||
.I ACTION
|
||||
to process it. By default,
|
||||
.I ACTION
|
||||
is
|
||||
.BR read ,
|
||||
which means that directories are read just as if they were ordinary files.
|
||||
If
|
||||
.I ACTION
|
||||
is
|
||||
.BR skip ,
|
||||
directories are silently skipped.
|
||||
If
|
||||
.I ACTION
|
||||
is
|
||||
.BR recurse ,
|
||||
.B grep
|
||||
reads all files under each directory, recursively;
|
||||
this is equivalent to the
|
||||
.B \-r
|
||||
option.
|
||||
.TP
|
||||
.BR \-E ", " \-\^\-extended-regexp
|
||||
Interpret
|
||||
.I PATTERN
|
||||
as an extended regular expression (see below).
|
||||
.TP
|
||||
.BI \-e " PATTERN" "\fR,\fP \-\^\-regexp=" PATTERN
|
||||
Use
|
||||
.I PATTERN
|
||||
as the pattern; useful to protect patterns beginning with
|
||||
.BR \- .
|
||||
.TP
|
||||
.BR \-F ", " \-\^\-fixed-strings
|
||||
Interpret
|
||||
.I PATTERN
|
||||
as a list of fixed strings, separated by newlines,
|
||||
any of which is to be matched.
|
||||
.TP
|
||||
.BI \-f " FILE" "\fR,\fP \-\^\-file=" FILE
|
||||
Obtain patterns from
|
||||
.IR FILE ,
|
||||
one per line.
|
||||
The empty file contains zero patterns, and therefore matches nothing.
|
||||
.TP
|
||||
.BR \-G ", " \-\^\-basic-regexp
|
||||
Interpret
|
||||
.I PATTERN
|
||||
as a basic regular expression (see below). This is the default.
|
||||
.TP
|
||||
.BR \-H ", " \-\^\-with-filename
|
||||
Print the filename for each match.
|
||||
.TP
|
||||
.BR \-h ", " \-\^\-no-filename
|
||||
Suppress the prefixing of filenames on output
|
||||
when multiple files are searched.
|
||||
.TP
|
||||
.B \-\^\-help
|
||||
Output a brief help message.
|
||||
.TP
|
||||
.BR \-I
|
||||
Process a binary file as if it did not contain matching data; this is
|
||||
equivalent to the
|
||||
.B \-\^\-binary-files=without-match
|
||||
option.
|
||||
.TP
|
||||
.BR \-i ", " \-\^\-ignore-case
|
||||
Ignore case distinctions in both the
|
||||
.I PATTERN
|
||||
and the input files.
|
||||
.TP
|
||||
.BR \-L ", " \-\^\-files-without-match
|
||||
Suppress normal output; instead print the name
|
||||
of each input file from which no output would
|
||||
normally have been printed. The scanning will stop
|
||||
on the first match.
|
||||
.TP
|
||||
.BR \-l ", " \-\^\-files-with-matches
|
||||
Suppress normal output; instead print
|
||||
the name of each input file from which output
|
||||
would normally have been printed. The scanning will
|
||||
stop on the first match.
|
||||
.TP
|
||||
.B \-\^\-mmap
|
||||
If possible, use the
|
||||
.BR mmap (2)
|
||||
system call to read input, instead of
|
||||
the default
|
||||
.BR read (2)
|
||||
system call. In some situations,
|
||||
.B \-\^\-mmap
|
||||
yields better performance. However,
|
||||
.B \-\^\-mmap
|
||||
can cause undefined behavior (including core dumps)
|
||||
if an input file shrinks while
|
||||
.B grep
|
||||
is operating, or if an I/O error occurs.
|
||||
.TP
|
||||
.BR \-n ", " \-\^\-line-number
|
||||
Prefix each line of output with the line number
|
||||
within its input file.
|
||||
.TP
|
||||
.BR \-q ", " \-\^\-quiet ", " \-\^\-silent
|
||||
Quiet; suppress normal output. The scanning will stop
|
||||
on the first match.
|
||||
Also see the
|
||||
.B \-s
|
||||
or
|
||||
.B \-\^\-no-messages
|
||||
option below.
|
||||
.TP
|
||||
.BR \-r ", " \-\^\-recursive
|
||||
Read all files under each directory, recursively;
|
||||
this is equivalent to the
|
||||
.B "\-d recurse"
|
||||
option.
|
||||
.TP
|
||||
.BR \-s ", " \-\^\-no-messages
|
||||
Suppress error messages about nonexistent or unreadable files.
|
||||
Portability note: unlike \s-1GNU\s0
|
||||
.BR grep ,
|
||||
traditional
|
||||
.B grep
|
||||
did not conform to \s-1POSIX.2\s0, because traditional
|
||||
.B grep
|
||||
lacked a
|
||||
.B \-q
|
||||
option and its
|
||||
.B \-s
|
||||
option behaved like \s-1GNU\s0
|
||||
.BR grep 's
|
||||
.B \-q
|
||||
option.
|
||||
Shell scripts intended to be portable to traditional
|
||||
.B grep
|
||||
should avoid both
|
||||
.B \-q
|
||||
and
|
||||
.B \-s
|
||||
and should redirect output to /dev/null instead.
|
||||
.TP
|
||||
.BR \-U ", " \-\^\-binary
|
||||
Treat the file(s) as binary. By default, under MS-DOS and MS-Windows,
|
||||
.BR grep
|
||||
guesses the file type by looking at the contents of the first 32KB
|
||||
read from the file. If
|
||||
.BR grep
|
||||
decides the file is a text file, it strips the CR characters from the
|
||||
original file contents (to make regular expressions with
|
||||
.B ^
|
||||
and
|
||||
.B $
|
||||
work correctly). Specifying
|
||||
.B \-U
|
||||
overrules this guesswork, causing all files to be read and passed to the
|
||||
matching mechanism verbatim; if the file is a text file with CR/LF
|
||||
pairs at the end of each line, this will cause some regular
|
||||
expressions to fail.
|
||||
This option has no effect on platforms other than MS-DOS and
|
||||
MS-Windows.
|
||||
.TP
|
||||
.BR \-u ", " \-\^\-unix-byte-offsets
|
||||
Report Unix-style byte offsets. This switch causes
|
||||
.B grep
|
||||
to report byte offsets as if the file were Unix-style text file, i.e. with
|
||||
CR characters stripped off. This will produce results identical to running
|
||||
.B grep
|
||||
on a Unix machine. This option has no effect unless
|
||||
.B \-b
|
||||
option is also used;
|
||||
it has no effect on platforms other than MS-DOS and MS-Windows.
|
||||
.TP
|
||||
.BR \-V ", " \-\^\-version
|
||||
Print the version number of
|
||||
.B grep
|
||||
to standard error. This version number should
|
||||
be included in all bug reports (see below).
|
||||
.TP
|
||||
.BR \-v ", " \-\^\-invert-match
|
||||
Invert the sense of matching, to select non-matching lines.
|
||||
.TP
|
||||
.BR \-w ", " \-\^\-word-regexp
|
||||
Select only those lines containing matches that form whole words.
|
||||
The test is that the matching substring must either be at the
|
||||
beginning of the line, or preceded by a non-word constituent
|
||||
character. Similarly, it must be either at the end of the line
|
||||
or followed by a non-word constituent character. Word-constituent
|
||||
characters are letters, digits, and the underscore.
|
||||
.TP
|
||||
.BR \-x ", " \-\^\-line-regexp
|
||||
Select only those matches that exactly match the whole line.
|
||||
.TP
|
||||
.B \-y
|
||||
Obsolete synonym for
|
||||
.BR \-i .
|
||||
.TP
|
||||
.BR \-Z ", " \-\^\-null
|
||||
Output a zero byte (the \s-1ASCII\s0
|
||||
.B NUL
|
||||
character) instead of the character that normally follows a file name.
|
||||
For example,
|
||||
.B "grep \-lZ"
|
||||
outputs a zero byte after each file name instead of the usual newline.
|
||||
This option makes the output unambiguous, even in the presence of file
|
||||
names containing unusual characters like newlines. This option can be
|
||||
used with commands like
|
||||
.BR "find \-print0" ,
|
||||
.BR "perl \-0" ,
|
||||
.BR "sort \-z" ,
|
||||
and
|
||||
.B "xargs \-0"
|
||||
to process arbitrary file names,
|
||||
even those that contain newline characters.
|
||||
.SH "REGULAR EXPRESSIONS"
|
||||
.PP
|
||||
A regular expression is a pattern that describes a set of strings.
|
||||
Regular expressions are constructed analogously to arithmetic
|
||||
expressions, by using various operators to combine smaller expressions.
|
||||
.PP
|
||||
.B Grep
|
||||
understands two different versions of regular expression syntax:
|
||||
\*(lqbasic\*(rq and \*(lqextended.\*(rq In
|
||||
.RB "\s-1GNU\s0\ " grep ,
|
||||
there is no difference in available functionality using either syntax.
|
||||
In other implementations, basic regular expressions are less powerful.
|
||||
The following description applies to extended regular expressions;
|
||||
differences for basic regular expressions are summarized afterwards.
|
||||
.PP
|
||||
The fundamental building blocks are the regular expressions that match
|
||||
a single character. Most characters, including all letters and digits,
|
||||
are regular expressions that match themselves. Any metacharacter with
|
||||
special meaning may be quoted by preceding it with a backslash.
|
||||
.PP
|
||||
A list of characters enclosed by
|
||||
.B [
|
||||
and
|
||||
.B ]
|
||||
matches any single
|
||||
character in that list; if the first character of the list
|
||||
is the caret
|
||||
.B ^
|
||||
then it matches any character
|
||||
.I not
|
||||
in the list.
|
||||
For example, the regular expression
|
||||
.B [0123456789]
|
||||
matches any single digit. A range of characters
|
||||
may be specified by giving the first and last characters, separated
|
||||
by a hyphen.
|
||||
Finally, certain named classes of characters are predefined.
|
||||
Their names are self explanatory, and they are
|
||||
.BR [:alnum:] ,
|
||||
.BR [:alpha:] ,
|
||||
.BR [:cntrl:] ,
|
||||
.BR [:digit:] ,
|
||||
.BR [:graph:] ,
|
||||
.BR [:lower:] ,
|
||||
.BR [:print:] ,
|
||||
.BR [:punct:] ,
|
||||
.BR [:space:] ,
|
||||
.BR [:upper:] ,
|
||||
and
|
||||
.BR [:xdigit:].
|
||||
For example,
|
||||
.B [[:alnum:]]
|
||||
means
|
||||
.BR [0-9A-Za-z] ,
|
||||
except the latter form depends upon the \s-1POSIX\s0 locale and the
|
||||
\s-1ASCII\s0 character encoding, whereas the former is independent
|
||||
of locale and character set.
|
||||
(Note that the brackets in these class names are part of the symbolic
|
||||
names, and must be included in addition to the brackets delimiting
|
||||
the bracket list.) Most metacharacters lose their special meaning
|
||||
inside lists. To include a literal
|
||||
.B ]
|
||||
place it first in the list. Similarly, to include a literal
|
||||
.B ^
|
||||
place it anywhere but first. Finally, to include a literal
|
||||
.B \-
|
||||
place it last.
|
||||
.PP
|
||||
The period
|
||||
.B .
|
||||
matches any single character.
|
||||
The symbol
|
||||
.B \ew
|
||||
is a synonym for
|
||||
.B [[:alnum:]]
|
||||
and
|
||||
.B \eW
|
||||
is a synonym for
|
||||
.BR [^[:alnum]] .
|
||||
.PP
|
||||
The caret
|
||||
.B ^
|
||||
and the dollar sign
|
||||
.B $
|
||||
are metacharacters that respectively match the empty string at the
|
||||
beginning and end of a line.
|
||||
The symbols
|
||||
.B \e<
|
||||
and
|
||||
.B \e>
|
||||
respectively match the empty string at the beginning and end of a word.
|
||||
The symbol
|
||||
.B \eb
|
||||
matches the empty string at the edge of a word,
|
||||
and
|
||||
.B \eB
|
||||
matches the empty string provided it's
|
||||
.I not
|
||||
at the edge of a word.
|
||||
.PP
|
||||
A regular expression may be followed by one of several repetition operators:
|
||||
.PD 0
|
||||
.TP
|
||||
.B ?
|
||||
The preceding item is optional and matched at most once.
|
||||
.TP
|
||||
.B *
|
||||
The preceding item will be matched zero or more times.
|
||||
.TP
|
||||
.B +
|
||||
The preceding item will be matched one or more times.
|
||||
.TP
|
||||
.BI { n }
|
||||
The preceding item is matched exactly
|
||||
.I n
|
||||
times.
|
||||
.TP
|
||||
.BI { n ,}
|
||||
The preceding item is matched
|
||||
.I n
|
||||
or more times.
|
||||
.TP
|
||||
.BI { n , m }
|
||||
The preceding item is matched at least
|
||||
.I n
|
||||
times, but not more than
|
||||
.I m
|
||||
times.
|
||||
.PD
|
||||
.PP
|
||||
Two regular expressions may be concatenated; the resulting
|
||||
regular expression matches any string formed by concatenating
|
||||
two substrings that respectively match the concatenated
|
||||
subexpressions.
|
||||
.PP
|
||||
Two regular expressions may be joined by the infix operator
|
||||
.BR | ;
|
||||
the resulting regular expression matches any string matching
|
||||
either subexpression.
|
||||
.PP
|
||||
Repetition takes precedence over concatenation, which in turn
|
||||
takes precedence over alternation. A whole subexpression may be
|
||||
enclosed in parentheses to override these precedence rules.
|
||||
.PP
|
||||
The backreference
|
||||
.BI \e n\c
|
||||
\&, where
|
||||
.I n
|
||||
is a single digit, matches the substring
|
||||
previously matched by the
|
||||
.IR n th
|
||||
parenthesized subexpression of the regular expression.
|
||||
.PP
|
||||
In basic regular expressions the metacharacters
|
||||
.BR ? ,
|
||||
.BR + ,
|
||||
.BR { ,
|
||||
.BR | ,
|
||||
.BR ( ,
|
||||
and
|
||||
.BR )
|
||||
lose their special meaning; instead use the backslashed
|
||||
versions
|
||||
.BR \e? ,
|
||||
.BR \e+ ,
|
||||
.BR \e{ ,
|
||||
.BR \e| ,
|
||||
.BR \e( ,
|
||||
and
|
||||
.BR \e) .
|
||||
.PP
|
||||
Traditional
|
||||
.B egrep
|
||||
did not support the
|
||||
.B {
|
||||
metacharacter, and some
|
||||
.B egrep
|
||||
implementations support
|
||||
.B \e{
|
||||
instead, so portable scripts should avoid
|
||||
.B {
|
||||
in
|
||||
.B egrep
|
||||
patterns and should use
|
||||
.B [{]
|
||||
to match a literal
|
||||
.BR { .
|
||||
.PP
|
||||
\s-1GNU\s0
|
||||
.B egrep
|
||||
attempts to support traditional usage by assuming that
|
||||
.B {
|
||||
is not special if it would be the start of an invalid interval
|
||||
specification. For example, the shell command
|
||||
.B "egrep '{1'"
|
||||
searches for the two-character string
|
||||
.B {1
|
||||
instead of reporting a syntax error in the regular expression.
|
||||
\s-1POSIX.2\s0 allows this behavior as an extension, but portable scripts
|
||||
should avoid it.
|
||||
.SH "ENVIRONMENT VARIABLES"
|
||||
.TP
|
||||
.B GREP_OPTIONS
|
||||
This variable specifies default options to be placed in front of any
|
||||
explicit options. For example, if
|
||||
.B GREP_OPTIONS
|
||||
is
|
||||
.BR "'\-\^\-binary-files=without-match \-\^\-directories=skip'" ,
|
||||
.B grep
|
||||
behaves as if the two options
|
||||
.B \-\^\-binary-files=without-match
|
||||
and
|
||||
.B \-\^\-directories=skip
|
||||
had been specified before any explicit options.
|
||||
Option specifications are separated by whitespace.
|
||||
A backslash escapes the next character,
|
||||
so it can be used to specify an option containing whitespace or a backslash.
|
||||
.TP
|
||||
\fBLC_ALL\fP, \fBLC_MESSAGES\fP, \fBLANG\fP
|
||||
These variables specify the
|
||||
.B LC_MESSAGES
|
||||
locale, which determines the language that
|
||||
.B grep
|
||||
uses for messages.
|
||||
The locale is determined by the first of these variables that is set.
|
||||
American English is used if none of these environment variables are set,
|
||||
or if the message catalog is not installed, or if
|
||||
.B grep
|
||||
was not compiled with national language support (\s-1NLS\s0).
|
||||
.TP
|
||||
\fBLC_ALL\fP, \fBLC_CTYPE\fP, \fBLANG\fP
|
||||
These variables specify the
|
||||
.B LC_CTYPE
|
||||
locale, which determines the type of characters, e.g., which
|
||||
characters are whitespace.
|
||||
The locale is determined by the first of these variables that is set.
|
||||
The \s-1POSIX\s0 locale is used if none of these environment variables
|
||||
are set, or if the locale catalog is not installed, or if
|
||||
.B grep
|
||||
was not compiled with national language support (\s-1NLS\s0).
|
||||
.TP
|
||||
.B POSIXLY_CORRECT
|
||||
If set,
|
||||
.B grep
|
||||
behaves as \s-1POSIX.2\s0 requires; otherwise,
|
||||
.B grep
|
||||
behaves more like other \s-1GNU\s0 programs.
|
||||
\s-1POSIX.2\s0 requires that options that follow file names must be
|
||||
treated as file names; by default, such options are permuted to the
|
||||
front of the operand list and are treated as options.
|
||||
Also, \s-1POSIX.2\s0 requires that unrecognized options be diagnosed as
|
||||
\*(lqillegal\*(rq, but since they are not really against the law the default
|
||||
is to diagnose them as \*(lqinvalid\*(rq.
|
||||
.B POSIXLY_CORRECT
|
||||
also disables \fB_\fP\fIN\fP\fB_GNU_nonoption_argv_flags_\fP,
|
||||
described below.
|
||||
.TP
|
||||
\fB_\fP\fIN\fP\fB_GNU_nonoption_argv_flags_\fP
|
||||
(Here
|
||||
.I N
|
||||
is
|
||||
.BR grep 's
|
||||
numeric process ID.) If the
|
||||
.IR i th
|
||||
character of this environment variable's value is
|
||||
.BR 1 ,
|
||||
do not consider the
|
||||
.IR i th
|
||||
operand of
|
||||
.B grep
|
||||
to be an option, even if it appears to be one.
|
||||
A shell can put this variable in the environment for each command it runs,
|
||||
specifying which operands are the results of file name wildcard
|
||||
expansion and therefore should not be treated as options.
|
||||
This behavior is available only with the \s-1GNU\s0 C library, and only
|
||||
when
|
||||
.B POSIXLY_CORRECT
|
||||
is not set.
|
||||
.SH DIAGNOSTICS
|
||||
.PP
|
||||
Normally, exit status is 0 if matches were found,
|
||||
and 1 if no matches were found. (The
|
||||
.B \-v
|
||||
option inverts the sense of the exit status.)
|
||||
Exit status is 2 if there were syntax errors
|
||||
in the pattern, inaccessible input files, or
|
||||
other system errors.
|
||||
.SH BUGS
|
||||
.PP
|
||||
Email bug reports to
|
||||
.BR bug-gnu-utils@gnu.org .
|
||||
Be sure to include the word \*(lqgrep\*(rq somewhere in the
|
||||
\*(lqSubject:\*(rq field.
|
||||
.PP
|
||||
Large repetition counts in the
|
||||
.BI { m , n }
|
||||
construct may cause grep to use lots of memory.
|
||||
In addition,
|
||||
certain other obscure regular expressions require exponential time
|
||||
and space, and may cause
|
||||
.B grep
|
||||
to run out of memory.
|
||||
.PP
|
||||
Backreferences are very slow, and may require exponential time.
|
||||
.\" Work around problems with some troff -man implementations.
|
||||
.br
|
569
archivemail.py
Executable file
569
archivemail.py
Executable file
|
@ -0,0 +1,569 @@
|
|||
#!/usr/bin/python -tt
|
||||
############################################################################
|
||||
# Copyright (C) 2002 Paul Rodger <paul@paulrodger.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (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
|
||||
############################################################################
|
||||
|
||||
"""Archive and compress old mail in mbox-format mailboxes"""
|
||||
|
||||
import atexit
|
||||
import fcntl
|
||||
import getopt
|
||||
import mailbox
|
||||
import os
|
||||
import re
|
||||
import rfc822
|
||||
import string
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
# globals
|
||||
VERSION = "archivemail v0.1.0"
|
||||
COPYRIGHT = """Copyright (C) 2002 Paul Rodger <paul@paulrodger.com>
|
||||
This is free software; see the source for copying conditions. There is NO
|
||||
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."""
|
||||
|
||||
options = None # global instance of the run-time options class
|
||||
stale = None # list of files to delete on abnormal exit
|
||||
|
||||
############## class definitions ###############
|
||||
|
||||
class Stats:
|
||||
"""collect and print statistics per mailbox"""
|
||||
archived = 0
|
||||
mailbox_name = None
|
||||
archive_name = None
|
||||
start_time = 0
|
||||
total = 0
|
||||
|
||||
def __init__(self, mailbox_name, final_archive_name):
|
||||
"""constructor for a new set of statistics - the mailbox names are
|
||||
only used for printing a friendly message"""
|
||||
self.start_time = time.time()
|
||||
self.mailbox_name = mailbox_name
|
||||
self.archive_name = final_archive_name + options.compressor_extension
|
||||
|
||||
def another_message(self):
|
||||
self.total = self.total + 1
|
||||
|
||||
def another_archived(self):
|
||||
self.archived = self.archived + 1
|
||||
|
||||
def display(self):
|
||||
"""Display one line of archive statistics for the mailbox"""
|
||||
end_time = time.time()
|
||||
time_seconds = end_time - self.start_time
|
||||
action = "archived"
|
||||
if options.delete_old_mail:
|
||||
action = "deleted"
|
||||
print "%s: %s %d of %d message(s) in %.1f seconds" % \
|
||||
(self.mailbox_name, action, self.archived, self.total,
|
||||
time_seconds)
|
||||
|
||||
|
||||
class StaleFiles:
|
||||
"""container for remembering stale files to delete on abnormal exit"""
|
||||
archive = None # tempfile for messages to be archived
|
||||
compressed_archive = None # compressed version of the above
|
||||
procmail_lock = None # original_mailbox.lock
|
||||
retain = None # tempfile for messages to be retained
|
||||
|
||||
|
||||
class Options:
|
||||
"""container for storing and setting our runtime options"""
|
||||
archive_suffix = "_archive"
|
||||
compressor = None
|
||||
compressor_extension = None
|
||||
days_old_max = 180
|
||||
delete_old_mail = 0
|
||||
lockfile_attempts = 5 # 5 seconds of waiting
|
||||
lockfile_extension = ".lock"
|
||||
quiet = 0
|
||||
script_name = os.path.basename(sys.argv[0])
|
||||
verbose = 0
|
||||
|
||||
def parse_args(self, args, usage):
|
||||
"""set our runtime options from the command-line arguments"""
|
||||
try:
|
||||
opts, args = getopt.getopt(args, '?IVZd:hqs:vz',
|
||||
["bzip2", "compress", "days=", "delete", "gzip",
|
||||
"help", "quiet", "suffix", "verbose",
|
||||
"version"])
|
||||
except getopt.error, msg:
|
||||
user_error(msg)
|
||||
for o, a in opts:
|
||||
if o == '--delete':
|
||||
self.delete_old_mail = 1
|
||||
if o in ('-d', '--days'):
|
||||
self.days_old_max = string.atoi(a)
|
||||
if (self.days_old_max < 1):
|
||||
user_error("argument to -d must be greater than zero")
|
||||
if (self.days_old_max >= 10000):
|
||||
user_error("argument to -d must be less than 10000")
|
||||
if o in ('-h', '-?', '--help'):
|
||||
print usage
|
||||
sys.exit(0)
|
||||
if o in ('-q', '--quiet'):
|
||||
self.quiet = 1
|
||||
if o in ('-v', '--verbose'):
|
||||
self.verbose = 1
|
||||
if o in ('-s', '--suffix'):
|
||||
self.archive_suffix = a
|
||||
if o in ('-V', '--version'):
|
||||
print VERSION + "\n\n" + COPYRIGHT
|
||||
sys.exit(0)
|
||||
if o in ('-z', '--gzip'):
|
||||
if (self.compressor):
|
||||
user_error("conflicting compression options")
|
||||
self.compressor = "gzip"
|
||||
if o in ('-Z', '--compress'):
|
||||
if (self.compressor):
|
||||
user_error("conflicting compression options")
|
||||
self.compressor = "compress"
|
||||
if o in ('-I', '--bzip2'):
|
||||
if (self.compressor):
|
||||
user_error("conflicting compression options")
|
||||
self.compressor = "bzip2"
|
||||
if not self.compressor:
|
||||
self.compressor = "gzip"
|
||||
extensions = {
|
||||
"compress" : ".Z",
|
||||
"gzip" : ".gz",
|
||||
"bzip2" : ".bz2",
|
||||
}
|
||||
self.compressor_extension = extensions[self.compressor]
|
||||
return args
|
||||
|
||||
|
||||
class Mailbox:
|
||||
""" generic read/writable 'mbox' format mailbox file"""
|
||||
count = 0
|
||||
file = None
|
||||
mbox = None
|
||||
|
||||
def __init__(self):
|
||||
"""constructor: doesn't do much"""
|
||||
pass
|
||||
|
||||
def store(self, msg):
|
||||
"""write one message to the mbox file"""
|
||||
vprint("saving message to file '%s'" % self.file.name)
|
||||
assert(msg.unixfrom)
|
||||
self.file.write(msg.unixfrom)
|
||||
assert(msg.headers)
|
||||
self.file.writelines(msg.headers)
|
||||
self.file.write("\n")
|
||||
|
||||
# The following while loop is about twice as fast in
|
||||
# practice to 'self.file.writelines(msg.fp.readlines())'
|
||||
while 1:
|
||||
body = msg.fp.read(8192)
|
||||
if not body:
|
||||
break
|
||||
self.file.write(body)
|
||||
self.count = self.count + 1
|
||||
|
||||
def unlink(self):
|
||||
"""destroy the whole thing"""
|
||||
if self.file:
|
||||
file_name = self.file.name
|
||||
self.close()
|
||||
vprint("unlinking file '%s'" % self.file.name)
|
||||
os.unlink(file_name)
|
||||
|
||||
def get_size(self):
|
||||
"""determine file size of this mbox file"""
|
||||
assert(self.file.name)
|
||||
return os.path.getsize(self.file.name)
|
||||
|
||||
def close(self):
|
||||
"""close the mbox file"""
|
||||
if not self.file.closed:
|
||||
vprint("closing file '%s'" % self.file.name)
|
||||
self.file.close()
|
||||
|
||||
def read_message(self):
|
||||
"""read one rfc822 message object from the mbox file"""
|
||||
if not self.mbox:
|
||||
self.file.seek(0)
|
||||
self.mbox = mailbox.UnixMailbox(self.file)
|
||||
assert(self.mbox)
|
||||
message = self.mbox.next()
|
||||
return message
|
||||
|
||||
def exclusive_lock(self):
|
||||
"""set an advisory lock on the whole mbox file"""
|
||||
vprint("obtaining exclusive lock on file '%s'" % self.file.name)
|
||||
fcntl.flock(self.file, fcntl.LOCK_EX)
|
||||
|
||||
def exclusive_unlock(self):
|
||||
"""unset any advisory lock on the mbox file"""
|
||||
vprint("dropping exclusive lock on file '%s'" % self.file.name)
|
||||
fcntl.flock(self.file, fcntl.LOCK_UN)
|
||||
|
||||
def procmail_lock(self):
|
||||
"""create a procmail-style .lock file to prevent clashes"""
|
||||
lock_name = self.file.name + options.lockfile_extension
|
||||
attempt = 0
|
||||
while os.path.isfile(lock_name):
|
||||
vprint("lockfile '%s' exists - sleeping..." % lock_name)
|
||||
time.sleep(1)
|
||||
attempt = attempt + 1
|
||||
if (attempt >= options.lockfile_attempts):
|
||||
user_error("Giving up waiting for procmail lock '%s'" % lock_name)
|
||||
vprint("writing lockfile '%s'" % lock_name)
|
||||
lock = open(lock_name, "w")
|
||||
stale.procmail_lock = lock_name
|
||||
lock.close()
|
||||
|
||||
def procmail_unlock(self):
|
||||
"""delete our procmail-style .lock file"""
|
||||
lock_name = self.file.name + options.lockfile_extension
|
||||
vprint("removing lockfile '%s'" % lock_name)
|
||||
os.unlink(lock_name)
|
||||
stale.procmail_lock = None
|
||||
|
||||
def leave_empty(self):
|
||||
"""This should be the same as 'cp /dev/null mailbox'.
|
||||
This will leave a zero-length mailbox file so that mail
|
||||
reading programs don't get upset that the mailbox has been
|
||||
completely deleted."""
|
||||
vprint("turning '%s' into a zero-length file" % self.file.name)
|
||||
atime = os.path.getatime(self.file.name)
|
||||
mtime = os.path.getmtime(self.file.name)
|
||||
blank_file = open(self.file.name, "w")
|
||||
blank_file.close()
|
||||
os.utime(self.file.name, (atime, mtime)) # reset to original timestamps
|
||||
|
||||
|
||||
|
||||
class RetainMailbox(Mailbox):
|
||||
"""a temporary mailbox for holding messages that will be retained in the
|
||||
original mailbox"""
|
||||
def __init__(self):
|
||||
"""constructor - create the temporary file"""
|
||||
temp_name = tempfile.mktemp("archivemail_retain")
|
||||
self.file = open(temp_name, "w")
|
||||
stale.retain = temp_name
|
||||
vprint("opened temporary retain file '%s'" % self.file.name)
|
||||
|
||||
def finalise(self, final_name):
|
||||
"""constructor - create the temporary file"""
|
||||
self.close()
|
||||
|
||||
atime = os.path.getatime(final_name)
|
||||
mtime = os.path.getmtime(final_name)
|
||||
|
||||
vprint("renaming '%s' to '%s'" % (self.file.name, final_name))
|
||||
os.rename(self.file.name, final_name)
|
||||
|
||||
os.utime(final_name, (atime, mtime)) # reset to original timestamps
|
||||
stale.retain = None
|
||||
|
||||
def unlink(self):
|
||||
"""Override the base-class version, removing from stalefiles"""
|
||||
Mailbox.unlink(self)
|
||||
stale.retain = None
|
||||
|
||||
|
||||
class ArchiveMailbox(Mailbox):
|
||||
"""all messages that are too old go here"""
|
||||
final_name = None # this is
|
||||
def __init__(self, final_name):
|
||||
"""copy any pre-existing compressed archive to a temp file which we
|
||||
use as the new soon-to-be compressed archive"""
|
||||
assert(final_name)
|
||||
compressor = options.compressor
|
||||
compressedfilename = final_name + options.compressor_extension
|
||||
|
||||
if os.path.isfile(final_name):
|
||||
user_error("There is already a file named '%s'!" % (final_name))
|
||||
|
||||
temp_name = tempfile.mktemp("archivemail_archive")
|
||||
|
||||
if os.path.isfile(compressedfilename):
|
||||
vprint("file already exists that is named: %s" % compressedfilename)
|
||||
uncompress = "%s -d -c %s > %s" % (compressor,
|
||||
compressedfilename, temp_name)
|
||||
vprint("running uncompressor: %s" % uncompress)
|
||||
stale.archive = temp_name
|
||||
system_or_die(uncompress)
|
||||
|
||||
stale.archive = temp_name
|
||||
self.file = open(temp_name, "a")
|
||||
self.final_name = final_name
|
||||
|
||||
def finalise(self):
|
||||
"""rename the temp file back to the original compressed archive
|
||||
file"""
|
||||
self.close()
|
||||
compressor = options.compressor
|
||||
compressed_archive_name = self.file.name + options.compressor_extension
|
||||
compress = compressor + " " + self.file.name
|
||||
vprint("running compressor: '%s'" % compress)
|
||||
|
||||
stale.compressed_archive = compressed_archive_name
|
||||
system_or_die(compress)
|
||||
stale.archive = None
|
||||
|
||||
compressed_final_name = self.final_name + options.compressor_extension
|
||||
vprint("renaming '%s' to '%s'" % (compressed_archive_name,
|
||||
compressed_final_name))
|
||||
os.rename(compressed_archive_name, compressed_final_name)
|
||||
stale.compressed_archive = None
|
||||
|
||||
|
||||
class OriginalMailbox(Mailbox):
|
||||
"""This is the mailbox that we read messages from to determine if they are
|
||||
too old. We will never write to this file directly except at the end
|
||||
where we override the whole file with the RetainMailbox."""
|
||||
file = None
|
||||
def __init__(self, mailbox_name):
|
||||
"""open the mailbox, ready for reading"""
|
||||
try:
|
||||
self.file = open(mailbox_name, "r")
|
||||
except IOError, msg:
|
||||
user_error(msg)
|
||||
|
||||
|
||||
def main(args = sys.argv[1:]):
|
||||
global options
|
||||
global stale
|
||||
|
||||
options = Options()
|
||||
usage = """Usage: %s [options] mailbox [mailbox...]
|
||||
Moves old mail messages in mbox-format mailboxes to compressed mailbox
|
||||
archives. This is useful for saving space and keeping your mailbox manageable.
|
||||
Options are as follows:
|
||||
-d, --days=<days> archive messages older than <days> days (default: %d)
|
||||
-s, --suffix=<name> suffix for archive filename (default: '%s')
|
||||
-z, --gzip compress the archive using gzip (default)
|
||||
-I, --bzip2 compress the archive using bzip2
|
||||
-Z, --compress compress the archive using compress
|
||||
--delete delete rather than archive old mail (use with caution!)
|
||||
-v, --verbose report lots of extra debugging information
|
||||
-q, --quiet quiet mode - print no statistics (suitable for crontab)
|
||||
-V, --version display version information
|
||||
-h, --help display this message
|
||||
Example: %s linux-devel
|
||||
This will move all messages older than %s days to a file called
|
||||
'linux-devel_archive.gz', deleting them from the original 'linux-devel'
|
||||
mailbox. If the 'linux-devel_archive.gz' mailbox already exists, the
|
||||
newly archived messages are appended.
|
||||
""" % (options.script_name, options.days_old_max, options.archive_suffix,
|
||||
options.script_name, options.days_old_max)
|
||||
|
||||
check_python_version()
|
||||
|
||||
args = options.parse_args(args, usage)
|
||||
if len(args) == 0:
|
||||
print usage
|
||||
sys.exit(1)
|
||||
|
||||
os.umask(077) # saves setting permissions on mailboxes/tempfiles
|
||||
stale = StaleFiles()
|
||||
atexit.register(clean_up)
|
||||
|
||||
for filename in args:
|
||||
tempfile.tempdir = os.path.dirname(filename) # don't use /var/tmp
|
||||
final_archive_name = filename + options.archive_suffix
|
||||
archive_mailbox(mailbox_name = filename,
|
||||
final_archive_name = final_archive_name)
|
||||
|
||||
|
||||
|
||||
######## errors and debug ##########
|
||||
|
||||
def vprint(string):
|
||||
"""this saves putting 'if (verbose) print foo' everywhere"""
|
||||
if options.verbose:
|
||||
print string
|
||||
|
||||
|
||||
def user_error(string):
|
||||
"""fatal error, probably something the user did wrong"""
|
||||
script_name = options.script_name
|
||||
message = "%s: %s\n" % (script_name, string)
|
||||
|
||||
sys.stderr.write(message)
|
||||
sys.exit(1)
|
||||
|
||||
########### operations on a message ############
|
||||
|
||||
def is_too_old(message):
|
||||
"""return true if a message is too old (and should be archived),
|
||||
false otherwise"""
|
||||
date = message.getdate('Date')
|
||||
delivery_date = message.getdate('Delivery-date')
|
||||
use_date = None
|
||||
time_message = None
|
||||
|
||||
if delivery_date:
|
||||
try:
|
||||
time_message = time.mktime(delivery_date)
|
||||
use_date = delivery_date
|
||||
vprint("using message 'Delivery-date' header")
|
||||
except ValueError:
|
||||
pass
|
||||
if date and not use_date:
|
||||
try:
|
||||
time_message = time.mktime(date)
|
||||
use_date = date
|
||||
vprint("using message 'Date' header")
|
||||
except ValueError:
|
||||
pass
|
||||
if not use_date:
|
||||
print message
|
||||
vprint("no valid dates found for message")
|
||||
return 0
|
||||
|
||||
time_now = time.time()
|
||||
if time_message > time_now:
|
||||
time_string = time.asctime(use_date)
|
||||
vprint("warning: message has date in the future: %s !" % time_string)
|
||||
return 0
|
||||
|
||||
secs_old_max = (options.days_old_max * 24 * 60 * 60)
|
||||
days_old = (time_now - time_message) / 24 / 60 / 60
|
||||
vprint("message is %.2f days old" % days_old)
|
||||
|
||||
if ((time_message + secs_old_max) < time_now):
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
############### mailbox operations ###############
|
||||
|
||||
def archive_mailbox(mailbox_name, final_archive_name):
|
||||
"""process and archive the given mailbox name"""
|
||||
archive = None
|
||||
retain = None
|
||||
|
||||
vprint("archiving '%s' to '%s' ..." % (mailbox_name, final_archive_name))
|
||||
stats = Stats(mailbox_name, final_archive_name)
|
||||
|
||||
original = OriginalMailbox(mailbox_name)
|
||||
if original.get_size() == 0:
|
||||
original.close()
|
||||
vprint("skipping '%s' because it is a zero-length file" %
|
||||
original.file.name)
|
||||
if not options.quiet:
|
||||
stats.display()
|
||||
return
|
||||
original.procmail_lock()
|
||||
original.exclusive_lock()
|
||||
|
||||
msg = original.read_message()
|
||||
if not msg:
|
||||
user_error("file '%s' is not in 'mbox' format" % mailbox.file.name)
|
||||
|
||||
while (msg):
|
||||
stats.another_message()
|
||||
message_id = msg.get('Message-ID')
|
||||
vprint("processing message '%s'" % message_id)
|
||||
if is_too_old(msg):
|
||||
stats.another_archived()
|
||||
if options.delete_old_mail:
|
||||
vprint("decision: delete message")
|
||||
else:
|
||||
vprint("decision: archive message")
|
||||
if (not archive):
|
||||
archive = ArchiveMailbox(final_archive_name)
|
||||
archive.store(msg)
|
||||
else:
|
||||
vprint("decision: retain message")
|
||||
if (not retain):
|
||||
retain = RetainMailbox()
|
||||
retain.store(msg)
|
||||
msg = original.read_message()
|
||||
vprint("finished reading messages")
|
||||
|
||||
original.exclusive_unlock()
|
||||
original.close()
|
||||
|
||||
if options.delete_old_mail:
|
||||
# we will never have an archive file
|
||||
if retain:
|
||||
retain.finalise(mailbox_name)
|
||||
else:
|
||||
original.leave_empty()
|
||||
elif archive:
|
||||
archive.finalise()
|
||||
if retain:
|
||||
retain.finalise(mailbox_name)
|
||||
else:
|
||||
original.leave_empty()
|
||||
else:
|
||||
# There was nothing to archive
|
||||
if retain:
|
||||
# retain will be the same as original mailbox -- no point copying
|
||||
retain.close()
|
||||
retain.unlink()
|
||||
|
||||
original.procmail_unlock()
|
||||
if not options.quiet:
|
||||
stats.display()
|
||||
|
||||
|
||||
############### misc functions ###############
|
||||
|
||||
def clean_up():
|
||||
"""This is run on exit to make sure we haven't left any stale
|
||||
files/lockfiles left on the system"""
|
||||
vprint("cleaning up ...")
|
||||
if stale.procmail_lock:
|
||||
vprint("removing stale procmail lock '%s'" % stale.procmail_lock)
|
||||
try: os.unlink(stale.procmail_lock)
|
||||
except (IOError, OSError): pass
|
||||
if stale.retain:
|
||||
vprint("removing stale retain file '%s'" % stale.retain)
|
||||
try: os.unlink(stale.retain)
|
||||
except (IOError, OSError): pass
|
||||
if stale.archive:
|
||||
vprint("removing stale archive file '%s'" % stale.archive)
|
||||
try: os.unlink(stale.archive)
|
||||
except (IOError, OSError): pass
|
||||
if stale.compressed_archive:
|
||||
vprint("removing stale compressed archive file '%s'" %
|
||||
stale.compressed_archive)
|
||||
try: os.unlink(stale.compressed_archive)
|
||||
except (IOError, OSError): pass
|
||||
|
||||
|
||||
def check_python_version():
|
||||
"""make sure we are running with the right version of python"""
|
||||
build = sys.version
|
||||
too_old_error = "requires python v2.0 or greater. Your version is: %s" % build
|
||||
try:
|
||||
version = sys.version_info # we might not even have this function! :)
|
||||
if (version[0] < 2):
|
||||
UserError(too_old_error)
|
||||
except: # I should be catching more specific exceptions
|
||||
UserError(too_old_error)
|
||||
|
||||
|
||||
def system_or_die(command):
|
||||
"""Give a user_error() if the command we ran returned a non-zero status"""
|
||||
rv = os.system(command)
|
||||
if (rv != 0):
|
||||
status = os.WEXITSTATUS(rv)
|
||||
user_error("command '%s' returned status %d" % (command, status))
|
||||
|
||||
|
||||
# this is where it all happens, folks
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in a new issue