// IfsTelnetFileUtil.bcpl -- Utilities for collecting and enumerating files
// Copyright Xerox Corporation 1979, 1981, 1982

// Last modified May 15, 1982  10:31 AM by Taft

get "Ifs.decl"
get "IfsFiles.decl"
get "IfsDirs.decl"
get "CmdScan.decl"

external
[
// outgoing procedures
CollectFilenames; NextFile; DestroyFGD; Subcommands; SetFGDLC
WhatChanged; CopyFD; PrintSubFilename

// incoming procedures
LookupIFSFile; DestroyFD; LookupFD; NextFD
GetString; TerminatingChar; CmdError
EnableCatch; EndCatch
ExtractSubstring
DefaultArgs; SysAllocate; SysAllocateZero; SysFree; FreePointer
Zero; MoveBlock
Puts; Errors; Wss

// incoming statics
primaryIFS
]

manifest maxNames = 10

//---------------------------------------------------------------------------
structure FGD:  // File Group Designator
//---------------------------------------------------------------------------
[
nNames word		// number of names
name↑1,maxNames+1 word	// -> name strings
			// (one extra to simplify CollectFilenames loop)
subcommands word	// true iff subcommand mode requested
lc word			// lookup control
iName word		// index of current name
fd word			// -> FD for enumeration
fs word			// -> IFS
]
manifest lenFGD = size FGD/16

//---------------------------------------------------------------------------
let CollectFilenames(cs, lc, prompt, defStar, fs; numargs na) = valof
//---------------------------------------------------------------------------
// Collects a group of filenames and returns a file group designator (fgd).
// lc is the lookup control for each name.  If the last name ends with
// comma return, the comma is stripped off and the subcommand word in fgd
// is set to true.  If defStar is false, an empty input line causes an
// error;  if true, an empty line silently defaults to "*".
// fs designates the file system; it defaults to primaryIFS.
[
DefaultArgs(lv na, -1, lcVHighest+lcMultiple, "file name(s)", false,
 primaryIFS)
let fgd = nil
if EnableCatch(cs) then [ DestroyFGD(fgd); EndCatch(cs) ]
fgd = SysAllocateZero(lenFGD)
fgd>>FGD.lc = lc
fgd>>FGD.fs = fs

   [  // loop to collect names
   let iName = fgd>>FGD.nNames+1
   let warn = cs>>CS.iPhOut eq cs>>CS.maxPhrases-1 % iName gr maxNames
   if warn then CmdError(cs, "[file list full]")  // warning, no abort yet
   let name = GetString(cs, 0, Wss, prompt)

   if TerminatingChar(cs) eq $*n then
      [
      let length = name>>String.length
      if name>>String.char↑length eq $, then
         [
         length = length-1
         name>>String.length = length
         fgd>>FGD.subcommands = true
         ]
      if length eq 0 then [ SysFree(name); break ]
      ]

   fgd>>FGD.name↑iName = name  // if error, will now be freed by DestroyFGD
   if warn then Errors(cs, 0)  // too many names
   let fd = LookupIFSFile(name, lc, 0, fs)
   if fd eq 0 then Errors(cs, 0)
   test iName eq 1
      ifso fgd>>FGD.fd = fd  // retain the first FD
      ifnot DestroyFD(fd)
   fgd>>FGD.nNames = iName
   Puts(cs, $*s)
   ] repeatuntil TerminatingChar(cs) eq $*n

if fgd>>FGD.nNames eq 0 then
   test defStar
      ifnot Errors(cs, 0)
      ifso
         [
         fgd>>FGD.nNames = 1
         fgd>>FGD.name↑1 = ExtractSubstring("**")
         ]
resultis fgd
]

//---------------------------------------------------------------------------
and NextFile(fgd) = valof
//---------------------------------------------------------------------------
// Returns an FD for the next file designated by the file group designator
// fgd, or zero if fgd is exhausted.  Expects to be called with the directory
// unlocked, and returns with it unlocked.
[
let fd = fgd>>FGD.fd
if fd ne 0 then
   [
   if fgd>>FGD.iName eq 0 then
      [ // first use of FD retained by CollectFilenames
      fgd>>FGD.iName = 1
      if LookupFD(fd) eq 0 resultis fd
      // specific file no longer exists, but others may
      ]
   if NextFD(fd) resultis fd
   fgd>>FGD.fd = DestroyFD(fd)
   ]
fgd>>FGD.iName = fgd>>FGD.iName+1
if fgd>>FGD.iName gr fgd>>FGD.nNames resultis 0
fd = LookupIFSFile(fgd>>FGD.name↑(fgd>>FGD.iName), fgd>>FGD.lc, 0,
 fgd>>FGD.fs)
fgd>>FGD.fd = fd
if fd ne 0 resultis fd
] repeat

//---------------------------------------------------------------------------
and DestroyFGD(fgd) be
//---------------------------------------------------------------------------
[
if fgd>>FGD.fd ne 0 then DestroyFD(fgd>>FGD.fd)
for i = 1 to maxNames+1 do FreePointer(lv fgd>>FGD.name↑i)
SysFree(fgd)
]

//---------------------------------------------------------------------------
and Subcommands(fgd) = fgd>>FGD.subcommands
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
and SetFGDLC(fgd, lc) be
//---------------------------------------------------------------------------
[
fgd>>FGD.lc = lc
// discard any retained FD, since its LC may no longer be valid.
if fgd>>FGD.fd ne 0 then fgd>>FGD.fd = DestroyFD(fgd>>FGD.fd)
]

//---------------------------------------------------------------------------
and WhatChanged(fd, lastFD) = valof
//---------------------------------------------------------------------------
// Compares the filename designated by fd against lastFD
// and updates lastFD.  Returns a code describing how much of the
// filename changed:
//  0 directory (or subdirectory) changed
//  1 name body changed
//  2 version changed
// Intended use: caller should start by allocating and zeroing a block of
// size lenFD.  During an enumeration of the (real) fd, this block should
// be passed as the lastFD argument to WhatChanged.  At the end of the
// enumeration, caller must DestroyFD(lastFD).
[
let i = 1
while i le fd>>FD.lenBodyString do
   [
   let c1 = fd>>FD.dr>>DR.pathName.char↑i
   let c2 = lastFD>>FD.dr>>DR.pathName.char↑i
   if c1 ne c2 then
      [
      c1 = c1 & #137; c2 = c2 & #137
      unless c1 eq c2 & c1 ge $A & c1 le $Z break
      ]
   i = i+1
   ]
let res = fd>>FD.lenSubDirString ne lastFD>>FD.lenSubDirString %
 i le fd>>FD.lenSubDirString? 0,
 fd>>FD.lenBodyString ne lastFD>>FD.lenBodyString %
 i le fd>>FD.lenBodyString? 1, 2
CopyFD(lastFD, fd)
resultis res
]

//---------------------------------------------------------------------------
and CopyFD(dFD, sFD) be
//---------------------------------------------------------------------------
// Replaces dFD with a copy of sFD.  Precisely:
// 1. Copies all of the sFD structure itself, with the exception of pointers
//    to other objects (dr, pathStk, and template).
// 2. Deallocates dFD's DR if there is one, and then makes a copy of sFD's DR
//    and installs it in dFD.
// 3. Unconditionally deallocates dFD's pathStk and template.
// This is a rather specialized operation used principally by WhatChanged.
[
FreePointer(lv dFD>>FD.dr, lv dFD>>FD.pathStk, lv dFD>>FD.template)
MoveBlock(dFD, sFD, lenFD)
dFD>>FD.pathStk = 0
dFD>>FD.template = 0
dFD>>FD.dr = SysAllocate(sFD>>FD.dr>>DR.length)
MoveBlock(dFD>>FD.dr, sFD>>FD.dr, sFD>>FD.dr>>DR.length)
]

//---------------------------------------------------------------------------
and PrintSubFilename(str, fd, first, last; numargs na) be
//---------------------------------------------------------------------------
// Prints the 'first' through 'last' characters of the filename
// designated by FD.
[
DefaultArgs(lv na, -2, 1, fd>>FD.dr>>DR.pathName.length)
for i = first to last do Puts(str, fd>>FD.dr>>DR.pathName.char↑i)
]