-- RemoteDeleteAllImpl.Mesa, last edit February 9, 1983 6:13 pm
-- Pilot 6.0/ Mesa 7.0
-- Usage: RemoteDeleteAll directory list of dfs (nesting allowed)
DIRECTORY
BTreeDefs: TYPE USING [BTreeHandle, Call, CreateAndInitializeBTree, Desc,
EnumerateFrom, Insert, ReleaseBTree, TestKeys],
BTreeSupportExtraDefs: TYPE USING [CloseFile, OpenFile],
CWF: TYPE USING [SetWriteProcedure, SWF1, SWF2, SWF4],
DateAndTimeUnsafe: TYPE USING [Parse, Unintelligible],
DFSubr: TYPE USING [AllocateDFSeq, DFSeq, FlattenDF, FreeDFSeq, StripLongName],
File: TYPE USING [Capability, Create, Delete, nullCapability],
FileIO: TYPE USING[Open],
Inline: TYPE USING [BITOR, BITXOR, BytePair, LowHalf],
IO: TYPE USING[card, Close, CreateDribbleStream, GetChar, Handle,
PutChar, PutF, PutFR, PutRope, string, UserAbort],
LongString: TYPE USING [AppendString, EqualString, EquivalentString, StringToDecimal],
Process: TYPE USING [Detach],
Rope: TYPE USING[ROPE, Text],
RopeInline: TYPE USING[InlineFlatten],
UnsafeSTP: TYPE USING [CompletionProcType, Delete, DesiredProperties,
Enumerate, Error, FileInfo, GetFileInfo,
Handle, NoteFileProcType, SetDesiredProperties],
STPSubr: TYPE USING [Connect, HandleSTPError, StopSTP],
String: TYPE USING [AppendChar, AppendString, LowerCase],
Subr: TYPE USING [AbortMyself, Any, CopyString, debugflg, EndsIn, errorflg,
FreeHugeZone, HugeZone, MakeTTYProcs, PagesUsedInHugeZone, PrintGreeting,
strcpy, SubrInit, SubrStop, SubStrCopy, TTYProcs],
Time: TYPE USING [Current],
TypeScript: TYPE USING[TS, Create],
UECP: TYPE USING[Argv, Parse],
UserExec: TYPE USING[AcquireResource, AskUser, CommandProc, RegisterCommand,
ReleaseResource, UserAbort],
ViewerEvents: TYPE USING[EventProc, EventRegistration, RegisterEventProc,
UnRegisterEventProc],
ViewerIO: TYPE USING[CreateViewerStreams],
Volume: TYPE USING [systemID];
RemoteDeleteAllImpl: PROGRAM
IMPORTS BTreeDefs, BTreeSupportExtraDefs, CWF, DateAndTimeUnsafe,
DFSubr, File, FileIO, Inline, IO, LongString,
Process, RopeInline, STP: UnsafeSTP, STPSubr, String, Subr, Time, TypeScript, UECP,
UserExec, ViewerEvents, ViewerIO, Volume = {
-- max number of files in all DF file we want to ignore
MAXDELETEFILE: CARDINAL = 9000; -- should be 12000
-- MDS usage!
globalhost: STRING ← [30];
globalremotepattern: STRING ← [100];
typeScript: TypeScript.TS;
in, out, logFile: IO.Handle;
destroyEventRegistration: ViewerEvents.EventRegistration;
-- endof MDS
possibleDelete: BOOL = TRUE;
initialBackingPages: CARDINAL = 10;
-- this is the procedure called by the Simple Executive
Main: UserExec.CommandProc = TRUSTED {
ENABLE UNWIND => [] ← UserExec.ReleaseResource[$RemoteDeleteAll];
p: PROCESS;
dfseq: DFSubr.DFSeq ← NIL;
flat: Rope.Text;
tok: STRING ← [100];
time, starttime: LONG CARDINAL;
remnam: STRING ← [100];
stemp: STRING ← [100];
last: STRING ← [100];
host: STRING ← [100];
remotepattern: STRING ← [100];
npages: CARDINAL;
parm: CARDINAL;
argv: UECP.Argv ← UECP.Parse[event.commandLine];
wh: Subr.TTYProcs;
Cleanup: PROC = {
DFSubr.FreeDFSeq[@dfseq];
STPSubr.StopSTP[];
Subr.SubrStop[];
};
[] ← UserExec.AcquireResource[$RemoteDeleteAll, "RemoteDeleteAll", exec];
starttime ← Time.Current[];
wh ← Subr.MakeTTYProcs[in, out, typeScript, MyConfirm];
Subr.errorflg ← Subr.debugflg ← FALSE;
Subr.PrintGreeting["RemoteDeleteAll"L];
{
ENABLE {
STP.Error => {
lcode: LONG CARDINAL ← LOOPHOLE[code, CARDINAL];
out.PutF["FTP Error. "];
IF error ~= NIL THEN
out.PutF["message: %s, code %d in Stp.Mesa\n", IO.string[error],
IO.card[lcode]];
Subr.errorflg ← TRUE;
GOTO leave;
};
Subr.AbortMyself => {
out.PutF["RemoteDeleteAll Aborted.\n"];
GOTO leave;
};
UNWIND => Cleanup[];
};
IF argv.argc = 1 THEN GOTO usage;
Subr.SubrInit[256];
dfseq ← DFSubr.AllocateDFSeq[maxEntries: MAXDELETEFILE, zoneType: huge];
IF UserExec.UserAbort[exec] THEN SIGNAL Subr.AbortMyself;
flat ← RopeInline.InlineFlatten[argv[1]];
Subr.strcpy[remnam, LOOPHOLE[flat]];
time ← Time.Current[];
parm ← 2;
WHILE parm < argv.argc DO
flat ← RopeInline.InlineFlatten[argv[parm]];
Subr.strcpy[tok, LOOPHOLE[flat]];
IF NOT Subr.Any[tok, '.] THEN
LongString.AppendString[tok, ".DF"L];
out.PutF["\nReading %s:\n", IO.string[tok]];
DFSubr.FlattenDF[dfseq: dfseq, dffilename: tok, h: wh,
checkForOverwrite: FALSE, printStatus: TRUE];
parm ← parm + 1;
ENDLOOP;
time ← Time.Current[] - time;
out.PutF["Time to read in all DF files: %r.\n", IO.card[time]];
[] ← DFSubr.StripLongName[remnam, host, stemp, last];
CWF.SWF2[remotepattern, "<%s>%s"L, stemp, last];
IF NOT Subr.EndsIn[remotepattern, "*"L] THEN
String.AppendChar[remotepattern, '*];
IF NOT Subr.Any[remotepattern, '!] THEN
String.AppendString[remotepattern, "!*"L];
npages ← Subr.PagesUsedInHugeZone[dfseq.dfzone];
out.PutF["%d pages used in Huge Zone, %d entries in flattened DF files.\n",
IO.card[npages], IO.card[dfseq.size]];
Subr.strcpy[globalhost, host];
Subr.strcpy[globalremotepattern, remotepattern];
p ← FORK RunDeleteAll[dfseq, starttime, wh];
Process.Detach[p];
dfseq ← NIL;
EXITS
usage => {
out.PutF["Usage: RemoteDeleteAll directory dffile(s).\n"];
};
leave => NULL;
};
[] ← UserExec.ReleaseResource[$RemoteDeleteAll];
};
IFSBytesPerPage: CARDINAL = 2048;
-- forked as a separate process
RunDeleteAll: PROC[dfseq: DFSubr.DFSeq, starttime: LONG CARDINAL, wh: Subr.TTYProcs] = {
stphandle: STP.Handle ← NIL;
ndelete, nskipdf, nskipno: CARDINAL ← 0;
connuser: STRING ← [40];
connpass: STRING ← [40];
deleteBackingFile: File.Capability ← File.nullCapability;
deleteBTree: BTreeDefs.BTreeHandle; -- no init value allowed
haveDeleteBTree: BOOL ← FALSE;
elapt: LONG CARDINAL ← starttime;
nskippages, ndeletepages: LONG CARDINAL ← 0;
fullspeed: BOOL ← FALSE;
lastDirectory: STRING ← [100];
NameList: TYPE = LONG POINTER TO NameListRecord;
NameListRecord: TYPE = RECORD[
host: LONG STRING,
directory: LONG STRING,
version: CARDINAL,
createtime: LONG CARDINAL,
list: NameList
];
shortnamearray: LONG POINTER TO ShortRec ← NIL;
ShortRec: TYPE = ARRAY [0 .. 8000] OF RECORD[
shortname: LONG STRING,
list: NameList
] ← ALL[[NIL, NIL]];
hugezone: UNCOUNTED ZONE ← Subr.HugeZone[];
indigoHost: STRING ← "Indigo"L;
ivyHost: STRING ← "Ivy"L;
lastD: LONG STRING ← NIL;
nLook, nScan, nInsert: CARDINAL ← 0;
deleteFile, keepFile: IO.Handle;
ComputeHost: PROC[o: LONG STRING] RETURNS[n: LONG STRING] = {
RETURN[IF LongString.EquivalentString[o, indigoHost] THEN indigoHost
ELSE IF LongString.EquivalentString[o, ivyHost] THEN ivyHost
ELSE Subr.CopyString[o, hugezone]];
};
ComputeDirectory: PROC[o: LONG STRING] RETURNS[n: LONG STRING] = {
IF lastD ~= NIL
AND LongString.EquivalentString[o, lastD] THEN n ← lastD
ELSE n ← lastD ← Subr.CopyString[o, hugezone];
};
AddName: PROC[host, directory, shortname: LONG STRING, version: CARDINAL,
createtime: LONG CARDINAL] = {
l: NameList ← NIL;
element: CARDINAL ← HashedLookup[shortname];
IF shortnamearray[element].shortname = NIL THEN {-- not in table
shortnamearray[element] ← [shortname: Subr.CopyString[shortname, hugezone],
list: NIL];
nInsert ← nInsert + 1;
};
l ← hugezone.NEW[NameListRecord];
l↑ ← [host: ComputeHost[host], directory: ComputeDirectory[directory],
version: version, createtime: createtime,
list: shortnamearray[element].list];
shortnamearray[element].list ← l;
};
-- returns element with shortname = NIL if can't find
-- (this will be the first NIL encountered)
-- returns element ~= NIL if found
HashedLookup: PROC[shortname: LONG STRING] RETURNS[element: CARDINAL] = {
hv: CARDINAL ← Hash[shortname, LENGTH[shortnamearray↑]]; -- hv IN [0 .. LENGTH[])
nLook ← nLook + 1;
FOR i: CARDINAL IN [hv .. LENGTH[shortnamearray↑]) DO
nScan ← nScan + 1;
IF shortnamearray[i].shortname = NIL THEN RETURN[i];
IF LongString.EquivalentString[shortnamearray[i].shortname, shortname] THEN
RETURN[i];
ENDLOOP;
FOR i: CARDINAL IN [0 .. hv) DO
nScan ← nScan + 1;
IF shortnamearray[i].shortname = NIL THEN RETURN[i];
IF LongString.EquivalentString[shortnamearray[i].shortname, shortname] THEN
RETURN[i];
ENDLOOP;
ERROR; -- full
};
BuildBTree: PROC = {
nFiles: CARDINAL ← 0;
time: LONG CARDINAL ← Time.Current[];
desiredProperties: STP.DesiredProperties ← ALL[FALSE];
-- NOTE: Desired properties have been set
EnumProc: STP.NoteFileProcType = {
pages: CARDINAL;
info: STP.FileInfo;
key: STRING ← [100];
value: STRING ← [100];
continue ← yes;
info ← STP.GetFileInfo[stphandle];
IF file.length > 2 AND Subr.EndsIn[file, ">!1"L] THEN
RETURN; -- skip these questionable cases
nFiles ← nFiles + 1;
pages ← Inline.LowHalf[info.size/IFSBytesPerPage + 2];
IF Match[globalhost, info]
-- OR LongString.EquivalentString[info.body, "Dir.Dir"L]
THEN {
IF NOT LongString.EquivalentString[lastDirectory, info.directory] THEN {
keepFile.PutChar['\n]; -- extra space
Subr.strcpy[lastDirectory, info.directory];
};
out.PutChar['+];
IF file.length > 60 THEN keepFile.PutF["%s %s %4d\n", IO.string[file],
IO.string[info.create], IO.card[pages]]
ELSE keepFile.PutF["%-60s %s %4d\n", IO.string[file],
IO.string[info.create], IO.card[pages]];
nskippages ← nskippages + pages;
nskipdf ← nskipdf + 1;
}
ELSE {-- insert in really delete bTree
author: STRING;
CWF.SWF1[key, "%07d"L, @nFiles];
-- value has a trailing "M" if file was stored by Morris (now obsolete)
author ← ""L;
CWF.SWF4[value, "%s*000%s*000%d%s"L, file, info.create, @pages, author];
ndeletepages ← ndeletepages + pages;
ndelete ← ndelete + 1;
BTreeDefs.Insert[deleteBTree, MakeBTreeDesc[key],
MakeBTreeDesc[value]];
};
IF in.UserAbort[] THEN continue ← no;
};
desiredProperties[directory] ← TRUE;
desiredProperties[nameBody] ← TRUE;
desiredProperties[version] ← TRUE;
desiredProperties[createDate] ← TRUE;
desiredProperties[size] ← TRUE;
STP.SetDesiredProperties[stphandle, desiredProperties];
STP.Enumerate[stphandle, globalremotepattern, EnumProc
! STP.Error =>
IF code = noSuchFile THEN {
out.PutF["%s not found.\n", IO.string[globalremotepattern]];
CONTINUE
}
ELSE IF STPSubr.HandleSTPError[stphandle, code,
error, wh] THEN RETRY
];
time ← Time.Current[] - time;
out.PutF["\nEnumeration complete, %d files, elapsed time %r.\n",
IO.card[nFiles], IO.card[time]];
out.PutF["Will offer to delete %d files.\n", IO.card[ndelete]];
out.PutF["This will free %d pages, and will leave %d pages alone.\n",
IO.card[ndeletepages], IO.card[nskippages]];
ndelete ← 0;
};
PrintListOfDeletes: BTreeDefs.Call = {
value: LONG STRING ← LOOPHOLE[BASE[v]];
longfile: STRING ← [100];
stringcreate: STRING ← [100];
stringpages: STRING ← [100];
directory: STRING ← [100];
create: LONG CARDINAL;
pages: CARDINAL;
morris: BOOL ← FALSE;
tonywest: BOOL ← FALSE;
Subr.strcpy[longfile, value];
FOR i: CARDINAL IN [0 .. value.length) DO
IF value[i] = 0C THEN {
longfile.length ← i;
EXIT;
};
ENDLOOP;
Subr.SubStrCopy[stringcreate, value, longfile.length + 1];
FOR i: CARDINAL IN [0 .. stringcreate.length) DO
IF stringcreate[i] = 0C THEN {
stringcreate.length ← i;
EXIT;
};
ENDLOOP;
create ← DateAndTimeUnsafe.Parse[stringcreate
! DateAndTimeUnsafe.Unintelligible => {
out.PutF["Error - invalid date\n"];
create ← 0;
CONTINUE;
}].dt;
Subr.SubStrCopy[stringpages, value,
longfile.length + stringcreate.length + 2];
IF Subr.EndsIn[stringpages, "M"L] THEN {
stringpages.length ← stringpages.length - 1;
morris ← TRUE;
};
IF Subr.EndsIn[stringpages, "T"L] THEN {
stringpages.length ← stringpages.length - 1;
tonywest ← TRUE;
};
pages ← LongString.StringToDecimal[stringpages];
Subr.strcpy[directory, longfile];
FOR i: CARDINAL DECREASING IN [0 .. longfile.length) DO
IF longfile[i] = '> THEN {
directory.length ← i;
EXIT;
};
ENDLOOP;
IF NOT LongString.EquivalentString[lastDirectory, directory] THEN {
deleteFile.PutChar['\n]; -- extra space
Subr.strcpy[lastDirectory, directory];
};
IF morris THEN deleteFile.PutF["Stored by Morris.PA:\n"]
ELSE IF tonywest THEN deleteFile.PutF["Stored by TonyWest.PA:\n"];
IF longfile.length > 60 THEN
deleteFile.PutF["%s %s %4d\n", IO.string[longfile], IO.string[stringcreate],
IO.card[pages]]
ELSE
deleteFile.PutF["%-60s %s %4d\n", IO.string[longfile], IO.string[stringcreate],
IO.card[pages]];
IF in.UserAbort[] THEN RETURN[FALSE, FALSE];
RETURN[TRUE, FALSE];
};
ReallyDeleteFiles: BTreeDefs.Call = {
ch: CHAR;
value: LONG STRING ← LOOPHOLE[BASE[v]];
longfile: STRING ← [100];
stringcreate: STRING ← [100];
mustConnect: BOOL ← FALSE;
r: Rope.ROPE;
DelComplete: STP.CompletionProcType = {
-- it turns out we don't get the right signal
-- so we have to raise it ourselves
-- the error is STP.Error[accessDenied], but the string
-- is IFS-specific
-- can't raise STP.Error in here, unwind will kill connection
-- so we set a flag
out.PutF["%s\n", IO.string[fileOrError]];
IF what = error
AND LongString.EqualString[fileOrError, "File is protected - access denied."L]
THEN
mustConnect ← TRUE;
};
Subr.strcpy[longfile, value];
FOR i: CARDINAL IN [0 .. value.length) DO
IF value[i] = 0C THEN {
longfile.length ← i;
EXIT;
};
ENDLOOP;
Subr.SubStrCopy[stringcreate, value, longfile.length + 1];
FOR i: CARDINAL IN [0 .. stringcreate.length) DO
IF stringcreate[i] = 0C THEN {
stringcreate.length ← i;
EXIT;
};
ENDLOOP;
r ← IO.PutFR["Delete %s of %s ", IO.string[longfile], IO.string[stringcreate]];
IF fullspeed THEN {
out.PutRope[r];
ch ← 'y;
}
ELSE ch ← wh.Confirm[wh.in, wh.out, wh.data, r, 'n];
IF ch = 'q THEN {
nskipno ← nskipno + 1;
RETURN[FALSE, FALSE];
};
IF ch = 'a THEN {
out.PutF["All\nDo you really want to delete without confirmation (Type ↑ to confirm) "];
IF wh.in.GetChar[] = '↑ THEN {
ch ← 'y;
fullspeed ← TRUE;
};
}; -- continues on to delete this file
IF ch = 'y THEN {
ndelete ← ndelete + 1;
stphandle ← STPSubr.Connect[host: globalhost, h: wh,
onlyOne: TRUE];
DO
mustConnect ← FALSE;
STP.Delete[stp: stphandle, name: longfile, confirm: NIL,
complete: DelComplete
! STP.Error =>
IF code = noSuchFile THEN {
out.PutF["%s not found.\n", IO.string[longfile]];
CONTINUE
}
ELSE IF STPSubr.HandleSTPError[stphandle, code,
error, wh] THEN RETRY
];
IF NOT mustConnect THEN EXIT;
[] ← STPSubr.HandleSTPError[stphandle, accessDenied,
NIL, wh];
ENDLOOP;
}
ELSE {
nskipno ← nskipno + 1;
};
RETURN[NOT in.UserAbort[], FALSE];
};
Match: PROC[host: LONG STRING, info: STP.FileInfo] RETURNS[dontDelete: BOOL] = {
element: CARDINAL;
remdate: LONG CARDINAL ← 0;
l: NameList;
element ← HashedLookup[info.body];
IF shortnamearray[element].shortname = NIL THEN RETURN[FALSE]; -- not found
l ← shortnamearray[element].list;
WHILE l ~= NIL DO
IF LongString.EquivalentString[l.directory, info.directory]
AND LongString.EquivalentString[l.host, host] THEN {
IF l.createtime > 0 THEN {
IF remdate = 0 THEN
remdate ← DateAndTimeUnsafe.Parse[info.create
! DateAndTimeUnsafe.Unintelligible => CONTINUE
].dt;
IF remdate = l.createtime THEN RETURN[TRUE];
};
IF l.version > 0 THEN {
IF info.version ~= NIL AND info.version.length > 0 THEN {
vers: CARDINAL ← LongString.StringToDecimal[info.version];
IF vers = l.version THEN RETURN[TRUE];
};
};
IF l.createtime = 0 AND l.version = 0 THEN -- >, ~=
RETURN[TRUE];
};
l ← l.list;
ENDLOOP;
RETURN[FALSE];
};
Cleanup: PROC = {
IF keepFile ~= NIL THEN keepFile.Close[];
keepFile ← NIL;
IF deleteFile ~= NIL THEN deleteFile.Close[];
deleteFile ← NIL;
IF haveDeleteBTree THEN BTreeSupportExtraDefs.CloseFile[
BTreeDefs.ReleaseBTree[deleteBTree]];
haveDeleteBTree ← FALSE;
IF deleteBackingFile ~= File.nullCapability THEN
File.Delete[deleteBackingFile];
deleteBackingFile ← File.nullCapability;
DFSubr.FreeDFSeq[@dfseq];
STPSubr.StopSTP[];
Subr.SubrStop[];
};
{
ENABLE {
UNWIND => Cleanup[];
STP.Error => {
lcode: LONG CARDINAL ← LOOPHOLE[code, CARDINAL];
out.PutF["FTP Error. "];
IF error ~= NIL THEN
out.PutF["message: %s, code %d in Stp.Mesa\n",
IO.string[error], IO.card[lcode]];
Subr.errorflg ← TRUE;
GOTO out;
};
ABORTED, Subr.AbortMyself => {
out.PutF["RemoteDeleteAll Aborted.\n"];
GOTO out;
};
};
shortnamearray ← hugezone.NEW[ShortRec ← ALL[[NIL, NIL]]];
out.PutF["Filling in hash table ... "];
FOR i: CARDINAL IN [0 .. dfseq.size) DO
AddName[dfseq[i].host, dfseq[i].directory, dfseq[i].shortname,
dfseq[i].version, dfseq[i].createtime];
ENDLOOP;
out.PutF["done.\n"];
-- debugging
out.PutF["%d insertions, %d scans in %d looks.\n", IO.card[nInsert], IO.card[nScan], IO.card[nLook]];
deleteBackingFile ← File.Create[Volume.systemID, initialBackingPages, [12345]];
deleteBTree ← BTreeDefs.CreateAndInitializeBTree[
fileH: BTreeSupportExtraDefs.OpenFile[deleteBackingFile],
initializeFile: TRUE, isFirstGreaterOrEqual: IsFirstGEQ,
areTheyEqual: AreTheyEQ];
haveDeleteBTree ← TRUE;
out.PutF["Enumerating [%s]%s ... \n", IO.string[globalhost], IO.string[globalremotepattern]];
stphandle ← STPSubr.Connect[host: globalhost, h: wh, onlyOne: TRUE];
out.PutF["Writing list of files that will NOT be deleted on 'RemoteDeleteAll.KeepFiles$'\n"];
keepFile ← FileIO.Open["RemoteDeleteAll.KeepFiles$", overwrite];
keepFile.PutF["\n\nThese files will not be deleted.\n\n"];
keepFile.PutF[" FileName CreateTime IFSPages\n"];
BuildBTree[];
keepFile.PutF["\n\n----------------------\n"];
keepFile.Close[];
keepFile ← NIL;
out.PutF["List of files that will NOT be deleted written on 'RemoteDeleteAll.KeepFiles$'\n"];
STPSubr.StopSTP[];
hugezone ← Subr.FreeHugeZone[hugezone];
-- be careful: do not use hugezone data structures after this
shortnamearray ← NIL;
dfseq ← NIL;
IF in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
--
out.PutF["Writing list of files that will be deleted on 'RemoteDeleteAll.DeleteFiles$'\n"];
deleteFile ← FileIO.Open["RemoteDeleteAll.DeleteFiles$", overwrite];
deleteFile.PutF["\nThese files will be deleted.\n\n"];
deleteFile.PutF[" FileName CreateTime IFSPages\n"];
BTreeDefs.EnumerateFrom[deleteBTree, MakeBTreeDesc[""L], PrintListOfDeletes];
deleteFile.PutF["\n\n----------------------\n"];
deleteFile.Close[];
deleteFile ← NIL;
IF in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
--
out.PutF["Type print RemoteDeleteAll.KeepFiles$ RemoteDeleteAll.DeleteFiles$ to print the files.\n"];
IF possibleDelete THEN {
out.PutF["\n\n\n(For each file, type y to delete, q to quit, \n\ta to delete subsequent files w/o confirmation and any other char to not delete.)\n"];
out.PutF["\nEnumerating [%s]%s ... \n", IO.string[globalhost], IO.string[globalremotepattern]];
BTreeDefs.EnumerateFrom[deleteBTree, MakeBTreeDesc[""L],
ReallyDeleteFiles];
};
out.PutF["\n%d files matched, %d files deleted, %d files skipped.\n",
IO.card[nskipdf], IO.card[ndelete], IO.card[nskipno]];
EXITS
out => NULL;
};
Cleanup[];
elapt ← Time.Current[] - elapt;
out.PutF["\nTotal elapsed time for RemoteDeleteAll %r.",IO.card[elapt]];
IF Subr.errorflg THEN
out.PutF["\tErrors logged.\n"];
out.PutChar['\n];
PrintSeparator[];
};
Hash: PROC[shortname: LONG STRING, modulo: CARDINAL] RETURNS[hv: CARDINAL] = {
h, i: CARDINAL ← 0;
v: Inline.BytePair;
IF shortname.length = 0 THEN ERROR;
DO
v.low ← LOOPHOLE[String.LowerCase[shortname[i]], CARDINAL];
i ← i + 1;
v.high ← IF i >= shortname.length THEN 0
ELSE LOOPHOLE[String.LowerCase[shortname[i]], CARDINAL];
i ← i + 1;
h ← Inline.BITXOR[h, v];
IF i >= shortname.length THEN EXIT;
ENDLOOP;
hv ← h MOD modulo;
-- debugging CWF.WF1["%d\n"L, @hv];
};
MakeBTreeDesc: PROC [s: STRING] RETURNS [d: BTreeDefs.Desc] = INLINE
{RETURN[DESCRIPTOR[LOOPHOLE[s, POINTER], (s.length + 1)/2 + 2]]};
IsFirstGEQ: BTreeDefs.TestKeys =
BEGIN
aS: LONG STRING = LOOPHOLE[BASE[a]];
bS: LONG STRING = LOOPHOLE[BASE[b]];
FOR i: CARDINAL IN [0..MIN[aS.length, bS.length]) DO
aC: CHAR = Inline.BITOR[aS[i], 40B];
bC: CHAR = Inline.BITOR[bS[i], 40B];
SELECT aC FROM
> bC => RETURN [TRUE];
< bC => RETURN [FALSE];
ENDCASE;
ENDLOOP;
RETURN [aS.length >= bS.length]
END;
AreTheyEQ: BTreeDefs.TestKeys =
BEGIN
aS: LONG STRING = LOOPHOLE[BASE[a]];
bS: LONG STRING = LOOPHOLE[BASE[b]];
IF aS.length ~= bS.length THEN RETURN [FALSE];
FOR i: CARDINAL IN [0..aS.length) DO
IF Inline.BITOR[aS[i], 40B] ~= Inline.BITOR[bS[i], 40B] THEN RETURN [FALSE];
ENDLOOP;
RETURN [TRUE]
END;
Init: PROC = {
typeScript ← TypeScript.Create[info: [name: "RemoteDeleteAll Window", iconic: FALSE]];
[in: in, out: out, file: logFile] ← SetUpLogStreams[typeScript];
[] ← CWF.SetWriteProcedure[TTYProc];
UserExec.RegisterCommand["RemoteDeleteAll.~", Main];
destroyEventRegistration ← ViewerEvents.RegisterEventProc[MyDestroy, destroy];
};
NullTTYProc: PROC[ch: CHAR] = {}; -- prints nothing
-- cannot print anything in this, monitor is locked
MyDestroy: ViewerEvents.EventProc = TRUSTED
{
IF event ~= destroy OR viewer ~= typeScript THEN RETURN;
[] ← CWF.SetWriteProcedure[NullTTYProc]; -- turn off printing
Subr.SubrStop[];
logFile.Close[];
ViewerEvents.UnRegisterEventProc[destroyEventRegistration, destroy];
};
SetUpLogStreams: PROC[ts: TypeScript.TS] RETURNS[in, out, file: IO.Handle] =
{
file ← FileIO.Open["RemoteDeleteAll.Log", overwrite];
[in, out] ← ViewerIO.CreateViewerStreams[name: "RemoteDeleteAll Window", viewer: ts,
editedStream: FALSE];
out ← IO.CreateDribbleStream[out, file];
};
MyConfirm: SAFE PROC[in, out: IO.Handle, data: REF ANY, msg: Rope.ROPE, dch: CHAR]
RETURNS[CHAR] = CHECKED {
value: ATOM;
value ← UserExec.AskUser[msg: msg, viewer: 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;
};
TTYProc: PROC[ch: CHAR] = {
out.PutChar[ch];
};
PrintSeparator: PROC = {
out.PutF["===============================\n"];
};
Init[];
}.