-- DeleteAllImpl.Mesa, last edit February 6, 1983 9:17 pm
-- Pilot 6.0/ Mesa 7.0

-- Switch Meaning
-- /a  do NOT confirm the deletes (and CheckForOverwrite is FALSE)
-- /n  do NOT use BasicCoPilotVolumeFiles.DF (or BasicClientVolumeFiles.DF)

    
DIRECTORY
CWF: TYPE USING [SetWriteProcedure, SWF1, WF0, WF1, WF3, WFCR],
DFSubr: TYPE USING [AllocateDFSeq, DF, DFSeq, FlattenDF, FreeDFSeq, LookupDF,
 NextDF, SortByFileName],
Directory: TYPE USING [DeleteFile],
File: TYPE USING [Capability, Unknown],
IO: TYPE USING[Handle, PutChar, PutFR, PutRope, string, UserAbort],
LongString: TYPE USING [EquivalentString],
Rope: TYPE USING[ROPE, Text],
RopeInline: TYPE USING[InlineFlatten],
UnsafeSTP: TYPE USING [Error, Handle],
STPSubr: TYPE USING [GeneralOpen, StopSTP],
Stream: TYPE USING [Delete, EndOfStream, Handle],
Subr: TYPE USING [AbortMyself, CheckForModify, CopyString, debugflg,
 EndsIn, EnumerateDirectory,
 errorflg, FileError, GetID, MakeTTYProcs, PackedTime, PrintGreeting,
 strcpy, SubrStop, SubStrCopy, TTYProcs],
Time: TYPE USING [Current],
UECP: TYPE USING[Argv, Parse],
UserExec: TYPE USING[AcquireResource, AskUser, CommandProc, ExecHandle, GetStreams,
 RegisterCommand, ReleaseResource],
Volume: TYPE USING [GetType, systemID, Type];

DeleteAllImpl: PROGRAM
IMPORTS CWF, DFSubr, Directory, File, IO, LongString,
 Rope, RopeInline, STP: UnsafeSTP, STPSubr, Stream, Subr, Time, UECP,
 UserExec, Volume = {

-- MDS USAGE !!!
useremotedeletelist: BOOL;
dontconfirm: BOOL;
argv: UECP.Argv;
parm: CARDINAL ← 1;
stdout: IO.Handle;
-- endof MDS usage

-- max number of files on local disk
maxdirsize: CARDINAL = 1100;

-- total # entries from Basic...VolumeFiles.DF
MAXSTANDARDDELETEFILE: CARDINAL = 1200;

-- max number of files in each DF file we want to ignore (listed on command file)
MAXDELETEFILE: CARDINAL = 1200;


Main: UserExec.CommandProc = TRUSTED {
ENABLE UNWIND => [] ← UserExec.ReleaseResource[$DeleteAll];
 h: Subr.TTYProcs;
 in, out: IO.Handle;
 [in, out] ← UserExec.GetStreams[exec];
 [] ← UserExec.AcquireResource[$DeleteAll, "DeleteAll", exec];
 h ← Subr.MakeTTYProcs[in, out, exec, MyConfirm];
 DeleteAllUsingProcs[h, event.commandLine];
 [] ← UserExec.ReleaseResource[$DeleteAll];
 };


DeleteAllUsingProcs: PROC[h: Subr.TTYProcs, commandLine: Rope.ROPE] = {
dfseq: DFSubr.DFSeq;
tok: STRING ← [100];
starttime: Subr.PackedTime;
elapt: LONG CARDINAL;

Cleanup: PROC = {
 DFSubr.FreeDFSeq[@dfseq];
 };

useremotedeletelist ← TRUE;
stdout ← h.out;
[] ← CWF.SetWriteProcedure[MyPutChar];
dontconfirm ← FALSE;
starttime ← Time.Current[];
Subr.errorflg ← Subr.debugflg ← FALSE;
Subr.PrintGreeting["DeleteAll"L];
dfseq ← DFSubr.AllocateDFSeq[maxEntries: maxdirsize, zoneType: shared];
{
ENABLE {
STP.Error => {
  CWF.WF0["FTP Error. "L];
  IF error ~= NIL THEN CWF.WF1["message: %s\n"L, error];
  Subr.errorflg ← TRUE;
  GOTO leave;
  };
 Subr.AbortMyself => {
  CWF.WF0["DeleteAll Aborted.\n"L];
  GOTO leave;
  };
UNWIND => Cleanup[];
 };

IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
argv ← UECP.Parse[commandLine];
DeleteAll[dfseq, h];
EXITS
leave => NULL;
};
STPSubr.StopSTP[];
elapt ← starttime;
elapt ← Time.Current[] - elapt;
CWF.WF1["\nTotal elapsed time for DeleteAll %lr."L,@elapt];
IF Subr.errorflg THEN CWF.WF0["\tErrors logged.\n"L];
CWF.WFCR[];
Cleanup[];
Subr.SubrStop[];
};

DeleteAll: PROC[dfseq: DFSubr.DFSeq, h: Subr.TTYProcs] = {
i, ndel: CARDINAL;
header: BOOLFALSE;
short: STRING ← [100];

-- if look.presentonlocaldisk is true, don't delete it
FillInDFSeq[dfseq];
IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
FOR i IN [0.. dfseq.size) DO
 dfseq[i].presentonlocaldisk ← FALSE;-- assume we will delete file
ENDLOOP;
DFSubr.SortByFileName[dfseq, 0, dfseq.size - 1];
IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
MatchAgainstFile[dfseq, h];
IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
IF useremotedeletelist THEN HandleRemoteDeleteList[dfseq, h];
IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
-- determine which are to be deleted
FOR i IN [0..dfseq.size) DO
-- check against built in list just in case
IF NOT dfseq[i].presentonlocaldisk THEN
  dfseq[i].presentonlocaldisk ←
   MatchesDontDeleteList[dfseq[i].shortname];
ENDLOOP;
IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
-- print out list of those to be left on disk
CWF.WF0["These files will be left on the disk:\n"L];
FOR i IN [0..dfseq.size) DO
IF dfseq[i].presentonlocaldisk THEN
  CWF.WF1["%s "L, dfseq[i].shortname];
ENDLOOP;
-- print out list of those to be deleted
CWF.WF0["\n\nThese files will be deleted:\n\n"L];
FOR i IN [0..dfseq.size) DO
IF NOT dfseq[i].presentonlocaldisk THEN {
  IF NOT header THEN {
   CWF.WF0["Delete.~"L];
   header ← TRUE;
   };
  -- these will be deleted
  CWF.WF1[" %s"L, dfseq[i].shortname];
  };
ENDLOOP;
IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
IF NOT dontconfirm THEN
CWF.WF0["\n\nType 'y' or CR to delete, 'n' to skip, and 'q' to quit.\n"L];
ndel ← 0;
FOR i IN [0..dfseq.size) DO
IF NOT dfseq[i].presentonlocaldisk THEN {
  r: Rope.ROPE;
  IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
  r ← IO.PutFR["Delete %s ", IO.string[dfseq[i].shortname]];
  IF NOT dontconfirm THEN {
   ch: CHAR;
   ch ← h.Confirm[h.in, h.out, h.data, r, 'y];
   IF ch = 'q THEN {
    EXIT;
    };
   IF ch ~= 'y THEN {
    LOOP;
    };
   }
  ELSE h.out.PutRope[r];
  CWF.WF0["Yes ... "L];
  -- deletes the file
  Subr.strcpy[short, dfseq[i].shortname];
  IF NOT Subr.CheckForModify[short, h] THEN {
   CWF.WF0[" ... can't be modified.\n"L];
   LOOP;
   };
  Directory.DeleteFile[dfseq[i].shortname
    ! File.Unknown => { -- bugs in directory package
     CWF.WF0["File/Directory Error.\n"L];
    CONTINUE;
    }
   ];
  CWF.WF0["Done.\n"L];
  ndel ← ndel + 1;
  };
ENDLOOP;
CWF.WF1["%u files deleted.\n"L, @ndel];
};

-- returns TRUE if the file should not be deleted
-- these files are NOT deleted by DeleteAll
MatchesDontDeleteList: PROC[p: LONG STRING] RETURNS[bool: BOOL] = {
OPEN LongString;
bool ← FALSE;
IF EquivalentString[p, "Binder.Log"L]
OR EquivalentString[p, "Compiler.Log"L]
OR EquivalentString[p, "Debug.Log"L]
OR EquivalentString[p, "Debuggee.outload"L]
OR EquivalentString[p, "Debugger.outload"L]
OR EquivalentString[p, "DeleteAll.Bcd"L]
OR EquivalentString[p, "Executive.Bcd"L]
OR EquivalentString[p, "Executive.DontDeleteMe"L]
OR EquivalentString[p, "FileTool.logD"L]
OR EquivalentString[p, "SimpleExec"L]
OR EquivalentString[p, "SimpleExec.Log"L]
OR EquivalentString[p, "user.cm"L]
THEN bool ← TRUE;
};

HandleRemoteDeleteList: PROC[dfdirseq: DFSubr.DFSeq, h: Subr.TTYProcs] ={
sfn: STRING ← [100];
type: Volume.Type;
dfseq: DFSubr.DFSeq ← NIL;

{
ENABLE UNWIND => DFSubr.FreeDFSeq[@dfseq];
dfseq ← DFSubr.AllocateDFSeq[maxEntries: MAXSTANDARDDELETEFILE, zoneType: shared];
type ← Volume.GetType[Volume.systemID];
CWF.SWF1[sfn, "[Indigo]<CedarLib>DFFiles>Basic%sVolumeFiles.DF"L,
IF type = normal THEN "Client"L ELSE "CoPilot"L];
CWF.WF1["Using delete list from '%s'\n"L, sfn];
DFSubr.FlattenDF[dfseq: dfseq, dffilename: sfn, checkForOverwrite: NOT dontconfirm,
 h: h]; 
DontDeleteTheseDFFiles[dfdirseq, dfseq, h];
DFSubr.FreeDFSeq[@dfseq];
CWF.WFCR[];
}};

MatchAgainstFile: PROC[dfdirseq: DFSubr.DFSeq, h: Subr.TTYProcs] = {
tok: STRING ← [100];
flat: Rope.Text;

WHILE parm < argv.argc DO
 flat ← RopeInline.InlineFlatten[argv[parm]];
 Subr.strcpy[tok, LOOPHOLE[flat]];
IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
IF tok[0] = '@ THEN { -- is an escaped at-sign
  sh: Stream.Handle;
  Subr.SubStrCopy[tok, tok, 1];
  [sh] ← STPSubr.GeneralOpen[filename: tok, h: h
   ! Subr.FileError => {
    CWF.WF1["Error - Can't open '%s'\n"L, tok];
    GOTO out
    }
   ];
  IF Subr.EndsIn[tok, ".df"L] THEN {
   dfseq: DFSubr.DFSeq ← NIL;
   {
   ENABLE UNWIND => DFSubr.FreeDFSeq[@dfseq];
   dfseq ← DFSubr.AllocateDFSeq[maxEntries: MAXDELETEFILE,
    zoneType: shared];
   DFSubr.FlattenDF[dfseq: dfseq, dffilename: tok,
    checkForOverwrite: NOT dontconfirm, h: h];
   DontDeleteTheseDFFiles[dfdirseq, dfseq, h];
   DFSubr.FreeDFSeq[@dfseq];
   }}
  ELSE  DontDeleteTheseFiles[sh, dfdirseq];
  Stream.Delete[sh];
  EXITS
  out => NULL;
  }
ELSE IF tok[0] = '- OR tok[0] = '/ THEN { -- is an option
  SELECT tok[1] FROM
  'a, 'A => dontconfirm ← TRUE;
  'n, 'N => useremotedeletelist ← FALSE;
  ENDCASE =>CWF.WF1["Error - malformed argument '%s'.\n"L, tok];
  }
ELSE {
  df: DFSubr.DF;
  df ← DFSubr.LookupDF[dfdirseq, tok];
  IF df = NIL THEN
   CWF.WF1["File %s is not on local disk.\n"L, tok]
  ELSE-- dont delete it
   df.presentonlocaldisk ← TRUE;
  };
ENDLOOP;
};

DontDeleteTheseFiles: PROC[sh: Stream.Handle, dfseq: DFSubr.DFSeq] = {
df: DFSubr.DF;
str: STRING ← [100];
DO
 str.length ← 0;
 Subr.GetID[sh, str
  ! Stream.EndOfStream => {
   str.length ← 0;
   EXIT;
   }
  ];
IF str.length = 0 THEN LOOP;
 df ← DFSubr.LookupDF[dfseq, str];
IF df = NIL THEN
  CWF.WF1["File %s is not on local disk.\n"L, str]
ELSE-- dont delete it
  df.presentonlocaldisk ← TRUE;
ENDLOOP;
};

-- dfdirseq is for the local directory
-- dfseq is the exception list
-- dfseq must already be filled
DontDeleteTheseDFFiles: PROC[dfdirseq, dfseq: DFSubr.DFSeq, h: Subr.TTYProcs] = {
df, dfdir: DFSubr.DF;
host: LONG STRING;

FOR i: CARDINAL IN [0.. dfseq.size) DO
IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
 df ← @dfseq[i];
IF df.host ~= NIL THEN host ← df.host;
 dfdir ← DFSubr.LookupDF[dfdirseq, df.shortname];
IF dfdir = NIL THEN {
  IF df.directory ~= NIL THEN
   CWF.WF3["File [%s]<%s>%s not on local disk.\n"L, host,
    df.directory, df.shortname]
  ELSE CWF.WF1["File %s not on local disk.\n"L, df.shortname];
  }
ELSE-- dont delete it
  dfdir.presentonlocaldisk ← TRUE;
ENDLOOP;
};

FillInDFSeq: PROC[dfseq: DFSubr.DFSeq] = {

AddDir: PROC[cap: File.Capability, name: LONG STRING] RETURNS[BOOL]={
 df: DFSubr.DF;
 df ← DFSubr.NextDF[dfseq];
IF df = NIL THEN {
  CWF.WF0["Too many files on local disk.\n"L];
  RETURN[TRUE];
  };
 df.shortname ← Subr.CopyString[name, dfseq.dfzone];
 df.presentonlocaldisk ← TRUE;
 df.cap ← cap;
RETURN[FALSE];
 };

Subr.EnumerateDirectory[AddDir];
CWF.WF1["%u files on the local disk.\n"L, @dfseq.size];
};

MyConfirm: PROC[in, out: IO.Handle, data: REF ANY, msg: Rope.ROPE, dch: CHAR]
RETURNS[CHAR] = {
 value: ATOM;
 value ← UserExec.AskUser[msg: msg, exec: NARROW[data],
  keyList: LIST[$Yes, $No, $All, $Local, $Quit]]; -- order is important
SELECT value FROM
 $All => RETURN['a];
 $Local => RETURN['l];
 $No => RETURN['n];
 $Quit => RETURN['q];
 $Yes => RETURN['y];
ENDCASE => ERROR;
 };

MyPutChar: PROC[ch: CHAR] = {
 stdout.PutChar[ch];
 };
 UserExec.RegisterCommand["DeleteAll.~", Main];
}.