On Sun, 2003-08-24 at 18:12, Stas Bekman wrote:
> Harold Martin wrote:
> > On Sun, 2003-08-24 at 17:21, Stas Bekman wrote:
> > 
> >>Harold Martin wrote:
> >>
> >>>Hello,
> >>>When I try to start up Apache, I get the error:
> >>>[error] syntax error at /usr/lib/perl5/site_perl/5.8.0/ProMP3.pm
line 8,
> >>>near "compile qw(:common)"
> >>>Compilation failed in require at
/etc/httpd/promp3/promp3.handler.pl
> >>>line 6.
> >>>BEGIN failed--compilation aborted at
/etc/httpd/promp3/promp3.handler.pl
> >>>line 6.
> >>>Compilation failed in require at (eval 1) line 1.
> >>>
> >>>ProMP3.pm's has:
> >>>use Apache2 ();
> >>>use Apache::compat;
> >>>use Apache::ServerUtil ();
> >>>use strict;
> >>>use vars qw($VERSION);
> >>>use DBI;
> >>>use Apache::Const -compile qw(:common);
> >>
> >>where did you find an example of this?
> >>
> >>It should be:
> >>
> >>use Apache::Const -compile => qw(:common);
> >>
> >>or:
> >>
> >>use Apache::Const -compile, qw(:common);
> > 
> > 
> > The form I used is from the Apache::Const page 
> > http://perl.apache.org/docs/2.0/api/Apache/Const.html#C__common_
> 
> Ah thank you, I've fixed that.
> 
> > Using either of the two other lines results in the error:
> > [error] Can't locate # in @INC ...
> > 
> > What's that all about?
> 
> Please post a short test example and a full error message as a part of
a 
> proper bug report as explained here:
> http://perl.apache.org/bugs/

Is there any other way to figure what's wrong? The code isn't mine, but
someone else's, so I'm really not a whole lot of use there. As for
posting an example, it's the same as from
http://sourceforge.net/project/showfiles.php?group_id=12829 but just
modified slightly according to the porting guidelines. I've only changed
and added a few use statements and changed the constants from the m_p 1
form to the m_p 2 from. I've attached the two pieces of code that I
modified (the module and the handler) for you.
Thanks,
Harold


package ProMP3::Config;
use strict;
use Apache2 ();
use Apache::compat;
use Apache::ServerUtil ();
use ProMP3;
my $config;


##########################
##
## config file for ProMP3
##
## Edit the defaults below if you would like some customization
## 
##########################

# set the mp3 player to play from the server (must be installed)
$config->{mp3_player} = "Xmms";  # valid entries are Xmms for now

# set the database to use in the back end:
$config->{database} = "Mysql";   # again, one valid choice: Mysql

# set database username:
$config->{database_username} = "root";

# set database password:
$config->{database_secret} = "youreouronlyhope";

# set database to use within the database:
# this will match what you tell (told) utils/promp3_setup.pl
$config->{database_db} = "music";

# turn on debug, this puts a lot of stuff into the apache error log...
$config->{debug} = 1;

#  Access lists -- set who can do what:
#
# The following are comma seperated access lists of ip addresses.
# They can be in the following form:
#   <ip>[<subnet>]
#   i.e.:
#         10.1.1.1/24 (allows 10.1.1.0 thru 10.1.1.255)
#         10.1.1.1/255.255.255.0 (same)
#         10.1.1.0 - 10.1.1.255 (same)
#         10.1.1.50 (only this host, /32 is implied)
#
#  They can be mixed and matched in the same string
#  for example:
#    "10.1.1.1/24, 192.168.10.48/28, 192.168.10.119, 10.250.1.13-10.250.1.18"
# allows 10.1.1.0 thru 10.1.1.255 and 
#        192.168.10.48 thru 192.168.10.63 and
#        192.168.10.119 and 
#        10.250.1.13 thru 10.250.1.18

# Enable streaming mp3, only these IP's will be allowed to stream :
$config->{allow_stream} = "10.10.10.0/24";

# Enable mp3 playing from the server, only these IP's will be able to control the server :
$config->{allow_player} = "10.10.10.1-10.10.10.9, 10.10.10.11-10.10.10.200";

# Allow these folks in, if they aren't in the other list, they'll just be able to look but not touch.
$config->{allow_view} = "0.0.0.0/0";

###########################
##
## That's all you need to set...
##
###########################

my @values = qw(allow_stream allow_player allow_view mp3_player database database_username database_secret database_db debug);
foreach my $key (keys %$config) {
  unless (grep /^$key$/, @values) {
    delete $config->{$key};
    print "Warning: $key is being ignored in the promp3 config file\n";
  }
}
foreach my $key (@values) {
  if (! exists $config->{$key}) {
    print "Warning: $key is not defined in the promp3 config file\n";
    $config->{$key} = undef;
  }
}

foreach my $section (qw(allow_stream allow_player allow_view) ) {
  (my $allow = $config->{$section})=~s/\s+//g;
  my @allow = split ',', $allow;
  delete $config->{$section};
  foreach my $string (@allow) {
    push @{$config->{$section}}, ProMP3::set_ip_range($string);
    if (! defined $config->{$section}[$#{$config->{$section}}]) {
      pop @{$config->{$section}};
    }
  }
}
sub get_config {
  return $config->{$_[0]} if (@_);
  return $config;
}

1;
package ProMP3;
use Apache2 ();
use Apache::compat;
use Apache::ServerUtil ();
use strict;
use vars qw($VERSION);
use DBI;
#use Apache::Const -compile qw(:common);
#use Apache::Const -compile => qw(:common);
use Apache::Const -compile, qw(:common);
$VERSION = '0.71';

sub new {
  my $proto = shift;
  my $class = ref($proto) || $proto;
  my $self = {
    "r" => shift,
    "_config"  => &ProMP3::Config::get_config, };
  bless $self, $class;
  return $self;
}

sub r { return shift->{r} }

sub config { 
  my $self = shift;
  # grab the field if it was passed in
  my $field = shift;
  # set the field if we need to
  $self->{_config}{$field} = shift if (@_ && $field);
  # return the field value if they requested it.
  return $self->{_config}{$field} if ($field);
  # else return the entire config
  return $self->{_config};
}

sub cleanup {
  my $r = shift;
  return unless ($r->current_callback eq "PerlCleanupHandler");
  my $promp3 = ProMP3->new($r);
  $promp3->init_player;
  $promp3->init_dbh;
  $promp3->{_player}->cleanup;
}

sub handler {
  my $r = shift;
  $r->warn("Content Type: ".$r->content_type);
  $r->warn("Filename : ".$r->filename);
  $r->warn("URI : ".$r->uri);
  if ($r->filename!~/m3u$/ && $r->uri!~/mp3$/) {
    return Apache::Constants::DECLINED() unless ($r->content_type=~/^text|directory/);
  }
  my $filename = (split '/', $r->uri)[-1];
  my $promp3 = ProMP3->new($r);
  $promp3->warn("Remote: ".$r->connection->remote_ip);
  $promp3->warn("content type is ".$r->content_type);
  $promp3->warn("uri is ".$r->uri);
  $promp3->warn("filename is ".$r->filename);
  $promp3->warn("real filename is set to $filename");
  if (! $promp3->can_view) {
    $promp3->warn("we should be bouncing these bad boys...");
    return Apache::Constants::DECLINED();
  }
  $promp3->can_stream;
  if ($filename=~/mp3$/) {
    if ($promp3->can_stream) {
      my %args = $r->args;
      if ($args{id}) {
        $promp3->init_dbh;
        return $promp3->stream_id($args{id});
      }
    } else {
      $promp3->warn("Remote has no priveledge to stream");
    }
  } elsif ($filename eq "show_titles.html") {
    my %args = $r->args;
    if ($args{id}) {
      $promp3->warn("looking at the titles of id $args{id}");
      $promp3->init_dbh;
      $promp3->show_titles($args{id});
    }
    return Apache::Constants::OK();
  } elsif ($filename eq "custom_list.html") {
    if ($promp3->can_play || $promp3->can_stream) {
      $promp3->init_dbh;
      $promp3->custom_list;
      $promp3->warn("finished here");
    }
    return Apache::Constants::OK();
  } elsif ($filename eq "edit_favlist.html") {
    if ($promp3->can_play || $promp3->can_stream) {
      $promp3->init_dbh;
      $promp3->edit_favlist;
      $promp3->warn("finished here");
    }
    return Apache::Constants::OK();
  }
  $promp3->init_player;

  if ($filename=~/^action_(\w{4})\.html/) {
    if ($promp3->can_play) {
      my $action = "\$promp3->{_player}->$1";
      $r->warn("running on $action");
      eval $action;
      if ($@) {
        $r->warn("invalid action: $action  $@");
        return 500;
      }
    }
    return $promp3->redirect;
  } elsif ($filename eq "edit.html") {
    if ($promp3->can_play || $promp3->can_stream) {
      $promp3->init_dbh;
      $promp3->edit;
      $promp3->warn("finished here");
    }
    return Apache::Constants::OK();
  } elsif ($r->uri=~/set_volume/) {
    if ($promp3->can_play) {
      $promp3->warn("set_volume called!");
      my %args = $r->args;
      foreach (sort keys %args) {
        next unless ($_=~/^(\d+),\d+$/);
        my $volume = int(($1+1)*(100/110));
        my $show = int(($volume+4)/10);
        $promp3->warn("           should set volume to around $volume, and show about 
$show");
        $promp3->{_player}->set_volume($volume);
      }
    }
    return $promp3->redirect;
  } elsif ($filename eq "play_song.html") {
    my %args = $r->args;
    if ($promp3->can_play && $args{id}) {
      $promp3->warn("playing id $args{id}");
      $promp3->{_player}->play_id($args{id});
    }
    return $promp3->redirect;
  }
  $promp3->init_dbh;
  if ($filename eq "cut_tracks.html") {
    if ($promp3->can_play) {
      $promp3->warn("cut tracks called");
      my $apr = Apache::Request->new($r, DISABLE_UPLOADS => 1);
      my @values = $apr->param('id');
      $promp3->{_player}->cut_tracks([EMAIL PROTECTED]);
      # $promp3->push_cleanup;
      $promp3->warn("returning a redirect");
    }
    return $promp3->redirect;
  } elsif ($filename eq "add_favlist.html") {
    $promp3->warn("add favlist called");
    my $apr = Apache::Request->new($r, DISABLE_UPLOADS => 1);
    my @values = $apr->param('id');
    my $favlist = $apr->param('favlist');
    $promp3->add_favlist($favlist, [EMAIL PROTECTED]);
    return $promp3->redirect;
  } elsif ($filename eq "add_tracks.html") {
    if ($promp3->can_play) {
      $promp3->warn("add tracks called");
      my $apr = Apache::Request->new($r, DISABLE_UPLOADS => 1);
      my @values = $apr->param('id');
      $promp3->{_player}->add_tracks([EMAIL PROTECTED]);
      $promp3->warn("returning a redirect");
    }
    return $promp3->redirect;
  } elsif ($filename eq "play_tracks.html") {
    if ($promp3->can_play) {
      $promp3->warn("play tracks called");
      my $apr = Apache::Request->new($r, DISABLE_UPLOADS => 1);
      my @values = $apr->param('id');
      $promp3->{_player}->play_tracks([EMAIL PROTECTED]);
      $promp3->warn("returning a redirect");
    }
    return $promp3->redirect;
  } elsif ($filename eq "stream_tracks.m3u") {
    if ($promp3->can_stream) {
      $promp3->warn("streaming function requested");
      my $apr = Apache::Request->new($r, DISABLE_UPLOADS => 1);
      my @values = $apr->param('id');
      $promp3->stream_tracks([EMAIL PROTECTED]);
      # $promp3->warn("returning a redirect");
    }
    # return $promp3->redirect;
  } elsif ($r->content_type=~/directory/ || $r->uri=~/index.html/) {
    return $promp3->index;
  } elsif ($filename eq "search.html") {
    return $promp3->search;
  } elsif ($filename eq "random.html") {
    if (! $promp3->random) {
      $promp3->log_error("got bad response back from random feature");
    }
    return $promp3->redirect;
  } elsif ($filename eq "genre.html") {
    return $promp3->genre;
  } else {
    $promp3->log_error("a request a made that I didn't understand: ".$r->uri." real 
filename: \"$filename\"");
  }
  return Apache::Constants::OK();
}

sub print {
  my $self = shift;
  $self->r->print(shift);
  return;
}

sub stream_id {
  my $self = shift;
  my $id = shift;
  $self->warn("Attempting to stream song ID $id");
  my $sql = "SELECT g.name, a.name, l.name, s.name, s.file FROM ";
    $sql .= "genre g, song s, album l, artist a, combo c ";
    $sql .= "WHERE s.combo_id = c.combo_id AND c.album_id = l.album_id ";
    $sql .= "AND c.genre_id = g.genre_id AND c.artist_id = a.artist_id AND s.song_id = 
?";
  $self->warn($sql);
  my $sth = eval { $self->dbh->prepare($sql); };
  if (! defined $sth) {
    $self->warn("ERROR: $DBI::errstr");
  }
  if ($@) {
    $self->log_error("Could not prepare stream inquiry: $@");
    return;
  }
  eval { $sth->execute($id); };
  if ($@) {
    $self->log_error("Could not execute stream inquiry info : $@");
    return;
  }
  my $info = $sth->fetchrow_arrayref;
  open (FILE,$info->[4]) || return Apache::Constants::DECLINED();
  my $title = join ', ', (@$info)[1..3];
  $self->print("ICY 200 OK\r\n");
  $self->print("icy-notice1:<BR>This stream requires a shoutcast/icecast compatible 
player.<BR>\r\n");
  $self->print("icy-notice2:ProMP3 module<BR>\r\n");
  $self->print("icy-name:$title\r\n");
  $self->print("icy-genre:".$info->[0]."\r\n");
  # $self->print("icy-url:",$self->stream_base(1),\r\n);
  $self->print("icy-pub:1\r\n");
  $self->print("icy-br:128\r\n");
  $self->print("\r\n");
  return Apache::Constants::OK() if $self->r->header_only;
  $self->r->send_fd(\*FILE);
  return Apache::Constants::OK();
}
  
sub stream_tracks {
  my $self = shift;
  my $values = shift;
  my $basename = "http://"; . $self->r->server->server_hostname;
  $basename = "http://pele";;
  $basename .= ':' . $self->r->get_server_port unless $self->r->get_server_port == 80;
  ($basename .= $self->r->uri)=~s/[^\/]+$//;
  $self->r->send_http_header('audio/mpegurl');
  return Apache::Constants::OK() if $self->r->header_only;

  my $sql = "SELECT a.name, l.name, s.name, s.song_id FROM song s, album l, artist a, 
combo c WHERE s.combo_id = c.combo_id AND c.album_id = l.album_id AND c.artist_id = 
a.artist_id AND s.combo_id IN (".(join ', ', @$values).") order by track, song_id";
  $self->warn("running: $sql");
  my $sth = eval { $self->dbh->prepare($sql); };
  if ($@) {
    $self->log_error("Could not prepare new playlist: $@");
    return;
  }
  eval { $sth->execute(); };
  if ($@) {
    $self->log_error("Could not execute new playlist info : $@");
    return;
  }
  while (my $row = $sth->fetchrow_arrayref) {
    my $file = join '/', (@$row)[0..2];
    $file =~ s!([^a-zA-Z0-9_\./])!uc sprintf("%%%02x",ord($1))!eg;
    $self->print ($basename.$file.".mp3?id=".$row->[3]."\r\n");
  }
  return 1;
}



sub custom_list {
  my $self = shift;
  my $sth = eval { $self->dbh->prepare("SELECT distinct(name) FROM favlist ORDER BY 
name"); };
  if ($@) {
    $self->log_error("could not prepare favlist lookup: $@");
    return;
  }
  eval { $sth->execute; };
  if ($@) {
    $self->log_error("could not execute favlist lookup: $@");
    return;
  }
  $self->print($self->head1("WebMusique: Add/Create Custom List"));
  $self->print('<script>'."\n");
  $self->print('function exit () {'."\n");
  $self->print('  if (! opener.closed) {'."\n");
  $self->print('    if (document.foo.alist.selectedIndex > 0) {'."\n");
  $self->print('      opener.document.searchform.favlist.value = 
document.foo.alist.options[document.foo.alist.selectedIndex].value;'."\n");
  $self->print('    } else {'."\n");
  $self->print('      opener.document.searchform.favlist.value = 
document.foo.blist.value;'."\n");
  $self->print('    }'."\n");
  $self->print('  }'."\n");
  $self->print('  self.close ();'."\n");
  $self->print('}'."\n");
  $self->print('</script>'."\n");
  $self->print($self->head2);

  $self->print('<div align=center>'."\n");
  $self->print('   <form name=foo onSubmit="exit (); return false;">'."\n");
  $self->print('    Please Pick what list you\'d like to add these to:<br>'."\n");
  $self->print('    <select name=alist>'."\n");
  $self->print('     <option value="null" selected> -- Choose --</option>'."\n");
  while (my ($name) = $sth->fetchrow_array) {
    $self->print('<option value="'.$name.'">'.$name.'</option>'."\n");
  }
  $sth->finish;
  $self->print('    </select><br>'."\n");
  $self->print('     - OR -<br>'."\n");
  $self->print('     Type in a new name:<br>'."\n");
  $self->print('    <input type=text name=blist><br>'."\n");
  $self->print('    <input type=submit value="OK">'."\n");
  $self->print('    <input type=button value="cancel" onClick="self.close ();">'."\n");
  $self->print('   </form>'."\n");
  $self->print('  </div>'."\n");
}

sub show_titles {
  my $self = shift;
  my $id = shift;
  $self->warn("-- -- Now entering ".(caller(0))[3]);
  $self->print($self->head1("WebMusique: Song Titles"));
  $self->print($self->head2);
  $self->print("<div align=center id=wayplainw>\n");
  my $sth = eval { $self->dbh->prepare("SELECT g.name, a.name, l.name, s.name from 
song s, album l, artist a, genre g, combo c WHERE g.genre_id = c.genre_id AND 
a.artist_id = c.artist_id AND l.album_id = c.album_id AND s.combo_id = c.combo_id AND 
c.combo_id = ? order by s.track, s.song_id"); };
  if ($@) {
    $self->log_error("Failed to prepare combo lookup: $@");
    $self->print("Failed to fill your request (-1)");
    return;
  }
  eval { $sth->execute($id); };
  if ($@) {
    $self->log_error("Failed to execute combo lookup: $@");
    $self->print("Failed to fill your request (-2)");
    return;
  }
  $self->print('<table border=0 cellspacing=0 cellpadding=2 width="90%">');
  my $title = 0;
  while (my $row = $sth->fetchrow_arrayref) {
    if (! $title) {
      $self->print('<tr><td align=left id=plain>'.$row->[1].'</td></tr>');
      $self->print('<tr><td align=left id=plain>"'.$row->[2].'"</td></tr>');
      $self->print('<tr><td align=left id=wayplainw>('.$row->[0].")</td></tr>\n");
      $title = 1;
    }
    $self->print("<tr><td align=left id=wayplainw><font 
color=lightsteelblue>&nbsp;&nbsp;&nbsp;&nbsp;".$row->[3]."</font></td></tr>\n");
  }
  $self->print("</table><P>\n");
  $self->print('<a href="javascript:self.close()" id=plain>Close Window</a></div>');
  $sth->finish;
  $self->warn("all done showing titles");
}

sub head {
  my $self = shift;
  my $title = shift;
  $self->warn("-- -- Now entering ".(caller(0))[3]);
  $self->print($self->head1($title));
  $self->print("<script>\n");
  $self->print($self->scroll_menu_js);
  $self->print($self->side_menu_js);
  my $body;
  if (! $self->can_play || ! $self->can_stream) {
    $self->print($self->no_permission_js);
  } else {
    $self->print($self->custom_list_js);
    $body = " onFocus='custom_list_on_top()';"
  }
  $self->print("</script>\n");
  $self->print($self->head2($body));
  $self->title_bar;
  $self->warn("back from title bar");
  $self->print('<table border=0 cellpadding=0 cellspacing=0 width="100%">');
  $self->print('<tr><td width=124><img src="empty.gif" width=124 height=1></td>');
  $self->print('<td width="95%" valign=top align=center id=plain>');
}

sub foot {
  my $self = shift;
  my $page = shift;
  $self->warn("-- -- Now entering ".(caller(0))[3]);
  $self->print("</td></tr></table>\n");
  $self->side_menu_html($page);
  $self->scroll_html;
  # $self->print("<script>\nonLoad = 
initDrop();\nonFocus=\"setTimeout('custom_list_on_top()', 150)\";\n</script>");
  $self->print("<script>onLoad = initDrop();</script>");
  $self->push_cleanup;
  $self->warn(" - - All finished with the foot (and this request probably)");
  return Apache::Constants::OK();
}

sub push_cleanup {
  my $self = shift;
  if ($self->can_play && Apache->can('push_handlers')) {
    $self->warn("pushing PerlCleanupHandler for playlist");
    Apache->push_handlers("PerlCleanupHandler", \&cleanup);
  } else {
    $self->warn("skipping the Playlist check");
  }
}

sub index {
  my $self = shift;
  $self->warn("-- -- Now entering ".(caller(0))[3]);
  $self->head("WebMusique: Current Playlist");
  $self->{_player}->show_simple();
  $self->foot("playlist");
  return Apache::Constants::OK();
}
  
sub add_favlist {
  my $self = shift;
  my $favlist = shift;
  my $id = shift;
  my $sth = eval { $self->dbh->prepare("INSERT INTO favlist (combo_id, name) VALUES 
(?, ?)"); };
  if ($@) {
    $self->log_error("Failed to prepare favlist insert: $@");
    return;
  }
  foreach my $combo_id (@$id) {
    $self->warn("attempting to insert $combo_id into favlist $favlist");
    eval { $sth->execute($combo_id, $favlist); };
    if ($@) {
      $self->log_error("failed to execute insert into favlist: $@");
    }
  }
}

sub edit_favlist {
  my $self = shift;
  my $apr = Apache::Request->new($self->r, DISABLE_UPLOADS => 1);
  my @values = $apr->param('id');
  my $name = $apr->param('edit_favlist');
  my $sql = "DELETE FROM favlist WHERE name = ".$self->dbh->quote($name)." AND 
combo_id in (";
  $sql .= join ', ', @values;
  $sql .= ")";
  eval { $self->dbh->do($sql); };
  if ($@) {
    $self->log_error("failed to remove entries from favlist: $@");
  }
  $self->warn("Successfully removed ".(scalar @values)." from favlist: $name");
  return $self->redirect;
}


sub edit {
  my $self = shift;
  my %args = $self->r->args;
  foreach (keys %args) {
    $self->warn("  Looking at arg value : $_ => ".$args{$_});
  }
  # do_what  genre_id
  (my $name = $args{genre_id}) =~ s/^fav_//g;
  if ($args{do_what} eq "delete") {
    my $sth = eval { $self->dbh->do("DELETE FROM favlist WHERE name = 
".$self->dbh->quote($name)); };
    if ($@) {
      $self->log_error("Could not do favlist delete: $@");
      return;
    }
    $self->warn("Succeeded in removing the fav list: $name");
    $self->redirect;
    return;
  } elsif ($args{do_what} eq "edit") {
    $self->head("WebMusique: Edit \"$name\"");
    my $sth = eval { $self->dbh->prepare("SELECT combo_id FROM favlist WHERE name = 
?"); };
    if ($@) {
      $self->log_error("could not prepare favlist lookup on $name: $@");
      $self->print("failed to retrieve that list (-1)");
    } else {
      eval { $sth->execute($name); };
      if ($@) {
        $self->log_error("could not execute favlist lookup on $name: $@");
        $self->print("failed to retrieve that list (-2)");
      } else {
        my @favs;
        while (my ($combo_id) = $sth->fetchrow_array) {
          push @favs, $combo_id;
        }
        $self->combo_list([EMAIL PROTECTED], "editfav", {edit_favlist => $name});
        $self->foot("editfav");
        return Apache::Constants::OK();
      }
    }
  }
}

sub genre {
  my $self = shift;
  $self->warn("-- -- Now entering ".(caller(0))[3]);
  my %args = $self->r->args;
  foreach (keys %args) {
    $self->warn("  Looking at arg value : $_ => ".$args{$_});
  }
  if ($args{genre_id} eq "null" || $args{genre_id}!~/\S/) {
    $self->redirect;
    return;
  }
  if ($args{genre_id}=~/^genre/) {
    $self->head("WebMusique: Listing by Category");
    $self->combo_list($args{genre_id});
  } elsif ($args{genre_id}=~/^fav/) {
    (my $fav = $args{genre_id})=~s/^fav_//g;
    $self->head("WebMusique: Listing from Custom List");
    my $sth = eval { $self->dbh->prepare("SELECT combo_id FROM favlist WHERE name = 
?"); };
    if ($@) {
      $self->log_error("could not prepare favlist lookup on $fav: $@");
      $self->print("failed to retrieve that list (-1)");
    } else {
      eval { $sth->execute($fav); };
      if ($@) {
        $self->log_error("could not execute favlist lookup on $fav: $@");
        $self->print("failed to retrieve that list (-2)");
      } else {
        my @favs;
        while (my ($combo_id) = $sth->fetchrow_array) {
          push @favs, $combo_id;
        }
        $self->combo_list([EMAIL PROTECTED]);
      }
    }
  }
  $self->warn("back from combo_list...");
  $self->foot("searchform");
  return Apache::Constants::OK();
}

sub random {
  my $self = shift;
  $self->warn("-- -- Now entering ".(caller(0))[3]);
  my %args = $self->r->args;
  foreach (keys %args) {
    $self->warn("  Looking at arg value : $_ => ".$args{$_});
  }
  if ($args{playlist_mod} eq "New") {
    $self->warn("  Should be removing old playlist");
  }
  $self->warn("  Should be adding ".$args{playlist_qty}." tracks");
  $self->warn("  Now we should be adding from ".$args{playlist_list});
  my $table = "a".$$;
  my $sql = "create table $table (myid int not null auto_increment, song_id int not 
null, primary key (myid), key (song_id))";
  eval { $self->dbh->do($sql); };
  if ($@) {
    $self->log_error("failed to create table: $table: $@");
    return;
  }
  $self->warn("Created a temp table : $table");
  my @binds;
  $sql = "insert into $table (song_id) ";
  if ($args{playlist_list}=~/^fav_(.*)/) {
    my $fav_name = $1;
    $sql .= "SELECT s.song_id from song s, favlist f where f.combo_id = s.combo_id AND 
f.name = ?";
    push @binds, $fav_name;
  } elsif ($args{playlist_list}=~/genre_(\d+)/) {
    my $category = $1;
    $sql .= "select s.song_id from song s, combo c where c.combo_id = s.combo_id AND 
c.genre_id = ?";
    push @binds, $category;
  }
  my $sth = eval { $self->dbh->prepare($sql); };
  if ($@) {
    $self->log_error("failed preparing the import song titles: $@");
    eval { $self->dbh->do("drop table $table"); };
    if ($@) {
      $self->log_error("******* Failed to cleanup temp table: $table");
    } else {
      $self->warn("we should have dropped table :$table");
    }
    return;
  }
  eval { $sth->execute(@binds); };
  if ($@) {
    $self->log_error("failed executing the import song titles: $@");
    eval { $self->dbh->do("drop table $table"); };
    if ($@) {
      $self->log_error("******* Failed to cleanup temp table: $table");
    } else {
      $self->warn("we should have dropped table :$table");
    }
    return;
  }
  my $total = $sth->rows;

  undef @binds;
  $self->warn("getting back $total rows -- looking for $args{playlist_qty}");

  my $count = $args{playlist_qty};
  if ($count >= $total) {
    $sql = "SELECT s.file, s.combo_id from song s, $table j WHERE s.song_id = 
j.song_id order by s.combo_id";
  } else {
    my $newcount = (($count*2) > $total)?($total-$count):$count;
    my $counter = $newcount;
    my %files;
    while ($counter > 0) {
      my $index = (int rand($total)) + 1;
      if (! $files{$index}) {
        $files{$index} = 1;
        $counter--;
      }
    }
    $sql = "SELECT s.file, s.combo_id from song s, $table j WHERE s.song_id = 
j.song_id AND j.myid ";
    $sql .= ($newcount == $count)?" IN (":" NOT IN (";
    $sql .= join ', ', ('?') x scalar keys %files;
    $sql .= ") order by s.combo_id";
    @binds = keys %files;
  }
  $self->{_player}->random_do($args{playlist_mod}, $sql, [EMAIL PROTECTED]);
  eval { $self->dbh->do("drop table $table"); };
  if ($@) {
    $self->log_error("******* Failed to cleanup temp table: $table");
  } else {
    $self->warn("we should have dropped table :$table");
  }
  $self->{_player}->cleanup;
  return 1;
}
  

sub search {
  my $self = shift;
  $self->warn("-- -- Now entering ".(caller(0))[3]);
  my %args = $self->r->args;
  foreach (keys %args) {
    $self->warn("  Looking at arg value : $_ => ".$args{$_});
  }
  $self->head("WebMusique: Searching $args{search_on} for $args{search_for}");
  if ($args{search_for}!~/\S/) {
    $self->warn("looking for a full list of everything!");
    $self->combo_list("all");
    $self->warn("back from combo_list...");
  } else {
    my $sql = "SELECT c.combo_id FROM combo c, ";
    if ($args{search_on} eq "song") {
      $sql .= "$args{search_on} s WHERE s.combo_id=c.combo_id AND s.name like ?";
    } else {
      $sql .= "$args{search_on} q WHERE q.".$args{search_on}."_id= 
c.".$args{search_on}."_id AND q.name like ?";
    }
    my $sth = eval { $self->dbh->prepare($sql); };
    if ($@) {
      $self->log_error("could not prepare search query: \n$sql\n$@");
      $self->print("Error trying to list your request");
      return;
    }
    eval { $sth->execute('%'.$args{search_for}.'%'); };
    if ($@) {
      $self->log_error("could not execute search query: \n$sql\n$@");
      $self->print("Error trying to list your request");
      return;
    }
    my @combo;
    while (my ($combo_id) = $sth->fetchrow_array) {
      push @combo, $combo_id;
    }
    $sth->finish;
    $self->warn("Found a total of ".(scalar @combo)." combo id's to pass to 
combo_list");
    $self->combo_list([EMAIL PROTECTED]);
  }
  $self->foot("searchform");
  return Apache::Constants::OK();
}

sub combo_list {
  my $self = shift;
  $self->warn("-- -- Now entering ".(caller(0))[3]);
  # combo is either an array ref of combo_id's or "all" representing a full list
  my $combo = shift;
  my $form = shift || "searchform";
  my $hidden = shift || {};
  if ($combo ne "all" && $combo!~/^genre/ && (! scalar @{$combo})) {
    $self->print("<div align=center id=plainw>No Matches</div>");
    $self->warn("no matches on nothing....");
    return;
  }
  my $sql = "SELECT distinct(artist_id) FROM combo";
  my $genre = 0;
  if ($combo=~/^genre/) {
    $genre = 1;
    (my $genre_id = $combo)=~s/^genre_//g;
    $sql .= " WHERE genre_id = ?";
    $combo = [ $genre_id ];
  } elsif ($combo ne "all") {
    $sql .= " WHERE combo_id in (";
    $sql .= join ', ', ('?') x scalar @{$combo};
    $sql .= ")";
  }

  my $sth = eval { $self->dbh->prepare($sql); };
  if ($@) {
    $self->log_error("could not prepare artist count: \n$sql\n$@");
    $self->print("Error trying to list your request");
    return;
  }
  if ($combo ne "all") {
    eval { $sth->execute(@$combo); };
  } else {
    eval { $sth->execute(); };
  }
  if ($@) {
    $self->log_error("could not execute passed in sql: \n$sql\n$@");
    $self->print("Error trying to list your request");
    return;
  }
  my $artist_count = $sth->rows;
  $self->warn(" match returned $artist_count artists...");
  $sth->finish;
  $sql = "SELECT g.name, r.name, l.name, c.combo_id FROM combo c, genre g, artist r, 
album l ";
  $sql .= "WHERE c.genre_id = g.genre_id AND r.artist_id = c.artist_id AND l.album_id 
= c.album_id";
  if ($genre) {
    $sql .= " AND c.genre_id = ?";
  } elsif ($combo ne "all") {
    $sql .= " AND c.combo_id in (";
    $sql .= join ', ', ('?') x scalar @{$combo};
    $sql .= ")";
  }

  $sql .= " ORDER BY r.name, l.name ";
  $sth = eval { $self->dbh->prepare($sql); };
  if ($@) {
    $self->log_error("could not prepare artist count: \n$sql\n$@");
    $self->print("Error trying to list your request");
    return;
  }
  if ($combo ne "all") {
    eval { $sth->execute(@$combo); };
  } else {
    eval { $sth->execute(); };
  }
  if ($@) {
    $self->log_error("could not execute passed in sql: \n$sql\n$@");
    $self->print("Error trying to list your request");
    return;
  }
  my $rows = $sth->rows;
  $self->warn("  ...and matched a total row count of $rows");
  my $total_rows = $rows + $artist_count;

  if (ref $hidden && defined $hidden->{edit_favlist}) {
    $self->print("<br>Editting \"".$hidden->{edit_favlist}."\"...\n");
  } else {
    $self->print("<br>Found $rows Album".($rows!=1?'s':'')." that matched.");
  }
  $self->print("<form name=$form method=post>\n");
  $self->print("<input type=hidden name=favlist>");
  if (ref $hidden) {
    foreach my $key (keys %$hidden) {
      $self->print("<input type=hidden name=$key value=\"".$hidden->{$key}."\">\n");
    }
  }
  $self->print('<table border=0 cellpadding=0 cellspacing=5 width="100%">');
  $self->print('<tr><td bgcolor=darkslateblue valign=top>');
  $self->print('<table border=0 cellpadding=1 cellspacing=0 width="100%">');
  my $temp = int $total_rows/2;
  my $artist;
  my $temp_genre;
  my $pos = 0;
  my $newrow = 0;

  my $can_play = $self->can_play;
  while (my $row = $sth->fetchrow_arrayref) {
    if ($row->[1] ne $artist) {
      $temp_genre = $row->[0];
      if (($total_rows > 20) && ($pos > ($total_rows/2)) && (! $newrow)) {
        $newrow = 1;
        $self->warn("halfway now... pos is $pos and total_rows is $total_rows");
        $self->print('</table></td><td valign=top bgcolor=darkslateblue 
width="50%"><table border=0 cellpadding=1 cellspacing=0 width="100%">');
        $self->print("\n");
      }
      $self->print('<tr><td valign=top bgcolor="darkslateblue" 
id=wayplainw>&nbsp;<b>'.$row->[1].'</b> <font 
size=-1><i>('.$row->[0].')</i></font></td></tr>');
      $self->print("\n");
      $pos++;
    }
    $pos++;
    $artist = $row->[1];
    $self->print('<tr><td valign=top bgcolor="darkslateblue" 
id=plainw>&nbsp;&nbsp;&nbsp;&nbsp;');
    if ($can_play) {
      $self->print('<input type="checkbox" name=id value="'.$row->[3].'">');
      $self->print('<a href="edit_combo.html?combo_id='.$row->[3].'"><img 
src="pencil2.gif" border=0 width=14 height=14></a>');
    } else {
      $self->print('&nbsp;&nbsp;&nbsp;');
    }
    $self->print('<A HREF="javascript:mp3()" id=wayplainw 
onClick="window.open(\'show_titles.html?id='.$row->[3].'\', \'show_titles\', 
\'dependant=1,status=0,titlebar=0,resizable=1,height=500,innerHeight=500,width=400,innerWidth=400,screenX=200,left=200,screenY=200,top=200\')">');
    $self->print($row->[2].'</a>');

    if ($row->[0] ne $temp_genre) {
      $self->print('&nbsp;<font size=-1><i>('.$row->[0].')</i></font>');
    }
    $self->print("</td></tr>\n");
  }
  $sth->finish;
  $self->print("</table></td></tr></table>\n");
  $self->warn("combo_list is finished...");
  return 1;
}

sub custom_list_js {
  return <<EOJS;
  var customListChild = null;
  function customPick() {
    customListChild=window.open('custom_list.html', 'custom_list', 
'dependant=1,status=0,titlebar=0,resizable=1,height=400,innerHeight=400,width=200,innerWidth=200,screenX=50,left=50,screenY=100,top=100');
  }
  function custom_list_on_top () {
    if (customListChild!=null) {
      if (customListChild.closed) {
        customListChild = null;
        document.forms.searchform.action = 'add_favlist.html';
        document.forms.searchform.submit();
      } else {
        customListChild.focus ();
      }
    }
  }
EOJS
}


sub title_bar {
  my $self = shift;
  $self->warn("-- -- Now entering ".(caller(0))[3]);
  $self->print("<table cellpadding=0 cellspacing=0 width=\"800\" border=0>");
  $self->print("<tr><td width=\"200\" rowspan=3>");
  $self->print("<img src=\"promp3.gif\" width=200 height=56 border=0 
alt=\"WebMusique\"></td>");
  $self->print("<td width=\"600\" align=center><img src=empty.gif width=1 height=19 
border=0></td></tr>");
  $self->print("<tr><td width=\"600\" align=center id=wayplain>");
  $self->{_player}->show_playing;
  $self->print("</td></tr><tr><td width=\"600\" align=center id=wayplain>");
  $self->print("</td></tr></table>");
  $self->print("\n");
  $self->mkline;
}

sub redirect {
  my $self = shift;
  my $url = shift;
  $url = "index.html" unless $url;
  $self->r->header_out(Location=> $url);
  $self->r->method('GET');
  $self->r->headers_in->unset('Content-length');
  $self->r->status(302);
  $self->r->send_http_header;
  return 302;
}

sub scroll_html {
  my $self = shift;
  $self->warn("-- -- Now entering ".(caller(0))[3]);
  $self->scroll_goodies("mngenre", "by_genre.gif", 1);
  $self->scroll_goodies("mnrandom", "random.gif", 2);
  $self->scroll_goodies("mnsearch", "search.gif", 3);
  $self->scroll_goodies("mnedit", "edit.gif", 4);
  $self->scroll_overlay;
}

sub side_menu_html {
  my $self = shift;
  my $page = shift;
  my $space = 3;
  $self->print("\n");
  $self->print('<div id=slideMenu 
style="position:absolute;font-family:arial;font-size:14pt;visibility:hidden;text-decoration:none;">');
  $self->print('<table width=114 border=0 cellpadding=0 cellspacing=0>');
  $self->print('<tr><td valign=middle align=center>');
  $self->{_player}->show_volume;
  $self->{_player}->show_controls;
  $self->mkline($space);
  if ($page eq "playlist" && $self->can_play) {
    $self->print('<a href="javascript:submitForm(\'playlist\', \'cut_tracks.html\')" 
id=wayplainw><b>Remove Selected</b></a><br>');
    $self->mkline($space);
  } elsif ($page eq "searchform") {
    if ($self->can_play || $self->can_stream) {
      $self->print('<A HREF="javascript:customPick()" id=plain>');
      $self->print('Add/Create Custom Lists</a><br>');
      $self->mkline($space);
    }
    if ($self->can_play) {
      $self->print('<a href="javascript:submitForm(\'searchform\', 
\'add_tracks.html\')" id=plain>Add to Playlist</a><br>');
      $self->mkline($space);
      $self->print('<a href="javascript:submitForm(\'searchform\', 
\'play_tracks.html\')" id=plain>Make a new Playlist</a><br>');
      $self->mkline($space);
    }
    if ($self->can_stream) {
      $self->print('<a href="javascript:submitForm(\'searchform\', 
\'stream_tracks.m3u\')" id=plain>Stream Selections</a><br>');
      $self->mkline($space);
    }
  } elsif ($page eq "editfav") {
    if ($self->can_stream || $self->can_play) {
      $self->print('<a href="javascript:submitForm(\'editfav\', 
\'edit_favlist.html\')" id=plain>Remove Selected</a><br>');
      $self->mkline($space);
    }
  }
  if (defined $page && $self->can_play) {
    $self->print('<a href="javascript:mp3()" onClick=\'invertForm("'.$page.'")\' 
id=wayplainw><b>Invert Selection</b></a><br>');
    $self->mkline($space);
  }
  $self->print('<a href="index.html" id=wayplainw><b>Current Playlist</b></a>');
  $self->mkline($space);
  
  # $self->print('<a href="scroll.html" id=wayplainw><b>Scroller</b></a><p>');
  # $self->print('<a href="scroll.html" id=wayplainw><b>Add to Favorites</b></a><p>');
  # $self->print('<a href="scroll.html" id=wayplainw><b>Add to Playlist</b></a><p>');
  # $self->print('<a href="scroll.html" id=wayplainw><b>Make New Playlist</b></a><p>');
  $self->print("</td></tr></table></div>\n");
}

sub scroll_goodies {
  my $self = shift;
  my $id = shift;
  my $image = shift;
  my $place = shift;
  $self->warn("-- -- Now entering ".(caller(0))[3]." (calling on $id)");
  $self->print("\n\n");
  $self->print('<div id="'.$id.'" 
style="position:absolute;visibility:hidden;z-index:100;">');
  my $link = substr($id, 2);
  $self->print('<form action="'.$link.'.html" method=get');
  if (($link eq "random" || $link eq "edit") && (! $self->can_play)) {
    $self->print(' onSubmit="no_permission(); return false;"');
  }
  $self->print(">\n");
  $self->print('<table width="600" border="0" cellspacing="0" cellpadding="0" 
height="75">');
  $self->print('<tr><td colspan=5 height=19 bgcolor="#666699"><img src="empty.gif" 
width=250 height=19 alt="" border="0"></td></tr>');
  $self->print('<tr><td colspan=5 height="40" align=center bgcolor="#666699"><nobr>');

  $self->{_player}->scroll_text($id);

  $self->print('</nobr></td></tr><tr>');
  for my $spot (1..4) {
    if ($place == $spot) {
      $self->print('<td width=150 height=16 align=center><a href="javascript:mp3()" 
onClick="element=\''.$id.'\';dropDown()"><img src="'.$image.'" width=100 height=16 
alt="" border="0"></a></td>');
    } else {
      $self->print('<td width=150 height=16 align=center><img src="empty.gif" 
width=100 height=1 border=0></td>');
    }
  }
  $self->print("</tr></table></form></div>\n");
}

sub scroll_overlay {
  my $self = shift;
  $self->warn("-- -- Now entering ".(caller(0))[3]);
  $self->print('<div id="overlay" 
style="position:absolute;visibility:hidden;z-index:200">');
  $self->print('<table width="600" border="0" cellspacing="0" cellpadding="0" 
height="16">');
  $self->print('<tr><td width=150 height=16 align=center><a href="javascript:mp3()" 
onClick="element=\'mngenre\';dropDown()">');
  $self->print('<img src="by_genre.gif" width=100 height=16 alt="" 
border="0"></a></td>');
  $self->print('<td width=150 height=16 align=center><a href="javascript:mp3()" 
onClick="element=\'mnrandom\';dropDown()">');
  $self->print('<img src="random.gif" width=100 height=16 alt="" 
border="0"></a></td>');
  $self->print('<td width=150 height=16 align=center><a href="javascript:mp3()" 
onClick="element=\'mnsearch\';dropDown()">');
  $self->print('<img src="search.gif" width=100 height=16 alt="" 
border="0"></a></td>');
  $self->print('<td width=150 height=16 align=center><a href="javascript:mp3()" 
onClick="element=\'mnedit\';dropDown()">');
  $self->print('<img src="edit.gif" width=100 height=16 alt="" 
border="0"></a></td></tr>');
  $self->print("</table></div>\n");
}

sub mkline {
  my $self = shift;
  my $space = shift || 0;
  $self->warn("-- -- Now entering ".(caller(0))[3]);
  $self->print("<table cellpadding=0 cellspacing=$space width=\"100%\" border=0>");
  $self->print("<tr><td bgcolor=\"\#443366\"><img src=\"empty.gif\" width=1 height=1 
border=0></td></tr></table>");
}


sub head1 {
  my $self = shift;
  my $title = shift || "WebMusique: Current Playlist";
  $self->warn("-- -- Now entering ".(caller(0))[3]);
  $self->r->content_type("text/html");
  $self->r->send_http_header;
  return <<EOH
  <HTML>
  <HEAD><TITLE>$title</title>
  <style type='text/css'>
    <!--
    
#plainw{font-family:arial,helvetica,sanserif,arial;font-size:14px;font-color:white;font-weight:bold;text-decoration:none;}
    
#wayplainw{font-family:arial,helvetica,sanserif,arial;font-size:14px;font-color:white;text-decoration:none;}
    
#linker{font-family:arial,helvetica,sanserif,arial;font-size:14px;font-color:lightsteelblue;text-decoration:none;}
    
#highlight{font-family:arial,helvetica,sanserif,arial;font-size:14px;font-color:khaki;text-decoration:none;}
    
#plain{font-family:arial,helvetica,sanserif,arial;font-size:14px;font-weight:bold;text-decoration:none;}
    
#wayplain{font-family:arial,helvetica,sanserif,arial;font-size:14px;text-decoration:none;}
    // -->
  </style>
EOH
}

sub head2 {
  my $self = shift;
  my $body = shift;
  $self->warn("-- -- Now entering ".(caller(0))[3]);
  $body = " ".$body if (defined $body && $body!~/^ /);
  $self->warn("Trying to set in the body: $body");
  return "</HEAD>\n<body bgcolor=\"darkslateblue\" marginheight=0 marginwidth=0 
leftmargin=0 topmargin=0 text=\"white\" link=lightsteelblue vlink=lightsteelblue 
alink=\"\#000099\"$body>\n";
}

sub warn {
  my $self = shift;
  return if (! $self->config('debug'));
  my $r = $self->r;
  my @caller = caller;
  $r->warn("$caller[0] ([$$] line $caller[2]): ".shift);
}

sub log_error{
  my $self = shift;
  my $r = $self->r;
  my @caller = caller;
  $r->warn("ERROR $caller[0] (line $caller[2]): ".shift);
}

sub init_player {
  my $self = shift;
  $self->warn("-- -- Now entering ".(caller(0))[3]);
  my $player = "ProMP3::Player::".$self->config("mp3_player");
  eval "use ".$player;
  if ($@) {
    $self->log_error("Could not find the player: $player");
    $self->log_error($@);
    return;
  }
  $self->warn("successfully loaded player: $player");
  my $new = "$player->new";
  $self->warn("Trying to execute $new");
  $self->{_player} = eval $new;
  $self->{_player}{r} = $self->r;
  $self->{_player}->init;
  $self->{_player}{_config} = $self->config;
}

sub init_dbh {
  my $self = shift;
  $self->warn("-- -- Now entering ".(caller(0))[3]);
  my $db = "ProMP3::DB::".$self->config("database");
  eval "use ".$db;
  if ($@) {
    $self->log_error("Could not find the DB module: $db");
    $self->log_error($@);
    return;
  }
  $self->warn("successfully loaded ProMP3::DB module: $db");
  my $new = "$db->new";
  $self->warn("Trying to execute $new");
  $self->{_db} = eval $new;
  $self->{_db}{r} = $self->r;
  $self->{_db}->init;
  if (defined $self->{_player}) {
    $self->warn("trying to set dbh");
    $self->{_player}{dbh} = $self->dbh;
    if (defined $self->{_player}{dbh}) {
      $self->warn("Dbh appears to be defined in player");
    } else {
      $self->warn("Dbh appears NOT to be defined in player");
    }
  }
}
  
sub dbh {
  return shift->{_db}{_dbh};
}

sub side_menu_js {
  return <<EOJS
  var isSliding = 0;
  var menuInterval;
  function positionMenu() {
    if (document.all) {
      document.all.slideMenu.style.left = 10;
      document.all.slideMenu.style.top = 65;
      document.all.slideMenu.style.visibility = "visible";
    } else if (document.layers) {
      document.slideMenu.pageX = 10;
      document.slideMenu.pageY = 65;
      document.slideMenu.visibility = "show";
    } else if (document.getElementById) {
      document.getElementById("slideMenu").style.left = "10px";
      document.getElementById("slideMenu").style.top = "65px";
      document.getElementById("slideMenu").style.visibility = "visible";
    }
    menuInterval = setInterval('slider()', 500);
  }
  
  function slider() {
    var offset;
    var menupos;
    if (document.all) {
      offset = document.body.scrollTop;
      menupos = parseInt(document.all.slideMenu.style.top);
    } else  {
      offset = window.pageYOffset;
      if (document.layers) {
        menupos = document.slideMenu.pageY;
      } else if (document.getElementById) {
        menupos = parseInt(document.getElementById("slideMenu").style.top);
      }
    }
    var targetpos = 0;
    if (offset < 65) {
      if (menupos > 65) {
        targetpos = 65;
      }
    } else if (menupos != (offset + 5)) {
      targetpos = offset + 5;
    }
    if (targetpos) {
      var delta;
      delta = Math.abs(menupos - targetpos)
      delta = Math.round(delta/3);
      if (delta < 1) {
        delta = 1;
      }
      if (menupos > targetpos) {
        targetpos = menupos - delta;
      } else if (menupos < targetpos) {
        targetpos = menupos + delta;
      } else {
        targetpos = menupos;
      }
      if (! isSliding) {
        clearInterval(menuInterval);
        menuInterval = setInterval('slider()', 20);
        isSliding = true;
      }
      if (document.all) {
        document.all.slideMenu.style.top = targetpos;
      } else if (document.layers) {
        document.slideMenu.pageY = targetpos;
      } else if (document.getElementById) {
        document.getElementById("slideMenu").style.top = targetpos + "px";
      }
    } else if (isSliding) {
      isSliding = false;
      clearInterval(menuInterval);
      menuInterval = setInterval('slider()', 500);
    }
  }
  function submitForm (element, dest) {
    var myForm = document.forms[element];
    if (! myForm) {
      return;
    }
    if (dest) {
      myForm.action = dest;
      myForm.submit();
    }
  }
  function invertForm (element) {
    var myForm = document.forms[element];
    if (! myForm) {
      return;
    }
    for(var id = 0; id < myForm.elements.length; id++) {
      if (myForm.elements[id].type == "checkbox") {
        myForm.elements[id].checked = (! myForm.elements[id].checked);
      }
    }
  }
EOJS
}

sub no_permission_js {
  return <<EOJS
  function no_permission() {
    alert("You do not have permission.");
  }
EOJS
}

sub scroll_menu_js {
  return <<EOJS
  var element = "";
  var tempelement = "";
  var xpos =  200;
  var ypos =  -59;
  var intervalID = "";
  var drops = new Array(3);
    drops["mnrandom"] = new Array;
    drops["mnrandom"]["dir"] = "down";
    drops["mnrandom"]["curpos"] = ypos;
    drops["mnsearch"] = new Array;
    drops["mnsearch"]["dir"] = "down";
    drops["mnsearch"]["curpos"] = ypos;
    drops["mngenre"] = new Array;
    drops["mngenre"]["dir"] = "down";
    drops["mngenre"]["curpos"] = ypos;
    drops["mnedit"] = new Array;
    drops["mnedit"]["dir"] = "down";
    drops["mnedit"]["curpos"] = ypos;

  function initDrop() {
    if (document.all) {
      mnsearch.style.posTop = ypos;
      mnsearch.style.posLeft = xpos;
      mnsearch.style.visibility = "visible";
      mnrandom.style.posTop = ypos;
      mnrandom.style.posLeft = xpos;
      mnrandom.style.visibility = "visible";
      mngenre.style.posTop = ypos;
      mngenre.style.posLeft = xpos;
      mngenre.style.visibility = "visible";
      mnedit.style.posTop = ypos;
      mnedit.style.posLeft = xpos;
      mnedit.style.visibility = "visible";
      overlay.style.posTop = 0;
      overlay.style.posLeft = xpos;
      overlay.style.visibility = "visible";
    } else if (document.layers) {
      document.mnrandom.pageX = xpos;
      document.mnrandom.pageY = ypos;
      document.mnrandom.visibility = "show";
      document.mnsearch.pageX = xpos;
      document.mnsearch.pageY = ypos;
      document.mnsearch.visibility = "show";
      document.mngenre.pageX = xpos;
      document.mngenre.pageY = ypos;
      document.mngenre.visibility = "show";
      document.mnedit.pageX = xpos;
      document.mnedit.pageY = ypos;
      document.mnedit.visibility = "show";
      document.overlay.pageX = xpos;
      document.overlay.pageY = 0;
      document.overlay.visibility = "show";
      // document.mnrandom.zIndex = 100;
    } else if (document.getElementById) {
      document.getElementById("mnsearch").style.left = xpos;
      document.getElementById("mnsearch").style.top = ypos + "px";
      document.getElementById("mnsearch").style.visibility = "visible";
      document.getElementById("mnsearch").style.zIndex = 10;
      document.getElementById("mnrandom").style.left = xpos;
      document.getElementById("mnrandom").style.top = ypos + "px";
      document.getElementById("mnrandom").style.visibility = "visible";
      document.getElementById("mnrandom").style.zIndex = 10;
      document.getElementById("mngenre").style.left = xpos;
      document.getElementById("mngenre").style.top = ypos + "px";
      document.getElementById("mngenre").style.visibility = "visible";
      document.getElementById("mngenre").style.zIndex = 10;
      document.getElementById("mnedit").style.left = xpos;
      document.getElementById("mnedit").style.top = ypos + "px";
      document.getElementById("mnedit").style.visibility = "visible";
      document.getElementById("mnedit").style.zIndex = 10;
      document.getElementById("overlay").style.left = xpos;
      document.getElementById("overlay").style.top = "0px";
      document.getElementById("overlay").style.visibility = "visible";
      document.getElementById("overlay").style.zIndex = 20;
    }
    positionMenu();
  }
  
  function dropDown() {
    var elem;
    var stopPoint = 0;
    if (tempelement) {
      elem=tempelement;
    } else {
      elem=element;
    }
    if (drops[elem]["dir"] == "down") {
      if (drops[elem]["curpos"] == ypos) {
        if (elem != "mnsearch" && drops["mnsearch"]["dir"] == "up") {
          tempelement = "mnsearch";
          dropDown();
          return;
        } else if (elem != "mnrandom" && drops["mnrandom"]["dir"] == "up") {
          tempelement = "mnrandom";
          dropDown();
          return;
        } else if (elem != "mngenre" && drops["mngenre"]["dir"] == "up") {
          tempelement = "mngenre";
          dropDown();
          return;
        } else if (elem != "mnedit" && drops["mnedit"]["dir"] == "up") {
          tempelement = "mnedit";
          dropDown();
          return;
        }
      }
      drops[elem]["curpos"] += 3;
      if (drops[elem]["curpos"] >= 0) {
        stopPoint = 1;
      }
    } else {
      drops[elem]["curpos"] -= 3;
      if (drops[elem]["curpos"] < ypos) {
        stopPoint = 1;
      }
    }
    if (stopPoint) {
      if (intervalID) {
        clearInterval(intervalID);
        intervalID = "";
      }
      if (drops[elem]["dir"] == "down") {
        drops[elem]["dir"] = "up";
      } else {
        drops[elem]["dir"] = "down";
      }
      if (tempelement) {
        tempelement = null;
        dropDown();
      }
      return;
    }
    if (document.all) {
      if (elem == "mnsearch") {
        mnsearch.style.posTop = drops[elem]["curpos"];
      } else if (elem == "mnrandom") {
        mnrandom.style.posTop = drops[elem]["curpos"];
      } else if (elem == "mngenre") {
        mngenre.style.posTop = drops[elem]["curpos"];
      } else if (elem == "mnedit") {
        mnedit.style.posTop = drops[elem]["curpos"];
      }
    } else if (document.layers) {
      document[elem].pageY = drops[elem]["curpos"];
    } else if (document.getElementById) {
      document.getElementById(elem).style.top = drops[elem]["curpos"] + "px";
    }
    if (! intervalID) {
      intervalID = setInterval('dropDown()', 1);
    }
  }
      
  function mp3 () {
  }
EOJS
}


# IP handling stuff:

sub can_view {
  my $self = shift;
  $self->access("allow_view");
}
sub can_stream {
  my $self = shift;
  $self->access("allow_stream");
}
  
sub can_play {
  my $self = shift;
  $self->access("allow_player");
}

sub access {
  my $self = shift;
  my $access_var = shift;
  my $ip = $self->r->connection->remote_ip;
  $ip = convert_ip($ip);
  $ip = join '', sprintf("%03d%03d%03d%03d", unpack("CCCC", $ip));
  foreach my $ref (@{$self->config($access_var)}) {
    # $self->warn("ACCESS: $access_var  IP: ".$ip);
    # $self->warn("ACCESS: $access_var  Low: ".$ref->{low});
    # $self->warn("ACCESS: $access_var  High: ".$ref->{high});
    if ($ref->{low} <= $ip && $ip <= $ref->{high}) {
      $self->warn("IP address ".$self->r->connection->remote_ip." has access 
($access_var)");
      return 1 ;
    }
  }
  $self->warn("IP address ".$self->r->connection->remote_ip." DENIED ($access_var)");
  return;
}


sub set_ip_range {
  my $string = shift;
  my ($low, $high);
  if ($string=~/\-/) {
    my @ip = split '-', $string;
    if ($#ip != 1) {
      print STDERR "Unknown string: $string\n";
      return;
    }
    foreach (@ip) {
      if (! valid_ip($_)) {
        print STDERR "Invalid IP address: $_\n";
        return;
      }
    }
    $low = sprintf("%03d%03d%03d%03d", unpack("CCCC", convert_ip($ip[0])));
    $high = sprintf("%03d%03d%03d%03d", unpack("CCCC", convert_ip($ip[1])));
  } else {
    my ($addr, $mask) = split '/', $string;
    unless (valid_ip($addr)) {
      print STDERR "Invalid IP address: $addr\n";
      return;
    }
    $mask = 32 if (! defined $mask);
    my $addr_bin = convert_ip($addr);
    my $mask_bin = convert_mask($mask);
    my $inv_mask = inv_mask($mask_bin);
    my $broadcast = $inv_mask | $addr_bin;
    my $network = $broadcast ^ $inv_mask;
    $low = sprintf("%03d%03d%03d%03d", unpack("CCCC", $network));
    $high = sprintf("%03d%03d%03d%03d", unpack("CCCC", $broadcast));
  }
  return {low=>$low, high=>$high};
}

sub valid_ip {
  return 1 if ($_[0]=~/\d{0,3}(\.\d{0,3}){3}/);
  return;
}

sub inv_mask {
  my @sects = split '', shift;
  my $overall = '';
  foreach my $pos (0..$#sects) {
    my $current = '';
    vec($current, 0, 8) = 0;
    for (my $c = 1; $c < 255; $c*=2) {
      my $one = '';
      vec($one, 0, 8) = $c;
      if (! unpack("C", ($sects[$pos] & $one))) {
        $current = $current | $one;
      }
    }
    $overall .= $current;
  }
  return $overall;
}

sub convert_ip {
  return join '', map {pack("C", $_)} (split /\./, shift);
}

sub convert_mask {
  my $mask = shift;
  return convert_ip($mask) if (valid_ip($mask));
  my $overall;
  my $string = reverse sprintf("%032s", '1' x $mask);
  for (my $c=0; $c<32; $c+=8) {
    my $out = 0;
    my @wank = split '', substr($string, $c, 8);
    for (my $m=1; $m<255; $m*=2) {
      $out += $m if (pop @wank);
    }
    $overall .= pack("C", $out);
  }
  return $overall;
}



1;
__END__

=head1 NAME

ProMP3 - Apache module for music organizing, playing and streaming mp3 files.

=head1 SYNOPSIS

  # Using Apache::DBI is optional
  PerlModule Apache::DBI

  # The main configuration file for ProMP3
  # set the correct path for wherever you place it
  PerlRequire /path/to/promp3.handler.pl

  # require the ProMP3 module
  PerlModule ProMP3

  # 'promp3' (in the "Location" below) can be 
  # whatever you choose but remember this for later
  <Location "/promp3/">
      SetHandler perl-script
      PerlHandler ProMP3
  </Location>

=head1 DESCRIPTION

This module will import information about your mp3's into
a database and use that information to either play them directly
from the webserver, or stream them to the client.

What sets ProMP3 apart from the rest of the mp3 tools is that
it does play mp3's from the server.  Meaning it uses Xmms on the server
and sends the music out a soundcard on the server.  It's on my To-Do
list to add support for other players.


=item A quick list of features:

-- Can play directly from xmms on the server  ...or

-- Allows streaming of mp3's over http

-- Categorizes by album genre for later displaying

-- Allows creation of "custom" categories to pick out your favorites
and full editting capability of the custom lists.

-- Allows random selections: 
      "Play 100 tracks from Rock" or
      "Add 300 tracks from Jay's Favorites"

-- Allows full text searching on Artist, Album or Song title.

-- probably a few more smaller points I can't think of.

=item History

ProMP3 came about from working in a fairly open office with fairly 
open people.  And also the desire to control a whole-home stereo
from any of the multiple computers throughout my house.  I couldn't
find any tools to do this.  To make matters worse, the streaming 
software I did find was geared towards napster-crazy folks who want
to listen to the 200 songs randomly through streaming.  Nothing 
I tried (or even wrote in the beginning) could withstand the 
1000+ plus albums in my collection.  They were either too slow, or 
impossible to quickly find what I wanted.

So the things I focused on was organization, speed and playing from a 
central server.  I added streaming later to please the napster-crazy folks.


=head2 Installation

See the b<INSTALL> file for specific instructions.

=head2 Using ProMP3

Once you get everything installed, and everything seems to be running, it's
time to fire up a browser.  As much as I hate to admit it, Netscape 4 is not
the browser to use for ProMP3.  Everything will work just fine, but for some
reason, it takes longer for Netscape to render the page then generating it and
downloading does.  Internet Explorer seems to render the fastest while NS6 is
a close second.

SIDE NOTE: I spent way too much time on the pretty-factor of the package.  There
is heavy use of javascript and style sheets.  You must use a browser that supports
these and be sure that they are allowed.

Go to the webserver and the directory specfied in the <Location> tag in the httpd.conf
file.  Using the default would translate to http://your.server.com/promp3/.  Of course,
an IP address can be substituted in place of your.server.com.  This should bring
up the intial page.  If you have songs loaded in xmms already, you may see them
listed on this page.  Let's take a moment to look at what's being displayed here.

=over 4

=item The Top of the window

There are four "drop-downs" along the top of the window, currently they are labelled
"Category", "Random", "Search" and "Edit".  You can click on any one of them and 
an area should slide down to reveal actions you can take.  I spent a lot of time 
getting that sliding motion so enjoy the geek-factor to its fullest.

=item Category

This allows you to quickly pick a category (or "genre") to view with the purpose of 
listening to it.  You'll see later how to stream them or put them into the playlist.
Once you've created custom categories, you'll see them listed in the bottom surrounded
by quotes.

=item Random

This allows you to "Add" to the playlist or "Make a new list" where you specify
a number of tracks to pick randomly from a category or custom category.  First,
you'd specify if you'd like to "Add" to the current list, meaning whatever is 
currently in the playlist stays there, and the tracks will be added to it.
Otherwise you can wipe out whatever is in there and create a new list of the 
random selections.  The category selection here is the same as under the category
section.

=item Search

This enables full text searching on Artist name, Album name, or Song title.  Just
pick what you'd like to search on, then type what you're searching for in the 
text box, and submit it.  It uses a full pattern matching system so the less you
type the more you'll get back.  And typing nothing at all returns everything which
currently is the only way to view everything in your collection.  This search is not 
case sensitive so "bach" will match "Johann Bach".  "Ann" will match "Johann Bach" 
as well as "Anne Murray".

=item Edit

This is to edit your custom categories.  Just choose a custom category (If you
haven't created any, nothing will be in the drop down).  This will show all of the 
albums in custom list and allow you to remove the unwanted albums.  Take note, you
can't play anything from this screen, just edit.

=back

Also at the top is a list of what's currently playing, the Artist and the Song title,
and the Album it's from.  It will try to deduce if the player is stopped, paused or
playing and the current position in the track.

=item Side Bar

The Side bar, contains the really good stuff. At all times the controls for
the player on the server will be there (even if you aren't allowed to control
it).

=over 4 

=item Volume

The first thing is the current volume in a percent range from 0 to 100.
The picture below it will display it rounded to the nearest multiple of 10.
You can click anywhere on that image to set the volume.  The smaller end of 
the slope represents quieter and the bigger end represents louder.  So by 
clicking on the very right, you'll set the volume around 100%, clicking
on the very left, you'll set the volume down around 0%.

=item Controls

The next box is the player controls, these should be pretty self-explanatory
and I won't talk much about them.  But I couldn't find a way to poll xmms for
the current random setting or repeat setting.  So you'll have to guess.

=item Actions

The text links below the controls are the actions you can take.  On the bottom
of every page will be the "Current Playlist" option, which will show you the 
contents of the current playlist.  You should always have this option even if
you're already on the current playlist.  Also, if you're coming from a host
not allowed to control the player or to stream, this will be the only option.
The other actions may or may not show up depending on what kind of page your 
viewing.  A common action is "Invert Selection", which will take any checkboxes
you have selected and invert them to not selected and vice versa for boxes not
selected.  The others should be self explanatory, such as "Stream Selections" 
will stream the selected tracks. etc etc.

=back

=item The Main Window

There isn't too much to explain here.  This is where you'll see the tracks
in the current playlist, or the results of a search, or the results of 
selecting a category, or editting a custom category.  But there are a few
things I'd like to point out, In the "Current Playlist" you can click on a 
track to start playing that immediately.  In other types of listings you
can click on the album title to popup a box showing the tracks on the album.

NOTE: In some versions you'll see pencil by the album name, that is currently an
unimplemented edit feature to edit the labels on the album and tracks.

=head2 WRAP UP

I won't lie, the setup isn't easy.  But I've tried to make everything run 
as smooth as possible, but only to a point.  ProMP3 is on it's third or 
fourth major rewrite and I want to finally get it out the door and to the public.

Contact me with any questions or concerns.

=head2 AUTHOR

Jay Jacobs
[EMAIL PROTECTED]

=head2 CONTRIBUTORS

ProMP3 is about 99% my own code.  There may be a few lines from Apache::MP3 by Lincoln 
Stein in there.  The very first version of ProMP3 was called "Apache::MP3Playlist" and
was based on a pre 1.0 version of Apache::MP3 by Lincoln Stein.  I ended up taking his 
advice ("become a contributing Netizen!") and evolved my orginal "Apache::MP3Playlist"
into the ProMP3 you have now.  But I would like to thank Lachlan Dunlop and my 
coworkers 
at LachNet Inc. for putting up with my "does that work nice for you?" and "does that 
make sense?" and the ever present "Hey, try this out."

http://www.lach.net

=head2 SEE ALSO

perl, mod_perl, and that kind of stuff.

=cut

Attachment: signature.asc
Description: This is a digitally signed message part

Reply via email to