-- 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: BOOL ← FALSE;
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: 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];
}.