summaryrefslogtreecommitdiff
path: root/comm/OLD/TextStream.cc
diff options
context:
space:
mode:
authorchatty1993-04-07 11:50:31 +0000
committerchatty1993-04-07 11:50:31 +0000
commitba066c34dde204aa192d03a23a81356374d93731 (patch)
tree39391f6235d2cf8a59a0634ac5ea430cdd21f5d4 /comm/OLD/TextStream.cc
parent05ab076e1c2a9ca16472f9a6b47b8d22914b3783 (diff)
downloadivy-league-ba066c34dde204aa192d03a23a81356374d93731.zip
ivy-league-ba066c34dde204aa192d03a23a81356374d93731.tar.gz
ivy-league-ba066c34dde204aa192d03a23a81356374d93731.tar.bz2
ivy-league-ba066c34dde204aa192d03a23a81356374d93731.tar.xz
Initial revision
Diffstat (limited to 'comm/OLD/TextStream.cc')
-rw-r--r--comm/OLD/TextStream.cc1030
1 files changed, 1030 insertions, 0 deletions
diff --git a/comm/OLD/TextStream.cc b/comm/OLD/TextStream.cc
new file mode 100644
index 0000000..a6d12d3
--- /dev/null
+++ b/comm/OLD/TextStream.cc
@@ -0,0 +1,1030 @@
+/*
+ * The Unix Channel
+ *
+ * by Michel Beaudouin-Lafon
+ *
+ * Copyright 1990-1993
+ * Laboratoire de Recherche en Informatique (LRI)
+ *
+ * Text streams
+ *
+ * $Id$
+ * $CurLog$
+ */
+
+#include "TextStream.h"
+#include "TimeOut.h"
+#include "PortServer.h"
+#include "Multiplexer.h"
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+// list is a set of words
+// the separator must be the first and last char of the string:
+// e.g. :yes:no: /foo/bar/toto/
+// word is a word to be looked up in the list.
+// we use the same kind of comparison as in cmpname, provided that the words
+// in the list are capitalized.
+// windex returns 0 if the word was not found, otherwise its index in the list.
+int
+windex (const char* list, const char* word)
+{
+ if (! list || ! word)
+ return 0;
+ char sep = *list;
+ if (! sep)
+ return 0;
+ register const char* p;
+ register const char* w = word;
+ int ind = 1;
+ for (p = list+1; *p; p++) {
+ if (w) { // so far the word matches
+ if (!*w && *p == sep) // found !
+ return ind;
+ if (*w == *p) // still ok
+ w++;
+ else if (isupper (*p)) {
+ if (*w == '-' || *w == '_')
+ w++;
+ if (*p == *w || tolower (*p) == *w)
+ w++;
+ else
+ w = 0;
+ } else // not this item
+ w = 0;
+ } else {
+ if (*p == sep) { // start new item
+ ind++;
+ w = word;
+ }
+ }
+ }
+ return 0;
+}
+
+// compare two names
+// an uppercase letter can match the same uppercase letter,
+// or an optional hyphen or underscore followed by the lowercase letter.
+// if the name starts with an uppercase, it matches the lowercase
+// FooBar == foo-bar, foo_bar, foobar
+// returns like strcmp
+int
+cmpname (register const char* s1, register const char* s2)
+{
+ for (; *s1 && *s2; s1++, s2++) {
+ if (*s1 == *s2)
+ continue;
+ if (isupper (*s1)) {
+ if (*s2 == '-' || *s2 == '_')
+ s2++;
+ if (*s1 == *s2 || tolower (*s1) == *s2)
+ continue;
+ else
+ break;
+ } else if (isupper (*s2)) {
+ if (*s1 == '-' || *s1 == '_')
+ s1++;
+ if (*s2 == *s1 || tolower (*s2) == *s1)
+ continue;
+ else
+ break;
+ } else
+ break;
+ }
+
+ if (*s1 == '\0' && *s2 == '\0')
+ return 0;
+ if (*s1 == '\0')
+ return -1;
+ if (*s2 == '\0')
+ return 1;
+ if (tolower (*s1) < tolower (*s2))
+ return -1;
+ return 1;
+}
+
+// sprintf a message (up to 5 args) and return the formatted string
+// the address of a static string is returned
+const char*
+stringf (const char* fmt, const void* a1, const void* a2, const void* a3, const void* a4, const void* a5)
+{
+ static char msg [1024];
+ sprintf (msg, fmt, a1, a2, a3, a4, a5);
+ return msg;
+}
+
+
+void
+UchTextWord :: SetVal (const char* val)
+{
+ // if Sval is null, then it's an integer, whose value is in Ival
+ // if Sval is non null, then it's a string, whose value is in Sval
+ Ival = atoi (val);
+ if (Ival == 0 && strcmp (val, "0") != 0)
+ Sval = val; // word is a string: atoi returned 0 and val is not "0"
+ else
+ Sval = 0; // word is an integer
+}
+
+const char*
+UchTextWord :: GetQuotes () const
+{
+ if (! Sval)
+ return ""; /* no quotes */
+ if (! *Sval)
+ return "\'\'"; /* empty string */
+ if (index (Sval, ' ') == 0)
+ return ""; /* no quotes */
+ if (index (Sval, '\'') == 0)
+ return "\'\'"; /* 'hello' */
+ if (index (Sval, '\"') == 0)
+ return "\"\""; /* "hello" */
+ return 0; /* cannot quote */
+}
+
+
+void
+UchTextLine :: NewWord (const char* s, int i)
+{
+ if (Num >= Max) {
+ if (Max == 0) {
+ Max = 16;
+ Words = new UchTextWord [Max];
+ } else {
+ UchTextWord* old = Words;
+ Words = new UchTextWord [Max *= 2];
+ UchTextWord* po = old;
+ UchTextWord* pn = Words;
+ for (int i = 0; i < Num; i++)
+ *pn++ = *po++;
+ delete [] old;
+ }
+ }
+ if (s)
+ Words [Num++].SetVal (s);
+ else
+ Words [Num++].SetVal (i);
+}
+
+UchTextLine :: UchTextLine ()
+: Num (0),
+ Max (0),
+ Words (0)
+{
+}
+
+UchTextLine :: ~UchTextLine ()
+{
+ if (Words)
+ delete [] Words;
+}
+
+void
+UchTextLine :: AddTrailer (const UchTextLine& line, int first)
+{
+ for (; first < line.Num; first++) {
+ UchTextWord& word = line [first];
+ NewWord (word, word);
+ }
+}
+
+bool
+UchTextLine :: Parse (char* line)
+{
+ enum WHERE { IN_SPACE, IN_WORD, IN_QUOTE };
+ char quote = '\0';
+ register char* s = line;
+ register char* word = line;
+ register WHERE where = IN_SPACE;
+
+ // split a line into words
+ // a word is a sequence of non blank characters,
+ // or a quoted string (' or ").
+ // '(', ')' and ',' are also ignored to allow for function like notation
+ for (; *s; s++) {
+ switch (where) {
+ case IN_WORD:
+ if (index (" \t(),", *s)) {
+ *s = '\0';
+ where = IN_SPACE;
+ AddWord (word);
+ }
+ break;
+ case IN_QUOTE:
+ if (*s == quote) {
+ *s = '\0';
+ where = IN_SPACE;
+ AddWord (word);
+ }
+ break;
+ case IN_SPACE:
+ if (*s == '\'' || *s == '\"') {
+ quote = *s;
+ *s = '\0';
+ where = IN_QUOTE;
+ word = s+1;
+ } else
+ if (index (" \t(),", *s)) {
+ *s = '\0';
+ } else {
+ where = IN_WORD;
+ word = s;
+ }
+ break;
+ }
+ }
+ if (where != IN_SPACE)
+ AddWord (word);
+ if (where == IN_QUOTE)
+ return FALSE;
+ return TRUE;
+}
+
+// decompile line into buffer
+// if the line does not fit in the buffer, it ends with '...'
+// the return value is dest, or a pointer to a static string "..."
+// if dest or len is 0.
+char*
+UchTextLine :: Unparse (char* dest, int len) const
+{
+ UchTextWord* pa = Words;
+ char* p = dest;
+ char* lim = dest + len -1;
+
+ if (! dest || ! len)
+ return "...";
+
+ for (int i = 0; i < Num; i++, pa++) {
+ // convert arg to string
+ const char* sa;
+ const char* quotes = "";
+ if (pa->IsInt ()) {
+ char num [32];
+ sprintf (num, "%d", int (*pa));
+ sa = num;
+ } else {
+ sa = *pa;
+ quotes = pa->GetQuotes ();
+ if (! quotes) {
+ fprintf (stderr, "UchTextLine::Unparse, cannot quote word\n");
+ quotes = "";
+ sa = "(unquotable)";
+ }
+ }
+ // try to append
+ if (p + strlen (sa) + strlen (quotes) < lim) {
+ if (*quotes)
+ *p++ = quotes [0];
+ strcpy (p, sa);
+ p += strlen (p);
+ if (*quotes)
+ *p++ = quotes [1];
+ } else {
+ if (*quotes)
+ *p++ = quotes [0];
+ strncpy (p, sa, (lim - p) - 3);
+ strcpy (lim -3, "...");
+ p = lim;
+ break;
+ }
+ // separator with next one
+ if (i < Num -1 && p < lim)
+ *p++ = ' ';
+ }
+ *p = '\0';
+ return dest;
+}
+
+// unparse the line in a buffer.
+// the unparsed string is _not_ terminated by a null
+// the return value is a pointer to the beginning of the string in the buffer
+char*
+UchTextLine :: Unparse (UchMsgBuffer* buf) const
+{
+ UchTextWord* pa = Words;
+ char* start = (char*) buf->Buffer ();
+
+ for (int i = 0; i < Num; i++, pa++) {
+ // convert arg to string and append it
+ const char* sa;
+ const char* quotes = "";
+ if (pa->IsInt ()) {
+ char num [32];
+ sprintf (num, "%d", int (*pa));
+ sa = num;
+ } else {
+ sa = *pa;
+ quotes = pa->GetQuotes ();
+ if (! quotes) {
+ fprintf (stderr, "UchTextLine::Unparse, cannot quote word <%s>\n", sa);
+ quotes = "";
+ sa = "(unquotable)";
+ }
+ }
+ if (*quotes)
+ buf->Append (quotes [0]);
+ buf->Append (sa, FALSE /*no newline*/);
+ if (*quotes)
+ buf->Append (quotes [1]);
+ // separator with next one
+ if (i < Num -1)
+ buf->Append (' ');
+ }
+ return start;
+}
+
+bool
+UchTextLine :: Match (const char* cmdname, const char* profile) const
+{
+ if (Num <= 0)
+ return FALSE; // no word on line
+ const char* myname = Words [0];
+ if (! myname || cmpname (myname, cmdname) != 0)
+ return FALSE; // wrong command name
+
+ // check the profile, if it is non null.
+ // each letter in the profile corresponds to one argument:
+ // 'i' for integer, 's' for string, 'b' for bool.
+ // the value for a boolean can be an integer (0=false, otherwise true)
+ // or a string: yes, y, true, t, on, no, n, false, f, off
+ // if the last character of the profile is '+', then the extra arguments
+ // are not checked.
+ // a null profile is identical to the profile "+".
+ // an empty profile ("") describes a command with no argument.
+ // *** we'll probably add features for repeated arguments,
+ // *** enums (mapping strings to ints), etc.
+ if (! profile)
+ return TRUE;
+ const char* p = profile;
+ for (int i = 1; i < Num; i++) {
+ switch (*p) {
+ case 'i':
+ if (! Words [i].IsInt ())
+ return FALSE;
+ break;
+ case 's':
+ if (! Words [i].IsString ())
+ return FALSE;
+ break;
+ case 'b':
+ {
+ if (Words [i].IsInt ())
+ break;
+ const char* sval = Words [i];
+ if (windex (":Yes:Y:True:T:On:", sval)) {
+ Words[i].SetVal (1);
+ break;
+ } else if (windex (":No:N:False:F:Off:", sval)) {
+ Words[i].SetVal (0);
+ break;
+ }
+ return FALSE;
+ }
+ case '+':
+ if (*(p+1)) // not at end of profile
+ fprintf (stderr, "UchTextLine::Match, invalid command profile: <%s>\n", profile);
+ return TRUE;
+ case '\0':
+ // profile shorter than arg list
+ return FALSE;
+ default:
+ fprintf (stderr, "UchTextLine::Match, invalid command profile: <%s>\n", profile);
+
+ return FALSE;
+ }
+ p++;
+ }
+ if (*p)
+ return FALSE; // arg list shorter than profile
+ return TRUE;
+}
+
+// return -1 if word is not in the line, otherwise return its index
+int
+UchTextLine :: Index (const char* word) const
+{
+ UchTextWord* w = Words;
+ int i = 0;
+
+ for (; i < Num; i++, w++)
+ if (w->IsString () && strcmp (*w, word) == 0)
+ return i;
+ return -1;
+}
+
+// return -1 if value is not in the line, otherwise return its index
+int
+UchTextLine :: Index (int value) const
+{
+ UchTextWord* w = Words;
+ int i = 0;
+
+ for (; i < Num; i++, w++)
+ if (w->IsInt () && *w == value)
+ return i;
+ return -1;
+}
+
+//---------------- constants
+
+// we start with a few quick retries
+// AbandonRetries doubles the time and decrement the retries until 1
+// 2 secs * 5 = 10 secs
+// 4 secs * 4 = 16 secs
+// 8 secs * 3 = 24 secs
+// 16 secs * 2 = 32 secs
+// 32 secs * 1 = 32 secs
+// total = 15 retries in 114 secs ~ 2 minutes
+// we want this to be significantly longer than the REG_TIME of the port server
+
+#define RETRY_TIME 2
+#define MAX_RETRIES 5
+
+/*?
+Construct a \typ{UchTextStream}.
+?*/
+UchTextStream :: UchTextStream ()
+: UchStream (),
+ InBuffer (256),
+ OutBuffer (256),
+ MyMpx (0)
+{
+}
+
+/*?
+Protected destructor.
+\fun{Close} must be used instead to properly close down a \typ{UchTextStream}
+?*/
+UchTextStream :: ~UchTextStream ()
+{
+ // nothing
+}
+
+/*?
+This virtual function is called by \fun{HandleRead} when an end of file
+is read (the argument is then \var{TRUE}), or when an error occured while
+reading (the argument is then \var{FALSE}).
+The default action is to call \fun{Close}.
+?*/
+void
+UchTextStream :: Closing (bool)
+{
+ Close ();
+}
+
+void
+UchTextStream :: AddNotify (UchMultiplexer& m)
+{
+ MyMpx = &m;
+}
+
+void
+UchTextStream :: RemoveNotify (UchMultiplexer& m)
+{
+ if (MyMpx == &m)
+ MyMpx = 0;
+}
+
+/*?
+Read and decode input. When a complete line (terminated either by
+a newline or cr-newline) is in the input buffer, it is parsed.
+If parsing is successful, \fun{Execute} is called with the line as argument.
+If \fun{Execute} returns \var{isCmdUnknown}, \fun{TryPredefined} is called
+to decode the predefined commands.
+Finally, \fun{ProcessCmdResult} is called and the line is flushed from
+the input buffer.
+Note that since the parsed line passed to \fun{Execute} contains pointers
+to the input buffer, \fun{Execute} {\em must not} keep copies of these
+pointers, but copy the contents instead.
+?*/
+void
+UchTextStream :: HandleRead ()
+{
+ InBuffer.NeedSize (128);
+ int n = Read (InBuffer);
+ if (n <= 0) {
+ Closing (n < 0 ? FALSE : TRUE);
+ return;
+ }
+
+ // scan buffer for complete commands
+ char* start = (char*) InBuffer.Buffer ();
+ char* stop = start + InBuffer.BufLength ();
+ for (char* p = start; p < stop; p++) {
+ if (*p == '\n') {
+ *p = '\0';
+ // check cr-nl sequence
+ if (p > start && *(p-1) == '\r')
+ *(p-1) = '\0';
+ // parse line, lookup command
+ UchTextLine line;
+ cmd_res res = isCmdUnknown;
+ if (line.Parse (start)) {
+ if (line.NumWords () == 0)
+ res = isCmdOk;
+ else
+ res = Execute (line);
+ if (res == isCmdUnknown)
+ res = TryPredefined (line);
+ } else
+ res = isCmdSyntax;
+ ProcessCmdResult (res, line);
+ // reset for scanning rest of buffer
+ start = p+1;
+ InBuffer.Flush ((byte*) start);
+ }
+ }
+}
+
+/*?
+Process the result \var{res} of the execution of the line \var{line}.
+If \var{res} is one of \var{isCmdSyntax}, \var{isCmdUnknown}, \var{isCmdError},
+a warning message is issued.
+If \var{res} is \var{isCmdClose} (resp. \var{isCmdQuit}), the virtual function
+\fun{Close} (resp. \fun{Quit}) is called.
+If \var{res} is \var{isCmdTerminate} (resp. \var{isCmdAbort}),
+the global function \fun{MpxTerminate} (resp. \fun{MpxAbort}) is called.
+If \var{res} is \var{isCmdExit}, \fun{exit(1)} is called.
+Finally, if \var{res} is \var{isCmdOk}, nothing happens.
+?*/
+void
+UchTextStream :: ProcessCmdResult (cmd_res res, const UchTextLine& line)
+{
+ const char* msg = 0;
+ switch (res) {
+ case isCmdSyntax:
+ msg = "syntax error in <%s>";
+ break;
+ case isCmdUnknown:
+ msg = "unknown command: <%s>";
+ break;
+ case isCmdOk:
+ return;
+ case isCmdError:
+ msg = "error while executing command <%s>";
+ break;
+ case isCmdClose:
+ // like eof (but smarter for the session)
+ Close ();
+ return;
+ case isCmdQuit:
+ //quit application
+ Quit ();
+ return;
+ case isCmdTerminate:
+ // terminate the multiplexer
+ if (MyMpx)
+ MyMpx->LoopEnd ();
+ return;
+ case isCmdAbort:
+ // abort the multiplexer
+// MpxAbort ();
+ return;
+ case isCmdExit:
+ exit (1);
+ }
+ if (msg) {
+ char buf [64];
+ line.Unparse (buf, sizeof (buf));
+ fprintf (stderr, "UchTextStream::ProcessCmdResult\n", msg, buf);
+ }
+}
+
+/*?
+If \var{line} is one of the words
+\com{Close}, \com{Quit}, \com{Terminate}, \com{Abort}, \com{Exit},
+the corresponding code is returned (\var{isCmdClose}, etc.),
+otherwise, \var{isCmdUnknown} is returned.
+?*/
+UchTextStream::cmd_res
+UchTextStream :: TryPredefined (const UchTextLine& line)
+{
+ const char* cname = line [0];
+
+ if (line.NumWords () != 1)
+ return isCmdUnknown;
+ switch (windex (":Close:Quit:Terminate:Abort:Exit:", cname)) {
+ case 1: return isCmdClose;
+ case 2: return isCmdQuit;
+ case 3: return isCmdTerminate;
+ case 4: return isCmdAbort;
+ case 5: return isCmdExit;
+ }
+ return isCmdUnknown;
+}
+
+/*?
+This virtual function is called by the members that send commands on the stream.
+By defaults, it writes the output buffer on the stream.
+Derived classes can redefine this function.
+?*/
+void
+UchTextStream :: DoSend ()
+{
+ Write (OutBuffer);
+}
+
+/*?
+This virtual function is called by \fun{ProcessCmdResult} when a \com{close}
+command has been received.
+It can be called directly by the application to close down the connection
+(remember that the destructor is private and cannot be called directly).
+By default it calls \fun{MpxRemoveChannel} to unregister this stream from the
+multiplexer.
+This will trigger deletion of the stream is the multiplexer holds the last
+smart pointer to it.
+Derived classes can redefine this function, but the redefinition
+should call the default implementation.
+?*/
+void
+UchTextStream :: Close ()
+{
+ MyMpx->Remove (*this);
+}
+
+/*?
+This virtual function is called by \fun{ProcessCmdResult} when a \com{quit}
+command has been received.
+By default is calls \fun{MpxTerminate}.
+Derived classes can redefine this function if they want to terminate more
+gracefully.
+?*/
+void
+UchTextStream :: Quit ()
+{
+ MyMpx->LoopEnd ();
+}
+
+//---------------- ServiceStarter
+
+class UchServiceStarter : public UchBaseTimeOut {
+friend class UchTextService;
+
+protected:
+ UchTextService* Service;
+ int Retries;
+ int Max;
+
+public:
+ UchServiceStarter (UchTextService*, int, int);
+ void Handle (Millisecond);
+};
+
+UchServiceStarter :: UchServiceStarter (UchTextService* s, int retry, int maxr)
+: UchBaseTimeOut (*s->GetMultiplexer (), retry * 1000),
+ Service (s),
+ Retries (0),
+ Max (maxr)
+{
+}
+
+void
+UchServiceStarter :: Handle (Millisecond)
+{
+ /* UchServiceStarters are not timers triggered on signals.
+ For that reason, Handle is allowed to be complex. */
+
+ if (! Service)
+ return;
+
+ fprintf (stderr, "ServiceStarter::TimeOut, attempting restart\n");
+ if (Service->GetStatus () == UchTextService::isRunning
+ || Service->Restart () == UchTextService::isRunning) {
+ Stop ();
+ Service->Starter = 0;
+ delete this;
+ return;
+ }
+
+ if (++Retries >= Max) {
+ Stop ();
+ if (Max <= 1) {
+ fprintf (stderr, "ServiceStarter::TimeOut, abandoning restart\n");
+ Service->Starter = 0;
+ Service->AbandonRestart ();
+ delete this;
+ } else {
+ Max--;
+ Retries = 0;
+ ChangePeriod (GetPeriod () * 2);
+ Restart ();
+ }
+ }
+}
+
+
+/*?
+Construct an empty \var{UchTextService}.
+When using this constructor, the application must call \fun{Init} at a later
+time in order to define the server to connect to.
+The initial state of the service is \var{isLost}, since the actual
+connection to the server is not established yet.
+?*/
+UchTextService :: UchTextService ()
+: UchTextStream (),
+ StatusFlag (isLost),
+ Starter (0),
+ Closed (FALSE),
+ User (),
+ Service (),
+ Host ()
+{
+}
+
+/*?
+Construct a \var{UchTextService} connected to server \var{s} on host \var{h}.
+This is equivalent to the empty constructor followed by a call to \fun{Init}.
+?*/
+UchTextService :: UchTextService (const char* s, const char* h)
+: UchTextStream (),
+ StatusFlag (isLost),
+ Starter (0),
+ Closed (FALSE),
+ User (),
+ Service (),
+ Host ()
+{
+ Init (s, h);
+}
+
+/*?
+The (protected) destructor.
+Call \fun{Close} or \fun{CloseNow} to close down the connection and
+delete the service.
+?*/
+UchTextService :: ~UchTextService ()
+{
+ if (Starter)
+ delete Starter;
+}
+
+/*?
+This protected virtual function is called by \fun{UchTextStream::HandleRead}
+when an end-of-file is read or when a read failed.
+It calls \var{LostServer} so that an attempt to reconnect to the server
+can be made.
+?*/
+void
+UchTextService :: Closing (bool)
+{
+ fprintf (stderr, "UchTextService::Closing, lost server\n");
+ StatusFlag = isLost;
+ LostServer ();
+}
+
+/*?
+Attempt to connect to the server.
+If the address of the server cannot be retrieved from the port server,
+this function sets the state of the service to \var{isUnavailable}
+and returns.
+If an address is obtained from the port server, this function tries to connect
+to that address.
+If this fails, it sets the state to \var{isError} and returns.
+Otherwise, it sets the state to \var{isRunning} and calls the virtual
+function \fun{GotServer}.
+The service is automatically registered with the multiplexer
+(by calling \fun{MpxAddChannel} with mode \var{IONone})
+the first time the connection is established.
+?*/
+UchTextService::status
+UchTextService :: Restart ()
+{
+ UchInetAddress* addr = PortServerInquire ("portserv", Service, Host);
+ if (! addr) {
+ fprintf (stderr, "UchTextService::Restart, no address in port server\n");
+ return StatusFlag = isUnavailable;
+ }
+ ConnectTo (addr);
+
+ bool wasopened = Fd.Opened ();
+ bool setup = TRUE;
+ if (wasopened) {
+ // we are in trouble because a stream socket can be connected
+ // only once and a channel is immutable...
+ // so we have to fake a reopen
+ int ofd = FilDes ();
+ if (ofd >= 0) {
+ close (ofd);
+ int nfd = socket (ConnectedTo () ->Family (), SockType (), 0);
+ fprintf (stderr, "UchTextService::Restart, reopening socket: %d -> %d\n", (void*) ofd, (void*) nfd);
+ if (nfd < 0) {
+ setup = FALSE;
+ } else if (nfd != ofd) {
+ dup2 (nfd, ofd);
+ close (nfd);
+ }
+ }
+ }
+ if (setup)
+ setup = Setup ();
+ if (! wasopened) {
+ SetMode (IONone);
+ MyMpx->Add (this);
+ }
+ if (! setup) {
+ fprintf (stderr, "UchTextService::Restart, could not setup connection\n");
+ return StatusFlag = isError;
+ }
+
+ StatusFlag = isRunning;
+ if (Starter) // do not log if got server at first attempt
+ fprintf (stderr, "UchTextService::Restart, got server\n");
+ GotServer ();
+ return StatusFlag;
+}
+
+/*?
+This protected function arranges for a connection to be attempted in
+\var{retry_time} seconds.
+If the connection cannot be established after \var{maxr} retries,
+\var{retry_time} is doubled and \var{maxr} is decremented and new attempts
+are made with these new values.
+When the number of retries is null, then the virtual function
+\fun{AbandonRestart} is called and no further attempts are made.
+If \var{retry_time} is 0, any pending attempts are cancelled.
+If \var{retry_time} or var{maxr} is negative, suitable default values are used.
+?*/
+void
+UchTextService :: AutoStart (int retry_time, int maxr)
+{
+ if (retry_time < 0)
+ retry_time = RETRY_TIME;
+ if (maxr < 0)
+ maxr = MAX_RETRIES;
+
+ if (retry_time == 0) { // cancel auto-start
+ if (Starter) {
+ delete Starter;
+ Starter = 0;
+ }
+ return;
+ }
+ if (Starter) {
+ Starter->ChangePeriod (retry_time);
+ Starter->Retries = 0;
+ Starter->Max = maxr;
+ } else if (MyMpx) {
+ Starter = new UchServiceStarter (this, retry_time, maxr);
+ } else
+ fprintf (stderr, "UchTextService::AutoStart, no multiplexer for time-out\n");
+}
+
+/*?
+This protected virtual function is called when the connection to the server
+has been broken.
+By default, it resets the mode of the channel to \var{IONone} so that the
+multiplexer does not attempt to read from the server.
+Derived classes can redefine this function, but the redefinition should
+call the default implementation.
+?*/
+void
+UchTextService :: LostServer ()
+{
+ MyMpx->SetMode (*this, IONone);
+ AutoStart ();
+}
+
+/*?
+This protected virtual function is called when the connection to the server
+has been established.
+By default, it sets the mode of the channel to \var{IORead} so that the
+multiplexer can monitor the channel.
+If the output buffer is not empty, it sends it to the server by calling
+\fun{DoSend}.
+Derived classes can redefine this function, but the redefinition should
+call the default implementation.
+?*/
+void
+UchTextService :: GotServer ()
+{
+ MyMpx->SetMode (*this, IORead);
+ if (OutBuffer.BufLength ())
+ DoSend ();
+}
+
+/*?
+This protected virtual function is called when the connection cannot
+be restarted (see \fun{AutoStart}).
+By default it calls \fun{CloseNow} which should ultimately destroy the object.
+A derived class can redefine this function to take whatever clean-up action
+is required.
+?*/
+void
+UchTextService :: AbandonRestart ()
+{
+ CloseNow ();
+}
+
+/*?
+This implementation of the protected virtual function \fun{UchTextStream::Execute}
+always return \var{isCmdOk}.
+This is suitable for the global function \var{TellServer}.
+Derived classes generally do redefine this function.
+?*/
+UchTextStream::cmd_res
+UchTextService :: Execute (const UchTextLine&)
+{
+ // just discard received request by default
+ // (suitable for TellServer below)
+ return isCmdOk;
+}
+
+/*?
+This implementation of the protected virtual function \fun{UchTextStream::DoSend}
+writes the output buffer only if the state of the connection is
+\var{isRunning}.
+Otherwise, the outgoing requests accumulate in the ouput buffer until the
+connection is established.
+This function also closes down the connection by calling \var{MpxRemoveChannel}
+if \fun{Close} has been called before the connection ws actually established.
+?*/
+void
+UchTextService :: DoSend ()
+{
+ if (StatusFlag == isRunning) {
+ Write (OutBuffer);
+ if (Closed)
+ MyMpx->Remove (*this);
+ }
+}
+
+/*?
+Define the server and host to connect to. This function must be used
+if the object was constructed with the default constructor.
+If the connection to the server cannot be established, \fun{Init} calls
+\var{AutoStart} to attempt the connection later.
+?*/
+void
+UchTextService :: Init (const char* s, const char* h)
+{
+ if (StatusFlag == isRunning)
+ return;
+
+ Service = s;
+ Host = h;
+
+ StatusFlag = Restart ();
+ if (StatusFlag != isRunning)
+ AutoStart ();
+}
+
+/*?
+Return the current retry time for the auto-restart of the connection.
+If the connection is not in auto-restart mode, return -1;
+?*/
+int
+UchTextService :: GetRetryTime ()
+{
+ return Starter ? Starter->GetPeriod () / 1000 : -1;
+}
+
+/*?
+Return the current maximum number of retries for the auto-restart of the connection.
+If the connection is not in auto-restart mode, return -1;
+?*/
+int
+UchTextService :: GetMaxRetries ()
+{
+ return Starter ? Starter->Max : -1;
+}
+
+/*?
+Remove this service from the multiplexer if the connection is established
+or if the output buffer is empty.
+Otherwise, mark the connection as being closed;
+in this case, the output buffer will be sent when the connection is etablished,
+and then the connection will be closed.
+?*/
+void
+UchTextService :: Close ()
+{
+ Closed = TRUE;
+ if (StatusFlag == isRunning || OutBuffer.BufLength () == 0)
+ MyMpx->Remove (*this);
+}
+
+/*?
+Remove this service from the multiplexer, whatever
+the state of the connection is.
+?*/
+void
+UchTextService :: CloseNow ()
+{
+ MyMpx->Remove (*this);
+}
+
+/*?
+This global function provides the simplest interface to send a request \var{req}
+to a server \var{sname} on host \var{host}.
+?*/
+bool
+TellServer (const char* sname, const char* host, const char* req)
+{
+ UchTextService* serv = new UchTextService;
+ serv->Init (sname, host);
+ serv->Send (req);
+ serv->Close ();
+ return TRUE;
+}