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:
ROPE ←
WITH 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: ROPE ← PFS.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:
BOOL ←
TRUE] ~ {
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:
ROPE ←
NIL]
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: BOOL ← TRUE];
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: ROPE ← PFS.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.
STREAM ←
NIL] ~ {
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: ROPE ← NIL; -- 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 ROPE ← LIST[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] ~ {
};
}.