CompareDFwithServerImpl.mesa
Copyright Ó 1991, 1992 by Xerox Corporation. All rights reserved.
Willie-s, March 27, 1992 4:26 pm PST
DIRECTORY
Basics USING [Comparison],
BasicTime USING [GMT, Now, nullGMT],
Commander USING [CommandProc, Handle, Register],
CommanderOps USING [Failed, NextArgument],
DFUtilities USING [DirectoryItem, FileItem, Filter, IncludeItem, ParseFromStream, ProcessItemProc, UsingList],
IO,
PFS,
PFSNames,
Process USING [CheckForAbort],
RedBlackTree USING [Compare, Create, Delete, DestroyTable, EachNode, EnumerateIncreasing, GetKey, Insert, Lookup, Table],
Rope;
CompareDFwithServerImpl: CEDAR MONITOR
IMPORTS BasicTime, Commander, CommanderOps, DFUtilities, IO, PFS, PFSNames, Process, RedBlackTree, Rope
= BEGIN
Types
GMT: TYPE = BasicTime.GMT;
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
PATH: TYPE = PFS.PATH;
FileEntry: TYPE = REF FileEntryRep;
FileEntryRep: TYPE = RECORD [
fullSrcName: ROPE,
includes version number
uid: PFS.UniqueID,
really just create date of the file for now
status: FileEntryStatus
indicates the status of the file
];
FileEntryStatus: TYPE = {dfOnly, dirOnly, both, differentDates};
fileCount: ARRAY FileEntryStatus OF CARD ¬ ALL[0];
Switches: TYPE = PACKED ARRAY CHAR['a..'z] OF BOOL;
Option variables
bangH: ROPE ~ "!H";
bangHPath: PATH ~ PFS.PathFromRope["*!H"];
Command Procedures
DoIt: PROC [dfNameRope: ROPE, table: RedBlackTree.Table, out: STREAM, verbose, isFile: BOOL] = {
EachEntry: RedBlackTree.EachNode = {
[data: RedBlackTree.UserData] RETURNS [stop: BOOL ← FALSE]
called for each item in the table to do the comparing.
WITH data SELECT FROM
entry: FileEntry => {
IF entry.status # both THEN {
explain the status of the file
SELECT entry.status FROM
dfOnly => IF verbose THEN {
out.PutF["--*****dfOnly: %g (%g)\n", [rope[entry.fullSrcName]],
IF entry.uid.egmt.gmt = BasicTime.nullGMT THEN [rope["nullGMT"]] ELSE [time[entry.uid.egmt.gmt]] ];
IF isFile THEN out.Flush[];
};
dirOnly => {
IF verbose THEN
out.PutF["Delete %g\n\t-- (%g)\n", [rope[entry.fullSrcName]], [time[entry.uid.egmt.gmt]] ]
ELSE out.PutF1["Delete %g\n", [rope[entry.fullSrcName]] ];
IF isFile THEN out.Flush[];
};
differentDates => {
out.PutF1["-- ## different dates for %g\n", [rope[entry.fullSrcName]] ];
IF isFile THEN out.Flush[];
};
ENDCASE;
};
fileCount[entry.status] ¬ fileCount[entry.status].SUCC;
Process.CheckForAbort[];
RETURN;
};
ENDCASE => ERROR;
};
VisitEntry: PROC [name: PATH, uid: PFS.UniqueID] = {
This procedure is used to visit each file in a simple DF closure, where the imports are NOT followed, but the inclusions ARE followed.
new: FileEntry ¬ NIL;
bytes: INT ¬ 0;
suffix: PATH ¬ NIL;
ropeName: ROPE ¬ PFS.RopeFromPath[name];
fileInfoName: PATH;
status: FileEntryStatus ¬ both; -- assume things are ok
Process.CheckForAbort[];
WITH RedBlackTree.Lookup[table, ropeName] SELECT FROM
entry: FileEntry => IF EqualUIDs[entry.uid, uid] THEN RETURN;
ENDCASE;
[fullFName: fileInfoName, uniqueID: uid, bytes: bytes] ¬ PFS.FileInfo[name: name, wantedUniqueID: uid !
PFS.Error => IF ( error.code = $unknownFile ) OR ( error.code = $unknownUniqueID ) OR ( error.code = $unknownCreatedTime ) OR ( error.code = $unknownServer ) THEN {
out.PutF["\n-- *PFS.Error[%g]\n\tasking for %g(%g)\n", [rope[error.explanation]], [rope[PFS.RopeFromPath[fileInfoName]]], IF uid.egmt.gmt = BasicTime.nullGMT THEN [rope["nullGMT"]] ELSE [time[uid.egmt.gmt]] ];
IF isFile THEN out.Flush[];
status ¬ dfOnly;
needsComment ¬ TRUE;
CONTINUE;
}
ELSE REJECT];
WITH RedBlackTree.Lookup[table, ropeName] SELECT FROM
entry: FileEntry => {
IF EqualUIDs[entry.uid, uid] THEN RETURN;
fileCount[differentDates] ¬ fileCount[differentDates].SUCC;
IF verbose THEN {
out.PutF["-- $Deleting %g (%g) from table; new date is %g\n", [rope[ropeName]],
[time[entry.uid.egmt.gmt]], [time[uid.egmt.gmt]] ];
IF isFile THEN out.Flush[];
needsComment ¬ TRUE;
};
[] ¬ RedBlackTree.Delete[table, ropeName];
};
ENDCASE;
new ¬ NEW[FileEntryRep ¬ [
fullSrcName: ropeName,
uid: uid,
status: status]];
RedBlackTree.Insert[table, new, ropeName];
};
VisitClosure: PROC [dfName: PATH, uid: PFS.UniqueID,
visitor: PROC [name: PATH, uid: PFS.UniqueID],
usingList: REF DFUtilities.UsingList ¬ NIL] = {
ENABLE PFS.Error => IF ( error.code = $unknownServer ) OR
( error.code = $unknownFile ) THEN {
out.PutF["\n-- ****PFS.Error[%g], in dfFile: %g\n", [rope[error.explanation]], [rope[PFS.RopeFromPath[dfName]]] ];
IF isFile THEN out.Flush[];
needsComment ¬ TRUE;
};
EachItem: DFUtilities.ProcessItemProc = {
WITH item SELECT FROM
dir: REF DFUtilities.DirectoryItem =>
prefix ¬ IF dir.readOnly THEN NIL ELSE PFS.PathFromRope[dir.path1];
file: REF DFUtilities.FileItem => IF prefix # NIL THEN {
name: PATH = PFSNames.ExpandName[PFS.PathFromRope[file.name], prefix];
UpdateDirList[name];
visitor[name, UIDFromGMT[file.date.gmt]];
};
incl: REF DFUtilities.IncludeItem => {
thisUID: PFS.UniqueID = UIDFromGMT[incl.date.gmt];
path1: PATH ¬ PFS.PathFromRope[incl.path1];
CheckCount[];
visitor[path1, thisUID];
VisitClosure[path1, thisUID, visitor];
};
ENDCASE => { i: INT ¬ 0; }; -- handy for setting breakpoints - (bj)
};
prefix: PATH ¬ NIL;
in: STREAM;
in ¬ PFS.StreamOpen[
fileName: dfName,
wantedUniqueID: uid
! PFS.Error => IF error.code = $unknownFile THEN {
out.PutF["-- PFS.Error[%g] - from dfFile %g\n", [rope[error.explanation]],
[rope[PFS.RopeFromPath[dfName]]] ];
IF isFile THEN out.Flush[];
needsComment ¬ TRUE;
in ¬ NIL;
CONTINUE;
}
ELSE REJECT
];
IF in#NIL THEN {
DFUtilities.ParseFromStream[in, EachItem -- global variable, hack for now! - (bj)
! UNWIND => in.Close[]];
in.Close[]
};
};
UpdateDirList: PROC[path: PATH] = {
thisDIR: PATH ¬ PFSNames.Directory[path];
lastsubDir: ROPE ¬ PFSNames.ComponentRope[
PFSNames.Fetch[thisDIR, PFSNames.ComponentCount[thisDIR]-1]];
IF lastsubDir.Equal["Top", FALSE] THEN RETURN;
IF lastsubDir.Equal["Documentation", FALSE] THEN RETURN;
IF lastsubDir.Equal["Commands", FALSE] THEN RETURN;
IF lastsubDir.Equal["FamousFiles", FALSE] THEN RETURN;
IF lastsubDir.Equal["Derived", FALSE] THEN RETURN;
FOR pL: LIST OF PATH ¬ dirList, pL.rest UNTIL pL=NIL DO
IF PFSNames.Compare[pL.first, thisDIR, FALSE] = equal THEN RETURN;
ENDLOOP;
IF verbose THEN {
out.PutF1["\n-- &&Adding %g to dirList\n", [rope[PFS.RopeFromPath[thisDIR]]] ];
IF isFile THEN out.Flush[];
needsComment ¬ TRUE;
};
dirList ¬ CONS[thisDIR, dirList];
};
CheckCount: PROC ~ {
IF (count ¬ count + 1) MOD 10 # 0 THEN RETURN;
IF needsComment THEN { out.PutRope["-- "]; needsComment ¬ FALSE };
IF count MOD 100 = 0 THEN {
out.PutF1["(%g) ", [integer[count]] ];
IF isFile THEN out.Flush[];
}
ELSE out.PutChar['.]
};
The mainline of DoIt
count: INT ¬ 0;
needsComment: BOOL ¬ TRUE;
dirList: LIST OF PATH ¬ NIL;
RedBlackTree.DestroyTable[table]; -- clear the table from the last run
Phase1, build up data base.
out.PutF1["\n-- ***** Building table from DF at %g\n", [time[BasicTime.Now[]]] ];
VisitClosure[PFS.PathFromRope[dfNameRope.Concat[bangH]], PFS.nullUniqueID, VisitEntry];
Phase2, scan directory.
ExamineDirectories[dirList, table, out, isFile];
Phase3, dump differences.
out.PutF1["\n-- ***** Examining tree at %g\n", [time[BasicTime.Now[]]] ];
RedBlackTree.EnumerateIncreasing[table, EachEntry];
out.PutF1["\n-- {Done at %g}\n", [time[BasicTime.Now[]]] ];
};
ExamineDirectories: PROC[dirList: LIST OF PATH, table: RedBlackTree.Table, out: STREAM, isFile: BOOL] = {
NameInfoProc: PFS.InfoProc = {
new: FileEntry;
ropeName: ROPE;
IF fileType = PFS.tDirectory THEN RETURN;
WITH RedBlackTree.Lookup[table, ropeName ¬ PFS.RopeFromPath[fullFName]] SELECT FROM
entry: FileEntry => {
IF EqualUIDs[entry.uid, uniqueID] THEN
entry.status ¬ both ELSE entry.status ¬ differentDates;
RETURN;
};
ENDCASE;
new ¬ NEW[FileEntryRep ¬ [
fullSrcName: ropeName,
uid: uniqueID,
status: dirOnly]];
RedBlackTree.Insert[table, new, ropeName];
};
out.PutF1["\n-- ***** Enumerating directories %g\n", [time[BasicTime.Now[]]] ];
IF isFile THEN out.Flush[];
FOR pL: LIST OF PATH ¬ dirList, pL.rest UNTIL pL=NIL DO
toSearch: PATH ¬ PFSNames.Cat[pL.first, bangHPath];
out.PutF1["--\t(%g)\N", [rope[PFS.RopeFromPath[toSearch]]] ];
IF isFile THEN out.Flush[];
PFS.EnumerateForInfo[toSearch, NameInfoProc];
ENDLOOP;
};
EqualUIDs: PROC[uid1, uid2: PFS.UniqueID] RETURNS[BOOL] = {
RETURN[ ( uid1.egmt.gmt = uid2.egmt.gmt ) ]; -- all for now
RETURN[ ( uid1.egmt.gmt = uid2.egmt.gmt ) AND ( uid1.egmt.usecs = uid2.egmt.usecs )
AND ( uid1.host.a = uid2.host.a ) AND ( uid1.host.a = uid2.host.a ) ];
};
UIDFromGMT: PROC [gmt: BasicTime.GMT] RETURNS [PFS.UniqueID] ~ INLINE {
RETURN [[egmt: [gmt: gmt, usecs: 0]]]
};
ShowTable: PROC [out: STREAM, table: RedBlackTree.Table] = {
EachEntry: RedBlackTree.EachNode = {
[data: RedBlackTree.UserData] RETURNS [stop: BOOL ← FALSE]
WITH data SELECT FROM
entry: FileEntry => ShowEntry[out, entry];
ENDCASE => ERROR;
};
RedBlackTree.EnumerateIncreasing[table, EachEntry];
};
ShowEntry: PROC [out: STREAM, entry: FileEntry] = {
IO.PutF[out, "[name: %g, date: %g, state: ",
[rope[entry.fullSrcName]], [time[entry.uid.egmt.gmt]] ];
SELECT entry.status FROM
dfOnly => out.PutRope["dfOnly]\n"];
dirOnly => out.PutRope["dirOnly]\n"];
both => out.PutRope["both]\n"];
differentDates => out.PutRope["differentDates]\n"];
ENDCASE;
};
GetKey: RedBlackTree.GetKey = {
[data: RedBlackTree.UserData] RETURNS [RedBlackTree.Key]
RETURN [data];
};
Compare: RedBlackTree.Compare = {
[k: RedBlackTree.Key, data: RedBlackTree.UserData] RETURNS [Basics.Comparison]
key: ROPE ¬ NIL;
WITH k SELECT FROM
ent: FileEntry => key ¬ ent.fullSrcName;
rope: ROPE => key ¬ rope;
ENDCASE => ERROR;
WITH data SELECT FROM
ent: FileEntry => RETURN [Rope.Compare[key, ent.fullSrcName, FALSE]];
ENDCASE;
ERROR;
};
CompareDFwithServer: Commander.CommandProc ~ {
ProcessSwitches: PROC [arg: ROPE] ~ {
sense: BOOL ¬ TRUE;
FOR index: INT IN [0..Rope.Length[arg]) DO
char: CHAR ¬ Rope.Fetch[arg, index];
SELECT char FROM
'- => LOOP;
'~ => {sense ¬ NOT sense; LOOP};
'a, 'A => { outFileName ¬ CommanderOps.NextArgument[cmd]; switches[char] ¬ sense };
'o, 'O => { outFileName ¬ CommanderOps.NextArgument[cmd]; switches[char] ¬ sense };
IN ['a..'z] => switches[char] ¬ sense;
IN ['A..'Z] => switches[char + ('a-'A)] ¬ sense;
ENDCASE;
sense ¬ TRUE;
ENDLOOP;
};
out: STREAM ¬ cmd.out;
dfName: ROPE;
outFileName: ROPE;
isFile: BOOL ¬ FALSE;
table: RedBlackTree.Table ¬ RedBlackTree.Create[getKey: GetKey, compare: Compare];
switches: Switches ¬ ALL[FALSE];
DO
arg: ROPE ¬ CommanderOps.NextArgument[cmd ! CommanderOps.Failed => { msg ¬ errorMsg; GO TO failed } ];
When parsing the command line, be prepared for failure. The error is reported to the user
ch: CHAR;
Process.CheckForAbort[];
IF arg = NIL THEN EXIT;
ch ¬ Rope.Fetch[arg, 0];
SELECT TRUE FROM
( ch = '- ) AND ( arg.Length[] = 2 ) => ProcessSwitches[arg]; -- switch
( ch = '{ ) => LOOP; -- ignore
( ch = '} ) => LOOP; -- ignore
( ch = '$ ) => LOOP; -- ignore
ENDCASE => dfName ¬ arg -- translations or other things
ENDLOOP;
IF outFileName # NIL THEN {
outPath: PFS.PATH ~ PFS.PathFromRope[outFileName];
outStream: STREAM;
outStream ¬ PFS.StreamOpen[outPath, IF switches['a] THEN $append ELSE $create ! PFS.Error => {
out.PutRope[error.explanation]; CONTINUE} ];
IF (isFile ¬ ( outStream # NIL )) THEN {
out.PutF1["Messages will be written on %g\n", [rope[outFileName]] ];
out ¬ outStream;
};
};
DoIt[dfName, table, out, switches['v] , isFile
! PFS.Error => {
out.PutF["-- PFS.Error[%g] - quitting.\n\t\t(at %g)\n\n", [rope[error.explanation]], [time[BasicTime.Now[]]] ];
CONTINUE;
};
];
IF outFileName # NIL THEN out.Close[];
EXITS
failed => {result ¬ $Failure};
};
Path: PROC[pt: PFS.PATH] RETURNS[rp: ROPE] ~ { RETURN[PFS.RopeFromPath[pt] ] };
Initialization
docRope: ROPE ~ "CompareDFwithServer {-o|-a fileName} { -v } dfName";
Commander.Register[ key: "CompareDFwithServer", proc: CompareDFwithServer, doc: docRope];
END.