-- File: HostWatcherPoke.mesa,  Last Edit: HGM  March 28, 1981  3:32 PM

DIRECTORY
  Ascii USING [CR, LF, SP],
  InlineDefs USING [BcplLongNumber, BcplToMesaLongNumber],
  String USING [
    AppendString, AppendChar, WordsForString, EquivalentString, AppendLongNumber],
  Time USING [Current],

  Librarian USING [PropertyList, PropertyPair, PropertyNumber],
  LibrarianPN USING [IDName, StringName],
  LibrarianOps USING [LibjectMessage, LibjectMessageType, ValidLibrarianMessage],

  Stream USING [Delete, GetChar, Handle, TimeOut],
  FTPDefs USING [
    FTPUser, FTPCreateUser, FTPDestroyUser, FTPError, FTPInitialize, FTPFinalize,
    FTPOpenConnection, SomeFilePrimitives, PupCommunicationPrimitives],
  GateControlDefs USING [
    gateControlStatsSend, gateControlStatsAck, GateControlStatsEntry],
  PupStream USING [PupByteStreamCreate, StreamClosing],
  PupDefs USING [
    GetFreePupBuffer, ReturnFreePupBuffer, PupBuffer, PupSocket, PupSocketDestroy,
    PupSocketMake, SecondsToTocks, SetPupContentsWords, GetPupContentsBytes,
    veryLongWait],
  PupTypes USING [fillInSocketID, PupAddress, PupType],
  HostWatcherOps USING [Info, ShowErrorPup, State];

HostWatcherPoke: PROGRAM
  IMPORTS
    String, Time, InlineDefs, Stream, FTPDefs, PupDefs, PupStream, HostWatcherOps
  EXPORTS HostWatcherOps =
  BEGIN OPEN PupDefs, PupTypes;

  Info: TYPE = HostWatcherOps.Info;

  debug: BOOLEAN ← FALSE;

  PokeGateway: PUBLIC PROCEDURE [info: Info] =
    BEGIN
    b: PupBuffer ← NIL;
    packetNumber: CARDINAL ← NextSequenceNumber[];
    mySoc: PupSocket ← PupSocketMake[
      fillInSocketID, info.address, SecondsToTocks[5]];
    tries: CARDINAL ← 10;
    THROUGH [0..tries) DO
      b ← GetFreePupBuffer[];
      b.pupID.a ← 27182;
      b.pupID.b ← (packetNumber ← packetNumber + 1);
      b.pupType ← GateControlDefs.gateControlStatsSend;
      SetPupContentsWords[b, 0];
      mySoc.put[b];
      UNTIL (b ← mySoc.get[]) = NIL DO
        SELECT TRUE FROM
          (b.pupType = error AND b.errorCode = noProcessPupErrorCode) =>
            BEGIN
            FOR i: CARDINAL IN [0..GetPupContentsBytes[b] - 2*(10 + 1 + 1)) DO
              String.AppendChar[info.text, b.errorText[i]]; ENDLOOP;
            HostWatcherOps.ShowErrorPup[b];
            GOTO Rejecting;
            END;
          (b.pupType = error) => HostWatcherOps.ShowErrorPup[b];
          (b.pupType = GateControlDefs.gateControlStatsAck)
            AND (b.pupID.b = packetNumber) =>
            BEGIN
            gse: LONG POINTER TO GateControlDefs.GateControlStatsEntry;
            gse ← LOOPHOLE[@b.pupBody];
            FOR i: CARDINAL IN [0..gse.versionText.length) DO
              String.AppendChar[info.text, gse.versionText.char[i]]; ENDLOOP;
            AppendUpTime[info.text, gse.startTime];
            GOTO Up;
            END;
          ENDCASE => NULL;
        ReturnFreePupBuffer[b];
        b ← NIL;
        ENDLOOP;
      REPEAT
        Rejecting => info.state ← rejecting;
        Up => info.state ← up;
        FINISHED => info.state ← timeout;
      ENDLOOP;
    IF b # NIL THEN ReturnFreePupBuffer[b];
    PupSocketDestroy[mySoc];
    END;

  AppendUpTime: PROCEDURE [s: STRING, startTime: InlineDefs.BcplLongNumber] =
    BEGIN
    now, then: LONG INTEGER;
    sec: LONG INTEGER;
    min: LONG INTEGER;
    hours: LONG INTEGER;
    String.AppendString[s, " up "L];
    then ← InlineDefs.BcplToMesaLongNumber[startTime];
    now ← Time.Current[];
    sec ← now - then;
    IF sec < 0 THEN
      BEGIN  -- Startup glitch
      sec ← -sec;
      String.AppendChar[s, '-];
      END;
    hours ← sec/3600;
    sec ← sec - hours*3600;
    min ← sec/60;
    sec ← sec - min*60;
    String.AppendLongNumber[s, hours, 10];
    String.AppendChar[s, ':];
    IF min < 10 THEN String.AppendChar[s, '0];
    String.AppendLongNumber[s, min, 10];
    String.AppendChar[s, ':];
    IF sec < 10 THEN String.AppendChar[s, '0];
    String.AppendLongNumber[s, sec, 10];
    END;

  PokeChat: PUBLIC PROCEDURE [info: Info] =
    BEGIN
    sh: Stream.Handle ← NIL;
    state: HostWatcherOps.State ← up;
    BEGIN
    ENABLE
      BEGIN
      PupStream.StreamClosing =>
        BEGIN
        IF text # NIL THEN String.AppendString[info.text, text];
        SELECT why FROM
          transmissionTimeout => state ← timeout;
          remoteReject => state ← rejecting;
          ENDCASE =>
            BEGIN
            LookAtThis: SIGNAL = CODE;
            IF debug THEN SIGNAL LookAtThis;
            state ← unknown;
            END;
        IF CheckForFull[info.text] THEN state ← full;
        CONTINUE;
        END;
      Stream.TimeOut => {state ← timeout; CONTINUE; }
      END;
    sh ← PupStream.PupByteStreamCreate[info.address, PupDefs.veryLongWait];
    IF sh # NIL THEN
      BEGIN
      DO
        c: CHARACTER ← Stream.GetChar[sh];
        IF c = Ascii.LF THEN LOOP;
        IF c = Ascii.CR THEN {IF info.text.length = 0 THEN LOOP ELSE EXIT; };
        String.AppendChar[info.text, c];
        ENDLOOP;
      Stream.Delete[sh];
      END;
    END;
    info.state ← state;
    END;

  CheckForFull: PROCEDURE [text: STRING] RETURNS [full: BOOLEAN] =
    BEGIN
    IF String.EquivalentString[text, "RFC Refused"L] THEN RETURN[TRUE];  -- Alto: FTP.run
    IF String.EquivalentString[text, "Server full, try again later"L] THEN
      RETURN[TRUE];  -- Maxc
    IF String.EquivalentString[text, "No dial-out lines available"L] THEN
      RETURN[TRUE];  -- DLS
    IF String.EquivalentString[text, "Server is full"L] THEN RETURN[TRUE];  -- Juniper
    IF String.EquivalentString[text, "IFS is full - try later"L] THEN
      RETURN[TRUE];
    IF String.EquivalentString[text, "Server full"L] THEN RETURN[TRUE];  -- Grapevine
    IF String.EquivalentString[text, "Sorry, we are full now"L] THEN RETURN[TRUE];
    -- Gateway
    RETURN[FALSE];
    END;

  PokeFtp: PUBLIC PROCEDURE [info: Info] =
    BEGIN OPEN FTPDefs;
    ftpUser: FTPUser ← NIL;
    state: HostWatcherOps.State ← up;
    FTPInitialize[];
    ftpUser ← FTPCreateUser[SomeFilePrimitives[], PupCommunicationPrimitives[]];
    FTPOpenConnection[
      ftpUser, info.name, files, info.text !
      FTPError =>
        BEGIN
        IF message # NIL THEN String.AppendString[info.text, message];
        SELECT ftpError FROM
          connectionTimedOut => state ← timeout;
          connectionRejected => state ← rejecting;
          ENDCASE =>
            BEGIN
            LookAtThis: SIGNAL = CODE;
            IF debug THEN SIGNAL LookAtThis;
            state ← unknown;
            END;
        IF CheckForFull[info.text] THEN state ← full;
        CONTINUE;
        END];
    info.state ← state;
    FTPDestroyUser[ftpUser ! FTPError => CONTINUE];
    FTPFinalize[];
    END;

  PokeMail: PUBLIC PROCEDURE [info: Info] =
    BEGIN OPEN FTPDefs;
    ftpUser: FTPUser ← NIL;
    state: HostWatcherOps.State ← up;
    FTPInitialize[];
    ftpUser ← FTPCreateUser[NIL, PupCommunicationPrimitives[]];
    FTPOpenConnection[
      ftpUser, info.name, mail, info.text !
      FTPError =>
        BEGIN
        IF message # NIL THEN String.AppendString[info.text, message];
        SELECT ftpError FROM
          connectionTimedOut => state ← timeout;
          connectionRejected => state ← rejecting;
          ENDCASE =>
            BEGIN
            LookAtThis: SIGNAL = CODE;
            IF debug THEN SIGNAL LookAtThis;
            state ← unknown;
            END;
        IF CheckForFull[info.text] THEN state ← full;
        CONTINUE;
        END];
    info.state ← state;
    FTPDestroyUser[ftpUser ! FTPError => CONTINUE];
    FTPFinalize[];
    END;

  PokeLibrarian: PUBLIC PROCEDURE [info: Info] =
    BEGIN OPEN Librarian, LibrarianPN, LibrarianOps;
    request: LibjectMessageType = FindID;
    b: PupBuffer;
    message: LONG POINTER TO LibjectMessage;
    plist: LONG POINTER TO ARRAY [0..2) OF PropertyPair;
    transactionID: CARDINAL ← NextSequenceNumber[];
    socket: PupSocket;
    socket ← PupSocketMake[fillInSocketID, info.address, SecondsToTocks[15]];
    FOR i: CARDINAL IN [0..5) DO
      b ← GetFreePupBuffer[];
      b.pupID ← [transactionID, i];
      message ← LOOPHOLE[@b.pupWords];
      message.id ← transactionID;
      message.password ← ValidLibrarianMessage;
      message.type ← request;
      message.plist ← DESCRIPTOR[NIL, 2];
      plist ← LOOPHOLE[message + SIZE[LibjectMessage]];
      message.nextfree ← LOOPHOLE[SIZE[LibjectMessage] + 2*SIZE[PropertyPair]];
      BEGIN
      COPY: PROCEDURE [from: POINTER, nwords: CARDINAL, to: LONG POINTER] =
        BEGIN
        FOR i: CARDINAL IN [0..nwords) DO (to + i)↑ ← (from + i)↑; ENDLOOP;
        END;
      string: STRING = "HostWatcher"L;
      wordsforstring: CARDINAL = String.WordsForString[string.length];
      dataArea: LONG POINTER ← message + LOOPHOLE[message.nextfree, CARDINAL];
      plist[0] ← PropertyPair[
        empty: FALSE, pn: StringName,
        body: String[
        length: string.length, string: LOOPHOLE[2*SIZE[PropertyPair]]]];
      COPY[from: string, nwords: wordsforstring, to: dataArea];
      message.nextfree ← message.nextfree + wordsforstring;
      END;
      plist[1] ← PropertyPair[empty: TRUE, pn: IDName, body: TwoWord[[lc[0]]]];
      SetPupContentsWords[b, LOOPHOLE[message.nextfree, INTEGER]];
      socket.put[b];
      UNTIL (b ← socket.get[]) = NIL DO
        message ← LOOPHOLE[@b.pupWords];
        SELECT TRUE FROM
          (b.pupType = error AND b.errorCode = noProcessPupErrorCode) =>
            BEGIN
            i: CARDINAL;
            FOR i IN [0..GetPupContentsBytes[b] - 2*(10 + 1 + 1)) DO
              String.AppendChar[info.text, b.errorText[i]]; ENDLOOP;
            HostWatcherOps.ShowErrorPup[b];
            GOTO Rejecting;
            END;
          (b.pupType = error) => HostWatcherOps.ShowErrorPup[b];
          message.password = ValidLibrarianMessage AND message.id = transactionID
            => GOTO Up;
          ENDCASE => NULL;  -- I wonder what this is
        ReturnFreePupBuffer[b];
        b ← NIL;
        ENDLOOP;
      REPEAT
        Rejecting => info.state ← rejecting;
        Up => info.state ← up;
        FINISHED => info.state ← timeout;
      ENDLOOP;
    IF b # NIL THEN ReturnFreePupBuffer[b];
    PupSocketDestroy[socket];
    END;

  PokeSpruce: PUBLIC PROCEDURE [info: Info] =
    BEGIN
    spruceStatusRequest: PupType = LOOPHOLE[200B];
    spruceStatusReply: PupType = LOOPHOLE[201B];
    b: PupBuffer ← NIL;
    packetNumber: CARDINAL ← NextSequenceNumber[];
    mySoc: PupSocket ← PupSocketMake[
      fillInSocketID, info.address, SecondsToTocks[5]];
    THROUGH [0..20) DO
      b ← GetFreePupBuffer[];
      b.pupID.a ← b.pupID.b ← packetNumber;
      b.pupType ← spruceStatusRequest;
      SetPupContentsWords[b, 0];
      mySoc.put[b];
      UNTIL (b ← mySoc.get[]) = NIL DO
        SELECT TRUE FROM
          (b.pupType = error AND b.errorCode = noProcessPupErrorCode) =>
            BEGIN
            FOR i: CARDINAL IN [0..GetPupContentsBytes[b] - 2*(10 + 1 + 1)) DO
              String.AppendChar[info.text, b.errorText[i]]; ENDLOOP;
            HostWatcherOps.ShowErrorPup[b];
            GOTO Rejecting;
            END;
          (b.pupType = error) => HostWatcherOps.ShowErrorPup[b];
          ((b.pupType # spruceStatusReply) OR (b.pupID.a # packetNumber)
            OR (b.pupID.b # packetNumber)) => NULL;
          ENDCASE =>
            BEGIN
            FOR i: CARDINAL IN [2..GetPupContentsBytes[b] - 1) DO
              c: CHARACTER ← b.pupChars[i];
              IF c = Ascii.CR THEN c ← Ascii.SP;
              String.AppendChar[info.text, c];
              ENDLOOP;
            SELECT b.pupWords[0] FROM
              1 => GOTO Down;  -- Not Spooling
              2 => GOTO Up;  -- Spooler is idle
              3 => GOTO Up;  -- Spooler is busy
              ENDCASE => NULL;
            GOTO Up;
            END;
        ReturnFreePupBuffer[b];
        b ← NIL;
        ENDLOOP;
      REPEAT
        Rejecting => info.state ← rejecting;
        Down => info.state ← down;
        Up => info.state ← up;
        FINISHED => info.state ← timeout;
      ENDLOOP;
    IF b # NIL THEN ReturnFreePupBuffer[b];
    PupSocketDestroy[mySoc];
    END;

  PokeEftp: PUBLIC PROCEDURE [info: Info] =
    BEGIN
    b: PupBuffer ← NIL;
    mySoc: PupSocket ← PupSocketMake[
      fillInSocketID, info.address, SecondsToTocks[5]];
    THROUGH [0..20) DO
      b ← GetFreePupBuffer[];
      b.pupID.a ← b.pupID.b ← 0;
      b.pupType ← eData;
      SetPupContentsWords[b, 0];
      mySoc.put[b];
      UNTIL (b ← mySoc.get[]) = NIL DO
        SELECT TRUE FROM
          (b.pupType = eAbort) =>
            BEGIN
            FOR i: CARDINAL IN [0..GetPupContentsBytes[b] - 2) DO
              String.AppendChar[info.text, b.pupChars[i + 2]]; ENDLOOP;
            GOTO Rejecting;
            END;
          (b.pupType = error AND b.errorCode = noProcessPupErrorCode) =>
            BEGIN
            FOR i: CARDINAL IN [0..GetPupContentsBytes[b] - 2*(10 + 1 + 1)) DO
              String.AppendChar[info.text, b.errorText[i]]; ENDLOOP;
            HostWatcherOps.ShowErrorPup[b];
            GOTO Rejecting;
            END;
          (b.pupType = error) => HostWatcherOps.ShowErrorPup[b];
          ((b.pupType = eAck) AND (b.pupID.a = 0) AND (b.pupID.b = 0)) => GOTO Up;
          ENDCASE => HostWatcherOps.ShowErrorPup[b];
        ReturnFreePupBuffer[b];
        b ← NIL;
        ENDLOOP;
      REPEAT
        Rejecting => info.state ← rejecting;
        Up => info.state ← up;
        FINISHED => info.state ← timeout;
      ENDLOOP;
    IF b # NIL THEN ReturnFreePupBuffer[b];
    PupSocketDestroy[mySoc];
    END;

  sequenceNumber: CARDINAL ← 0;
  NextSequenceNumber: PROCEDURE RETURNS [CARDINAL] =
    BEGIN RETURN[sequenceNumber ← sequenceNumber + 1]; END;

  END.