// FtpUserUtil.bcpl -- routines common to FtpKbd* and FtpCli*
// Copyright Xerox Corporation 1979, 1980, 1981, 1982
// Last modifed October 11, 1982  1:01 AM by Boggs

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

external
[
// outgoing procedures
FtpUserFinishProc; LogPuts
ProcessNoCode; GetNamePassword
OpenUserConn; UserKey
ListPrint; ListPuts
FillPLFromLD

// incoming procedures
FalsePredicate; MoveBlock
Resets; Closes; Puts
OtherPup; FreePointer
SetTimer; TimerHasExpired; Block
OpenLevel1Socket; CloseLevel1Socket
OpenRTPSocket; CloseRTPSocket
CreateBSPStream; CloseBSPSocket; UserOpen
InitCmd; DefaultPhrase; TerminatingChar; GetString; GetPhrase
ExtractSubstring; StringCompare
PutTemplate; Wss; CliError
UNPACKDT; CONVUDT
InitPList; FileType
ReadLeaderPage; LnPageSize

// outgoing statics
listST

// incoming statics
CtxRunning; defaultPL; cliFlag; lvUserFinishProc
userSoc; userCtx; userDsp; userKeys; userUFP; userDspLen; logStream
]

manifest
[
fcOK = 0
kbdAd = 177034b
]

static listST

//-----------------------------------------------------------------------------------------
let FtpUserFinishProc(fc) be
//-----------------------------------------------------------------------------------------
[
if userCtx>>FtpCtx.connFlag then
   [
   Wss(userDsp, "*NClosing connections...*N")
   CtxRunning>>FtpCtx.connFlag = false
   CloseBSPSocket(userSoc, (fc eq fcOK? 3000, 0))
   ]
@lvUserFinishProc = userUFP
]

//-----------------------------------------------------------------------------------------
and LogPuts(st, char) be
//-----------------------------------------------------------------------------------------
[
Puts(userDspLen, char)
if logStream ne 0 then Puts(logStream, char)
]

//-----------------------------------------------------------------------------------------
and ProcessNoCode(noCode, pList) = valof
//-----------------------------------------------------------------------------------------
[
Resets(userKeys)
switchon noCode into
   [
   case 20b: case 21b: case 2:  // user params
      [
      unless GetNamePassword("*NLogin user ",
       lv defaultPL>>PL.UNAM, lv defaultPL>>PL.UPSW) resultis false
      ResetProp(lv pList>>PL.UNAM, defaultPL>>PL.UNAM)
      ResetProp(lv pList>>PL.UPSW, defaultPL>>PL.UPSW)
      endcase
      ]
   case 23b: case 24b:  // connect params
      [
      unless GetNamePassword("*NConnect to directory ",
       lv defaultPL>>PL.CNAM, lv defaultPL>>PL.CPSW) resultis false
      ResetProp(lv pList>>PL.CNAM, defaultPL>>PL.CNAM)
      ResetProp(lv pList>>PL.CPSW, defaultPL>>PL.CPSW)
      endcase
      ]
   default: [ if cliFlag then CliError(); resultis false ]
   ]
Wss(userDsp, " Retrying...")
resultis true
]

//-----------------------------------------------------------------------------------------
and ResetProp(lvFPL, def) be
//-----------------------------------------------------------------------------------------
[
if def ne 0 then
   if StringCompare(def, @lvFPL) ne 0 then
      [
      FreePointer(lvFPL)
      @lvFPL = ExtractSubstring(def)
      ]
]

//-----------------------------------------------------------------------------------------
and GetNamePassword(prompt, lvName, lvPassword) = valof
//-----------------------------------------------------------------------------------------
[
let cs = InitCmd(256, 3, 0, 0, 0, userKeys, userDsp)
if cs eq 0 resultis false
if lvName ne 0 then
   [
   if prompt ne 0 then Wss(cs, prompt)
   if @lvName ne 0 then DefaultPhrase(cs, @lvName)
   let name = GetString(cs)
   FreePointer(lvName); @lvName = name
   if TerminatingChar(cs) eq $*N then lvPassword = 0
   ]
if lvPassword ne 0 then
   [
   FreePointer(lvPassword)
   Wss(cs, " Password ")
   GetPhrase(cs, 0, 0, FalsePredicate)
   Resets(cs)
   @lvPassword = GetString(cs)
   ]
Closes(cs)
resultis true
]

//-----------------------------------------------------------------------------------------
and OpenUserConn(port) = valof
//-----------------------------------------------------------------------------------------
[
if (port>>Port.socket↑1 eq 0 & port>>Port.socket↑2 eq 0) %
 port>>Port.host eq 0 then resultis false
OpenLevel1Socket(userSoc, 0, port)
OpenRTPSocket(userSoc, 0, modeInitAndReturn, 0, OtherPup)
let timer = nil; SetTimer(lv timer, 6000)
Block() repeatwhile userSoc>>RTPSoc.state eq stateRFCOut &
 not UserKey() & not TimerHasExpired(lv timer)
unless userSoc>>RTPSoc.state eq stateOpen do
   [
   Wss(userDsp, "*NConnection attempt failed")
   CloseRTPSocket(userSoc, 0)
   CloseLevel1Socket(userSoc)
   resultis false
   ]
CtxRunning>>FtpCtx.bspStream = CreateBSPStream(userSoc)
let Version(stream) be Wss(stream, "BCPL Pup FTP User, 14 May 82")
resultis UserOpen(Version)
]

//-----------------------------------------------------------------------------------------
and UserKey() = (kbdAd!1 & 2) eq 0
//-----------------------------------------------------------------------------------------

//-----------------------------------------------------------------------------------------
and ListPrint(remotePL, localPL) be
//-----------------------------------------------------------------------------------------
[
let options = listST>>ST.par1
if listST>>ST.par3 then  //print a title
   [
   listST>>ST.par3 = false
   unless options eq 1b15 rshift offset DPRP.SFIL do Puts(userDsp, $*N)
   listST>>ST.par2 = 0; NextField(wName)
   if (options & lbType) ne 0 then
      [ NextField(wType); Wss(listST, "Type") ]
   if (options & lbLength) ne 0 then
      [ NextField(wLength); Wss(listST, "Length") ]
   if (options & lbCreate) ne 0 then
      [ NextField(wDate); Wss(listST, "Create") ]
   if (options & lbWrite) ne 0 then
      [ NextField(wDate); Wss(listST, "Write") ]
   if (options & lbRead) ne 0 then
      [ NextField(wDate); Wss(listST, "Read") ]
   if (options & lbAuthor) ne 0 then
      [ NextField(wAuthor); Wss(listST, "Author") ]
   ]

listST>>ST.par2 = 0; Puts(userDsp, $*N); NextField(wName)
Wss(listST, (remotePL>>PL.SFIL? remotePL>>PL.SFIL, remotePL>>PL.NAMB))

if (options & lbType) ne 0 then
   [
   NextField(wType)
   switchon remotePL>>PL.TYPE into
      [
      case Text:
         [ Wss(listST, "Text"); endcase ]
      case Binary:
         [ PutTemplate(listST, "B($UD)", remotePL>>PL.BYTE); endcase ]
      default:
         [ Wss(listST, "  ?"); endcase ]
      ]
   ]

if (options & lbLength) ne 0 then
   [
   NextField(wLength)
   PutTemplate(listST, "$EUD", lv remotePL>>PL.SIZE)
   ]

if (options & lbCreate) ne 0 then
   [ NextField(wDate); PrintDate(lv remotePL>>PL.CDAT) ]
if (options & lbWrite) ne 0 then
   [ NextField(wDate); PrintDate(lv remotePL>>PL.WDAT) ]
if (options & lbRead) ne 0 then
   [ NextField(wDate); PrintDate(lv remotePL>>PL.RDAT) ]
if (options & lbAuthor) ne 0 then
   [
   NextField(wAuthor)
   test remotePL>>PL.AUTH ne 0
      ifso Wss(listST, remotePL>>PL.AUTH)
      ifnot Wss(listST, "  ---")
   ]
]

//-----------------------------------------------------------------------------------------
and ListPuts(st, char) be
//-----------------------------------------------------------------------------------------
[
Puts(userDsp, char)
st>>ST.par2 = st>>ST.par2 -1
]

//-----------------------------------------------------------------------------------------
and NextField(width) be
//-----------------------------------------------------------------------------------------
[
test listST>>ST.par2 gr 0
   ifso until listST>>ST.par2 eq 0 do Puts(listST, $*S)
   ifnot if listST>>ST.par2 le 0 then
      [
      width = width + listST>>ST.par2  // par2 is negative
      Wss(listST, "  ")
      ]
listST>>ST.par2 = width
]

//-----------------------------------------------------------------------------------------
and PrintDate(dt) be
//-----------------------------------------------------------------------------------------
[
if dt!0 eq 0 then [ Wss(listST, "   ---"); return ]
let uv = vec 7; UNPACKDT(dt, uv)
let date = vec 12; CONVUDT(date, uv)
Wss(listST, date)
]

//-----------------------------------------------------------------------------------------
and FillPLFromLD() = valof
//-----------------------------------------------------------------------------------------
[
let pl = InitPList(defaultPL)

let fileType = FileType()
if defaultPL>>PL.TYPE eq Text & fileType eq Binary then
   [
   Wss(userDsp, "*NFile is Binary, but you have made the default Text")
   Wss(userDsp, "*NThis operation will lose information")
   ]
if pl>>PL.TYPE eq 0 then pl>>PL.TYPE = fileType
test pl>>PL.TYPE eq Binary
   ifso if pl>>PL.BYTE eq 0 then pl>>PL.BYTE = 8
   ifnot if pl>>PL.EOLC eq 0 then pl>>PL.EOLC = CR

let ld = CtxRunning>>FtpCtx.buffer
ReadLeaderPage(CtxRunning>>FtpCtx.diskStream, ld)

MoveBlock(lv pl>>PL.CDAT, lv ld>>LD.created, 2)
MoveBlock(lv pl>>PL.RDAT, lv ld>>LD.read, 2)
MoveBlock(lv pl>>PL.WDAT, lv ld>>LD.written, 2)

let siz = lv pl>>PL.SIZE
let fa = lv ld>>LD.hintLastPageFa
let lnBytes = LnPageSize(CtxRunning>>FtpCtx.diskStream) +1
let numPages = fa>>FA.pageNumber -1  //don't count leader page
unless numPages eq -1 do
   [
   siz!0 = numPages rshift (16-lnBytes)
   siz!1 = numPages lshift lnBytes + fa>>FA.charPos
   ]

resultis pl
]