// GateConEFTP.bcpl -- store/retrieve a file on the gateway's disk
// Last modified July 2, 1983  10:31 PM by Boggs

get "Streams.d"
get "AltoDefs.d"
get "PupEftp.decl"
get "GateConServ.decl"

external
[
// outgoing procedures
Eftp

// incoming procedures
Zero; MoveBlock; SendCommand
DoubleAdd; DoubleDifference; Divide32x16
SetTimer; TimerHasExpired; Dismiss; ReadCalendar
GetPBI; ReleasePBI; AppendStringToPup; CompletePup
StringCompare; CopyString
CreateCmdBox; ResetCmdMenu; TopLevel; BoxProc; ReturnFrom
Gets; GetString; PutTemplate; Ws; Wss; BackSpace
OpenFile; SetFilePos; ReadBlock; WriteBlock
FilePos; Endofs; Closes; Puts
OpenEFTPSoc; SendEFTPBlock; ReceiveEFTPBlock
SendEFTPEnd; CloseEFTPSoc

// incoming statics
dsp; keys; errorDsp; gcHost; gcNet
]

//----------------------------------------------------------------------------
let Eftp() be
//----------------------------------------------------------------------------
[
ResetCmdMenu()
CreateCmdBox(TopLevel, "TopLevel")
CreateCmdBox(StoreFile, "Store")
CreateCmdBox(RetrieveFile, "Retrieve")
// ResetCmdMenu destroyed the BoxQ which BoxProc is following.
// If we just return now, BoxProc will get horribly confused.
// So don't let it continue: force a return from BoxProc.
ReturnFrom(BoxProc)
]

//----------------------------------------------------------------------------
and StoreFile() = valof
//----------------------------------------------------------------------------
// This procedure doesn't actually return a value:
// If things go sour, we abort by saying "resultis BailOut(...)"
[
let bytes, soc, pbi, stream = 0, 0, 0, 0
let name = vec 128
unless GetString("*NStore local file ", name) resultis false
stream = OpenFile(name, ksTypeReadOnly, charItem)
if stream eq 0 then resultis Ws(" - no such file")
let bootFile = StringCompare(name, ".boot", name>>String.length-4) eq 0
PutTemplate(dsp, "as remote file $S", name)
let char = Gets(keys); if char ne $*N then
   [
   for i = name>>String.length to 1 by -1 do
      BackSpace(dsp, name>>String.char↑i)
   unless GetString(0, name, true, char) resultis
      BailOut(bytes, soc, pbi, stream)
   ]

// send file name and get EFTP port
let soc = vec lenEFTPSoc; OpenEFTPSoc(soc, 0, 0)
pbi = GetPBI(soc)
AppendStringToPup(pbi, 1, name)
pbi = SendCommand(pbi, ptStore, pbi>>PBI.pup.length, 10)
if pbi eq 0 then
   [
   Ws(" - no response")
   resultis BailOut(bytes, soc, pbi, stream)
   ]
if pbi>>PBI.pup.type ne ptAck then
   [
   Ws(" - server refuses")
   resultis BailOut(bytes, soc, pbi, stream)
   ]
MoveBlock(lv soc>>PupSoc.frnPort, lv pbi>>PBI.pup.words, lenPort)

// send file, reformatting if SO-format bootFile
let buffer = lv pbi>>PBI.pup
bytes = ReadBlock(stream, buffer, 256) lshift 1
if (FilePos(stream) & 1) eq 1 then bytes = bytes -1
if bootFile then
   [
   ReadCalendar(buffer+3)  //set date to now
   test buffer!1 ne 0  //SO-format?
      ifso buffer!1 = 0  //mark as B-format now
      ifnot bootFile = false  //already B-format
   ]
bytes = SendEFTPBlock(soc, buffer, bytes, 1000)
if bytes le 0 resultis BailOut(bytes, soc, pbi, stream)

test bootFile
   ifso
      [
      Ws("*NReformatting...")
      SetFilePos(stream, 1, 254 lshift 9)
      ReadBlock(stream, buffer, 256)  //loc 0-#377
      bytes = SendEFTPBlock(soc, buffer, 512, 1000)
      if bytes le 0 resultis BailOut(bytes, soc, pbi, stream)
      SetFilePos(stream, 0, 512)
      ]
   ifnot Ws("*NTransferring...")

let bits = vec 1; Zero(bits, 2)
let saveCursor = vec 16; MoveBlock(saveCursor, cursorBitMap, 16)
for i = 0 to 7 do cursorBitMap!i = 177400b
for i = 8 to 15 do cursorBitMap!i = 377b
let startTime = vec 1; ReadCalendar(startTime)
for i = 2 to bootFile? 253, 1000 do
   [
   if Endofs(stream) then break
   bytes = ReadBlock(stream, buffer, 256) lshift 1
   if (FilePos(stream) & 1) eq 1 then bytes = bytes -1
   if bytes eq 0 break
   bytes = SendEFTPBlock(soc, buffer, bytes, 3000)
   for i = 0 to 15 do cursorBitMap!i = not cursorBitMap!i
   let inc = vec 1; inc!0 = 0; inc!1 = 8*bytes
   DoubleAdd(bits, inc)
   if bytes ls 0 break
   ]
MoveBlock(cursorBitMap, saveCursor, 16)
if bytes ls 0 resultis BailOut(bytes, soc, pbi, stream)
let stopTime = vec 1; ReadCalendar(stopTime)
test SendEFTPEnd(soc, 3000)
   ifso
      [
      Divide32x16(bits, DoubleDifference(stopTime, startTime))
      PutTemplate(dsp, "$EUD bits/sec...", bits)
      ]
   ifnot resultis BailOut(EFTPTimeout, soc, pbi, stream)

// See if he got it
let gotReply = false
let timer = nil; SetTimer(lv timer, 12000)  //2 minutes!
   [
   let rpbi = SendCommand(GetPBI(soc), ptStats, pupOvBytes)
   if rpbi ne 0 then
      [
      let status = rpbi>>PBI.pup.type eq ptAck?
       (lv rpbi>>PBI.pup.words)>>Stats.ftpStatus, statusBusy
      ReleasePBI(rpbi)
      switchon status into
         [
         case statusBusy:
            [ Dismiss(500); loop ]
         case statusYes:
            [ Ws("Done"); gotReply = true; break ]
         default:
            [
            Ws(" - failed")
            gotReply = true
            if status ne statusNo then
               PutTemplate(errorDsp, " - error code $UO", status)
            break
            ]
         ]
      ]
   ] repeatuntil TimerHasExpired(lv timer)
unless gotReply do
   [
   Ws("no response")
   Ws("*NI am unable to confirm safe delivery of the file")
   ]

BailOut(0, soc, pbi, stream)
]

//----------------------------------------------------------------------------
and BailOut(bytes, soc, pbi, stream) be
//----------------------------------------------------------------------------
// Releases any resources we accumulated.
[
if stream ne 0 then Closes(stream)
if pbi ne 0 then ReleasePBI(pbi)
if soc ne 0 then CloseEFTPSoc(soc)
if bytes ls 0 then
   [
   Ws(selecton bytes into
      [
      case EFTPTimeout: " - Timeout"
      case EFTPAbortReceived: " - Abort received"
      case EFTPAbortSent: " - Abort sent"
      default: " - Unknown error code"
      ])
   ]
]

//----------------------------------------------------------------------------
and RetrieveFile() = valof
//----------------------------------------------------------------------------
// This procedure doesn't actually return a value:
// If things go sour, we abort by saying "resultis BailOut(...)"
[
let bytes, soc, pbi, stream = 0, 0, 0, 0
let buffer = vec 256
let localName, remoteName = buffer, buffer+128
unless GetString("*NRetrieve remote file ", remoteName) resultis false
CopyString(localName, remoteName)
PutTemplate(dsp, "as local file $S", localName)
let char = Gets(keys); if char ne $*N then
   [
   for i = localName>>String.length to 1 by -1 do
      BackSpace(dsp, localName>>String.char↑i)
   unless GetString(0, localName, true, char) resultis false
   ]
stream = OpenFile(localName, ksTypeWriteOnly, charItem)

// send file name
let soc = vec lenEFTPSoc; OpenEFTPSoc(soc, 0, 0)
for i = 1 to 10 do  //worst case: 20 seconds
   [
   let pbi = GetPBI(soc)
   pbi>>PBI.pup.id↑1 = gcPassword
   pbi>>PBI.pup.dPort.net = gcNet
   pbi>>PBI.pup.dPort.host = gcHost
   pbi>>PBI.pup.dPort.socket↑1 = gcSocket1
   pbi>>PBI.pup.dPort.socket↑2 = gcSocket2
   AppendStringToPup(pbi, 1, remoteName)
   CompletePup(pbi, ptRetrieve)
   bytes = ReceiveEFTPBlock(soc, buffer, 200)  //2 seconds
   if bytes gr 0 break
   ]
if bytes le 0 resultis BailOut(bytes, soc, pbi, stream)

Ws("*NTransferring...")
let bits = vec 1; Zero(bits, 2)
let saveCursor = vec 16; MoveBlock(saveCursor, cursorBitMap, 16)
for i = 0 to 7 do cursorBitMap!i = 177400b
for i = 8 to 15 do cursorBitMap!i = 377b
let startTime = vec 1; ReadCalendar(startTime)
   [
   WriteBlock(stream, buffer, bytes rshift 1)
   if (bytes & 1) eq 1 then Puts(stream, buffer!(bytes rshift 1) rshift 8)
   bytes = ReceiveEFTPBlock(soc, buffer, 3000)  //30 seconds
   for i = 0 to 15 do cursorBitMap!i = not cursorBitMap!i
   let inc = vec 1; inc!0 = 0; inc!1 = 8*bytes
   DoubleAdd(bits, inc)
   ] repeatuntil bytes le 0
MoveBlock(cursorBitMap, saveCursor, 16)
if bytes ls 0 resultis BailOut(bytes, soc, pbi, stream)
let stopTime = vec 1; ReadCalendar(stopTime)
Divide32x16(bits, DoubleDifference(stopTime, startTime))
PutTemplate(dsp, "$EUD bits/sec...", bits)
BailOut(0, soc, pbi, stream)
Ws("Done")
]