#!/usr/bin/perl
# GoldenPod version 0.2
# Copyright (C) Eskild Hustvedt 2005
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

# The modules we want to use
use strict;				# Make my coding strict
use warnings;				# Warn me!
use Fatal qw/ open chdir mkdir /;	# So I don't have to type or die too much :)
use File::Basename;			# Needed to find out our directory and name
use Cwd;				# Needed for getcwd
use Getopt::Long;			# Commandline parsing
use File::Copy;				# We need to copy files!
# Allow bundling of options with GeteOpt
Getopt::Long::Configure ("bundling", 'prefix_pattern=(--|-)');

# Declare initial variables
my ($PreviousDownloads, $Config, $Verbose, $DryRun, $FirstOnly, $CreatedDir, $DownloadedSomething, $NoLog, $NeedToDownload, $NoCat, $CopyFilesTo, $CopyFilesNumber, $CopyFiles_Delete, $ConfigDir, $WorkingDir, $ProgramLog, $PodcastLog, $DefaultVerbosity, $UserConfigDir, $ConfIsNotEmpty, $PodcastFilter); # Scalars
my (%DownloadQueue, %URLs, %AlreadyDownloaded, %DryRun, %PodNames); # Hashes

# Set some global values
my $CurlVerbosity = "--silent --show-error";	# Default is that curl is silent but displays errors (logged, not STDOUT)
my $Version = "0.2-CVS";			# My version
my $CurlGlobal = "-C -k -L --retry-max-time 60 -A 'GoldenPod $Version - http://goldenpod.nongnu.org/'";# Global curl options
# Curl options:
# -C to continue when possible
# -k for insecure (allow ssl servers with odd certificates)
# -L to follow location hints
# -A sets the user agent string
# --retry-max-time should hopefully explain itself

# - - - - - - - - - - -
# Function declerations
# - - - - - - - - - - -

# The function that actually outputs the help
# This is just because I'm too lazy to type the printf every time
# and this function makes it more practical.
# Syntax is simply: &PrintHelp("shortoption", "longoption", "description")
sub PrintHelp {
	printf "%-4s %-15s %s\n", "$_[0]", "$_[1]", "$_[2]";
}
sub Help {
	my $Command = basename($0);
	&Version;
	print "\nUsage: $Command";
	print "\n  or : $Command [OPTIONS]\n\n";
	&PrintHelp("", "--version", "Display version information and exit");
	&PrintHelp("-h", "--help", "This help screen");
	# Inform the user about the default based upon the value of $DefaultVerbosity
	if ($DefaultVerbosity) {
		&PrintHelp("-v", "--verbose", "Be verbose (default)");
		&PrintHelp("-s", "--silent", "Be silent");
	} else {
		&PrintHelp("-v", "--verbose", "Be verbose");
		&PrintHelp("-s", "--silent", "Be silent (default)");
	}
	&PrintHelp("", "--dumpinfo", "Print the files goldenpod would work with");
	&PrintHelp("-l", "--nolog", "Don't create a message logfile when in non-verbose mode.");
	&PrintHelp("", "", "Has no use with --verbose.");
	&PrintHelp("-c", "--nocat", "Don't create catalogue entries.");
	&PrintHelp("-f", "--first-only", "Only download the first file in any stream,");
	&PrintHelp("", "", "useful if you just want to download the first file.");
	&PrintHelp("", "", "All other podcasts will be written to podcast.log as if");
	&PrintHelp("", "", "you used --dry-run (but does not imply --verbose).");
	&PrintHelp("-r", "--dry-run", "Only write the logfile, don't download anything.");
	&PrintHelp("", "", "Useful when you want to subscribe to a podcast but not");
	&PrintHelp("", "", "download all the old issues. You can edit $PodcastLog");
	&PrintHelp("", "", "afterwards and remove those you want to download.");
	&PrintHelp("", "", "Implies --verbose.");
	&PrintHelp("-p", "--copy [path]", "Copy the last N downloaded files to path.");
	&PrintHelp("", "", "N is either 4 or the number supplied to --files.");
	&PrintHelp("-i", "--files N", "Copy N files instead of 4 (use with --copy)");
	&PrintHelp("-d", "--delete", "Delete all other files in the target --copy");
	&PrintHelp("", "", "directory");
}

sub Version { print "GoldenPod $Version\n by Eskild Hustvedt\nGoldenPod comes with ABSOLUTELY NO WARRANTY!\n"; };

# Routine that checks wether a specific command is in PATH
sub InPath {
        foreach (split /:/, $ENV{PATH}) {
                if (-x "$_/@_") {
                        return 1;
                }
        }
        return 0;
}

# DryRun routine - Add all %DryRun and %DownloadedCasts to the logfile
sub PerformDryRun {
	print "\nPerforming a dry run..." if $Verbose and !$FirstOnly;
	open(LOGFILE, ">>$PodcastLog");
	foreach (keys (%DryRun)) {
		print LOGFILE "$_\n" unless $AlreadyDownloaded{$_};
	}
	print " done\n" if $Verbose and !$FirstOnly;
	close(LOGFILE);
}

# Copy routine.
sub CopyFiles {
	# Declare variables
	my ($NumberOfFiles, @FileList, %Files, @CopyTheseFiles, %DontDeleteThese);
	$NumberOfFiles = $CopyFilesNumber if $CopyFilesNumber;
	$NumberOfFiles = "4" unless $NumberOfFiles;
	# Unless it's an integer we can't continue.
	if ($NumberOfFiles =~ /\D/) {
		print "Error: The option passed to --files ($NumberOfFiles) is not an integer number.\n" and &Help and die "\n";
	}
	# Do a few directory checks before moving on.
	die "The ./catalogue/All/ directory did not exist!\nAre you sure you have downloaded some podcasts?\n" unless -e "./catalogue/All/";
	die "$CopyFilesTo does not exist!\n" unless -e $CopyFilesTo;
	die "$CopyFilesTo is not a directory!\n" unless -d $CopyFilesTo;
	die "I can't write to the directory $CopyFilesTo!\n" unless -w $CopyFilesTo;
	
	# Figure out which files are the latest podcasts
	chdir "./catalogue/All/";
	# Create a hash of possible filenames
	my %FileCopyList;
	foreach my $FileName (<*>) {
		# If the link points to something that doesn't exist them we omit it.
		if (-e (readlink $FileName)) {
			$FileCopyList{$FileName} = 1;
		}
	}
	# Create a sorted array of filenames
	# map { [ $_, -M $_||0 ] } keys(%FileCopyList); = that means make a two dimensional array so that $_->[0]="filename.txt" and $_->[1] is the -M value
	# sort { $a->[1] <=> $b->[1] } = sort the array we just thought of by the $_->[1] index, ie the -M times
	# map { $_->[0] } take the sorted array and convert it back to a plain list of filenames by reading out the $_->[0] values.  These are now in the right order as they are sorted
	@FileList = map { $_->[0] } sort { $a->[1] <=> $b->[1] } map { [ $_, -M readlink($_) ] } keys(%FileCopyList);
	# Create an array of the files we should copy
	my $CopiedFiles = 0;
	while ($CopiedFiles < $NumberOfFiles) {
		my $TargetBase = basename($FileList[$CopiedFiles]);
		push(@CopyTheseFiles, $FileList[$CopiedFiles]);
		$DontDeleteThese{$TargetBase} = 1;
		$CopiedFiles++;
	}
	# Delete routine (delete files unless we would have copied it)
	if ($CopyFiles_Delete) {
		# Time to delete files in the target directory
		# Babysitting the user :)
		die "Not allowed to delete in the directory \"$CopyFilesTo\"\n" if $CopyFilesTo =~ m,^($ENV{HOME}(/|/Documents/*.*)*$|^/(usr|var|dev|etc|lib|sbin|sys|boot|proc)/*),;
		# Delete the files
		foreach (<$CopyFilesTo/*>) {
			my $TargetBase = basename($_);
			# If it is in the $DontDeleteThis hash or is a directory we skip it.
			next if $DontDeleteThese{$TargetBase} or -d $_;
			print "Deleting $_...\n";
			unlink("$_") or warn "Deleting of $_ failed: $!\n";
		}
	}
	# Copy the files
	foreach (@CopyTheseFiles) {
		my $TargetBase = basename($_);
		print "Skipping \"$TargetBase\", it already exists in $CopyFilesTo.\n" if $Verbose and -e "$CopyFilesTo/$TargetBase";
		next if -e "$CopyFilesTo/$TargetBase";
		print "Copying $TargetBase...\n";
		copy("$_", "$CopyFilesTo") or die "Copying failed: $!\n";
	}
	# And finally, attempt to run sync once.
	if (InPath "sync") {
		print "Synchronizing disks... " if $Verbose;
		system("sync");
		print "done\n" if $Verbose;
	}
	exit
}

# Function to initialize the global config file from $BaseDir
sub InitGlobalConfig {
	# Creat the directory if it isn't already there
	&InitConfigDir unless -e $UserConfigDir;
	# Load the config
	my $ConfigReturn = do { 
		$Settings::WorkingDir = undef;
		$Settings::DefaultVerbosity = undef;
		$Settings::PodcastFilter = undef;
		package Settings;
		do "$UserConfigDir/goldenpod.conf"
	};
	# Check errors
	unless (defined $ConfigReturn) {
		my $Error;
		$Error = $@ ? $@ : $!;
		die "Failed to parse the configuration file $UserConfigDir/goldenpod.conf: $Error\n";
	}
	# Set config variables
	$WorkingDir = $Settings::WorkingDir;
	$DefaultVerbosity = $Settings::DefaultVerbosity;
	$PodcastFilter = $Settings::PodcastFilter
}

# Function to create ~/.goldenpod (or any other $BaseDir)
sub InitConfigDir {
	mkdir $UserConfigDir or die "Unable to create the directory $UserConfigDir: $!" unless -e $UserConfigDir;
	# If /etc/goldenpod.conf exists the copy that to ~/.goldenpod/podcasts.conf
	if ( -e "/etc/goldenpod.conf" ) {
		warn "Copying /etc/goldenpod.conf to $UserConfigDir/podcasts.conf";
		copy("/etc/goldenpod.conf", "$UserConfigDir/podcasts.conf") or warn "Copying of /etc/goldenpod.conf failed: $!";
	}
	# If we don't have ~/.goldenpod/podcasts.conf (no /etc/goldenpod.conf or failure copying it)
	# then write an empty one.
	unless ( -e "$UserConfigDir/podcasts.conf" ) {
		open(PODCASTS_CONF, ">$UserConfigDir/podcasts.conf");
		print PODCASTS_CONF "# Put your podcast feed URLs in this file seperated by newlines\n# All lines starting with # is ignored";
		close(PODCASTS_CONF);
	}
	# Write the default configuration file
	open(PRIMARY_CONFIG, ">$UserConfigDir/goldenpod.conf");
	print PRIMARY_CONFIG "# GoldenPod configuration file\n# Do NOT put podcast feed URLs in this file! Put them in podcasts.conf\n\n# The directory the podcasts will be downloaded to\n";
	print PRIMARY_CONFIG "# This is only used if there is no ./podcasts.conf or ./bp.conf\n# If those are present WorkingDir will be ./";
	print PRIMARY_CONFIG "\n\$WorkingDir = \"$ENV{HOME}/Podcasts\";\n\n# The default verbosity, anything other than 0 means verbose\n\$DefaultVerbosity = \"0\";";
	print PRIMARY_CONFIG "\n# Set this to 1 if you want GoldenPod only to download audio feeds\n\$PodcastFilter = \"0\";";
	close(PRIMARY_CONFIG);
}

# Function to detect which mode we are in
sub ModeDetection {
	my ($Mode, $LocalDir);
	# First check if we can use files in ./
	if (-e "./bp.conf" or -e "./podcasts.conf") {
		$Mode = "local";
		$LocalDir = Cwd::getcwd;
	}

	# If we can't use files in ./ let's check something else
	unless ($Mode) {
		# Check for files in the directory containing the goldenpod executeable (if that dir is -w)
		my $BaseDirectory = dirname($0);
		if (-w $BaseDirectory) {
			if (-e "$BaseDirectory/bp.conf" or -e "$BaseDirectory/podcasts.conf") {
				$Mode = "local";
				$LocalDir = "$BaseDirectory";
			}
		}
		# If all else fails (not that it's a bad thing really) we use ~/.goldenpod
		$Mode = "global" unless $Mode;
	} else {
		# If we're in local mode because PWD is ~/.goldenpod then work in global
		# mode instead.
		$Mode = "global" if $LocalDir eq "$ENV{HOME}/.goldenpod";
	}

	# Initialize config
	$UserConfigDir = "$ENV{HOME}/.goldenpod";
	&InitGlobalConfig;

	# If we're working in local mode, set some specific settings.
	if ($Mode eq "local") {
		$ConfigDir = $LocalDir;
		$WorkingDir = $LocalDir;
	} else {
		$ConfigDir = $UserConfigDir;
	}

	# Do base initialization
	$ProgramLog = "$ConfigDir/goldenpod.log";	# When we are silent we log to this file
	# Only use podcast.log if it exists, we want podcasts.log if it doesn't.
	# The file we write the downloaded URLs to.
	$PodcastLog = "$ConfigDir/podcast.log" if -e "$ConfigDir/podcast.log";
	$PodcastLog = "$ConfigDir/podcasts.log" unless $PodcastLog;
	# Detect configuration file
	$Config = "$ConfigDir/bp.conf" if -e "$ConfigDir/bp.conf";
	$Config = "$ConfigDir/podcasts.conf" unless $Config;
	&Arg_Verbose if $DefaultVerbosity;		# Be verbose by default if the user wants to
	mkdir $WorkingDir unless -e $WorkingDir;
	chdir $WorkingDir;
	die "I cannot write to $WorkingDir!\n" unless -w $WorkingDir;
}
# Detect our mode
&ModeDetection;

# Argument functions
sub Arg_Verbose { print "\n" unless $Verbose; $Verbose = "1"; $NoLog = "1" unless $NoLog; $CurlVerbosity = "-#";}

# - - - - - - - - - - -
# Main program
# - - - - - - - - - - -

# Parse commandline arguments
GetOptions ( 'verbose|v' => \&Arg_Verbose,
	'version' => sub { &Version; exit 0},
	'help|h' => sub { &Help; exit 0; },
	'dry-run|dryrun|r' => sub { &Arg_Verbose; $DryRun = "1" },
	'first-only|firstonly|first|f' => \$FirstOnly,
	'nocat|c' => \$NoCat,
	'nolog|l' => sub { $NoLog = "2"},
	'copy|p=s'=> \$CopyFilesTo,
	'files|i=s' => \$CopyFilesNumber,
	'delete|d' => \$CopyFiles_Delete,
	'silent|s' => sub { $Verbose = 0; $NoLog = "0" unless $NoLog == "2"; $CurlVerbosity = "--silent --show-error"; },
	'dumpinfo' => sub { &Version;
		print "\nI would read the configuration file $UserConfigDir/goldenpod.conf\n";
		print "I would read podcasts from $Config\n";
		print "I would log the program messages to $ProgramLog\n" unless $Verbose or $NoLog;
		print "I would log the downloaded podcasts to $PodcastLog\n";
		print "I would write to $WorkingDir\n";
		exit 0 }
) or &Help and die "\n";

# Conflicting commandline arguments
die "Conflicting options: --first-only and --dry-run. Please read --help\n" if $FirstOnly and $DryRun;
die "Conflicting options: --dry-run and --nolog. Please read --help about --verbose\n" if $DryRun and $NoLog == 2;
die "Conflicting options: --nolog and --verbose. Please read --help\n" if $Verbose and $NoLog == "2";
die "Useless use of --delete without --copy\n" if $CopyFiles_Delete and not $CopyFilesTo;
die "Useless use of --files without --copy\n" if $CopyFilesNumber and not $CopyFilesTo;

# If we're just copying then run the &CopyMyFiles
&CopyFiles if $CopyFilesTo;
# Make sure we have curl
die "GoldenPod requires curl to work, you don't seem to have curl installed!\nPlease install curl, then re-run GoldenPod\n" unless (InPath "curl");
# Add CurlVerbosity to CurlGlobal
$CurlGlobal = "$CurlGlobal $CurlVerbosity";

die "The configuration file \"$Config\" does not exist!\nPlease read the manpage included to get instructions on how to set one up.\n" unless -e $Config;

# Open the configuration file
open(CONFIG, "$Config");
# Unless we're verbose, write stuff to $ProgramLog
open(STDOUT, ">>$ProgramLog") unless $NoLog;
open(STDERR, ">>$ProgramLog") unless $NoLog;

# If we're in logging mode then log the date and time started
$_ = localtime(time) and print "Started at $_\n" unless $NoLog;
# If we're not verbose and $NoLog is 2 (-l) then write stuff to /dev/null ;)
open(STDOUT, ">/dev/null") if $NoLog and $NoLog == "2";
open(STDERR, ">/dev/null") if $NoLog and $NoLog == "2";

# Read the configuration file and fetch feeds
foreach my $podcast (<CONFIG>) {
	chomp $podcast;
	next if $podcast =~ /^\s*#/; # Ignore lines starting with # (comments)
	my $PodcastTitle;
	print "Fetching $podcast\n" if $Verbose;
	$ConfIsNotEmpty = 1;	# The config isn't empty! Yay!
	# Open a filehandle to curl (curl outputs to stdout)
	open(MYRSS, "curl $CurlGlobal ".$podcast." |");
	my $FirstDone = "0" if $FirstOnly;
	# Parse the feed
	while (<MYRSS>) {
		chomp $_;
		# Get the title of the podcast
		unless ($PodcastTitle) {
			if (/<title>([A-Za-z0-9 ]+).*<\/title>/) {
				$PodcastTitle = $1;
				# Run it through a filter
				$PodcastTitle =~ s/\s+/_/g;
				1 while $PodcastTitle =~ s/[\-\_]+(episodes?|promo|mp3|ogg|feed)*$//i;	 # Remove various junk
				$PodcastTitle =~ s/[\-\_].$//; # Remove -_ in the end of the name
			}
		}
		# This is a hack to make sure we parse RSS feeds without newlines correctly
		s/>/>\n/g;
		foreach (split(/\n/,$_)) {
			chomp $_;
			# Do the real parsing and add to @urls
			# We want to extract all url='s
			if (/url=[\"\']([^\"\']+)[\"\']/)
	    		{
				# Filter away non-audio feeds if the user wants it.
				if ($PodcastFilter) {
					next unless basename($1) =~ /.*\.(ogg|mp3|wav|flac|wmv).*/i;
				}
				# If we're in --first-only then add it to @urls unless
				# $FirstDone is true, add it to @DryRun if it is.
				# If we aren't, then just add it to @urls.
				if ($FirstOnly) {
					$URLs{$1} = 1 unless $FirstDone;
					$DryRun{$1} = 1 if $FirstDone;
					$FirstDone = "1";
				} else { $URLs{$1} = 1; }
				# Add the title string to the PodNames array (if we have found the title)
				my $BaseName = basename($1);
				$PodNames{$BaseName} = $PodcastTitle;
			}
		}
	}
	close MYRSS;
}

# If the config is empty, die.
die "The podcast list in $Config is empty!\n" unless $ConfIsNotEmpty;

# Close the configuration file
close CONFIG;

# Create the %AlreadyDownloaded hash.
if ( -e $PodcastLog) {
	# Open the logfile containing previously downloaded files
	open(LOGFILE, "$PodcastLog");
	# Add them to a hash
	%AlreadyDownloaded = map { chomp; $_=>1 } <LOGFILE>;
	# Close the logfile
	close LOGFILE;
}


# If we're in a dry run then copy the contents of the URLs hash to
# DryRun.
%DryRun = %URLs if $DryRun;
# If we're in a dry run then create a logfile with all current options
# # and exit
&PerformDryRun and exit 0 if $DryRun;

# Make sure only new podcasts are downloaded
foreach my $url (keys %URLs)
{
	$DownloadQueue{$url} = 1 unless $AlreadyDownloaded{$url};
	$NeedToDownload++ unless $AlreadyDownloaded{$url};
}
unless (keys %DownloadQueue) {
	# Dry run if --first-only
	print "Nothing to download :o\n" unless $Verbose;
	exit 0
}

# Okay, we really need to download something
# If we're in verbose mode, clear the screen and display how many files
# we need to download.
if (InPath "clear") {
	system("clear") if $Verbose;
}
print "$NeedToDownload new podcasts.\n\n" if $NeedToDownload > 1;
print "$NeedToDownload new podcast.\n\n" if $NeedToDownload == 1;
# Open the logfile for writing
open(LOGFILE, ">>$PodcastLog");

# Get the current date
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
$year += 1900;					# The year doesn't have the format we want
$mon++; $mon = "0$mon" unless $mon >= 10;	# Make the month be 1-12 instead of 0-11 and add a 0 prefix to 1-9
my $date="$year-$mon-$mday"; 			# The date in the international date format
# Create the ./$date directory if it doesn't exist
$CreatedDir = "1" unless -e "./$date";
mkdir "./$date" unless -e "./$date";

# Change working directory to $date
chdir "./$date";
# Download the podcasts
foreach (sort keys %DownloadQueue) {
	print "Downloading $_\n";
	# Curl returns nonzero on failure
	my $DownloadStatus = system("curl $CurlGlobal -O $_");
	print LOGFILE "$_\n" unless $DownloadStatus;
	warn "Download of $_ failed! Curl exited with return value $DownloadStatus\n" if $DownloadStatus;
	# If we're in --first-only we add it to $AlreadyDownloaded{$_} so that DryRun doesn't add it
	if ($FirstOnly) {
		$AlreadyDownloaded{$_} = 1 if $DownloadStatus;
	}
	# Set DownloadedSomething to true if the download succeeded.
	$DownloadedSomething = "1" unless $DownloadStatus;
}
chdir "..";

# Remove the directory we created if all downloads failed, if at least one
# download was successfull then we link ./latest to ./$date and add it to
# the catalogue.
if ($CreatedDir && !$DownloadedSomething) {
	unlink glob "$date/* $date/.*";
	rmdir "./$date";
} elsif ($DownloadedSomething) {
	# Filename filter
	chdir "./$date";
	foreach (<*?*>) {
		my $OldName = $_;
		s/\?.*//g;
		rename("$OldName", "$_");
	}
	chdir "..";
	# Make the ./latest symlink point to $date
	unlink "./latest";
	symlink "./$date", "./latest";

	# Catalogue
	unless ($NoCat) {
		# Create our catalogue (podcasts sorted in named directories)
		mkdir "./catalogue" unless -e "./catalogue";
		mkdir "./catalogue/All/" unless -e "./catalogue/All/";
		chdir "./$date";
		# For every file, make sure it has a catalogue entry.
		foreach (<*>) {
			my $OrigName = "$_";
			# We don't want to do anything to playlists
			unless (/\.m3u/) {
				my ($Existed, $PodBaseName);
				# Get the base name of the podcast
				if ($PodNames{$OrigName}) {
					$PodBaseName = $PodNames{$OrigName};
				} else {
					# If we couldn't get the podcast name from the feed then try even
					# harder here.
					/[0-9]*([a-zA-Z\-\_]*)[0-9]*/;	# Get alphabetical characters and -_.
					$_ = "$1";
					1 while s/[\-\_]+(show|promo)*$//i; 		# Remove various junk
					s/[\-\_].$//;					# Remove -_ in the end of the name
					$PodBaseName = "\u$_";				# Make the first character be uppercase
					$PodBaseName = "Unknown" unless $PodBaseName;	# Bah :(
				}
				chdir "../catalogue";
				mkdir "./$PodBaseName" unless -e "./$PodBaseName";
				chdir "./$PodBaseName";
				# We don't want to do anything if it already exists
				unless (-e "./$OrigName") {
					# Try to get the extension
					my $NameExtension = $OrigName;
					$NameExtension =~ s/.*(\.\w)/$1/;
					# Symlink the files and write the playlist
					symlink "../../$date/$OrigName", "./$OrigName";
					unlink "./latest$NameExtension" if -e "./latest$NameExtension";
					symlink "../../$date/$OrigName", "./latest$NameExtension";
					open(PLAYLIST, ">>./$PodBaseName.m3u");
					print PLAYLIST "$OrigName\n";
					close(PLAYLIST);
				}
				# Add it to All too if needed
				unless (-e "../All/$OrigName") {
					chdir "../All/";
					# Symlink it
					symlink "../../$date/$OrigName", "./$OrigName";
				}
				chdir "../../$date";
			}
		}
	}
	# Dry run if in --first-only
	&PerformDryRun if $FirstOnly;
}
