: # *-*-perl-*-*
  eval 'exec perl -S  $0 ${1+"$@"}' 
    if 0;  # if running under some shell
#
# GDJ - Gnut DJ
#
# This script plays mp3's from your gnut cache, uploads and downloads
# directories.
#
# It plays all tracks in rotation, making sure you never hear a track
# twice until at least 60% of the other tracks have intervened. (In other
# words, if there are 10 MP3's, it will always play at least 6 others
# before repeating)
#
# GDJ also plays new tracks immediately. When new tracks are downloaded
# into the cache or download directory, they will play as soon as the
# current track is done.
#
# For best results, turn on the cache (check the gnut manual chapter 4)
# and run gnut, then run this script in another window. If you leave your
# gnut running, it will keep loading random files into the cache, and this
# script will play the files in a random order.
#
# You can move tracks from one directory to another and GDJ won't mind.
# If you rename a track, GDJ will think the new name is a new track,
# and play it right away. You can even delete a track while it's playing,
# and it will finish playing (the kernel deletes files only after they
# are closed)
#
# Set or change the following variables to customize GDJ:


$gap = 3; # number of seconds between tracks

$rotatefactor = 60; # Percent of tracks to play before repeating something
                    # twice. 0 = completely random; 100 = repeat the same
                    # play order forever

# command-line-based player programs, arguments, and redirect if needed
$player{"mp3"} = "mpg123 -b 100 <file> >/dev/null 2>&1";
$player{"mp3"} = "mpg123 -F -q -b 100 <file>";

# Add more $player lines for each file type you want to be able to play

# Un-comment-out the following line if you want it to play in mono at
# a low sampling rate. This makes it sound like AM radio, and also
# eliminates the "glitches" that happen when your computer is busy doing
# other things
# $player{"mp3"} = "mpg123 --mix -r 8000 --8bit -s -q <file> >/dev/dsp 2>/dev/null";

# If the full pathname of a file matches this pattern, the file will be copied
# to a local temp file before playing. This is intended for use by people
# who have CD-ROMs full of MP3 files, and wish to be able to eject a CDROM
# and insert another without stopping and restarting GDJ. This will probably
# only work if your UNIX has an automount driver.
$copypathpat = "^/mnt/cdrom";

# 20010517 First version, just plays stuff in cache
# 20010518 Scan uploads and downloads directories too
# 20010519 Minor improvements
# 20010602 You can now specify players for different filename extensions
# 20010602 Add $copypathpat to facilitate hot-swapping MP3-CDs
# 20010612 Replace playlist operations with the tr_xxx routines for modularity, and add GDJ_log_playlist setting.

sub unquote
{
  local($l) = @_;

  $l =~ s/\"//g; # "
  return $l;
}

sub read_gnutrc
{
  local($l);

  chdir;

  if (!(-e ".gnutrc")) {
    print "No .gnutrc file in home directory.\n";
    exit 0;
  }

  # find the cache directory
  open(IN, ".gnutrc");
  while($l = <IN>) {
    chop $l;
    if ($l =~ m/^ *set +cache_path +(.+)$/) {
      $cpath = $1;
      $cpath = &unquote($cpath);
    } elsif ($l =~ m/^ *set +download_path +(.+)$/) {
      $dpath = $1;
      $dpath = &unquote($dpath);
    } elsif ($l =~ m/^ *set +share_paths +(.+)$/) {
      $spaths = $1;
      $spaths = &unquote($spaths);
    } elsif ($l =~ m/^ *set +GDJ_extra_paths +(.+)$/) {
      $gpaths = $1;
      $gpaths = &unquote($gpaths);
    } elsif ($l =~ m/^ *set +GDJ_log_playlist +(.+)$/) {
      $logplaylist = $1;
      $logplaylist = &unquote($logplaylist);
    }
  }
  close IN;
}

sub adddir
{
  local($d, $ty) = @_;

  if ($d eq "") {
    return;
  }

  $havedir{$d} = 1;
  $dirtype{$d} = $ty;
}

$forever = 100000;

sub tr_defined_p
{
  local($tr) = @_;

  if ($order{$tr} eq "") {
    return 0;
  }
  return 1;
}

sub tr_getval
{
  local($tr) = @_;

  return $order{$tr};
}

sub tr_set
{
  local($tr, $val) = @_;

  $order{$tr} = $val;
}

sub tr_makenew
{
  local($tr) = @_;

  $order{$tr} = $forever + 1;
}

sub tr_validate
{
  local($tr) = @_;

  $order{$tr} += 0;
}

sub tr_allkeys
{
  return(keys %order);
}

sub tr_valrandom
{
  local($tr) = @_;

  return($order{$tr} + (0.5 * rand(1.0)));
}

sub tr_winner
{
  local($tr1, $c1, $tr2, $c2) = @_;
  local($rn, $rc);

  if ($c1 > $c2) {
    $rn = $tr1;
    $rc = $c1;
  } else {
    $rn = $tr2;
    $rc = $c2;
  }
  return($rn, $rc);
}

sub tr_undefine
{
  local($tr) = @_;

  $order{$tr} = "";
}

# The ageing algorithm is to decrease the values by one until they
# reach 0. Anything at 0 stays at 0; tracks with the lowest value are
# eligible to be played. When a track is "touched" (because it has
# just been played) its value is set to the number of files times 60%.
# The effect of this algorithm is that the next selection will be
# chosen from the oldest 40% of the available files, where "oldest"
# refers to how long it's been since the last time that file was
# played.
sub tr_age
{
  local($tr) = @_;
  local($cutoff);

  if (&tr_defined_p($tr)) {
    $cutoff = int($numfiles * $rotatefactor / 100);

    if (($order{$tr} < $cutoff) && ($order{$tr} < $forever)) {
      $order{$tr} += 1;
    }
  }
}

sub tr_ancient
{
  local($tr) = @_;

  if ($order{$tr} >= $forever) {
    return 1;
  }
  return 0;
}

sub tr_touch
{
  local($tr) = @_;

  $order{$tr} = 0;
}

sub tr_ageall
{
  local($k);

  foreach $k (&tr_allkeys()) {
    &tr_age($k);
  }
}

sub tr_avail
{
  local($tr) = @_;

  if (-e $fullpath{$tn}) {
    return 1;
  }
  return 0;
}

sub tr_pickone
{
  local($rn, $rc, $tn, $tc);

  $rn = ""; $rc = 0;
  foreach $tn (&tr_allkeys()) {
    if (&tr_avail($tn)) {
      $tc = &tr_valrandom($tn);
      ($rn, $rc) = &tr_winner($rn, $rc, $tn, $tc);
    }
  }
  return($rn);
}

close STDERR;
open(STDERR, ">&STDOUT");

&read_gnutrc();

if (($cpath eq "") && ($dpath eq "") && ($spaths eq "") && ($gpaths eq "")) {
  print "There should be at least one directory (cache_path, share_paths,
download_path, or GDJ_extra_paths, set in .gnutrc.\n";
  exit 0;
}

&adddir($cpath, "ca");
&adddir($dpath, "dl");
foreach $l (split(":", $spaths)) {
  &adddir($l, "sh");
}
foreach $l (split(":", $gpaths)) {
  &adddir($l, "ge");
}

# The playlist index is kept in the home directory
chdir;

# Read the playlist index
$ixfile = ".GDJplaylist";
if (-e $ixfile) {
  open(IN, $ixfile);
  while($l = <IN>) {
    chop $l;
    if ($l =~ m/^([-0-9]+) (.+)$/) {
      $file = $2;
      $count = $1;
      &tr_set($file, $count);
    }
  }
  close IN;
}

while(1) {
  # Scan for new files
  $numfiles = 0;
  undef %fullpath;
  foreach $dir (keys %havedir) {
    open(IN, "find \"$dir\" 2>/dev/null |");
    while($l = <IN>) {
      chop $l;
      if (-d $l) {
      } elsif (!(-e $l)) {
      } else {
        if ($l =~ m|^(.+)/([^/]+)$|) {
          $ldir = $1; $leaf = $2;
          if ($leaf =~ m/^\./) {
            # dot-file: ignore
          } elsif ($leaf =~ m/\.([A-Za-z0-9]+)$/) {
            $lext = $1; $lext = lc($lext);
            # Check if we have a player for this extension
            if ($player{$lext} ne "") {
              # Check if this file is "new"
              if ($fullpath{$leaf} eq "") {
                $fullpath{$leaf} = $l;
                $ftype{$leaf} = $dirtype{$dir};
                $numfiles++;
                if (&tr_defined_p($leaf)) {
                  &tr_validate($leaf);
                } else {
                  &tr_makenew($leaf);
                }
              }
            }
          }
        }
      }
    }
    close IN;
  }

  # Purge ancient unavailable files
  foreach $tn (sort {lc($a) cmp lc($b)} &tr_allkeys()) {
    if ($fullpath{$tn} eq "") {
      if (&tr_ancient($tn)) {
        &tr_undefine($tn);
      }
    }
  }

  # Find the one to play
  $lnreal = &tr_pickone();

  # Age all tracks, and "touch" the one we're about to play
  &tr_ageall();
  &tr_touch($lnreal);

  # Update index file
  open(OUT, "> $ixfile");
  foreach $f (sort {lc($a) cmp lc($b)} (&tr_allkeys())) {
    if (&tr_defined_p($f)) {
      print OUT (&tr_getval($f) . " " . $f . "\n");
    }
  }
  close OUT;

  $ft = $ftype{$lnreal};
  print(("_" x 78) . "\n-$ft- $lnreal\n");

  if ($logplaylist) {
    open(LPL, ">> $logplaylist");
    print LPL "$lnreal\n";
    close LPL;
  }

  $lnreal = $fullpath{$lnreal};

  # Play it
  if (-e $lnreal) {
    if ($lnreal =~ m/\.([A-Za-z0-9]+)$/) {
      $lext = $1; $lext = lc($lext);
      if ($player{$lext} ne "") {
        # Copy to local file if appropriate
        if ($lnreal =~ m/$copypathpat/) {
          system("cp $lnreal ./GDJ.temp.$lext");
          $lnreal = "GDJ.temp.$lext";
          $delfile = $lnreal;
        } else {
          $delfile = "";
        }

        # Escape filename for player program
        $lnesc = $lnreal;
        $lnesc =~ s/([ \,\&\(\)\'\"\|\[\]\;\{\}\`\~\!\@\#\$\%\^])/\\\1/g; # "

        # Create command line
        $cmd = $player{$lext};
        $cmd =~ s/\<file\>/$lnesc/;
        # print "$cmd\n";
        system($cmd);

        if ($delfile ne "") {
          unlink($delfile);
        }
      }
    }
  }

  # Pause a few seconds, so you can hit control-C twice to quit GDJ
  sleep($gap);
}
