<> <> <> <> 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 = { <> MAXDELETEFILE: CARDINAL = 9000; -- should be 12000 <> globalhost: STRING _ [30]; globalremotepattern: STRING _ [100]; typeScript: TypeScript.TS; in, out, logFile: IO.Handle; destroyEventRegistration: ViewerEvents.EventRegistration; <> possibleDelete: BOOL = TRUE; initialBackingPages: CARDINAL = 10; IFSBytesPerPage: CARDINAL = 2048; <> 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 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]; Process.Detach[FORK RunDeleteAll[dfseq, starttime, wh]]; dfseq _ NIL; EXITS usage => { out.PutF["Usage: RemoteDeleteAll directory dffile(s).\n"]; }; leave => NULL; }; [] _ UserExec.ReleaseResource[$RemoteDeleteAll]; }; <> 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: BOOL _ FALSE; elapt: LONG CARDINAL _ starttime; nskippages, ndeletepages: LONG CARDINAL _ 0; fullspeed: BOOL _ FALSE; 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 STRING _ NIL; nLook, nScan, nInsert: CARDINAL _ 0; longfile: LONG STRING _ NIL; 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; }; <> <<(this will be the first NIL encountered)>> <> 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]; <> 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] <> 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: BOOL _ FALSE] = { ch: CHAR; r: Rope.ROPE; longfile: STRING; mustConnect: BOOL _ FALSE; DelComplete: STP.CompletionProcType = { <> <> <> <> <> <> 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 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"]; <> 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]; <> 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; <> }; 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 <> 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[]; }.