#!/usr/bin/perl # # unrmm This script undeletes a "deleted" MH/NMH mail message. # # usage: # unrmm [-verbose | -help | -Version] ... # # Revision History # 1.0 Initial revision. 140206 # 1.1 Added licensing info. 180531 # # Written by Wayne Morrison, 140206. # # Copyright 2014 Wayne Morrison # # Licensed 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. # use strict; use Getopt::Long qw(:config no_ignore_case_always prefix_pattern=--|-); # # Version information. # my $NAME = "unrmm"; my $VERS = "$NAME version: 1.1"; ############################################################################ # # Options fields. # my %opts = (); # Options. # # Command line arguments. # my @opts = ( 'verbose', # Give verbose output. 'help', # Give a help message. 'Version', # Display the program version. ); my $verbose = 0; # Verbose flag. ############################################################################ my $INBOX = 'inbox'; # Default inbox folder name. my $mbox = $INBOX; # Path to the target NMH folder. my $folderpath; # Mail folder path. my %msgs = (); # List of messages to restore. ############################################################################ main(); exit(0); #----------------------------------------------------------------------------- # Routine: main() # # Purpose: Driver routine. # sub main { $| = 1; # # Munch on the options and arguments. # optsandargs(); # # Get paths to a set of MH folders. # getdirs(); # # Restore the specified messages to an appropriate place in # the inbox. # foreach my $msg (sort(keys(%msgs))) { reanimate($msg); } } #---------------------------------------------------------------------- # Routine: optsandargs() # # Purpose: Parse the command line for options and arguments. # We'll pull all the "+foo" arguments out, and the last # one will be the mail folder we'll be working in. # We'll also save the messages in the %msgs hash. # sub optsandargs { my $errs = 0; # Invalid message names. # # Parse the options. # GetOptions(\%opts,@opts) || usage(); # # Check for some immediate-action options. # usage() if(defined($opts{'help'})); version() if(defined($opts{'Version'})); $verbose = $opts{'verbose'}; # # Peek at the arguments. Save the folder name if a mail folder # was found amongst the arguments. If a message number was found # (with or without a preceding comma), it'll be saved in a message # hash. Anything else will get an error message. # for(my $ind=0; $ind < @ARGV; $ind++) { my $arg = $ARGV[$ind]; # Argument to examine. if($arg =~ /^\+(.*)/) { $mbox = $1; if($mbox eq '') { print STDERR "mailbox name must be specified with \"+\"\n"; exit(2); } } elsif(($arg =~ /^,(\d+)$/) || ($arg =~ /^(\d+)$/)) { $msgs{$1}++; } else { print STDERR "invalid message number \"$arg\"\n"; $errs++; } } # # Bail if we found any errors. # exit(3) if($errs); # # Give a usage message if no messages were given. # usage() if(@ARGV == 0); print "restoring in folder +$mbox\n" if($verbose); } #---------------------------------------------------------------------- # Routine: getdirs() # # Purpose: Get the path to the specified MH folders. # sub getdirs { # # Get the path to the MH inbox. # $folderpath = `mhpath +$mbox`; chomp $folderpath; if($folderpath eq '') { print STDERR "unable to find the path for mailbox \"$mbox\"\n"; exit(1); } } #---------------------------------------------------------------------- # Routine: reanimate() # # Purpose: This routine figures out the inbox message number for the # restored file, and then moves the deleted message to that # inbox file. # sub reanimate { my $fn = shift; # File to save. my $fullfn; # Full filename of message. my $newfn; # New pathname for message. my $newmsgnum; # Message number of new file. # # Get the full path to the deleted file. # $fullfn = "$folderpath/,$fn"; if(! -e $fullfn) { print "deleted message $fn does not exist in $mbox\n"; return; } # # Get the full path to the default restored file. # $newmsgnum = $fn; $newfn = "$folderpath/$fn"; if(-e $newfn) { my $scstr; # Message scan output. # # Get the message number for the last message in the mailbox # and increment it. # $scstr = `scan +$mbox last`; $scstr =~ /^ ?(\d+)[ \+]/; $newmsgnum = $1 + 1; $newfn = "$folderpath/$newmsgnum"; } # # Restore the deleted message path to the new file. # system("mv $fullfn $newfn"); # print "$fullfn -> $newfn\n"; system("scan $newmsgnum | cat -v") if($verbose); } #---------------------------------------------------------------------- # Routine: version() # # Purpose: Print the version number(s) and exit. # sub version { print STDERR "$VERS\n"; exit(0); } #---------------------------------------------------------------------- # Routine: usage() # # Purpose: Give usage message and exit. # sub usage { print STDERR "usage: unrmm [options] ... \n"; print STDERR "\n"; print STDERR "\twhere [options] are:\n"; print STDERR "\t\t-verbose\n"; print STDERR "\t\t-help\n"; print STDERR "\t\t-Version\n"; exit(0); } 1; ############################################################################## =pod =head1 NAME B - restores a deleted MH/NMH mail message =head1 SYNOPSIS unrmm [options] ... =head1 DESCRIPTION B restores "deleted" messages in an MH/NMH mail folder. It will attempt to restore it with the same message number it originally had, but if the original message number is in use, then the restored message will be put at the end of the mailbox's messages. MH and NMH use directories and files for mail folders and mail messages, with everything for a user being stored in either I<~/mail> or I<~/Mail>. (Other directories are possible, but these are the most common.) Each mail folder is its own subdirectory. Each individual mail message is stored in its own file whose name is the message's number in its mailbox. In most cases, deleting a message (with the B command) doesn't actually delete the message, but instead prepends a comma to the message's file name. The comma informs MH not to consider that an "active" mail message. Similarly, when a message is refiled from one folder to another, a copy remains in the original folder but it has a comma prepended to its name. When specifying messages to restore, B I distinguish between deleted messages (those with a comma prepended to the message number) and active messages that haven't been deleted. In practical terms, this means that the message number given on the command line -- "unrmm ,12" and "unrmm 12" will act on the same deleted messages. B tries to restore a message to the same message number it had when it was deleted. If that message number is already in use in the mailbox, then the message will be moved to the end of the mailbox's message list. There are two cases for restored files: =over 4 =item * an active message exists with the deleted message's number The restored message will be moved to the end of the mailbox. =item * an active message does not exist with the deleted message's number The restored message will be given its original (de-comma'd) message number. =back =head1 MAIL FOLDERS By default, the I mail folder will be searched for deleted and active messages. A mail folder may be specified on the command line by preceding the folder name with a plus sign. For example, "unrmm +papers 42" will restore deleted message 42 in the I mail folder. Multiple mail folders may be given on a single command line, but only the last folder will be used for message restoration. Thus, "unrmm +docs 42 +verbiage 88 +papers" will not generate an error, but deleted messages 42 and 88 will only be restored in the I folder. =head1 OPTIONS B takes the following options: =over 4 =item I<-verbose> This option provides verbose information about the operation of B. =item I<-Version> Display the version information for B. =item I<-help> Display a help message. =back =head1 AUTHOR Wayne Morrison, wayne@waynemorrison.com =head1 LICENSE Copyright 2014 Wayne Morrison Licensed 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. =head1 SEE ALSO B, B, B, B =cut