Server IP : 85.214.239.14 / Your IP : 18.226.222.148 Web Server : Apache/2.4.62 (Debian) System : Linux h2886529.stratoserver.net 4.9.0 #1 SMP Tue Jan 9 19:45:01 MSK 2024 x86_64 User : www-data ( 33) PHP Version : 7.4.18 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare, MySQL : OFF | cURL : OFF | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /usr/share/perl5/Mail/SpamAssassin/ |
Upload File : |
# <@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__