#-*- perl -*- # # Copyright (C) 2001,2002,2003,2004 Ken'ichi Fukamachi # All rights reserved. This program is free software; you can # redistribute it and/or modify it under the same terms as Perl itself. # # $FML: Queue.pm,v 1.47 2004/09/04 03:12:26 fukachan Exp $ # package Mail::Delivery::Queue; use strict; use Carp; use vars qw($Counter @class_list); use File::Spec; use Mail::Delivery::ErrorStatus qw(error_set error error_clear); =head1 NAME Mail::Delivery::Queue - handle queue directory. =head1 SYNOPSIS use Mail::Message; $msg = new Mail::Message; use Mail::Delivery::Queue; my $queue = new Mail::Delivery::Queue { directory => "/some/where" }; # queue in a new message # "/some/where/new/$queue_id" is created. $queue->in( $msg ) || croak("fail to queue in"); # ok to deliver this message ! $queue->setrunnable() || croak("fail to set queue deliverable"); # get the filename of this $queue object my $filename = $queue->filename(); =head1 DESCRIPTION C provides basic manipulation of mail queue. =head1 DIRECTORY STRUCTURE C method assigns a new queue id C<$qid> and filename C<$qf> but not do actual works. C method creates a new queue file C<$qf>. So, C<$qf> follows: $qf = "$queue_dir/new/$qid" When C<$qid> queue is ready to be delivered, you must move the queue file from new/ to active/ by C. To make this queue deliverable, use C method. $queue_dir/new/$qid ---> $queue_dir/active/$qid The actual delivery is done by other modules such as C. C manipulates only queue around things. =head1 METHODS =head2 new($args) constructor. You must specify at least C as $args->{ dirctory } . If C is not specified, C assigns the queue id, queue files to be used. C assigns them but do no actual works. =cut my $default_policy = "oldest"; my $dir_mode = 0755; @class_list = qw(lock new deferred active incoming info sender recipients transport strategy ); # Descriptions: constructor. # Arguments: OBJ($self) HASH_REF($args) # Side Effects: initialize object # Return Value: OBJ sub new { my ($self, $args) = @_; my ($type) = ref($self) || $self; my $dir = $args->{ directory } || croak("specify directory"); my $id = $args->{ id } || _new_queue_id(); my $me = { _directory => $dir, _id => $id, }; # bless ! bless $me, $type; # update queue directory mode $dir_mode = $args->{ directory_mode } || $dir_mode; # initialize directories. -d $dir || $me->_mkdirhier($dir); for my $class (@class_list) { my $fp = sprintf("%s_dir_path", $class); my $_dir = $me->can($fp) ? $me->$fp($id) : undef; -d $_dir || $me->_mkdirhier($_dir); } # hold information for delivery my $qf_new = $me->new_file_path($id); my $files = []; push(@$files, $qf_new); $me->{ _clean_up_files } = $files; return bless $me, $type; } # Descriptions: mkdir recursively. # Arguments: OBJ($self) STR($dir) # Side Effects: none # Return Value: ARRAY or UNDEF sub _mkdirhier { my ($self, $dir) = @_; eval q{ use File::Path; mkpath( [ $dir ], 0, $dir_mode); }; warn($@) if $@; } # Descriptions: return new queue identifier. # Arguments: none # Side Effects: increment counter $Counter # Return Value: STR sub _new_queue_id { $Counter++; return time.".$$.$Counter"; } =head2 id() return the queue id assigned to this object C<$self>. =cut # Descriptions: return object identifier (queue id). # Arguments: OBJ($self) # Side Effects: none # Return Value: STR sub id { my ($self) = @_; $self->{ _id }; } =head2 list( [ $class ] ) return queue list as ARRAY REFERENCE. by default, return a list of queue filenames in C directory. $ra = $queue->list(); for $qid (@$ra) { something for $qid ... } where C<$qid> is like this: 990157187.20792.1 =head2 list_all() return all queue list in all classes. =cut # Descriptions: return queue file list as ARRAY_REF. # Arguments: OBJ($self) STR($class) # Side Effects: none # Return Value: ARRAY_REF sub list { my ($self, $class) = @_; my $fp = $class ? "${class}_dir_path" : "active_dir_path"; my $dir = $self->can($fp) ? $self->$fp() : undef; use DirHandle; my $dh = new DirHandle $dir; if (defined $dh) { my @r = (); my $file; ENTRY: while (defined ($file = $dh->read)) { next ENTRY unless $file =~ /^\d+/o; push(@r, $file); } return $self->_list_ordered_by_policy(\@r); } return []; } # Descriptions: return list of all queue irrespective of validity. # Arguments: OBJ($self) # Side Effects: none # Return Value: ARRAY_REF sub list_all { my ($self) = @_; my (@r) = (); my (%r) = (); for my $class (@class_list) { my $ra = $self->list($class); push(@r, @$ra); } for my $q (sort @r) { $r{ $q } = 1; } @r = keys %r; return \@r; } =head2 set_policy($policy) set queue management policy. =head2 get_policy() return queue management policy. =cut # Descriptions: set queue management policy. # Arguments: OBJ($self) STR($policy) # Side Effects: update $self. # Return Value: none sub set_policy { my ($self, $policy) = @_; if (defined $policy) { $self->{ _policy } = $policy; } } # Descriptions: get queue management policy. # Arguments: OBJ($self) STR($policy) # Side Effects: update $self. # Return Value: none sub get_policy { my ($self, $policy) = @_; return( $self->{ _policy } || $default_policy ); } # Descriptions: return re-ordering queue list. # Arguments: OBJ($self) ARRAY_REF($list) # Side Effects: none # Return Value: ARRAY_REF sub _list_ordered_by_policy { my ($self, $list) = @_; my $policy = $self->get_policy() || $default_policy; if ($policy eq 'oldest') { my (@r) = sort _queue_streategy_oldest @$list; return \@r; } elsif ($policy eq 'newest') { my (@r) = sort _queue_streategy_newest @$list; return \@r; } elsif ($policy eq 'fair_queue' || $policy eq 'fair-queue') { my (@o) = sort _queue_streategy_oldest @$list; my (@n) = sort _queue_streategy_newest @$list; my (@p) = (); my $max = $#o; ENTRY: while ($max >= 0) { push(@p, shift @o); $max--; last ENTRY if $max < 0; push(@p, shift @n); $max--; last ENTRY if $max < 0; } return \@p; } else { for my $q (@$list) { ;} } return $list; } # Descriptions: sort by normal date order. # Arguments: IMPLICIT # Side Effects: none # Return Value: NUM sub _queue_streategy_oldest { my $xa = $a; my $xb = $b; $xa =~ s/\.\d+.*$//; $xb =~ s/\.\d+.*$//; $xa <=> $xb; } # Descriptions: sort by reverse date order. # Arguments: IMPLICIT # Side Effects: none # Return Value: NUM sub _queue_streategy_newest { my $xa = $a; my $xb = $b; $xa =~ s/\.\d+.*$//; $xb =~ s/\.\d+.*$//; $xb <=> $xa; } # Descriptions: update queue info for queue management policy. # Arguments: OBJ($self) HASH_REF($policy_args) # Side Effects: update $self. # Return Value: none sub update_schedule { my ($self, $policy_args) = @_; my $id = $policy_args->{ queue_id } || $self->id(); my $qf_deferred = $self->deferred_file_path($id); # get hints. my $hints = $self->_update_schedule_info($id); my $sleep = $hints->{ sleep } || 300; my $time = time + $sleep; # set expired time. utime $time, $time, $qf_deferred; } # Descriptions: get hints for this queue id. # Arguments: OBJ($self) STR($id) # Side Effects: none # Return Value: none sub _update_schedule_info { my ($self, $id) = @_; my $info = {}; my $file = $self->strategy_file_path($id); use IO::Adapter; my $hint = new IO::Adapter $file; $hint->touch(); $hint->open(); # sleep time my $cur_sleep = $hint->find("SLEEP") || 300; $cur_sleep =~ s/^.*SLEEP\s+//; $cur_sleep =~ s/\s*$//; my $new_sleep = ($cur_sleep || 300 ) * 2; $hint->delete("SLEEP"); $hint->add("SLEEP", [ $new_sleep ]); $info->{ sleep } = $new_sleep; $hint->close(); return $info; } # Descriptions: move deferred queue to active one. # Arguments: OBJ($self) STR($id) # Side Effects: update queue. # Return Value: none sub wakeup_queue { my ($self, $id) = @_; $self->_change_queue_mode($id, "active"); } # Descriptions: move active queue to deferred one. # Arguments: OBJ($self) STR($id) # Side Effects: update queue. # Return Value: none sub sleep_queue { my ($self, $id) = @_; $self->_change_queue_mode($id, "deferred"); } # Descriptions: change mode. # move active queue to deferred one, vice versa. # Arguments: OBJ($self) STR($id) STR($to_mode) # Side Effects: update queue. # Return Value: none sub _change_queue_mode { my ($self, $id, $to_mode) = @_; $id ||= $self->id(); if ($self->lock()) { my $qf_deferred = $self->deferred_file_path($id); my $qf_active = $self->active_file_path($id); my $qstr_args = { queue_id => $id, }; if ($to_mode eq 'active') { if (-f $qf_deferred) { rename($qf_deferred, $qf_active); $self->touch($qf_active); if (-f $qf_active) { $self->log("qid=$id activated."); } else { $self->log("error: qid=$id operation failed."); } } else { $self->log("no such deferred queue qid=$id"); } } elsif ($to_mode eq 'deferred' || $to_mode eq 'defer') { if (-f $qf_active) { rename($qf_active, $qf_deferred); $self->touch($qf_deferred); $self->update_schedule($qstr_args); if (-f $qf_deferred) { $self->log("qid=$id deferred"); } else { $self->log("error: qid=$id operation failed."); } } else { $self->log("no such active queue qid=$id"); } } else { $self->log("invalid mode"); } $self->unlock(); } else { $self->log("qid=$id lock failed."); } } # Descriptions: reschedule queues. wake up queue if needed. # Arguments: OBJ($self) # Side Effects: wake up queue if needed. # Return Value: none sub reschedule { my ($self) = @_; my $q_list = $self->list("deferred"); my $count = 0; my $early = 0; my $total = 0; use File::stat; for my $qid (@$q_list) { my $qf = $self->deferred_file_path($qid); my $st = stat($qf); $total++; if ($st->mtime < time) { $self->wakeup_queue($qid); $count++; } else { $early++; } } if ($count) { $self->log("activate $count queue(s)"); $self->log("$early queue(s) sleeping") if $early; } else { $self->log("$early queue(s) sleeping"); } } =head1 METHODS TO MANIPULATE INFORMATION =head2 getidinfo($id) return information related with the queue id C<$id>. The returned information is id => $id, path => "$dir/active/$id", sender => $sender, recipients => \@recipients, =cut # Descriptions: get information of queue for this object. # Arguments: OBJ($self) STR($id) # Side Effects: none # Return Value: HASH_REF sub getidinfo { my ($self, $id) = @_; my $dir = $self->{ _directory }; my ($fh, $sender, @recipients); # validate if the queue id is given $id ||= $self->id(); # sender use FileHandle; $fh = new FileHandle $self->sender_file_path($id); if (defined $fh) { $sender = $fh->getline; $sender =~ s/[\n\s]*$//o; $fh->close; } # recipient array $fh = new FileHandle $self->recipients_file_path($id); if (defined $fh) { my $buf; ENTRY: while (defined($buf = $fh->getline)) { $buf =~ s/[\n\s]*$//o; push(@recipients, $buf); } $fh->close; } return { id => $id, path => $self->active_file_path($id), sender => $sender || '', recipients => \@recipients || [], }; } # Descriptions: when last modified. # Arguments: OBJ($self) STR($id) # Side Effects: none # Return Value: NUM(oldest unix time) sub last_modified_time { my ($self, $id) = @_; my $min_mtime = time; # queue id. $id ||= $self->id(); use File::stat; for my $class (@class_list) { my $fp = sprintf("%s_file_path", $class); my $file = $self->can($fp) ? $self->$fp($id) : undef; if (-f $file) { my $st = stat($file); my $mtime = $st->mtime(); # find oldest file info. $min_mtime = $min_mtime < $mtime ? $min_mtime : $mtime; } } return $min_mtime; } =head1 LOCK =head2 lock() =head2 unlock() =cut use FileHandle; use Fcntl qw(:DEFAULT :flock); # Descriptions: lock queue. # Arguments: OBJ($self) HASH_REF($args) # Side Effects: flock queue # Return Value: NUM(1 or 0) sub lock { my ($self, $args) = @_; my $wait = defined $args->{ wait } ? $args->{ wait } : 10; my $is_prep = defined $args->{ lock_before_runnable } ? 1 : 0; my $id = $self->id(); my $qf_new = $self->new_file_path($id); my $qf_lock = $self->lock_file_path($id); my $qf_act = $self->active_file_path($id); my $lckfile = $is_prep ? $qf_new : (-f $qf_lock ? $qf_lock : $qf_act); my $fh = new FileHandle $lckfile; eval { local($SIG{ALRM}) = sub { croak("lock timeout");}; alarm( $wait ); flock($fh, &LOCK_EX); $self->{ _lock }->{ _fh } = $fh; }; alarm(0); ($@ =~ /lock timeout/o) ? 0 : 1; } # Descriptions: unlock queue. # Arguments: OBJ($self) # Side Effects: unlock queue by flock(2) # Return Value: NUM(1 or 0) sub unlock { my ($self) = @_; my $fh = $self->{ _lock }->{ _fh } || undef; if (defined $fh) { flock($fh, &LOCK_UN); } } =head2 in($msg) C creates a queue file in C directory (C. C<$msg> is C object by default. If C<$msg> object has print() method, arbitrary C<$msg> is acceptable. REMEMBER YOU MUST DO C for the queue to be delivered. If you not C it, the queue file is removed by C. =cut # Descriptions: create a new queue file. # Arguments: OBJ($self) OBJ($msg) # Side Effects: none # Return Value: 1 or 0 sub in { my ($self, $msg) = @_; my $id = $self->id(); my $qf_qstr = $self->strategy_file_path($id); my $qf_lock = $self->lock_file_path($id); my $qf_new = $self->new_file_path($id); $self->touch($qf_lock) unless -f $qf_lock; $self->touch($qf_qstr) unless -f $qf_qstr; $self->touch($qf_new) unless -f $qf_new; use FileHandle; my $fh = new FileHandle "> $qf_new"; if (defined $fh) { $fh->autoflush(1); $fh->clearerr(); $msg->print($fh); if ($fh->error()) { $self->error_set("write error"); } $fh->close; if ($msg->can('write_count')) { my $write_count = $self->{ _write_count } = $msg->write_count(); use File::stat; my $try_count = 3; my $ok = 0; TRY: while ($try_count-- > 0) { my $st = stat($qf_new); if ($st->size == $write_count) { $ok = 1; last TRY; } sleep 1; } unless ($ok) { $self->error_set("write error: size mismatch"); } } } # check the existence and the size > 0. return( (-e $qf_new && -s $qf_new) ? 1 : 0 ); } # Descriptions: return num of bytes written successfully. # Arguments: OBJ($self) # Side Effects: none # Return Value: NUM sub write_count { my ($self) = @_; return( $self->{ _write_count } || 0 ); } =head2 set($key, $args) $queue->set('sender', $sender); $queue->set('recipients', [ $recipient0, $recipient1 ] ); It sets up delivery information in C and C directories. =cut # Descriptions: set value for key. # Arguments: OBJ($self) STR($key) STR($value) # Side Effects: none # Return Value: same as close() sub set { my ($self, $key, $value) = @_; my $id = $self->id(); my $qf_sender = $self->sender_file_path($id); my $qf_recipients = $self->recipients_file_path($id); my $qf_transport = $self->transport_file_path($id); use FileHandle; if ($key eq 'sender') { my $fh = new FileHandle "> $qf_sender"; if (defined $fh) { $fh->clearerr(); print $fh $value, "\n"; if ($fh->error()) { $self->error_set("write error"); } $fh->close; } } elsif ($key eq 'recipients') { my $fh = new FileHandle ">> $qf_recipients"; if (defined $fh) { $fh->clearerr(); if (ref($value) eq 'ARRAY') { for my $rcpt (@$value) { print $fh $rcpt, "\n";} } if ($fh->error()) { $self->error_set("write error"); } $fh->close; } } elsif ($key eq 'recipient_maps') { my $fh = new FileHandle ">> $qf_recipients"; if (defined $fh) { $fh->clearerr(); if (ref($value) eq 'ARRAY') { for my $map (@$value) { use IO::Adapter; my $obj = new IO::Adapter $map; if (defined $obj) { $obj->open(); my $buf; while ($buf = $obj->get_next_key()) { print $fh $buf, "\n"; } $obj->close(); } } } if ($fh->error()) { $self->error_set("write error"); } $fh->close; } } elsif ($key eq 'transport') { my $fh = new FileHandle "> $qf_transport"; if (defined $fh) { $fh->clearerr(); print $fh $value, "\n"; if ($fh->error()) { $self->error_set("write error"); } $fh->close; } } } =head2 setrunnable() set the status of the queue assigned to this object C<$self> deliverable. This file is scheduled to be delivered. In fact, setrunnable() Cs the queue id file from C directory to C directory like C queue strategy. =cut # Descriptions: enable this object queue to be deliverable. # Arguments: OBJ($self) # Side Effects: move $queue_id file from new/ to active/ # Return Value: NUM( 1 (success) or 0 (fail) ) sub setrunnable { my ($self) = @_; my $id = $self->id(); my $qf_new = $self->new_file_path($id); my $qf_active = $self->active_file_path($id); my $qf_sender = $self->sender_file_path($id); my $qf_recipients = $self->recipients_file_path($id); # something error. if ($self->error()) { warn( $self->error() ); return 0; } # There must be a set of these three files. # 1. exisntence unless (-f $qf_new && -f $qf_sender && -f $qf_recipients) { return 0; } # 2. non-zero size. unless (-s $qf_new && -s $qf_sender && -s $qf_recipients) { return 0; } # move new/$id to active/$id if (rename($qf_new, $qf_active)) { return 1; } return 0; } =head2 touch($file) =cut # Descriptions: touch file. # Arguments: OBJ($self) STR($file) # Side Effects: create $file. # Return Value: none sub touch { my ($self, $file) = @_; use FileHandle; my $fh = new FileHandle; if (defined $fh) { $fh->open($file, "a"); $fh->close(); my $now = time; utime $now, $now, $file; } } =head2 remove() remove all queue assigned to this object C<$self>. =head2 valid() check if the queue file is broken or not. return 1 (valid) or 0 (broken). =cut # Descriptions: remove queue files for this object (queue). # Arguments: OBJ($self) # Side Effects: remove queue file(s) # Return Value: none sub remove { my ($self) = @_; my $id = $self->id(); my $count = 0; my $removed = 0; for my $class (@class_list) { my $fp = sprintf("%s_file_path", $class); my $f = $self->can($fp) ? $self->$fp($id) : undef; if (-f $f) { $count++; unlink $f; $removed++ unless -f $f; } } if ($count > 0) { if ($count == $removed) { $self->log("qid=$id removed"); } else { $self->log("qid=$id remove failed"); } } } # Descriptions: this object (queue) is sane as active queue? # Arguments: OBJ($self) # Side Effects: none # Return Value: 1 or 0 sub is_valid_active_queue { my ($self) = @_; my $ok = 0; my $id = $self->id(); # files to check. my $qf_active = $self->active_file_path($id); my $qf_sender = $self->sender_file_path($id); my $qf_recipients = $self->recipients_file_path($id); for my $f ($qf_active, $qf_sender, $qf_recipients) { $ok++ if -f $f && -s $f; } ($ok == 3) ? 1 : 0; } # Descriptions: this object (queue) is sane as active queue? # Arguments: OBJ($self) # Side Effects: none # Return Value: 1 or 0 sub is_valid_queue { my ($self) = @_; my $ok = 0; my $id = $self->id(); # files to check. my $qf_active = $self->active_file_path($id); my $qf_deferred = $self->deferred_file_path($id); my $qf_sender = $self->sender_file_path($id); my $qf_recipients = $self->recipients_file_path($id); for my $f ($qf_sender, $qf_recipients) { $ok++ if -f $f && -s $f; } # XXX You need lock() before calling is_valid_*_queue() method. # # if "-s $qf_active ; rename() in some other process; -s $qf_deferred" # operation is done, this check fails. # make this check more robust, check again. # this logic is stupid and not perfect but effective ? if (-s $qf_active || -s $qf_deferred) { $ok++;} if (-s $qf_active || -s $qf_deferred) { $ok++;} ($ok == 4) ? 1 : 0; } # Descriptions: clear this queue file. # Arguments: OBJ($self) # Side Effects: unlink this queue # Return Value: NUM sub DESTROY { my ($self) = @_; my $files = $self->{ _clean_up_files } || []; for my $file (@$files) { unlink $file if -f $file; } } =head1 UTILITIES =cut # Descriptions: return "lock" directory path. # Arguments: OBJ($self) STR($id) # Side Effects: none # Return Value: STR sub lock_dir_path { my ($self, $id) = @_; my $dir = $self->{ _directory } || croak("directory undefined"); return File::Spec->catfile($dir, "lock"); } # Descriptions: return "lock" file path. # Arguments: OBJ($self) STR($id) # Side Effects: none # Return Value: STR sub lock_file_path { my ($self, $id) = @_; my $dir = $self->{ _directory } || croak("directory undefined"); return File::Spec->catfile($dir, "lock", $id); } # Descriptions: return "incoming" directory path. # Arguments: OBJ($self) STR($id) # Side Effects: none # Return Value: STR sub incoming_dir_path { my ($self, $id) = @_; my $dir = $self->{ _directory } || croak("directory undefined"); return File::Spec->catfile($dir, "incoming"); } # Descriptions: return "incoming" file path. # Arguments: OBJ($self) STR($id) # Side Effects: none # Return Value: STR sub incoming_file_path { my ($self, $id) = @_; my $dir = $self->{ _directory } || croak("directory undefined"); return File::Spec->catfile($dir, "incoming", $id); } # Descriptions: return "new" directory path. # Arguments: OBJ($self) STR($id) # Side Effects: none # Return Value: STR sub new_dir_path { my ($self, $id) = @_; my $dir = $self->{ _directory } || croak("directory undefined"); return File::Spec->catfile($dir, "new"); } # Descriptions: return "new" file path. # Arguments: OBJ($self) STR($id) # Side Effects: none # Return Value: STR sub new_file_path { my ($self, $id) = @_; my $dir = $self->{ _directory } || croak("directory undefined"); return File::Spec->catfile($dir, "new", $id); } # Descriptions: return "active" directory path. # Arguments: OBJ($self) STR($id) # Side Effects: none # Return Value: STR sub active_dir_path { my ($self, $id) = @_; my $dir = $self->{ _directory } || croak("directory undefined"); return File::Spec->catfile($dir, "active"); } # Descriptions: return "active" file path. # Arguments: OBJ($self) STR($id) # Side Effects: none # Return Value: STR sub active_file_path { my ($self, $id) = @_; my $dir = $self->{ _directory } || croak("directory undefined"); return File::Spec->catfile($dir, "active", $id); } # Descriptions: return "deferred" directory path. # Arguments: OBJ($self) STR($id) # Side Effects: none # Return Value: STR sub deferred_dir_path { my ($self, $id) = @_; my $dir = $self->{ _directory } || croak("directory undefined"); return File::Spec->catfile($dir, "deferred"); } # Descriptions: return "deferred" file path. # Arguments: OBJ($self) STR($id) # Side Effects: none # Return Value: STR sub deferred_file_path { my ($self, $id) = @_; my $dir = $self->{ _directory } || croak("directory undefined"); return File::Spec->catfile($dir, "deferred", $id); } # Descriptions: return "info" directory path. # Arguments: OBJ($self) STR($id) # Side Effects: none # Return Value: STR sub info_dir_path { my ($self, $id) = @_; my $dir = $self->{ _directory } || croak("directory undefined"); return File::Spec->catfile($dir, "info"); } # Descriptions: return "info" file path. # Arguments: OBJ($self) STR($id) # Side Effects: none # Return Value: STR sub info_file_path { my ($self, $id) = @_; my $dir = $self->{ _directory } || croak("directory undefined"); return File::Spec->catfile($dir, "info", $id); } # Descriptions: return "sender" directory path. # Arguments: OBJ($self) STR($id) # Side Effects: none # Return Value: STR sub sender_dir_path { my ($self, $id) = @_; my $dir = $self->{ _directory } || croak("directory undefined"); return File::Spec->catfile($dir, "info", "sender"); } # Descriptions: return "sender" file path. # Arguments: OBJ($self) STR($id) # Side Effects: none # Return Value: STR sub sender_file_path { my ($self, $id) = @_; my $dir = $self->{ _directory } || croak("directory undefined"); return File::Spec->catfile($dir, "info", "sender", $id); } # Descriptions: return "recipients" directory path. # Arguments: OBJ($self) STR($id) # Side Effects: none # Return Value: STR sub recipients_dir_path { my ($self, $id) = @_; my $dir = $self->{ _directory } || croak("directory undefined"); return File::Spec->catfile($dir, "info", "recipients"); } # Descriptions: return "recipients" file path. # Arguments: OBJ($self) STR($id) # Side Effects: none # Return Value: STR sub recipients_file_path { my ($self, $id) = @_; my $dir = $self->{ _directory } || croak("directory undefined"); return File::Spec->catfile($dir, "info", "recipients", $id); } # Descriptions: return "transport" directory path. # Arguments: OBJ($self) STR($id) # Side Effects: none # Return Value: STR sub transport_dir_path { my ($self, $id) = @_; my $dir = $self->{ _directory } || croak("directory undefined"); return File::Spec->catfile($dir, "info", "transport"); } # Descriptions: return "transport" file path. # Arguments: OBJ($self) STR($id) # Side Effects: none # Return Value: STR sub transport_file_path { my ($self, $id) = @_; my $dir = $self->{ _directory } || croak("directory undefined"); return File::Spec->catfile($dir, "info", "transport", $id); } # Descriptions: return "strategy" directory path. # Arguments: OBJ($self) STR($id) # Side Effects: none # Return Value: STR sub strategy_dir_path { my ($self, $id) = @_; my $dir = $self->{ _directory } || croak("directory undefined"); return File::Spec->catfile($dir, "info", "strategy"); } # Descriptions: return "strategy" file path. # Arguments: OBJ($self) STR($id) # Side Effects: none # Return Value: STR sub strategy_file_path { my ($self, $id) = @_; my $dir = $self->{ _directory } || croak("directory undefined"); return File::Spec->catfile($dir, "info", "strategy", $id); } =head1 LOG =head2 log() =head2 get_log_function() =head2 set_log_function($fp) =cut # Descriptions: log interface. # Arguments: OBJ($self) STR($s) # Side Effects: none # Return Value: none sub log { my ($self, $s) = @_; my $fp = $self->get_log_function(); my $buf = "qmgr: $s"; if (defined $fp) { eval q{ &$fp($buf);}; if ($@) { carp($@); } } } # Descriptions: return log function pointer. # Arguments: OBJ($self) # Side Effects: none # Return Value: CODE sub get_log_function { my ($self) = @_; return( $self->{ _log_function } || undef ); } # Descriptions: return log function pointer. # Arguments: OBJ($self) CODE($fp) # Side Effects: update $self. # Return Value: CODE sub set_log_function { my ($self, $fp) = @_; $self->{ _log_function } = $fp || undef; } =head1 CODING STYLE See C on fml coding style guide. =head1 AUTHOR Ken'ichi Fukamachi =head1 COPYRIGHT Copyright (C) 2001,2002,2003,2004 Ken'ichi Fukamachi All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 HISTORY Mail::Delivery::Queue first appeared in fml8 mailing list driver package. See C for more details. =cut 1;