// TridentEFTP.bcpl

// last modified on October 24, 1980  11:18 AM by Butterfield
// - EFTP, add T global switch to go to Trident drive 0 - 10/23
// - EFTPCli, don't use GP to open the files - 10/23
// - derived from:
   // EFTP.bcpl -- top level routine and user interface for
   // the EFTP subsystem
   // June 17, 1976  3:44 PM  ap,bak,jfs
   // last modified on June 3, 1979  11:52 AM by Taft
   // Copyright Xerox Corporation 1979
//                                                 - 10/23/80

get "PupEFTP.decl"  //that will also get pup-related stuff
get "Streams.d"

external
[
//outgoing procedures
Msg

//incoming procedures
InitEFTPPackage; EFTPSendFile; EFTPReceiveFile
FixedLeft; GetFixed; InitializeZone; Allocate
InitializeContext; CallContextList; Block
InitPupLevel1; InitializeTimer; Enqueue; GetPartner
OpenFile; Closes; Endofs; Gets; Puts
Ws; Wl; Wo; Wns; CallSwat
SetupReadParam; ReadParam; EvalParam
LoadRam; SysErr; TFSInit; TFSClose; TFSSilentBoot;

//outgoing statics
EFTPZone; EFTPCtxq
EFTPFrnPort; EFTPLclPort; EFTPSocket

//incoming statics
keys;dsp
DiskRamImage; sysDisk;
]

compileif nova then [ external [ InitNovaAlto ] ]

static
[
eftpDisk;
EFTPZone;EFTPCtxq
EFTPFrnPort;EFTPLclPort;EFTPSocket

tempfile; tempstring
DebugSwitch = false
DebugConfirmSwitch = false
cliBody; cliSwitches   
]

manifest
[
cliStackSize = 3000
kbdStackSize = 3000
noHintFp = 0;  // so readers won't have to figure out what the argument is
]


//-----------------------------------------------------------------
let EFTP() be
//-----------------------------------------------------------------
[
let versionText = "EFTP of October 23, 1980"

//For the time being, allocate everything here
let frnPort = vec lenPort; EFTPFrnPort = frnPort
let lclPort = vec lenPort; EFTPLclPort = lclPort
let socket = vec lenEFTPSoc; EFTPSocket = socket
let tempString = vec 128; tempstring = tempString

compileif alto then [ Msg("Initialize zones") ]
let zoneSize = FixedLeft()-1500   //Set up a zone, for later use
if zoneSize ls 0 then zoneSize = #77777  //biggest Alloc can handle
EFTPZone = GetFixed(zoneSize)
InitializeZone(EFTPZone, zoneSize)
compileif nova then [ InitNovaAlto(EFTPZone) ]
Wl(versionText)
// Ws("["); Wns(dsp, zoneSize, 0, 10); Ws(" - (")
// Wns(dsp, zoneSize, 0, 10); Ws("+1) = ")
// Wns(dsp, FixedLeft(), 0, 10)
// Wl("]")

// Check for any global flags...  We will set it up here, others
// can read more of it later
let cli = false
let body = vec 256; cliBody = body
let switches = vec 128; cliSwitches = switches

// Read from the default, file COM.CM
SetupReadParam(cliBody, cliSwitches, 0, 0)
// Set global switch defaults
eftpDisk = sysDisk;
// Scan the global switches
for i = 1 to cliSwitches!0 switchon cliSwitches!i into
   [GlobalSwitchBlock
   case $d: case $D:
      [
      DebugSwitch = true
      endcase
      ]
   case $c: case $C:
      [
      DebugConfirmSwitch = true
      DebugSwitch = true
      endcase
      ]
   case $s: case $S:
      [
      CallSwat("Scanning global switches")
      endcase
      ]
   case $t: case $T:
      [
      if LoadRam(DiskRamImage, true) ls 0 then
       [ Wl("Cannot load ram"); finish; ]
      eftpDisk = TFSInit(EFTPZone, true);
      if eftpDisk eq 0 then [ Wl("Cannot initialize Trident"); finish; ]
      endcase
      ]
   default:
      [
      Ws("Unknown global switch: ")
      Puts(dsp, cliSwitches!i)
      Wl("")
      ]
   ]GlobalSwitchBlock

// See if there is anything else left in COM.CM, get unpacked string
ReadParam(0, 0, 0, 0, true)   //puts file name into cliBody
if cliBody!0 ne 0 then cli = true

// Major task here is to initialize storage and context list,
// start level1 running, decide what other processes (contexts)
// to enable, and then let them all run

Msg("Initialize contexts")
EFTPCtxq = Allocate(EFTPZone, 2)   //Main context queue
EFTPCtxq!0 = 0

Msg("Initialize pup stuff")
//Initializations
InitPupLevel1(EFTPZone, EFTPCtxq, 10)   //up to 10 buffers
InitEFTPPackage()


// Decide if we should schedule the interactive input handler,
// or the command line interpreter

test cli   
   ifso InitEFTPCli()
   ifnot InitEFTPKbd()

Msg("Start context mechanism")
//Now pass off control, and never return here
CallContextList(EFTPCtxq!0) repeat    //loop forever
]


// These next two routines run in the context (and stack) of
// EFTP(), but merely establish and enqueue a context in which
// the real routines can run.  Actual routines can live elsewhere.
//-----------------------------------------------------------------
and InitEFTPCli() be
//-----------------------------------------------------------------
Enqueue(EFTPCtxq, InitializeContext(Allocate(EFTPZone, cliStackSize),
 cliStackSize, EFTPCli))


//-----------------------------------------------------------------
and InitEFTPKbd() be
//-----------------------------------------------------------------
Enqueue(EFTPCtxq, InitializeContext(Allocate(EFTPZone, kbdStackSize),
 kbdStackSize, EFTPKbd))


//-----------------------------------------------------------------
and Msg(str) be
//-----------------------------------------------------------------
[
unless DebugSwitch return
Ws(str)
test DebugConfirmSwitch  
   ifso [ Wl("[OK??]"); Gets(keys) ]
   ifnot Wl("")
]


// One of these routines will have been placed on the context list,
// and will therefore be running
//-----------------------------------------------------------------
and EFTPCli() be
//-----------------------------------------------------------------
[
// Pick off the relevent information from the file COM.CM
// format is: eftp  to/from 
// If we get here, the file name is already in cliBody
ReadParam(0, 0, tempstring)   //get the direction
switchon tempstring!1 into
   [CliCmd
   case $t: case $T:
      [
      //tempfile =
      //   EvalParam(cliBody, "I", "File does not exist, try again:")
      let body = cliBody;
         [  // beginning of a repeat
         EvalParam(body, "P", "File does not exist, try again: ", tempstring);
         tempfile = OpenFile(tempstring, ksTypeReadOnly, charItem, verLatest,
          noHintFp, SysErr, EFTPZone, nil, eftpDisk);
         let nullBody = vec 256; nullBody!0 = 0; body = nullBody;
         ] repeatuntil tempfile
      unless GetPartner(ReadParam("P"), dsp, EFTPFrnPort,
       0, socketEFTPReceive) do
         [
         CliError()
         endcase
         ]
      ReportResult(EFTPSendFile(tempfile, EFTPFrnPort))
      Closes(tempfile)
      endcase
      ]
   case $f: case $F:
      [
      //tempfile= EvalParam(cliBody, "O")
      let body = cliBody;
         [  // beginning of a repeat
         EvalParam(body, "P", "Can't open file, try again: ", tempstring);
         tempfile = OpenFile(tempstring, ksTypeWriteOnly, charItem, verNew,
          noHintFp, SysErr, EFTPZone, nil, eftpDisk);
         let nullBody = vec 256; nullBody!0 = 0; body = nullBody;
         ] repeatuntil tempfile
      unless GetPartner(ReadParam("P"), dsp, EFTPFrnPort) do
         [
         CliError()
         endcase
         ]
      ReportResult(EFTPReceiveFile(tempfile, EFTPFrnPort))
      Closes(tempfile)
      endcase
      ]
   default:
      [
      CliError()
      endcase
      ]
   ]CliCmd
if eftpDisk ne sysDisk then [ TFSClose(eftpDisk); TFSSilentBoot(); ]
finish   // we do not hang around.....
]

//-----------------------------------------------------------------
and CliError() be
//-----------------------------------------------------------------
[
Wl("Take care, the syntax for command line should be:")
Ws("eftp  to/from ")
]

//-----------------------------------------------------------------
and EFTPKbd() be
//-----------------------------------------------------------------
[
[CmdLoop
let abortCmd = false
Msg("Get command from kbd")
Ws("->")
let cmd = GetKeys()
switchon cmd into
   [CommandBlock
   case $S: case $s:
      [
      Ws("Send file -- name or number of remote host: ")
         [
         unless GetString(tempstring) then 
            [
            abortCmd = true
            break
            ]
         if tempstring>>String.length eq 0 then loop
         if GetPartner(tempstring, dsp, EFTPFrnPort,
          0, socketEFTPReceive) then break
         Ws("...try again: ")
         ] repeat
      if abortCmd then loop
      Ws("local file name: ")
         [
         unless GetString(tempstring) then 
            [
            abortCmd = true
            break
            ]
         tempfile = OpenFile(tempstring, ksTypeReadOnly, charItem, verLatest,
          noHintFp, SysErr, EFTPZone, nil, eftpDisk); 
         if tempfile ne 0 then break
         Ws("File "); Ws(tempstring)
         Ws(" does not exist, try again: ")
         ] repeat
      if abortCmd then loop
      ReportResult(EFTPSendFile(tempfile, EFTPFrnPort))
      Closes(tempfile)
      endcase
      ]
   case $R: case $r:
      [
      Ws("Receive a file -- name or number of remote host: ")
         [
         unless GetString(tempstring) then 
            [
            abortCmd = true
            break
            ]
         if tempstring>>String.length eq 0 then loop
         if GetPartner(tempstring, dsp, EFTPFrnPort) then break
         Ws("...try again: ")
         ] repeat
      if abortCmd then loop
         [
         Ws("local file name: ")
         unless GetString(tempstring) then 
            [
            abortCmd = true
            break
            ]
         tempfile = OpenFile(tempstring, ksTypeReadOnly, charItem, verLatest,
          noHintFp, SysErr, EFTPZone, nil, eftpDisk);
         if tempfile ne 0 then
            [
            //File opened, it already exists
            Closes(tempfile)
            unless Confirm("File exists, OK to Overwrite?")  loop
            ] 
         //Now open a new file, for writing
         tempfile = OpenFile(tempstring, ksTypeWriteOnly, charItem, verNew,
          noHintFp, SysErr, EFTPZone, nil, eftpDisk);
         break
         ] repeat
      if abortCmd then loop

      ReportResult(EFTPReceiveFile(tempfile, EFTPFrnPort))
      Closes(tempfile)
      endcase
      ]
   case #15:      //CR is a no-op
      [
      Wl("")
      endcase
      ]
   case $Q: case $q:
      [
      Ws("Quit...")
      if Confirm("Confirm?") then
         [
         if eftpDisk ne sysDisk then [ TFSClose(eftpDisk); TFSSilentBoot(); ]
         finish;
         ]
      endcase   //program terminates here!!!!
      ]
   case $?: 
      [
      Wl("Send, Receive, Quit...")
      endcase
      ]
   default:
      [
      Puts(dsp,cmd)
      Wl(" ??? Send, Receive, Quit...")
      endcase
      ]
   ]CommandBlock
Block()
]CmdLoop repeat         //will run until a quit
]


//-----------------------------------------------------------------
and ReportResult(boolean) be
//-----------------------------------------------------------------
[
test boolean
   ifso Wl("...all done")
   ifnot Wl("...file transfer failed")
]


//-----------------------------------------------------------------
and GetKeys() = valof
//-----------------------------------------------------------------
[
while Endofs(keys) do Block()
resultis Gets(keys)
]


//-----------------------------------------------------------------
and GetString(str) = valof
//-----------------------------------------------------------------
[
// Caller must provide the place to put the string
// Returns false if DEL is hit.  Uses default streams dsp and keys
let deletingFlag, char = false, 0
str>>String.length = 0

   [charLoop
   if str>>String.length eq 255 then resultis true
   char = GetKeys()   //this will block for a bit
   switchon char into
      [CharBlock
      case #15:      //CR
         [
         Wl("")
         resultis true
         ]
      case #21:      //↑Q, reset the line
         [
         Wl(" xxx")
         deletingFlag=false
         char=0
         str>>String.length=0
         endcase
         ]
      case #10: case #1:      //BS or ↑A
         [
         if str>>String.length eq 0 then endcase
         unless deletingFlag then
            [
            deletingFlag=true
            Ws("[")
            ]
         Puts(dsp,str>>String.char↑(str>>String.length))
         str>>String.length = str>>String.length - 1
         endcase
         ]
      case #22:      //↑R, retype the line
         [
         Wl(""); Ws(str); deletingFlag=false
         endcase
         ]
      case #177:   //DEL,delete the line and return false
         [
         Wl(" XXX")
         resultis false
         ]
      default:      
         [
         if deletingFlag then
            [
            deletingFlag=false
            Ws("]")
            ]
         str>>String.length = str>>String.length + 1
         str>>String.char↑(str>>String.length) = char
         Puts(dsp, char)
         ]
      ]CharBlock
   ]charLoop  repeat

]


//-----------------------------------------------------------------
and Confirm(confirmString) = valof
//-----------------------------------------------------------------
[
// A general guy, for confirming some action
Puts(dsp,$[); Ws(confirmString); Puts(dsp,$])
switchon GetKeys() into
   [
   case $y: case $Y: case $*N:
      [ Wl(" Yes."); resultis true ]
   case $n: case $N: case $*177:
      [ Wl(" No."); resultis false ]
   default: Wl(" ?")
   ] repeat
]