From ba066c34dde204aa192d03a23a81356374d93731 Mon Sep 17 00:00:00 2001 From: chatty Date: Wed, 7 Apr 1993 11:50:31 +0000 Subject: Initial revision --- comm/OLD/TextStream.cc | 1030 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1030 insertions(+) create mode 100644 comm/OLD/TextStream.cc (limited to 'comm/OLD/TextStream.cc') 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 +#include +#include +#include + +// 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; +} -- cgit v1.1