#!/usr/bin/perl -Tw
#
#NAME
#  sess.cgi- ABC Musical Session tool
#
#SYNOPSIS
#  http://host.dom.ain:port/CGI/sess
#  http://host.dom.ain:port/CGI/sess/some/dir/
#
#DESCRIPTION
#  This is an experimental program to produce a useful listing of  ABC  files
#  in a "session" or "repertoire" directory and its subdirectories.  The idea
#  is to give the user a list of available tunes, and some useful information
#  about  them,  plus  links  to  conversions  to  various "sheet music" file
#  formats.
#
#  If called from a link that includes a PATH_INFO string  (the  stuff  after
#  this  script's  name, we use that as the directory to list.  Otherwise, we
#  look for the HTTP-REFERER string, which most browsers send, but not all.
#
#
#OPTIONS and other inputs
#
# Verbose levels, the variable $V:
#   $V=0 Only very fatal error messages
#   $V=1 Brief info about what's being done, warnings, etc.
#   $V=2 Basic debug info
#   $V=3 Even more debug info
#   ...
# V may be gotten as HTTP "parameters" to the URL, or may be calculated  from
# information  about  the  client.   So  far, we just look for a match to the
# $Vaddr variable in our cgilocal.pm file, and default to 1 otherwise.
#
#FILES
#
#BUGS
#
#SEE ALSO
#
#AUTHOR John Chambers <jc@trillian.mit.edu>
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #

$| = 1;
$B = 0;					# Border width for a few containers
$V = 1;					# Verbose level, >1 for debugging
($P = $0) =~ s"^.*/"";	# The name we were called with
#use strict;			# Nah; don't do this ;-)
our $Vtest = 2;			# Verbose level for "special" IP address
our $Btest = 0;			# Border widths for testing
$gethostname = 1;		# We want our hostname in some messages
$ENV{PATH} = '/bin:/sbin:/usr/bin:/usr/sbin';	# Fixed path for safety
push @INC, '.', 'sh';	# Where to look for our .pm files
@msgs = ();				# Message list during initialization

require ($cgifil = &LocalSetup(2));
$V = &max($Vtest, $V, 1) if ($RA =~ /$Vaddr/);
$B = &max($Btest, $B, 1) if ($RA =~ /$Vaddr/);
print STDERR "$P: Called with V=$V Vtest=$Vtest Vaddr=$Vaddr B=$B.\n" if $V>0;

$centertables = 0;	# Whether to center each letter's table of tunes
$imgcol   = 'img';	# Name of "image" column
$lcsuffix   = 1;	# Show suffixes in lower case (for small screens)?
#minyear    = 1400;	# Earliest "year" that we'll believe.
#maxyear    = 2100;	# Latest "year" that we'll believe. (Pleae adjust as needed.)
$seldir     = '_';	# Files are selected by being linked into this directory
%selfiles   = ();	# set of files "selected" for empasis (bold for now)
$showDate   = 1;	# Should we show date info from the files?
$showDir    = 0;	# Should we show (sub)directories in listing?
$showImg    = 1;	# Should we show image files?
$showInfo   = 1;	# Should we show the info field?
#showi      = 1;	# Should we show random info?
#ShowDate   = 1;	# Should we read data from the files?
$showimg    = 1;	# Should we show source files?
$showst     = 1;	# Should we show the staff count?
$showw      = 1;	# Should we show the w: line count?
$showNames  = 0;	# Should we show the (file) names?
$showTitles = 0;	# Should we show (sub)titles? [0: Only the file name]
#Title      = '';	# Web page title, if any
$TitleSize  = "-1";	# Size adjustment for some titles (0=same -1=smaller)

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Global variables:

#cols     = 15;		# Number of columns in the tune tables
$exitstat = 0;		# Set to nonzero for a failure exit status
@Fields   = ( 		# Fields to display in tune list
	'get','gif','png','pdf','svg','midi',$imgcol,'Class','K','B','st','w','Name','Titles','date','info'
);
$fields = int(@Fields);	# How many fields (columns) we show
@msgs     = ();		# Line bufer for output to client
$HDRsent  = 0;		# Whether we've sent a HEADER file
$Lopen    = 0;		# Is the logfile open?
$pid      = $$;		# Our process ID number
#RisClass = 1;		# Interpret the R: header as a general classification
$split    = 1;		# Should we split the table (based on first letter)?
$tblChr   = '';		# First char of last-seen file name
$tblMin   = 0;		# Start new table if current table > this number
$tblCnt   = 11;		# Length of current table

# Some initially-undefined variables:
#my($e, @names, $query, $v, @vals);
#my($botname, $hms, $msg, $ymd);
$botpat   = 		# Pattern to recognize bots in UA
		'[-\w]*Google\w*|msnbot|BecomeBot|bingbot|Yahoo[-.\s\w]*|scirus-crawler';
$UA = '?.?.?.?';	# Is this still used anywhere?

# Scale factors for ABC formatting:
$S_GIF = '0.65';
$S_PNG = '0.65';
$S_PS  = '0.65';
$S_PDF = '0.65';
$S_SVG = '0.65';
$S_MIDI= '0.65';	# Probably not used
#S_IMG = '0.65';	# Is this ever used?
#S_SRC = '0.65';	# Is this ever used?

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Assorted globals:

$doctitle  = '';	# What's the best way to title the results?
$homedir   = '?';	# Our home directory, if known.
$infofile  = '?';	# The .info file, if it exists
$sesdir    = '?';	# The session's directory, relative to .../music/
$titlefile = '?';	# Where we found the directory's title
$uriargs   = '';	# No known args (yet)

$hostname = `/bin/hostname`;	# What does this machine call itself?
$hostname =~ s/^\s*([-_.\w]*)([\r\s]*)$/$1/;	# Strip off white stuff
($host = $1) =~ s/\..*//;		# Strip off domain stuff if present

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
use CGI;
use CGI::Carp 'fatalsToBrowser';
use diagnostics;

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
$fqdn = $ENV{'HTTP_HOST'} || $ENV{'SERVER_NAME'} || 'UNKNOWN';	# Client's name for us
$sess = '?';
$baseuri = $ENV{'REQUEST_URI'} || '';
if ($baseuri =~ m"^(.*)\?(.*)$") {	# Are args included in URI?
	$baseuri = $1;		# The "real" base URI
	$uriargs = $2;		# Separate off the args
	if ($uriargs =~ /\<V=(\d+)/) {	# Is there a verbose option included?
		$V = int($1);	# Use it.
	}
}
if ($baseuri =~ m"https*://([-:._\w]*).*([^/]*)/$") {
	$fqdn = $1;		# Use it as the hostname 
	$sess = $2;		# Last field in URL
} elsif ($baseuri =~ m"/([^/]*)/*$") {
	$sess = $1;
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
push @msgs, "Content-type: text/html\n\n";
push @msgs, "<html>";
push @msgs, "<head>";
push @msgs, "\t<title>$sess Tune List @ $fqdn</title>";
push @msgs, "\t<meta http-equiv='Content-Type' content='text/html;charset=utf-8' />";
push @msgs, "\t<meta name='robots' content='index,nofollow' />\n";
push @msgs, "\t<meta name=viewport content='width=device-width, initial-scale=1' />";
$B = 0 if $V<3;		# Sometimes we want to draw "debug" borders.
# Do we need to define a "style" for the body tag?
#ush @msgs, "\t<script src="abcjs_basic_2.0-min.js" type="text/javascript"></script>\n";
#ush @msgs, "<style type='text/css'>";
#ush @msgs, "\tbody.sess {border:${B}px dotted green; margin:0px; padding:0px; text-align:left; width:'100%'}";
#ush @msgs, "</style>";
push @msgs, "</head>";
#ush @msgs, "<body class=sess>";
push @msgs, "<body style=\"border:${B}px dotted green; margin:0px; padding:0px; text-align:left; width:'100%'\">";
push @msgs, "<center> $0  B=$B V=$V Vtest=$Vtest Vaddr=$Vaddr \"$cgiloc\"</center>\n" if $V>2;
push @msgs, "<center>[JC's experimental session-collection app]</center>" if $V>2;

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Local initialization: 

push @msgs, "\@INC: {" . join(' ',@INC) . "}<br>\n" if $V>3;
push @msgs, "REMOTE_ADDR: \"$REMOTE_ADDR\"<br>" if $V>2;
push @msgs, "$P $pid <$RA V=$V Vtest=$Vtest B=$B.<br>" if $V>2;
push @msgs, "cgiloc: \"$cgiloc\"<br>" if $V>2;
if ($V > 0) {		# Little info box at top of page:
	push @msgs, "<center><table border=2 style=\"border:2px blue\"><tr><td align=center><small>";
	push @msgs, "\t$cymd $hms $pid $P on $host from $RA V=$V Vtest=$Vtest B=$B<br>";
	push @msgs, "</small></td></tr></table></center>";
}
push @msgs, "cgifil: \"$cgifil\"<br>" if $V>2;
push @msgs, "Vaddr: \"$Vaddr\"<br>" if $V>2;
#ush @msgs, "usrurl: \"$usrurl\"<br>" if $V>2;
push @msgs, "homedir: \"$homedir\"<br>" if $V>2;
#ush @msgs, "webdir: \"$webdir\"<br>" if $V>2;
push @msgs, "cgiurl: \"$cgiurl\"<br>" if $V>2;
push @msgs, "musdir: \"$musdir\"<br>" if $V>2;
push @msgs, "musurl: \"$musurl\"<br>" if $V>2;
#ush @msgs, "abcdir: \"$abcdir\"<br>" if $V>2;
#ush @msgs, "abcurl: \"$abcurl\"<br>" if $V>2;
#ush @msgs, "docdir: \"$docdir\"<br>" if $V>2;
push @msgs, "now: $now<.br>" if $V>2;
push @msgs, "0: \"$0\"<br>" if $V>3;
push @msgs, "P: \"$P\"<br>" if $V>2;
push @msgs, "P4: \"$P4\"<br>" if $V>4;
push @msgs, "Host: \"$fqdn\"<br>" if $V>2;

&sendmsgs();	# Send all the accumulated lines to the client

$query = new CGI;		# Get the form info for the call, if any
&chkclient() if $V>0;	# Do we answer this client?

if ($Varg = $query->param('V')) {	# Verbose level in URL overrides default
	if ($Varg =~ /(\d+)/) {
		push @msgs, "$P: V=$1 [from client]\n" if $V>1;
		$V = int($1);
	}
}

&dumpform()  if $V>2;
&dumpenv()   if $V>2;

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Read in our host-specific stuff:

require   "sendsubs.pm";	# Routines to send messages
require   "abc2utf8.pm";	# Routines to deal with UTF-8 encoding
require    "HTMLenc.pm";	# Routines to deal with HTML encoding
require "fieldTable.pm";	# Routines for the file table 
require    "webpage.pm";	# Routines for building web pages

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Set up our logfile.

push @msgs, "$P: Open logfile ...<br>\n" if $V>2;
$logfile = "$tmpdir/Sess-$pid.log";
$altfile = "$tmpdir/lastSess.log";
push @msgs, "logfile: \"$logfile\"<br>\n" if $V>2;
if (!open(L,">>$logfile")) {
	push @msgs, "<b>Can't write \"$logfile\" ($!)\n" if $V>1;
	&quit(1);
}
$Lopen = 1;		# Logfile is now open
push @msgs, "$P: Logfile: \"$logfile\"<br>\n" if $V>2;
select L; $| = 1; select STDOUT;
unlink($altfile) if -f $altfile;
link($logfile,$altfile);
&sendLog("$0: pid=$pid RA=\"$RA\" UA=\"$UA\" V=$V Vtest=$Vtest B=$B \"$cgiloc\"\n") if $V>0;

&sendmsgs();	# Send all the accumulated lines to the client

&showCWD("L" . __LINE__) if $V>1;

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# What info can we get out of the PATH_INFO environment variable?

if ($pathinfo = $ENV{'PATH_INFO'}) {	# The directory can be after the CGI's name, relative to .../music/
	&send_LW("pathinfo: \"$pathinfo\" from PATH_INFO") if $V>2;
	if ($pathinfo =~ m"^.*/music/+([-_/\w\x81-\xFF]+)/*$") {	# Del stuff before /music/
		$sesdir = $1;
		&send_LW("sesdir: \"$sesdir\" from PATH_INFO") if $V>2;
	} elsif ($pathinfo =~ m"^/*([-_/\w\x81-\xFF]+)/*$") {	
		$sesdir = $1;
		&send_LW("sesdir: \"$sesdir\" from PATH_INFO") if $V>2;
	} else {
		print "$P: PATHINFO \"$pathinfo\" not recognized.<br>\n" if $V>0;
		quit(3,"Bar PATHINFO \"$pathinfo\"");
	}
} elsif ($ref = $ENV{'HTTP_REFERER'}) {		# Standard, but iPad's Safari doesn't send it
	&send_LW("$P: ref=\"$ref\" from HTTP_REFERER") if $V>1;
	unless ($ref =~ m"^(.*)/music/(.*)$") {
		print "$P: No /music/ in HTTP_REFERER string \"$ref\"<br>\n" if $V>
		quit(3,"No /music/ in HTTP_REFERER string");
	}
	($sesdir = $2) =~ s"/+$"";
	&send_LW("sesdir: \"$sesdir\"") if $V>2;
} elsif ($ref = $ENV{'HTTP_REFERRER'}) {	# Some clients do this
	print "ref: \"$ref\" from HTTP_REFERRER<br>\n" if $V>2;
} else {
	&send_LW("### No PATH_INFO or HTTP_REFERER value ###") if $V>0;
	&quit(1,"No PATH_INFO or HTTP_REFERER");
}
&send_LW("sesdir: \"$sesdir\"") if $V>2;
# Figure out our working directory:
if (-d ($path = "$musdir/$sesdir")) {
	&send_LW("$P: path=\"$path\" [musdir]") if $V>2;
} elsif (-d ($path = "$webdir/$sesdir")) {
	&send_LW("$P: path=\"$path\" [webdir]") if $V>2;
} else {
	&send_LW("$P: No path hint found.") if $V>0;
	$path = $webdir;	# Default
}
$path =~ s"/+$"";	# Remove any final / from path.
&send_LW("$P: path=\"$path\"") if $V>2;
if (-f ($titlefile = "$path/.title.html") && open(TITLE,$titlefile)) {	# We send all of .title.html
	&send_LW("titlefile=\"$titlefile\"\n") if $V>2;
	for $line (<TITLE>) {
		print $line;
	}
} elsif (-f ($titlefile = "$path/.title") && open(TITLE,$titlefile)) {	# We send only the title line
	&send_LW("titlefile: \"$titlefile\"") if $V>2;
	for $line (<TITLE>) {
		$line =~ s/[\r\s]+$//;
		next unless $line;
		print "line: \"$line\"<br>\n" if $V>2;
		if ($line =~ /^\s*#/) {	# Ignore comments
			print "Ignore \"$line\"<br>\n" if $V>2;
		} else {
			$line =~ s/^(T|Title):\s*//;	# Strip off initial title-line attribute
			$doctitle = $line;		# Take the first non-comment line as the title
			print "doctitle: \"$doctitle\"<br>\n" if $V>2;
			close TITLE;			# Ignore anything after that
		}
	}
} else {
	&send_LW("No title file found.") if $V>2;
	$doctitle = '';
}
if ($doctitle) {
	&sendLog("doctitle: \"$doctitle\" after search.<br>\n") if $V>2;
	print "<center><b><strong>$doctitle</strong></b></center>\n" if $doctitle;
}
unless (-d $path) {
	&send_LW("$P: \"$path\" is not a directory.") if $V>0;
	&quit(2,"Directory $path/ does not exist here");
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# 
$specials = $query->param('_') || '_';		# Show separate _/ directory list
$show     = $query->param('show') || 'all';	# Which of the Name/Title columns to show
$split    = $query->param('split') || 1;	# Should we split the table (based on first letter)?
&send_LW("$P: show='$show' specials='$specials' split=$split\n") if $V>2;
if ($show =~ /^date*/i) {	# The date column is the composition/publication date
	$showDate = 1;
	&send_LW("$P: Show date.\n") if $V>2;
} if ($show =~ /^Img*/i) {	# Some collections have graphical images of various types
	$showImg = 1;
	&send_LW("$P: Show Images.\n") if $V>2;
} if ($show =~ /^info*/i) {	# Display (compact) info about the tune
	$showInfo = 1;
	&send_LW("$P: Show info.\n") if $V>2;
} elsif ($show =~ /^names*/i) {	# Display (trimmed) file names
	$showNames = 1;
	&send_LW("$P: Show names.\n") if $V>2;
} elsif ($show =~ /^titles*/i) {	# Display tune titles
	$showTitles = 1;
	&send_LW("$P: Show titles.\n") if $V>2;
}
unless ($showNames || $showTitles) {
	$showNames = $showTitles = 1;
	&sendAll("$P: Show Names and Titles.\n") if $V>2;
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# If there's a .info file, we read it and save the info in global variables:

&send_LW("$P: readInfo(path=\"$path\") ...") if $V>2;
&readInfo($path);	# Read the .info file if there is one
&send_LW("$P: readInfo(path=\"$path\") done.") if $V>2;
&sendmsgs();		# (readInfo leaves its comments in @msgs)
print "\t<center><bigger><b>" . $Info{Title} . "</bigger></b></center>\n" if $Info{Title};

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Read the file names in the current directory and search for special files:

&sendLog("$P: List path=\"$path\"\n") if $V>1;
if (-f ($file = "$path/HEADER.sess.html")) {	# Is there a special HEADER file for us?
	&sendLog("Header file \"$file\" spotted.<br>\n") if $V>0;
	&webpage("$file");
	$HDRsent ++;
}
@files = `ls $path`;
file:
for $file (@files) {
	$file =~ s/[\r\s]+$//;
	if ($file =~ 'HEADER*') {
		$HIDE{$file} = 1;	# Don't show it in the list
		next file if $HDRsent;	# Only show the first HEADER* that we noticed
		&sendLog("Header file \"$file\" spotted (HIDE=" . $HIDE{$file} . ")<br>\n") if $V>0;
		next file if $HDRsent;
		&webpage("$path/$file");
		$HDRsent ++;
	}
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Get data from the desired directory:

&htmllist($path);	# Find all the tune files under the directory
print "$P: htmllist done.<br>\n" if $V>2;

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
print "Exit with status $exitstat.<br>\n" if $V>1;
print "<hr>\n";
print "</body class=sess>\n";
print "</html>\n";
exit $exitstat;

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

sub chkclient {
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Check for known bots, and reject their request if they've asked for a  tune #
# match.   But we don't discriminate against them here; we just announce that #
# we think they're a bot.                                                     #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	if ($x = $query->param('HTTP_USER_AGENT')) {
		$UA = $x;		# Note that this sets the global $UA
		if ($UA =~ /$botpat/i) {
			$botname = $1;
			my($ss,$mm,$hh,$DD,$MM,$CY) = gmtime(time);
			$CY += 1900;
			$ymd = sprintf("%04d%02d%02d",$CY,1+$MM,$DD);	# CCYY-MM-DD
			$hms = sprintf("%02d%02d%02d",$hh,$mm,$ss);	# HH:MM:SS
			$msg = "[$ymd $hms] $$ $0: Request from \"$botname\" bot.";
			print STDERR "$msg\n";	# Tell the errlog about it
			print        "$msg\n";	# Tell the client about it
		}
	}
}

sub dumpenv {
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	print "<hr>Here are our environment variables:\n";
	print "<dl>\n";
	for $e (sort keys %ENV) {
		$v = $ENV{$e};
		print "<dt>$e<dd>$v\n";
	}
	print "</dl>\n";
	print "<hr>\n";
}

sub dumpform {
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	local($n);
	print "<hr>Here are our form elements:\n";
	@names = $query->param();
	print "<dl compact>\n";
	for $n (sort @names) {
		if (@vals = $query->param($n)) {
			for $v (@vals) {print "<dt>$n<dd>\"$v\"\n"}
		} elsif ($v = $query->param($n)) {
			print "<dt>$n<dd>\"$v\"\n";
		} else {
			print "<dt>$n<dd>(NO VALUE)\n";
		}
	}
	print "</dl>\n";
	print "<hr>\n";
}

sub getDir {
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Figure out what directory (relative to $webdir) that we're to list.  If the
# PATH_INFO  string  exists, we'll use it, so the user can name the directory
# in the URL.  Otherwise, we look for the HTTP_REFERER string, which browsers
# should  send us.  This way, an HTML file can just link to this program, and
# the "current" directory will be listed.  Don't call this before we've sent
# the HTTP and HTML header stuff.
#
	($PI = $ENV{PATH_INFO} || '') =~ s"^/+"";
	&esend("PI=\"$PI\"") if $V>3;
	if ($PI) {
		&esend("Using PATH_INFO \"$PI\"") if $V>3;
		&esend("PI=\"$PI\"") if $V>3;
	} elsif (($PI = $ENV{'HTTP_REFERER'}) && ($PI =~ m"/$")) {
		&esend("Using HTTP_REFERER \"$PI\"") if $V>3;
		$PI =~ s"^http://([\w.:]+)/"";
		$HP = $1;
		&esend("PI=\"$PI\" HP=\"$HP\"") if $V>3;
		$PI =~ s"^~\w+"";		# Strip out user id
		&esend("PI=\"$PI\"") if $V>3;
		($vhost = $HP) =~ s/:.*$//;
		&esend("PI=\"$PI\" vhost=\"$vhost\"") if $V>3;
	} elsif ($PI = $data{D} || $data{DIR} || $data{DIR1} || $data{DIR2}) {
		&esend("Using DIR \"$PI\"") if $V>3;
		$PI =~ s"^http://[\w.:]+/"";
		&esend("PI=\"$PI\"") if $V>3;
		$PI =~ s"^~\w+"";		# Strip out user id
		&esend("PI=\"$PI\"") if $V>3;
	} else {
		$PI = '';
	}
	if ($PI =~ m"^/*(.*)/*$") {
		$pathinfo = "$1";	# Untaint the path info
	} 
	&lsend("pathinfo=\"$pathinfo\"\n") if $V>3;
}

sub linkdir {my $F='linkdir'; $url = shift;
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Given the URL of a directory, we return a hyperlink that returns it  as  an #
# object of type dir.                                                         #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	return "<a href=\"$url/\">dir</a>";
}

sub linkABC {my $F='linkABC'; $url = shift;
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Given the URL of a file, we return a hyperlink  that  returns  it  as  an #
# object of type ABC.                                                         #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	return "<a href=\"$url\">ABC</a>";
}

sub linkSRC {my $F='linkSRC'; local($file) = shift;
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Given the URL of an image file, we return a hyperlink that returns it in an #
# <img> tag.                                                                  #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	local($cmd,$line,$name,$rest,$suff,$val,$url);
	local($val) = '';	# Default return value if no image found.
	&sendLog("$F: file=\"$file\"\n") if $V>0;
	if (($name,$rest) = ($file =~ m/^([_\w]+)(.*)$/)) {
		&sendLog("$F: name=\"$name\"\n") if $V>0;
		&sendLog("$F: rest=\"$rest\"\n") if $V>0;
		$cmd = "/bin/ls $path/$imgcol/$name.*";
		&sendLog("$F: cmd=\"$cmd\"\n") if $V>0;
		unless (open LIST, "$cmd 2>&1 |") {
			print STDERR "$F: Can't find any .abc files here.\n" if $V>0;
			return $val;
		}
		for $line (<LIST>) {
			$line =~ s/[\s\r]*$//;
			&sendLog("$F: line=\"$line\"\n") if $V>0;
			if ($line =~ m".*/$name.(\w+)$") {
				$suff = $1;
				$url = "$musurl/${sesdir}/$imgcol/$name.$suff";
				&sendLog("$F: url=\"$url\"\n") if $V>0;
				$suff = lc($suff) if $lcsuffix;	# Show suffix in lower case?
				return "<a href=\"$url\">$suff</a>";
			}
		}
	} else {
		&sendLog("$F: Can't parse \"$file\"\n");
		$url = $val;
	}
	return $val;
}

sub linkTXT {my $F='linkTXT'; $url = shift;
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Given the URL of a file, we return a hyperlink  that  returns  it  as  an #
# object of type TXT.                                                         #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
#	return "<a href=\"$url\">TXT</a>";
	return "<a href=\"$cgiurl/get.cgi?F=TXT&U=$url\">abc</a>";
}

sub linkGIF {my $F='linkGIF'; $url = shift;
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Given the URL of a file, we return a hyperlink that returns it as an object #
# of type GIF.                                                                #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
#	return "<a href=\"$url\">GIF</a>";
	return "<a href=\"$cgiurl/get.cgi?F=GIF&S=$S_GIF&U=$url\">gif</a>";
}

sub linkPNG {my $F='linkPNG'; $url = shift;
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Given the URL of a file, we return a hyperlink that returns it as an object #
# of type PNG.                                                                #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
#	return "<a href=\"$url\">PNG</a>";
	return "<a href=\"$cgiurl/get.cgi?F=PNG&S=$S_PNG&U=$url\">png</a>";
}

sub linkPDF {my $F='linkPDF'; $url = shift;
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Given the URL of a file, we return a hyperlink that returns it as an object #
# of type PDF.                                                                #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
#	return "<a href=\"$url\">PDF</a>";
	return "<a href=\"$cgiurl/get.cgi?F=PDF&S=$S_PDF&U=$url\">pdf</a>";
}

sub linkPS {my $F='linkPS'; $url = shift;
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Given the URL of a file, we return a hyperlink that returns it as an object #
# of type PS.                                                                #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
#	return "<a href=\"$url\">PS</a>";
	return "<a href=\"$cgiurl/get.cgi?F=PS&S=$S_PS&U=$url\">ps</a>";
}

sub linkMIDI {my $F='linkMIDI'; $url = shift;
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Given the URL of a file, we return a hyperlink that returns it as an object #
# of type MIDI.                                                                #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
#	return "<a href=\"$url\">MIDI</a>";
	return "<a href=\"$cgiurl/get.cgi?F=MIDI&S=$S_MIDI&U=$url\">midi</a>";
}

sub linkSVG {my $F='linkSVG'; $url = shift;
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Given the URL of a file, we return a hyperlink that returns it as an object #
# of type SVG.                                                                #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
#	return "<a href=\"$url\">SVG</a>";
	return "<a href=\"$cgiurl/get.cgi?F=SVG&S=$S_SVG&U=$url\">svg</a>";
}

sub LocalSetup {my $F='LocalSetup'; $Vtest = @_;
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Figure out where we're running, and try to require a *cgilocal.pm file  for #
# local  settings.   The  end  result of this is to initialize a long list of #
# global variables.  The return value is the name of the cgilocal file, which #
# we usually feed to the 'require' command.                                   #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
#	$ENV{PATH} = '/bin:/sbin:/usr/bin:/usr/sbin';		# Fixed path for safety
	$fqdn = $ENV{'HTTP_HOST'} || $ENV{'SERVER_NAME'};	# Client's name for us
	$Vtest = 2 unless defined $Vtest;	# Verbose level for testing
	$V = max($Vtest, $V, 2);
	push @msgs, "$P/$F: V=$V Vtest=$Vtest.<br>\n" if $V>0;
	$homedir = '' unless defined $homedir;
	if ($0 =~ m"^(.*)/([^/]*)$") {	# Look in our process name for directory
		$Pdir = $1; push @msgs, "$F 1: Pdir='$Pdir'<br>\n" if $V>0;
		$P    = $2; push @msgs, "$F 1: P='$P'<br>\n" if $V>0;
	} elsif (-d $homedir) {
		$Pdir = $homedir;	# If no full pathname, use the homedir
		($P = $0) =~ s".*/"";
		$Pdir = $1; push @msgs, "$F 2: Pdir='$Pdir'<br>\n" if $V>0;
		$P    = $2; push @msgs, "$F 2: P='$P'<br>\n" if $V>0;
	} else {
		push @msgs, "$0: Can't figure out our home directory, using '.'.\n" if $V>0;
		$Pdir = '.';
		$homedir = '.';		# Maybe this will work ;-)
#		exit 1;
	}
	push @INC, $Pdir;
	push @msgs, "\@INC: {" . join(' ',@INC) . "}<br>\n" if $V>3;
	umask 0002;		# Output files must be group writable
	if (defined $Pdir && -d $Pdir) {chdir $Pdir}
	$| = 1;			# Unbuffered STDOUT
	$" = ',';		# Used in verbose messages
	&Vopt($ENV{"V_$P"},'1');
	push @msgs, "$P/$F: called with V=$V Pdir=$Pdir<br>\n" if $V>0;
	$P4 = ($P =~ /^(....)/) ? $1 : 'tune';
	$Venv = (defined($ENV{"V_$P"}) ? $ENV{"V_$P"} : '1') . ' 1';
	$Vsrc = "Venv='$Venv'";
	if ($Venv =~ /(\d+)/) {	# Verbose level
		$V = $1;
	}
	$Vtest = 1 unless defined $Vtest;
	if (($REMOTE_ADDR = $ENV{REMOTE_ADDR} || '?') =~ m/^\s*([\d.]+)\s*$/) {
		$RA = $1;
	} elsif ($REMOTE_ADDR eq '0.0.0.0' || $REMOTE_ADDR eq '::1') {
		$RA = '[localhost]';
		$V = $Vtest;
	} else {
		$RA = '[unknown]';
	}
	push @msgs, "$P/$F: RA=\"$RA\"<br>\n" if $V>0;
	$Vaddr = '0.0.0.0' unless defined $Vaddr;
	if ($RA eq $Vaddr) {				# My home machine?
		if ($V<$Vtest) {$V = $Vtest; $Vsrc = "Vtest=$Vtest"}
	} elsif ($RA =~ /^192\.168\./) {	# My home network?
		$V = $Vtest if $V<$Vtest;
		if ($V<$Vtest) {$V = $Vtest; $Vsrc = "Vtest=$Vtest"}
	}
	local($ss,$mm,$hh,$DD,$MM,$YY) = gmtime($now = time);	# Current date/time
	$cymd = sprintf("%d-%02d-%02d",1900+$YY,1+$MM,$DD);	# CCYY-MM-DD
	$hms = sprintf("%02d:%02d:%02d",$hh,$mm,$ss);		# HH:MM:SS
	if ($gethostname && !defined($host)) {
		$hostname = `/bin/hostname`;	# What does this machine call itself?
		$hostname =~ s/^\s*([-_.\w]*)([\r\s]*)$/$1/;		# Strip off white stuff
		($host = $1) =~ s/\..*//;		# Strip off domain stuff if present
		$hstloc = $host . "-cgilocal.pm";
	}
	$cgiloc = (-f $hstloc) ? $hstloc : 'cgilocal.pm';
	push @msgs, "$P/$F: host='$host' cgiloc='$cgiloc'\n" if $V>0;
	return $cgiloc;
}

sub max {my $F='max';
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	local($arg,$num,$val);
	for $arg (@_) {
		if ($arg =~ /(-*)(\d+).(\d*)/) {
			$num = $1 . $2 . '.' . $3;
		} elsif ($arg =~ /(-*\d*)/) {
			$num = int($1);
		} else {
			push @msgs, "$F: \"$arg\"<br>\n" if $V>0;
		}
		if (defined($val)) {$val = $num if $num > $val} else {$val = $num}
	}
	return $val;
}

sub quit {my $P='quit'; local($stat,$reason) = @_;
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Quit with an exit status and message.  The message is written to stdout, so #
# the  client will see it.  A nonzero exit status should produce a message in #
# the server's error log.                                                     #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	print "$P: Exit with status $stat [$reason]<b>\n" if $V>0 && $stat>0;
	exit $stat;
}

sub readInfo {my $F='readInfo'; local($dir) = @_;
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# We attempt to read a .info file, which may contain info that we  can  share #
# with  the  client.   We  may  put stuff into @msgs here, but the <HEAD> and #
# <BODY> tags may not have been sent yet,  so  we  shouldn't  print  anything #
# directly to the client.                                                     #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	local($l,$T,$f);
	push @msgs, "$F: Read $dir/.info file ...<br>" if $V>2;
	$f = "$dir/.info";
	if (open(INFO,$f)) {
		$infofile = $Info{infofile} = $f;
		push @msgs, "$F: infofile \"$infofile\"<br>" if $V>2;
line:	while ($l = <INFO>) {
			$l =~ s/[\r\s]$//;	# Trim away white stuff
			push @msgs, "$F: LINE \"$l\"<br>" if $V>2;
			next line if $l =~ /^\s*#/; # Ignore comments
			next unless $l;				# Ignore blank lines
			if ($l =~ /^(V):\s*(\d+)/) {	# Verbose level
				if ($V < $2) {	# Only use if higher than we already have
					$V = int($2) if $2 > $V;
					$Vsrc = ".info file";
				}
				push @msgs, "$F: V=$V.<br>" if $V>2;
			} elsif ($l =~ /^(Show):\s*(.*)/) {	# Directory title
				@Fields = split('[,\s]+',$2);
				$fields = int(@Fields);
				push @msgs, "Show $fields Fields: [" . join(',',@Fields) . "]<br>" if $V>2;
			} elsif ($l =~ /^(T|Title):\s*(.*)/) {			# Directory title
				$doctitle = $Info{Title} = $T = $2 || '';	# Web page title, may be null
				$titlefile = $f;
				push @msgs, "$F: T=\"$T\" from .info file.<br>" if $V>2;
			} elsif ($l =~ /^(Date):\s*(.*)/) {	# Directory title
				$Info{Date} = $DT = $2;
				push @msgs, "$F: DT=\"$DT\" from .info file.<br>" if $V>2;
			} else {
				push @msgs, "$F: Ignore \"$l\"<br>" if $V>2;
			}
		}
	} else {
		push @msgs,"$F: [no $dir/.info file]<br>" if $V>2;
	}
}

sub sendmsgs {
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Send the buffered-up lines in @msgs to the client, and empty the buffer.    #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	for $line (@msgs) {
		print "$line\n";
	}
	@msgs = ();		# Done with that batch
}

sub showCWD {my($F)=@_;
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	if ($cwd = `/bin/pwd`) {
		$cwd =~ s/[\r\s]*$//;
		&send_LW("$F: Directory: \"$cwd\"") if $V>2;
	} else {
		&send_LW("$F: Can't find cwd [$!]\n") if $V>0;
	}
}

sub htmllist {my $F='htmllist'; local($pth) = @_;
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Produce a HTML list (actualy a table) of the *.abc  files  under  the  $pth #
# directory.   We first stuff them into the %list table with the file name as #
# the key, and the path to the file as the value.   We  then  sort  the  file #
# names, and generate the HTML table of tune files. We might also extract the #
# "T:" headers from each file and sort on those, but the response time  would #
# be a lot slower.  The return value is the number of files found.            #
#
# 2015-11-16: Added code to start a new table every time the first char of the
# file name changes.
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	local($file,$files,$flds,$date,$info,$key,%list,$rest,$rows,$R,$url,$where);
	local($B1,$B2,$c,$cmd,$d,$dir,$k,$n,$m,$s,$sep,$t,$u,$val,$w);
	local($tfil,$tinf,$tlnk,$tnam,$ttl,$tttl); # Tune file/info/underscored/title strings
	$B1 = $B2 = '';		# Bold open/close tags, if we want them
	$date = $info = $k = $m = $name = $s = $ttl = '';
	if ($V>0) {
		$flds = 'tunes';
		$flds .=  ' names' if $showNames;
		$flds .=  ' titles' if $showTitles;
		&send_LW("$F: List $flds in \"$pth\" ...") if $V>2;
	}
# Start a "/bin/ls" subprocess to hunt down all the *.abc files under the $pth
# directory. Note that we list all *.abc files in the $pth directory, and also
# all *.abc files in subdirectories whose names start with a lower-case letter.
# This may change if we come up with better ideas.
	$cmd = "/bin/ls $pth/*.abc $pth/[0-9a-z]*/*.abc";
	&showCWD("$F." . __LINE__);
	unless (open LIST, "$cmd 2>&1 |") {
		print STDERR "$F: Can't find any .abc files here.\n" if $V>0;
		return 0;
	}
	$files = 0;
	&sendLog("$F: Generate the tune-list table ...\n") if $V>0;
# Run through the files and build the %list table of their names:
file:
	while ($file = <LIST>) {
		next file if ($file =~ /No such file/);
		$file =~ s/[\r\s]+$//;	# We get varied trailing white stuff from different systems' ls commands.
		&sendLog("$F: file=\"$file\" <====================\n") if $V>0;
		++$files;
		$tfil = $tinf = $tlnk = $tnam = $tttl = '';
		if ($file =~ m"^$pth/*(.*)$") {	# Pick off the file name
			$where = $1;
			&sendLog("$F: where='$where'\n") if $V>0;
		} else {
			&sendLog("$F: ### Wrong path in '$file'\n") if $V>0;
			next file;
		}
		if (($R,$tfil) = ($where =~ m"^(.*)/(.+)$")) {
			$tfil = '' unless defined $tfil;
			&sendLog("$F: Tune R='$R' tfil='$tfil'\n") if $V>2;
			$list{"$tfil/$R"} = $where;	# Sort by name, type
		} else {	# No directory in name
			$tfil = '' unless defined $tfil;
			$tfil = $where;
			$R = '.';
			$list{"$tfil/$R"} = $where;	# Sort by tfil
	#	} else {	# No directory
	#		&sendLog("$F: ### Can't parse location \"$where\"\n") if $V>0;
	#		next file;
		}
		$tfil = '' unless defined $tfil;
		if (-f "$pth/$seldir/$tfil") {	# Files "selected" by being linked into this directory
			&send_LW("$F: + _/$tfil [selected]\n") if $V>2;
			$selfiles{$tfil} = 1;	# Remember the file as special.
		} else {
			&send_LW("$F: - _/$tfil [not selected]\n") if $V>2;
		}
		&sendLog("$F: tfil='$tfil' tnam='$tnam' tinf='$tinf' tttl='$tttl' [before]\n") if $V>2;
		if (($tnam,$tinf) = ($tfil =~ m/^([_\w]+)(.*)$/)) {
			$tnam = '' unless defined $tnam;
			$tinf = '' unless defined $tinf;
			($tttl = $tnam) =~ s/_+/ /g;
			&sendLog("$F: tfil='$tfil' tnam='$tnam' tinf='$tinf' tttl='$tttl'\n") if $V>2;
		}
		&sendLog("$F: showInfo=$showInfo showTitles=$showTitles.\n") if $V>0;
		if ($showDate || $showInfo || $showTitles) {	# Do we want to read the file?
			&sendLog("$F: Read file '$tfil' [showDate=$showDate showInfo=$showInfo showTitles=$showTitles]\n") if $V>0;
			&sendLog("$F: fileData(\"$tfil\",\"$file\") ...\n") if $V>0;
			$TT = &fileData($tfil,$file);	# Extract stuff from the file
			&sendLog("$F: fileData found tune_title '$TT'\n") if $V>0;
			$tinf = '' unless defined $tinf;
			$tnam = '' unless defined $tnam;
			&sendLog("$F: tfil='$tfil' tnam='$tnam' tinf='$tinf' tttl='$tttl' [after fileData]\n") if $V>0;
			if ($date = $tuneDate{$TT} || '') {	# Year of tune's first-known appearance
				&sendLog("$F: date='$date' for tune_title '$TT'\n") if $V>0;
			}
			if ($info = $tuneInfo{$TT} || '') {	# Random information about a tune
				&sendLog("$F: info='$info' for tune_title '$TT'\n") if $V>0;
			}
		}
	}
	&sendLog("$F: Done with the tune-list table.\n") if $V>0;
	close LIST;
	print "<center>$files tune files in \"$pth\"</center>\n" if $V>2;
	$tblCnt = 0;		# Number of tables so far
# Get a list of the initial letters and produce line of links:
#	%inits = ();		# List of initial chars in file names
	print "<center>\n" if $centertables;
	%initials = ();		# Or 'Initial letters:';
	for $key (sort keys %list) {
		&sendLog("$F:\n") if $V>0;
		&sendLog("$F: key='$key'\n") if $V>0;
		$c = substr($key,0,1);
#		$keys{$c} ++;	# Count the number of times each initial char occurs
		$initials{$c} ++;	# Top-level list of initial letters of titles
	}
	$n = 0;
	$sep = '';
	print "<center><table border=0 margin=0 padding=0 style='border:2px ridge blue'><tr><td align=center>\n";
	for $c (sort keys %initials) {
		$sep = '- ' if $n;
		print "\t$sep<a href='#$c'><big><B>$c</B></big></a>\n";
		++$n;
	}
	print "</td></tr></table></center>\n";
	print "</center>\n" if $centertables;
# Run through the file names alphabetically and generate the HTML table:
	$rows = 0;
	for $key (sort {uc($a) cmp uc($b)} keys(%list)) {	# Sort files by title, etc.
		&sendLog("$F: ========================= NEXT SORT KEY\n") if $V>0;
		$val = $list{$key};
		&sendLog("$F: key='$key'\n") if $V>0;
		&sendLog("$F: val='$val'\n") if $V>0;
		&sendLog("$F: tttl=\"$tttl\" [before parsepath]\n") if $V>0;
		$tinf = $tnam = $tttl = '';
		$ttl = $tfil = $R = $k = $m = $s = $tlnk = $u = $url = $rest = $B1 = $B2 = '';
		if (($ttl,$tfil,$R,$k,$m,$s,$tlnk,$u,$url,$rest) = &parsepath($key)) {	# Is key in our fancy format?
			++$tblCnt;			# Count the items in the table.
			$k    = '' unless defined $k;	# Why are these sometimes undefined?
			$m    = '' unless defined $m;
			$s    = '' unless defined $s;
			$rest = '' unless defined $rest;
			$ttl  = '' unless defined $ttl;
			&sendLog("$F: Table row '$tfil' - - - - - - - - - ->\n") if $V>0;
			if ($tfil =~ s/\+/%2B/g) {&sendLog("$F: + tfil='$tfil'\n") if $V>0}
			if ($url  =~ s/\+/%2B/g) {&sendLog("$F: +  url='$url'\n") if $V>0}
			&sendLog("$F: - tfil='$tfil'\n") if $V>0;
			&sendLog("$F: -    R='$R'\n") if $V>0;
			&sendLog("$F: -    k='$k'\n") if $V>0;
			&sendLog("$F: -    m='$m'\n") if $V>0;
			&sendLog("$F: -    s='$s'\n") if $V>0;
			&sendLog("$F: - tlnk='$tlnk'\n") if $V>0;
			&sendLog("$F: -    u='$u'\n") if $V>0;
			&sendLog("$F: -  url='$url'\n") if $V>0;
			&sendLog("$F: - rest='$rest'\n") if $V>0;
			$c = uc(substr($tfil,0,1));	# First char of file name, capitalized
			&sendLog("$F: tblCnt=$tblCnt tblChr='$tblChr' c='$c' tfil='$tfil'\n") if $V>2;
			if ($c ne $tblChr) {		# New first char?
				&sendLog("$F: New tblChr='$c' after '$tblChr' split=$split.\n") if $V>2;
				if ($tblCnt > $tblMin) {	# Start a new table
					&tblFtr() if $tblCnt>0;	# Close out previous table
					&tblHdr($c);		# Start new table
					$tblCnt = 1;		# This item starts a new table
				}
				$tblChr = $c;			# Note the table's (first) char
			#	$tblCnt++;				# Count the tables
			}
			$k = '' unless defined $k;
			$m = '' unless defined $m;
			$R = '' unless defined $R;
			$s = '' unless defined $s;
			$k =~ s',', 'g;	# Space the keys to permit line wrapping [Does this really work?]
			&sendLog("$F: tfil='$tfil' c='$c' R='$R' k='$k' m='$m' s='$s'\n") if $V>0;
			if ($s =~ /^([\d,]*)[Ww](\w*)$/) {		# Does it have a line count for words?
				$w = $2 || '+';	# Note the word-line count separately
				$s = $1;		# Note the staff count
			} else {
				$w = '';
			}
			if ($selfiles{$tfil}) {	# Is this file selected for special display?
				$B1 = '<B>';		# We display the name in bold text
				$B2 = '</B>';
				&sendLog("$F: B1='$B1' B2='$B2' for '$tfil' [selected]\n") if $V>0;
			} else {
				&sendLog("$F: B1='$B1' B2='$B2' for '$tfil' [not selected]\n") if $V>0;
			}
			++$rows;		# Count the tune files listed.
			print "<tr><!-- -->\n";
			print "\t<td>" . &linkABC($url) . "</td>\n";
			print "\t<td>" . &linkGIF($url) . "</td>\n";
			print "\t<td>" . &linkPNG($url) . "</td>\n";
#	#	#	print "\t<td>" . &linkPS($url) . "</td>\n";
			print "\t<td>" . &linkPDF($url) . "</td>\n";
			print "\t<td>" . &linkSVG($url) . "</td>\n";
			print "\t<td>" . &linkMIDI($url) . "</td>\n";
#	#	#	print "\t<td>" . &linkIMG($tfil) . "</td>\n";
			print "\t<td>" . &linkSRC($tfil) . "</td>\n";
			&sendLog("$F: Format links done for '$tfil'\n") if $V>0;
#	#	#	$R =~ s/^(\w)/\u$1/ if $RisClass;
			print "\t<td>$R</td>\n";
			print "\t<td>$k</td>\n";
			print "\t<td>$m</td>\n";
			print "\t<td>$s</td>\n";
			print "\t<td>$w</td>\n";
			&sendLog("$F: Property fields done for '$tfil'\n") if $V>0;
			print "\t<td>$B1$tlnk $rest$B2</td>\n" if $showNames;
			&sendLog("$F: rest=\"$rest\" added to Names column.\n") if $rest && $V>0;
			&sendLog("$F: Name/Titles done for '$tfil'\n") if $V>0;
			&sendLog("$F: DONE with title-key-bars-staffs.abc/type key '$key'\n") if $V>0;
		} elsif (($tfil,$R) = ($key =~ m"^(.*)/(.*)$")) {	# Parse key into file/type
			&sendLog("$F: parsepath failed.\n") if $V>0;
			($url = "$musurl/$sesdir/$R/$tfil") =~ s"/+"/"g;	# Some of these fields may be null
			&sendLog("$F: url=\"$url\"\n") if $V>0;
			&sendLog("$F: B1='$B1' B2='$B2' for '$tfil'\n") if $V>0;
			print "\t<td>" . &linkTXT($url) . "</td>\n";
			print "\t<td>" . &linkGIF($url) . "</td>\n";
			print "\t<td>" . &linkPNG($url) . "</td>\n";
#			print "\t<td>" . &linkPS($url) . "</td>\n";
			print "\t<td>" . &linkPDF($url) . "</td>\n";
			print "\t<td>" . &linkSVG($url) . "</td>\n";
			print "\t<td>" . &linkMIDI($url) . "</td>\n";
#			print "\t<td>" . &linkIMG($tfil) . "</td>\n";
			print "\t<td>" . &linkSRC($tfil) . "</td>\n";
			&sendLog("$F: Format links done for '$tfil'\n") if $V>0;
			print "\t<td>$R</td>\n";
			# Now we figure out whether there's a special directory for the title:
			# Everything before the first '-' is the title, the rest is info about the tune:
			&sendLog(__LINE__ . ": B1='$B1' B2='$B2' for '$tfil'\n") if $V>0;
			if (($t,$k,$m,$s) = ($tfil =~ m"^(.*)-([A-G]*[^-]*)-([,\d]+)-([,\d]+)\.abc")) {
				&sendLog("$F: t='$t' k='$k' m='$m' s='$s'\n") if $V>2;
				($n = $t) =~ s/_/ /g;			# Get dance name by converting underscores to spaces
				($d = $t) =~ s/_\d+[a-z]*$//;	# The directory is the title minus final version number
				&send_LW("$F: d=\"$d\"<br>\n") if $V>0;
				if (-d ($dir = "$pth/$d")) {	# Is there a directory for the title?
					&sendLog("$F: baseuri=\"$baseuri\"\n") if $V>2;
					&sendLog("$F: musurl=\"$musurl\"\n") if $V>2;
					&sendLog("$F: sesdir=\"$sesdir\"\n") if $V>2;
					$u = "$baseuri$musurl/$sesdir/$d/";	# URL to list tune's directory
					$u =~ s"/+\./"/"g;					# Strip out "." directories
					&sendLog("$F: u=\"$u\"\n") if $V>2;
				} else {
					$tlnk = $n;		# No subdirectory; just give name
					$dir = '';		# Don't remember the subdirectory
				}
				&sendLog("$F: tlnk=\"$tlnk\"\n") if $V>2;
				&sendLog("$F: n=\"$n\"\n") if $V>2;
				if ($selfiles{$tfil}) {	# Is this file selected for special display?
					$B1 = '<B>';		# We display the name in bold text
					$B2 = '</B>';
					&sendLog("$F: B1='$B1' B2='$B2' for '$tfil' [selected]\n") if $V>0;
				} else {
					&sendLog("$F: B1='$B1' B2='$B2' for '$tfil' [not selected]\n") if $V>0;
				}
			#	print "\t<td>$k</td>\n";
				print "\t<td>K: $k</td>\n";
				print "\t<td>$m</td>\n";
				print "\t<td>$s</td>\n";
				print "\t<td>-</td>\n";
				&sendLog("$F: Property fields done for '$tfil'\n") if $V>0;
				print "\t<td>$tlnk</td>\n" if $showNames;
				&sendLog("$F: Name/Titles done for '$tfil'\n") if $V>0;
			} else {
				print "\t<td><a href=''>---</a></td>\n";
				print "\t<td colspan=5>?</td>\n";
				print "\t<td>$tfil</td>\n" if $showNames;
			}
			&sendLog("$F: DONE with title/type key '$key'\n") if $V>0;
		} else {		# File name not in any known format
			print "\t<td colspan=4>$key</td>\n";	# Non-music columns empty for now
		}
		&sendLog("$F: showDir=$showDir showNames=$showNames showTitles=$showTitles.\n") if $V>0;
		&sendLog("$F: tlnk=\"$tlnk\" done.\n") if $V>2;
		print "\t<td><a href='$tlnk'>$d</a></td>\n" if $showNames && $showDir && $tlnk;	# Used?
		$d = '???' unless defined $d;
		&sendLog("$F: B1='$B1' B2='$B2' for '$d'\n") if $V>0;
		if ($showTitles) {
			$ttl = '???' unless defined $ttl;	# This is happening [20190708]
			$name = '???' unless defined $name;	# ditto
			print "\t<td><!-- Titles -->\n";
			print "\t<font size=\"$TitleSize\">\n" if $TitleSize && $showNames;
			if (($showNames<1) && $tlnk) {		# Did we show tune link in Name column?
				if (substr($tlnk,0,1) eq "<") {	# Do we have a subdirectory for this title?
					&sendLog("$F: Send link to tune directory.\n") if $V>2;
				#	$linkshown = 1;				# Note that we've done this
					print "\t\t$B1$tlnk$B2<br>\n";	# Link to tune directory in Titles column
				}
			}
			# Kludge: If we're not showing the "Name" column, we look for a directory
			# that matches the file's first title.  If there's a subdirectory with the
			# same name, we replace the first title in the title list with a link to
			# the subdirectory.  This tends to produce both "titles" if they are spelled
			# even slightly differently, but that's OK. There is a tendency for what
			# looks like duplicate names on the screen, when the rendering doesn't show
			# the actual differences.
			if ($ttlH = $ttlH{$tfil}) {			# This tune's HTML title list
				&sendLog("$F: ttlH=\"$ttlH\"\n") if $V>2;
				&sendLog("$F: tttl=\"$tttl\" [before trim]\n") if $V>2;
				if ($ttlH =~ s/^$tttl<br>//) {
					&sendLog("$F: ttlH=\"$ttlH\" <== Shortened.\n") if $V>2;
				} elsif ($ttlH =~ s/^$tttl$//) {
					&sendLog("$F: ttlH=\"$ttlH\" <== Deleted.\n") if $V>2;
				} else {
					&sendLog("$F: ttlH=\"$ttlH\" <== Unchanged.\n") if $V>2;
				}
				print "\t\t$B1$ttlH$B2<br>\n";
			} else {
				print "\t\t[No title for \"$tfil\"]\n" if $V>0;
			}
			print "\t</font>\n" if $TitleSize;
			print "\t</td>\n";
		#	print "\t<td><a href='$Titles'>$d</a></td>\n";
			&sendLog("$F: Info field <---------- \n") if $V>0;
			&sendLog("$F: Info field name='$name'\n") if $V>0;
			&sendLog("$F: Info field ttl='$ttl'\n") if $V>0;
			if ($showDate) {
				&sendLog("$F: showDate=$showDate ttl='$ttl' date='$date'\n") if $V>0;
				if ($date  = $tuneDate{$ttl} || '') {
					&sendLog("$F: date='$date' for '$ttl'\n") if $V>0;
				} else {
					&sendLog("$F: No date for ttl=\"$ttl\"\n") if $V>0;
				}
				print "\t<td><!-- Date -->\n";
				print "\t<font size=\"$TitleSize\">\n" if $TitleSize;
				print "\t\t$date\n";
				print "\t</font>\n" if $TitleSize;
				print "\t</td>\n";
			}
			if ($showInfo) {
				&sendLog("$F: showInfo=$showInfo ttl='$ttl' info='$info'\n") if $V>0;
				if ($info  = $tuneInfo{$ttl} || '') {
					&sendLog("$F: Date: info='$info' for '$ttl'\n") if $V>0;
				} else {
					&sendLog("$F: Date: no info for ttl=\"$ttl\"\n") if $V>0;
				}
				print "\t<td><!-- Info -->\n";
				print "\t<font size=\"$TitleSize\">\n" if $TitleSize;
				print "\t\t$info\n";
				print "\t</font>\n" if $TitleSize;
				print "\t</td>\n";
			}
		} else {	# Don't show titles, only the file name
			&sendLog("$F: No titles; just the file name \"$B1$tfil$B2\"\n") if $V>0;
		}
		print "<td>$tblCnt</td>\n";
		print "</tr>\n";
	#	++$tblCnt;			# Count the items in the table.
	}
	&tblFtr();
	print "$rows tune files found.<br>\n" if $V>1;
	print "</center>\n" if $centertables;
	&footnotes();
	return $files;
}

sub tblFtr {my $F='tableHeader';
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	print "</tbody>\n";
	print "</table>\n";
	print "</center>\n";
	print "<!-- -- -- -- Table for '$tblChr' -- -- -- -->\n";
}

sub tblHdr {my $F='tblHdr'; local($chr) = @_;
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	&sendLog("$F: '$chr'\n") if $V>0;
	print "<center>\n" if $centertables;
	print "<!-- == == == Table for '$chr' == == == -->\n";
	print "<table border=2 style=\"border:2px green; border-collapse:collapse; margin:1px; padding:1px; width='98%'\">\n";
	print "<thead>\n";
	print "<TR>\n";
	print "\t<TH colspan=$fields>\n";
	print "\t\t<strong><a name='$chr'>&mdash; <B><big>$chr</big></B> &mdash;</a></strong>\n";
	print "\t\t&nbsp;&nbsp;<strong><a href='#top'><B>top</B></a></strong>\n";
	print "\t</TH>\n";
	print "</TR>\n";
	print "</thead>\n";
	print "<tbody>\n";
	print "<TR>\n";
	print "\t<TH>get</TH>\n";
	print "\t<TH>gif</TH>\n";
	print "\t<TH>png</TH>\n";
#	print "\t<TH>ps</TH>\n";
	print "\t<TH>pdf</TH>\n";
	print "\t<TH>svg</TH>\n";
	print "\t<TH>midi</TH>\n";
	print "\t<TH><a href='#colimg'>$imgcol</a></TH>\n" if $showimg;
#	print "\t<TH>type</TH>\n";
	print "\t<TH><a href='#colClass'>Class</a></TH>\n";
#	print "\t<TH>key</TH>\n";
	print "\t<TH><a href='#KNote'>K</a></TH>\n";
	print "\t<TH><a href='#colB'>B</a></TH>\n";
	print "\t<TH><a href='#colst'>st</a></TH>\n";
	print "\t<TH><a href='#colw'>w</a></TH>\n";
	print "\t<TH><a href='#colName'>Name</a></TH>\n" if $showNames;
	print "\t<TH><a href='#colDir'>Dir</a></TH>\n" if $showDir;
	print "\t<TH><a href='#colTitles'>Titles</a></TH>\n" if $showTitles;
	print "\t<TH><a href='#coldate'> d </a></TH>\n" if $showDate;
	print "\t<TH><a href='#colinfo'> i </a></TH>\n" if $showInfo;
	print "</TR>\n";
}

sub parsepath {my $F='parsepath'; my($path) = @_;
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Here we try to extract info about a file's content from its pathname.   The #
# first  thing we look for is a containing directory that looks like the name #
# or a  rhythm  or  other  classification.   Some  of  our  collections  have #
# directories  named  for  rhythms  and  contain tunes in that rhythm.  Other #
# directories are named for a tune and  contain  multiple  versions  of  that #
# tune. Still other directories are named for a dance, and contain files with #
# tunes for that dance.                                                       #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	local($ttl,$name,$type,$key,$bars,$stfs,$lnk,$u,$url);	# Return values.
	local($d,$lnk,$n,$nam,$rest,$subdir);
	&sendLog("$F: Called for path='$path' V=$V.\n") if $V>0; 
	$name = '[name]';	# Some may be missing.
	$type = '[type]';	# These are "missing" indicators when they show up in our output.
	$key  = '[key]';
	$bars = '[bars]';
	$stfs = '[stfs]';
	$rest = '';			# For stuff at the end, before the suffix.
	$lnk  = '[lnk]';
	$u    = '[u]';
	$url  = '[url]';
	# First, does its directory looke like a rhythm/classification/date?
	if (($name,$type,$rest) = ($path =~ m"^(.*)/([-_.0-9a-z]*)(.*)$")) {	# Parse path into file/class/date if possible
		&sendLog("$F: name='$name'\n") if $V>1;
		&sendLog("$F: type='$type'\n") if $V>1;
		&sendLog("$F: rest='$rest'\n") if $V>1;
		$type = '' if $type eq '.';		# Current directory isn't a type ;-)
		($url = "$musurl/$sesdir/$type/$name") =~ s"/+"/"g;	# Some of these fields may be null
		&sendLog("$F: url=\"$url\"\n") if $V>1;
		# Now we figure out whether there's a special directory for the title:
		# Everything before the first '-' is the title, the rest is info about the tune:
		if (($ttl,$key,$bars,$stfs,$rest) = ($name =~ m"^(.*)[-=_]([A-Z]+[^-]*)-([\d,]*)-([\d,Ww]*)(.*)\.abc")) {
			$rest =~ s/^-//;
			&sendLog("$F: ttl='$ttl'\n") if $V>1;
			&sendLog("$F: key='$key'\n") if $V>1;
			&sendLog("$F: bars='$bars'\n") if $V>1;
			&sendLog("$F: stfs='$stfs'\n") if $V>1;
			&sendLog("$F: rest='$rest'\n") if $V>1;
			($n = $ttl) =~ s/[-_=]/ /g;		# Get file/tune/dance display name by converting underscores to spaces
			($d = $ttl) =~ s/_\d+[a-z]*$//;	# The directory is the title minus final version number
			if (-d ($subdir = "$pth/$d")) {	# Is there a directory for the title?
				&sendLog("$F: subdir \"$subdir\" exists.\n") if $V>0;
				&sendLog("$F: baseuri=\"$baseuri\"\n") if $V>0;
				&sendLog("$F: musurl=\"$musurl\"\n") if $V>0;
				&sendLog("$F: sesdir=\"$sesdir\"\n") if $V>0;
				$u = "$baseuri/$d/";		# URL to list tune's directory
				$u =~ s"/+\./"/"g;			# Strip out "." directories
				&sendLog("$F: u=\"$u\"\n") if $V>0;
				$lnk = "<a href=\"$u\">$n</a>";
			} else {
				&sendLog("$F: '$subdir' not a directory.\n") if $V>0;
				$lnk = $n;
			}
			&sendLog("$F: lnk='$lnk'\n") if $V>0;
			&sendLog("$F: Return all values:\n") if $V>0;
			$ttl  = '' unless defined $ttl ; &sendLog("$F: 1 ttl ='$ttl'\n")  if $V>1;
			$name = '' unless defined $name; &sendLog("$F: 1 name='$name'\n") if $V>1;
			$type = '' unless defined $type; &sendLog("$F: 1 type='$type'\n") if $V>1;
			$key  = '' unless defined $key ; &sendLog("$F: 1 key ='$key'\n")  if $V>1;
			$bars = '' unless defined $bars; &sendLog("$F: 1 bars='$bars'\n") if $V>1;
			$stfs = '' unless defined $stfs; &sendLog("$F: 1 stfs='$stfs'\n") if $V>1;
			$lnk  = '' unless defined $lnk ; &sendLog("$F: 1 lnk ='$lnk'\n")  if $V>1;
			return($ttl,$name,$type,$key,$bars,$stfs,$lnk,$name,$url,$rest);	# Return values.
		} elsif (($nam,$suf) = ($name =~ /^(.*)\.(abc)$/)) {
			&sendLog("$F: Matched ABC files with name '$nam' sufix '$suf'\n") if $V>0;
			$nam =~ s/_/ /g;	# "Display" name derived from file name
			$name = '' unless defined $name; &sendLog("$F: 1 name='$name'\n") if $V>1;
			$suf  = '' unless defined $suf;  &sendLog("$F: 1 suf='$suf'\n")   if $V>1;
			$ttl  = '' unless defined $ttl;  &sendLog("$F: 1 ttl='$ttl'\n")   if $V>1;
			&sendLog("$F: Return type '$suf' name '$nam'\n") if $V>0;
			&sendLog("$F: 2 ttl ='$ttl'\n") if $V>0;
			&sendLog("$F: 2 name='$name'\n") if $V>0;
			return($ttl,$name,$type,$key,$bars,$stfs,$nam,$name,$url,$rest);	# Return values.
		} else {
			&sendLog("$F: name doesn't have title-key-bars-staffs.abc syntax.\n") if $V>0;
			&sendLog("$F: Return name,type\n") if $V>0;
			&sendLog("$F: 3 ttl ='$ttl'\n") if $V>0;
			&sendLog("$F: 3 name='$name'\n") if $V>0;
			&sendLog("$F: 3 type='$type'\n") if $V>0;
			&sendLog("$F: 3 key ='$key'\n") if $V>0;
			&sendLog("$F: 3 bars='$bars'\n") if $V>0;
			&sendLog("$F: 3 stfs='$stfs'\n") if $V>0;
			&sendLog("$F: 3 lnk ='$lnk'\n") if $V>0;
			&sendLog("$F: 3 name='$name'\n") if $V>0;
			&sendLog("$F: 3 url ='$url'\n") if $V>0;
			&sendLog("$F: 3 rest='$rest'\n") if $V>0;
			return($ttl,$name,$type,$key,$bars,$stfs,$lnk,$name,$url,$rest);	# Return values.
		}
	} else {
		&sendLog("$F: file/type syntax not found.\n") if $V>0;
		&sendLog("$F: ###FAIL###\n") if $V>0;
		return undef;	# Failure
	}
	&sendLog("$F: ###FAIL###\n") if $V>0;
	return undef;	# Failure
}

sub footnotes {
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Print out a table of footnote  with  explanations  of  some  parts  of  the #
# "session" tune table.  This should perhaps be moved into a separate module, #
# perhaps with this info in tables or separate text files.                    #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	print "<hr>\n";
	print "<a name='Footnotes'>Footnotes</a>:<br>\n";
	print "<table border=0>\n";
#
	print "<tr valign=top>\n";
	print "\t<td><a name='colB'><b>B</b>:</a></td>\n";
	print "\t<td>Bar count for once through the tune.</td>\n";
	print "</td>\n";
	print "</tr>\n";
#
	print "<tr valign=top>\n";
	print "\t<td><a name='colClass'><b>Class</b>:</a></td>\n";
	print "\t<td>The tune's classification (rhythm, dance, origin, etc.).</td>\n";
	print "</tr>\n";
#
	if ($showDate) {
		print "<tr valign=top>\n";
		print "\t<td><b><a name='coldate'>date:</a></b></td>\n";
		print "\t<td>The earliest known date for a tune.\n";
		print "\t\tThis is generally not very reliable, of course.\n";
		print "</td>\n";
		print "</tr>\n";
	}
#
	if ($showimg) {
		print "<tr valign=top>\n";
		print "\t<td><b><a name='colimg'>$imgcol:</a></b></td>\n";
		print "\t<td>Source-file images of the type(s) shown.\n";
		print "\t\tSuch files may be of any type.\n";
		print "\t\tThey and are usually formatted for printing,\n";
		print "\t\tand may contain extra information in addition to the music.\n";
		print "\t\tThey may not fit well on a small screen.\n";
		print "\t\tThe gif/png/pdf/svg/midi links are produced from the abc source,\n";
		print "\t\tusually without borders, and will usually fit better in a small window.\n";
		print "</td>\n";
		print "</tr>\n";
	}
#
	if ($showInfo) {
		print "<tr valign=top>\n";
		print "\t<td><b><a name='colinfo'>info:</a></b></td>\n";
		print "\t<td>Assorted information about the file,\n";
		print "\t\tdepending on what has been implemented and supplied.\n";
		print "\t\tCurrently only shows the date of (earliest known) publication.\n";
		print "</td>\n";
		print "</tr>\n";
	}
#
	print "<tr valign=top>\n";
	print "\t<td><a name='Knote'><b>K</b>:</a></td>\n";
	print "\t<td>The tune's initial or main key.</td>\n";
	print "</tr>\n";
#
	if ($showst) {
		print "<tr valign=top>\n";
		print "\t<td><a name='colst'><b>st</b>:</a></td>\n";
		print "\t<td>Staff count for this version of the tune.</td>\n";
		print "</tr>\n";
	}
#
	if ($showNames) {
		print "<tr valign=top>\n";
		print "\t<td><a name='colName'><b>Name</b>:</a></td>\n";
		print "\t<td>is taken from the file name, with underscores replaced by spaces.\n";
		print "\t\t If a link, points to a directory with several versions of the tune.\n";
		print "\t\t <b>Bold names or titles</b> are used to draw attention to files,\n";
		print "\t\t often because they're on a program for an upcoming event,\n";
		print "\t\t or because they're in a session's commonly-played repertoire.\n";
		print "\t</td>\n";
		print "</tr>\n";
	}
#
	if ($showTitles) {
		print "<tr valign=top>\n";
		print "\t<td><a name='colTitles'><b>Titles</b>:</a></td>\n";
		print "\t<td>is a list of the titles found in the file.\n";
		print "\t\tDuplicate titles are only shown once.\n";
		print "\t\tFor tunes that don't have a T: title, the first P: value is shown.\n";
		print "\t</td>\n";
		print "</tr>\n";
	}
#
	if ($showw) {
		print "<tr valign=top>\n";
		print "\t<td><a name='colw'><b>w</b>:</a></td>\n";
		print "\t<td>Words (lyrics) line count for this version of the tune.</td>\n";
		print "</tr>\n";
	}
#
	print "</table>\n";
	print "\n";
}

sub Vopt {
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Set the verbosity from various environment variables. The value may #
# be  a  verbose  level (1 digit), plus an optional output file name. #
# The file V is opened to the file, if any, or STDERR by default. The #
# default  value  for the verbosity level is 1, which generally means #
# to produce only serious error messages.                             #
#                                                                     #
# Here's how this routine is typically called:                        #
#    ($P = $0) =~ s'.*/'' unless defined($P);                         #
#    &Vopt($ENV{"V_$P"} || $ENV{"D_$P"} || $ENV{"T_$P"} || '1');      #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	$Vopt = shift || '1';
#	print "<br>Vopt: Vopt=\"$Vopt\"<br>\n" if $V>1;
	if ($Vopt =~ /^(\d+)(.+)$/) {
		$V = $1;
		$Vfil = $2;
		if (!open(V,">>$Vfil")) {
			print V "$P: Can't write \"$Vfil\" ($!)\n" if $V>0;
			open(V,">>&STDERR");
		}
	} else {
		$V = $Vopt;
		open(V,">>&STDERR");
	}
	select V; $| = 1; select STDOUT; $| = 1;
	$esep = '=' x 70; print V "\n$P $esep\n" if $V>1;
	$hsep = '-' x 70; print V "\n$P $hsep\n" if $V>1;
	print V "$P started with V=$V ", `date` if $V>1;
}
