-- file: STPsD.mesa - Simple/Stream Transfer Protocol 
-- Common and protocol stuff in here
-- Edited by:
-- Mark on: Feb 12, 1981 11:39 PM
-- Smokey on: 11-Mar-81 14:51:17
-- Karlton on: Oct 10, 1980 5:24 PM
-- Evans on: Nov 13, 1980 2:10 PM
-- Levin on:  8-Mar-82 15:59:49

DIRECTORY
  Ascii USING [SP],
  HeapString USING [AppendChar, AppendString, Replace],
  PupStream USING[StreamClosing],
  Storage USING [
    CopyString, EmptyString, Free, FreeString, FreeStringNil, String],
  STP USING [Error, ErrorCode, FileInfo, FileType, Open],
  STPOps USING [
    DestroyPList, ErrorCodeToSTPErrorCode, FileProperties, FilenameType,
    GenerateErrorString, GenerateProtocolError, Handle, markAbort, markComment, markEOC,
    markHereIsPList, markIAmVersion, markNo, markYes, markYouAreUser,
    maxStringLength, Object, PList, PListArray, ServerType, SmashClosed,
    UserProperties, ValidProperties],
  Stream USING [
    Block, GetChar, Handle, Object, PutBlock, PutChar, SetSST,
    SSTChange, SubSequenceType, TimeOut],
  String USING [EquivalentString, StringToLongNumber],
  Time USING [Append, Packed, Unpack];
  
STPsD: MONITOR
  IMPORTS
    HeapString, PupStream, Storage, STP, STPOps, Stream, String, Time
  EXPORTS STP, STPOps =
  BEGIN OPEN STPOps;
  
  -- Exported Types
  
  Object: PUBLIC TYPE = STPOps.Object;
  
  -- Global Data
  
  MarkEncountered: PUBLIC ERROR = CODE;
  BadProperty: PUBLIC ERROR = CODE;
  propertyStrings: PListArray = [
    userName: "User-Name", userPassword: "User-Password",
    connectName: "Connect-Name", connectPassword: "Connect-Password",
    byteSize: "Byte-Size", type: "Type", size: "Size", directory: "Directory",
    nameBody: "Name-Body", version: "Version", createDate: "Creation-Date",
    readDate: "Read-Date", writeDate: "Write-Date", author: "Author",
    eolConversion: "End-of-Line-Convention", account: "Account",
    userAccount: "User-Account", device: "Device",
    serverName: "Server-Filename"];
    
-- Commonly used stuff  

  Connect: PUBLIC PROCEDURE [stp: STPOps.Handle, name, password: STRING] =
    BEGIN
    HeapString.Replace[@stp.userState[connectName], name];
    HeapString.Replace[@stp.userState[connectPassword], password];
    END;
    
  Login: PUBLIC PROCEDURE [stp: STPOps.Handle, name, password: STRING] =
    BEGIN
    HeapString.Replace[@stp.userState[userName], name];
    HeapString.Replace[@stp.userState[userPassword], password];
    END;
    
  SetHost: PUBLIC PROCEDURE [stp: STPOps.Handle, host: STRING] =
    BEGIN HeapString.Replace[@stp.host, host]; END;
    
  SetDirectory: PUBLIC PROCEDURE [stp: STPOps.Handle, directory: STRING] =
    BEGIN HeapString.Replace[@stp.userState[directory], directory]; END;
    
-- Procedures for doing FTP protocol operations  

  GetCommand: PUBLIC PROCEDURE [stp: STPOps.Handle, ps: POINTER TO STRING]
    RETURNS [mark: Stream.SubSequenceType, code: CHARACTER] =
    BEGIN
    code ← 0C;
    CheckConnection[stp];
    mark ← MyGetMark[stp];
    SELECT mark FROM
      markAbort, markComment, markYouAreUser => CollectString[stp, ps];
      markIAmVersion, markNo, markYes => {code ← CollectCode[stp]; CollectString[stp, ps]};
      ENDCASE => GenerateProtocolError[badMark, mark, code];
    RETURN[mark, code]
    END;
    
  GetHereIsAndPList: PUBLIC PROCEDURE [stp: STPOps.Handle, gobbleEOC: BOOLEAN ← TRUE] =
    BEGIN
    mark: Stream.SubSequenceType;
    DO
      SELECT (mark ← MyGetMark[stp]) FROM
	markComment => CollectString[stp, @stp.remoteString];
	markHereIsPList => {GetPList[stp, gobbleEOC]; EXIT};
	markNo =>
	  BEGIN
	  code: CHARACTER = CollectCode[stp];
	  errorCode: STP.ErrorCode =
	    ErrorCodeToSTPErrorCode[requestRefused, code]; 
	  CollectString[stp, @stp.remoteString];
	  ErrorIfNextNotEOC[stp];
	  GenerateErrorString[errorCode, stp.remoteString, code];
	  END;
	ENDCASE => GenerateProtocolError[badMark, mark, 0C];
      ENDLOOP;
    END;
    
  GetPList: PUBLIC PROCEDURE [stp: STPOps.Handle, gobbleEOC: BOOLEAN ← TRUE] =
    BEGIN
    property: STRING ← Storage.String[maxStringLength];
    value: STRING ← Storage.String[maxStringLength];
    mark: Stream.SubSequenceType;
    CheckConnection[stp];
    BEGIN ENABLE UNWIND => {Storage.FreeString[property]; Storage.FreeString[value]};
    parens: INTEGER ← 0;
    char: CHARACTER;
    string: STRING ← property;
    DO
      char ← MyGetChar[stp ! MarkEncountered => GOTO BadPList];
      SELECT char FROM
	'( =>
	  BEGIN parens ← parens + 1; string ← property; string.length ← 0; END;
	Ascii.SP =>
	  IF string = property THEN BEGIN string ← value; string.length ← 0; END
	  ELSE HeapString.AppendChar[@string, char];
	') =>
	  BEGIN
	  IF property.length # 0 AND value.length # 0 THEN
	    BEGIN
	    SetPListItem[
	      stp.plist, property, value ! BadProperty => GOTO BadPList];
	    property.length ← value.length ← 0;
	    END;
	  IF (parens ← parens - 1) = 0 THEN EXIT;
	  END;
	ENDCASE => HeapString.AppendChar[@string, char];
      ENDLOOP;
    IF (property.length # 0 AND value.length # 0) THEN GOTO BadPList;
    IF gobbleEOC AND MyGetMark[stp] # markEOC THEN GOTO BadPList;
    EXITS
      BadPList => {ResetPList[stp.plist]; GenerateProtocolError[badPList, mark]};
    END;
    Storage.FreeString[property];
    Storage.FreeString[value];
    END;
    
  PutCommand: PUBLIC PROCEDURE [
    stp: STPOps.Handle, mark: Stream.SubSequenceType, code: CHARACTER,
    string: STRING, sendEOC: BOOLEAN ← TRUE] =
    BEGIN
    CheckConnection[stp];
    Stream.SetSST[stp.byteStream, mark];
    Stream.PutChar[stp.byteStream, code];
    IF ~Storage.EmptyString[string] THEN MyPutString[stp.byteStream, string];
    IF sendEOC THEN Stream.SetSST[stp.byteStream, markEOC];
    END;
    
  PutPList: PUBLIC PROCEDURE [
    stp: STPOps.Handle, mark: Stream.SubSequenceType, sendEOC: BOOLEAN ← TRUE] =
    BEGIN
    state: {okay, tryOpen, triedOpen} ← okay;
    DoIt: PROCEDURE = 
      BEGIN
      CheckConnection[stp];
      Stream.SetSST[stp.byteStream, mark];
      Stream.PutChar[stp.byteStream, '(];
      FOR i: ValidProperties IN ValidProperties DO
	IF ~Storage.EmptyString[stp.plist[i]] THEN
	  PutPListItem[stp.byteStream, i, stp.plist[i]];
	ENDLOOP;
      Stream.PutChar[stp.byteStream, ')];
      IF sendEOC THEN Stream.SetSST[stp.byteStream, markEOC];
      END;
    DO
      DoIt[! PupStream.StreamClosing, Stream.TimeOut =>
	IF state = okay AND ~Storage.EmptyString[stp.host] THEN
	  {state ← tryOpen; CONTINUE}];
      IF state # tryOpen THEN EXIT;
      BEGIN
      savedPList: PList ← stp.plist;
      stp.plist ← NIL;
      SmashClosed[stp];  -- Call outside catch so Pup monitor unlocked
      Storage.FreeString[STP.Open[stp, stp.host]];
      stp.plist ← DestroyPList[stp.plist];
      stp.plist ← savedPList;
      state ← triedOpen;
      END;
      ENDLOOP;
    END;
    
-- Utility routines

  CollectString: PUBLIC PROCEDURE [stp: STPOps.Handle, ps: POINTER TO STRING] =
    BEGIN
    char: CHARACTER;
    IF ps↑ # NIL THEN ps↑.length ← 0 ELSE ps↑ ← Storage.String[15];
    DO
      char ← MyGetChar[stp ! MarkEncountered => {
	mark: Stream.SubSequenceType ← LookAtMark[stp];
	SELECT mark FROM
	  markHereIsPList, markEOC => EXIT;
	  ENDCASE => GenerateProtocolError[eocExpected, mark]}];
      HeapString.AppendChar[ps, char];
      ENDLOOP;
    END;
    
  CollectCode: PUBLIC PROCEDURE [stp: STPOps.Handle] RETURNS [code: CHARACTER] =
    BEGIN
    code ← 0C;
    CheckConnection[stp];
    code ← MyGetChar[stp ! MarkEncountered =>
      GenerateProtocolError[noCode, MyGetMark[stp]]];
    END;
    
  CheckConnection: PUBLIC PROCEDURE [stp: STPOps.Handle] =
    BEGIN
    WHILE stp.byteStream = NIL DO
      SIGNAL STP.Error[noConnection, "Please open a connection!"L, 0C] ENDLOOP
    END;
    
  ErrorIfNextNotYes: PUBLIC PROCEDURE [stp: STPOps.Handle] =
    BEGIN
    code: CHARACTER ← 0C;
    mark: Stream.SubSequenceType;
    [mark, code] ← GetCommand[stp, @stp.remoteString];
    IF mark # markYes THEN GenerateErrorString[requestRefused, stp.remoteString, code];
    END;
    
  ErrorIfNextNotEOC: PUBLIC PROCEDURE [stp: STPOps.Handle] =
    BEGIN
    mark: Stream.SubSequenceType ← MyGetMark[stp];
    IF mark # markEOC THEN GenerateProtocolError[eocExpected, mark];
    END;
    
  GetServerType: PUBLIC PROCEDURE [server: STRING]
    RETURNS [serverType: ServerType] =
    BEGIN OPEN String;
    RETURN[SELECT TRUE FROM
      EquivalentString[server, "MAXC"L],
      EquivalentString[server, "MAXC1"L],
      EquivalentString[server, "MAXC2"L] => tenex,
      ENDCASE => ifs];
    END;
    
  LookAtMark: PUBLIC PROCEDURE [stp: STPOps.Handle]
    RETURNS [Stream.SubSequenceType] =
    BEGIN
    IF ~stp.gotMark THEN
      DO
	[] ← MyGetChar[stp ! MarkEncountered => CONTINUE];
	IF stp.gotMark THEN EXIT;
	ENDLOOP;
    RETURN[stp.mark]
    END;
    
  MyGetChar: PUBLIC PROCEDURE [stp: STPOps.Handle] RETURNS [char: CHARACTER] =
    BEGIN
    char ← Stream.GetChar[
      stp.byteStream !
      Stream.SSTChange =>
	BEGIN stp.mark ← sst; stp.gotMark ← TRUE; ERROR MarkEncountered; END];
    RETURN[char];
    END;
    
  MyGetMark: PUBLIC PROCEDURE [stp: STPOps.Handle]
    RETURNS [mark: Stream.SubSequenceType] =
    BEGIN mark ← LookAtMark[stp]; stp.gotMark ← FALSE; RETURN[mark] END;
    
  MyPutString: PUBLIC PROCEDURE [byteStream: Stream.Handle, string: STRING] =
    BEGIN
    block: Stream.Block ← [@string.text, 0, string.length];
    Stream.PutBlock[byteStream, block, FALSE];
    END;
    
  PropertyString: PUBLIC PROCEDURE [prop: STPOps.ValidProperties]
    RETURNS [string: STRING] = BEGIN RETURN[propertyStrings[prop]]; END;
    
  SetCreateTime: PUBLIC PROCEDURE [stp: STPOps.Handle, creation: Time.Packed] =
    BEGIN
    cDate: STRING ← [24];
    Time.Append[cDate, Time.Unpack[creation]];
    HeapString.Replace[@stp.plist[createDate], cDate];
    END;
    
  SetFileType: PUBLIC PROCEDURE [
    stp: STPOps.Handle, fileType: STP.FileType] =
    BEGIN
    HeapString.Replace[@stp.plist[type],
      SELECT fileType FROM text => "Text"L, binary => "Binary"L, ENDCASE => NIL];
    END;
    
  SetByteSize: PUBLIC PROCEDURE [stp: STPOps.Handle, fileType: STP.FileType] =
    BEGIN
    HeapString.Replace[@stp.plist[byteSize],
      SELECT stp.serverType FROM
        ifs, unknown, tenex => IF fileType = text THEN NIL ELSE "8",
	ENDCASE => ERROR];
    END;
    
  StringToFileType: PUBLIC PROCEDURE [string: STRING]
    RETURNS [type: STP.FileType] =
    BEGIN
    type ←
      SELECT TRUE FROM
        string = NIL => unknown,
	String.EquivalentString["Text"L, string] => text,
	String.EquivalentString["Binary"L, string] => binary,
	ENDCASE => unknown;
    END;
    
  -- PList and FileInfo Utilities
  
  NameToPList: PUBLIC PROCEDURE [plist: PList, name: STRING, type: FilenameType] =
    BEGIN
    i: CARDINAL;
    versionSeen: BOOLEAN ← FALSE;
    temp: STRING;
    IF Storage.EmptyString[name] THEN RETURN;
    temp ← Storage.String[100];
    FOR i IN [0..name.length) DO
      SELECT name[i] FROM
	'[ => IF i # 0 THEN HeapString.AppendChar[@temp, name[i]];
	'], ': =>
	  BEGIN HeapString.Replace[@plist[device], temp]; temp.length ← 0; END;
	'< =>
	  BEGIN
	  IF temp.length # 0 THEN HeapString.Replace[@plist[device], temp];
	  temp.length ← 0;
	  HeapString.AppendChar[@temp, name[i]];
	  IF plist[directory] # NIL THEN plist[directory].length ← 0;
	  END;
	'> =>
	  BEGIN
	  IF
	    (Storage.EmptyString[plist[directory]] OR
	      plist[directory][plist[directory].length-1] # '>) AND
	    (temp.length = 0 OR temp[0] # '<)
	    THEN HeapString.AppendChar[@plist[directory], name[i]];
	  HeapString.AppendString[@plist[directory], temp];
	  temp.length ← 0;
	  END;
	'!, '; =>
	  BEGIN
	  IF temp.length # 0 THEN HeapString.Replace[@plist[nameBody], temp];
	  versionSeen ← TRUE;
	  temp.length ← 0;
	  END;
	ENDCASE => HeapString.AppendChar[@temp, name[i]];
      ENDLOOP;
    IF temp.length # 0 THEN {
      IF versionSeen THEN HeapString.Replace[@plist[version], temp]
      ELSE HeapString.Replace[@plist[nameBody], temp]};
    Storage.FreeString[temp];
    END;
    
  PListToName: PUBLIC PROCEDURE [plist: PList, type: FilenameType]
    RETURNS [name: STRING] =
    BEGIN
    name ← Storage.String[40];
    IF ~Storage.EmptyString[plist[directory]] THEN
      BEGIN
      IF plist[directory][0] # '< THEN HeapString.AppendChar[@name, '<];
      HeapString.AppendString[@name, plist[directory]];
      IF plist[directory][0] # '< THEN HeapString.AppendChar[@name, '>];
      END;
    IF ~Storage.EmptyString[plist[nameBody]] THEN HeapString.AppendString[@name, plist[nameBody]];
    IF ~Storage.EmptyString[plist[version]] THEN
      BEGIN
      IF type = alto THEN HeapString.AppendChar[@name, '!]
      ELSE HeapString.AppendChar[@name, ';];
      HeapString.AppendString[@name, plist[version]];
      END;
    END;
    
  MakeRemoteName: PUBLIC PROCEDURE [plist: PList, type: FilenameType] RETURNS [STRING] = {
    RETURN[IF Storage.EmptyString[plist[serverName]] THEN PListToName[plist, type]
	  ELSE Storage.CopyString[plist[serverName]]]};
	  
  PutPListItem: PUBLIC PROCEDURE [
    byteStream: Stream.Handle, property: STPOps.ValidProperties, value: STRING] =
    BEGIN
    Stream.PutChar[byteStream, '(];
    MyPutString[byteStream, PropertyString[property]];
    Stream.PutChar[byteStream, Ascii.SP];
    MyPutString[byteStream, value];
    Stream.PutChar[byteStream, ')];
    END;
    
  ResetPList: PUBLIC PROCEDURE [plist: PList] =
    BEGIN
    i: STPOps.ValidProperties;
    IF plist = NIL THEN RETURN;
    FOR i IN STPOps.ValidProperties DO
      IF plist[i] # NIL THEN plist[i] ← Storage.FreeStringNil[plist[i]]; ENDLOOP;
    END;
    
  GetFileInfo: PUBLIC PROCEDURE [stp: STPOps.Handle] RETURNS [STP.FileInfo] =
    BEGIN
    FOR i: STPOps.FileProperties IN STPOps.FileProperties DO
      SELECT i FROM
	directory => stp.info.directory ← stp.plist[i];
	nameBody => stp.info.body ← stp.plist[i];
	version => stp.info.version ← stp.plist[i];
	author => stp.info.author ← stp.plist[i];
	createDate => stp.info.create ← stp.plist[i];
	readDate => stp.info.read ← stp.plist[i];
	writeDate => stp.info.write ← stp.plist[i];
	size =>
	  stp.info.size ←
	    IF Storage.EmptyString[stp.plist[i]] THEN 0
	    ELSE String.StringToLongNumber[stp.plist[i], 10];
	type => stp.info.type ← StringToFileType[stp.plist[i]];
	ENDCASE;
      ENDLOOP;
    RETURN[stp.info];
    END;
    
  SetPListItem: PUBLIC PROCEDURE [plist: PList, property, value: STRING] =
    BEGIN
    FOR i: STPOps.ValidProperties IN STPOps.ValidProperties DO
      IF String.EquivalentString[PropertyString[i], property] THEN {
	HeapString.Replace[@plist[i], value]; RETURN};
      ENDLOOP;
    ERROR BadProperty
    END;
    
  UserStateToPList: PUBLIC PROCEDURE [stp: STPOps.Handle] =
    BEGIN OPEN stp;
    FOR i: STPOps.UserProperties IN STPOps.UserProperties DO
      -- Solution 1
      HeapString.Replace[@plist[i], userState[i]]; -- Solution 2
      -- IF plist[i] # NIL THEN ERROR Error[undefinedError];
      -- IF ~Storage.EmptyString[userState[i] THEN
      -- plist[i] ← Storage.CopyString[userState[i]];
      -- Solution 3
      -- IF userState[i] # NIL THEN
      -- HeapString.Replace[@plist[i], userState[i]];
      
      ENDLOOP;
    END;
    
  END. -- of STPsB