<> <> <> <<>> DIRECTORY BasicTime USING [GMT, nullGMT, Period], CommandTool USING [ArgumentVector, Failed, Parse], Commander USING [CommandProc, Handle, Register], FS USING [Delete, Error, ErrorDesc, FileInfo], FSBackdoor USING [EntryPtr, Enumerate, Flush, highestVersion, MakeFName, noVersion, TextFromTextRep, Version], FSName USING [BangVersionFile, ServerAndFileRopes, VersionFromRope], FSRemoteFile USING [Info], IO USING [PutF, STREAM], Process USING [CheckForAbort], Rope USING [Fetch, Find, Flatten, Length, ROPE, Substr]; Ferret: CEDAR PROGRAM IMPORTS BasicTime, Commander, CommandTool, FS, FSBackdoor, FSName, FSRemoteFile, IO, Process, Rope = { ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; Ferret: Commander.CommandProc = { <<[cmd: REF CommandObject] RETURNS [result: REF _ NIL, msg: ROPE _ NIL]>> <> <> <> out: STREAM _ cmd.out; attachedSwitch: BOOL _ TRUE; cachedSwitch: BOOL _ TRUE; deleteSwitch: BOOL _ FALSE; flushSwitch: BOOL _ FALSE; rootName: ROPE; localName: ROPE; -- NIL => Checking cached file remoteName: ROPE; thisVersion: FSBackdoor.Version; createWanted: BasicTime.GMT; CheckFile: PROC RETURNS [stop: BOOL] = { versionFound: FSBackdoor.Version; bytesFound: INT; createFound: BasicTime.GMT _ BasicTime.nullGMT; server, fileName: ROPE; fsError: FS.ErrorDesc _ [ok, NIL, NIL]; stop _ AbortRequested[cmd]; [server: server, file: fileName] _ FSName.ServerAndFileRopes[remoteName]; [versionFound, bytesFound, createFound] _ FSRemoteFile.Info[ server, fileName, createWanted ! FS.Error => { fsError _ error; CONTINUE; } ]; IF fsError.group # ok THEN { IO.PutF[out, "\n"]; IF localName # NIL THEN { -- Attached file SELECT fsError.code FROM $illegalName, $unknownFile, -- Remote file doesn't exist at all (no versions) <<$illegalName is probably no directory.>> $unknownCreatedTime => { -- Remote file exists, but not the desired versionStamp fullFName: ROPE _ NIL; created: BasicTime.GMT; IO.PutF[out, "Attached file missing from server.\n %G => %G\n", [rope[localName]], [rope[remoteName]]]; [fullFName: fullFName, created: created] _ FS.FileInfo[ name: rootName, remoteCheck: TRUE, wDir: "///" ! FS.Error => CONTINUE]; IF fullFName # NIL THEN { SELECT BasicTime.Period[to: created, from: createWanted] FROM > 0 => { IO.PutF[out, " Newer local version is %G\n", [rope[fullFName]]]; deleted _ deleted.SUCC; IF deleteSwitch THEN { IO.PutF[out, " Deleting %G ...", [rope[localName]]]; FS.Delete[localName]; IO.PutF[out, " ok.\n"]; } ELSE IO.PutF[out, " Would have deleted %G.\n", [rope[localName]]]; RETURN; }; = 0 => { <> targetVersion: FSBackdoor.Version _ ExtractVersion[remoteName]; remoteVersion: FSBackdoor.Version _ FSBackdoor.highestVersion; remoteCreate: BasicTime.GMT; [server: server, file: fileName] _ FSName.ServerAndFileRopes[remoteName]; fileName _ FSName.BangVersionFile[fileName, FSBackdoor.highestVersion]; [remoteVersion, , remoteCreate] _ FSRemoteFile.Info[server, fileName, BasicTime.nullGMT ! FS.Error => CONTINUE]; IF remoteVersion = FSBackdoor.highestVersion THEN { <> <> IO.PutF[out, " No remote version at all.\n"]; RETURN; }; fileName _ FSName.BangVersionFile[remoteName, remoteVersion]; SELECT BasicTime.Period[to: remoteCreate, from: createWanted] FROM > 0 => { IO.PutF[out, " Newer remote version is %G\n", [rope[fileName]]]; deleted _ deleted.SUCC; IF deleteSwitch THEN { IO.PutF[out, "Deleting %G ...", [rope[localName]]]; FS.Delete[localName]; IO.PutF[out, " ok.\n"]; } ELSE IO.PutF[out, " Would have deleted %G.\n", [rope[localName]]]; RETURN; }; = 0 => ERROR; < 0 => { rescueMe _ rescueMe.SUCC; IO.PutF[out, " *** Beware: Missing remote version was NEWER.\n %G >> %G\n", [rope[remoteName]], [rope[fileName]]]; IO.PutF[out, " *** There is hope. The file is in your cache!\n"]; }; ENDCASE => ERROR; }; < 0 => IO.PutF[out, " *** Beware: Missing remote version was attached to a NEWER local version %G!h\n", [rope[rootName]]]; ENDCASE => ERROR; }; }; ENDCASE => { rescueMe _ rescueMe.SUCC; IO.PutF[out, "*** %G\n", [rope[fsError.explanation]]]; }; RETURN; }; IF localName = NIL THEN { -- Cached file SELECT fsError.code FROM $illegalName, $unknownFile => { -- Remote file doesn't exist targetVersion: FSBackdoor.Version _ ExtractVersion[remoteName]; remoteVersion: FSBackdoor.Version _ FSBackdoor.highestVersion; remoteCreate: BasicTime.GMT; IO.PutF[out, "Cached file missing from server.\n %G\n", [rope[remoteName]]]; [server: server, file: fileName] _ FSName.ServerAndFileRopes[rootName]; fileName _ FSName.BangVersionFile[rootName, FSBackdoor.highestVersion]; [remoteVersion, , remoteCreate] _ FSRemoteFile.Info[server, fileName, createWanted ! FS.Error => CONTINUE ]; IF remoteVersion = FSBackdoor.highestVersion THEN { -- No remote versions IO.PutF[out, " No remote versions exist.\n"]; flushed _ flushed.SUCC; IF flushSwitch THEN { IO.PutF[out, " Flushing %G ...", [rope[remoteName]]]; FSBackdoor.Flush[remoteName]; IO.PutF[out, " ok.\n"]; } ELSE IO.PutF[out, " Would have Flushed %G.\n", [rope[remoteName]]]; RETURN; } ELSE { -- Other versions exist fileName _ FSName.BangVersionFile[rootName, remoteVersion]; SELECT remoteVersion FROM > targetVersion => { IO.PutF[out, " Newer remote version is %G\n", [rope[fileName]]]; flushed _ flushed.SUCC; IF flushSwitch THEN { IO.PutF[out, " Flushing %G ...", [rope[remoteName]]]; FSBackdoor.Flush[remoteName]; IO.PutF[out, " ok.\n"]; } ELSE IO.PutF[out, " Would have Flushed %G.\n", [rope[remoteName]]]; RETURN; }; = targetVersion => IO.PutF[out, " *** Beware: Remote version exists, but it has a DIFFERENT timestamp.\n\n"]; < targetVersion => { rescueMe _ rescueMe.SUCC; IO.PutF[out, " *** Beware: Missing remote version was NEWER.\n %G >> %G\n", [rope[remoteName]], [rope[fileName]]]; IO.PutF[out, " *** There is hope. The file is in your cache!\n"]; }; ENDCASE => ERROR; }; }; ENDCASE => { rescueMe _ rescueMe.SUCC; IO.PutF[out, "*** %G\n", [rope[fsError.explanation]]]; }; RETURN; }; }; IF createWanted = BasicTime.nullGMT THEN RETURN; IF createWanted = createFound THEN RETURN; IO.PutF[out, "%G => %G\n", [rope[localName]], [rope[remoteName]]]; RETURN; }; GrabInfo: UNSAFE PROC [entry: FSBackdoor.EntryPtr] RETURNS [accept, stop: BOOL _ FALSE] = UNCHECKED { WITH e: entry^ SELECT FROM local => accept _ FALSE; attached => { attached _ attached.SUCC; IF attachedSwitch THEN { rootName _ FSBackdoor.TextFromTextRep[@entry[e.nameBody]]; localName _ FSBackdoor.MakeFName[rootName, e.version]; thisVersion _ e.version; remoteName _ FSBackdoor.TextFromTextRep[@entry[e.attachedTo]]; createWanted _ e.created; accept _ TRUE; }; }; cached => { cached _ cached.SUCC; IF cachedSwitch THEN { rootName _ FSBackdoor.TextFromTextRep[@entry[e.nameBody]]; localName _ NIL; remoteName _ FSBackdoor.MakeFName[rootName, e.version]; thisVersion _ FSBackdoor.noVersion; createWanted _ BasicTime.nullGMT; accept _ TRUE; }; }; ENDCASE => ERROR; }; passes: INT _ 0; attached, cached, deleted, flushed, rescueMe: INT _ 0; argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd: cmd ! CommandTool.Failed => {msg _ errorMsg; GO TO Failed}]; FOR i: NAT IN [1..argv.argc) DO arg: ROPE = argv[i]; IF Rope.Length[arg] = 0 THEN LOOP; IF Rope.Fetch[arg, 0] = '- THEN { sense: BOOL _ TRUE; FOR i: INT IN [1..Rope.Length[arg]) DO SELECT Rope.Fetch[arg, i] FROM '~ => {sense _ NOT sense; LOOP}; 'd, 'D => deleteSwitch _ sense; 'f, 'F => flushSwitch _ sense; ENDCASE; sense _ TRUE; ENDLOOP; LOOP; }; FSBackdoor.Enumerate[ volName: NIL, nameBodyPattern: Rope.Flatten[arg], localOnly: FALSE, allVersions: TRUE, version: [0], matchProc: GrabInfo, acceptProc: CheckFile]; passes _ passes.SUCC; ENDLOOP; IF passes = 0 THEN FSBackdoor.Enumerate[ volName: NIL, nameBodyPattern: NIL, localOnly: FALSE, allVersions: TRUE, version: [0], matchProc: GrabInfo, acceptProc: CheckFile]; IO.PutF[out, "\nProcessed %G attachments and %G cached files.\n", [integer[attached]], [integer[cached]]]; IF deleted # 0 THEN { IF deleteSwitch THEN IO.PutF[out, "%G files were deleted.\n", [integer[deleted]]] ELSE IO.PutF[out, "%G files would have been deleted.\n", [integer[deleted]]]; }; IF flushed # 0 THEN { IF flushSwitch THEN IO.PutF[out, "%G files were flushed.\n", [integer[flushed]]] ELSE IO.PutF[out, "%G files would have been flushed.\n", [integer[flushed]]]; }; IF rescueMe # 0 THEN IO.PutF[out, "***** %G files need rescuing by hand. *****\n", [integer[rescueMe]]]; EXITS Failed => result _ $Failure; }; ExtractVersion: PROC [fileName: ROPE] RETURNS [version: FSBackdoor.Version] = { bang: INT _ Rope.Find[fileName, "!", 0]; IF bang < 0 THEN RETURN[FSBackdoor.noVersion]; version _ FSName.VersionFromRope[Rope.Substr[fileName, bang+1]]; }; AbortRequested: PROC [cmd: Commander.Handle] RETURNS [BOOL] = { Process.CheckForAbort[]; RETURN [FALSE]; }; Init: PROCEDURE = { Commander.Register[ "///Commands/Ferret", Ferret, "Ferret -- Find files in cache that are NOT on the server. (Finds lots of junk too.)"]; }; <<>> <> Init[]; }.