#!/usr/bin/perl -w
#
# playlist 1.0
# Patrick Hearon <patrickh@rice.edu>
#
use Getopt::Long;
use Cwd;

# First, read settings; precedence is defaults < rcfile < args
$home = $ENV{HOME} || $ENV{LOGDIR} || (getpwuid($<))[7] || die "No home directory found: $!\n";

# Establish default settings
$ROOTDIR=cwd();
$LISTSUFFIX="m3u";
$FINDSUFFIX="mp3";
$MASTERLIST="master";
$INDEXFILE="index.html";
$CLEAN=0;
$PRINTHELP=0;
$PRINTVERSION=0;
$VERSION="1.0";
$EMAIL="patrickh\@rice.edu";
$WEBSITE="http://www.owlnet.rice.edu/~patrickh/playlist.html";

# Read settings from the rcfile if it exists and is readable
if ( -f "$home/.playlistrc" && -r "$home/.playlistrc") {
    open(RCFILE, "<$home/.playlistrc") or warn "Your .playlistrc could not be opened: $!.\n";
    while(<RCFILE>) {
	chomp;
	$_ =~ s[#.*][];
	next unless($_ =~ /\w+\s*=\s*.+/); # Skip lines that aren't of the form option=value
	($option, $value) = split(/\s*=\s*/, $_);
	${$option} = "$value";
    }
    close RCFILE;
}

# Examine command-line arguments
# Allowed arguments:
# --root, --rootdir     path to starting directory
# --ls,   --listsuffix  filename extension for lists (without preceding ".")
# --fs,   --findsuffix  filename extension to list (without preceding ".")
# --ml,   --masterlist  name of master list (relative to rootdir)
# --idx,  --indexfile   name of html version of list for each directory
# --c,    --clean       delete existing lists instead of creating new ones
# --s,    --silent      do not print out totals
# --h,    --help        prints this list of options and descriptions and exits
# --v, --V, --version   prints out version number and exits
my $opts_ok = &GetOptions("rootdirs|rootdir|root=s" => \$ROOTDIR,
			  "listsuffix|ls:s"         => \$LISTSUFFIX,
			  "findsuffix|fs:s"         => \$FINDSUFFIX,
			  "masterlist|ml=s"         => \$MASTERLIST,
			  "indexfile|idx=s"         => \$INDEXFILE,
			  "clean|c"                 => \$CLEAN,
			  "silent|s"                => \$SILENT,
			  "help|h"                  => \$PRINTHELP,
			  "version|v|V"             => \$PRINTVERSION,
			  "<>"                      => \&PrintHelp);

PrintHelp("That") unless $opts_ok;

if($PRINTHELP)
{
    PrintVersion();
    PrintHelp();
    exit 0;
}

if($PRINTVERSION)
{
    PrintVersion();
    exit 0;
}

$COUNT = 0;

@ROOTDIRS = split /:/, $ROOTDIR;
# If $CLEAN is set, delete the master file; otherwise
# create empty master list file in rootdir
foreach $dir (@ROOTDIRS) {
    $COUNT = 0;
    next unless ( -d $dir );
    if ($CLEAN) {
	unlink "$dir/$MASTERLIST.$LISTSUFFIX" or warn "Couldn't delete old master list $dir/$MASTERLIST.$LISTSUFFIX: $!\n";
	FileWalk($dir, \&DoNothing, \&Cleanup, \&DoNothing) or die "Oops. $!\n";
    }
    else {
	open(MASTERLIST, ">$dir/$MASTERLIST.$LISTSUFFIX") or die "Can't create new master list $dir/$MASTERLIST.$LISTSUFFIX: $!\n";
	close MASTERLIST;
	FileWalk($dir, \&BuildList, \&CreateList, \&FinishList) or die "Oops. $!\n";
    }
    print "$COUNT $FINDSUFFIX files indexed in $dir\n" unless ($SILENT || $CLEAN);
}

sub DoNothing{}

##########################################################
#   CreateList(cwd)
#   Creates an empty playlist and $INDEXFILE file in the
#   current directory.
##########################################################

sub CreateList
{
    my $cwd = $_[0];
    my $path = $_[0];
    $cwd =~ s[.*/][]; # Sets cwd to just the directory name, not the full monty
    my $currlist = "$cwd.$LISTSUFFIX";
    my $htmlfile = "$path/$INDEXFILE";
    open(CURRLIST, ">$path/$currlist") or die "Can't create new list $path/$currlist: $!\n";
    open(HTMLFILE, ">$htmlfile") or die "Can't create new index file $htmlfile: $!\n";
    htmlInit($htmlfile, $path, "Listing of $FINDSUFFIX files in $cwd");
    close CURRLIST;
    close HTMLFILE;
}

##########################################################
#   Cleanup(cwd)
#   Deletes the output files in a given directory
##########################################################

sub Cleanup
{
    my $cwd = $_[0];
    my $path = $_[0];
    $cwd =~ s[.*/][];
    my $currlist = "$cwd.$LISTSUFFIX";
    my $htmlfile = "$path/$INDEXFILE";
    my @files = ($htmlfile,"$path/$currlist");
    my $ret = unlink @files;
}

##########################################################
#   BuildList(cwd, path-to-$FINDSUFFIXfile)
#   Adds a file ending in $FINDSUFFIX to the output files
##########################################################

sub BuildList
{
    my $cwd = $_[0];
    my $path = $_[0];
    my $filename = $_[1];
    $cwd =~ s[.*/][];
    my $currlist = "$path/$cwd.$LISTSUFFIX";
    my $htmlfile = "$path/$INDEXFILE";
    my $rootfilename = "$path/$filename";
    $rootfilename =~ s[$ROOTDIR/][]o; # Gets the path relative to the rootdir
    open(CURRLIST, ">>$currlist") or die "Can't append to $currlist: $!\n";
    open(HTMLFILE, ">>$htmlfile") or die "Can't append to $htmlfile: $!\n";
    open(MASTERLIST, ">>$MASTERLIST.$LISTSUFFIX") or die "Can't append to $MASTERLIST: $!\n";
    if(( -f $filename ) && ( $filename =~ /\.$FINDSUFFIX$/ )) {
	print CURRLIST "$filename\n";
	print MASTERLIST "$rootfilename\n";
	print HTMLFILE "<li><a href=\"$filename\">$filename</a></li>\n";
	$COUNT++;
    }
    close CURRLIST;
    close HTMLFILE;
    close MASTERLIST;
}

##########################################################
#   FinishList(cwd)
#   Adds closing tags to the html listing
##########################################################

sub FinishList
{
    my $path = $_[0];
    my $htmlfile = "$path/$INDEXFILE";
    open(HTMLFILE, ">>$htmlfile") or die "Can't append to $htmlfile: $!\n";
    print HTMLFILE "</ul>\n";
    print HTMLFILE "<hr><br>\n";
    print HTMLFILE "</body></html>\n";
    close HTMLFILE;
}

##########################################################
#   htmlInit(path-to-htmlfile, cwd, title)
#   Assumes an empty file and sets up header stuff
#   plus adding links to child and parent directories
##########################################################

sub htmlInit
{
    my $file=$_[0];
    my $path=$_[1];
    my $title=$_[2];   
    open(INDEXFILE, ">>$file") or die "Can't append to index file $file: $!\n";
    print INDEXFILE "<html><head>\n";
    print INDEXFILE "<title>$title</title>\n";
    print INDEXFILE "<body bgcolor=#FFFFFF>\n";
    print INDEXFILE "<p><center>$title</center></p><br>\n";
    if (-f "../$INDEXFILE") {
	print INDEXFILE "<p><a href=\"../$INDEXFILE\">Previous directory</a><br><hr>\n";
    }
    print INDEXFILE "<ul>\n";
    opendir DIR, $path or return 0;
    my @dirlist = grep !/^\.{1,2}$/, grep ((-d $_), readdir DIR);
    closedir DIR;
    foreach $dir (sort @dirlist) {
	print INDEXFILE "<li><a href=\"$dir/$INDEXFILE\">$dir</a></li>\n";
    }
    print INDEXFILE "</ul><hr><ul>";
    close INDEXFILE;
}

##########################################################
#    TreeWalk(cwd, func)
#    Applies func to cwd recursively
#    where it's func($cwd)
##########################################################

sub TreeWalk
{
    my $curr = $_[0];
    my $sub = $_[1];
    return 0 if (!defined($curr));
    $curr =~ s!//!/!g;
    chdir $curr or return 0;
    opendir DIR, $curr or return 0;
    my @dirlist = grep !/^\.{1,2}$/, grep ((-d $_), readdir DIR);
    closedir DIR;
    &$sub($curr);
    foreach $dir (sort @dirlist) {
	&TreeWalk("$curr/$dir", $sub) or warn "Warning: $!\n";
	chdir $curr;
    }
    return 1;
}

##########################################################
#    FileWalk(cwd, func, setup, finish)
#    Applies func to every file in every subdirectory of cwd
#    where it's func($cwd, $file);
#    Setup is run once per dir, whereas func is used once
#    per file; it's setup($cwd).
#    Likewise for finish($cwd).
##########################################################

sub FileWalk
{
    (my $cwd, local $sub, local $setup, local $finish) = ($_[0], $_[1], $_[2], $_[3]);
    sub _FileWalkHelper {
	my $cwd = $_[0];
	opendir _FILEWALKDIR, $cwd or warn "Can't open cwd: $!\n";
	@files = readdir _FILEWALKDIR;
	closedir _FILEWALKDIR;
	&$setup($cwd);
	foreach $file (sort @files) {
	    -f $file and -r $file and &$sub($cwd, $file);
	}
	&$finish($cwd);
    }
    return &TreeWalk($cwd, \&_FileWalkHelper);
}

##########################################################
#   PrintHelp()
#   Prints out summary of command-line options.
#   PrintHelp(bad_option)
#   Prints out error message for bad_option plus the
#   summary of valid options.
##########################################################

sub PrintHelp
{
    if($_[0]) {
	my $bad_option = $_[0];
	print "$bad_option is not a valid option\n";
    }
    print "Usage: playlist [option | option=value]\n";
    print "\n";
    print "--root, --rootdir(s)  list of directories to index (separated by colons)\n";
    print "--ls,   --listsuffix  filename extension for lists (without preceding \".\")\n";
    print "--fs,   --findsuffix  filename extension to list (without preceding \".\")\n";
    print "--ml,   --masterlist  name of master list (relative to rootdir)\n";
    print "--idx,  --indexfile   name of html version of list for each directory\n";
    print "--c,    --clean       delete existing lists instead of creating new ones\n";
    print "--s,    --silent      do not print out totals\n";
    print "--h,    --help        prints this list of options plus version and author\n";
    print "--v, --V, --version   prints out the version number\n";
    if($_[0]) {
	exit 0;
    }
}

##########################################################
#   PrintVersion()
#   Prints out the version number and contact information.
##########################################################

sub PrintVersion
{
    print "playlist $VERSION by Patrick Hearon\n";
    print "$EMAIL\n";
    print "$WEBSITE\n";
}
