This is Qi.m in view mode; [Download] [Up]
/*--------------------------------------------------------------------------- Qi.m -- Copyright (c) 1991 Rex Pruess This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA, or send electronic mail to the the author. There is one Qi object for each unique server. Each Qi object keeps track of the from & to file pointers used to talk to the server. It is also responsible for getting the "field" values from the server & setting up QiField objects (one for each field). Rex Pruess <Rex-Pruess@uiowa.edu> $Header: /rpruess/apps/Ph/qiServers.subproj/RCS/Qi.m,v 3.0 93/06/26 07:44:45 rpruess Exp $ ----------------------------------------------------------------------------- $Log: Qi.m,v $ Revision 3.0 93/06/26 07:44:45 rpruess Brought Ph code up to NeXTSTEP 3.0 specifications. Revision 2.2 91/12/15 15:52:33 rpruess Added timer code to timeout inactive sockets. The socket is automatically re-created if the user makes a subsequent query request. Revision 2.1 91/12/10 16:22:46 rpruess Added code to suppress the display of the status panel for the default query in certain situations. If the user has requested the "hide default query" preference, then the panel is not displayed. Also, if Ph is auto-launched & the user has requested "hide on auto-launch", the status panel is not displayed. Revision 2.0 91/11/19 08:24:24 rpruess Revision 2.0 is the initial production release of Ph. -----------------------------------------------------------------------------*/ #define QUITSTR "quit\n" /* Command to close the Qi connection */ #define TIMERMINS 15 /* Animator wakeup interval (in minutes) */ /* C header files */ #include <netdb.h> #include <sys/socket.h> #include <netinet/in.h> /* Application class header files */ #import "Qi.h" #import "QiField.h" #import "../PhShare.h" #import "../info.h" /* Qi Server header file (Qi return codes) */ #import "QiReplies.h" /*--------------------------------------------------------------------------- The Qi private section is based somewhat on the Subprocess code found in the /NextDeveloper/Examples directory. This code handles all output from a Qi server. Each buffer of output is separated into lines before being distributed to the delegate process. -----------------------------------------------------------------------------*/ @implementation Qi (Private) - fdHandler:(int)theFd { int bufferCount; /* Number of bytes read */ int len; /* Line length */ char *nl; /* Pointer to '\n' in outputBuffer */ char *ob; /* Pointer which moves thru outputBuffer */ /*** If a socket error occurs, it will most likely first be detected here. If so, close the files & send a dummy error line to the delegate process so it knows something is awry. Otherwise, the delegate will wait forever for data that is never coming. */ if (((bufferCount = read (theFd, outputBuffer, MAXSIZE - 1)) <= 0)) { strcpy (errMsg, "%s socket read error. Try request again if you wish "); strcat (errMsg, "to re-establish the link.\n"); strcat (errMsg, strerror (errno)); NXRunAlertPanel (NULL, errMsg, NULL, NULL, NULL, server); [self closeFiles:self]; strcpy (lineBuffer, "9999: Socket closed. Try again if you wish to re-open it.\n"); if (delegate && [delegate respondsTo:@selector (qiOutput:)]) [delegate perform:@selector (qiOutput:) with :(void *)&lineBuffer]; lineBuffer[0] = '\0'; return self; } /* *** Normal processing passes through here. Set the socketIO variable to indicate we've seen I/O. Then, terminate the output buffer properly. */ socketIO = YES; outputBuffer[bufferCount] = '\0'; ob = outputBuffer; /*** This loop moves through the output buffer and sends each line it finds to the delegate process. */ while ((nl = index (ob, '\n')) != 0) { len = nl - ob + 1; strncat (lineBuffer, ob, MIN (len, MAXSIZE - strlen (lineBuffer) - 1)); if (debug) [self debugOut:lineBuffer toQi:NO]; if (delegate && [delegate respondsTo:@selector (qiOutput:)]) [delegate perform:@selector (qiOutput:) with :(void *)&lineBuffer]; lineBuffer[0] = '\0'; ob += len; } /*** The output buffer may still have a partial line in it. If so, assign it to the lineBuffer so we don't lose it next time through. */ if (strlen (ob) == 0) lineBuffer[0] = '\0'; else strcpy (lineBuffer, ob); return self; } static void fdHandler (int theFd, id self) { [self fdHandler:theFd]; } @end @implementation Qi /*--------------------------------------------------------------------------- Initialize variables to safe values. -----------------------------------------------------------------------------*/ - init { [super init]; [NXApp loadNibSection:"Qi.nib" owner:self withNames:NO]; delegate = nil; hasQiFields = NO; hasClosed = NO; isTimerRunning = NO; nDefaults = 0; server = NULL; socketIO = NO; watchingFd = NO; sock = -1; lineBuffer[0] = '\0'; outputBuffer[0] = '\0'; fieldList = [[List alloc] init]; if (strcmp (NXGetDefaultValue ([NXApp appName], DEBUG), "YES") == 0) debug = YES; else debug = NO; qiAnimator = [[Animator alloc] initChronon:TIMERMINS * 60 adaptation:0.0 target:self action:@selector (qiTimeCheck) autoStart:NO eventMask:0]; return self; } /*--------------------------------------------------------------------------- Save the server name, try to open a socket, and send the "fields" command to the server. -----------------------------------------------------------------------------*/ - (BOOL) connectTo:(const char *)aServer { server = malloc (strlen (aServer) + 1); strcpy (server, aServer); [self showStatusPanel:"Connecting to:" server:server]; if ([self openSocket] == NO) { [self hideStatusPanel:self]; return NO; } /*** Send the "fields" command to the server. */ [self showStatusPanel:"Fetching fields from:" server:aServer]; [self qiSend:"fields\n" delegate:self]; return YES; } /*--------------------------------------------------------------------------- Set up the socket and try to connect to the server. -----------------------------------------------------------------------------*/ - (BOOL)openSocket { struct hostent *hp, *gethostbyname (); struct sockaddr_in qiSock; struct servent *theNs; /*** Create socket */ sock = socket (PF_INET, SOCK_STREAM, 0); if (sock < 0) { strcpy (errMsg, "Trouble opening socket.\n"); strcat (errMsg, strerror (errno)); NXRunAlertPanel (NULL, errMsg, NULL, NULL, NULL); return NO; } /*** Find the proper port */ qiSock.sin_family = AF_INET; if ((theNs = getservbyname (NSSERVICE, "tcp")) != NULL) qiSock.sin_port = theNs -> s_port; else qiSock.sin_port = htons (DEFAULTPORT); /*** Connect socket to Qi server */ hp = gethostbyname (server); if (hp == 0) { strcpy (errMsg, "Unable to get network host entry for '%s'.\n"); strcat (errMsg, strerror (errno)); NXRunAlertPanel (NULL, errMsg, NULL, NULL, NULL, server); return NO; } bcopy (hp -> h_addr, &qiSock.sin_addr, hp -> h_length); if (connect (sock, (struct sockaddr *) & qiSock, sizeof (qiSock)) < 0) { strcpy (errMsg, "Trouble connecting to socket.\n"); strcat (errMsg, strerror (errno)); NXRunAlertPanel (NULL, errMsg, NULL, NULL, NULL); return NO; } return YES; } /*--------------------------------------------------------------------------- This method is set to be Qi's delegate. It processes one line of Qi output at a time. This output is the result of the "fields" inquiry. Upon end of output, it is responsible for telling Qi to stop. -----------------------------------------------------------------------------*/ - qiOutput:(char *)aBuf { char *aPtr; int fieldNum; char *fieldName; int ind; int theCode; id theQiField; theQiField = nil; theCode = atoi (aBuf); if (theCode >= LR_OK) { [self hideStatusPanel:self]; [self stopFd:self]; if (theCode == LR_OK || theCode == LR_RONLY) { hasQiFields = YES; return self; } strcpy (errMsg, "%s server error.\n"); strncat (errMsg, aBuf, MIN (strlen (aBuf), sizeof (errMsg) - strlen (errMsg) - 1)); NXRunAlertPanel (NULL, errMsg, NULL, NULL, NULL, server); [self stopFd:self]; [self closeFiles:self]; return self; } if (theCode != -LR_OK) return self; if ((aPtr = strtok (aBuf, ":")) == NULL) /* Code */ return self; if ((aPtr = strtok (NULL, ":")) == NULL) /* Field number */ return self; fieldNum = atoi (aPtr); if ((fieldName = strtok (NULL, ":")) == NULL) /* Field name */ return self; if ((aPtr = strtok (NULL, ":")) == NULL) /* keywords or description */ return self; /*** If we have this field in the fieldList, then this line must be its description. */ for (ind = 0; ind < [fieldList count]; ind++) { if ([[fieldList objectAt:ind] number] == fieldNum) { [[fieldList objectAt:ind] setDescription:aPtr]; return self; } } /*** If we get here, it is a brand new field. If it is a default field, then it is inserted after the last default entry. Non-default fields are inserted alphabetically after the last default field (per Steve Dorner's recommendation.) */ theQiField = [[QiField alloc] init]; [theQiField setNumNameKeys:fieldNum name:fieldName keys:aPtr]; if (strstr (aPtr, "Default") != NULL) ind = nDefaults++; else { for (ind = nDefaults; ind < [fieldList count]; ind++) { if (strcmp (fieldName,[[fieldList objectAt:ind] name]) < 0) break; } } [fieldList insertObject:theQiField at:ind]; return self; } /*--------------------------------------------------------------------------- Send a command to the Qi server. -----------------------------------------------------------------------------*/ - qiSend:(const char *)aCommand delegate:aDelegate { const char *aPtr; int bytes; int i; BOOL isQuitCommand; /*** Set the "isQuitCommand" flag appropriately. */ if (strcmp (aCommand, QUITSTR) == 0) isQuitCommand = YES; else isQuitCommand = NO; /*** If the socket has been closed, then we will silently ('cept for debugging fprint message) try to rebind it. */ if (hasClosed) { if (isQuitCommand) return self; if (debug) printf ("%s: Binding new socket with %s.\n", [NXApp appName], server); if ([self openSocket] == NO) return self; hasClosed = NO; [self stopFd:self]; } /*** If we are waiting on output for some delegate, then abort this request. */ if (watchingFd && aDelegate != nil) { strcpy (errMsg, "Output is pending from a previous command. Please try later."); NXRunAlertPanel (NULL, errMsg, NULL, NULL, NULL); return self; } /*** All's well so far. If debugging is enabled, show the command to the user too. */ if (debug) [self debugOut:aCommand toQi:YES]; /*** Send the command to the server. If the write fails, try to re-bind the socket and send it again. */ if ((bytes = write (sock, aCommand, strlen (aCommand))) < 0) { if (isQuitCommand) return self; [self closeFiles:self]; fprintf (stderr, "%s: Re-establishing socket with %s.\n", [NXApp appName], server); if ([self openSocket] == NO) return self; hasClosed = NO; if ((bytes = write (sock, aCommand, strlen (aCommand))) < 0) { strcpy (errMsg, "%s socket write error. Aborting write to server.\n"); strcat (errMsg, strerror (errno)); NXRunAlertPanel (NULL, errMsg, NULL, NULL, NULL, server); return self; } } /*** Start watching the file descriptor (except when sending the "quit" command). */ if (!isQuitCommand) [self startFd:aDelegate]; /*** I doubt the following "if" code will ever be executed. It is possible the "write" will not send all of the command. In such a case, it is necessary to loop until all of the command has been sent. Quit when all data has been sent, if an error is encountered, or after a few iterations. This is not elegant, but should suffice. */ if (bytes < strlen (aCommand)) { aPtr = aCommand + bytes; for (i = 1; i <= 5; i++) { if ((bytes = write (sock, aPtr, strlen (aPtr))) < 0) break; if (bytes >= strlen (aPtr)) break; aPtr += bytes; sleep (1); } } /*** If the timer is not running, then start it (except when sending the "quit" command). The timer will periodically check to see if the connection should be closed due to inactivity. socketIO is the boolean flag used to indicate there has been activity. */ if (!isTimerRunning && !isQuitCommand) [self startQiTimer:self]; socketIO = YES; return self; } /*--------------------------------------------------------------------------- Print a debugging line of output. -----------------------------------------------------------------------------*/ - debugOut:(const char *)aLine toQi:(BOOL) aCommand { printf ("%s: Server=%s, %s=%s",[NXApp appName], server, (aCommand ? "Command" : "Data"), aLine); fflush (stdout); return self; } /*--------------------------------------------------------------------------- This method is closed during normal shutdown or when a socket error occurs. -----------------------------------------------------------------------------*/ - closeFiles:sender { close (sock); hasClosed = YES; if (isTimerRunning) [self stopQiTimer:self]; return self; } /*--------------------------------------------------------------------------- Start watching the file descriptor. -----------------------------------------------------------------------------*/ - startFd:aDelegate { delegate = aDelegate; if (!watchingFd) DPSAddFD (sock, (DPSFDProc) fdHandler, (id) self, NX_MODALRESPTHRESHOLD + 1); watchingFd = YES; return self; } /*--------------------------------------------------------------------------- Stop watching the file descriptor. Setting the delegate to nil is necessary to allow any future sends. -----------------------------------------------------------------------------*/ - stopFd:sender { delegate = nil; if (watchingFd) DPSRemoveFD (sock); watchingFd = NO; return self; } /*--------------------------------------------------------------------------- Tell the server to quit. Stop watching the file descriptor, close files, and stop the timer. -----------------------------------------------------------------------------*/ - quit { [self qiSend:QUITSTR delegate:nil]; [self stopFd:self]; [self closeFiles:self]; return self; } /*--------------------------------------------------------------------------- Periodically, check to see if there has been any activity on the socket. If not, close the connection. -----------------------------------------------------------------------------*/ - qiTimeCheck { if (socketIO) socketIO = NO; else [self quit]; return self; } /*--------------------------------------------------------------------------- Start the timer. -----------------------------------------------------------------------------*/ - startQiTimer:sender { isTimerRunning = YES; [qiAnimator startEntry]; return self; } /*--------------------------------------------------------------------------- Stop the timer. -----------------------------------------------------------------------------*/ - stopQiTimer:sender { isTimerRunning = NO; [qiAnimator stopEntry]; return self; } /*--------------------------------------------------------------------------- Hide the status window. -----------------------------------------------------------------------------*/ - hideStatusPanel:sender { [statusPanel orderOut:self]; return self; } /*--------------------------------------------------------------------------- Insert the name of the server in the text field & display the panel. The status panel is not displayed for the default nameserver if Ph has been auto-launched or if the user has requested the default Query window be hidden. -----------------------------------------------------------------------------*/ - showStatusPanel:(const char *)aString server:(const char *)aServer { const char *defServer; /* Default CSO Nameserver */ /*** Get the default nameserver name & see if we should display the panel. */ defServer = NXGetDefaultValue ([NXApp appName], DEFAULTSERVER); if (defServer != NULL && strcmp (defServer, aServer) == 0) { if (strcmp (NXGetDefaultValue ([NXApp appName], "NXAutoLaunch"), "YES") == 0) return self; if (strcmp (NXGetDefaultValue ([NXApp appName], HIDEQUERY), "YES") == 0) return self; } /*** Guess, we gotta display the panel. */ [msgTextField setStringValue:aString]; [serverTextField setStringValue:aServer]; [statusPanel makeKeyAndOrderFront:self]; return self; } /*--------------------------------------------------------------------------- Methods to return the requested data. -----------------------------------------------------------------------------*/ - fieldList { return fieldList; } - (BOOL) hasQiFields { return hasQiFields; } - (BOOL) hasClosed { return hasClosed; } - (const char *)server { return server; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.