The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Mail::TieFolder::mh;

require 5.005_62;
use strict;
use warnings;
use Carp;
use Mail::Internet;
use File::Temp qw/ tempfile /;

our $VERSION = '0.04';

=head1 NAME

Mail::TieFolder::mh - Tied hash interface for mh mail folders

=head1 SYNOPSIS

  use Mail::TieFolder::mh;
  tie (%inbox, 'Mail::TieFolder::mh', 'inbox');

  # get list of all message IDs in folder
  @messageIDs = keys (%inbox);

  # fetch message by ID 
  $msg = $inbox{'9287342.2138749@foo.com'};

=head1 DESCRIPTION

Mail::TieFolder::mh implements a tied hash interface to the mh folder
format.

See L<Mail::TieFolder> for additional info and background.

=cut

sub TIEHASH
{
  my ($class, $folder, $rargs) = @_;

  # warn @Mail::TieFolder::ISA;
  
  my $self={};
  $self->{'folder'} = $folder;
  for(keys %$rargs)
  {
    $self->{$_} = $rargs->{$_};
  }
  bless $self, ref($class) ? ref($class) : $class;
  $self->{'unseen'} = 1 unless exists $self->{'unseen'};

  chomp(my $mhpath = `mhparam Path`);
  die "can't find mh base directory" unless $mhpath;

  # make sure folder exists
  # my $folderpath=$folder;
  # $folderpath=$ENV{HOME}/$mhpath/$1 if $folder =~ /^+(.*)/;
  # `mkdir -p $folderpath`; 

  # BUG -- FIRSTKEY/NEXTKEY won't work if you do a packf after TIEHASH
  open(SCAN, "scan $folder -width 9999 -format '%(msg) %{message-id}' 2>/dev/null |") || die $!;
  my ($num, $id);
  while(<SCAN>)
  {
    chomp;
    ($num, $id) = split;
    $self->id2num($id,$num);
    $self->num2id($num,$id);
    $self->{'firstnum'} = $num unless $self->{'firstnum'};
    $self->{'lastnum'} = $num;
  }
  return $self;
}

sub FETCH
{
  my ($self,$id) = @_;
  chomp($id);
  my $folder = $self->{'folder'};
  my $cmd = "pick $folder --message-id '$id' 2> /dev/null ";
  # warn $cmd;
  chomp(my $msgnum = `$cmd`);
  # warn "\n\n$?\n\n";
  # warn "$id $msgnum\n";
  return undef if $? >> 8;
  return undef unless $msgnum;
  open(MSG, "show -nohead -noshowproc $folder $msgnum |") || die $!;
  # my $msg = join('',<MSG>);
  my $msg = new Mail::Internet \*MSG;
  return undef unless $msg;
  chomp(my $testid = $msg->head->get('Message-Id'));
  # warn "\n$id $testid\n";
  return undef unless $id eq $testid;
  $self->id2num($id,$msgnum);
  $self->num2id($msgnum,$id);
  if ($self->{'unseen'})
  {
    `mark $folder $msgnum -seq unseen`;
    die "cannot mark unseen: message $msgnum in folder $folder" if $? >> 8;
  }
  return $msg;
}

sub FIRSTKEY
{
  my ($self) = @_;
  my $id = $self->num2id($self->{'firstnum'});
  return $id;
}

sub NEXTKEY
{
  my ($self,$id) = @_;
  chomp($id);
  my $nextid;
  for(my $num=$self->id2num($id) + 1; $num <= $self->{'lastnum'}; $num++)
  {
    # warn $num;
    last if ($nextid = $self->num2id($num))
  }
  return $nextid;
}

sub EXISTS
{
  my ($self,$id) = @_;
  chomp($id);
  return $self->FETCH($id);
}

# $h{'new'} to create new
# $h{$id} to overwrite
sub STORE
{
  my ($self,$oldid,$msg) = @_;
  my $oldmsg;
  chomp($oldid);
  chomp(my $newid = $msg->head->get('Message-Id'));
  return undef unless $oldid eq $newid || $oldid eq 'new';
  if ($self->EXISTS($newid))
  {
    return undef if $oldid eq 'new';
    $oldmsg = $self->DELETE($newid); 
  }
  my ($tmpfh, $tmpname) = tempfile();
  print $tmpfh $msg->as_string();
  close $tmpfh;
  my $folder = $self->{'folder'};
  `inc $folder -silent -file $tmpname`;
  die if $? >> 8;
  unlink($tmpname) || die $!;
  $msg = $self->FETCH($newid);
  die unless $msg;
  if ($self->{'unseen'})
  {
    my $msgnum = $self->id2num($newid);
    `mark $folder $msgnum -seq unseen`;
    die "cannot mark unseen: message $msgnum in folder $folder" if $? >> 8;
  }
  return $oldmsg if $oldmsg;
  return $msg;
}

sub DELETE
{
  my ($self,$id) = @_;
  chomp($id);
  my $folder = $self->{'folder'};
  my $msg = $self->FETCH($id);
  return undef unless $msg;
  my $num = $self->id2num($id);
  die unless $num;
  `rmm $folder $num`;
  die if $? >> 8;
  $self->id2num($id,0);
  $self->num2id($num,"");
  return $msg;
}




sub num2id
{
  my $self=shift;
  my $num=shift;
  my $id=shift;
  chomp($id) if $id;
  $self->{'num2id'}{$num} = $id if $id;
  $id = $self->{'num2id'}{$num};
  return $id;
}

sub id2num
{
  my $self=shift;
  my $id=shift;
  my $num=shift;
  chomp($id);
  $self->{'id2num'}{$id} = $num if $num;
  $num = $self->{'id2num'}{$id};
  return $num;
}

=head1 PREREQUISITES AND NOTES

You will need Mail::TieFolder installed in order for the tests to work.

You should use the Mail::TieFolder parent module to access
Mail::TieFolder::mh anyway -- see the Mail::TieFolder perldoc.  You
can get away with using Mail::TieFolder::mh directly for now, but this
is not guaranteed to work in future versions.  

The MH command-line utilities (scan, rmm, pick, rmf, etc...) must be
installed and in the user's PATH in order for Mail::TieFolder::mh to
work.  In this version, I wrapped the mh tools rather than
re-implement the mh algorithms.  This should not be a major
restriction in most cases; if anyone is using MH mail folders, then
the MH tools will virtually always be there.  

If you are looking for a complete re-implementation of the MH folder
access algorithms in Perl, see Mail::Box::MH -- Mark Overmeer seems to
have done a pretty thorough job.  I wrote Mail::TieFolder::mh before
he released Mail::Box::MH, and am considering wrapping his code in a
future version of Mail::TieFolder::mh in order to remove the need for
the MH tools.

=head1 AUTHOR

Steve Traugott, stevegt@TerraLuna.Org

=head1 SEE ALSO

L<mh>, 
L<perltie>, 
L<Mail::TieFolder>,
L<Mail::Box::MH>

=cut

1;

__END__