mirror of
https://git.proxmox.com/git/proxmox-spamassassin
synced 2025-04-28 16:01:29 +00:00
215 lines
5.6 KiB
Perl
215 lines
5.6 KiB
Perl
# <@LICENSE>
|
|
# Licensed to the Apache Software Foundation (ASF) under one or more
|
|
# contributor license agreements. See the NOTICE file distributed with
|
|
# this work for additional information regarding copyright ownership.
|
|
# The ASF licenses this file to you under the Apache License, Version 2.0
|
|
# (the "License"); you may not use this file except in compliance with
|
|
# the License. You may obtain a copy of the License at:
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
# </@LICENSE>
|
|
|
|
=head1 NAME
|
|
|
|
Mail::SpamAssassin::AICache - provide access to cached information for
|
|
ArchiveIterator
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
This module allows ArchiveIterator to use cached atime information instead of
|
|
having to read every message separately.
|
|
|
|
=head1 PUBLIC METHODS
|
|
|
|
=over 4
|
|
|
|
=cut
|
|
|
|
package Mail::SpamAssassin::AICache;
|
|
|
|
use File::Spec;
|
|
use File::Path;
|
|
use File::Basename;
|
|
use Mail::SpamAssassin::Logger;
|
|
|
|
use strict;
|
|
use warnings;
|
|
use re 'taint';
|
|
use Errno qw(EBADF);
|
|
|
|
=item new()
|
|
|
|
Generates a new cache object.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub new {
|
|
my $class = shift;
|
|
$class = ref($class) || $class;
|
|
|
|
my $self = shift;
|
|
if (!defined $self) { $self = {}; }
|
|
|
|
$self->{cache} = {};
|
|
$self->{dirty} = 0;
|
|
$self->{prefix} ||= '/';
|
|
|
|
my $use_cache = 1;
|
|
|
|
# be sure to use rel2abs() here, since otherwise relative paths
|
|
# are broken by the prefix stuff
|
|
if ($self->{type} eq 'dir') {
|
|
$self->{cache_file} = File::Spec->catdir(
|
|
$self->{prefix},
|
|
File::Spec->rel2abs($self->{path}),
|
|
'.spamassassin_cache');
|
|
|
|
my @stat = stat($self->{cache_file});
|
|
@stat or dbg("AIcache: no access to %s: %s", $self->{cache_file}, $!);
|
|
$self->{cache_mtime} = $stat[9] || 0;
|
|
}
|
|
else {
|
|
my @split = File::Spec->splitpath($self->{path});
|
|
$self->{cache_file} = File::Spec->catdir(
|
|
$self->{prefix},
|
|
File::Spec->rel2abs($split[1]),
|
|
join('_', '.spamassassin_cache', $self->{type}, $split[2]));
|
|
|
|
my @stat = stat($self->{cache_file});
|
|
@stat or dbg("AIcache: no access to %s: %s", $self->{cache_file}, $!);
|
|
$self->{cache_mtime} = $stat[9] || 0;
|
|
|
|
# for mbox and mbx, verify whether mtime on cache file is >= mtime of
|
|
# messages file. if it is, use it, otherwise don't.
|
|
@stat = stat($self->{path});
|
|
@stat or dbg("AIcache: no access to %s: %s", $self->{path}, $!);
|
|
if ($stat[9] > $self->{cache_mtime}) {
|
|
$use_cache = 0;
|
|
}
|
|
}
|
|
$self->{cache_file} = File::Spec->canonpath($self->{cache_file});
|
|
|
|
# go ahead and read in the cache information
|
|
local *CACHE;
|
|
if (!$use_cache) {
|
|
# not in use
|
|
} elsif (!open(CACHE, $self->{cache_file})) {
|
|
dbg("AIcache: cannot open AI cache file (%s): %s", $self->{cache_file},$!);
|
|
} else {
|
|
for ($!=0; defined($_=<CACHE>); $!=0) {
|
|
my($k,$v) = split(/\t/, $_);
|
|
next unless (defined $k && defined $v);
|
|
$self->{cache}->{$k} = $v;
|
|
}
|
|
defined $_ || $!==0 or
|
|
$!==EBADF ? dbg("AIcache: error reading from AI cache file: $!")
|
|
: warn "error reading from AI cache file: $!";
|
|
close CACHE
|
|
or die "error closing AI cache file (".$self->{cache_file}."): $!";
|
|
}
|
|
|
|
bless($self,$class);
|
|
$self;
|
|
}
|
|
|
|
sub count {
|
|
my ($self) = @_;
|
|
return keys %{$self->{cache}};
|
|
}
|
|
|
|
sub check {
|
|
my ($self, $name) = @_;
|
|
|
|
return $self->{cache} unless $name;
|
|
|
|
# for dir collections: just use the info on a file, if an entry
|
|
# exists for that file. it's very unlikely that a file will be
|
|
# changed to contain a different Date header, and it's slow to check.
|
|
# return if ($self->{type} eq 'dir' && (stat($name))[9] > $self->{cache_mtime});
|
|
|
|
$name = $self->canon($name);
|
|
return $self->{cache}->{$name};
|
|
}
|
|
|
|
sub update {
|
|
my ($self, $name, $date) = @_;
|
|
|
|
return unless $name;
|
|
$name = $self->canon($name);
|
|
|
|
# if information is different than cached version, set dirty and update
|
|
if (!exists $self->{cache}->{$name} || $self->{cache}->{$name} != $date) {
|
|
$self->{cache}->{$name} = $date;
|
|
$self->{dirty} = 1;
|
|
}
|
|
}
|
|
|
|
sub finish {
|
|
my ($self) = @_;
|
|
|
|
return unless $self->{dirty};
|
|
|
|
# Cache is dirty, so write out new file
|
|
|
|
# create enclosing dir tree, if required
|
|
eval {
|
|
mkpath(dirname($self->{cache_file}));
|
|
1;
|
|
} or do {
|
|
my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
|
|
warn "cannot mkpath for AI cache file ($self->{cache_file}): $eval_stat\n";
|
|
};
|
|
|
|
my $towrite = '';
|
|
while(my($k,$v) = each %{$self->{cache}}) {
|
|
$towrite .= "$k\t$v\n";
|
|
}
|
|
|
|
{
|
|
# ignore signals while we're writing this file
|
|
local $SIG{'INT'} = 'IGNORE';
|
|
local $SIG{'TERM'} = 'IGNORE';
|
|
|
|
if (!open(CACHE, ">".$self->{cache_file}))
|
|
{
|
|
warn "creating AI cache file failed (".$self->{cache_file}."): $!";
|
|
# TODO: should we delete it/clean it up?
|
|
}
|
|
else {
|
|
print CACHE $towrite
|
|
or warn "error writing to AI cache file: $!";
|
|
close CACHE
|
|
or warn "error closing AI cache file (".$self->{cache_file}."): $!";
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
sub canon {
|
|
my ($self, $name) = @_;
|
|
|
|
if ($self->{type} eq 'dir') {
|
|
# strip off dirs, just look at filename
|
|
$name = (File::Spec->splitpath($name))[2];
|
|
}
|
|
else {
|
|
# we may get in a "/path/mbox.offset", so trim to just offset as necessary
|
|
$name =~ s/^.+\.(\d+)$/$1/;
|
|
}
|
|
return $name;
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
1;
|
|
__END__
|