-- file: [Igor]<Emerson>STP>Private>UnsafeSTPsB.mesa - Simple/Stream Transfer Protocol 
-- Traditional FTP-like interface stuff in here
-- Edited by:
-- Smokey,	17-Jul-81  7:48:31
-- JGS,		17-Aug-81 11:26:24
-- Karlton,	15-Mar-82 17:26:37
-- Loretta,	10-Nov-81 16:07:59
-- Davirro,	24-Sep-82 10:35:49
-- Daniels,	21-Sep-82 13:05:55

DIRECTORY
  Environment USING [Byte, bytesPerPage],
  FileStream USING[GetIndex, SetIndex],
  Heap USING [systemZone],
  Inline USING [BITAND],
  PupStream USING [CloseReason, PupByteStreamAbort, StreamClosing],
  UnsafeSTP USING [
    Close, Completion, CompletionProcType, Confirmation, ConfirmProcType,
    CredentialsErrors, Error, ErrorCode, FileErrors, NoteFileProcType, Type],
  UnsafeSTPOps USING [
    CheckConnection, CollectCode, CollectString, ErrorIfNextNotYes, ErrorIfNextNotEOC,
    ErrorCodeToSTPErrorCode, 
    GenerateErrorString, GenerateProtocolError, GenerateStreamClosingError, GetCommand,
    GetHereIsAndPList, GetPList, Handle, LookAtMark, MarkEncountered, 
    MakeRemoteName, markComment, markDelete,
    markDirectory, markEOC, markHereIsFile, markHereIsPList, markNo, markNewDirectory, 
    markNewStore, markRename,
    markRetrieve, markYes, MyGetMark, NameToPList, Object, Operation, PList,
    PutCommand, PutPList, ResetPList, SelectError, SetByteSize, SetCreateTime,
    SetFileType, UserStateToPList],
  UnsafeSTPReplyCode USING[ReplyCode],
  Stream USING [
    Block, CompletionCode, EndOfStream, GetByte,  Handle,
    PutBlock, SetSST, SSTChange, SubSequenceType,
    TimeOut],
  Time USING [Packed];
  
UnsafeSTPsB: PROGRAM
  IMPORTS FileStream, Heap, Inline, PupStream, STP: UnsafeSTP, STPOps: UnsafeSTPOps, Stream
  EXPORTS UnsafeSTP, UnsafeSTPOps =
  BEGIN OPEN PupStream, STPOps;
  
-- Data and types

  BytesPerPage: CARDINAL = Environment.bytesPerPage;
  PageOfStorage: TYPE = PACKED ARRAY [0..BytesPerPage) OF Environment.Byte;
  Object: PUBLIC TYPE = STPOps.Object;
  z: UNCOUNTED ZONE = Heap.systemZone;
  
-- Procedures for normal FTP-like interface 

  Delete: PUBLIC PROCEDURE [
    stp: STPOps.Handle, name: LONG STRING, confirm: STP.ConfirmProcType,
    complete: STP.CompletionProcType] =
    BEGIN DoFiles[stp, name, confirm, complete, delete]; END;
    
  Enumerate: PUBLIC PROCEDURE [
    stp: STPOps.Handle, name: LONG STRING, proc: STP.NoteFileProcType] =
    BEGIN
    Foo1: STP.ConfirmProcType =
      BEGIN
      RETURN[answer: IF proc[file] = yes THEN do ELSE abort, localStream: NIL];
      END;
    Foo2: STP.CompletionProcType = BEGIN END;
    DoFiles[stp, name, Foo1, Foo2, directory];
    END;
    
  Rename: PUBLIC PROCEDURE [
    stp: STPOps.Handle, old, new: LONG STRING] =
    BEGIN
    mark, saveMark: Stream.SubSequenceType;
    code: CHARACTER ← 0C;
    CheckConnection[stp];
    ResetPList[stp.plist];
    UserStateToPList[stp];
    NameToPList[stp.plist, old, alto];
    PutPList[stp, markRename, FALSE];
    ResetPList[stp.plist];
    UserStateToPList[stp];
    NameToPList[stp.plist, new, alto];
    PutPList[stp, 0B];
    [saveMark, code] ← GetCommand[stp, @stp.remoteString];
    IF (mark ← MyGetMark[stp]) # markEOC THEN
      GenerateProtocolError[stp, eocExpected, mark];
    IF saveMark # markYes THEN
      GenerateErrorString[stp, requestRefused, stp.remoteString, code];
    END;
    
  Retrieve: PUBLIC PROCEDURE [
    stp: STPOps.Handle, file: LONG STRING, confirm: STP.ConfirmProcType ← NIL,
    complete: STP.CompletionProcType ← NIL] =
    BEGIN DoFiles[stp, file, confirm, complete, retrieve]; END;
    
  Store: PUBLIC PROCEDURE [
    stp: STPOps.Handle, file: LONG STRING, stream: Stream.Handle,
    noteFile: STP.NoteFileProcType ← NIL,
    fileType: STP.Type, creation: Time.Packed] =
    BEGIN
    Confirm: STP.ConfirmProcType =
      {IF noteFile = NIL OR noteFile[file] = yes THEN
      RETURN[do, stream] ELSE RETURN[skip, NIL]};
    CheckConnection[stp];
    ResetPList[stp.plist];
    UserStateToPList[stp];
    NameToPList[stp.plist, file, alto];
    IF fileType = unknown THEN fileType ← FindFileType[stream];
    SetFileType[stp, fileType];
    STPOps.SetByteSize[stp, fileType];
    STPOps.SetCreateTime[stp, creation];
    DoFiles[stp, file, Confirm, NIL, store];
    END;
    
-- common routine for Delete, Enumerate and Retrieve

  DoFiles: PUBLIC PROCEDURE [
    stp: STPOps.Handle, file: LONG STRING, confirm: STP.ConfirmProcType,
    complete: STP.CompletionProcType, op: STPOps.Operation] =
    BEGIN
    reason: PupStream.CloseReason;
    reply: STP.Confirmation;
    string: LONG STRING ← NIL;
    local: Stream.Handle ← NIL;
    killConnection: BOOLEAN ← TRUE;
    CleanUp: PROCEDURE = 
      BEGIN
      IF killConnection THEN SmashClosed[stp];
      IF string ~= NIL THEN z.FREE[@string]
      END;
    {ENABLE {
      PupStream.StreamClosing => {reason ← why; GOTO streamClosing};
      Stream.TimeOut => {reason ← transmissionTimeout; GOTO streamClosing};
      UNWIND => CleanUp[]};
    IF op # store THEN
      BEGIN
      CheckConnection[stp];
      ResetPList[stp.plist];
      UserStateToPList[stp];
      NameToPList[stp.plist, file, alto];
      END;
    IF op = directory THEN 
    	SELECT TryNewDirectory[stp, confirm  ! STP.Error =>
				IF code IN STP.CredentialsErrors OR code IN STP.FileErrors THEN 
	  				killConnection ← FALSE] FROM
    	finished => RETURN;
    	aborted => {CleanUp[]; RETURN};	
    	tryOldDirectory => NULL;	-- just falls thru
    	ENDCASE => ERROR;
   PutPList[
      stp,
      SELECT op FROM
	delete => markDelete,
	directory => markDirectory,
	retrieve => markRetrieve,
	store => markNewStore,
	ENDCASE => ERROR];
    DO
      IF LookAtMark[stp] = markEOC THEN {[] ← MyGetMark[stp]; EXIT};
      GetHereIsAndPList[stp, op # directory ! STP.Error =>
	IF code IN STP.CredentialsErrors OR code IN STP.FileErrors THEN 
	  killConnection ← FALSE];
      SELECT TRUE FROM
	confirm = NIL => {reply ← do; local ← NIL};
	ENDCASE => [reply, local] ← confirm[string ← MakeRemoteName[stp.plist, alto]];
      IF string ~= NIL THEN z.FREE[@string];
      SELECT reply FROM
	do => {
	  completion: STP.Completion;
	  SELECT op FROM
	    delete => {
	      code: CHARACTER ← 0C;
	      mark: Stream.SubSequenceType;
	      PutCommand[stp, markYes, 0C, "Yes, please"L];
	      [mark, code] ← GetCommand[stp, @stp.remoteString];
	      SELECT mark FROM
		markYes => completion ← ok;
		markNo => completion ← error;
		ENDCASE => GenerateErrorString[stp, protocolError, stp.remoteString, code]};
	    retrieve => {
	      PutCommand[stp, markYes, 0C, "Yes, please"L];
	      completion ← IF GetFile[stp, local, stp.plist[nameBody]] THEN ok ELSE error};
	    store => {
	      PutFile[stp, local, stp.plist[nameBody]];
	      ErrorIfNextNotYes[stp]};
	    ENDCASE;
	  IF complete # NIL THEN complete[completion, stp.remoteString];
	  IF LookAtMark[stp] = markEOC THEN {[] ← MyGetMark[stp]; EXIT}};
	skip => {
	  IF op # directory THEN
	    PutCommand[stp, markNo, 106C, "No Thanks"L];
	  IF op = store THEN {
	    mark: Stream.SubSequenceType;
	    code: CHARACTER;
	    [mark, code] ← GetCommand[stp, @stp.remoteString];
	    IF mark # markNo THEN
	      GenerateErrorString[stp, protocolError, stp.remoteString, code]};
	  };
	abort => {CleanUp[]; RETURN};
	ENDCASE => ERROR;
      ResetPList[stp.plist];
      ENDLOOP;
    EXITS
      streamClosing => GenerateStreamClosingError[stp, reason]};
    END;
    
TryNewDirectory: PROC[stp: STPOps.Handle, confirm: STP.ConfirmProcType] 
	RETURNS[{finished, aborted, tryOldDirectory}] = {
	mark: Stream.SubSequenceType;
	PutPList[stp, markNewDirectory];
	DO
		SELECT (mark ← MyGetMark[stp]) FROM
		markComment => CollectString[stp, @stp.remoteString];
		markHereIsPList => {
			string: LONG STRING;
			reply: STP.Confirmation;
			DO
				GetPList[stp: stp, gobbleEOC: FALSE, propertiesOk: TRUE
					! MarkEncountered => {
						mark: Stream.SubSequenceType ← LookAtMark[stp];
						IF mark = markEOC THEN EXIT
						ELSE GenerateProtocolError[stp, eocExpected, mark]
						}];
				string ← MakeRemoteName[stp.plist, alto];
				[answer: reply] ← confirm[string];
				IF string ~= NIL THEN z.FREE[@string];
				IF reply = abort THEN RETURN[aborted];
				ENDLOOP;
			ErrorIfNextNotEOC[stp];
			RETURN[finished];
			};
		markNo =>
	  		BEGIN
	  		code: CHARACTER = CollectCode[stp];
	  		errorCode: STP.ErrorCode =
	    		ErrorCodeToSTPErrorCode[requestRefused, code]; 
	  		CollectString[stp, @stp.remoteString];
	  		ErrorIfNextNotEOC[stp];
	  		IF LOOPHOLE[code, UnsafeSTPReplyCode.ReplyCode] = badCommand THEN
	  			RETURN[tryOldDirectory];
	  		GenerateErrorString[stp, errorCode, stp.remoteString, code];
	  		END;
		ENDCASE => GenerateProtocolError[stp, badMark, mark, 0C];
		ENDLOOP;
    };

-- Procedures for doing FTP protocol operations  

  GetFile: PUBLIC PROCEDURE [
    stp: STPOps.Handle, stream: Stream.Handle, file: LONG STRING]
    RETURNS[gotIt: BOOLEAN] =
    BEGIN
    mark: Stream.SubSequenceType;
    CheckConnection[stp];
    SELECT (mark ← MyGetMark[stp]) FROM
      markHereIsFile => NULL;
      markNo => {
	[] ← CollectCode[stp]; CollectString[stp, @stp.remoteString]; RETURN[FALSE]};
      ENDCASE => SelectError[stp, "HereIsFile Expected"L, mark];
    TransferTheFile[stp: stp, from: stp.byteStream, to: stream];
    ErrorIfNextNotYes[stp]; -- should do something about file on local disk
    RETURN[TRUE]
    END;
    
  PutFile: PUBLIC PROCEDURE [
    stp: STPOps.Handle, stream: Stream.Handle, file: LONG STRING, sendEOC: BOOLEAN ← TRUE] =
    BEGIN
    CheckConnection[stp];
    Stream.SetSST[stp.byteStream, markHereIsFile];
    TransferTheFile[stp: stp, from: stream, to: stp.byteStream];
    PutCommand[stp, markYes, 0C, "Transfer Completed"L, sendEOC];
    END;
    
  TransferTheFile: PUBLIC PROCEDURE [stp: STPOps.Handle, from, to: Stream.Handle] =
    BEGIN
    block: Stream.Block ← [NIL, 0, BytesPerPage];
    BEGIN
    ENABLE UNWIND => IF block.blockPointer ~= NIL THEN z.FREE[@block.blockPointer];
    bytesTransferred: CARDINAL;
    why: Stream.CompletionCode;
    savedSST: Stream.SubSequenceType;
    block.blockPointer ← LOOPHOLE[z.NEW[PageOfStorage]];
    DO
      block.startIndex ← 0;
      block.stopIndexPlusOne ← BytesPerPage;
      [bytesTransferred, why, savedSST] ← from.get[
	from, block, from.options !
	Stream.EndOfStream =>
	  BEGIN why ← endOfStream; bytesTransferred ← nextIndex; CONTINUE; END;
	Stream.SSTChange =>
	  BEGIN
	  why ← sstChange;
	  savedSST ← sst;
	  bytesTransferred ← nextIndex;
	  CONTINUE;
	  END];
      block.stopIndexPlusOne ← bytesTransferred;
      Stream.PutBlock[to, block, FALSE];
      SELECT why FROM
	normal => NULL;
	sstChange => {stp.mark ← savedSST; stp.gotMark ← TRUE; EXIT};
	endOfStream => EXIT;
	ENDCASE => ERROR;
      ENDLOOP;
    END;
    IF block.blockPointer ~= NIL THEN z.FREE[@block.blockPointer];
    END;
    
  SmashClosed: PUBLIC PROCEDURE [stp: STPOps.Handle] =
    BEGIN
    IF stp = NIL OR stp.byteStream = NIL THEN RETURN;
    -- First smash connection so it will not give us any grief, THEN close it
    PupStream.PupByteStreamAbort[stp.byteStream, "Unwinding..."L];
    STP.Close[stp ! STP.Error => IF code = noConnection THEN CONTINUE];
    END;
    
  FindFileType: PUBLIC PROCEDURE [stream: Stream.Handle]
    RETURNS [fileType: STP.Type] =
    BEGIN
    currentIndex: LONG CARDINAL;
    currentIndex ← FileStream.GetIndex[stream -- !-- 
      -- Stream.InvalidOperation => {fileType ← unknown; GOTO return}-- ];
    FileStream.SetIndex[stream, 0];
    fileType ← text;
    DO ENABLE Stream.EndOfStream => GOTO streamEND;
      IF (Inline.BITAND[Stream.GetByte[stream], 200B]) # 0 THEN
	{fileType ← binary; EXIT};
      REPEAT streamEND => NULL;
      ENDLOOP;
    FileStream.SetIndex[stream, currentIndex];
    -- EXITS return => RETURN;
    END;
    
  END. -- of STPsB