FileSetsImpl.Mesa
Copyright © 1984 by Xerox Corporation. All rights reserved.
Last Edited by: Spreitzer, April 7, 1986 5:18:53 pm PST
DIRECTORY Basics, BasicTime, BcdDefs, CommandTool, Convert, DFUtilities, FileSets, FileSetsPrivate, FS, FSPseudoServers, FSRemoteFile, IO, Process, RedBlackTree, Rope, STP, STPConnectionCache, Tempus, TextFind, TextReplace, VersionMap, ViewerClasses, ViewerTools;
FileSetsImpl: CEDAR PROGRAM
IMPORTS BasicTime, CommandTool, Convert, DFUtilities, FS, FSPseudoServers, FSRemoteFile, IO, Process, RedBlackTree, Rope, STP, STPConnectionCache, Tempus, TextFind, TextReplace, VersionMap
EXPORTS FileSets, FileSetsPrivate
={OPEN FileSets, FileSetsPrivate;
Basics:
Error: PUBLIC ERROR [message: ROPE] = CODE;
For syntax errors and such.
Warning: PUBLIC SIGNAL [message: ROPE] = CODE;
For syntax errors and such.
Miss: PUBLIC SIGNAL [name: ROPE, created: GMT] = CODE;
Informational signal: Couldn't identify.
FileNote Operations:
GetCreated: PUBLIC PROC [fn: FileNote] RETURNS [created: GMT] = {
IF (created ← fn.created) # noGMT THEN RETURN;
IF NOT fn.createTried THEN {
ok: BOOLTRUE;
ct: GMT ← noGMT;
fn.createTried ← TRUE;
[created: ct] ← FS.FileInfo[name: fn.id.name, remoteCheck: FALSE !FS.Error => {ok ← FALSE; CONTINUE}];
IF ok THEN fn.created ← created ← ct};
};
GetAccounting: PUBLIC PROC [fn: FileNote] RETURNS [read, write: GMT, author: ROPE] = {
h: STPConnectionCache.Handle;
full, hostName, rest: ROPE;
cp: FS.ComponentPositions;
IF fn.readTried THEN RETURN [fn.read, fn.write, fn.author];
fn.readTried ← TRUE;
[full, cp] ← FS.ExpandName[fn.fsName];
hostName ← full.Substr[start: cp.server.start, len: cp.server.length];
IF hostName.Length[] = 0 THEN {--try for a remote attachment--
attachedTo: ROPE;
[attachedTo: attachedTo] ← FS.FileInfo[full];
IF attachedTo # NIL THEN {
[full, cp] ← FS.ExpandName[attachedTo];
hostName ← full.Substr[start: cp.server.start, len: cp.server.length];
};
};
rest ← full.Substr[start: cp.dir.start-1];
IF rest.Fetch[0] # '< THEN ERROR;
IF hostName.Length[] # 0 THEN {
servers: LOR ← FSPseudoServers.TranslateForRead[hostName];
found: BOOLFALSE;
FOR servers ← servers, servers.rest WHILE servers # NIL AND NOT found DO
server: ROPE ← servers.first;
h ← STPConnectionCache.Get[server, "FileSetsImpl.GetAccounting" !STP.Error => IF code IN STP.ConnectionErrors THEN LOOP];
{ENABLE UNWIND => h.Release[stpTimeout];
PerMatch: PROC [file: ROPE] RETURNS [continue: STP.Continue] = {
fi: STP.FileInfo ← h.stpHandle.GetFileInfo[];
fn.read ← FSRemoteFile.FTPTimeToGMT[fi.read];
fn.write ← FSRemoteFile.FTPTimeToGMT[fi.write];
fn.author ← fi.author;
found ← TRUE;
RETURN [yes]};
h.stpHandle.Enumerate[rest, PerMatch !
STP.Error => IF code = noSuchFile THEN CONTINUE];
};
h.Release[stpTimeout];
ENDLOOP;
found ← found;
};
RETURN [fn.read, fn.write, fn.author];
};
stpTimeout: INT--seconds-- ← 15;
FileSet Operations:
NewFileSet: PUBLIC PROC [ids: IdentificationScheme ← []] RETURNS [fs: FileSet] = {
fs ← NEW [FileSetRep ← [
elts: RedBlackTree.Create[Id, IF ids.askFS OR ids.create THEN CompareFullFileNotes ELSE CompareFileNotesByName],
ids: ids,
summary: NIL]];
};
Id: PROC [data: REF ANY] RETURNS [key: REF ANY] --RedBlackTree.GetKey-- =
{key ← data};
CompareFileNotesByName: PROC [k, data: REF ANY] RETURNS [c: Basics.Comparison] --RedBlackTree.Compare-- = {
fn1: FileNote ← NARROW[k];
fn2: FileNote ← NARROW[data];
c ← fn1.id.name.Compare[fn2.id.name, FALSE]};
CompareFullFileNotes: PROC [k, data: REF ANY] RETURNS [c: Basics.Comparison] --RedBlackTree.Compare-- = {
fn1: FileNote ← NARROW[k];
fn2: FileNote ← NARROW[data];
IF (c ← fn1.id.name.Compare[fn2.id.name, FALSE]) = equal THEN {
c ← SELECT BasicTime.Period[from: fn1.id.created, to: fn2.id.created] FROM
<0 => less,
=0 => equal,
>0 => greater,
ENDCASE => ERROR;
};
};
EnumSet: PUBLIC PROC [fs: FileSet, to: FileConsumer] = {
FOR fn: FileNote ← NARROW[fs.elts.LookupSmallest[]], NARROW[fs.elts.LookupNextLarger[fn]] WHILE fn # NIL DO
Process.CheckForAbort[];
to[fn];
ENDLOOP;
fs ← fs};
First: PUBLIC PROC [fs: FileSet] RETURNS [fn: FileNote] =
{fn ← NARROW[fs.elts.LookupSmallest[]]};
Next: PUBLIC PROC [fs: FileSet, fn: FileNote] RETURNS [next: FileNote] =
{next ← NARROW[fs.elts.LookupNextLarger[fn]]};
Lookup: PUBLIC PROC [fs: FileSet, fn: FileNote] RETURNS [found: FileNote] =
{found ← NARROW[fs.elts.Lookup[fn]]};
Delete: PUBLIC PROC [fs: FileSet, fn: FileNote] RETURNS [found: FileNote] = {
node: RedBlackTree.Node ← fs.elts.Delete[fn];
found ← IF node # NIL THEN NARROW[node.data] ELSE NIL;
};
Insert: PUBLIC PROC [fs: FileSet, fn: FileNote] RETURNS [news: BOOL] = {
prevFN: FileNote ← Lookup[fs, fn];
SELECT (news ← prevFN = NIL) FROM
TRUE => fs.elts.Insert[fn, fn];
FALSE => NULL;
ENDCASE => ERROR;
};
Size: PUBLIC PROC [fs: FileSet] RETURNS [numberOfFiles: INT] =
{numberOfFiles ← fs.elts.Size[]};
MapNames: PUBLIC PROC [fs: FileSet, map: RopeMap, mapIDName, mapFSName: BOOLTRUE] RETURNS [mfs: FileSet] = {
TranslateFile: PROC [fn: FileNote] = {
newFN: FileNote ← NEW [FileNoteRep ← fn^];
IF mapIDName THEN newFN.id.name ← MapName[newFN.id.name, mfs.ids];
IF mapFSName THEN newFN.fsName ← MapName[newFN.fsName, fullID];
IF NOT Insert[mfs, newFN] THEN ERROR;
};
MapName: PROC [pn: ROPE, ids: IdentificationScheme] RETURNS [rn: ROPE] = {
full: ROPE;
cp: FS.ComponentPositions;
rn ← map.Apply[pn];
[full, cp] ← FS.ExpandName[rn];
rn ← TrimNameByIDS[full, cp, ids];
};
mfs ← NewFileSet[fs.ids];
EnumSet[fs, TranslateFile];
mfs.summary ← Convert.RopeFromInt[Size[mfs]].Cat[":(MapNames ", fs.summary, ")"];
};
fullID: IdentificationScheme = [server: TRUE, directory: TRUE, version: TRUE, create: TRUE, askFS: FALSE];
ReIdentify: PUBLIC PROC [fs: FileSet, ids: IdentificationScheme ← []] RETURNS [mfs: FileSet] = {
Mapit: PROC [fn: FileNote] = {
newFN: FileNote = CreateNote[fn.id.name, fn.id.created, ids];
IF newFN # NIL THEN [] ← Insert[mfs, newFN];
};
mfs ← NewFileSet[ids];
EnumSet[fs, Mapit];
mfs.summary ← Convert.RopeFromInt[Size[mfs]].Cat[":(ReIdentify ", fs.summary, ")"];
};
MapNameAndLookup: PUBLIC PROC [fs: FileSet, map: RopeMap, ids: IdentificationScheme ← []] RETURNS [mfs: FileSet] = {
TranslateFile: PROC [fn: FileNote] = {
newName: ROPE = map.Apply[fn.id.name];
newFN: FileNote = CreateNote[newName, noGMT, ids];
IF newFN # NIL THEN [] ← Insert[mfs, newFN];
};
mfs ← NewFileSet[ids];
EnumSet[fs, TranslateFile];
mfs.summary ← Convert.RopeFromInt[Size[mfs]].Cat[":(MapNameAndLookup ", fs.summary, ")"];
};
pseudoServerToRead: PUBLIC RopeMap ← NEW [TextReplace.RopeMapRep ← [
MapPseudoServerToRead]];
MapPseudoServerToRead: PROC [data: REF ANY, pn: ROPE] RETURNS [rn: ROPE] = {
full: ROPE;
cp: FS.ComponentPositions;
IF pn.Length[] = 0 THEN RETURN [pn];
[full, cp] ← FS.ExpandName[pn];
rn ← FS.ConstructFName[[
server: FSPseudoServers.TranslateForRead[full.Substr[cp.server.start, cp.server.length]].first,
dir: full.Substr[cp.dir.start, cp.dir.length],
subDirs: full.Substr[cp.subDirs.start, cp.subDirs.length],
base: full.Substr[cp.base.start, cp.base.length],
ext: full.Substr[cp.ext.start, cp.ext.length],
ver: full.Substr[cp.ver.start, cp.ver.length]
]];
};
Direction: TYPE = {normal, reverse};
HasRelative: PROC [fn: FileNote, fs: FileSet, fr: FileRelation, direction: Direction] RETURNS [has: BOOL] = {
NoteRelative: PROC [rfn: FileNote] = {has ← TRUE; Enuf};
has ← FALSE;
EnumerateRelatives[fn, fs, fr, direction, NoteRelative !Enuf => CONTINUE];
has ← has;
};
Enuf: ERROR = CODE;
EnumerateRelatives: PROC [fn: FileNote, fs: FileSet, fr: FileRelation, direction: Direction, NoteRelative: PROC [FileNote]] = {
fir: FileIDRange = SELECT direction FROM
normal => fr.LeftToRight[fr.data, fn.id],
reverse => fr.RightToLeft[fr.data, fn.id],
ENDCASE => ERROR;
Relates: PROC [fn2: FileNote] RETURNS [b: BOOL] = {
b ← SELECT direction FROM
normal => fr.Test[fr.data, fn.id, fn2.id],
reverse => fr.Test[fr.data, fn2.id, fn.id],
ENDCASE => ERROR;
};
test: FileNote = NEW [FileNoteRep ← [
id: LowEnd[fir]
]];
highID: FileID = HighEnd[fir];
found: FileNote ← NARROW[fs.elts.Lookup[test]];
IF found # NIL AND Relates[found] THEN NoteRelative[found];
FOR found ← NARROW[fs.elts.LookupNextLarger[test]], NARROW[fs.elts.LookupNextLarger[found]] WHILE found # NIL AND LessOrEqual[found.id, highID] DO
IF Relates[found] THEN NoteRelative[found];
ENDLOOP;
found ← found;
};
LowEnd: PROC [fir: FileIDRange] RETURNS [fid: FileID] = {
fid ← [
name: fir.lowName,
created: IF fir.earlyCreated = noGMT THEN BasicTime.earliestGMT ELSE fir.earlyCreated
];
};
HighEnd: PROC [fir: FileIDRange] RETURNS [fid: FileID] = {
fid ← [
name: fir.highName,
created: IF fir.lateCreated = noGMT THEN BasicTime.latestGMT ELSE fir.lateCreated
];
};
LessOrEqual: PROC [fid1, fid2: FileID] RETURNS [leq: BOOL] = {
leq ← SELECT fid1.name.Compare[fid2.name, FALSE] FROM
less => TRUE,
equal => BasicTime.Period[from: fid1.created, to: fid2.created] <= 0,
greater => FALSE,
ENDCASE => ERROR;
};
OpWork: PROC [fsl: FileSetList, head: ROPE, combine: PROC [sofar, this: FileSet] RETURNS [sum: FileSet]] RETURNS [fs: FileSet] = {
summary: ROPE ← head;
fs ← NIL;
IF fsl = NIL THEN ERROR Error["NIL FileSetList given --- that's a no-no"];
FOR fsl ← fsl, fsl.rest WHILE fsl # NIL DO
IF fs # NIL AND fs.ids # fsl.first.ids THEN ERROR;
fs ← combine[fs, fsl.first];
summary ← summary.Cat[" ", Convert.RopeFromInt[Size[fsl.first]]];
ENDLOOP;
IF fs = NIL THEN ERROR;
fs.summary ← Convert.RopeFromInt[Size[fs]].Cat[":", summary, ")"];
};
Union: PUBLIC PROC [fsl: FileSetList, fr: FileRelation ← NIL] RETURNS [fs: FileSet] = {
UnionCombine: PROC [sofar, this: FileSet] RETURNS [sum: FileSet] = {
Add: PROC [fn: FileNote] --FileConsumer-- = {[] ← Insert[sum, fn]};
AddIfNotRelated: PROC [fn: FileNote] --FileConsumer-- = {
related: BOOL = HasRelative[fn, sum, fr, reverse];
IF NOT related THEN [] ← Insert[sum, fn];
};
sum ← sofar;
IF sum = NIL THEN sum ← NewFileSet[this.ids];
EnumSet[this, IF fr = NIL THEN Add ELSE AddIfNotRelated];
};
fs ← OpWork[fsl, "(union", UnionCombine];
};
Difference: PUBLIC PROC [fsl: FileSetList, fr: FileRelation ← NIL] RETURNS [fs: FileSet] = {
DiffCombine: PROC [sofar, this: FileSet] RETURNS [sum: FileSet] = {
IF sofar = NIL
THEN {
Add: PROC [fn: FileNote] --FileConsumer-- = {[] ← Insert[sum, fn]};
sum ← NewFileSet[this.ids];
EnumSet[this, Add];
}
ELSE {
Subtract: PROC [fn: FileNote] --FileConsumer-- = {[] ← Delete[sum, fn]};
SubtractRelatives: PROC [fn: FileNote] --FileConsumer-- = {
EnumerateRelatives[fn, sum, fr, reverse, Subtract];
};
sum ← sofar;
EnumSet[this, IF fr = NIL THEN Subtract ELSE SubtractRelatives];
};
};
fs ← OpWork[fsl, "(diff", DiffCombine];
};
SymmetricDifference: PUBLIC PROC [fsl: FileSetList, fr: FileRelation ← NIL] RETURNS [fs: FileSet] = {
SDiffCombine: PROC [sofar, this: FileSet] RETURNS [sum: FileSet] = {
IF sofar = NIL
THEN {
Add: PROC [fn: FileNote] --FileConsumer-- = {[] ← Insert[sum, fn]};
sum ← NewFileSet[this.ids];
EnumSet[this, Add];
}
ELSE {
SimpleSDiffit: PROC [fn: FileNote] --FileConsumer-- = {
SELECT Lookup[sum, fn] FROM
=NIL => [] ← Insert[sum, fn];
#NIL => [] ← Delete[sum, fn];
ENDCASE => ERROR;
};
GeneralSDiffit: PROC [fn: FileNote] --FileConsumer-- = {
related: BOOLFALSE;
SeeLeftee: PROC [fn: FileNote] --FileConsumer-- = {
[] ← Delete[sum, fn];
related ← TRUE;
};
EnumerateRelatives[fn, sum, fr, reverse, SeeLeftee];
IF NOT related THEN [] ← Insert[sum, fn];
};
sum ← sofar;
EnumSet[this, IF fr = NIL THEN SimpleSDiffit ELSE GeneralSDiffit];
};
};
fs ← OpWork[fsl, "(sdiff", SDiffCombine];
};
Intersection: PUBLIC PROC [fsl: FileSetList, fr: FileRelation ← NIL] RETURNS [fs: FileSet] = {
IntersectionCombine: PROC [sofar, this: FileSet] RETURNS [sum: FileSet] = {
IF sofar = NIL
THEN {
Add: PROC [fn: FileNote] --FileConsumer-- = {[] ← Insert[sum, fn]};
sum ← NewFileSet[this.ids];
EnumSet[this, Add];
}
ELSE {
SimpleIntersect: PROC [fn: FileNote] --FileConsumer-- = {
IF Lookup[this, fn] = NIL THEN [] ← Delete[sum, fn];
};
GeneralIntersect: PROC [fn: FileNote] --FileConsumer-- = {
IF NOT HasRelative[fn, this, fr, normal] THEN [] ← Delete[sum, fn];
};
sum ← sofar;
EnumSet[sum, IF fr = NIL THEN SimpleIntersect ELSE GeneralIntersect];
};
};
fs ← OpWork[fsl, "(intersect", IntersectionCombine];
};
equality: PUBLIC FileRelation ← NEW [FileRelationPrivate ← [
TestEquality, PointRange, PointRange]];
TestEquality: PROC [data: REF ANY, left, right: FileID] RETURNS [b: BOOL] = {
b ←
left.name.Equal[right.name, FALSE]
AND left.created = right.created;
};
PointRange: PROC [data: REF ANY, fid: FileID] RETURNS [fir: FileIDRange] = {
fir ← [
lowName: fid.name,
highName: fid.name,
earlyCreated: fid.created,
lateCreated: fid.created
];
};
ReverseFileRelation: PUBLIC PROC [fr: FileRelation] RETURNS [frr: FileRelation] = {
frr ← NEW [FileRelationPrivate ← [
TestReverse, ReverseLeftToRight, ReverseRightToLeft, fr]];
};
TestReverse: PROC [data: REF ANY, left, right: FileID] RETURNS [b: BOOL] = {
fr: FileRelation = NARROW[data];
b ← fr.Test[fr.data, right, left];
};
ReverseLeftToRight: PROC [data: REF ANY, fid: FileID] RETURNS [fir: FileIDRange] = {
fr: FileRelation = NARROW[data];
fir ← fr.RightToLeft[fr.data, fid];
};
ReverseRightToLeft: PROC [data: REF ANY, fid: FileID] RETURNS [fir: FileIDRange] = {
fr: FileRelation = NARROW[data];
fir ← fr.LeftToRight[fr.data, fid];
};
FilterFileSet: PUBLIC PROC [subject: FileSet, filter: Filter] RETURNS [filtered: FileSet] = {
DoFilter: PROC [fn: FileNote] =
{IF filter.Eval[fn, filter.data] THEN [] ← Insert[filtered, fn]};
filtered ← NewFileSet[subject.ids];
EnumSet[subject, DoFilter];
filtered.summary ← Convert.RopeFromInt[Size[filtered]].Cat[":(filter ", subject.summary, ")"];
};
And: PUBLIC PROC [fl: FilterList] RETURNS [f: Filter] = {
f ← NEW [FilterRep ← [AndFilters, fl]];
};
AndFilters: PROC [fn: FileNote, data: REF ANY] RETURNS [BOOL] = {
fl: FilterList ← NARROW[data];
FOR fl ← fl, fl.rest WHILE fl # NIL DO
IF NOT fl.first.Eval[fn, fl.first.data] THEN RETURN [FALSE];
ENDLOOP;
RETURN [TRUE];
};
Or: PUBLIC PROC [fl: FilterList] RETURNS [f: Filter] = {
f ← NEW [FilterRep ← [OrFilters, fl]];
};
OrFilters: PROC [fn: FileNote, data: REF ANY] RETURNS [BOOL] = {
fl: FilterList ← NARROW[data];
FOR fl ← fl, fl.rest WHILE fl # NIL DO
IF fl.first.Eval[fn, fl.first.data] THEN RETURN [TRUE];
ENDLOOP;
RETURN [FALSE];
};
Not: PUBLIC PROC [f: Filter] RETURNS [nf: Filter] = {
nf ← NEW [FilterRep ← [NegateFilter, f]]};
NegateFilter: PROC [fn: FileNote, data: REF ANY] RETURNS [BOOL] = {
f: Filter ← NARROW[data];
RETURN [NOT f.Eval[fn, f.data]];
};
IfCreated: PUBLIC PROC [reln: TimeReln, when: GMT] RETURNS [f: Filter] = {
f ← NEW [FilterRep ← [TestTime, NEW [TimeFilterRep ← [created, reln, when]]]];
};
IfRead: PUBLIC PROC [reln: TimeReln, when: GMT] RETURNS [f: Filter] = {
f ← NEW [FilterRep ← [TestTime, NEW [TimeFilterRep ← [read, reln, when]]]];
};
TimeFilter: TYPE = REF TimeFilterRep;
TimeFilterRep: TYPE = RECORD [
which: WhichTime,
reln: TimeReln,
when: GMT];
WhichTime: TYPE = {created, read};
TestTime: PROC [fn: FileNote, data: REF ANY] RETURNS [BOOL] = {
tf: TimeFilter ← NARROW[data];
time: GMTSELECT tf.which FROM
read => GetAccounting[fn].read,
created => GetCreated[fn],
ENDCASE => ERROR;
IF time = noGMT THEN RETURN [FALSE];
RETURN [BasicTime.Period[from: tf.when, to: time] * (SELECT tf.reln FROM before => -1, after => 1, ENDCASE => ERROR) > 0];
};
NameMatches: PUBLIC PROC [pattern: ROPE, literal, word, ignoreCase, addBounds: BOOLFALSE] RETURNS [f: Filter] = {
f ← NEW [FilterRep ← [TestName, TextFind.CreateFromRope[pattern: pattern, literal: literal, word: word, ignoreCase: ignoreCase, addBounds: addBounds] ]];
};
TestName: PROC [fn: FileNote, data: REF ANY] RETURNS [BOOL] = {
finder: TextFind.Finder ← NARROW[data];
RETURN [TextFind.SearchRope[finder, fn.fsName].found];
};
isOnline: PUBLIC Filter ← NEW [FilterRep ← [TestOnline, NIL]];
TestOnline: PROC [fn: FileNote, data: REF ANY] RETURNS [BOOL] = {
exists: BOOLTRUE;
[] ← FS.FileInfo[name: fn.fsName, wantedCreatedTime: GetCreated[fn] !FS.Error => {exists ← FALSE; CONTINUE}];
RETURN [exists];
};
DFContentsFilter: TYPE = REF DFContentsFilterRec;
DFContentsFilterRec: TYPE = RECORD [
filter: Filter,
dfFilter: DFUtilities.Filter,
allowableExceptions: NAT,
ids: IdentificationScheme
];
ContentsPass: PUBLIC PROC [filter: Filter, dfFilter: DFUtilities.Filter, allowableExceptions: NAT ← 0, ids: IdentificationScheme] RETURNS [f: Filter--applicable only to DF files--] = {
f ← NEW [FilterRep ← [TestDFContents, NEW [DFContentsFilterRec ← [filter, dfFilter, allowableExceptions]] ]];
};
TestDFContents: PROC [fn: FileNote, data: REF ANY] RETURNS [BOOL] = {
dfcf: DFContentsFilter ← NARROW[data];
exceptions: NAT ← 0;
TestContent: PROC [fn: FileNote] = {
IF NOT dfcf.filter.Eval[fn, dfcf.filter.data] THEN exceptions ← exceptions + 1;
};
created: GMT ← GetCreated[fn];
EnumDF[
dfName: fn.fsName,
date: IF created # noGMT THEN [explicit, created] ELSE [],
to: TestContent,
filter: dfcf.dfFilter,
which: remote,
ids: dfcf.ids];
RETURN [exceptions <= dfcf.allowableExceptions];
};
Primitive Enumerations:
CreateNote: PUBLIC PROC [fileName: ROPE, created: GMT, ids: IdentificationScheme] RETURNS [fn: FileNote] = {
createTried: BOOLFALSE;
fsName: ROPENIL;
realCreated: GMT ← created;
SELECT ids.askFS FROM
TRUE => {
ok: BOOLTRUE;
createTried ← TRUE;
[fullFName: fileName, created: created] ← FS.FileInfo[name: fileName, wantedCreatedTime: created, remoteCheck: FALSE !FS.Error => {ok ← FALSE; CONTINUE}];
IF NOT ok THEN {
SIGNAL Miss[fileName, created];
RETURN [NIL]};
fsName ← fileName;
realCreated ← created;
createTried ← TRUE;
};
FALSE => {
cp: FS.ComponentPositions;
needInfo: BOOLFALSE;
[fileName, cp] ← FS.ExpandName[fileName];
IF (ids.version AND cp.ver.length = 0) OR (ids.create AND created = noGMT) THEN {
ok: BOOLTRUE;
[fullFName: fileName, created: created] ← FS.FileInfo[name: fileName, wantedCreatedTime: created, remoteCheck: FALSE !FS.Error => {ok ← FALSE; CONTINUE}];
IF NOT ok THEN {
SIGNAL Miss[fileName, created];
RETURN [NIL]};
[fileName, cp] ← FS.ExpandName[fileName];
realCreated ← created;
createTried ← TRUE;
};
fsName ← fileName;
fileName ← TrimNameByIDS[fileName, cp, ids];
IF NOT ids.create THEN created ← noGMT;
};
ENDCASE => ERROR;
fn ← NEW [FileNoteRep ← [id: [name: fileName, created: created], fsName: fsName, created: realCreated, createTried: createTried]];
};
TrimNameByIDS: PROC [full: ROPE, cp: FS.ComponentPositions, ids: IdentificationScheme] RETURNS [fileName: ROPE] = {
fileName ← full;
IF NOT ids.version THEN fileName ← fileName.Substr[len: cp.ext.start + cp.ext.length];
IF NOT ids.directory THEN fileName ← fileName.Substr[start: cp.base.start]
ELSE IF NOT ids.server THEN fileName ← fileName.Substr[start: cp.dir.start-1];
};
FromFS: PUBLIC PROC [pattern: ROPE, ids: IdentificationScheme ← []] RETURNS [fs: FileSet] = {
Add: PROC [fn: FileNote] --FileConsumer-- = {[] ← Insert[fs, fn]};
fs ← NewFileSet[ids];
EnumFSMatches[pattern, Add, ids];
fs.summary ← Convert.RopeFromInt[Size[fs]];
};
EnumFSMatches: PUBLIC PROC [pattern: ROPE, to: FileConsumer, ids: IdentificationScheme ← []] = {
PerMatch: PROC [fullFName: ROPE, created: GMT, primaryVolume, backupVolume: ROPE] = {
fn: FileNote ← CreateNote[fullFName, created, ids];
IF primaryVolume # NIL OR backupVolume # NIL THEN ERROR;
IF fn # NIL THEN to[fn];
};
HashEnumerate[pattern, FSEnumerate, PerMatch];
};
FSEnumerate: PROC [pattern: ROPE, Consume: Consumer] = {
PerFile: PROC [
fullFName, attachedTo: ROPE, created: BasicTime.GMT, bytes: INT, keep: CARDINAL] RETURNS [continue: BOOL] = {
Consume[fullFName, created, NIL, NIL];
continue ← TRUE};
FS.EnumerateForInfo[pattern: pattern, proc: PerFile];
};
FromVersionMapsByShortName: PUBLIC PROC [vml: VersionMapList, pattern: ROPE, ids: IdentificationScheme ← []] RETURNS [fs: FileSet] = {
Add: PROC [fn: FileNote] --FileConsumer-- = {[] ← Insert[fs, fn]};
fs ← NewFileSet[ids];
EnumVersionMapsByShortName[vml, pattern, Add, ids];
fs.summary ← Convert.RopeFromInt[Size[fs]];
};
EnumVersionMapsByShortName: PUBLIC PROC [vml: VersionMapList, pattern: ROPE, to: FileConsumer, ids: IdentificationScheme ← []] = {
VMEnumerate: PROC [pattern: ROPE, Consume: Consumer] = {EnumVMsByShort[vml, pattern, Consume]};
PerMatch: PROC [fullFName: ROPE, created: GMT, primaryVolume, backupVolume: ROPE] = {
fn: FileNote ← CreateNote[fullFName, created, ids];
IF primaryVolume # NIL OR backupVolume # NIL THEN ERROR;
IF fn # NIL THEN to[fn];
};
HashEnumerate[pattern, VMEnumerate, PerMatch];
};
EnumVMsByShort: PROC [vml: VersionMapList, pattern: ROPE, Consume: Consumer] = {
size: INT = Rope.Length[pattern];
starPos: INT = pattern.Index[0, "*"];
match: BOOL = starPos # size;
prefix: ROPE = Rope.Flatten[pattern, 0, starPos];
prefixLen: INT = Rope.Length[prefix];
rangeList: VersionMap.RangeList ← VersionMap.ShortNameToRanges[vml, prefix];
IF size = 0 THEN RETURN;
WHILE rangeList # NIL DO
range: VersionMap.Range ← rangeList.first;
map: VersionMap.Map = range.map;
rangeList ← rangeList.rest;
Process.CheckForAbort[];
IF match
THEN {
entries: CARDINAL = VersionMap.Length[map];
IF range.first >= entries THEN LOOP;
range.len ← entries - range.first;
WHILE range.len # 0 DO
fullName: ROPE;
stamp: BcdDefs.VersionStamp;
thisShort: ROPE;
created: BasicTime.GMT;
[fullName, stamp, created, range] ← VersionMap.RangeToEntry[range];
thisShort ← ShortName[fullName];
IF Rope.Run[prefix, 0, thisShort, 0, FALSE] # prefixLen THEN EXIT;
IF Rope.Match[pattern, thisShort, FALSE] THEN {
Consume[fullName, created, NIL, NIL];
};
ENDLOOP;
}
ELSE {
WHILE range.len # 0 DO
fullName: ROPE;
stamp: BcdDefs.VersionStamp;
created: BasicTime.GMT;
[fullName, stamp, created, range] ← VersionMap.RangeToEntry[range];
Consume[fullName, created, NIL, NIL];
ENDLOOP;
};
ENDLOOP;
};
ShortName: PROC [r: ROPE] RETURNS [ROPE] = {
make a long name into a short one
assumes a valid long name, of course
first: INT ← 0;
last: INT ← Rope.Length[r];
FOR i: INT DECREASING IN [0..last) DO
c: CHAR ← r.Fetch[i];
SELECT c FROM
'>, '/ => {first ← i+1; EXIT};
'! => last ← i
ENDCASE;
ENDLOOP;
RETURN [r.Substr[first, last - first]]
};
FromVersionMapsByCreateDate: PUBLIC PROC [vml: VersionMapList, range: TimeRange, ids: IdentificationScheme ← []] RETURNS [fs: FileSet] = {
Add: PROC [fn: FileNote] --FileConsumer-- = {[] ← Insert[fs, fn]};
fs ← NewFileSet[ids];
EnumVersionMapsByCreateDate[vml, range, Add, ids];
fs.summary ← IO.PutFR["%g:(versionMapFilesCreated \"%g\" \"%g\")", [integer[Size[fs]]], [time[range.from]], [time[range.to]]];
};
EnumVersionMapsByCreateDate: PUBLIC PROC [vml: VersionMapList, range: TimeRange, to: FileConsumer, ids: IdentificationScheme ← []] = {
FOR vml ← vml, vml.rest WHILE vml # NIL DO
map: VersionMap.Map = vml.first;
Process.CheckForAbort[];
FOR i: INT IN [0 .. map.Length[]) DO
created: GMT = map.FetchCreated[i];
IF InRange[created, range] THEN {
fn: FileNote = CreateNote[VersionMap.FetchName[map, i], created, ids];
IF fn # NIL THEN to[fn];
};
ENDLOOP;
ENDLOOP;
};
InRange: PROC [time: GMT, range: TimeRange] RETURNS [in: BOOL] = INLINE {
in ← BasicTime.Period[range.from, time] >= 0 AND BasicTime.Period[time, range.to] >= 0;
};
HashEnumerate: PUBLIC PROC [pattern: ROPE, Enumerate: Enumerator, Consume: Consumer] = {
fsPattern: ROPE ← pattern;
matchPattern: ROPE ← pattern;
filteringNeeded: BOOLFALSE;
nChecks: INT ← 0;
checks: LORNIL;
finder: TextFind.Finder ← NIL;
NoteFile: PROC [fullFName: ROPE, created: GMT, primaryVolume, backupVolume: ROPE] = {
IF filteringNeeded THEN {
found: BOOL ← TextFind.SearchRope[finder, fullFName].found;
rm: TextReplace.RopeMap ← TextReplace.MapNamedSubfieldToMatch[finder, fullFName];
IF NOT found THEN {
SIGNAL Warning[IO.PutFR["FileSets Enumeration/directory filter bug: couldn't match %g against %g", IO.refAny[matchPattern], IO.refAny[fullFName]]];
RETURN;
};
FOR cl: LOR ← checks, cl.rest WHILE cl # NIL DO
IF rm.Apply[cl.first].Find[">"] # -1 THEN RETURN;
ENDLOOP;
};
Consume[fullFName, created, primaryVolume, backupVolume];
};
FOR i: INT ← fsPattern.Find["#"], fsPattern.Find["#"] WHILE i >= 0 DO
filteringNeeded ← TRUE;
fsPattern ← fsPattern.Substr[len: i].Cat["*", fsPattern.Substr[start: i+1]];
ENDLOOP;
IF filteringNeeded THEN {
IF matchPattern.Find["/"] # -1 THEN ERROR Error[IO.PutFR["FileSets can't enumerate a pattern in slashy format with a # in it (%g)", IO.refAny[matchPattern]]];
SELECT matchPattern.Fetch[0] FROM
'[, '< => NULL;
IN ['A .. 'Z], IN ['a .. 'z], IN ['0 .. '9], '., '!, '*, '# => {
cwd: ROPE ← GetCWD[];
matchPattern ← cwd.Cat[matchPattern];
fsPattern ← cwd.Cat[fsPattern];
};
ENDCASE => ERROR Error[IO.PutFR["FileSets thinks file name pattern %g is bogus", IO.refAny[matchPattern]]];
matchPattern ← TextReplace.Apply[quoteMetas, matchPattern];
matchPattern ← matchPattern.Substr[len: Rope.Index[s1: matchPattern, s2: "!"]].Concat["!*"];
FOR i: INT ← matchPattern.Find["#"], matchPattern.Find["#"] WHILE i >= 0 DO
checks ← CONS[IO.PutFR["ck%g", IO.int[nChecks ← nChecks + 1]], checks];
matchPattern ← matchPattern.Substr[len: i].Cat["<", checks.first, ":*>", matchPattern.Substr[start: i+1]];
ENDLOOP;
finder ← TextFind.CreateFromRope[pattern: matchPattern, ignoreCase: TRUE];
};
Enumerate[fsPattern, NoteFile];
};
quoteMetas: TextReplace.RopeMap ← TextReplace.RopeMapFromPairs[LIST[["<", "'<", TRUE], [">", "'>", TRUE]]];
GetCWD: PROC RETURNS [cwd: ROPE--in brackety format--] = {
cwd ← FS.ExpandName["x!3", CommandTool.CurrentWorkingDirectory[]].fullFName;
cwd ← cwd.Substr[len: cwd.Length[]-3];
};
FromDF: PUBLIC PROC [dfName: ROPE, date: DFUtilities.Date ← [], filter: DFUtilities.Filter, which: DFWhich, ids: IdentificationScheme ← []] RETURNS [fs: FileSet] = {
Add: PROC [fn: FileNote] --FileConsumer-- = {[] ← Insert[fs, fn]};
fs ← NewFileSet[ids];
EnumDF[dfName, date, Add, filter, which, ids];
fs.summary ← Convert.RopeFromInt[Size[fs]];
};
FromDFs: PUBLIC PROC [dfs: FileSet, filter: DFUtilities.Filter, which: DFWhich, ids: IdentificationScheme ← []] RETURNS [fs: FileSet] = {
Add: PROC [fn: FileNote] --FileConsumer-- = {[] ← Insert[fs, fn]};
PerDF: PROC [fn: FileNote] --FileConsumer-- = {
created: GMT ← GetCreated[fn];
dfDate: DFUtilities.Date ← IF created # noGMT THEN [explicit, created] ELSE [];
EnumDF[fn.fsName, dfDate, Add, filter, which, ids];
};
fs ← NewFileSet[ids];
EnumSet[dfs, PerDF];
fs.summary ← Convert.RopeFromInt[Size[fs]].Cat[":(fromDFS ", dfs.summary, ")"];
};
EnumDF: PUBLIC PROC [dfName: ROPE, date: DFUtilities.Date ← [], to: FileConsumer, filter: DFUtilities.Filter, which: DFWhich, ids: IdentificationScheme ← []] = {
dir: ROPE ← "?";
readOnly: BOOLFALSE;
PerItem: PROC [item: REF ANY] RETURNS [stop: BOOLFALSE] = {
Process.CheckForAbort[];
WITH item SELECT FROM
di: REF DFUtilities.DirectoryItem => {dir ← di.path1; readOnly ← di.readOnly};
fi: REF DFUtilities.FileItem => {
useDir: ROPESELECT which FROM remote => dir, local => NIL, ENDCASE => ERROR;
fiName: ROPE;
fiCP: FS.ComponentPositions;
fn: FileNote;
[fiName, fiCP, ] ← FS.ExpandName[fi.name, useDir];
IF (fn ← SELECT which FROM
remote => CreateNote[fiName, fi.date.gmt, ids],
local => CreateNote[DFUtilities.RemoveVersionNumber[fiName], noGMT, ids],
ENDCASE => ERROR) # NIL THEN to[fn];
};
ii: REF DFUtilities.ImportsItem => {
subFilter: DFUtilities.Filter ← [
comments: filter.comments,
filterA: filter.filterA,
filterB: IF ii.form = $exports THEN $public ELSE filter.filterB,
filterC: $all,
list: IF ii.form = $list THEN ii.list ELSE filter.list
];
EnumDF[ii.path1, ii.date, to, subFilter, which, ids];
};
ii: REF DFUtilities.IncludeItem => EnumDF[ii.path1, ii.date, to, filter, which, ids];
ci: REF DFUtilities.CommentItem => NULL;
wi: REF DFUtilities.WhiteSpaceItem => NULL;
ENDCASE => ERROR;
};
file: FS.OpenFile ← FS.nullOpenFile;
from: STREAM;
file ← FS.Open[
name: dfName,
wantedCreatedTime: SELECT date.format FROM
explicit => date.gmt,
omitted, greaterThan, notEqual => BasicTime.nullGMT,
ENDCASE => ERROR
!FS.Error => {
SIGNAL Warning[IO.PutFR["FS.Error[%g, %g]; assumed empty", IO.atom[error.code], IO.rope[error.explanation]]];
file ← FS.nullOpenFile;
CONTINUE
}];
IF file = FS.nullOpenFile THEN RETURN;
from ← FS.StreamFromOpenFile[file];
DFUtilities.ParseFromStream[in: from, proc: PerItem, filter: filter !DFUtilities.SyntaxError => {SIGNAL Warning[IO.PutFR["Syntax error near [%g] in DF-file %g: %g", IO.int[from.GetIndex[]], IO.rope[dfName], IO.rope[reason]]]; CONTINUE}];
from.Close[];
};
MentionedDFs: PUBLIC PROC [dfName: ROPE, filter: DFUtilities.Filter ← [], ids: IdentificationScheme ← []] RETURNS [fs: FileSet] = {
Add: PROC [fn: FileNote] = {[] ← Insert[fs, fn]};
fs ← NewFileSet[ids];
EnumMentionedDFs[dfName, filter, Add, ids];
fs.summary ← Convert.RopeFromInt[Size[fs]];
};
EnumMentionedDFs: PUBLIC PROC [dfName: ROPE, filter: DFUtilities.Filter ← [], to: FileConsumer, ids: IdentificationScheme ← []] = {
ProcessDFFile: PROC [fullDFName: ROPE, filter: DFUtilities.Filter] = {
in: IO.STREAM = FS.StreamOpen[fullDFName];
ProcessItem: PROC [item: REF ANY] RETURNS [stop: BOOLFALSE] --DFUtilities.ProcessItemProc-- = {
WITH item SELECT FROM
di: REF DFUtilities.DirectoryItem => NULL;
fi: REF DFUtilities.FileItem => NULL;
ii: REF DFUtilities.ImportsItem => {
fn: FileNote = CreateNote[ii.path1, ii.date.gmt, ids];
to[fn];
};
ii: REF DFUtilities.IncludeItem => {
fn: FileNote = CreateNote[ii.path1, ii.date.gmt, ids];
to[fn];
ProcessDFFile[ii.path1, filter];
};
ci: REF DFUtilities.CommentItem => NULL;
wi: REF DFUtilities.WhiteSpaceItem => NULL;
ENDCASE => ERROR;
};
DFUtilities.ParseFromStream[in, ProcessItem, filter];
in.Close[];
};
ProcessDFFile[dfName, filter];
};
FromRope: PUBLIC PROC [rope: ROPE, ids: IdentificationScheme ← []] RETURNS [fs: FileSet] = {
s: STREAMIO.RIS[rope];
fs ← FromStream[s, ids];
s.Close[];
};
FromFile: PUBLIC PROC [fileName: ROPE, ids: IdentificationScheme ← []] RETURNS [fs: FileSet] = {
s: STREAMFS.StreamOpen[fileName];
fs ← FromStream[s, ids];
s.Close[];
};
CRBreak: PROC [char: CHAR] RETURNS [cc: IO.CharClass] --IO.BreakProc-- = {
cc ← SELECT char FROM
'\n => break,
IN ['\000 .. ' ] => sepr,
ENDCASE => other;
};
FromStream: PUBLIC PROC [from: STREAM, ids: IdentificationScheme ← []] RETURNS [fs: FileSet] = {
fs ← NewFileSet[ids];
FOR i: INT ← from.SkipWhitespace[], from.SkipWhitespace[] WHILE NOT from.EndOf[] DO
fileName: ROPE ← from.GetTokenRope[CRBreak].token;
fn: FileNote;
created: GMT ← noGMT;
GetToke: PROC RETURNS [toke: ROPE] =
{toke ← IF from.EndOf[] THEN "\n" ELSE from.GetTokenRope[CRBreak].token};
FOR toke: ROPE ← GetToke[], GetToke[] UNTIL toke.Equal["\n"] DO
SELECT TRUE FROM
toke.Equal["Of", FALSE] => {
createdRope: ROPE ← from.GetLineRope[];
[created] ← Tempus.Parse[rope: createdRope, search: FALSE];
EXIT;
};
ENDCASE => ERROR;
ENDLOOP;
fn ← CreateNote[fileName, created, ids];
IF fn # NIL THEN [] ← Insert[fs, fn];
ENDLOOP;
fs.summary ← Convert.RopeFromInt[Size[fs]];
};
ToRope: PUBLIC PROC [fs: FileSet] RETURNS [rope: ROPE] = {
s: IO.STREAMIO.ROS[];
ToStream[fs, s];
rope ← s.RopeFromROS[];
};
ToFile: PUBLIC PROC [fs: FileSet, fileName: ROPE] = {
s: IO.STREAMFS.StreamOpen[fileName, create];
ToStream[fs, s];
s.Close[];
};
ToStream: PUBLIC PROC [fs: FileSet, to: IO.STREAM] = {
PerNote: PROC [fn: FileNote] --FileConsumer-- = {
created: GMT ← GetCreated[fn];
to.PutRope[fn.fsName];
IF created # noGMT THEN to.PutF[" Of %g", IO.time[created]];
to.PutChar['\n];
};
EnumSet[fs, PerNote];
};
}.