#!/usr/bin/perl
##
##  updatefreq.cgi --  Presents the client with a form then updates the frequency data base
##

use File::Copy;

$FREQDB =   "../freq/freq.db";
$BOILERHTML =  "../freq/freqBoiler.html";
$BAKFILE =  "../freq/freq.html";
$FREQHTML = "../freq.html";
$LOCK = "../freq/freq_lock";

# Global variables
local ($arg = $ARGV[0]);
local ($method = $ENV{'REQUEST_METHOD'});

# Test if creating a new data base
if ($arg eq "create") {
	printf("Creating new database: '%s'\n", $FREQDB);
	creatdb($FREQDB);
	exit(0);
}


##
#  GET: Called from the html anchor... //hostname/cgi-bin/updatefreq.cgi?freq=nn
#       or "//hostname/cgi-bin/updatefreq.cgi?config" to configure the environment
#	Builds a form and submits it to the client.  Client will then submit the form (as type POST)
#	and recalls this program (which is processed by the "elsif($method eq "POST")")
##
if ($method eq "GET") {
    local ($i, $freq, @inUseMembers);

    # Process the query string from the URL and snatch the $freq value
    $freq = $ENV{'QUERY_STRING'};

    # Check if configuring the environment
    if ($freq eq "config") {
    	config();
	exit(0);
    }

    # Check if we're simply syncing up the database
    if ($freq eq "update") {
    	update();
	exit(0);
    }

    # List the database
    if ($freq eq "list") {
    	list();
	exit(0);
    }

    #Pick off the numeric part of the query string
    $freq =~ s/^.*=//;



    # Make a list of club members using this frequency
    @inUseMembers = split(/,/, mklist($freq, $FREQDB));


    ## Build the HTML form and send it to the client ##

    MIME_header("text/html", "Frequency Update Form");
    printf("<DIV align=\"center\"><H1>Update Frequency Use Database for Channel %s</H1></DIV>\n", $freq);
    if (@inUseMembers > 0) {
		printf("<P>Club members currently using <B>Channel %s:</B></P>\n", $freq);
		printf("<UL>\n");
		for ($i=0; $inUseMembers[$i]; $i++) {
		    printf("<LI>%s</LI>\n", $inUseMembers[$i]);
		}
		printf("</UL>\n");
    }
    else {
    		printf("<H3>There are no club members currently using this frequency</H3>\n");
    }


    print <<END_OF_BUILD_FORM1;
<BR><BR>
<P>Click the appropriate button below and select</P>
<UL>
  <LI type="circle">
    <B>Add</B> and enter name of club member who is currently using this channel
  </LI>
END_OF_BUILD_FORM1
    
    if (@inUseMembers > 0) {
  	printf("<LI type=\"circle\">\n");
    	printf("<B>Delete</B> and select the club member who is no longer using this channel</LI>\n");
    }

    print <<END_OF_BUILD_FORM2;
</UL>
<BR><BR>
<FORM method="post">
  <INPUT type="hidden" name="freq" value="$freq">
  <INPUT type="radio" name="command" value="add">Add:
  <INPUT type="text" name="newname" value="" size="25" maxlength="80">
  <I>(Enter club member's name here)</I>
  <BR>
END_OF_BUILD_FORM2

    if (@inUseMembers > 0) {
  	printf("<INPUT type=\"radio\" name=\"command\" value=\"delete\">Delete:\n");
  	printf("<SELECT name=\"name\" size=\"1\">\n");

	for ($i=0; $inUseMembers[$i]; $i++) {
	    printf("<OPTION>%s\n", $inUseMembers[$i]);
	}
    }

    print <<END_OF_BUILD_FORM3;
  </SELECT>
  <BR><BR><BR>
  <INPUT type="submit" name="submit" value="Submit the change to the database">
  <INPUT type="submit" name="cancel" value="Don't make any changes, just QUIT">
  </FORM>
</BODY>
</HTML>
END_OF_BUILD_FORM3
} #GET



##
#  POST: Called when client submits form built and returned to client via GET portion of this program.
#	(1) This code will first update the "data/freq.db" data base that contains the list of names corresponding
#	    to each frequency that is in use.  The updates will come from form data "name" and command="add" or "delete"
#
#	(2) $HTMLBASE will then be merged with the FREQDB and overwrite $FREQHTML
#
#	(3) $FREQHTML (the parent HTML) will be refreshed to reflect the update upon exit of this program
##
elsif ($method eq "POST") {
	local (%FORM, %db, $name, $freq);

	#Get the form data variables
	parse_form_data(*FORM);

	$freq  = $FORM{'freq'};

	if ($FORM{'cancel'}) {
		MIME_header("text/html", "Canceled!");
		print <<END_OF_CANCEL_SCRIPT;
<SCRIPT LANGUAGE="JavaScript"><!--
      location.href="../freq.html";
//--></SCRIPT>
END_OF_CANCEL_SCRIPT
		exit(0);
	}


	if ($FORM{'command'} eq "add") {
		$name = $FORM{'newname'};
		if (! $name) {
			MIME_header("text/html", "ERROR");
			printf("<H1>ERROR: You must enter a name to add.</H1></BODY></HTML>\n");
			printf("<BR><BR><BR><DIV align=\"center\"><A HREF=\"../freq.html\">Return</A></DIV>\n");
			exit(1);
		}

		# Load the database
		if (! loaddb(*db, $FREQDB)) {
			MIME_header("text/html", "ERROR");
			printf("<H1>ERROR: Cannot load the database.</H1></BODY></HTML>\n");
			printf("<BR><BR><BR><DIV align=\"center\"><A HREF=\"../freq.html\">Return</A></DIV>\n");
			exit(1);
		}

		# Add the name to the data base
		if (! addmember(*db, $freq, $name)) {
			MIME_header("text/html", "ERROR");
			printf("<H1>An error occurred while attempting to add '%s' to the database.</H1></BODY></HTML>\n", $name);
			printf("<BR><BR><BR><DIV align=\"center\"><A HREF=\"../freq.html\">Return</A></DIV>\n");
			exit(1);
		}

		# Store the updated database and merge the database and boilerplate html to create an updated freq.html file
    submit(*db, $FREQDB, $BOILERHTML, $FREQHTML);
} #add

elsif ($FORM{'command'} eq "delete") {
    $name = $FORM{'name'};
    if (! $name) {
	    MIME_header("text/html", "ERROR");
	    printf("<H1>ERROR: You must enter a name to delete.</H1></BODY></HTML>\n");
	    printf("<BR><BR><BR><DIV align=\"center\"><A HREF=\"../freq.html\">Return</A></DIV>\n");
	    exit(1);
    }

    # Load the database
    if (! loaddb(*db, $FREQDB)) {
	    MIME_header("text/html", "ERROR");
	    printf("<H1>ERROR: Cannot load the database.</H1></BODY></HTML>\n");
	    printf("<BR><BR><BR><DIV align=\"center\"><A HREF=\"../freq.html\">Return</A></DIV>\n");
	    exit(1);
    }

    # Delete the name from the data base
    if (! delmember(*db, $freq, $name)) {
	    MIME_header("text/html", "ERROR");
	    printf("<H1>An error occurred while attempting to delete '%s' from the database.</H1></BODY></HTML>\n",
		    $name);
	    printf("<BR><BR><BR><DIV align=\"center\"><A HREF=\"../freq.html\">Return</A></DIV>\n");
	    exit(1);
    }

    # Store the updated database and merge the database and boilerplate html to create an updated freq.html file
    submit(*db, $FREQDB, $BOILERHTML, $FREQHTML);
} #delete


else {
    MIME_header("text/html", "ERROR");
    printf("<H1>ERROR: You must select either the <B>Add</B> or <B>Delete</B> button.</H1></BODY></HTML>\n");
    printf("<BR><BR><BR><DIV align=\"center\"><A HREF=\"../freq.html\">Return</A></DIV>\n");
    exit(1);
}


##
#   Display a results page and reload the freqs page
##
printf("Content-type: text/html\n\n");
print <<END_OF_SUBMIT1;
<HTML>
<HEAD>
<TITLE>
    Submission Results
</TITLE>

<SCRIPT LANGUAGE="JavaScript"><!--
    function reload() {
	    ForceReloadFromServer = true;
	    Window.location.reload(ForceReloadFromServer);
	    Window.location.reload();
    }
//--></SCRIPT>
</HEAD>

<BODY background="../logoa.gif">
END_OF_SUBMIT1

printf("<DIV align=\"center\"><H1>Your Change has been added to the Database</H1></DIV>\n");
printf("<H3>Frequency: '%s'</H3>\n", $freq);
printf("<H3>Action: '%s'</H3>\n", $FORM{'command'});
if ($FORM{'command'} eq "add") {
printf("<H3>New club member name: '%s'</H3>\n", $FORM{'newname'});
}
else {
printf("<H3>Club member deleted from data base for channel \"%s\": '%s'</H3>\n", $freq, $FORM{'name'});
}

printf("<BR><BR><BR><DIV align=\"center\"><A HREF=\"../freq.html\" onLoad=\"reload()\">Return</A></DIV>\n");

print <<END_OF_SUBMIT2;
</BODY>
</HTML>
END_OF_SUBMIT2
} #POST



## Invalid server method
else {
return_error(405, "Method Not Allowed", "Server uses unsupported method");
}


exit(0);





###############################################################################################################################
##
#  MIME_header -- Output the boilerplate of the html document
# 		- Argument #1: the MIME type (e.g. "text/html")
# 		- Argument #2: the title
#	Call with: MIME_header($mime_type, $title)
##
###############################################################################################################################
sub MIME_header
{
local ($mime_type, $title_string) = @_;

print "Content-type: ", $mime_type, "\n\n";
print "<HTML>\n";
print "<HEAD><TITLE>", $title_string, "</TITLE></HEAD>\n";
print "<BODY background=\"../logoa.gif\">\n";

} #MIME_header



###############################################################################################################################
##
#  return_error -- Issues an error page to the client
#	Call with: return_error($status, $keyword, $message)
##
###############################################################################################################################
sub return_error
{
local ($status, $keyword, $message) = @_;

print "Content-type: text/html\n";
print "Status: ", $status, " ", $keyword, "\n\n";

print <<END_OF_ERROR;
<TITLE>--ERROR--</TITLE>
<H1>$keyword</H1>
<HR>$message</HR>

END_OF_ERROR

exit(1);
} #return_error





#############################################################################
##
#  createdb -- Creates a new database with no allocated frequencies
#	Call with:  createdb($dbname)
##
#############################################################################
sub creatdb
{
local ($dbname) = @_;

local ($freq);

open(DB, "> $dbname") || return 0;
for ($freq=10; $freq <= 60; $freq++) {
printf(DB "%s:\n", $freq);
}
close(DB);
return(1);
} #creatdb


#############################################################################
##
#  mklist -- Reads the database and returns all member name for a given
#            frequency
#	Call with: $members = mklist($freq, $dbname)
#             where $members will get "name1,name2,...nameN"
##
#############################################################################
sub mklist
{
local ($freq, $dbname) = @_;

open(DB, "< $dbname") || return(NULL);

while(<DB>) {
if (/^$freq:/) {
    $_ = Chomp($_);
    last;
}
}
close(DB);

s/^.*://;
return($_);
} #mklist


#############################################################################
##
#   loaddb -- Loads the database into a hash table keyed on frequency
#             The keys range from "10" to "60" and values will be null if
#             no club members are using that frequency
#             Example:  db{"25"} = "Sam Smith,Bob Jones,Bill Davis"
#	Call with: loaddb(*db, $dbname)
##
#############################################################################
sub loaddb
{
local (*db, $dbname) = @_;

local ($freq, @list);

open(DB, "< $dbname") || return(0);

while(<DB>) {
$_ = Chomp($_);
$freq = $_;
$freq =~ s/:.*//;
if (! s/^.*://) {
    next;
}

$db{$freq} = $_;
}

close(DB);

return(1);
} #loaddb


#############################################################################
##
#   stordb -- Writes a hash table to a file of format "freq:member1,member2..."
#	Call with: stordb(*db, $dbname)
##
#############################################################################
sub stordb
{
local (*db, $dbname) = @_;

local ($i, $j, $name1, $name2);

  # Make backups of the database
  for ($i=4, $j=5; $i >= 0; $i--, $j--) {
	$name1 = $dbname . "." . $i;
	$name2 = $dbname . "." . $j;
  	if (-e $name1) {
		copy($name1, $name2);
	}
  }
  copy($dbname, $dbname . ".0");

open(DB, "> $dbname") || return(0);

for ($i=10; $i <= 60; $i++) {
if ($db{$i}) {
    printf(DB "%s:%s\n", $i, $db{$i});
}
else {
    printf(DB "%s:\n", $i);
}
}

close(DB);

return(1);
} #stordb


#############################################################################
##
#   addmember -- Adds a new club member to a frequency in the hash table
#                Refuses to add a duplicate name and returns 0 in that case.
#	Call with: addmember(*db, $key, $member)
##
#############################################################################
sub addmember
{
local (*db, $key, $member) = @_;

# Don't add a duplicate name
if (index($db{$key}, $member) >= 0) {
return(0);
}
if ($db{$key}) {
$db{$key} = $db{$key} . "," . $member;
}
else {
$db{$key} = $member;
}

return(1);

} #addmember


#############################################################################
##
#   delmember -- Deletes a club member from a frequency in the hash table
#	Call with: delmember(*db, $key, $member)
##
#############################################################################
sub delmember
{
local (*db, $key, $member) = @_;
local ($i, $j, @members, @newmembers);

# Exit if member is not in the list
if (index($db{$key}, $member) < 0) {
return(0);
}

# Split the member list out into an array
@members = split(/,/, $db{$key});

for ($i=0, $j=0; $members[$i]; $i++) {
if ($members[$i] ne $member) {
    $newmembers[$j++] = $members[$i];
}
}

# Put the new list back into the data base
$db{$key} = join(",", @newmembers);

return(1);

} #delmember



#############################################################################
##
#   buildNewHtml -- Merges the database with a boilerplate HTML then copies
#                   it to the htdocs/freq.html web page.
#	Call with: buildNewHtml(*db, $boilerHTML, $targetHTML)
##
#############################################################################
sub buildNewHtml
{
local (*db, $boiler, $target) = @_;

local ($freq, @members, $i, $n, %names);

open(TARG, "> $BAKFILE") || return(0);
open(SRC, "< $boiler") || return(0);

while(<SRC>) {
$_ = Chomp($_);
if (/X[1-6][0-9]X/) {
    #Get the frequency number
    $freq = $_;
    $freq =~ s/^.*X([1-6][0-9])X.*$/\1/;
    $freq = Chomp($freq);

    #Get the list of members associated with the frequency
    @members = split(/,/, $db{$freq});
    chomp(@members);
    $n = @members;

    #Output a title
    if ($n > 0) {
	    printf(TARG "\t\ttitle=\"");
	    for ($i=0; $members[$i]; $i++) {
		    $members[$i] = Chomp($members[$i]);
		    if (! $members[$i+1]) {
			    printf(TARG "%s\"\n", $members[$i]);
		    }
		    else {
			    printf(TARG "%s\n", $members[$i]);
		    }

		    # Add the member name to the name hash
		    $names{$members[$i]} = 1;
	    }
    }

    # Output the number of members using this frequency
    s/X[1-6][0-9]X/$n/;
    printf(TARG "%s\n", $_);

}

# Output the number of members following the "list table" line in the html
elsif (/updatefreq\.cgi\?list/) {
    local ($n, @a);
    printf(TARG "%s\n", $_);
    @a = keys %names;
    $n = $#a + 1;
    printf(TARG "<BR><DIV ALIGN=\"center\">");
    printf(TARG "There are %d club members represented in this table</DIV>\n", $n);
}

else {
    printf(TARG "%s\n", $_);
}
}

close(SRC);
close(TARG);

# Copy the  temporary file to its final destination
copy($BAKFILE, $target);
return(1);
} #buildNewHtml



#############################################################################
##
#   submit -- Stores the data hash table to the data base and  merges it
#             with the boilerplate HTML file to create a new freq.html file
#
#             This routine is atomic and uses a lock file as a semaphore.
#
#        Call with: submit(*db, $FREQDB, $BOILERHTML, $FREQHTML)
#
#	 This routine will exit if an error occurs at any time during the
#        process.
##
#############################################################################
sub submit
{
local (*db, $dbfile, $boilerfile, $freqfile) = @_;

# Obtain a semaphore before attempting to perform the update
# This process will wait a maximum of 30 seconds trying to obtain lock before giving up
if (! setlock()) {
    MIME_header("text/html", "ERROR");
    printf("<H1>ERROR: Cannot obtain exclusive use ('%s') of database.</H1></BODY></HTML>\n", $LOCK);
    printf("<BR><BR><BR><DIV align=\"center\"><A HREF=\"../freq.html\">Return</A></DIV>\n");
    exit(1);
}

# Store the updated database
if (! stordb(*db, $dbfile)) {
    MIME_header("text/html", "ERROR");
    printf("<H1>ERROR: Cannot store the database.</H1></BODY></HTML>\n");
    printf("<BR><BR><BR><DIV align=\"center\"><A HREF=\"../freq.html\">Return</A></DIV>\n");
    exit(1);
}

# Merge the database and boilerplate html to create an updated freq.html file
if (! buildNewHtml(*db, $boilerfile, $freqfile)) {
    MIME_header("text/html", "ERROR");
    printf("<H1>ERROR: Cannot merge database with web page.</H1></BODY></HTML>\n");
    printf("<BR><BR><BR><DIV align=\"center\"><A HREF=\"../freq.html\">Return</A></DIV>\n");
    exit(1);
}

# Release the semaphore
rellock();
} #submit




#############################################################################
##
#   parse_form_data -- Builds hash table (%FORM_DATA) from POST input
#	Call with: parse_form_data(*FORM)
##
#############################################################################
sub parse_form_data
{
local (*FORM_DATA) = @_;

local ( $request_method, $post_info, @key_value_pairs,
$key_value, $key, $value);

read (STDIN, $post_info, $ENV{'CONTENT_LENGTH'});

@key_value_pairs = split (/&/, $post_info);

foreach $key_value (@key_value_pairs) {
($key, $value) = split (/=/, $key_value);
$value =~ tr/+/ /;
$value =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg;

if (defined($FORM_DATA{$key})) {
    $FORM_DATA{$key} = join (" ", $FORM_DATA{$key}, $value);
}else {
    $FORM_DATA{$key} = $value;
}
} #foreach
} #parse_form_data



#############################################################################
##
#   Chomp -- deletes all whitespace from the end of a string
#	Call with: Chomp($string)
#	Returns resulting string
##
#############################################################################
sub Chomp
{
local ($string) = @_;

$string =~ s/\s+$//;
return $string;
} #Chomp



#############################################################################
##
# setlock --  Sets semaphore
##
#############################################################################
sub setlock
{
local ($i);

for ($i=10; $i > 0; $i--) {
if (open(SEM, "> $LOCK")) {
    flock(SEM, 2);
    return(1);
}
sleep(3);
}
return(0);
} #setlock


#############################################################################
##
#  rellock -- Releases semaphore
##
#############################################################################
sub rellock
{
flock(SEM, 8);
close(SEM);
} #rellock


#############################################################################
##
#   list -- Lists the contents of the database in an HTML document.
##
#############################################################################
sub list
{
  if (! open(DB, "< $FREQDB")) {
  	MIME_header("text/html", "ERROR");
  	printf("<H1>ERROR: Cannot load the database.</H1></BODY background=\"logoa.gif\"></HTML>\n");
  	printf("<BR><BR><BR><DIV align=\"center\"><A HREF=\"../freq.html\">Return</A></DIV>\n");
  	exit(1);
  }

  MIME_header("text/html", "Frequency Data Base Contents");
  printf("<DIV align=\"center\"><H1>Contents of the Frequency Data Base</H1></DIV>\n");
  while(<DB>) {
  	printf("%s<BR>\n", $_);
  }
  close(DB);
  printf("<BR><DIV align=\"center\"><A HREF=\"../freq.html\">Return</A></DIV>\n");
} #list



#############################################################################
##
#   update -- Re-merges the database and boilerplate to create a new html.
##
#############################################################################
sub update
{
  local (%db);

  # Load the database
  if (! loaddb(*db, $FREQDB)) {
  	MIME_header("text/html", "ERROR");
  	printf("<H1>ERROR: Cannot load the database.</H1></BODY background=\"logoa.gif\"></HTML>\n");
  	printf("<BR><BR><BR><DIV align=\"center\"><A HREF=\"../freq.html\">Return</A></DIV>\n");
  	exit(1);
  }
  
  # Merge the database and boilerplate html to create an updated freq.html file
  if (! buildNewHtml(*db, $BOILERHTML, $FREQHTML)) {
	MIME_header("text/html", "ERROR");
	printf("<H1>ERROR: Cannot merge database with web page.</H1></BODY></HTML>\n");
	printf("<BR><BR><BR><DIV align=\"center\"><A HREF=\"../freq.html\">Return</A></DIV>\n");
	exit(1);
  }

  MIME_header("text/html", "Updated");
  printf("<DIV align=\"center\"><H1>%s has been updated</H1></DIV>\n", $FREQHTML);
  printf("<DIV align=\"center\"><A HREF=\"../freq.html\">Return</A></DIV>\n");
  exit(0);
} #update


#############################################################################
##
#   config -- Called via GET and is used to configure the updatefreq
#             environment.  Sets the permissions on the files, creates
#             a new database, and builds a new freq.html.
##
#############################################################################
sub config
{
  local (%db);
  
  creatdb($FREQDB);
  
  # Load the database
  if (! loaddb(*db, $FREQDB)) {
  	MIME_header("text/html", "ERROR");
  	printf("<H1>ERROR: Cannot load the database.</H1></BODY background=\"logoa.gif\"></HTML>\n");
  	printf("<BR><BR><BR><DIV align=\"center\"><A HREF=\"../freq.html\">Return</A></DIV>\n");
  	exit(1);
  }
  
  # Store the updated database and merge the database and boilerplate html to create an updated freq.html file
  submit(*db, $FREQDB, $BOILERHTML, $FREQHTML);

  MIME_header("text/html", "updatefreq utility configuration");
  printf("<H3>CGI script pathname: '%s'</H3>\n", $ENV{'SCRIPT_NAME'});
  printf("<H3>updatefreq web page: '%s'</H3>\n", $FREQHTML);
  printf("<H3>New database: '%s'</H3>\n", $FREQDB);
  printf("<H3>HTML boilerplate file: '%s'</H3>\n", $BOILERHTML);
  printf("<H3>Temporary HTML filename: '%s'</H3>\n", $BAKFILE);
  printf("<H3>Lock file: '%s'</H3>\n", $LOCK);
  printf("<BR><BR><BR><DIV align=\"center\"><A HREF=\"../freq.html\">Return</A></DIV>\n");
  exit(0);
} #config

