Performace of a Web chat application can be based upon a few measurements. The speed at which pages load for the user is a measurement of performance. How fast or slow the transcript refreshes itself is something users are keenly aware of and the performace of a Web chat environment is sometimes based upon this measurement. Another way to look at performace is the number of simultaneous users the Web chat environment can sustain before noticeable performace degradation. This is measured mostly by feel: Does the Web chat appear to be running slower when more people join the discussion, or does the change in number of users alter performance any appreciable amount?
These two performance issues are derived from the limitations you have seen with the previous Web chat applications. Beyond those performace issues are still more abstract qualities of the Web chat environment that must be examined as performance tradeoffs. For example, the added features supported by the Web chat application to help you manage the content of the dialog is something to consider. Does the Web chat application provide a means to edit or delete messages as necessary? This is a different kind of performance tradeoff compared to the somewhat numerical performace mesurement of speed and capacity.
To confront these performace issues, you learn about a new Web chat application called SuperChat. SuperChat, unlike the previous Web chat applications handles many more users without serious loss of performance. It also adds to your collection of tools for administrating the Web chat environment. SuperChat is built to allow messages to be manipulated after they have been posted, giving you tighter control of the chat dialog.
Up until now, we've explored the use of Web chat as a real-time application for users to communicate on the Web. We've shown how to implement rudimentary chat applications using Perl in the UNIX environment.
Our examples and applications have one thing in common: They all use flat files to store the messages. As a result, bad things can happen when using flat files. The main issue is controlling access to the file. The CGI scripts that manipulate the flat files storing the messages need to have "atomic" access to the transcript file.
The operations to read and write to the transcript files in the Web chat applications presented so far are not atomic. In other words, the operations to read and write to the transcript files could be preempted by other users joining the Web chat environment. Load averages and basic usage limitations make the use of flat files unreliable in large, high-volume Web chat environments.
In contrast, there are major steps we can take to alleviate these problems. These improvements are based on how we decide to store the messages. We need a method that allows easy access to the messages, that is relatively fast for getting the messages up on the Web page, and that is less prone to mix-ups when the transcript log needs to be updated. We need a better solution than flat files.
A simple answer is a new way to store the messages. If we store all messages in memory and not in a flat file (on disk), then we can address each of our concerns about reliability, speed, and standardization for updating.
Of course, the use of memory instead of flat files introduces a new set of requirements. But by examining each component of the memory usage model, we hope to clearly define what the requirements are. We'll show how to apply these techniques for a new kind of Web chat environment, one we call SuperChat.
SuperChat is a Web chat application with three main components. First, there is the CGI script that handles data from the login form and the transcript page. The login form and transcript page contain HTML forms for gathering and passing data (see Fig. 15.1).
Our implementation of SuperChat uses the same data variables as the other Web chat environments (see Fig. 15.2). In fact, the login page and transcript pages themselves are almost identical in structure. What is unique about SuperChat, though, is the method used to generate those pages.
The second component of SuperChat is a new program called the Chat Server. Since we are going to store all messages in memory, we need to construct a new program that can run as a stand-alone process on the Web server.
The Chat Server program creates a highly organized data structure for the insertion and deletion of messages. Think of it as a turbo-charged filing cabinet (see Fig. 15.3). The Chat Server accepts requests to perform certain tasks and the Web server responds by accepting new data or giving the output of all the current messages.
The third component of SuperChat is another new program called the Chat Caller. We want to isolate the function of storing and retrieving the messages within two separate programs so they can each be highly optimized for handling requests. The Chat Caller is the program designed to communicate with the Chat Server.
We are trying to do several things with this three component model. The system of CGI, Chat Server, and Chat Caller is grounded in a client-server model (see Fig. 15.4).
There are three components to SuperChat:
The CGI script does all the site-specific work. It is the interface builder and HTML- generation program. The CGI script doesn't include code specifying how the data is actually stored. The CGI script "knows" how to interpret the messages stored and how to create interfaces for the stages in the chat cycle.
The Chat Caller program is the main client in this client-server model. When requested by the CGI program, the Chat Caller relays commands to the Chat Server. The Chat Caller knows how to reformat data and commands from the CGI program in such a way the Chat Server can process them very quickly.
The Chat Caller is also equipped with the programming to communicate with the Chat Server over the Internet. The Chat Caller and Chat Server interact using TCP/IP socket calls. This means that as with any true client-server application, the Chat Server and Chat Caller can exist on any two machines on the Internet (or on the same machine-even the same machine as the Web server itself). We will find that the client-server model exists at several levels within the Web.
From a higher level, the relationship between the CGI script, the Chat Server, and Chat Caller is that of a client-server model. We use the name "Chat Caller," not "Chat Client," however, so that we don't confuse Chat Caller with the client software owned by the user. The Chat Caller isn't the Web browser: it's a special program installed by the Webmaster on the Web server that is part of this SuperChat application (see Fig. 15.5).
Figure 15.5: The various pieces to the puzzle. From user to Web Server, how it all works together.
SuperChat is a system of programs working together to maintain a very reliable and fast Web chat experience for the user.
To make good on our description of SuperChat, we choose to write the Chat Server and Chat Caller programs in C. These C programs run faster than a script language (like Perl). The CGI script is written in Perl for our application so that once it's installed, the Webmaster can easily make minor changes by the Webmaster.
The Chat Server is a C program that performs several tasks:
Messages are stored in memory. Each message is in a node in a linked list of nodes maintained by the Chat Server. Each node is a structure defined in the Chat Server program. There is a one-to-one association between a node and a message.
Each node stores the name of the user who posted the message, the message itself, the time of day it was posted, the number of times that message has been posted (discussed later), and a pointer to another node structure (see Fig. 15.6). The linked list of nodes begins with the first message posted.
Figure 15.6: How each node in memory is linked together, a simple linked list.
Memory allocation for all the nodes is handled by the Chat Server. When a new message is sent and processed by the Chat Server, the Chat Server allocates memory for the node and inserts the node on the end of the list, adjusting the pointers accordingly.
At some point, nodes (messages) may be too old compared to the current chat session. If the Web chat session is left to run for a long time (longer than 24 hours), then the very early messages may need to be cleaned out so the content is fresh. When we use flat files (as we saw earlier), the Webmaster could go in by hand and edit out the old messages.
Webmasters could also create their own tools to automate the process of cleaning the message log. With the SuperChat application, you do this by using functions built into the Chat Server rather than manually editing files.
The advantage of SuperChat over the other Web chat applications so far is the separation of message storage from the slow file-access mechanisms of PlainChat and PushedChat. To get this level of isolation, you can run the SuperChat application Chat Server program on any machine connected via the network to the Web server. The Chat Caller program must reside on the same machine as the Web server.
It's unusual for a Web server itself to be distributed over many machines. Only if you are expecting millions of hits per day would you want to consider splitting up the Web server system over many machines. The Chat Server portion of the SuperChat application is generic enough so that it can run both on the same machine as the Web server or, if needed, on a separate machine connected via the network.
The Chat Caller program communicates with the Chat Server using
very basic socket operations. The interface between the Chat Server
and Chat Caller is much like the
interaction with a Network News Transfer Protocol (NNTP) server:
The design of the SuperChat server and caller programs is based
on the model of an NNTP server.
The NNTP server waits for connections to a specific port (119, actually) and then accepts several kinds of commands. For instance, one command issued by some news-reader applications is List. A news reader needs to know the list of all the newsgroups to build the interface for the user. Other commands used by NNTP servers are Post, Newsgroup, and Quit.
The similarity of NNTP servers to the Chat Server lies here. The Chat Server accepts a few commands: transcript, update, time, and clean. These commands are transmitted over the network by the Chat Caller. The Chat Caller is responsible for organizing the data to be sent to the Chat Server and for formatting the requests in a uniform style.
The design of the relationship between the Chat Server and Chat Caller is similar to the NNTP model in other areas. For example, for any given Web chat environment, there is only one Chat Server; and there is only one process running to store the messages in memory and handle commands.
On the other hand, there may be many Chat Caller processes running, all trying to communicate with the Chat Server. The SuperChat application is still prone to message mix-up if the level of communication between a set of Chat Callers overwhelms the Chat Server. But the window of communication is only open for a short period of time. Thus, we're taking advantage of the speed of network communication to offset the possibility of messages getting mixed up or dropped by the Chat Server.
The rate of data transfer between a process and the disk, and the rate of data transfer between two processes through the network are very far apart. As a result, we can be relatively sure that the process-to-process communication is handled quickly enough to avoid the access problems of a chat application based on a flat file.
The process-to-process communication we use in the Chat Server and Chat Caller is common to Internet applications. Interprocess communication (IPC) is discussed in several books on network programming so there is plenty of additional material with examples of how to implement IPC.
Elsewhere in the Chat Server and Chat Caller programs are functions for handling memory management and for recognition of requests made to the Chat Server. Utility functions for dumping the transcript log back to the Chat Caller, string operations, and formatting operations are all listed as separate functions built into the Chat Server and Chat Caller programs.
Now that we have an understanding of what kind of model it is based on, we can look at the Chat Server program in terms of what commands it accepts. We choose to make the Chat Server program much like an NNTP server program. When the Chat Server is invoked, it performs the steps necessary to setup a network connection with potential callers and waits.
When a connection is made, data pours into the Chat Server. The Chat Server is expecting only a few types of requests from the calling program. These requests range from commands that instruct the Chat Server to return the transcript in its entirety, to commands that instruct the Chat Server to update or erase a particular message based on some key information. This could be a user's name or the time stamp given the message when it is posted.
Commands the Chat Server accepts that need no arguments are as follows:
Commands the Chat Server accepts that need arguments are as follows:
Some of the commands accepted by the Chat Server don't need information other than the command itself. These commands are instructions that inform the Chat Server to do something and either generate output or perform tasks internal to the Chat Server.
The transcript command tells the Chat Server to collect all the messages in memory and dump the data back to the caller. The Chat Server does not format the data in HTML; it's sent as raw data to the caller (in a stream of bytes). The caller reads the data until there is no more left.
The actual format of the data sent out is much like the other chat applications we've seen. There is a delimiter for each message, a time stamp, the username, and the body of the message.
The Chat Server doesn't do any processing on the data for each message. All the data is considered correct as it was input. For example, the time stamp for each message is stored as a long integer representing the time of day in seconds since January 1, 1970 (the standard clock count on UNIX machines).
As this is written, today's current date and time is 824499999. It doesn't look like a familiar version of the date and time. If someone asked you for the time at the bus stop, you wouldn't respond with "831200322." But to make this application generic enough to work with any formatting requirements, we need the time stored in this fashion.
When the date and time are stored this way, though, the program that ultimately displays the time stamp can reformat it for any situation. We can display the date and time as February 16, 1996, 11:54 a.m. or as 2/16/96, 11:45:00 a.m., and so on. The function available in Perl to pick apart the time of day into its components, localtime(), takes one argument: the time of day in seconds.
Following the time stamp is the user's name and then another delimiting character (like a new line).
So far our messages streaming out of the Chat Server look like this:
##time-of-day## Username::
We obfuscate the time of day and username with a series of delimiting characters so that it's still readable while the software is debugged. We could have packed the data into a binary structure but while the program is being tested, it is nice to have a textual version of the data. We'll explore data compression later in the chapter when we discuss pointers to modifying the application.
After the time stamp and username data, we end the message unit with the body of the message. The body of the message is the lines of data stored by the Chat Server when the message is first passed to the Chat Server. The stream of transcript coming out of the Chat Server is a repetition of this structure: time stamp, username, and body.
The status command is invoked by the Chat Caller for checking the pulse of the Chat Server. The key data that is considered status is the number of unique users who have posted messages, the number of messages, the amount of time the chat environment has been up, and the frequency of new messages being added to the transcript.
It's a lot easier to get this information quickly with SuperChat than with other types of chat applications. If we had to determine these figures using a flat file system, for example, we would have to store the data in the same way we store the transcript log file. The system would function just as the SuperChat application, but the flat-file-based applications would run into problems as soon as the chat environment became busy with users.
The clean command instructs the Chat Server to completely forget all the messages stored so far. All nodes are deallocated and all messages are erased completely from memory as if they never existed.
This command is useful if Webmasters need access to the Chat Server to purge messages without actually shutting down the Chat Server. For example, a complete record of the messages can be dumped with the transcript command and archived. If Webmasters are cleaning up their Web chat environments, they would first dump all of the content with a transcript command and then clean out the messages with clean.
The clean command generates no output. But it does internally call the status command just before all the messages are erased so the Webmaster has an accounting record of what exactly was erased.
There are some commands accepted by the Chat Server that do need arguments. These are commands that require additional information, which tells the Chat Server what to do with the specific request. For example, the update command needs to know which article to update. That additional information about "which article" is the argument required by the update command.
Commands that need arguments accept arguments the same way the commands themselves are accepted. When the Chat Server reads data from the Chat Caller, the command is picked up first. The Chat Server is programmed to read the first line of data from the Chat Caller to be the command to use. Arguments (if necessary) will follow and the Chat Server will already have "switched" into a mode to accept those additional arguments. The switching is just the C code and if a certain string is recognized, then it will go into a special conditional where additional lines are read in and assumed to be the additional arguments specifically needed by that command.
The update command is a useful command for maintaining the messages stored by the Chat Server. There are instances where Webmasters might need to edit the content of a Web chat environment. Web chat environments are popular attractions to Web sites and they attract all kinds of opinions and statements. It might be policy for a particular site to limit discussion to the topic at hand or to filter certain vocabulary from messages so that no one participating is offended.
This is a sticky issue with Web sites because of the freedom-of-speech issues colliding with the responsibilities and rights of site ownership. Our SuperChat application doesn't take any stand on the issue of censorship within a chat environment. We'll leave that for the Webmaster of the site to worry about. But the ability to perform sensible edits to individual messages is part of the Chat Server program.
The time stamp granularity of each message is a second. Each user in the chat environment is probably going to keep the same name throughout the session. So two pieces of data that almost surely identify a particular message are the time stamp and the name of the user who posted the message.
It is entirely possible that several messages are added in just one second. But they probably wouldn't come from the same person. If they did, that might introduce some problems maintaining the integrity of the message transcript.
On the other hand, if someone is posting new messages more than one per second, when are they finding the time to read anything else posted? But we're not going to be concerned about that extreme situation. Messages are uniquely identified by both a time stamp and username.
We haven't yet discussed the interface to updating messages that is actually a function of a CGI script and an HTML form. This interface is not one of the three-part group of programs: Chat Server, Chat Caller, and CGI Web chat interface.
The functionality of updating messages is explored later in the chapter when we talk about ways of improving the SuperChat application. How the update command works is that the Chat Caller sends the update command to the Chat Server, and also sends the time stamp and username of the message to update. Following that is the new body to replace the existing body for the message.
Still, before we move on to the other commands accepted by the Chat Server program, we should note that the update command does not mimic the clean command. clean, as you recall, erases all messages currently stored by the Chat Server, whereas update changes only the content of the messages stored by the Chat Server and otherwise leaves them intact.
The update command could potentially erase the body of the message but the message would still exist in the linked list of messages. The Chat Caller program and additional interface tools discussed later in the chapter are written to avoid nullifying the body of messages. The update command doesn't return any output.
A middle ground between clean and update is the delete command. What if we need to erase just one message from the transcript? We don't want to use update to nullify the message since it would appear in the transcript page as something said even though nothing was said. And we don't want to use clean because that would destroy all the other messages. The delete command, however, removes only one message from the list of messages-as if it never existed.
The unique identifier of each message, as we explained above, is the time stamp and the username. The same "key" is used to identify which message to delete. How this works is that the Chat Caller tells the Chat Server to delete a message, and provide the time stamp and username data. This way the Chat Server can find the offending message and erase it. The delete command doesn't return any output.
The time command is only concerned with setting the time-to-live for all the messages. Built into the Chat Server is a value that determines when messages have expired. Webmasters installing the SuperChat application can set this value to any length of time they wish. The value is stored in seconds so one full day is 86,400 seconds, 5 minutes is 300, and so on.
Webmasters should judge the expected hits and volume on the chat environment and set the time-to-live at a value reasonable for the environment. But on occasion, they might have to reset that value.
For example, if the volume of messages is growing too fast and the transcript log is getting very long, it is hard to justify keeping all the older messages if they are so far down the page.
Let's say the original time-to-live value is 4 hours. Every message that is older than 4 hours is just erased (the same as if it was deleted). Then, suddenly, over the next few minutes, activity on the chat grows so fast that the transcript page now takes up more than 6 pages.
It might be of interest for people who are just joining the chat to read up on everything said so far and get a clear picture of what's going on. At the same time, those who are deep into the chat environment might be annoyed by the Webmaster's inability to maintain a "fast" chat.
To shorten the transcript log, the Webmaster just resets the time-to-live value for messages. So in one action, all messages older than the new time-to-live value of 1 hour are erased.
The time command is like the update command in that it is not a command a Chat Caller would send to the Chat Server. The Chat Caller, as you recall, is the go-between for the CGI script and the Chat Server program. A separate set of tools for the Webmaster includes both access to the update command and the time command.
The argument to the time command is just the new time-to-live value. The data returned by the Chat Server is the value of the old time-to-live value.
All these commands don't make the Chat Server equal to a sendmail or NNTP server in terms of complexity. Those server applications do much more processing and handle many more requests. But the six commands used by the Chat Server program offer a good basis for building other custom Web chat applications that need speed, reliable message storage, and reliable retrieval mechanisms.
The chat cycle from the user's point of view is almost the same no matter what kind of application is supporting the environment. The chat cycle for the user is the login page, then the transcript page.
We saw with the PushedChat application that there was an optional page: the read-only transcript page joined with the traditional read-write transcript page. These interfaces and the mechanisms enabled by them can remain intact even if you decide to prop them up under the swarm of network activity generated by SuperChat.
Before we go into the details of how SuperChat works, let's review the other chat environments. We'll do this with state diagrams and show how messages (both chat messages and data) are processed.
The notation used to describe the mechanics of the chat environments involves the user, the Web server, and arrows showing which way data is being sent.
The mechanics of PlainChat are seen in Figure 15.7.
Figure 15.7: The PlainChat sequence is very simple.
The mechanics of PushedChat are seen in Figure 15.8.
The mechanics of SuperChat are seen in Figure 15.9.
When the CGI script invokes the Chat Caller program, it goes into a new branch sequence of events. The CGI script constructs a command line to execute Chat Server host, port number, and request string (see Fig. 15.10).
The script that generates the HTML the user sees in a Web chat environment (supported by SuperChat) is generated by the CGI script in Listing 15.1.
Listing 15.1 SuperChat.cgi-The CGI Component of
the SuperChat Application
#!/usr/local/bin/perl @INC=('../lib', @INC); require 'web.pl'; %Form = &getStdin; &beginHTML("SuperChat","bgcolor=ffffff"); &process if $Form{'process'}; &display; exit; sub process { return unless length($Form{'body'})>0; $args = join(" ", 4004, "$ThisHost", "\"$Form{'name'}\""); open(PI, "| /export/home/jdw/bookweb/bin/caller $args"); print PI $Form{'body'}; close PI; } sub display { print "<html>\n", "<title>The SuperChat Transcript Page</title>\n", "<body bgcolor=ffffff>\n", "<form method=\"post\"\n", " action=\"/cgi-bin/SuperChat.cgi\">\n", "Welcome To SuperChat<br>\n", "Enter your name below:<br>\n", "<textarea name=\"body\" rows=5 cols=40>\n", "</textarea><br>\n", "<input type=\"hidden\" name=\"process\" value=1>\n", "<input type=\"hidden\" name=\"name\" ", "value=\"$Form{'name'}\">\n", "<input type=\"submit\" value=\"Transmit\">\n", "</form>\n", "<form method=\"post\"\n", " action=\"/cgi-bin/SuperChat.cgi\">\n", "<input type=\"hidden\" name=\"process\" value=1>\n", "<input type=\"hidden\" name=\"name\" ", "value=\"$Form{'name'}\">\n", "<input type=\"submit\" value=\"Refresh\">\n", "</form>\n"; &transcript; } sub transcript { local(@out, $i, $x, $tod, $tod_time, $tod_date); chop(@out='/export/home/jdw/bookweb/bin/caller 4004 $ThisHost transcript'); foreach $one (@out) { if ($one =~ /^\#\#(\d+)\#\#\d+ (.*)::/) { $tod = $1; $who = $2; $tod_date = &pretty_date($tod); $tod_time = &pretty_time($tod); print "<b>On $tod_date $tod_time</b> $who said:<br>\n"; } else { print "$one<br>\n"; } } } # mmddyy sub pretty_date { local($x) = $_[0]; local(@t) = localtime($x); return sprintf("%d/%d/%d", $t[4]+1,$t[3],$t[5]); } sub pretty_time { local($x) = $_[0]; local(@t) = localtime($x); return sprintf("%02d:%02d %s", $t[2]>12?$t[2]-12:$t[2],$t[1], $t[2]>12?"PM":"AM"); }
ModeratedChat (see Listing 15.2) is an implementation of the SuperChat application that allows a person to control the messages that appear in the transcript log. The idea behind ModeratedChat is that there are two places messages exist. Messages that have been approved exit on the transcript page we are used to seeing. As far as the user is concerned, there is only one transcript page.
The other place where messages exist is on a special transcript page that only the moderator can see. As new messages are posted, they appear on the moderator's transcript page and not the public transcript page.
The moderator's transcript page displays each message verbatim. Next to each message is a button to approve or cancel the candidate message.
Listing 15.2 ModeratedChat.cgi-The Hybrid of SuperChat
That Allows You to Monitor Activity of the Chat Environment
#!/usr/local/bin/perl @INC=('../lib', @INC); require 'web.pl'; $Port = 4005; %Form = &getStdin; &beginHTML("Moderated Chat", "bgcolor=ffffff"); &process if $Form{'process'}; &display; exit; sub process { return unless length($Form{'body'})>0; $args = join(" ", $Port, "$ThisHost", "\"$Form{'name'}\""); open(PI, "| /export/home/jdw/bookweb/bin/caller $args"); print PI $Form{'body'}; close PI; } sub display { print "<html>\n", "<title>Chapter 15, ModeratedChat</title>\n", "<body bgcolor=ffffff>\n", "<a href=\"/cgi-bin/moderate.cgi\">Link visible only for Moderator", " - click here to be Moderator</a><p>\n", "<form method=\"post\"\n", " action=\"/cgi-bin/ModeratedChat.cgi\">\n", "Enter message here:<br>\n", "<textarea name=\"body\" rows=5 cols=40>\n", "</textarea><br>\n", "<input type=\"hidden\" name=\"process\" value=1>\n", "<input type=\"hidden\" name=\"name\" ", "value=\"$Form{'name'}\">\n", "<input type=\"submit\" value=\"Transmit\">\n", "</form>\n", "<form method=\"post\"\n", " action=\"/cgi-bin/moderate.cgi\">\n", "<input type=\"hidden\" name=\"process\" value=1>\n", "<input type=\"hidden\" name=\"name\" ", "value=\"$Form{'name'}\">\n", "<input type=\"submit\" value=\"Refresh\">\n", "</form>\n"; &transcript; } sub transcript { local(@out, $i, $x, $tod, $tod_time, $tod_date); chop(@out='/export/home/jdw/bookweb/bin/caller $Port $ThisHost transcript'); foreach $one (@out) { if ($one =~ /^\#\#(\d+)\#\#\d+ (.*)::/) { $tod = $1; $who = $2; $tod_date = &pretty_date($tod); $tod_time = &pretty_time($tod); print "<b>On $tod_date $tod_time</b> $who said:<br>\n"; } else { print "$one<br>\n"; } } } # mmddyy sub pretty_date { local($x) = $_[0]; local(@t) = localtime($x); return sprintf("%02d/%02d/%02d", $t[4]+1,$t[3],$t[5]); } sub pretty_time { local($x) = $_[0]; local(@t) = localtime($x); return sprintf("%02d:%02d %s", $t[2]>12?$t[2]-12:$t[2],$t[1], $t[2]>12?"PM":"AM"); }
The ModeratedChat login page is like the one for SuperChat (see Fig. 15.11).
When the user enters the ModeratedChat environment, it doesn't appear that the environment is being monitored (see Fig. 15.12). The user has no idea that there is someone else watching over the chat dialog. For the person monitoring the moderated chat environment, there is a special page generated by the CGI script moderate.cgi in Listing 15.3.
Figure 15.12: The transcript page before a message gets transmitted.
Listing 15.3 moderate.cgi-Generates the Page That
the Moderator Uses to Monitor the Chat Environment
#!/usr/local/bin/perl @INC=('../lib', @INC); require 'web.pl'; $Port = 4005; %Form = &getStdin; &beginHTML("Chapter 15 Moderated Chat", "bgcolor=ffffff"); $Form{'name'} = "foobar" unless $Form{'name'}; ($ttl, $numes, @users) = &get_status; $ttl = sprintf("%.02f", $ttl / 3600); foreach (@users) { ($name, $when) = split(/ /); $Users{$name}++; } print "<b>Message Life:</b> $ttl hours<br>\n", "<b>Number</b> of total messages: $numes<p>\n", "List of <b>users</b> with messages:\n"; undef @users; foreach $person (sort keys %Users) { push(@users, ucfirst($person)); } print join(", ",@users),"<p>\n"; if ($Form{'change'}) { foreach $one (keys %Form) { if ($one =~ /^when_(\d+)/) { $ww = $1; &do_update($Form{"who_$ww"}, $ww, $Form{"what_$ww"}); } } } &display; exit; sub display { print "<a href=\"/cgi-bin/ModeratedChat.cgi\">ModeratedChat</a><p>\n", "<hr>\n"; &transcript; } sub get_status { local(@wg, $ttl, $nmes); chop(@wg ='/export/home/jdw/bookweb/bin/caller $Port misl status'); $ttl = shift @wg; $nmes = shift @wg; return ($ttl, $nmes, @wg); } sub transcript { local(@out, $i, $x, $tod, $tod_time, $tod_date); chop(@out='/export/home/jdw/bookweb/bin/caller $Port misl transcript'); print "<form method=post action=\"/cgi-bin/moderate.cgi\">\n"; foreach $one (@out) { if ($one =~ /^\#\#(\d+)\#\#\d+ (.*)::/) { $tod = $1; $who = $2; $tod_date = &pretty_date($tod); $tod_time = &pretty_time($tod); if ($gotone) { print "</textarea><p>\n"; $gotone=0; } print "<input type=hidden name=\"who_$tod\" value=\"$who\">\n"; print "<input type=hidden name=\"when_$tod\" value=\"$tod\">\n"; print "<b>On $tod_date $tod_time</b> $who said:<br>\n"; print "<textarea rows=5 cols=40 name=\"what_$tod\">\n"; } else { print "$one\n"; $gotone=1; } } if ($gotone) { print "</textarea><p>\n"; } print "<input type=submit name=\"change\" value=\"Make Changes\"><br>\n", "<input type=submit name=\"nochange\" value=\"No Changes\"><br>\n"; print "</form>\n"; } # mmddyy sub pretty_date { local($x) = $_[0]; local(@t) = localtime($x); return sprintf("%d/%d/%d", $t[4]+1,$t[3],$t[5]); } sub pretty_time { local($x) = $_[0]; local(@t) = localtime($x); return sprintf("%02d:%02d %s", $t[2]>12?$t[2]-12:$t[2],$t[1], $t[2]>12?"PM":"AM"); } sub do_update { local($who, $when, $what) = @_; $args = join(" ", $Port, "misl", "update"); open(PI, "| /export/home/jdw/bookweb/bin/caller $args"); print PI "$who\n", "$when\n", "$what\n"; close(PI); }
When the moderator wants to edit any content of the chat dialog, he can replace the text directly in the text areas generated for each message by moderate.cgi (see Fig. 15.13).
The result is that users don't get interrupted in their discussion, but the messages are edited to suit the guidelines of the moderator.