// FtpCli1.bcpl -- data transfer commands
// Copyright Xerox Corporation 1979, 1980, 1981, 1982
// Last modified July 21, 1982  6:23 PM by Boggs

get "FtpProt.decl"
get "FtpUser.decl"
get "AltoFileSys.d"
get "Streams.d"

external
[
// outgoing procedures
CliStore; CliRetrieve; CliDump; CliLoad

// incoming procedures from FtpUserProt
UserStore; UserRetrieve; UserDirectory

// incoming procedures from FtpUtil
DiskToNet; NetToDisk; DumpToNet; LoadFromNet
FTPM; DblUsc; FillPLFromLD

// incoming procedures from FtpMiscB
CloseLocalFile; MakeNAMB; ProcessNoCode; FreePointer

// incoming procedures form FtpPlist
FreePList; InitPList; WritePTV

// incoming procedures from FtpCliUtil
CliSwitches; IsCommand; CliError
CliGetString; CliConfirm

// miscellaneous
ExtractSubstring; StringCompare; CopyString
Allocate; Free; Zero; MoveBlock; CallSwat
OpenFile; DeleteFile; LookupEntries
ReadLeaderPage; WriteLeaderPage
Closes; Puts; Enqueue; Dequeue
PutTemplate; Wss

// incoming statics
sysZone; ftpDisk; defaultPL; CtxRunning
userDsp; cli; mt; fpSysDir

overwrite; selective; verify
all; dates; update
]

manifest maxNames = 50

static [ nameVec; cdat; newVers ]

structure String [ length byte; char↑1,1 byte ]

//-----------------------------------------------------------------------------------------
let CliStore() be
//-----------------------------------------------------------------------------------------
[
selective, verify = false, false
all, dates, update = false, false, false
CliSwitches()

let localPL = 0
nameVec = Allocate(sysZone, maxNames); Zero(nameVec, maxNames)
let dvVec = Allocate(sysZone, lDV*maxNames)
let numNames = 0  //number of valid entries in nameVec
let nameIndex = 0  //name we are working on
let firstTime = true
   [  //giant repeat loop
   if selective & not firstTime break
   if nameIndex eq numNames then  //nameVec is empty.  fill it.
      [
      if cli ne 0 break  //exhausted nameVec and bumped into a command
      numNames, nameIndex = 0, 0
      for i = 0 to maxNames-1 do FreePointer(nameVec+i)
      for i = 0 to maxNames-1 do
         [
         cli = CliGetString(false); if IsCommand() break
         nameVec!i = cli; cli = 0; numNames = numNames+1
         ]
      if numNames eq 0 break  //didn't find any.  we are done.
      let sysDir = OpenFile("SysDir", 0, 0, 0, fpSysDir, 0, 0, 0, ftpDisk)
      if sysDir eq 0 then CallSwat("Can't open sysDir!")
      LookupEntries(sysDir, nameVec, dvVec, numNames, true)
      Closes(sysDir)
      ]

   if selective & numNames ne 2 then
      [ CliError("Store/S must specifiy EXACTLY 2 filenames"); break ]
   let dv = dvVec + lDV*nameIndex
   let fp = dv + offset DV.fp/16
   let name = nameVec!nameIndex
   PutTemplate(userDsp, "$S$S", (firstTime? "", "*N**Store "), name)
   firstTime = false
   CtxRunning>>FtpCtx.diskStream = OpenFile(name, ksTypeReadOnly, charItem,
    0, fp, 0, 0, 0, ftpDisk)
   nameIndex = nameIndex+1
   if CtxRunning>>FtpCtx.diskStream eq 0 then
      [ CliError(" - No such file"); loop ]

   localPL = FillPLFromLD()
   if dates then PutTemplate(userDsp, " [$P]", WritePTV, lv localPL>>PL.CDAT)
   Wss(userDsp, " as remote file ")
   test selective
      ifso
         [
         localPL>>PL.SFIL = nameVec!1; nameVec!1 = 0
         Wss(userDsp, localPL>>PL.SFIL)
         ]
      ifnot
         [
         localPL>>PL.NAMB = MakeNAMB(name)
         Wss(userDsp, localPL>>PL.NAMB)
         ]

// CliStore (cont'd)

   let mark, ok = 0, true
   if update % verify % dates then
      [
      localPL>>PL.DPRP.CDAT = true
      localPL>>PL.DPRP.VERS = true
      mt>>MT.ptx↑markNo = false
      let v = vec 2; Zero(v, 2); cdat = v
      newVers = true
         [
         mark = UserDirectory(localPL, CliStoreList)
         if mark<<Mark.mark eq markNo then
            test mark<<Mark.subCode eq 100b  // file not found
               ifso
                  [
                  Wss(userDsp, " [New file]")
                  if update then ok = all
                  ]
               ifnot if ProcessNoCode(mark<<Mark.subCode, localPL) loop
         if mark<<Mark.mark eq markEndOfCommand then
            [
            if update then
               ok = (((table [ dLS; dEQ; dGR ]+1)!DblUsc(lv localPL>>PL.CDAT, cdat)) & update) ne 0
            test dates
               ifso PutTemplate(userDsp, " [$P]", WritePTV, cdat)
               ifnot test newVers
                  ifso Wss(userDsp, " [New version]")
                  ifnot Wss(userDsp, " [Old file]")
            ]
         break
         ] repeat
      mt>>MT.ptx↑markNo = true
      ]

   if ok & verify then ok = CliConfirm()
   localPL>>PL.DPRP = 0
   localPL>>PL.DPRP.SFIL = true
   if ok then
      [
      mark = UserStore(localPL, CliStoreFile)
      if mark<<Mark.mark eq markNo then
         if ProcessNoCode(mark<<Mark.subCode, localPL) loop
      break
      ] repeat
   if mark ne markYes then Wss(userDsp, " - NOT stored")

   CloseLocalFile()
   localPL = FreePList(localPL)
   if mark eq 0 break
   ] repeat

CloseLocalFile()
FreePList(localPL)
for i = 0 to maxNames-1 do FreePointer(nameVec+i)
Free(sysZone, nameVec)
Free(sysZone, dvVec)
]

//-----------------------------------------------------------------------------------------
and CliStoreFile(remotePL, localPL) = valof
//-----------------------------------------------------------------------------------------
[
if remotePL eq 0 then remotePL = localPL
PutTemplate(userDsp, "*N$S, ", remotePL>>PL.SFIL)
resultis DiskToNet(remotePL, localPL)
]

//-----------------------------------------------------------------------------------------
and CliStoreList(remotePL, localPL) be
//-----------------------------------------------------------------------------------------
[
if DblUsc(lv remotePL>>PL.CDAT, cdat) gr 0 then
   MoveBlock(cdat, lv remotePL>>PL.CDAT, 2)
newVers = remotePL>>PL.VERS ne 0
]

//-----------------------------------------------------------------------------------------
and CliRetrieve() be
//-----------------------------------------------------------------------------------------
[
overwrite = true
selective, verify = false, false
all, dates, update = false, false, false
CliSwitches()

let firstTime = true
   [
   cli = CliGetString(false); if IsCommand() return
   unless firstTime do Wss(userDsp, "*N**Retrieve ")
   firstTime = false
   PutTemplate(userDsp, "remote file $S", cli)
   let localPL = InitPList(defaultPL)
   localPL>>PL.SFIL = cli; cli = 0
   localPL>>PL.DPRP.SFIL = true
   localPL>>PL.DPRP.NAMB = true
   localPL>>PL.DPRP.TYPE = true
   localPL>>PL.DPRP.BYTE = true
   localPL>>PL.DPRP.CDAT = true
   localPL>>PL.DPRP.SIZE = true
   if dates % update then localPL>>PL.DPRP.CDAT = true
   let mark = nil
      [
      mark = UserRetrieve(localPL, CliRetrieveWantFile, CliRetrieveCleanup)
      if mark<<Mark.mark eq markEndOfCommand break
      test mark<<Mark.mark eq markNo
         ifso if ProcessNoCode(mark<<Mark.subCode, localPL) loop
         ifnot CliError(" - command failed")
      break
      ] repeat
   CloseLocalFile()
   FreePList(localPL)
   if mark eq 0 % (selective & mark ne markEndOfCommand) break
   ] repeat
]

//-----------------------------------------------------------------------------------------
and CliRetrieveWantFile(remotePL, localPL) = valof
//-----------------------------------------------------------------------------------------
[
if remotePL>>PL.NAMB eq 0 resultis false
if remotePL>>PL.SFIL eq 0 then
   remotePL>>PL.SFIL = ExtractSubstring(remotePL>>PL.NAMB)
if selective then
   [
   FreePointer(lv remotePL>>PL.NAMB)
   remotePL>>PL.NAMB = CliGetString()
   ]
test CliOpenLocalFile(remotePL)
   ifnot [ Wss(userDsp, " - Not retrieved"); resultis false ]
   ifso [ Puts(userDsp, $*N); resultis NetToDisk ]
]

//-----------------------------------------------------------------------------------------
and CliRetrieveCleanup(remotePL, ok) be
//-----------------------------------------------------------------------------------------
[
CloseLocalFile()
unless ok do DeleteFile(remotePL>>PL.NAMB, 0, 0, 0, 0, ftpDisk)
]

//-----------------------------------------------------------------------------------------
and CliDump() be
//-----------------------------------------------------------------------------------------
[
FreePointer(lv cli); cli = CliGetString(); if IsCommand() return
PutTemplate(userDsp, "to remote file $S", cli)
let localPL = InitPList(defaultPL)
localPL>>PL.TYPE = Binary
localPL>>PL.BYTE = 8
localPL>>PL.SFIL = cli; cli = 0
localPL>>PL.DPRP.SFIL = true
   [
   let mark = UserStore(localPL, CliDumpFile)
   if mark<<Mark.mark eq markYes break
   test mark<<Mark.mark eq markNo
      ifso if ProcessNoCode(mark<<Mark.subCode, localPL) loop
      ifnot CliError(" - command failed")
   break
   ] repeat
CloseLocalFile()
FreePList(localPL)
]

//-----------------------------------------------------------------------------------------
and CliDumpFile(remotePL, localPL) = valof
//-----------------------------------------------------------------------------------------
[
let itWentOK = true
let nameVec = Allocate(sysZone, maxNames); Zero(nameVec, maxNames)
let dvVec = Allocate(sysZone, lDV*maxNames)
let numNames = 0  //number of valid entries in nameVec
let nameIndex = 0  //name we are working on
FTPM(markHereIsFile)
   [  //repeat
   if nameIndex eq numNames then  //nameVec is empty.  fill it.
      [
      if cli ne 0 break
      numNames, nameIndex = 0, 0
      for i = 0 to maxNames-1 do FreePointer(nameVec+i)
      for i = 0 to maxNames-1 do
         [
         cli = CliGetString(false); if IsCommand() break
         nameVec!i = cli; cli = 0; numNames = numNames+1
         ]
      if numNames eq 0 break  //didn't find any.  we are done.
      let sysDir = OpenFile("SysDir", 0, 0, 0, fpSysDir, 0, 0, 0, ftpDisk)
      if sysDir eq 0 then CallSwat("Can't open sysDir!")
      LookupEntries(sysDir, nameVec, dvVec, numNames, true)
      Closes(sysDir)
      ]

   let dv = dvVec + lDV*nameIndex
   let fp = dv + offset DV.fp/16
   let name = nameVec!nameIndex; nameIndex = nameIndex+1
   PutTemplate(userDsp, "*N$S ← $S", remotePL>>PL.SFIL, name)
   CtxRunning>>FtpCtx.diskStream = OpenFile(name, ksTypeReadOnly, charItem,
    0, fp, 0, 0, 0, ftpDisk)
   if CtxRunning>>FtpCtx.diskStream eq 0 then
      [ CliError(" - No such file"); loop ]
   FreePointer(lv localPL>>PL.NAMB)
   localPL>>PL.NAMB = ExtractSubstring(name)
   let ld = CtxRunning>>FtpCtx.buffer
   ReadLeaderPage(CtxRunning>>FtpCtx.diskStream, ld)
   MoveBlock(lv localPL>>PL.CDAT, lv ld>>LD.created, 2)
   itWentOK = DumpToNet(remotePL, localPL)
   CloseLocalFile()
   unless itWentOK break
   ] repeat

if itWentOK then DumpToNet(0)  //write END block
for i = 0 to maxNames-1 do FreePointer(nameVec+i)
Free(sysZone, nameVec)
Free(sysZone, dvVec)
resultis itWentOK
]

//-----------------------------------------------------------------------------------------
and CliLoad() be
//-----------------------------------------------------------------------------------------
[
overwrite = true
selective, verify = false, false
all, dates, update = false, false, false
CliSwitches()

let firstTime = true
   [
   cli = CliGetString(false); if IsCommand() return
   unless firstTime do Wss(userDsp, "*N**Load ")
   firstTime = false
   PutTemplate(userDsp, "from remote file $S", cli)
   let localPL = InitPList(defaultPL)
   localPL>>PL.TYPE = Binary
   localPL>>PL.BYTE = 8
   localPL>>PL.SFIL = cli; cli = 0
   localPL>>PL.DPRP.SFIL = true
   localPL>>PL.DPRP.NAMB = true
   localPL>>PL.DPRP.BYTE = true
   localPL>>PL.DPRP.TYPE = true

   if selective then
      [
      let t = vec 1; t!0 = 0; nameVec = t
         [
         cli = CliGetString(false); if IsCommand() break
         if cli>>String.char↑(cli>>String.length) eq $. then
            cli>>String.length = cli>>String.length -1
         let t = Allocate(sysZone, cli>>String.length lshift 1 +2)
         CopyString(t+1, cli); FreePointer(lv cli)
         Enqueue(nameVec, t)
         ] repeat
      if nameVec!0 eq 0 then selective = false
      ]

   let mark = nil
      [
      mark = UserRetrieve(localPL, CliLoadWantFile)
      if mark<<Mark.mark eq markEndOfCommand break
      test mark<<Mark.mark eq markNo
         ifso if ProcessNoCode(mark<<Mark.subCode, localPL) loop
         ifnot CliError(" - command failed")
      break
      ] repeat
   CloseLocalFile()
   FreePList(localPL)
   if selective then
      [
      while nameVec!0 ne 0 do
         Free(sysZone, Dequeue(nameVec))
      break
      ]
   if mark eq 0 break  //catastrophic error
   ] repeat
]

//-----------------------------------------------------------------------------------------
and CliLoadWantFile(remotePL, localPL) = valof
//-----------------------------------------------------------------------------------------
[
unless remotePL>>PL.TYPE eq Binary & remotePL>>PL.BYTE eq 8 do
   [
   CliError("*N$S skipped - not in dump format", false, remotePL>>PL.SFIL)
   resultis false
   ]
resultis CliLoadFile
]

//-----------------------------------------------------------------------------------------
and CliLoadFile(remotePL, localPL) = valof
//-----------------------------------------------------------------------------------------
[
let more = LoadFromNet(remotePL, localPL)
CloseLocalFile()
unless more resultis true
FreePointer(lv remotePL>>PL.SFIL)
remotePL>>PL.SFIL = ExtractSubstring(remotePL>>PL.NAMB)
let found = not selective
if selective then
   [
   let name = nameVec!0
   while name ne 0 do
      [
      let namb = remotePL>>PL.NAMB
      if namb>>String.char↑(namb>>String.length) eq $. then
         namb>>String.length = namb>>String.length -1
      if StringCompare(name+1, namb) eq 0 then [ found = true; break ]
      name = name!0
      ]
   ]
test found
   ifso CliOpenLocalFile(remotePL)
   ifnot PutTemplate(userDsp, "*N$S", remotePL>>PL.NAMB)
] repeat

//-----------------------------------------------------------------------------------------
and CliOpenLocalFile(pl) = valof
//-----------------------------------------------------------------------------------------
// Handles the all, overwrite, update, dates, and verify flags.
// Returns true with CtxRunning>>FtpCtx.diskStream open for writing,
//  or zero if the file should be skipped.
[
PutTemplate(userDsp, "*N$S", pl>>PL.SFIL)
if dates then PutTemplate(userDsp, " [$P]", WritePTV, lv pl>>PL.CDAT)
PutTemplate(userDsp, " to local file $S", pl>>PL.NAMB)

let hintFP = vec lFP; Zero(hintFP, lFP)
CtxRunning>>FtpCtx.diskStream = OpenFile(pl>>PL.NAMB,
 ksTypeReadOnly, charItem, verLatest, hintFP, 0, 0, 0, ftpDisk)
let ld = CtxRunning>>FtpCtx.buffer

let ok = true
test CtxRunning>>FtpCtx.diskStream eq 0
   ifso
      [
      Wss(userDsp, " [New file]")
      if update then ok = all
      ]
   ifnot
      [
      if dates % update then
         ReadLeaderPage(CtxRunning>>FtpCtx.diskStream, ld)
      if update then
         ok = (((table [ dLS; dEQ; dGR ]+1)!DblUsc(lv pl>>PL.CDAT, lv ld>>LD.created)) & update) ne 0
      test dates
         ifso PutTemplate(userDsp, " [$P]", WritePTV, lv ld>>LD.created)
         ifnot Wss(userDsp, " [Old file]")
      unless overwrite do ok = false
      CloseLocalFile()
      ]

if ok & verify then ok = CliConfirm()
if ok then
   [
   CtxRunning>>FtpCtx.diskStream = OpenFile(pl>>PL.NAMB,
    ksTypeWriteOnly, charItem, 0, hintFP, 0, 0, 0, ftpDisk)
   if CtxRunning>>FtpCtx.diskStream eq 0 resultis CliError(" - Open Failed")
   if pl>>PL.CDAT.h ne 0 then  // remove when Juniper implements dates
      [
      ReadLeaderPage(CtxRunning>>FtpCtx.diskStream, ld)
      MoveBlock(lv ld>>LD.created, lv pl>>PL.CDAT, 2)
      WriteLeaderPage(CtxRunning>>FtpCtx.diskStream, ld)
      ]
   ]

resultis ok
]