This is GopherClient.m in view mode; [Download] [Up]
#import "GopherClient.h"
#import "StuartSpeaker.h"
#import "GopherDispatcher.h"
#import <appkit/Listener.h>
#import <appkit/Speaker.h>
#import "PhSpeaker.h"
#include <stdio.h>
// Declared in GopherDispatcher.m
extern char defaultHost[256];
extern int defaultPort;
static int isIndexedText = NO;
static char lastIndexWord[256];
@implementation GopherClient
///////////////////////////////////////////////////
//
- (char *)currentCellName
{
id CurrentCell;
CurrentCell = [[MainScroller matrixInColumn:[MainScroller selectedColumn]]
selectedCell];
return (char *)[CurrentCell stringValue];
}
//////////////////////////////////////////////////
// Compute a new location for a window.
static void newLocation(NXPoint *p)
{
static count = 0;
p->x += (20.0 * count);
p->y -= (25.0 * count);
count = (count > 10)? 0 : count+1;
}
//////////////////////////////////////////////////
// Automatically called by the system when an instance is created.
- init
{
NXRect theFrame;
[super init];
strncpy(currenthostname, defaultHost, 250);
strcpy(currentfilename, "");
currentport = defaultPort;
[NXApp loadNibSection:"GopherClient.nib" owner:self];
[MainScroller setCellClass:[GopherBrowserCell class]];
[MainScroller setDelegate:self];
[MainScroller useScrollButtons:YES];
[MainScroller loadColumnZero];
//[myWindow setDelegate:self];
[myWindow getFrame:&theFrame];
newLocation(&theFrame.origin);
[myWindow moveTo:theFrame.origin.x :theFrame.origin.y];
[myWindow makeKeyAndOrderFront:nil];
[[MainText docView] setAutodisplay:YES];
theText = (char *)malloc(MAXTEXTLENGTH);
return self;
}
/////////////////////////////////////////////////
// Close the window.
- close
{
[myWindow performClose:self];
[self clean];
[myDispatcher oldFrontWindow:self]; // Theoretically unnecessary
return self;
}
/////////////////////////////////////////////////
// Clean things up.
- clean
{
[ItemInfoPanel performClose:self];
return self;
}
/////////////////////////////////////////////////
// Target method for the browser.
- cellClicked:sender
{
id CurrentCell;
int remotePort;
ThreadArgument *theArg;
extern int defaultApp;
char *remotePath;
// Stuff borrowed from Rex Pruess's Remotes program
id stuart;
int code, ind;
char *command;
char *comArgs[10];
int pid;
char portStr[16];
char loginStr[256];
char errMsg[256];
extern char **environ; /* User environment */
#ifdef DEBUG
char **tempPtr;
#endif
// Stuff for Rex Pruess's Ph CSO Nameserver client
int flag;
int msgDelivered;
char server[128];
char site[128];
CurrentCell = [[sender matrixInColumn:[sender selectedColumn]] selectedCell];
[ItemInfoHostName setStringValue:[CurrentCell remoteHost]];
[ItemInfoFileName setStringValue:[CurrentCell remoteName]];
[ItemInfoPortNumber setIntValue:[CurrentCell remotePort]];
switch ([CurrentCell cellType]) {
case '0' : // Document : retrieve and display
strncpy(currenthostname, [CurrentCell remoteHost], 255);
strcpy(currentfilename, [CurrentCell remoteName]);
currentport = [CurrentCell remotePort];
[ItemInfoType setStringValue:"ASCII text"];
[self displayDocument];
break;
case '1' : // Directory : do nothing, taken care of in
// browser:fillMatrix:inColumn:
isIndexedText = NO;
[ItemInfoType setStringValue:"Directory"];
[[MainText docView] setText:""];
break;
case '2': // CSO Nameserver
if (![self createPort:self])
break;
strncpy (server, [CurrentCell remoteHost], 128);
strncpy (site, [CurrentCell stringValue], 128);
msgDelivered = [mySpeaker showServer:server site:site ok:&flag];
if (msgDelivered != 0)
fprintf (stderr, "Sorry, showServer message not delivered.\n");
else
if (!flag) {
fprintf (stderr, "\n*** showServer call failed. ***\n");
fprintf (stderr, "*** Should only happen if 'server' is empty or too long.\n");
}
free (mySpeaker);
[ItemInfoType setStringValue:"Phone Server"];
break;
case '7' : // Index search
[ItemInfoType setStringValue:"Index"];
isIndexedText = YES;
break;
case 'R':
case 'T':
case '8': // Telnet session. Revised by David Lacey
[ItemInfoType setStringValue:"Telnet Session"];
isIndexedText = NO;
[[MainText docView] setText:""];
if ([CurrentCell remotePort] == 0) // Seems common, so fix it
remotePort = 23;
else
remotePort = [CurrentCell remotePort];
// If there is a login ID specified and we're telnetting, alert the user
if (([CurrentCell cellType] == '8') &&
*(remotePath = [CurrentCell remoteName])) {
sprintf (errMsg, "Login to remote system as '%s'.", remotePath);
NXRunAlertPanel (NULL, errMsg, NULL, NULL, NULL, NULL);
}
if (defaultApp == 0) { // Stewart or Terminal?
ind = 0;
command = "/NextApps/Terminal";
comArgs[ind++] = "Terminal";
comArgs[ind++] = "-Shell";
switch ([CurrentCell cellType]) {
case 'R': // RLOGIN
strcpy (loginStr, "/usr/ucb/rlogin ");
if (*[CurrentCell remoteName] != '\0') {
strcat (loginStr, " -l ");
strcat (loginStr, [CurrentCell remoteName]);
}
strcat (loginStr, " ");
strcat (loginStr, [CurrentCell remoteHost]);
break;
case '8': // TELNET:
strcpy (loginStr, "/usr/ucb/telnet ");
strcat (loginStr, [CurrentCell remoteHost]);
if (remotePort != 23) {
sprintf (portStr, " %d", remotePort);
strcat (loginStr, portStr);
}
break;
case 'T': // TN3270:
strcpy (loginStr, "/usr/ucb/tn3270 ");
strcat (loginStr, [CurrentCell remoteHost]);
break;
}
comArgs[ind++] = loginStr;
comArgs[ind++] = NULL;
#ifdef DEBUG
/* printf ("%s: %s",[NXApp appName], command);
tempPtr = comArgs;
while (*++tempPtr != NULL)
printf (" %s", *tempPtr);
printf ("\n");
}
*/
#endif
pid = vfork ();
if (pid == 0) {
code = execve (command, comArgs, environ);
perror ([NXApp appName]);
fprintf (stderr, "%s: Execve error code=%d.\n",[NXApp appName], code);
_exit (0);
}
} else { // Send a message to Stuart
stuart = [StuartSpeaker new];
switch ([CurrentCell cellType]) {
case 'R': // RLOGIN
strcpy (loginStr, "rlogin ");
if (*[CurrentCell remoteName] != '\0') {
strcat (loginStr, " -l ");
strcat (loginStr, [CurrentCell remoteName]);
}
strcat (loginStr, " ");
strcat (loginStr, [CurrentCell remoteHost]);
break;
case '8': // TELNET:
strcpy (loginStr, "telnet ");
strcat (loginStr, [CurrentCell remoteHost]);
if (remotePort != 23) {
sprintf (portStr, " %d", remotePort);
strcat (loginStr, portStr);
}
break;
case 'T': // TN3270:
strcpy (loginStr, "tn3270 ");
strcat (loginStr, [CurrentCell remoteHost]);
break;
}
[stuart default:"Shell" as:loginStr];
[stuart default:"WriteStuartrc" as:"NO"];
[stuart setActivate:TRUE];
if ([stuart stuartConnectAndNew]) {
strcpy (errMsg, "There was a problem communicating with the Stuart ");
strcat (errMsg, "application. (Speaker port unavailable.) This may ");
strcat (errMsg, "have occurred because Stuart is not installed on ");
strcat (errMsg, "your system or because Stuart was slow in starting.");
NXRunAlertPanel (NULL, errMsg, NULL, NULL, NULL, NULL);
}
[stuart free];
}
break;
case 's' : // Sound
isIndexedText = NO;
[ItemInfoType setStringValue:"Sound"];
[[MainText docView] setText:""];
// Check that we're the only thread. We do it here because
// we can't have NXRunAlertPanel in a thread (why not ?)
if (mutex_try_lock(soundIsPlaying) == 0) {
NXRunAlertPanel("Gopher error", "Only one sound at a time !", "OK", NULL, NULL);
break;
}
mutex_unlock(soundIsPlaying);
soundShouldStopFlag = 0;
// The argument is malloc'ed because we may have several threads.
theArg = malloc(sizeof(ThreadArgument));
strncpy(theArg->hostname, [CurrentCell remoteHost], 255);
theArg->portnumber = [CurrentCell remotePort];
strncpy(theArg->filename, [CurrentCell remoteName], 1023);
cthread_detach(cthread_fork((cthread_fn_t)PlaySound, (any_t)theArg));
break;
default :
break;
}
return self;
}
///////////////////////////////////////////////////////////////////
// Highlight next occurance of search string, if it's indexed text.
//
- highlightNextWord
{
int findIndex, spaceIndex;
int minIndex = -1;
char *word, *cp = NULL, *textBuffer;
char wordStowage[256];
int done = 0;
NXSelPt begin, end;
strcpy(wordStowage, lastIndexWord);
word = wordStowage;
[[MainText docView] getSel:&begin :&end];
textBuffer = theText + end.cp;
while (!done) {
cp = strchr(word, ' ');
if (cp == NULL)
done = 1;
else
*cp='\0';
#ifdef DEBUG
fprintf(stderr, "Word is: %s\n", word);
#endif
// Don't highlight stupid words
if ((strcasecmp(word, "and") != 0) &&
(strcasecmp(word, "or") != 0) &&
(strcasecmp(word, "not") != 0) &&
(strcasecmp(word, "the") != 0)) {
findIndex = (int)strcasestr(textBuffer, word);
if (findIndex == 0)
; // Not found
else {
if (minIndex == -1)
minIndex = findIndex;
else if (minIndex > findIndex)
minIndex = findIndex;
}
}
word = cp +1; // Move on to the next word.
}
if (minIndex != -1) {
spaceIndex = (int)index((char *)minIndex, ' ');
if (spaceIndex > (int)index((char*)minIndex, '\r'))
spaceIndex = (int)index((char*)minIndex, '\r');
findIndex = (int)minIndex - (int)theText;
spaceIndex = (int)spaceIndex - (int)theText;
// Highlight the found text, make it visible.
[[MainText docView] setSel:findIndex :spaceIndex];
[[MainText docView] scrollSelToVisible];
}
else
NXBeep();
return self;
}
////////////////////////////////////////////////////////////////
// Connect to currenthostname on port currentport, retrieve
// document currentfilename and display it.
- displayDocument
{
int iLength = 1, i;
int currentsocket;
currentsocket = create_socket(currenthostname, currentport);
if (currentsocket < 0)
{
switch (currentsocket)
{
case -1 :
NXRunAlertPanel("Gopher error", "Unknown host", "OK", NULL, NULL);
return self;
break;
case -2 :
NXRunAlertPanel("Gopher error", "Socket call failed",
"OK", NULL, NULL);
return self;
break;
case -3 :
NXRunAlertPanel("Gopher error", "Connect call failed", "OK", NULL, NULL);
return self;
break;
default :
break;
}
}
writestring(currentsocket, currentfilename);
writestring(currentsocket, "\r\n");
for (i=0; (i < MAXTEXTLENGTH) && iLength; i += iLength)
iLength = read(currentsocket, theText + i, 10000);
theText[i - 3] = '\0'; // -3 to get rid of final period
[[MainText docView] setText:theText];
close(currentsocket);
[[MainText docView] setSel:0 :0];
[[MainText docView] scrollSelToVisible];
if (isIndexedText)
[self highlightNextWord];
/* findIndex = (int)strstr(theText, lastIndexWord);
if (findIndex == 0)
; // Not found
else {
spaceIndex = (int)index((char *)findIndex, ' ');
findIndex = (int)findIndex - (int)theText;
spaceIndex = (int)spaceIndex - (int)theText;
[[MainText docView] setSel:findIndex :spaceIndex];
[[MainText docView] scrollSelToVisible];
}
}
*/
return self;
}
/////////////////////////////////////////////////
// Find our dispatcher
- setDispatcher:sender
{
myDispatcher = sender;
return self;
}
/////////////////////////////////////////////////
// Print the contents of the text window.
- print
{
[[MainText docView] printPSCode:self];
return self;
}
/////////////////////////////////////////////////
// Saves the contents of the text window.
- saveAs:(char *)filename
{
NXStream *tmpstream;
tmpstream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
[[MainText docView] writeText:tmpstream];
NXSaveToFile(tmpstream, filename);
NXCloseMemory(tmpstream, NX_FREEBUFFER);
return self;
}
/////////////////////////////////////////////////
//
- searchIndex:sender
{
[myIndexPanel orderOut:self];
[NXApp stopModal:1];
return self;
}
/////////////////////////////////////////////////
//
- cancelIndex:sender
{
[myIndexPanel orderOut:self];
[NXApp stopModal:2];
return self;
}
/////////////////////////////////////////////////
//
- getIndexString:(char *)theString maxLength:(int)maxlength
{
[myIndexPanel makeKeyAndOrderFront:self];
[IndexTextItem selectText:self];
if ([NXApp runModalFor:myIndexPanel] == 1) // User clicked OK
strncpy(theString, [IndexTextItem stringValue], maxlength);
else
strcpy(theString, ""); // User clicked Cancel
return self;
}
/////////////////////////////////////////////////
//
- showItemInfo
{
[ItemInfoPanel makeKeyAndOrderFront:self];
return self;
}
/////////////////////////////////////////////////
// Delegate method for the browser
- (int)browser:sender fillMatrix:matrix inColumn:(int)column
{
id tmpcell;
int iLength;
char inputline[512];
char *remotename, *remotehost, *remoteport;
int currentsocket;
id CurrentCell;
CurrentCell = [[sender matrixInColumn:[sender selectedColumn]] selectedCell];
switch ([CurrentCell cellType]) {
case '0' : // Document : should never happen
break;
case '1' : // Directory : the browser will
// automatically update
strncpy(currenthostname, [CurrentCell remoteHost], 250);
strcpy(currentfilename, [CurrentCell remoteName]);
currentport = [CurrentCell remotePort];
break;
case '7' : // Index search
strncpy(currenthostname, [CurrentCell remoteHost], 250);
[self getIndexString:(char *)&lastIndexWord maxLength:250];
if (strlen(lastIndexWord) == 0) // User clicked Cancel
return 0;
strcpy(currentfilename, [CurrentCell remoteName]);
strcat(currentfilename, "\t");
strcat(currentfilename, lastIndexWord);
currentport = [CurrentCell remotePort];
break;
case '8' : // Telnet session : should never happen
break;
case 's' : // Sound : should never happen
break;
default :
break;
}
currentsocket = create_socket(currenthostname, currentport);
if (currentsocket < 0)
switch (currentsocket) {
case -1 :
NXRunAlertPanel("Gopher error", "Unknown host", "OK", NULL, NULL);
return(0);
break;
case -2 :
NXRunAlertPanel("Gopher error", "Socket call failed", "OK", NULL, NULL);
return(0);
break;
case -3 :
NXRunAlertPanel("Gopher error", "Connect call failed", "OK", NULL, NULL);
return(0);
break;
default :
break;
}
writestring(currentsocket, currentfilename);
writestring(currentsocket, "\r\n");
for(;;) {
iLength = readline(currentsocket, inputline, 512);
if (iLength == -1)
break;
if (iLength == 0)
break;
ZapCRLF(inputline);
if ((inputline[0] == '0') || (inputline[0] == '1') ||
(inputline[0] == '7') || (inputline[0] == '8') ||
(inputline[0] == 'R') || (inputline[0] == 'T') ||
(inputline[0] == 's') || (inputline[0] == '2')) {
if ((inputline[0] == 's') && (shouldUseSound == 0))
continue;
[matrix addRow];
tmpcell = [matrix cellAt:([matrix cellCount] -1) :0];
remotename = index(inputline, '\t') + 1;
*(remotename-1) = '\0';
remotehost = index(remotename, '\t') + 1;
*(remotehost-1) = '\0';
remoteport = index(remotehost, '\t') + 1;
*(remoteport-1) = '\0';
[tmpcell setRemoteName:remotename];
[tmpcell setRemoteHost:remotehost];
[tmpcell setRemotePort:atoi(remoteport)];
[tmpcell setStringValue:(inputline+1)];
[tmpcell setLoaded:YES];
if (inputline[0] == '0') { // Document
[tmpcell setLeaf:YES];
[tmpcell setCellType:'0'];
}
else if (inputline[0] == '1') { // Directory
[tmpcell setLeaf:NO];
[tmpcell setCellType:'1'];
}
else if (inputline[0] == '2') { // CSO Nameserver
[tmpcell setLeaf:YES];
[tmpcell setCellType:'2'];
}
else if (inputline[0] == '7') { // Index
[tmpcell setLeaf:NO];
[tmpcell setCellType:'7'];
}
else if (inputline[0] == '8') { // Telnet
[tmpcell setLeaf:YES];
[tmpcell setCellType:'8'];
}
else if (inputline[0] == 'R') { // Rlogin
[tmpcell setLeaf:YES];
[tmpcell setCellType:'R'];
}
else if (inputline[0] == 'T') { // TN3270
[tmpcell setLeaf:YES];
[tmpcell setCellType:'T'];
}
else if (inputline[0] == 's') { // Sound
[tmpcell setLeaf:YES];
[tmpcell setCellType:'s'];
}
}
if ((inputline[0] == '.') && (inputline[1] == '\0'))
break;
}
close(currentsocket);
return([matrix cellCount]);
}
////////////////////////////////////////////////////////////////////////////
// Delegate methods for the window
/////////////////////////////////////////////////
// Called when our window becomes the key window.
- windowDidBecomeKey:sender
{
[myDispatcher newFrontWindow:self];
return self;
}
/////////////////////////////////////////////////
// Called when our window has stopped being the key window.
- windowDidResignKey:sender
{
[myDispatcher oldFrontWindow:self];
return self;
}
/////////////////////////////////////////////////
// Called when our window is going to close.
- windowWillClose:sender
{
[self clean];
[myDispatcher oldFrontWindow:self];
return self;
}
// Stuff from Rex Pruess's Ph CSO Nameserver App
/*---------------------------------------------------------------------------
This internal support method sets the Speaker port and creates the PhSpeaker
object.
-----------------------------------------------------------------------------*/
- (BOOL) createPort:sender
{
port_t thePort;
thePort = NXPortFromName ("Ph", NULL);
if (thePort == PORT_NULL) {
fprintf (stderr, "\n*** PhSpeaker port is NULL.\n");
fprintf (stderr, "*** Perhaps Ph is not installed in a standard directory.\n");
return NO;
}
mySpeaker = [[PhSpeaker alloc] init];
[mySpeaker setSendPort:thePort];
return YES;
}
@end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.