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]
CommandObject = [
in, out, err: STREAM, commandLine, command: ROPE,
propertyList: List.AList, procData: CommandProcHandle]
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 => {
Groan. The highest local version of this file has the desired stamp and it is in our cache. The server has some version of this file, but not the one we want. This is dull if a better file is on the server, but it's VERY interesting if our copy is better than what's on the server.
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 {
No remote version, but directory and such are reasonable
Another opportunity for more housecleaning, but again, this path doesn't happen very often.
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]]; };