use strict;
use warnings;
+$SIG{'PIPE'} = 'IGNORE';
+
use Data::Dumper;
use Errno;
use Fcntl qw(O_WRONLY O_EXCL O_CREAT);
our $SCAN_P_FOLDER = 0.1;
our $SCAN_P_MESSAGE = 0.0625;
our $SCAN_P_FROM = 0.175;
-
-our $folder_sorter =
- sub { my ($a,$b) = @_;
- $a eq 'inbox' and return -1;
- $b eq 'inbox' and return 1;
- return $a cmp $b;
-};
+our @folder_sort_list = (qr/^inbox$/);
+our $folder_sorter = sub { sort_by_list(@_, @folder_sort_list) };
my $mh;
###############################################################################
# Utility procedures
+sub sort_by_list {
+ my $a = shift;
+ my $b = shift;
+
+ for my $i (@_) {
+ my $am = $a =~ $i;
+ my $bm = $b =~ $i;
+ if ($am) {
+ if ($bm) {
+ last;
+ }
+ return -1;
+ } elsif ($bm) {
+ return 1;
+ }
+ }
+
+ return $a cmp $b;
+}
+
sub mkfolder {
my $folder = shift;
my $target;
}
if (@result <= 2) {
- exit(0);
+ return ();
}
STDOUT->autoflush(1);
return @result;
}
+my %msgnum_cache;
sub get_highest_msgnum {
my $mhfolder = shift;
my $dir;
my $highest;
my $msgnum;
+ if (defined($msgnum_cache{$mhfolder})) {
+ return $msgnum_cache{$mhfolder}++;
+ }
+
$dir = "$mh/$mhfolder";
if (not opendir(DIR, $dir)) {
die("opendir($dir): $!");
}
}
- return $highest;
+ $msgnum_cache{$mhfolder} = $highest;
+ return $msgnum_cache{$mhfolder}++;
+}
+
+sub mark {
+ my $folder = shift;
+ my $msgnum = shift;
+ my $seq = shift;
+ my $fn = "$mh/$folder/.mh_sequences";
+ my $fh;
+ my @sequences;
+
+ if (not open($fh, $fn)) {
+ $!{ENOENT} or die("open($fn): $!");
+ } else {
+ @sequences = <$fh>;
+ chomp(@sequences);
+ }
+
+ my $marked = 0;
+ open($fh, '>', $fn) or die("open(>$fn): $!");
+ for $_ (@sequences) {
+ if (/^$seq: (.*)/) {
+ my @parts;
+ my @result;
+ my $done = 0;
+ for my $part (split(' ', $1)) {
+ if (not $done) {
+ my ($st, $en) = split('-', $part);
+ if ((defined($en) and ($msgnum >= $st and $msgnum <= $en))
+ or $msgnum == $st) {
+ # It's already there.
+ $done = 1;
+ }
+ if (defined($en)) {
+ if ($st - 1 == $msgnum) {
+ $part = "$msgnum-$en";
+ $done = 1;
+ } elsif ($en + 1 == $msgnum) {
+ $part = "$st-$msgnum";
+ $done = 1;
+ }
+ } else {
+ if ($part - 1 == $msgnum) {
+ $part = "$msgnum-$part";
+ $done = 1;
+ } elsif ($part + 1 == $msgnum) {
+ $part = "$part-$msgnum";
+ $done = 1;
+ }
+ }
+ }
+ push(@result, $part);
+ }
+ if (not $done) {
+ push(@result, $msgnum);
+ }
+ print($fh "$seq: ", join(' ', @result), "\n");
+ $marked = 1;
+ } else {
+ print($fh "$_\n");
+ }
+ }
+ $marked or print($fh "$seq: $msgnum\n");
+ close($fh) or die("close(>$fn): $!");
}
sub store_message {
if ($mhfolder eq 'SPAM') {
push(@SPAM, $msgnum);
} else {
- $status = system('mark', "+$mhfolder", "$msgnum", '-sequence',
- 'unseen', '-add');
- # XXX need to handle signalled and stopped, and print
- # the exit code or signal number.
- if (not WIFEXITED($status)) {
- die("Failed to run mark");
- } elsif (WEXITSTATUS($status) != 0) {
- die("Failed to mark message unseen.");
- }
+ mark($mhfolder, $msgnum, 'unseen');
}
}
my $header;
my $contents;
my $pair;
- my $match;
+ my $regexp;
my $expression;
my $result;
if ($header =~ /$MAGIC_TO_REGEX/i) {
foreach $contents (@{$headers{lc($header)}}) {
foreach $pair (@filter) {
- ($match, $expression) = @$pair;
- if ($contents =~ /$match/i) {
+ ($regexp, $expression) = @$pair;
+ if ($contents =~ $regexp) {
if (eval "\$result = \"$expression\"") {
return $result;
}
# Walk the list of message headers matching the filter's
# specified header.
foreach $contents (@{$headers{lc($header)}}) {
- # Walk the filter's list of match/expression pairs.
+ # Walk the filter's list of regexp/expression pairs.
foreach $pair (@filter) {
- ($match, $expression) = @$pair;
- if ($contents =~ /$match/i) {
+ ($regexp, $expression) = @$pair;
+ if ($contents =~ $regexp) {
if (eval "\$result = \"$expression\"") {
return $result;
}
}
sub filter_mail {
+ @_ or return ();
my $msgcount = @_ - 2; # don't count . and ..
my $len = length($msgcount);
my @baton;
for ($from, $subject) {
tr/\x00-\x1f\x80-\xff/ /;
}
- print("\r");
- print(' ' x $COLUMNS);
printf("\r\%-${nf}s \%${nm}d \%-${nF}s \%s\n",
substr($mhfolder, 0, $nf), substr($msgnum, 0, $nm),
substr($from, 0, $nF),
$hook->(\%batons, \%headers, $mhfolder, $msgnum);
}
- print("\r");
printf(" \%${len}d SPAM \%${len}d saved \%${len}d/%1d",
$spam, $saved, $spam + $saved, $msgcount);
build_dot_folders($folders, $fh);
}
+# XXX Could use some unification with getfiles...
+sub maildir_spam {
+ my $dir = "$MAILDIR/spam";
+
+ if (not chdir($dir)) {
+ print(STDERR "skipping maildir spam: chdir($dir): $!\n");
+ return;
+ }
+
+ if (not opendir(DIR, '.')) {
+ print(STDERR "skipping maildir spam: opendir($dir): $!\n");
+ return;
+ }
+
+ $! = 0;
+ my @spams = readdir(DIR);
+ if ($! != 0) {
+ print(STDERR "skipping maildir spam: readdir($dir): $!\n");
+ return;
+ }
+
+ closedir(DIR);
+
+ for my $msg (@spams) {
+ ($msg eq '.' or $msg eq '..') and next;
+ store_message($msg, 'SPAM');
+ }
+}
+
\f
MAIN: {
my $st;
}
getfiles('.'));
- $run and update_dot_folders(\%folders);
+ $run and %folders and update_dot_folders(\%folders);
+
+ maildir_spam();
@SPAM and (exec('scan', '+SPAM', @SPAM) or die);
}