Thu Dec 17 20:38:00 EST 1998 Art Cancro <ajc@uncnsrd.mt-kisco.ny.us>
* Added the screens to send pages
* Changed message headers to display in bigger font, non-boldface
+ * Added the chat system
Wed Dec 16 16:23:58 EST 1998 Art Cancro <ajc@uncnsrd.mt-kisco.ny.us>
* Replace "Citadel/UX" in menu bar with the Citadel/UX logo
void showuser(void);
void display_page(void);
void page_user(void);
+void do_chat(void);
wprintf("</BODY></HTML>\n");
wDumpContent();
}
+
+
+
+/*
+ * multiuser chat
+ */
+void do_chat(void) {
+
+ printf("HTTP/1.0 200 OK\n");
+ output_headers(1);
+
+ wprintf("<TABLE WIDTH=100% BORDER=0 BGCOLOR=000077><TR><TD>");
+ wprintf("<FONT SIZE=+1 COLOR=\"FFFFFF\"");
+ wprintf("<B>Real-time chat</B>\n");
+ wprintf("</FONT></TD></TR></TABLE>\n");
+
+ wprintf("A chat window should be appearing on your screen ");
+ wprintf("momentarily. When you're ");
+ wprintf("done, type <TT>/quit</TT> to exit. You can also ");
+ wprintf("type <TT>/help</TT> for more commands.\n");
+
+ wprintf("<applet codebase=\"/static\" ");
+ wprintf("code=\"wcchat\" width=2 height=2>\n");
+ wprintf("<PARAM NAME=username VALUE=\"%s\">\n", wc_username);
+ wprintf("<PARAM NAME=password VALUE=\"%s\">\n", wc_password);
+ wprintf("<H2>Oops!</H2>Looks like your browser doesn't support Java, ");
+ wprintf("so you won't be able to access Chat. Sorry.\n");
+ wprintf("</applet>\n");
+ wprintf("</BODY></HTML>\n");
+ wDumpContent();
+ }
--- /dev/null
+all: MultiUserChat102.class wcCitUtil.class wcCitServer.class wcchat.class
+
+MultiUserChat102.class: MultiUserChat102.java
+ javac MultiUserChat102.java
+
+wcCitServer.class: wcCitServer.java
+ javac wcCitServer.java
+
+wcCitUtil.class: wcCitUtil.java
+ javac wcCitUtil.java
+
+wcchat.class: wcchat.java
+ javac wcchat.java
--- /dev/null
+/*
+ * MultiUserChat102.java
+ *
+ * Chat mode...
+ *
+ */
+
+import java.awt.*;
+import java.util.*;
+
+/*
+ * ReceiveChat implements a thread which listens on the server socket and
+ * display anything which comes across the wire in the Panel specified
+ * to the constructor.
+ */
+class ReceiveChat extends Thread {
+ Panel TheArea;
+ wcCitServer serv;
+ String boof;
+ String cUser;
+ String cText;
+ String ThisLine;
+ String LastLineUser;
+ String MyName;
+ StringTokenizer ST;
+ MultiUserChat102 ParentMUC;
+ Label[] Linez = new Label[25];
+ int a;
+
+ ReceiveChat(MultiUserChat102 muc, Panel t, wcCitServer s, String n) {
+ TheArea = t;
+ serv = s;
+ MyName = n;
+ ParentMUC = muc;
+ serv.AddClientThread(this);
+
+ TheArea.setLayout(new GridLayout(25,1));
+
+ for (a=0; a<25; ++a) {
+ Linez[a] = new Label(" ");
+ Linez[a].setBackground(Color.black);
+ Linez[a].setForeground(Color.black);
+ TheArea.add(Linez[a]);
+ }
+
+ TheArea.validate();
+
+ }
+
+ private void ScrollIt(String TheNewLine, Color NewLineColor) {
+ for (a=0; a<24; ++a) {
+ Linez[a].setText(Linez[a+1].getText());
+ Linez[a].setForeground(Linez[a+1].getForeground());
+ }
+ Linez[24].setText(TheNewLine);
+ Linez[24].setForeground(NewLineColor);
+ }
+
+
+ public void run() {
+ Color UserColor;
+ int a;
+
+ LastLineUser = " ";
+ while(true) {
+ boof = serv.ServGets();
+
+ if (boof.equals("000")) {
+ serv.ServPuts("QUIT");
+ ParentMUC.dispose();
+ stop();
+ destroy();
+ }
+
+
+ ST = new StringTokenizer(boof, "|");
+ if (ST.hasMoreTokens()) {
+ cUser = ST.nextToken();
+ }
+ else {
+ cUser = ":";
+ }
+ if (ST.hasMoreTokens()) {
+ cText = ST.nextToken();
+ }
+ else {
+ cText = " ";
+ }
+ if (!cText.startsWith("NOOP")) {
+ if (!LastLineUser.equals(cUser)) {
+ ScrollIt("", Color.black);
+ ThisLine = cUser + ": ";
+ }
+ else {
+ThisLine = " ".substring(0, cUser.length()+2);
+ }
+ ThisLine = ThisLine + cText;
+ UserColor = Color.green;
+ if (cUser.equals(":")) {
+ UserColor = Color.red;
+ }
+ if (cUser.equalsIgnoreCase(MyName)) {
+ UserColor = Color.yellow;
+ }
+ ScrollIt(ThisLine, UserColor);
+ LastLineUser = cUser;
+ }
+ }
+ }
+
+ }
+
+
+
+
+
+public class MultiUserChat102
+ extends Frame {
+
+wcCitServer serv;
+ReceiveChat MyReceive;
+Panel AllUsers;
+TextField SendBox;
+wcchat ParentApplet;
+
+
+MultiUserChat102(wcCitServer PrimaryServ, wcchat P) {
+ super ("Multiuser Chat");
+
+ String boof;
+
+ /* Set up a new server connection as a piggyback to the first. */
+ serv = PrimaryServ;
+ ParentApplet = P;
+
+ resize(600,400);
+ setLayout(new BorderLayout());
+
+ boof = "This is the buffer before the chat command.";
+ serv.ServPuts("CHAT");
+ boof = serv.ServGets();
+
+ if (boof.charAt(0) != '8') {
+ add("Center", new Label("ERROR: " + boof) );
+ show();
+ }
+
+ else {
+ DoChat(PrimaryServ.GetUserName());
+ }
+
+ }
+
+
+/*
+ * Do the actual chat stuff
+ */
+private void DoChat(String MyName) {
+ String boof;
+
+ SendBox = new TextField(80);
+
+ AllUsers = new Panel();
+
+ add("Center", AllUsers);
+ add("South", SendBox);
+ show();
+
+ MyReceive = new ReceiveChat(this, AllUsers, serv, MyName);
+ MyReceive.start();
+
+ SendBox.requestFocus();
+ }
+
+
+public boolean handleEvent(Event evt) {
+ int LastSpace;
+
+ if ( (evt.target == SendBox) && (evt.id == Event.ACTION_EVENT) ) {
+ serv.ServPuts(SendBox.getText());
+ SendBox.setText("");
+ }
+
+ else if (evt.target == SendBox) {
+ if ( SendBox.getText().length() + serv.GetUserName().length() > 78 ) {
+ LastSpace = SendBox.getText().lastIndexOf(' ');
+ if (LastSpace < 0) {
+ serv.ServPuts(SendBox.getText());
+ SendBox.setText("");
+ }
+ else {
+ serv.ServPuts(SendBox.getText().substring(0,LastSpace));
+ SendBox.setText(SendBox.getText().substring(LastSpace));
+ if (SendBox.getText().charAt(0) == ' ') {
+ SendBox.setText(SendBox.getText().substring(1));
+ }
+ }
+ }
+ }
+
+ return super.handleEvent(evt);
+ }
+
+
+}
--- /dev/null
+The program in this directory comprises a Java-based chat client to be used
+with the WebCit client for Citadel/UX. Most of the code was lifted from an
+all-Java client I'm currently writing, but since we expect this one to work
+from the most popular browsers, everything has been tweaked in such a way as
+to make it run happily in a JDK 1.0.2 environment.
+
+ Since not everyone has a Java compiler on hand, I've supplied the binaries
+as a courtesy. Since Java bytecode is universal, there should be no need to
+recompile.
--- /dev/null
+/*
+ * wcCitServer.java
+ *
+ * This module handles the tasks involving establishing a connection to a
+ * Citadel/UX server and moving data across the connection. It also handles
+ * server "keepalives", the dispatching of "express messages", the
+ * registration & termination of multiple threads, and a semaphore mechanism
+ * to prevent multiple threads from executing server commands at the same
+ * time.
+ */
+
+import java.net.*;
+import java.io.*;
+import java.util.*;
+
+/*
+ * We send 'keepalive' commands (actually just a NOOP) to the server every
+ * fifteen seconds, as a separate thread. This prevents the server session
+ * from timing out, and also allows the client to be notified of any incoming
+ * express messages if the user isn't doing anything.
+ */
+class wcKeepAlive extends Thread {
+
+ wcCitServer serv; /* Pointer to server connection class */
+ boolean FullKeepAlives; /* TRUE for full keepalives, FALSE for half keepalives */
+
+ wcKeepAlive() {
+ }
+
+ public void PointToServer(wcCitServer which_serv, boolean WhatKindOfKeepAlives) {
+ serv = which_serv;
+ serv.AddClientThread(this);
+ FullKeepAlives = WhatKindOfKeepAlives;
+ }
+
+ public void run() {
+ String buf;
+ while(true) {
+
+ /* Sleep for sixty seconds between keepalives. */
+ try {
+ sleep(60000);
+ }
+ catch (InterruptedException e) {
+ }
+ /* Full keepalives - send a NOOP, wait for a reply, then retrieve
+ * express messages if the server said there are any.
+ */
+ if (FullKeepAlives) {
+ buf = serv.ServTrans("NOOP") + " ";
+ }
+
+ /* Half keepalives - blindly send a NOOP and we're done. */
+ else {
+ serv.ServPuts("NOOP");
+ }
+
+ }
+ }
+
+ }
+
+
+
+/*
+ * the wcCitServer class handles communication with the server.
+ */
+public class wcCitServer {
+ int connected = 0;
+ DataOutputStream ofp;
+ DataInputStream ifp;
+ Socket sock;
+ String TransBuf;
+ wcKeepAlive ka;
+ boolean in_trans = false;
+ Vector ClientThreads = new Vector();
+ Vector ServerInfo = new Vector();
+ String PrimaryServerHost;
+ int PrimaryServerPort;
+ String PrimaryServerUser;
+ String PrimaryServerPassword;
+
+ public void SetTransBuf(String bufstring) {
+ TransBuf = bufstring;
+ }
+
+ public String GetTransBuf() {
+ return TransBuf;
+ }
+
+/* attach to the server */
+ private void BuildConnection(String ServerHost, int ServerPort, boolean WhichKA) {
+
+ String buf;
+
+ try {
+ sock = new Socket(ServerHost, ServerPort);
+ ofp = new
+ DataOutputStream(sock.getOutputStream());
+ ifp = new
+ DataInputStream(sock.getInputStream());
+ }
+ catch(UnknownHostException e) {
+ System.out.println(e);
+ }
+ catch(IOException e) {
+ System.out.println(e);
+ }
+
+ /* Connection established. At this point, this function
+ * has the server connection all to itself, so we can do
+ * whatever we want with it. */
+
+ /* Get the 'server ready' message */
+ buf = ServGets();
+
+ /* Identify the client software to the server */
+ ServTrans("IDEN 0|5|001|Cit/UX Java Client|");
+
+ /* Download various information about the server */
+ SetTransBuf("");
+ buf = ServTrans("INFO");
+ StringTokenizer InfoST = new StringTokenizer(TransBuf,"\n");
+ while (InfoST.hasMoreTokens()) {
+ ServerInfo.addElement(InfoST.nextToken());
+ }
+
+
+ /* At this point, all server accesses must cooperate with
+ * server accesses from other threads by using the BeginTrans
+ * and EndTrans semaphore mechanism.
+ */
+ ka = new wcKeepAlive();
+ ka.PointToServer(this, WhichKA);
+ ka.start();
+
+ }
+
+
+/*
+ * Attach to server command for the primary socket
+ */
+ public void AttachToServer(String ServerHost, int ServerPort) {
+
+ /* Connect to the server */
+/* NOTE ... we've changed the primary connection keepalives to HALF because we're using the
+ * primary connection to jump right into a chat window.
+ */
+ BuildConnection(ServerHost, ServerPort, false);
+
+ /* And remember where we connected to, in case we have to
+ * build any piggyback connections later on.
+ */
+ PrimaryServerHost = ServerHost;
+ PrimaryServerPort = ServerPort;
+ }
+
+
+/*
+ * Learn info about the primary connection for setting up piggybacks
+ */
+ public String GetServerHost() {
+ return PrimaryServerHost;
+ }
+
+ public int GetServerPort() {
+ return PrimaryServerPort;
+ }
+
+
+/*
+ * Set up a piggyback connection
+ */
+ public void Piggyback(wcCitServer PrimaryServ, boolean KeepAliveType) {
+ PrimaryServerHost = PrimaryServ.GetServerHost();
+ PrimaryServerPort = PrimaryServ.GetServerPort();
+ PrimaryServerUser = PrimaryServ.GetUserName();
+ PrimaryServerPassword = PrimaryServ.GetPassword();
+ BuildConnection(PrimaryServerHost, PrimaryServerPort,
+ KeepAliveType);
+
+ }
+
+
+
+/* return info about the site we're currently connected to */
+ public String ServInfo(int index) {
+ String retbuf;
+ if (index >= ServerInfo.size()) {
+ return("");
+ }
+ else {
+ retbuf = ServerInfo.elementAt(index).toString();
+ return(retbuf);
+ }
+ }
+
+
+/* read a line from the server */
+ public String ServGets() {
+
+ String buf = "";
+
+ try {
+ buf = ifp.readLine();
+ }
+ catch(IOException e) {
+ System.out.println(e);
+ }
+ return buf;
+ }
+
+
+/* write a line to the server */
+ public void ServPuts(String buf) {
+
+ try {
+ ofp.writeBytes(buf + "\n");
+ }
+ catch(IOException e) {
+ System.out.println(e);
+ }
+ }
+
+
+/* lock the server connection so other threads don't screw us up */
+ public synchronized void BeginTrans() {
+ while(in_trans) {
+ try {
+ System.out.println("-sleeping-"); /* trace */
+ Thread.sleep(100);
+ }
+ catch (InterruptedException e) {
+ }
+ }
+
+ in_trans = true;
+ }
+
+/* release the lock */
+ public void EndTrans() {
+ in_trans = false;
+ }
+
+
+/* perform an autonomous server transaction */
+ public String ServTrans(String ServCmd) {
+ String buf;
+ BeginTrans();
+ buf = DataTrans(ServCmd);
+ EndTrans();
+ return buf;
+ }
+
+/* perform a non-autonomous server transaction */
+ public String DataTrans(String ServCmd) {
+ String buf = "";
+ String inbuf = "";
+ String expbuf = "";
+
+ /* perform the transaction */
+
+ System.out.println(">"+ServCmd); /* trace */
+ ServPuts(ServCmd);
+ buf = ServGets();
+ System.out.println("<"+buf); /* trace */
+
+ try {
+ if (buf.startsWith("4")) {
+ ofp.writeBytes(TransBuf);
+ if (!TransBuf.endsWith("\n")) {
+ ofp.writeBytes("\n");
+ }
+ ofp.writeBytes("000");
+ TransBuf = "";
+ }
+
+ if (buf.startsWith("1")) {
+ TransBuf = "";
+ inbuf = "";
+ do {
+ inbuf = ServGets();
+ if (!TransBuf.equals("")) {
+ TransBuf = TransBuf + "\n";
+ }
+ if (!inbuf.equals("000")) {
+ TransBuf = TransBuf + inbuf;
+ }
+ } while (!inbuf.equals("000"));
+ }
+ }
+ catch(IOException e) {
+ System.out.println(e);
+ }
+
+ return(buf);
+ }
+
+ public void AddClientThread(Thread ct) {
+ ClientThreads.addElement(ct);
+ System.out.println("--new thread registered--");
+ }
+
+ public void RemoveClientThread(Thread ct) {
+ ClientThreads.removeElement(ct);
+ System.out.println("--thread removed--");
+ }
+
+
+
+ /* The following four functions take care of keeping track of the
+ * user name and password being used on the primary server connection
+ * in case we want to use them for a piggyback connection.
+ */
+ public void SetUserName(String U) {
+ PrimaryServerUser = U;
+ }
+
+ public void SetPassword(String P) {
+ PrimaryServerPassword = P;
+ }
+
+ public String GetUserName() {
+ return PrimaryServerUser;
+ }
+
+ public String GetPassword() {
+ return PrimaryServerPassword;
+ }
+
+
+
+ }
+
+
--- /dev/null
+/*
+ * wcCitUtil.java
+ *
+ * Miscellaneous functionality...
+ */
+
+import java.util.*;
+
+
+public class wcCitUtil extends Object {
+
+public static String Extract(String SourceString, int ParmNum) {
+ StringTokenizer toks;
+ String buf;
+ int pos;
+
+ pos = 0;
+ toks = new StringTokenizer(SourceString, "|");
+ while (toks.hasMoreTokens()) {
+ buf = toks.nextToken();
+ if (pos == ParmNum) return buf;
+ ++pos;
+ }
+ return "";
+ }
+
+}
--- /dev/null
+/*
+ * Java client for Citadel/UX
+ * Copyright (C) 1997 by Art Cancro - All Rights Reserved
+ *
+ * This module is designed to be able to run either as an application or
+ * as an applet.
+ */
+
+import java.applet.*;
+
+public class wcchat extends Applet {
+
+String ServerHost = "uncnsrd.mt-kisco.ny.us";
+int ServerPort = 504;
+
+ public void init() {
+
+ /* Unless overridden, the Citadel server is expected to be
+ * the same as the applet host. In most cases this is all
+ * that's allowed anyway.
+ */
+ if (getDocumentBase() != null) {
+ ServerHost = getDocumentBase().getHost();
+ }
+
+ /* The 'host' parameter tells the client to look somewhere other
+ * than the applet host for the Citadel server.
+ */
+ if (getParameter("host") != null) {
+ ServerHost = getParameter("host");
+ }
+
+ /* The 'port' parameter tells the client to look on a
+ * nonstandard port for the Citadel server.
+ */
+ if (getParameter("port") != null) {
+ ServerPort = Integer.parseInt(getParameter("port"));
+ }
+
+ }
+
+ public void start() {
+ wcCitServer serv = new wcCitServer();
+ String buf = null;
+
+ serv.AttachToServer(ServerHost, ServerPort);
+ buf = serv.ServTrans("USER " + getParameter("username"));
+ if (buf.charAt(0) == '3') {
+ buf = serv.ServTrans("PASS "+getParameter("password"));
+ if (buf.charAt(0) == '2') {
+ serv.SetUserName(wcCitUtil.Extract(buf.substring(4), 0));
+ new MultiUserChat102(serv, this);
+ }
+ }
+ else {
+ System.out.println("ooops...");
+ }
+ }
+
+}
page_user();
}
+ else if (!strcasecmp(action, "chat")) {
+ do_chat();
+ }
+
/* When all else fails... */
else {
printf("HTTP/1.0 200 OK\n");