The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# -*- perl -*-
#
#  Mail::Spool::Node - adpO - Mail::Spool inode encapsulization
#
#  $Id: Node.pm,v 1.1 2001/12/08 05:52:59 rhandom Exp $
#
#  Copyright (C) 2001, Paul T Seamons
#                      paul@seamons.com
#                      http://seamons.com/
#
#  This package may be distributed under the terms of either the
#  GNU General Public License
#    or the
#  Perl Artistic License
#
#  All rights reserved.
#
#  Please read the perldoc Mail::Spool::Node
#
################################################################

package Mail::Spool::Node;

use strict;
use vars qw($AUTOLOAD $VERSION);
use File::NFSLock 1.10 ();
use IO::File ();

$VERSION = $Mail::Spool::VERSION;

###----------------------------------------------------------------###

sub new {
  my $type  = shift;
  my $class = ref($type) || $type || __PACKAGE__;
  my $self  = @_ && ref($_[0]) ? shift() : {@_};

  bless $self, $class;

  if( ! $self->load_node_properties ){
    return undef;
  }

  return $self;
}

###----------------------------------------------------------------###

sub load_node_properties {
  my $node = shift;
  
  return undef if $node->name =~ /\.NFSLock$/i; # skip lock files
  return undef if $node->name =~ /^\.+$/;       # skip root directory nodes
  return undef if -d $node->filename;           # skip directories

  # looking for stuff like 995909015-80EA68E2D942BA85-forward@b.c-paul@seamons.com
  if( $node->name !~ /^(\d+)-([^\-]+)-([^\-]+)-([^\-]*)$/ ){
    die "Strange file found in spool dir \"".$node->filename."\"\n";
  }

  my($time,$message_id,$to_addr,$from_addr) = ($1,$2,$3,$4);
  $from_addr ||= ''; # allow for undeliverables

  ### unencode the to and from
  foreach ( $to_addr, $from_addr ){
    s/%([a-f0-9]{2})/chr(hex($1))/eig;
  }

  ### store some properties
  $node->time( $time );
  $node->id( $message_id );
  $node->to( $to_addr );
  $node->from( $from_addr );
  
  return 1;
}

###----------------------------------------------------------------###

### is this node up for processing
sub can_process {
  my $node = shift;

  die "No wait property found in mail spool handle"
    if ! defined $node->msh->wait;

  return (time() - $node->time >= $node->msh->wait);
}

sub size {
  my $node = shift;
  return -s $node->filename || 0;
}

###----------------------------------------------------------------###

### exclusive lock (NFS or not)
sub lock_node {
  my $node = shift;
  my $lock = File::NFSLock->new($node->filename,
                                "NONBLOCKING",
                                0,
                                ($node->msh->spool->max_connection_time+2),
                                );
  $node->{lock_error} = $File::NFSLock::errstr;
  return $lock;
}

sub lock_error {
  return shift()->{lock_error};
}

###----------------------------------------------------------------###

sub filehandle {
  my $node = shift;
  my $mode = shift;
  $mode = 'r' if ! $mode || $mode !~ /^(a|w|r|wr)$/;

  my $fh = IO::File->new($node->filename,$mode);

  if( ! $fh ){
    warn "Couldn't open file ".$node->filename." [$!]";
    return undef;
  }
  return $fh;
}

sub filename {
  my $node = shift;
  return $node->msh->spool_dir .'/'. $node->name;
}

sub fallback_filename {
  my $node = shift;
  my $name = join("-",time(),$node->id,$node->to,$node->from);
  return undef if ! defined $node->msh->fallback_dir;
  return $node->msh->fallback_dir .'/'. $name;
}

sub fallback {
  my $node = shift;
  
  if( ! rename($node->filename,
               $node->fallback_filename) ){
    warn "Couldn't rename ".$node->filename." to ".$node->fallback_filename." [$!]";
    unlink $node->filename; # maybe some more error checking
  }
}

sub delete_node {
  my $node = shift;
  unlink $node->filename;
}

###----------------------------------------------------------------###

sub AUTOLOAD {
  my $node = shift;
  my ($method) = $AUTOLOAD =~ /([^:]+)$/;
  die "No method found in \$AUTOLOAD \"$AUTOLOAD\"" unless defined $method;
  
  ### allow for dynamic installation of some subs
  if( $method =~ /^(to|from|id|time|msh|name)$/ ){
    no strict 'refs';
    * { __PACKAGE__ ."::". $method } = sub {
      my $self = shift;
      my $val = $self->{$method};
      $self->{$method} = shift if @_;
      return $val;
    };
    use strict 'refs';
    
    ### now that it is installed, call it again
    return $node->$method( @_ );
  }

  die "Unknown method \"$method\"";
}

sub DESTROY {}

1;



__END__


=head1 NAME

Mail::Spool::Node - Mail Spool inode encapsulization

=head1 SYNOPSIS

  #!/usr/bin/perl -w
  package MySpoolNode;

  use Mail::Spool::Node;
  @ISA = qw(Mail::Spool::Node);

  # OR

  sub new {
    my $self = __PACKAGE__->SUPER::new(@_);

    ### do my own stuff here

    return $self;
  }

=head1 DESCRIPTION

Mail::Spool::Node is intended as an encapsulization
of an inode for use by Mail::Spool::Handle.  It has been
written with the intent of being able to use a database
or other "file" system as a backend.

=head1 PROPERTIES

Properties of Mail::Spool::Node are accessed methods of
the same name.  They may be set by calling the
method and passing the new value as an argument.
For example:

  my $from = $self->from;
  $self->from($new_from);

The following properties are
available:

=over 4

=item to

Returns the "To" email address of this node.

=item from

Returns the "From" email address of this node.

=item id

Returns the message id of this node.

=item time

Returns the time this node was placed in the spool.

=item msh

Returns the mail spool handle that this node is in.

=item name

Returns the filename of this node in the mail spool handle
directory.

=back

=head1 METHODS

=over 4

=item new

Returns a Mail::Spool::Node object.  Arguments in
the form of a hash or hash ref are used to
populate the object.  Also calls load_node_properties.

=item can_process

Returns whether the node is eligible for processing.  This
is based upon how long it has been in the mail spool handle.

=item size

Returns the size of the node in bytes.

=item lock_node

Locks the node to prevent any other process from trying to write
to it.  This is done via File::NFSLock.  Returns the lock object.

=item lock_error

Returns the error of File::NFSLock should something happen during
the locking process.

=item filehandle

Returns an IO::Handle style object opened to the filename of this node.

=item filename

Returns the filename of this node.

=item fallback_filename

Returns the place to put this file in case the node could not
be sent right now.  Returns undef if fallback cannot proceed
(undeliverable).

=item fallback

Actually perform the fallback operation.

=item delete_node

Unlink the node from the directory.

=back

=head1 SEE ALSO

Please see also
L<Mail::Spool>,
L<Mail::Spool::Handle>.

=head1 COPYRIGHT

  Copyright (C) 2001, Paul T Seamons
                      paul@seamons.com
                      http://seamons.com/

  This package may be distributed under the terms of either the
  GNU General Public License
    or the
  Perl Artistic License

  All rights reserved.

=cut