-- file: STPsB.mesa - Simple/Stream Transfer Protocol 
-- Traditional FTP-like interface stuff in here
-- Edited by:
-- Smokey on: 12-Mar-81 12:15:19
-- Evans on: Feb 8, 1980 10:16 AM
-- Karlton on: September 17, 1980  6:12 PM

DIRECTORY
  Inline USING [LowHalf],
  PupStream USING [CloseReason, StreamClosing],
  Storage USING [Free, FreePages, FreeString, FreeStringNil, Pages],
  STP USING [
    Access, Completion, CompletionProcType, Confirmation, ConfirmProcType,
    CreateFileStream, CredentialsErrors, defaultOptions, Error, FileErrors, FileType, GetFileTimes, NoteFileProcType],
  STPOps USING [
    CheckConnection, CollectCode, CollectString, ErrorIfNextNotYes,
    FindFileType, GenerateErrorString, GenerateStreamClosingError, GetCommand,
    GetHereIsAndPList, Handle, LookAtMark, MakeRemoteName, markDelete, markDirectory,
    markEOC, markHereIsFile, markNo, markNewStore, markRetrieve, markYes,
    MyGetMark, NameToPList, Object, Operation, PList, PutCommand, PutPList,
    ResetPList, SelectError, SetByteSize, SetCreateTime, SetFileDates,
    SetFileType, SmashClosed, UserStateToPList, ValidProperties],
  Stream USING [
    Block, CompletionCode, EndOfStream, Handle, Object, 
    PutBlock, SetSST, SSTChange, SubSequenceType, TimeOut],
  Streams USING [Destroy],
  Time USING [Packed];
  
STPsB: MONITOR
  IMPORTS
    Inline, PupStream, Storage, STP, STPOps, Stream, Streams
  EXPORTS STP, STPOps =
  BEGIN OPEN PupStream, STPOps;
  
-- Data and stuff

  BytesPerPage: CARDINAL = 512; -- AltoDefs.BytesPerPage --
  Object: PUBLIC TYPE = STPOps.Object;
  
-- Procedures for normal FTP-like interface 

  Delete: PUBLIC PROCEDURE [
    stp: STPOps.Handle, name: STRING, confirm: STP.ConfirmProcType,
    complete: STP.CompletionProcType] =
    BEGIN DoFiles[stp, name, confirm, complete, delete]; END;
    
  Enumerate: PUBLIC PROCEDURE [
    stp: STPOps.Handle, name: 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;
    
  Retrieve: PUBLIC PROCEDURE [
    stp: STPOps.Handle, file: STRING, confirm: STP.ConfirmProcType ← NIL,
    complete: STP.CompletionProcType ← NIL] =
    BEGIN DoFiles[stp, file, confirm, complete, retrieve]; END;
    
  Store: PUBLIC PROCEDURE [
    stp: STPOps.Handle, file: STRING, stream: Stream.Handle ← NIL,
    noteFile: STP.NoteFileProcType ← NIL,
    fileType: STP.FileType, creation: Time.Packed] =
    BEGIN
    myStream: BOOLEAN ← stream = NIL;
    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 myStream THEN
      BEGIN
      stream ← STP.CreateFileStream[stp.plist[nameBody], read];
      creation ← STP.GetFileTimes[stream].create;
      END;
    IF fileType = unknown THEN fileType ← FindFileType[stream];
    SetFileType[stp, fileType];
    STPOps.SetByteSize[stp, fileType];
    STPOps.SetCreateTime[stp, creation];
    DoFiles[stp, file, Confirm, NIL, store];
    IF myStream THEN Streams.Destroy[stream];
    END;
    
-- common routine for Delete, Enumerate and Retrieve

  DoFiles: PUBLIC PROCEDURE [
    stp: STPOps.Handle, file: STRING, confirm: STP.ConfirmProcType,
    complete: STP.CompletionProcType, op: STPOps.Operation] =
    BEGIN
    reason: PupStream.CloseReason;
    reply: STP.Confirmation;
    string: STRING ← NIL;
    local: Stream.Handle ← NIL;
    killConnection: BOOLEAN ← TRUE;
    CleanUp: PROCEDURE = 
      BEGIN
      IF killConnection THEN SmashClosed[stp];
      Storage.FreeString[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;
    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]];
      string ← Storage.FreeStringNil[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[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[protocolError, stp.remoteString, code]};
	  };
	abort => {CleanUp[]; RETURN};
	ENDCASE => ERROR;
      ResetPList[stp.plist];
      ENDLOOP;
    EXITS
      streamClosing => GenerateStreamClosingError[reason]};
    END;
    
-- Procedures for doing FTP protocol operations  

  GetFile: PUBLIC PROCEDURE [
    stp: STPOps.Handle, stream: Stream.Handle, file: STRING]
    RETURNS[gotIt: BOOLEAN] =
    BEGIN
    mark: Stream.SubSequenceType;
    mine: BOOLEAN ← FALSE;
    {ENABLE UNWIND => IF mine AND stream # NIL THEN Streams.Destroy[stream];
    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];
    IF stream = NIL THEN {mine ← TRUE; stream ← STP.CreateFileStream[file, write]};
    TransferTheFile[stp: stp, from: stp.byteStream, to: stream];
    SetFileDates[stp, stream]};
    IF mine THEN Streams.Destroy[stream]; -- HACK! 
    ErrorIfNextNotYes[stp]; -- should do something about file on local disk
    RETURN[TRUE]
    END;
    
  PutFile: PUBLIC PROCEDURE [
    stp: STPOps.Handle, stream: Stream.Handle, file: 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 OPEN Inline;
    block: Stream.Block ← [NIL, 0, BytesPerPage];
    BEGIN
    ENABLE UNWIND => Storage.FreePages[LowHalf[block.blockPointer]];
    bytesTransferred: CARDINAL;
    why: Stream.CompletionCode;
    savedSST: Stream.SubSequenceType;
    block.blockPointer ← Storage.Pages[1];
    DO
      block.startIndex ← 0;
      block.stopIndexPlusOne ← BytesPerPage;
      [bytesTransferred, why, savedSST] ← from.get[
	from, block, STP.defaultOptions !
	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;
    Storage.FreePages[LowHalf[block.blockPointer]];
    END;
    
  END. -- of STPsB