TarTrickleImpl.mesa
Copyright Ó 1990 by Xerox Corporation. All rights reserved.
Bill Jackson (bj), May 21, 1990 5:54 pm PDT
DIRECTORY
BasicTime USING [GMT, nullGMT],
DFUtilities USING [DirectoryItem, FileItem, Filter, ImportsItem, IncludeItem, ParseFromStream, ProcessItemProc, UsingList],
IO USING [Close, PutChar, PutF, PutF1, PutFR, STREAM],
PFS USING [EnumerateForInfo, Error, FileInfo, FileType, InfoProc, PathFromRope, RopeFromPath, StreamOpen, tDirectory, UniqueID],
PFSNames USING [PATH, SetVersionNumber, ShortName, Version],
PFSPrefixMap USING [Translate],
Process USING [CheckForAbort],
RedBlackTree USING [Compare, Create, Delete, GetKey, Insert, Lookup, Table],
RefText USING [AppendChar, AppendRope, InlineAppendChar, line, ObtainScratch, ReleaseScratch],
Rope USING [Compare, Concat, Find, IsPrefix, ROPE, Substr, Translate, TranslatorType],
TarTrickle USING [FileEntry, FileEntryRep, TCInfo];
TarTrickleImpl: CEDAR MONITOR
IMPORTS DFUtilities, IO, PFS, PFSNames, PFSPrefixMap, Process, RedBlackTree, Rope
EXPORTS TarTrickle ~ {
OPEN TarTrickle;
ROPE: TYPE ~ Rope.ROPE;
Various
maxRetries: NAT ← 10; -- # of times to retry connectionRejected from STP
retrySeconds: NAT ← 20; -- # of seconds between retry attempts
caseFileNamePrefix: ROPE ~ ".~case~"; -- SunNFSRemoteFile.caseFileNamePrefix
Symbol Table (case insensitive)
GetKey: PUBLIC RedBlackTree.GetKey ~ {
RETURN [data];
};
Compare: PUBLIC RedBlackTree.Compare ~ {
key: ROPEWITH k SELECT FROM
ent: FileEntry => ent.name,
rope: ROPE => rope,
ENDCASE => ERROR;
WITH data SELECT FROM
ent: FileEntry => RETURN [key.Compare[ent.name, FALSE]];
ENDCASE => ERROR;
};
Utility Routines
BumpCounter: PUBLIC PROC [out: IO.STREAM, num: INT] RETURNS [res: INT] ~ {
increment count, printing a dot every ten, and the number every 100
res ← num.SUCC;
SELECT TRUE FROM
( res MOD 10 # 0 ) => { NULL };
( res MOD 100 # 0 ) => { out.PutChar['.] };
ENDCASE => { out.PutF["(%g) ", [integer[res]] ] };
};
Pathname Routines
stolen from RunCommandsImpl, -bj, May 15, 1990
ForceLower: Rope.TranslatorType ~
{ RETURN[IF old IN ['A..'Z] THEN old+('a-'A) ELSE old] };
UXNameFromPath: PROC [path: PFSNames.PATH] RETURNS [string: ROPE] ~ {
path should be a fullFName, with numeric version iff it is VUX.
This is a crock until we get the real goods out of PFS.
translated: PFSNames.PATH ~ PFSPrefixMap.Translate[path];
version: PFSNames.Version ~ PFSNames.ShortName[translated].version;
sans: PFSNames.PATH ~ PFSNames.SetVersionNumber[translated, [none]];
name: ROPEPFS.RopeFromPath[sans, slashes];
colon: INT ~ name.Find[":"];
IF ( colon >= 0 ) THEN name ← name.Substr[colon.SUCC];
IF ( version.versionKind = numeric ) THEN {
Assume VUX for now.
xx: ROPE ~ Rope.Translate[base: name, translator: ForceLower];
name ← IO.PutFR["%g.~%g~", [rope[xx]], [cardinal[version.version]]];
};
string ← name;
};
ContainedIn: PROC [tcInfo: TCInfo, pathname: ROPE, out: IO.STREAM]
RETURNS [yes: BOOLTRUE] ~ {
srcPrefix: ROPE ~ tcInfo.arg; -- bug!
fullFName: ROPE ~ PFS.RopeFromPath[path];
yes ← srcPrefix.IsPrefix[pathname, FALSE];
IF ( NOT yes ) THEN {
out.PutF["\t***%g is not a prefix of %g - ignoring\n",
[rope[srcPrefix]], [rope[pathname]] ];
};
};
NameFromDF: PROC [tcInfo: TCInfo, name: ROPE, dirClause: ROPENIL]
RETURNS
[pathname: ROPE] ~ {
pathname ← dirClause.Concat[name];
};
PFS Enumeration Routines
InfoProc: TYPE = PROC [pathname: ROPE, uniqueID: PFS.UniqueID, version: PFSNames.Version, bytes: INT, fileType: PFS.FileType] RETURNS [continue: BOOLTRUE];
EnumerateForInfo: PROC [pattern: ROPE, proc: InfoProc] ~ {
pathy: PFSNames.PATH ~ PFS.PathFromRope[pattern];
Translator: PFS.InfoProc ~ {
pathname: ROPE ~ PFS.RopeFromPath[fullFName];
dir: PFSNames.PATH ~ PFSNames.Directory[fullFName];
base: ROPE ~ PFSNames.ComponentRope[PFSNames.ShortName[fullFName]];
version: PFSNames.Version ~ PFSNames.ShortName[fullFName].version;
continue ← proc[pathname, uniqueID, version, bytes, fileType];
};
PFS.EnumerateForInfo[pathy, Translator];
};
PFS FileInfo/StreamOpen Routines
VersionInfo: PROC [tcInfo: TCInfo, pathname: ROPE, date: BasicTime.GMT, out: IO.STREAM]
RETURNS [created: BasicTime.GMT, bytes: INT, version: PFSNames.Version] ~ {
ENABLE PFS.Error => {
SELECT TRUE FROM
( error.code = $unknownFile ) => { NULL };
( error.code = $unknownCreatedTime ) => { NULL };
( error.code = $unknownServer ) --AND ( bestEfforts )-- => { NULL };
ENDCASE => { REJECT };
out.PutF1["\tFS.Error[%g]\n", [rope[error.explanation]]];
GOTO Failed;
};
pattern: PFSNames.PATH ~ PFS.PathFromRope[pathname];
wantedUniqueID: PFS.UniqueID ~ [egmt: [time: date]];
fullFName: PFSNames.PATH; uid: PFS.UniqueID;
[fullFName: fullFName, uniqueID: uid, bytes: bytes] ← PFS.FileInfo[pattern, wantedUniqueID];
full: ROPEPFS.RopeFromPath[fullFName];
created ← uid.egmt.time;
version ← PFSNames.ShortName[fullFName].version;
EXITS
Failed => { created ← BasicTime.nullGMT; bytes ← 0; version ← [none] };
};
OpenDF: PROC [tcInfo: TCInfo, pathname: ROPE, date: BasicTime.GMT, out: IO.STREAM]
RETURNS [stream: IO.STREAMNIL] ~ {
ENABLE PFS.Error => {
SELECT TRUE FROM
( error.code = $unknownServer ) --AND ( bestEfforts )-- => { NULL };
ENDCASE => { REJECT };
out.PutF1["PFS.Error[%g] - best efforts requested so continuing\n", [rope[error.explanation]]];
GOTO Failed;
};
xPath: PFSNames.PATH ~ PFS.PathFromRope[pathname];
uid: PFS.UniqueID ~ [egmt: [time: date]];
stream ← PFS.StreamOpen[fileName: xPath, wantedUniqueID: uid];
EXITS
Failed => { stream ← NIL };
};
Traversal Logic
VisitorProc: TYPE ~ PROC [tcInfo: TCInfo, pathname: ROPE, date: BasicTime.GMT];
EnumerateDFs: PUBLIC PROC [pattern: ROPE, tcInfo: TCInfo, out: IO.STREAM, test: BOOL]
RETURNS [table: RedBlackTree.Table, filesSeen: INT ← 0] ~ {
NoteFileSeen: PROC [bytes: INT] ~ INLINE {
filesSeen ← BumpCounter[out, filesSeen];
bytesSeen ← bytesSeen + bytes;
};
VisitEntry: VisitorProc ~ {
This procedure is used to visit each file in a simple DF closure,
where the imports are NOT followed, but the inclusions ARE followed.
Process.CheckForAbort[]; -- let's be nice!
WITH table.Lookup[pathname] SELECT FROM
entry: FileEntry => IF ( entry.date = date ) THEN RETURN;
ENDCASE => NULL;
{
version: PFSNames.Version; bytes: INT;
[date, bytes, version] ← VersionInfo[tcInfo, pathname, date, out];
IF ( date = BasicTime.nullGMT ) THEN {
IF ( test ) THEN {
out.PutF["In VisitEntry, pathname: %g,\n", [rope[pathname]] ];
};
RETURN;
};
WITH table.Lookup[pathname] SELECT FROM
entry: FileEntry => {
IF ( entry.date = date ) THEN RETURN;
[] ← table.Delete[pathname]; -- we're going to replace (insert) it!
perhaps we could save the carcass here? or perhaps we lost something?
};
ENDCASE => NULL;
{
version: PFSNames.Version ~ [none];
entry: FileEntry ~ NEW[FileEntryRep ← [name: pathname, date: date, version: version, len: bytes, fileType: 0, state: init] ];
table.Insert[entry, pathname];
NoteFileSeen[bytes];
IF ( test ) THEN {
entry.state ← moved;
out.PutF["\n**entry: [name: %g]**\n", [rope[entry.name]] ];
out.PutF["\n**entry: [name: %g,\n\t dir: %g,\n\t short: %g]**\n",
[rope[entry.name]], [rope[entry.dir]], [rope[entry.short]] ];
};
};
};
};
VisitClosure: PROC [dfName: ROPE, visitor: VisitorProc, date: BasicTime.GMT ← BasicTime.nullGMT] ~ {
dirClause: ROPENIL; -- the sticky value for file
EachItem: DFUtilities.ProcessItemProc ~ {
WITH item SELECT FROM
dir: REF DFUtilities.DirectoryItem => {
dirClause ← dir.path1;
};
file: REF DFUtilities.FileItem => {
pathname: ROPE ~ NameFromDF[tcInfo, file.name, dirClause];
visitor[tcInfo, pathname, file.date.gmt];
};
incl: REF DFUtilities.IncludeItem => {
pathname: ROPE ~ NameFromDF[tcInfo, incl.path1];
VisitClosure[pathname, visitor, incl.date.gmt];
visitor[tcInfo, pathname, incl.date.gmt];
just in case the df doesn't contain itself!
};
imports: REF DFUtilities.ImportsItem => { -- this stuff is for me - (bj)
};
ENDCASE => { i: INT ← 0; }; -- handy for setting breakpoints - (bj)
};
in: IO.STREAM ~ OpenDF[tcInfo, dfName, date, out];
IF ( in # NIL ) THEN {
filter: DFUtilities.Filter ← [FALSE, all, all, defining];
filter.list ← usingList;
DFUtilities.ParseFromStream[in, EachItem, filter ! UNWIND => in.Close[]];
filter is now longer a global variable! - (bj)
in.Close[]
};
};
EachDfFile: InfoProc ~ {
Process.CheckForAbort[];
IF ( NOT ContainedIn[tcInfo, pathname, out] ) THEN RETURN;
IF ( test ) THEN out.PutF["VisitClosure of %g\n", [rope[pathname]] ];
VisitClosure[pathname, VisitEntry];
};
table ← RedBlackTree.Create[GetKey, Compare];
EnumerateForInfo[pattern, EachDfFile];
};
EnumerateFiles: PUBLIC PROC [pattern: ROPE, out: IO.STREAM]
RETURNS
[table: RedBlackTree.Table, filesSeen: INT ← 0, bytesSeen: INT ← 0] ~ {
NoteFileSeen: PROC [bytes: INT] ~ INLINE {
filesSeen ← BumpCounter[out, filesSeen];
bytesSeen ← bytesSeen + bytes;
};
children: LIST OF ROPELIST[pattern];
EachFile: InfoProc ~ {
created: BasicTime.GMT ~ uniqueID.egmt.time;
Process.CheckForAbort[];
IF ( NOT ContainedIn[tcInfo, pathname, out] ) THEN RETURN;
WITH table.Lookup[pathname] SELECT FROM
entry: FileEntry => {
IF ( entry.date = created ) THEN RETURN;
[] ← table.Delete[pathname]; -- we're going to replace (insert) it!
perhaps we could save the carcass here? or perhaps we lost something?
};
ENDCASE => NULL;
{
entry: FileEntry ~ NEW[FileEntryRep ← [name: pathname, date: created, version: version, len: bytes, fileType: fileType, state: init] ];
table.Insert[entry, pathname];
NoteFileSeen[bytes];
};
IF ( fileType = PFS.tDirectory ) THEN { -- cut recursion?
pattern: ROPE ~ pathname.Concat["/*!H"];
children ← CONS[pattern, children];
out.PutF1[">>> dir: %g <<<\n", IO.rope[pathname] ];
};
};
table ← RedBlackTree.Create[GetKey, Compare];
WHILE ( children # NIL ) DO -- depth search!
thisLevel: LIST OF ROPE ~ children; children ← NIL;
FOR tail: LIST OF ROPE ← thisLevel, tail.rest WHILE ( tail # NIL ) DO
dir: ROPE ~ tail.first;
out.PutF1[">>> dir: %g <<<\n", IO.rope[dir] ];
EnumerateForInfo[dir, EachFile];
ENDLOOP;
ENDLOOP;
};
ExpandInfo: PUBLIC PROC [table: RedBlackTree.Table, out: IO.STREAM]
RETURNS [bytesSeen: INT ← 0] ~ {
};
}.