-- file: HelloFTP.mesa from OthelloFTP.mesa
-- Edited by
-- Lichtenberg  6-Jul-84 18:54:38 - Lisp stuff
-- Johnsson,  15-Sep-83 15:40:26
-- Sandman,   October 11, 1979  8:33 AM
-- Gobbel,    February 14, 1980  7:30 PM
-- Forrest,   13-Jan-82 10:22:30
-- Bruce,     28-Dec-81 10:19:42

DIRECTORY
  Environment USING [bytesPerPage, bytesPerWord],
  Heap USING [systemZone],
  File USING [
    Create, Delete, File, MakePermanent, nullFile, PageNumber, PageCount, GetSize, SetSize, Unknown],
  FileTypes USING [tUntypedFile],
  OthelloDefs,
  OthelloOps USING [
    BootFileType, GetVolumeBootFile,
    MakeBootable, MakeUnbootable, SetPhysicalVolumeBootFile,
    SetVolumeBootFile],
  PhysicalVolume USING [ID],
  Process USING [Detach, Pause, SecondsToTicks],
  Space USING [Map, ScratchMap, Unmap],
  STP USING [
    Close, Connect, Create, CreateRemoteStream, Enumerate, Error,
    ErrorCode, FileInfo, GetFileInfo, Handle, IsOpen, Login, NextFileName, 
    NoteFileProcType, Open, SetDirectory, SetHost],
  Stream USING [EndOfStream, GetBlock, Handle],
  String USING [
    AppendChar, AppendCharAndGrow, AppendExtensionIfNeeded,
    AppendStringAndGrow, CopyToNewString, Length],
  TemporaryBooting USING [InvalidParameters],
  Volume USING [Close, ID, InsufficientSpace, Open, GetAttributes];

HelloFTP: PROGRAM
  IMPORTS
    File, Heap, OthelloDefs, OthelloOps,
    Process, Space, STP, Stream, String, TemporaryBooting, Volume
  EXPORTS OthelloDefs =
  BEGIN OPEN OthelloOps;

  host:            LONG STRING ← NIL;
  userName:        LONG STRING ← NIL;
  userPassword:    LONG STRING ← NIL;
  connectName:     LONG STRING ← NIL;
  connectPassword: LONG STRING ← NIL;
  directory:       LONG STRING ← NIL;
  cmFile:          LONG STRING ← NIL;
  fileName:            LONG STRING ← NIL;
  
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- String/Credentials Commands
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  Directory: PROC = {
    OthelloDefs.MyNameIs[
      myNameIs: "Directory"L,
      myHelpIs: "Set Default FTP directory"L];
    OthelloDefs.GetName["Directory: "L, @directory]};

  GetConnectNamePassword: PROC = {
    OthelloDefs.MyNameIs[
      myNameIs: "Connect"L, myHelpIs: "Set secondary Credentials"L];
    OthelloDefs.GetName["Directory: "L, @connectName];
    OthelloDefs.GetName["Password: "L,  @connectPassword, stars]};

  GetUserNamePassword: PROC = {
    OthelloDefs.MyNameIs[
      myNameIs: "Login"L, myHelpIs: "Set user name-password"L];
    OthelloDefs.GetName["User: "L,     @userName];
    OthelloDefs.GetName["Password: "L, @userPassword, stars]};

  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- Basic Fetch Commands
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  FetchBoot: PROC = {
    Fetch[pilot, "Boot file name: "L, "Fetch Pilot Boot File"L, "Fetch Boot File"L, "boot"L, FALSE]};

  FetchGerm: PROC = {
    Fetch[germ,  "Germ file name: "L, "Germ Fetch"L, "Fetch Pilot Germ"L, "germ"L, FALSE]};

  FetchPilotMicrocode: PROC = {
    Fetch[
      softMicrocode,
      "Pilot microcode file name: "L,
      "Pilot Microcode Fetch"L,
      "Fetch and Install Pilot Microcode"L,
      "db"L,
      FALSE]};
 
 -- Fetch Lisp sysout command: Installs a Lisp sysout on the volume.    
  FetchLispSysout: PROC = {
    Fetch[
      hardMicrocode,
      "Lisp sysout file name: "L,
      "Lisp Sysout Fetch"L,
      "Fetch and Install Lisp Sysout"L,
      "sysout"L,
      TRUE]};

  FetchDiagnosticMicrocode: PROC = {
    Fetch[
      hardMicrocode,
      "Diagnostic microcode file name: "L,
      "Diagnostic Microcode Fetch"L,
      "Fetch and Install Pilot Diagnostic Microcode"L,
      "db"L,
      FALSE]};

  Fetch: PROC [type: BootFileType, prompt, name, helpMsg, extension: STRING, lispSysout: BOOLEAN] =
    BEGIN
    created:   BOOLEAN ← FALSE;
    file:       File.File;
    firstPage: File.PageNumber;
    lvID:      Volume.ID;

    OthelloDefs.MyNameIs[myNameIs: name, myHelpIs: helpMsg];
    IF ~ConnectionOpen[] AND ~ReOpen[] THEN
      OthelloDefs.AbortingCommand["Please open a connection"L];
    lvID ← OthelloDefs.GetLvIDFromUser[].lvID;
    OthelloDefs.GetName[prompt, @fileName];
    [] ← String.AppendExtensionIfNeeded[@fileName, extension, Heap.systemZone];
    Volume.Open[lvID];
    [file, firstPage] ← GetVolumeBootFile[lvID, type];
    IF (created ← file = File.nullFile) THEN
      file ← File.Create[lvID, 1, FileTypes.tUntypedFile]
    ELSE MakeUnbootable[file, type, firstPage ! 
      File.Unknown => CONTINUE;
      TemporaryBooting.InvalidParameters => {
        OthelloDefs.WriteLine["Warning, trouble making unbootable"L];
	CONTINUE}];
    Retrieve[destination: [pilotFileSystemWrite[file]]
    ! UNWIND => {IF created THEN File.Delete[file]; Volume.Close[lvID]}];
    OthelloDefs.WriteString["Installing..."L];
    SetVolumeBootFile[file, type, OthelloDefs.leaderPages];
    File.MakePermanent[file];
 --   IF lispSysout THEN {
 --   	OthelloDefs.WriteString["Expanding to fill volume..."L];
 --   	SetBootFileSize[file,lvID];
 --  	OthelloDefs.WriteString["OK..."L];
 --	};
    MakeBootable[file, type, OthelloDefs.leaderPages
    ! TemporaryBooting.InvalidParameters => {
      OthelloDefs.WriteLine["Warning, trouble making bootable"L]; CONTINUE}];
    OthelloDefs.WriteLine["Done."L];
    IF lispSysout THEN OthelloDefs.WriteLine["[Remember to use Expand VMem command if necessary]"L];

	      
    IF lispSysout THEN {IF OthelloDefs.Yes["Shall I also make this the default boot lisp? "L
       ! UNWIND => Volume.Close[lvID]] THEN
       SetPhysicalVolumeBootFile[file,type,OthelloDefs.leaderPages];
       }
    ELSE 
    IF type IN [hardMicrocode..germ] AND
         OthelloDefs.Yes["Shall I also use this for the Physical Volume? "L
         ! UNWIND => Volume.Close[lvID]] THEN 
        SetPhysicalVolumeBootFile[file, type, OthelloDefs.leaderPages];
    Volume.Close[lvID];
    END;


-- UGH.. This kludge sets the file's size to be as big as Pilot will let it be, on 100 page increments.

  SetBootFileSize: PRIVATE PROC[file: File.File, lvID: Volume.ID] = BEGIN
    newSize: File.PageCount ← File.GetSize[file] + Volume.GetAttributes[lvID].freePageCount;
    File.SetSize[file, newSize 
	  ! File.Unknown => {OthelloDefs.WriteLine["Warning: Trouble making file fill volume - File.Unknown"L];
	    CONTINUE};
	    Volume.InsufficientSpace => {OthelloDefs.WriteString["."L]; newSize ← newSize - 100; RETRY}];
    END;
    


  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- Initial Ucode Fetch Command
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  FetchInitialMicrocode: PUBLIC PROC [
    InstallProc: PROC [getPage: PROC RETURNS [LONG POINTER]]] = {
    IF ~ConnectionOpen[] AND ~ReOpen[] THEN 
      OthelloDefs.AbortingCommand["Please open a connection"L];
    OthelloDefs.GetName["File name: "L, @fileName];
    OthelloDefs.Confirm[];
    Retrieve[destination: [rawWrite[InstallProc]]]};
    
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- Indirect command files
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  AlternateGetCMFile: PUBLIC PROC [s: STRING] = {
    Heap.systemZone.FREE[@cmFile];
    cmFile ← Heap.systemZone.NEW[StringBody[s.length]];
    FOR i: CARDINAL IN [1..s.length) DO
      String.AppendChar[cmFile, s[i]] ENDLOOP;
    DoIndirect[]};

  Indirect: PROC = {
    OthelloDefs.MyNameIs[
      myNameIs: "@", myHelpIs: "Run command file from remote host"L];
    OthelloDefs.GetName["Command file: "L, @cmFile
    ! OthelloDefs.Question => {
      OthelloDefs.WriteLine["[Host]<Dir>Filename"L]; RESUME}];
    [] ← String.AppendExtensionIfNeeded[@cmFile, "hello"L, Heap.systemZone];
    DoIndirect[]};
    
  DoIndirect: PROC =
    BEGIN
    ParseCmFileName: PROC = {
      hostEnd: CARDINAL;
      IF cmFile.length = 0 THEN RETURN;
      FOR i: CARDINAL IN [0..cmFile.length) DO
        c: CHARACTER = cmFile[i];
        SELECT c FROM
          '[, '{ => LOOP; '], '} => {hostEnd ← i; EXIT};
          ENDCASE => String.AppendCharAndGrow[@host, c, Heap.systemZone];
        REPEAT FINISHED => {Heap.systemZone.FREE[@host]; RETURN}
	ENDLOOP;
      -- hostEnd points at '] 
      FOR i: CARDINAL IN (hostEnd..cmFile.length) DO
        IF cmFile[i] = '< AND directory#NIL THEN directory.length ← 0;
        String.AppendCharAndGrow[@fileName, cmFile[i], Heap.systemZone];
	IF cmFile[i] = '> THEN {
	  FOR j: CARDINAL IN [0..fileName.length) DO
	    String.AppendCharAndGrow[@directory, fileName[j], Heap.systemZone] ENDLOOP;
	  fileName.length ← 0};
	ENDLOOP};
    s:         LONG STRING ← NIL;
    GetString: PROC [c: LONG STRING] = {s ← String.CopyToNewString[c, Heap.systemZone]};

    Heap.systemZone.FREE[@host];
    Heap.systemZone.FREE[@directory];
    Heap.systemZone.FREE[@fileName];
    ParseCmFileName[];
    CloseCmd[]; Open[];
    Retrieve[destination: [string[GetString]]
    ! UNWIND => Heap.systemZone.FREE[@s]];
    OthelloDefs.WriteLine["done"L];
    OthelloDefs.SetCommandString[s];
    Close[];
    END;

  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- MISC Stuff/Commands
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  userOpened: BOOLEAN ← FALSE;
  OpenCmd: PROC = {
    OthelloDefs.MyNameIs[myNameIs: "Open"L, myHelpIs: "Open FTP connection"L];
    CloseCmd[];
    OthelloDefs.GetName["Open connection to "L, @host];
    Open[]; userOpened ← TRUE};

  ReOpen: PROC RETURNS [BOOLEAN] = {
    IF userOpened=FALSE THEN RETURN[FALSE];
    Open[]; RETURN[TRUE]};
    
  CloseCmd: PROC = {
    OthelloDefs.MyNameIs[myNameIs: "Close"L, myHelpIs: "Close FTP connection"L];
    userOpened ← FALSE; Close[]};
    
  RemoteList: PROC = {
    OthelloDefs.MyNameIs[
      myNameIs: "List Remote Files"L, myHelpIs: "List Remote Files"L];
    IF ~ConnectionOpen[] AND ~ReOpen[] THEN
      OthelloDefs.AbortingCommand["Please open a connection"L];
    OthelloDefs.GetName["Pattern: "L, @fileName];
    IF String.Length[fileName] = 0 THEN
      String.AppendCharAndGrow[@fileName, '*, Heap.systemZone];
    ListFiles[]};

  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- Central commands
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  commandProcessor: OthelloDefs.CommandProcessor ← [FtpCommands];

  FtpCommands: PROC [index: CARDINAL] = {
    SELECT index FROM
      0 => Indirect[];
      1 => GetConnectNamePassword[];
      2 => Directory[];
      3 => GetUserNamePassword[];
      4 => OpenCmd[];
      5 => CloseCmd[];
      6 => FetchBoot[];
      7 => FetchDiagnosticMicrocode[];
      8 => FetchGerm[];
      9 => FetchPilotMicrocode[];
     10 => RemoteList[];
     11 => FetchLispSysout[];
     ENDCASE => OthelloDefs.IndexTooLarge};


  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- STP Stuff/Commands
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  stp: STP.Handle ← NIL;
  
  ConnectionOpen: PROC RETURNS [BOOLEAN] = {
    RETURN[stp # NIL AND STP.IsOpen[stp]]};
    
  -- all callers close the connection first
  Open: PROC = {
    herald: LONG STRING ← NIL;
    IF stp = NIL THEN stp ← STP.Create[];
    DO
      herald ← STP.Open[stp, host
      ! STP.Error => SELECT code FROM
          connectionTimedOut, connectionClosed, noRouteToNetwork,
          noNameLookupResponse, connectionRejected => {
	    OthelloDefs.WriteLine[error]; CONTINUE};
          ENDCASE => OthelloDefs.AbortingCommand[error]];
      IF herald # NIL THEN EXIT;
      Process.Pause[Process.SecondsToTicks[10]];
      OthelloDefs.CheckUserAbort[];
      ENDLOOP;
    OthelloDefs.WriteLine[herald];
    Heap.systemZone.FREE[@herald];
    STP.SetHost[stp, host]};
  
  CloseFetch: PUBLIC PROC = {
    Close[! OthelloDefs.AbortingCommand => {
      OthelloDefs.WriteString[reason];
      OthelloDefs.WriteLine[reasonOne];
      CONTINUE}]};
  
  Close: PROC = {
    IF ~ConnectionOpen[] THEN RETURN;
    STP.Close[stp ! STP.Error => OthelloDefs.AbortingCommand[error]];
    OthelloDefs.WriteLine["connection closed"L]};
  
  -- could mess with directories.
  -- who cares
  ListFiles: PROC = {
    ListOne: STP.NoteFileProcType = {
      info: STP.FileInfo = STP.GetFileInfo[stp];
      OthelloDefs.WriteString[file];
      THROUGH [file.length..80-info.create.length) DO
        OthelloDefs.WriteChar[' ] ENDLOOP;
      OthelloDefs.WriteLine[info.create];
      --OthelloDefs.WriteChar[' ];
      --OthelloDefs.WriteLine[info.author];
      --OthelloDefs.WriteChar[' ];
      --OthelloDefs.WriteLongNumber[info.size];
      --OthelloDefs.WriteLine[" bytes]"L];
      OthelloDefs.CheckUserAbort[];
      RETURN[yes]};
    STP.Login[stp, userName, userPassword];
    STP.Connect[stp, connectName, connectPassword];
    STP.SetDirectory[stp, directory];
    STP.Enumerate[stp, fileName, ListOne
    ! STP.Error => OthelloDefs.AbortingCommand[error]]};

  Destination: TYPE = RECORD [
    SELECT type: * FROM
      pilotFileSystemWrite => [localFile: File.File],
      string               => [stringProc: PROC [LONG STRING]],
      rawWrite             => [
        linkProc: PROC [getPage: PROC RETURNS [LONG POINTER]]],
      ENDCASE];
      
  StartFeedback: SIGNAL = CODE;
  Retrieve: PROC [destination: Destination] = {
    rs:     Stream.Handle;
    rsSize: LONG CARDINAL;
    note: LONG STRING ← NIL;
    Cleanup: PROC = {
      rs.delete[rs];
      OthelloDefs.SetCursor[pointer];
      Heap.systemZone.FREE[@note]};
    [rs, rsSize, note] ← GetReadStream[
    ! STP.Error => OthelloDefs.AbortingCommand[error]];
    IF rs=NIL THEN RETURN;
    GrabBits[rs, rsSize, destination, note
    ! STP.Error => OthelloDefs.AbortingCommand[error];
      StartFeedback => {
        OthelloDefs.WriteString["Fetching..."L];
        OthelloDefs.SetCursor[ftp];
        RESUME};
      UNWIND => Cleanup[]];
    Cleanup[]};
    
  HasWildCard: PROC [s: LONG STRING] RETURNS [BOOLEAN] = {
    IF s#NIL THEN FOR i: CARDINAL IN [0..s.length) DO 
      IF s[i] = '* THEN RETURN[TRUE] ENDLOOP;
    RETURN[FALSE]};

  PagesForBytes: PROC [bytes: LONG CARDINAL] RETURNS [LONG CARDINAL] = INLINE {
    OPEN Environment; RETURN[(bytes + bytesPerPage - 1)/bytesPerPage]};

  GetReadStream: PROC RETURNS [rs: Stream.Handle, rsSize: LONG CARDINAL, note: LONG STRING ← NIL] = {
    wild:   BOOLEAN = HasWildCard[directory] OR HasWildCard[fileName];
    info: STP.FileInfo;
    
    STP.Login[stp, userName, userPassword];
    STP.Connect[stp, connectName, connectPassword];
    STP.SetDirectory[stp, directory];
    rs ← STP.CreateRemoteStream[stp, fileName, read];
    rs.options.signalEndOfStream ← TRUE;
    DO 
      s: LONG STRING ← STP.NextFileName[rs ! UNWIND => rs.delete[rs]];
      IF s = NIL THEN {rs.delete[rs]; RETURN[NIL, 0, NIL]};
      info ← STP.GetFileInfo[stp ! UNWIND => rs.delete[rs]];
      rsSize ← PagesForBytes[info.size];
      OthelloDefs.WriteString[s];
      Heap.systemZone.FREE[@s];
      OthelloDefs.WriteChar['[]; OthelloDefs.WriteString[info.create];
      OthelloDefs.WriteString[", pages = "]; OthelloDefs.WriteLongNumber[rsSize];
      OthelloDefs.WriteChar[']];
      IF ~wild THEN {OthelloDefs.NewLine[]; EXIT};
      IF OthelloDefs.Yes[" [Confirm]: "L ! UNWIND => rs.delete[rs]] THEN EXIT;
      ENDLOOP;
    note ← Heap.systemZone.NEW[StringBody[60]];
    String.AppendChar[note, '[];
    String.AppendStringAndGrow[@note, host, Heap.systemZone];
    String.AppendCharAndGrow[@note, '], Heap.systemZone];
    IF String.Length[info.directory] # 0 THEN {
      String.AppendCharAndGrow[@note, '<, Heap.systemZone];
      String.AppendStringAndGrow[@note, info.directory, Heap.systemZone];
      String.AppendCharAndGrow[@note, '>, Heap.systemZone]};
    String.AppendStringAndGrow[@note, info.body, Heap.systemZone];
    String.AppendStringAndGrow[@note, " ("L, Heap.systemZone];
    String.AppendStringAndGrow[@note, info.create, Heap.systemZone];
    String.AppendCharAndGrow[@note, '), Heap.systemZone];
    RETURN};
    

  bufPages: CARDINAL = 8;

  GrabBits: PROC [
    rs: Stream.Handle, rsSizePages: LONG CARDINAL, destination: Destination, note: LONG STRING ← NIL] = {
    WITH destination SELECT FROM
      pilotFileSystemWrite => {
	buffer: LONG POINTER ← NIL;
        base: File.PageNumber ← 0;
	got: CARDINAL;
        File.SetSize[localFile, rsSizePages + OthelloDefs.leaderPages
	! Volume.InsufficientSpace => OthelloDefs.AbortingCommand["Volume Full"L]];
	SetLeaderPage[localFile, note];
	SIGNAL StartFeedback;
	WHILE base < rsSizePages DO
          thisPages: CARDINAL = CARDINAL[MIN[rsSizePages-base, bufPages]];
	  size:      CARDINAL = thisPages*Environment.bytesPerPage;
	  start:     CARDINAL ← 0;
	  buffer ← Space.Map[
	    window:[localFile, base+OthelloDefs.leaderPages, thisPages],
	    life: dead].pointer;
          DO
	    [bytesTransferred: got] ← rs.GetBlock[[
	      blockPointer: buffer, startIndex: start, stopIndexPlusOne: size] !
	        Stream.EndOfStream => {
	          got ← 0; start ← start + nextIndex; CONTINUE};
	        UNWIND => [] ← Space.Unmap[buffer]];
	    IF got = 0 THEN {[] ← Space.Unmap[buffer]; RETURN};
            IF (start ← start + got) = size THEN EXIT;
            ENDLOOP;
          --buffer ← Space.Unmap[buffer, return];
	  Process.Detach[LOOPHOLE[FORK Space.Unmap[buffer]]]; buffer ← NIL;
          OthelloDefs.FlipCursor[];
	  base ← base + thisPages;
          ENDLOOP;
	buffer ← Space.ScratchMap[1]; -- check for any leftover stuff
	[bytesTransferred: got] ← rs.GetBlock[[
	  blockPointer: buffer, startIndex: 0,
	  stopIndexPlusOne: Environment.bytesPerPage] !
	    Stream.EndOfStream => {got ← nextIndex; CONTINUE};
	    UNWIND => [] ← Space.Unmap[buffer]];
	[] ← Space.Unmap[buffer];
	IF got # 0 THEN OthelloDefs.AbortingCommand[
	  "File longer than advertised length"L]};
      string => {
	SIGNAL StartFeedback;
	DO
          stringOverhead: CARDINAL = SIZE[StringBody]*Environment.bytesPerWord;
          string: LONG STRING = Space.ScratchMap[bufPages];
          string↑ ← [
	    length: 0,
	    maxlength: bufPages*Environment.bytesPerPage - stringOverhead,
	    text: ];
	  WHILE string.length < string.maxlength DO
            got: CARDINAL;
	    [bytesTransferred: got] ← rs.get[
	      rs,
	      [blockPointer: LOOPHOLE[@string.text],
	       startIndex: string.length, stopIndexPlusOne: string.maxlength],
	      rs.options
	      ! Stream.EndOfStream => {
	          got ← 0; string.length ← string.length + nextIndex; CONTINUE};
                UNWIND => [] ← Space.Unmap[string]];
	    IF got = 0 THEN {
	      stringProc[string! UNWIND => [] ← Space.Unmap[string]];
	      [] ← Space.Unmap[string]; RETURN};
            string.length ← string.length + got;
            ENDLOOP;
          [] ← Space.Unmap[string];
          OthelloDefs.AbortingCommand["Command file too long!"L];
          ENDLOOP};
      rawWrite =>{
	buffer: LONG POINTER = Space.ScratchMap[1];
        done:  BOOLEAN ← FALSE;
        first: BOOLEAN ← TRUE;
	GetPage: PROC RETURNS [LONG POINTER] = {
          got:   CARDINAL; index: CARDINAL ← 0;
          IF first THEN {SIGNAL StartFeedback; first ← FALSE};
	  WHILE ~done DO
	    [bytesTransferred: got] ← rs.get[
	      rs,
	      [blockPointer: buffer, startIndex: index,
	       stopIndexPlusOne: Environment.bytesPerPage],
	      rs.options
	      ! Stream.EndOfStream => {got ← nextIndex; done ← TRUE; CONTINUE}];
            IF (index ← index + got) = Environment.bytesPerPage 
	       OR done THEN {OthelloDefs.FlipCursor[]; EXIT}
            ENDLOOP;
          RETURN[IF done AND index = 0 THEN NIL ELSE buffer]};
        linkProc[GetPage ! UNWIND => [] ← Space.Unmap[buffer]];
        WHILE ~done DO [] ← GetPage[! UNWIND => [] ← Space.Unmap[buffer]] ENDLOOP;
        [] ← Space.Unmap[buffer]};
      ENDCASE => ERROR};
   
  SetLeaderPage: PROCEDURE [file: File.File, note: LONG STRING] =
    BEGIN
    lp: LONG POINTER TO OthelloDefs.LeaderPage ← Space.Map[[file, 0, OthelloDefs.leaderPages]].pointer;
    lp.version ← OthelloDefs.lpVersion;
    lp.length ← MIN[note.length, OthelloDefs.lpNoteLength];
    FOR i: CARDINAL IN [0..lp.length) DO
      lp.note[i] ← note[i];
      ENDLOOP;
    [] ← Space.Unmap[lp];
    END;
   
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- initialization
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  StringInit: PROC = {
    OthelloDefs.SetCommandString[String.CopyToNewString["Online RD0"L, Heap.systemZone]]};

  OthelloDefs.RegisterCommandProc[@commandProcessor];
  StringInit[];

  END.....
  
-- November 13, 1979  10:16 AM By Forrest Changed to InitializeFTP once (and never Finalize), to use a bigger chunk in transfering (should use two buffers);  increased size of directory string to 60 from 50; Add twiddle cursor hack since people were worried about ftp dying; export Server*loaded and UserMailLoaded to eliminate 3 modules from FTP 
-- January 12, 1980  4:46 PM By Forrest Changed to used new Othello FTP and support calls to writing Raw Data
-- February 14, 1980  7:30 PM By Gobbel Notice if we didn't really retrieve any files
-- July 26, 1980  7:04 PM By Forrest Change name to OthelloFTP, export OthelloDefs
-- April 14, 1981  1:53 PM By Bruce Added string variant to dest; fixed AR4051

; Add twiddle cursor hack since people were worried about ftp dying; export Server*loaded and UserMailLoaded to eliminate 3 modules from FTP 
-- January 12, 1980  4:46 PM By Forrest Changed to used new Othello FTP and support calls to writing Raw Data
-- February 14, 1980  7:30 PM By Gobbel Notice if we didn't really retrieve any files
-- July 26, 1980  7:04 PM By Forrest Change name to OthelloFTP, export OthelloDefs
-- April 14, 1981  1:53 PM By Bruce Added string variant to dest; fixed AR4051
-- 11-Nov-81 16:54:09 By Forrest added missing catch phrase to Retrieve
-- 28-Dec-81 10:21:01 By Bruce removed Guest Guest and set signalEndOfStream TRUE
-- 13-Jan-82 10:23:20 By Forrest removed extra ! UNWIND => Space.Unmap[space] from Initial ucode fetch.
-- 11-Dec-82 15:28:08 By Johnsson removed Storage; added default extensions.
-- 13-Apr-83  9:38:39 By Johnsson Klamath conversion.