#!/usr/bin/perl -w # # 1999-08-21 -- tkil@scrye.com # fixed handling of track names, including some of the weird # workman features. # # 1999-08-02 -- tkil@scrye.com # oops, need to protect \' inside of URLs. sigh. use strict; use CGI qw(-no_debug); use CGI::Carp qw(fatalsToBrowser); use URI::URL; use HTML::Entities; use DBI; my $home = 'http://slinky.scrye.com/~tkil'; # ====================================================================== # common init my $q = new CGI; my $dbh = DBI->connect("DBI:mysql:hostname=localhost:database=Tkil_CDs", "tkil_ro", "") or die DBI->errstr; my $albums_per_page = 100; # ====================================================================== # utility functions sub make_url { my ($mode, %params) = @_; my $u = URI::URL->new($q->url(relative => 1)); my @query_bits; while (my ($param, $val) = each %params) { push @query_bits, "$param=$val"; } my $query = join "&", "mode=$mode", @query_bits; $u->query($query); # ouch, i use single quotes everywhere, so... my $rv = $u->as_string(); $rv =~ s/\'/%27/g; return $rv; } sub mm_ss { my $seconds = shift; return sprintf "%d:%02d", int($seconds/60), $seconds % 60; } sub make_album_phrase { my ($album, $album_id) = @_; my $url = make_url("album", album => $album, album_id => $album_id); $album = encode_entities($album); # and insert line breaks where appropriate $album =~ s!//!
\n    !g; return "$album"; } sub make_year_phrase { my $year = shift; return "$year" ; } sub make_format_phrase { my ($format, $format_id) = @_; my $url = make_url("all_cds", format => $format, format_id => $format_id); return "" . encode_entities($format) . "" ; } sub make_artist_phrase { my ($artist, $artist_id) = @_; my $url = make_url("artist", artist => $artist, artist_id => $artist_id); my $a = encode_entities($artist); $a =~ s!//!
\n    !g; return "$a" ; } sub make_label_phrase { my ($label, $label_id) = @_; my $url = make_url("all_cds", label => $label, label_id => $label_id); return "" . encode_entities($label) . "" ; } sub print_album_row { my ($artist, $artist_id, $album_aref, $album_id_aref, $year_aref, $len_aref) = @_; # ========== make artist a link to by-artist lists ========== my $artist_phrase = make_artist_phrase($artist, $artist_id); # do same for albums my @albums = map { make_album_phrase($album_aref->[$_], $album_id_aref->[$_]) } 0 .. $#$album_aref; # and for years my @years = map make_year_phrase($_), @$year_aref; # lengths are just simply formatted my @lengths = map mm_ss($_), @$len_aref; # ========== emit HTML ========== # and handle the first row specially, since we already started # it with the artist name. print "\n", " $artist_phrase\n", " ", shift(@albums), "\n", " ", shift(@years), "\n", " ", shift(@lengths), "\n", "\n"; # then do the rest of the rows. while (@albums) { print "\n", " ", shift(@albums), "\n", " ", shift(@years), "\n", " ", shift(@lengths), "\n", "\n"; } } { my ($all_by_artist, $all_by_album, $all_by_year, $all_by_length) = map { make_url("all_cds", sort => $_) } qw(artist album year length); my $sth; # ========== get (first-letter) indexes ========== $sth = $dbh->prepare("select substring(sort_key, 1, 1) as init, count(*) " . "from Artist group by init") or die $dbh->errstr; $sth->execute or die $sth->errstr; my %cds_by_init; while (my $cur = $sth->fetch) { my ($init, $count) = @$cur; $init = uc $init; $cds_by_init{$init} = $count; } $sth->finish; my @index_span = ( [ '0' .. '9' ], [ 'A' .. 'J' ], [ 'K' .. 'T' ], [ 'U' .. 'Z' ] ); my @indexes; foreach my $span (@index_span) { my @this_row = map { if (exists $cds_by_init{$_}) { $_ = ("$_ (" . $cds_by_init{$_} . ")"); } "\t $_\n"; } @$span; push @indexes, join "", @this_row; } my $index_bar = " " . join("\t\n\t\n", @indexes) . "\n"; my $n_index_rows = @indexes; # ========== get by-year list ========== $sth = $dbh->prepare("select year, count(*) from Album group by year") or die $dbh->errstr; $sth->execute or die $sth->errstr; my %cds_from_year; while (my $cur = $sth->fetch) { my ($year, $count) = @$cur; $cds_from_year{$year} = $count; } $sth->finish; my ($min_year, $max_year) = (sort { $a <=> $b } grep { $_ != 0 } keys %cds_from_year )[0, -1]; my ($min_decade, $max_decade) = map { int($_ / 10) } $min_year, $max_year; my $n_decades = 0; my @years; for my $decade ($min_decade .. $max_decade) { # and find the "real" start of this decade. my $real_decade = 10*$decade; my @this_decade = map { if (exists $cds_from_year{$_}) { $_ = ("$_ (" . $cds_from_year{$_} . ")"); } "\t $_\n" } $real_decade .. $real_decade+9; push @years, join "", @this_decade; ++ $n_decades; } my $year_bar = join "\t\n\t", @years; # ============================== compose navigation bar my $nav_bar = < All CDs by: Artist | Album | Year | Length Groups: $index_bar Albums from: $year_bar About: Tkil | his perl examples | this CGI (and its development version) | DDL & Support Files HTML sub print_header { my $title = shift; $title = encode_entities($title); print $q->header; # argh, stupid netscape table parsing is BROKEN BROKEN BROKEN! print < Tkil CDs: $title $nav_bar
HTML } sub print_footer { print < $nav_bar HTML } } sub die_politely { my $msg = shift; print_header("Sorry..."); print <Sorry... $msg; HTML print_footer("Sorry..."); } # ====================================================================== # browse all CDs if (!$q->param('mode') || $q->param('mode') eq "all_cds") { my $do_pages = 1; my %dbs_to_use = (Artist => 1, Album => 1); my $sort_clause; my $sort_title; my $sort_mode = $q->param('sort'); if (!$sort_mode || $sort_mode eq 'artist') { $sort_clause = "ORDER BY Artist.sort_key, Album.year, Album.name"; $sort_title = "All CDs by Artist"; } elsif ($sort_mode eq 'year') { $sort_clause = "ORDER BY Album.year, Artist.sort_key, Album.name"; $sort_title = "All CDs by Year"; } elsif ($sort_mode eq 'album') { $sort_clause = "ORDER BY Album.name, Artist.sort_key, Album.year"; $sort_title = "All CDs by Album"; } elsif ($sort_mode eq 'length') { $sort_clause = "ORDER BY Album.length, Artist.sort_key, Album.year, Album.name"; $sort_title = "All CDs by Length"; } print_header("$sort_title"); # ========== get only a certain subset of artists? ========== my $where_clause = "WHERE Artist.id = Album.artist_id"; if (my $pat = $q->param('pattern')) { $sort_title = "Artists matching '$pat'"; # translate from glob to SQL glob. for ($pat) { s/\_/\\_/g; s/\%/\\%/g; s/\*/\%/g; s/\?/\_/g; } $where_clause .= " AND Artist.sort_key LIKE " . $dbh->quote($pat); $do_pages = 0; } # ========== get only a certain year? ========== if (my $year = $q->param('year')) { $sort_title = "Albums from $year"; $where_clause .= " AND Album.year = $year"; $dbs_to_use{Album} = 1; $do_pages = 0; } # ========== get only a certain label? ========== if ($q->param('label_id') || $q->param('label')) { my ($real_label_name, $real_label_id); if (my $param_label_id = $q->param('label_id')) { # get the "real" name out of the db. my $sth = $dbh->prepare ("SELECT name, id from Label where id=$param_label_id") or die $dbh->errstr; $sth->execute or die $sth->errstr; my $cur = $sth->fetch or die_politely("couldn't determine label name for label_id = $param_label_id"); ($real_label_name, $real_label_id) = @$cur; $sth->finish; } elsif (my $param_label_name = $q->param('label')) { # get the label id out of the db. my $sth = $dbh->prepare ("SELECT name, id from Label where name=" . $dbh->quote($param_label_id)) or die $dbh->errstr; $sth->execute or die $sth->errstr; my $cur = $sth->fetch or die_politely("couldn't determine label id for label_name = $param_label_name"); ($real_label_name, $real_label_id) = @$cur; $sth->finish; } $sort_title = "Albums from label '$real_label_name'"; @dbs_to_use{qw(AlbumLabel Album)} = (1, 1); $where_clause .= (" AND AlbumLabel.label_id = $real_label_id" . " AND AlbumLabel.album_id = Album.id"); $do_pages = 0; } my $page_bar = ""; my $limit_clause = ""; goto SKIP_DO_PAGES unless $do_pages; my $page = $q->param('page') || 0; my $num_cds; { my $sth_count_cds = $dbh->prepare("select count(*) from Album") or die $dbh->errstr; $sth_count_cds->execute() or die $sth_count_cds->errstr; $num_cds = $sth_count_cds->fetch->[0]; $sth_count_cds->finish(); } my $max_page = $num_cds/$albums_per_page - 1; if ($max_page > int($max_page)) { $max_page += 1; } $max_page = int $max_page; $max_page = 0 if $max_page < 0; my @pages; if ($page > 0) { $q->param('page', 0); push @pages, "<< First"; } if ($page > 1) { $q->param('page', $page-1); push @pages, "< Prev"; } for (0 .. $max_page) { $q->param('page', $_); push @pages, "" . ($_+1) . ""; } if ($page < $max_page-1) { $q->param('page', $page+1); push @pages, "Next >"; } if ($page < $max_page) { $q->param('page', $max_page); push @pages, "Last >>"; } # restore old value $q->param('page', $page); # only set this to something if there's something to display. if (@pages) { $page_bar = ( "

Page: \n " . join(" |\n ", @pages) . "

\n"); my $offset = $page*$albums_per_page; $limit_clause = ($offset ? "LIMIT $offset, $albums_per_page" : "LIMIT $albums_per_page"); $sort_title .= " [page " . ($page+1) . " of " . ($max_page+1) . "]"; } SKIP_DO_PAGES: print <$sort_title $page_bar HTML my $dbs_string = join ", ", keys %dbs_to_use; my $sth = $dbh->prepare(<errstr; select Artist.name, Artist.id, Album.name, Album.id, Album.year, Album.length from $dbs_string $where_clause $sort_clause $limit_clause SQL $sth->execute() or die $sth->errstr; my ($artist, $artist_id, $album, $album_id, $year, $length); $sth->bind_columns(undef, \$artist, \$artist_id, \$album, \$album_id, \$year, \$length); # some variables for grouping by artist, if practical. hm... i # should just use this logic for all of 'em, as it will occasionally # be useful even if we're sorting by album or year. my $last_artist; my $last_artist_id; my @album_accum; my @album_id_accum; my @year_accum; my @length_accum; while (my $cur = $sth->fetch) { # print STDERR qq[artist="$artist", album="$album", year=$year, length=$length\n]; if ($last_artist && $last_artist ne $artist) { print_album_row($last_artist, $last_artist_id, \@album_accum, \@album_id_accum, \@year_accum, \@length_accum); # empty out the accumulators. @album_accum = (); @album_id_accum = (); @year_accum = (); @length_accum = (); } $last_artist = $artist; $last_artist_id = $artist_id; push @album_accum, $album; push @album_id_accum, $album_id; push @year_accum, $year; push @length_accum, $length; } if (@album_accum) { print_album_row($last_artist, $last_artist_id, \@album_accum, \@album_id_accum, \@year_accum, \@length_accum); } print <
Artist Album Year Length
$page_bar HTML print_footer(); } # ====================================================================== # browse a given artist elsif ($q->param('mode') eq 'artist') { my $param_artist_id = $q->param('artist_id'); my $param_artist_name = $q->param('artist'); my $artist_spec; my $real_name; if ($param_artist_id) { $artist_spec = "Artist.id = $param_artist_id"; # get the "real" name out of the db. my $sth = $dbh->prepare ("SELECT name from Artist where id=$param_artist_id") or die $dbh->errstr; $sth->execute or die $sth->errstr; my $cur = $sth->fetch or die_politely("couldn't determine artist name for artist_id = $param_artist_id"); $real_name = $cur->[0]; $sth->finish; } elsif ($param_artist_name) { $artist_spec = "Artist.name = " . $dbh->quote($param_artist_name); $real_name = $param_artist_name; } else { die_politely("Mode artist requires either " . "artist_id or artist parameters."); } my $sth = $dbh->prepare(<execute or die $sth->errstr; my ($album, $album_id, $year, $length, $format, $format_id); $sth->bind_columns(undef, \$album, \$album_id, \$year, \$length, \$format, \$format_id); print_header("Albums by $real_name"); print <Albums by $real_name HTML while (my $cur = $sth->fetch) { my $album_phrase = make_album_phrase($album, $album_id); my $year_phrase = make_year_phrase($year); my $format_phrase = make_format_phrase($format, $format_id); print "\n", " \n", " \n", " \n", " \n", "\n"; } $sth->finish; print <
Album Year Length Format
$album_phrase$year_phrase", mm_ss($length), "$format_phrase
HTML print_footer(); } # ====================================================================== # browse a given CD. elsif ($q->param('mode') eq 'album') { my $album_spec; my $real_name; my $param_album_id = $q->param('album_id'); my $param_album_name = $q->param('album'); if ($param_album_id) { $album_spec = "Album.id = $param_album_id"; # get the "real" name out of the db. my $sth = $dbh->prepare ("SELECT Album.name from Album where id=$param_album_id") or die $dbh->errstr; $sth->execute or die $sth->errstr; my $cur = $sth->fetch or die_politely("couldn't determine album name for album_id = $param_album_id"); $real_name = $cur->[0]; $sth->finish; } elsif ($param_album_name) { my $album_sql_name = $dbh->quote($param_album_name); $album_spec = "Album.name = $album_sql_name"; $real_name = $param_album_name; } else { die_politely("Mode album requires either " . "album_id or artist parameters."); } # get info about all albums that might match my $sth_album = $dbh->prepare(<errstr; SELECT Artist.name, Artist.id, Album.id, Album.name, Album.year, Album.length, Formats.name, Formats.id FROM Artist, Album, Formats WHERE Artist.id = Album.artist_id AND Formats.id = Album.format_id AND $album_spec LIMIT 20 SQL $sth_album->execute or die $sth_album->errstr; my ($artist_name, $artist_id, $album_id, $album_name, $album_year, $album_length, $format_name, $format_id); $sth_album->bind_columns (undef, \$artist_name, \$artist_id, \$album_id, \$album_name, \$album_year, \$album_length, \$format_name, \$format_id) or die $sth_album->errstr; print_header("Album '$real_name'"); my $page = 0; while (my $album_info_aref = $sth_album->fetch) { print "
\n" if $page > 0; ++ $page; # ========== get labels ========== my $sth = $dbh->prepare(<errstr; SELECT Label.id, Label.name FROM AlbumLabel, Label WHERE AlbumLabel.album_id = $album_id AND AlbumLabel.label_id = Label.id SQL $sth->execute or die $sth->errstr; my ($label_id, $label_name); $sth->bind_columns(undef, \$label_id, \$label_name) or die $sth->errstr; my @labels; while (my $cur = $sth->fetch) { push @labels, make_label_phrase($label_name, $label_id); } my $labels = join " |\n", @labels; $sth->finish; my $html_album_name = encode_entities($album_name); my $format_phrase = make_format_phrase($format_name, $format_id); my $artist_phrase = make_artist_phrase($artist_name, $artist_id); my $year_phrase = make_year_phrase($album_year); print <$artist_phrase: $html_album_name ($year_phrase)

Labels: $labels

Format: $format_phrase

Tracks:

HTML # ========== get tracks ========== $sth = $dbh->prepare(<errstr; SELECT track_num, name, length FROM Track WHERE album_id = $album_id ORDER BY Track.track_num SQL $sth->execute or die $sth->errstr; my ($track_num, $track_name, $track_length); $sth->bind_columns(undef, \$track_num, \$track_name, \$track_length); print < Track TItle Length HTML my ($last_artist, $last_album, $last_author); while (my $cur = $sth->fetch) { # munge the workman format lines my @lines = split '//', $track_name; foreach (@lines) { if (/^\+\[(.*)\]$/) { my $author = $1 || $last_author || ""; $_ = "Writer: " . encode_entities($author) . ""; $last_author = $author; } elsif (/^\+/) { my $album = substr($_, 1) || $last_album || ""; $_ = "From: " . encode_entities($album) . "", $last_album = $album; } elsif (/^\@/) { my $artist = substr($_, 1) || $last_artist || ""; $_ = ( "By: " . encode_entities($artist) . "" ); $last_artist = $artist; } else { $_ = encode_entities($_); } } my $track_content = join "
", @lines; print "\n", " $track_num\n", " $track_content\n", " ", mm_ss($track_length), "\n", "\n"; } print < HTML } print_footer(); } # ====================================================================== # punt else { my $html_mode = encode_entities($q->param('mode')); die_politely(< This program does not understand given mode $html_mode. Please select another option from the navigation bar. Thanks!

If you got this message after clicking a link on one of the generated pages, please mail me to let me know. I appreciate it!

HTML } # ====================================================================== # common termination $dbh->disconnect();