-- File: STPsD.mesa - last edit:
-- AOF                 20-Feb-87  9:29:13
-- Copyright (C) 1981, 1982, 1984 , 1987 by Xerox Corporation. All rights reserved. 
-- file: STPsD.mesa - Simple/Stream Transfer Protocol 
-- Common and protocol stuff in here
-- Edited by:
-- Mark,	 Feb 12, 1981 11:39 PM
-- SXW   ,	17-Jul-81  7:20:16
-- PXK    ,	17-Jul-81 17:45:12
-- JGS,		14-Aug-81 10:35:26
-- LXR    	27-Oct-82 10:45:58
-- AXD    	21-Sep-82 13:07:12
-- BJD    	17-Feb-84 11:16:31

DIRECTORY
  Ascii USING [SP],
  Heap USING [systemZone],
  PupStream USING[StreamClosing],
  STP USING [DesiredProperties, Error, ErrorCode, FileInfo, Open, Type],
  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, PutChar, PutBlock, SetSST,
    SSTChange, SubSequenceType, TimeOut],
  String USING [
    AppendCharAndGrow, AppendStringAndGrow, CopyToNewString, EquivalentString,
    EmptyString, FreeString, Replace, StringToLongNumber],
  Time USING [Append, Packed, Unpack];
  
STPsD: PROGRAM
  IMPORTS
    Heap, PupStream, STP, STPOps, Stream, String, Time
  EXPORTS STP, STPOps =
  BEGIN OPEN STPOps;
  
  -- Exported Types
  
  Object: PUBLIC TYPE = STPOps.Object;
  
  -- Global Data
  
  z: UNCOUNTED ZONE = Heap.systemZone;
  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"];
  desiredPropString: LONG STRING = "Desired-property";
    
-- Commonly used stuff  

  Connect: PUBLIC PROCEDURE [stp: STPOps.Handle, name, password: LONG STRING] =
    BEGIN
    String.Replace[@stp.userState[connectName], name, z];
    String.Replace[@stp.userState[connectPassword], password, z];
    END;
    
  IsOpen: PUBLIC PROCEDURE [stp: STPOps.Handle] RETURNS [yes: BOOLEAN] = {
    yes ← stp.byteStream # NIL};
    
  GetProperty: PUBLIC PROCEDURE [stp: STPOps.Handle, prop: ValidProperties] RETURNS [LONG STRING] = {
    RETURN[stp.plist[prop]]};

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

  GetCommand: PUBLIC PROCEDURE [stp: STPOps.Handle, ps: LONG POINTER TO LONG 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[stp, 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[stp, errorCode, stp.remoteString, code];
	  END;
	ENDCASE => GenerateProtocolError[stp, badMark, mark, 0C];
      ENDLOOP;
    END;
    
  GetPList: PUBLIC PROCEDURE [stp: STPOps.Handle, gobbleEOC: BOOLEAN ← TRUE] =
    BEGIN
    property: LONG STRING ← z.NEW[StringBody[maxStringLength]];
    value: LONG STRING ← z.NEW[StringBody[maxStringLength]];
    mark: Stream.SubSequenceType;
    CheckConnection[stp];
    BEGIN ENABLE UNWIND => {z.FREE[@property]; z.FREE[@value]};
    parens: INTEGER ← 0;
    quote: CHARACTER = '';
    char: CHARACTER;
    string: LONG STRING ← property;
    IF stp.plist[nameBody] # NIL THEN z.FREE[@stp.plist[nameBody]];
    DO
      char ← MyGetChar[stp ! MarkEncountered => GOTO BadPList];
      SELECT char FROM
	'( =>
	  BEGIN parens ← parens + 1; string ← property; string.length ← 0; END;
	quote => String.AppendCharAndGrow[
	  @string, MyGetChar[stp ! MarkEncountered => GOTO BadPList], z];
	Ascii.SP =>
	  IF string = property THEN BEGIN string ← value; string.length ← 0; END
	  ELSE String.AppendCharAndGrow[@string, char, z];
	') =>
	  BEGIN
	  IF property.length # 0 AND value.length # 0 THEN
	    BEGIN
	    SetPListItem[
	      stp.plist, property, value ! BadProperty => CONTINUE];
	    property.length ← value.length ← 0;
	    END;
	  IF (parens ← parens - 1) = 0 THEN EXIT;
	  END;
	ENDCASE => String.AppendCharAndGrow[@string, char, z];
      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[stp, badPList, mark]};
    END;
    z.FREE[@property];
    z.FREE[@value];
    END;
    
  PutCommand: PUBLIC PROCEDURE [
    stp: STPOps.Handle, mark: Stream.SubSequenceType, code: CHARACTER,
    string: LONG STRING, sendEOC: BOOLEAN ← TRUE] =
    BEGIN
    CheckConnection[stp];
    Stream.SetSST[stp.byteStream, mark];
    Stream.PutChar[stp.byteStream, code];
    IF ~String.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];
      IF mark # 0B THEN Stream.SetSST[stp.byteStream, mark];
      Stream.PutChar[stp.byteStream, '(];
      FOR i: ValidProperties IN ValidProperties DO
	IF ~String.EmptyString[stp.plist[i]] THEN
	  PutPListItem[stp.byteStream, i, stp.plist[i]];
	ENDLOOP;
      IF stp.desiredProps # ALL[TRUE] THEN PutDesiredProps[stp];
      Stream.PutChar[stp.byteStream, ')];
      IF sendEOC THEN Stream.SetSST[stp.byteStream, markEOC];
      END;
    DO
      DoIt[! PupStream.StreamClosing, Stream.TimeOut =>
	IF state = okay AND ~String.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
      String.FreeString[z, 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: LONG POINTER TO LONG STRING] =
    BEGIN
    char: CHARACTER;
    IF ps↑ # NIL THEN ps↑.length ← 0 ELSE ps↑ ← z.NEW[StringBody[15]];
    DO
      char ← MyGetChar[stp ! MarkEncountered => {
	mark: Stream.SubSequenceType ← LookAtMark[stp];
	SELECT mark FROM
	  markHereIsPList, markEOC => EXIT;
	  ENDCASE => GenerateProtocolError[stp, eocExpected, mark]}];
      String.AppendCharAndGrow[ps, char, z];
      ENDLOOP;
    END;
    
  CollectCode: PUBLIC PROCEDURE [stp: STPOps.Handle] RETURNS [code: CHARACTER] =
    BEGIN
    code ← 0C;
    CheckConnection[stp];
    code ← MyGetChar[stp ! MarkEncountered =>
      GenerateProtocolError[stp, noCode, MyGetMark[stp]]];
    END;
    
  CheckConnection: PUBLIC PROCEDURE [stp: STPOps.Handle] =
    BEGIN
    WHILE stp.byteStream = NIL DO
      SIGNAL STP.Error[stp, 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[stp, requestRefused, stp.remoteString, code];
    END;
    
  ErrorIfNextNotEOC: PUBLIC PROCEDURE [stp: STPOps.Handle] =
    BEGIN
    mark: Stream.SubSequenceType ← MyGetMark[stp];
    IF mark # markEOC THEN GenerateProtocolError[stp, eocExpected, mark];
    END;
    
  GetServerType: PUBLIC PROCEDURE [server: LONG 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;
    
  PutPListValue: PROCEDURE [
    byteStream: Stream.Handle, string: LONG STRING] =
    BEGIN
    FOR i: NATURAL IN [0..string.length) DO
      char: CHAR = string[i];
      SELECT char FROM
	'(, '), '' => byteStream.PutChar['']
	ENDCASE => NULL;
      byteStream.PutChar[char];
      ENDLOOP;
    END;
    
  MyPutString: PUBLIC PROCEDURE [byteStream: Stream.Handle, string: LONG STRING] =
    BEGIN
    block: Stream.Block ← [LOOPHOLE[@string.text], 0, string.length];
    Stream.PutBlock[byteStream, block, FALSE];
    END;
        
  PropertyString: PUBLIC PROCEDURE [prop: STPOps.ValidProperties]
    RETURNS [string: LONG STRING] = BEGIN RETURN[propertyStrings[prop]]; END;
    
  SetCreateTime: PUBLIC PROCEDURE [stp: STPOps.Handle, creation: Time.Packed] =
    BEGIN
    cDate: STRING ← [24];
    Time.Append[
      s: cDate, unpacked: Time.Unpack[creation], zone: TRUE, zoneStandard: Alto];
    String.Replace[@stp.plist[createDate], cDate, z];
    END;
    
  SetFileType: PUBLIC PROCEDURE [
    stp: STPOps.Handle, fileType: STP.Type] =
    BEGIN
    String.Replace[@stp.plist[type],
      SELECT fileType FROM text => "Text"L, binary => "Binary"L, ENDCASE => NIL, z];
    END;
    
  SetByteSize: PUBLIC PROCEDURE [stp: STPOps.Handle, fileType: STP.Type] =
    BEGIN
    String.Replace[@stp.plist[byteSize],
      SELECT stp.serverType FROM
        ifs, unknown, tenex => IF fileType = text THEN NIL ELSE "8",
	ENDCASE => ERROR STP.Error[stp, undefinedError, NIL], z];
    END;
    
  StringToFileType: PUBLIC PROCEDURE [string: LONG STRING]
    RETURNS [type: STP.Type] =
    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: LONG STRING, type: FilenameType] =
    BEGIN
    i: CARDINAL;
    versionSeen: BOOLEAN ← FALSE;
    temp: LONG STRING;
    IF String.EmptyString[name] THEN RETURN;
    temp ← z.NEW[StringBody[100]];
    FOR i IN [0..name.length) DO
      SELECT name[i] FROM
	'[ => IF i # 0 THEN String.AppendCharAndGrow[@temp, name[i], z];
	'], ': =>
	  BEGIN String.Replace[@plist[device], temp, z]; temp.length ← 0; END;
	'< =>
	  BEGIN
	  IF temp.length # 0 THEN String.Replace[@plist[device], temp, z];
	  temp.length ← 0;
	  String.AppendCharAndGrow[@temp, name[i], z];
	  IF plist[directory] # NIL THEN plist[directory].length ← 0;
	  END;
	'> =>
	  BEGIN
	  IF
	    (String.EmptyString[plist[directory]] OR
	      plist[directory][plist[directory].length-1] # '>) AND
	    (temp.length = 0 OR temp[0] # '<)
	    THEN String.AppendCharAndGrow[@plist[directory], name[i], z];
	  String.AppendStringAndGrow[@plist[directory], temp, z];
	  temp.length ← 0;
	  END;
	'!, '; =>
	  BEGIN
	  IF temp.length # 0 THEN String.Replace[@plist[nameBody], temp, z];
	  versionSeen ← TRUE;
	  temp.length ← 0;
	  END;
	ENDCASE => String.AppendCharAndGrow[@temp, name[i], z];
      ENDLOOP;
    IF temp.length # 0 THEN {
      IF versionSeen THEN String.Replace[@plist[version], temp, z]
      ELSE String.Replace[@plist[nameBody], temp, z]};
    z.FREE[@temp];
    END;
    
  PListToName: PUBLIC PROCEDURE [plist: PList, type: FilenameType]
    RETURNS [name: LONG STRING] =
    BEGIN
    name ← z.NEW[StringBody[40]];
    IF ~String.EmptyString[plist[directory]] THEN
      BEGIN
      SELECT plist[directory][0] FROM
        '> => FOR i: CARDINAL IN [1..plist[directory].length) DO
	       String.AppendCharAndGrow[@name, plist[directory][i], z] ENDLOOP;
	'< => String.AppendStringAndGrow[@name, plist[directory], z];
	ENDCASE => {
	  String.AppendCharAndGrow[@name, '<, z];
          String.AppendStringAndGrow[@name, plist[directory], z]};
      IF plist[directory][plist[directory].length - 1] # '> THEN 
        String.AppendCharAndGrow[@name, '>, z];
      END;
    IF ~String.EmptyString[plist[nameBody]] THEN
      String.AppendStringAndGrow[@name, plist[nameBody], z];
    IF ~String.EmptyString[plist[version]] THEN
      BEGIN
      IF type = alto THEN String.AppendCharAndGrow[@name, '!, z]
      ELSE String.AppendCharAndGrow[@name, ';, z];
      String.AppendStringAndGrow[@name, plist[version], z];
      END;
    END;
    
  MakeRemoteName: PUBLIC PROCEDURE [plist: PList, type: FilenameType] RETURNS [LONG STRING] = {
    RETURN[IF String.EmptyString[plist[serverName]] THEN PListToName[plist, type]
	  ELSE String.CopyToNewString[plist[serverName], z]]};
	  
  PutPListItem: PUBLIC PROCEDURE [
    byteStream: Stream.Handle, property: STPOps.ValidProperties, value: LONG STRING] =
    BEGIN
    Stream.PutChar[byteStream, '(];
    MyPutString[byteStream, PropertyString[property]];
    Stream.PutChar[byteStream, Ascii.SP];
    PutPListValue[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 z.FREE[@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 String.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: LONG STRING] =
    BEGIN
    FOR i: STPOps.ValidProperties IN STPOps.ValidProperties DO
      IF String.EquivalentString[PropertyString[i], property] THEN {
	String.Replace[@plist[i], value, z]; RETURN};
      ENDLOOP;
    ERROR BadProperty
    END;
    
  UserStateToPList: PUBLIC PROCEDURE [stp: STPOps.Handle] =
    BEGIN OPEN stp;
    FOR i: STPOps.UserProperties IN STPOps.UserProperties DO
      -- Solution 1
      String.Replace[@plist[i], userState[i], z]; -- Solution 2
      -- IF plist[i] # NIL THEN ERROR Error[stp, undefinedError];
      -- IF ~String.EmptyString[userState[i] THEN
      -- plist[i] ← String.CopyToNewString[userState[i], z];
      -- Solution 3
      -- IF userState[i] # NIL THEN
      -- String.Replace[@plist[i], userState[i], z];
      
      ENDLOOP;
    END;
    
  -- Desired Property stuff
  
  SetDesiredProperties: PUBLIC PROC[stp: STPOps.Handle, props: STP.DesiredProperties] =
    BEGIN
    stp.desiredProps ← props
    END;
    
  GetDesiredProperties: PUBLIC PROC [stp: STPOps.Handle]
    RETURNS [props: STP.DesiredProperties] =
    BEGIN
    RETURN[stp.desiredProps]
    END;

  PutDesiredProps: PUBLIC PROCEDURE [stp: STPOps.Handle] =
    BEGIN
    FOR i: STPOps.FileProperties IN STPOps.FileProperties DO
      IF stp.desiredProps[i] THEN
        PutDesiredPropsItem[stp.byteStream, PropertyString[i]]
	ENDLOOP;
    END;
    
  PutDesiredPropsItem: PUBLIC PROCEDURE [
    byteStream: Stream.Handle, value: LONG STRING] =
    BEGIN
    Stream.PutChar[byteStream, '(];
    MyPutString[byteStream, desiredPropString];
    Stream.PutChar[byteStream, Ascii.SP];
    PutPListValue[byteStream, value];
    Stream.PutChar[byteStream, ')];
    END;

  END. -- of STPsD