RemoteDeleteAllImpl.Mesa, last edit February 9, 1983 6:13 pm
Pilot 6.0/ Mesa 7.0
Usage: RemoteDeleteAll directory list of dfs (nesting allowed)
Last Edited by: Maxwell, May 31, 1983 11:46 am
DIRECTORY
CWF: TYPE USING [SetWriteProcedure, SWF2],
DateAndTimeUnsafe: TYPE USING [Parse, Unintelligible],
DFSubr: TYPE USING [AllocateDFSeq, DFSeq, FlattenDF, FreeDFSeq, StripLongName],
FileIO: TYPE USING[Open],
Inline: TYPE USING [BITXOR, BytePair, LowHalf],
IO: TYPE USING[card, Close, CreateDribbleStream, EndOf, GetChar, GetToken, Handle,
PutChar, PutF, PutFR, PutRope, rope, SkipOver, string, UserAbort, WhiteSpace],
IOExtras: TYPE USING[GetLine],
LongString: TYPE USING [AppendString, EqualString, EquivalentString, StringToDecimal],
Process: TYPE USING [Detach],
Rope: TYPE USING[Fetch, Length, 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, 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];
RemoteDeleteAllImpl: PROGRAM
IMPORTS CWF, DateAndTimeUnsafe,
DFSubr, FileIO, Inline, IO, IOExtras, LongString,
Process, Rope, RopeInline, STP: UnsafeSTP, STPSubr, String, Subr, Time, TypeScript, UECP,
UserExec, ViewerEvents, ViewerIO = {
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;
IFSBytesPerPage: CARDINAL = 2048;
this is the procedure called by the Simple Executive
Main: UserExec.CommandProc = TRUSTED {
ENABLE UNWIND => [] ← UserExec.ReleaseResource[$RemoteDeleteAll];
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 CARDINALLOOPHOLE[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];
Process.Detach[FORK RunDeleteAll[dfseq, starttime, wh]];
dfseq ← NIL;
EXITS
usage => {
out.PutF["Usage: RemoteDeleteAll directory dffile(s).\n"];
};
leave => NULL;
};
[] ← UserExec.ReleaseResource[$RemoteDeleteAll];
};
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];
haveDeleteBTree: BOOLFALSE;
elapt: LONG CARDINAL ← starttime;
nskippages, ndeletepages: LONG CARDINAL ← 0;
fullspeed: BOOLFALSE;
keepDirectory: STRING ← [100];
deleteDirectory: 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 STRINGNIL;
nLook, nScan, nInsert: CARDINAL ← 0;
longfile: LONG STRINGNIL;
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
};
WriteKeepAndDeleteFiles: 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;
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[keepDirectory, info.directory] THEN {
keepFile.PutChar['\n]; -- extra space
Subr.strcpy[keepDirectory, 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 delete file
IF NOT LongString.EquivalentString[deleteDirectory, info.directory] THEN {
keepFile.PutChar['\n]; -- extra space
Subr.strcpy[deleteDirectory, info.directory];
};
IF file.length > 60 THEN deleteFile.PutF["%s %s\n", IO.string[file],
IO.string[info.create]]
ELSE deleteFile.PutF["%-60s %s\n", IO.string[file], IO.string[info.create]];
ndeletepages ← ndeletepages + pages;
ndelete ← ndelete + 1;
};
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;
};
DeleteFile: PROC [fileName, createDate: Rope.ROPE] RETURNS[quit: BOOLFALSE] = {
ch: CHAR;
r: Rope.ROPE;
longfile: STRING;
mustConnect: BOOLFALSE;
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;
};
r ← IO.PutFR["Delete %s of %s ", IO.rope[fileName], IO.rope[createDate]];
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[quit: TRUE];
};
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 AND possibleDelete 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;
};
};
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 dfseq ~= NIL THEN {DFSubr.FreeDFSeq[@dfseq]; dfseq ← NIL};
STPSubr.StopSTP[];
Subr.SubrStop[];
};
{
ENABLE {
UNWIND => Cleanup[];
STP.Error => {
lcode: LONG CARDINALLOOPHOLE[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]];
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 be deleted on 'RemoteDeleteAll.DeleteFiles$'\n"];
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"];
deleteFile ← FileIO.Open["RemoteDeleteAll.DeleteFiles$", overwrite];
deleteFile.PutF["\nThese files will be deleted.\n\n"];
deleteFile.PutF[" FileName CreateTime IFSPages\n"];
WriteKeepAndDeleteFiles[];
keepFile.PutF["\n\n----------------------\n"];
keepFile.Close[];
keepFile ← NIL;
deleteFile.PutF["\n\n----------------------\n"];
deleteFile.Close[];
deleteFile ← NIL;
out.PutF["List of files that will be deleted written on 'RemoteDeleteAll.DeleteFiles$'\n"];
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["Type print RemoteDeleteAll.KeepFiles$ RemoteDeleteAll.DeleteFiles$ to print the files.\n"];
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]];
deleteFile ← FileIO.Open["RemoteDeleteAll.DeleteFiles$"];
UNTIL deleteFile.EndOf[] DO
fileName, createDate: Rope.ROPE;
fileName ← deleteFile.GetToken[IO.WhiteSpace];
deleteFile.SkipOver[]; -- skips over white space before create date
createDate ← IOExtras.GetLine[deleteFile]; -- gets the remainder of the line
IF fileName.Length[] = 0 THEN LOOP;
IF fileName.Fetch[0] # '< THEN LOOP;
IF DeleteFile[fileName, createDate].quit THEN EXIT;
IF in.UserAbort[] THEN EXIT;
ENDLOOP;
deleteFile.Close[];
deleteFile ← NIL;
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];
};
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[];
}.