#!/usr/bin/perl -w use strict; # Ziff! (an offline biff replacement) # # Author: Stefano "Zack" Zacchiroli # Copyright: this software is freely distributed under the term of the GNU # General Public License (GPL). # # Ziff is an offline biff replacement, used to know how many new mails # reside in various mailboxes. # Ziff parse a mutt configuration file (mutt is a really powerful Mail # User Agent!!) to know where user mailboxes reside and then parse all # that mailboxes showing how many new mails are in each of them. # # Try "ziff -h" for command line arguments. # # If no path is given for mutt configuration file, Ziff try to use the # .muttrc file of the user, otherwise use the given mutt configuration # file. # Note that you really don't need mutt to use Ziff, you can create a # fake mutt configuration file formatted as below: # # set folder=~/Mail # mailboxes $MAIL =personal =mylove =mom =mydog =bill_games # # The folder assignmente specify that '=mailbox' is relative to 'folder' # value (i.e. 'folder' value is the base dir for what follow the '=' # sign). # 'mailboxes' line specify a list of space separated mailboxes, # environment variable substitution is performed on mailbox names. # # Enjoy! # # Last modified: Wed, 25 Aug 2004 15:21:13 +0200 zack ######################################################################## # LIBS ######################################################################## use vars qw/ $opt_f $opt_p $opt_h /; use Getopt::Std; use Compress::Zlib; my $use_lsmbox = 1; ######################################################################## # SUBS ######################################################################## # parse a mutt configuration file and return a list containing file # marked as "mailboxes". See mutt documentation for 'mailboxes' command. sub parseMailboxes($); sub parseMailboxes($) { my ($muttrc) = @_; # mutt "set folder=" directive my $setFolderRE = '^[ \t]*set[ \t]*folder[ \t]*=[ \t]*'; # mutt "mailboxes" directive my $mailboxesRE = '^[ \t]*mailboxes[ \t]*'; # reference to environment variable like $VARNAME my $sourceRE = '^[ \t]*source[ \t]*([^\s]*)'; my $varNameRE = '\$([a-zA-Z]\w*)'; my ($name,$passwd,$uid, $gid,$quota,$comment, $gcos,$homedir, $shell,$expire) = getpwnam(getlogin()); my ($folderDir, @mailboxes); my @todo = (); # sourced muttrc open(MUTTRC, "< $muttrc") or die "Can't open mutt configuration file: $muttrc"; while() { # parse mutt configuration file chomp($_); if ($_ =~ /$setFolderRE/) { # is a "set folder=" line $_ =~ s/^[^=]*=(.*)$/$1/; # get 'foler' variable value $folderDir = $_; } elsif ($_ =~ /$mailboxesRE/) { # is a "mailboxes " line $_ =~ s/^[ \t]*mailboxes[ \t]*//; # remove "mailboxes " header push @mailboxes, (split /[ \t]+/, $_); # collect mailbox names } elsif ($_ =~ /$sourceRE/) { # "source" line: remember sourced rc my $filename = $1; $filename =~ s/^~/$ENV{HOME}/; push @todo, $filename; } else { # other muttrc lines # do nothing } } close(MUTTRC); foreach my $filename (@todo) { # recurse on sourced rcs my @tmpMailboxes = parseMailboxes($filename); push @mailboxes, @tmpMailboxes; } if ($folderDir) { # patch '=' with folderDir if defined map { s/=/$folderDir/; } @mailboxes; } map { # patch "~" with home directory s/~/$homedir/; } @mailboxes; map { # patch $VARNAME with value of VARNAME environmente variable if ($_ =~ /$varNameRE/) { # line contains a variable reference my $varname = $_; $varname =~ s/$varNameRE/$1/; $_ =~ s/$varNameRE/$ENV{"$varname"}/g; } } @mailboxes; return(@mailboxes); } # parseMailboxes # Check a line of a mailbox against a status and return a new # status. A status is a triple (mails, oldmails, inHeaderFlag). sub chkMBoxLine($$$$) { my ($line, $mails, $oldmails, $inHeaders) = @_; my $mailStartRE = '^From '; # start of a new mail my $mailStatusRE = '^Status:'; # "Status:" header chomp($line); if ($line =~ /$mailStartRE/) { # mail envelope From $mails++; $inHeaders = 1; } elsif (($line =~ /$mailStatusRE/) and ($inHeaders == 1)) { # "Status:" # if we are still reading headers and current header is a # "Status:" header, we have found an old mail $oldmails++; } elsif ($line =~ /^$/) { # start mail body $inHeaders = 0; } return($mails, $oldmails, $inHeaders); } # chkMBoxLine # check whether a file is gzipped or not sub isGzipped($) { my ($fname) = @_; return ($fname =~ /\.gz$/); } # return number of new mail in a given mailbox sub newMails_old($) { my ($mailbox) = @_; # mailbox to check my ($mails, $oldmails, $inHeaders) = (0, 0, 0); if (isGzipped($mailbox)) { # compressed mailbox my $line; my $gz = gzopen($mailbox, "r"); if (not $gz) { print "Can't open compressed mailbox: $mailbox\n"; return(-1); } while($gz->gzreadline($line) > 0) { ($mails, $oldmails, $inHeaders) = chkMBoxLine($line, $mails, $oldmails, $inHeaders); } $gz->gzclose(); } else { # uncompressed mailbox if (not open(MAILBOX, "< $mailbox")) { # error opening mailbox print "Can't open mailbox: $mailbox\n"; return(-1); } else { # mailbox opened while() { ($mails, $oldmails, $inHeaders) = chkMBoxLine($_, $mails, $oldmails, $inHeaders); } close(MAILBOX); } } return($mails - $oldmails); } # newMails sub newMails($) { my ($mailbox) = @_; if (not $use_lsmbox) { return(newMails_old($mailbox)); } else { open(LS, "lsmbox $mailbox |"); my $line = ; $line = ; $line =~ /^(.*)\s+(\d+)\s+(\d+).*$/; return($2 - $3); close(LS); } } # print an help message sub usage() { print <= 1) { # at least one new mail print "$m contains $newmails new mail(s)\n" if ($newmails != 0); push @newMbs, $m; $totNewMails += $newmails; } else { # no new mails system("touch -a $m"); } } print "New mails to be read: $totNewMails :-(", "("x($totNewMails/10), "\n" unless ($totNewMails == 0); foreach my $m (@newMbs) { system("touch -m $m"); }