BringOverImpl.mesa
Copyright © 1984, 1985 by Xerox Corporation. All rights reserved.
Levin on December 19, 1983 4:18 pm
Russ Atkinson (RRA) May 21, 1985 11:49:01 am PDT
DIRECTORY
BasicTime USING [Period],
DFInternal USING [AbortDF, CheckAbort, Client, ClientDescriptor, DefaultInteractionProc, GetFileInfo, DoAbort, LocalFile, LocalFileInfo, RemoteFileInfo, ReportFSError, ShortName, SimpleInteraction, YesOrNo],
DFOperations USING [AbortInteraction, BringOverAction, BringOverFilter, DFInfoInteraction, FileAction, FileInteraction, InfoInteraction, InteractionProc],
DFUtilities USING [ClassifyFileExtension, Date, DateToRope, DifferenceOfUsingLists, DirectoryItem, FileItem, Filter, ImportsItem, IncludeItem, ParseFromStream, ProcessItemProc, RemoveVersionNumber, SortUsingList, SyntaxError, UsingEntry, UsingList],
FS USING [Close, Copy, Error, ExpandName, Open, StreamOpen],
IO USING [Close, GetIndex, PutFR, PutFR1, STREAM],
Rope USING [Equal, Fetch, Length, ROPE, Run, Substr];
BringOverImpl: CEDAR PROGRAM
IMPORTS BasicTime, DFInternal, DFUtilities, FS, IO, Rope
EXPORTS DFOperations = BEGIN
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
BringOver: PUBLIC PROC [dfFile: ROPE, filter: DFOperations.BringOverFilter ← [], action: DFOperations.BringOverAction ← $enter, interact: DFOperations.InteractionProc ← NIL, clientData: REF ANYNIL, log: STREAMNIL] RETURNS [errors, warnings, filesActedUpon: INT ← 0] = {
client: DFInternal.Client = NEW[DFInternal.ClientDescriptor ← [
(interact ← IF interact = NIL THEN DFInternal.DefaultInteractionProc ELSE interact),
clientData,
log
]];
retrievedFiles: REF DFUtilities.UsingList ← NIL;
InRetrievedList: PROC [shortName: ROPE] RETURNS [BOOL] = {
FOR i: NAT IN [0..retrievedFiles.nEntries) DO
IF Rope.Equal[shortName, retrievedFiles[i].name, FALSE] THEN RETURN [TRUE];
ENDLOOP;
RETURN [FALSE];
};
ConvertFilter: PROC [f: DFOperations.BringOverFilter] RETURNS [filter: DFUtilities.Filter] = {
filter ← [filterA: VAL[f.filterA.ORD], filterB: VAL[f.filterB.ORD], filterC: VAL[f.filterC.ORD]];
IF f.list ~= NIL THEN {
nEntries: NAT ← 0;
i: NAT ← 0;
FOR l: LIST OF ROPE ← f.list, l.rest UNTIL l = NIL DO
nEntries ← nEntries.SUCC;
ENDLOOP;
filter.list ← NEW[DFUtilities.UsingList[nEntries]];
FOR l: LIST OF ROPE ← f.list, l.rest UNTIL l = NIL DO
file: ROPE = DFUtilities.RemoveVersionNumber[l.first];
IF ~(filter.filterA = $all OR DFUtilities.ClassifyFileExtension[file] = filter.filterA) THEN
NotFoundWarning[file];
filter.list.u[i] ← DFUtilities.UsingEntry[name: file];
i ← i.SUCC;
ENDLOOP;
filter.list.nEntries ← nEntries;
DFUtilities.SortUsingList[usingList: filter.list, nearlySorted: FALSE];
};
};
NotFoundWarning: PROC [file: ROPE] = {
warnings ← warnings.SUCC;
DFInternal.SimpleInteraction[
client,
NEW[DFOperations.InfoInteraction ← [
class: $warning,
message: IO.PutFR1["'%g' could not be found in any nested DF file.", [rope[file]]]
]]
];
};
BringOverInner: PROC [dfFile: ROPE, date: DFUtilities.Date, filter: DFUtilities.Filter] RETURNS [retrieved: NAT ← 0] = {
Note: 'retrieved' is maintained, and therefore meaningful, only if filter.list ~= NIL.
requested: NAT = IF filter.list ~= NIL THEN filter.list.nEntries ELSE NAT.LAST;
Note: 'iAllocatedRetrievedFiles' is TRUE iff this instance of 'BringOverInner' is the outermost one for which filter.list ~= NIL. Thus, when recursion below this instance is complete, all of filter.list should have been retrieved.
iAllocatedRetrievedFiles: BOOLFALSE;
directoryPath: ROPENIL;
Localize: PROC [remote: ROPE, date: DFUtilities.Date, action: DFOperations.BringOverAction, directory: ROPENIL] RETURNS [localInfo: REF DFInternal.LocalFileInfo, dontProcess: BOOLFALSE] = {
remoteInfo: REF DFInternal.RemoteFileInfo ← NEW[DFInternal.RemoteFileInfo ← [remote, date]];
AttachNeeded: PROC RETURNS [action: {alreadyLocal, doAttach, userSaidNo}] = {
attach: BOOLTRUE;
sameDate: BOOL = (localInfo.date.gmt = remoteInfo.date.gmt);
IF DFInternal.LocalFile[remoteInfo.name] THEN RETURN[$alreadyLocal];
Assert: remoteInfo.date.gmt ~= BasicTime.nullGMT
Assert: (localInfo.date.format = $explicit and localInfo.date.gmt ~= BasicTime.nullGMT) iff local file exists.
IF localInfo.date.format = $explicit THEN {
pos: INT ← Rope.Run[localInfo.attachedTo, 0, remoteInfo.name, 0, FALSE];
IF pos < Rope.Length[localInfo.attachedTo]
AND Rope.Fetch[localInfo.attachedTo, pos] # '! THEN GO TO needAttach;
IF pos < Rope.Length[remoteInfo.name]
AND Rope.Fetch[remoteInfo.name, pos] # '! THEN GO TO needAttach;
Both a local and a remote file exist and the local file is attached to a file whose name (without version) matches the remote file's name (without version). Check to see if they have the correct relationship in time.
SELECT date.format FROM
$greaterThan =>
attach ← BasicTime.Period[from: localInfo.date.gmt, to: remoteInfo.date.gmt] > 0;
ENDCASE => attach ← ~sameDate;
EXITS needAttach => {};
};
IF ~attach THEN RETURN[$alreadyLocal];
IF ~sameDate AND ~DFInternal.YesOrNo[
client: client,
message:
IO.PutFR[
"%g {%g} to %g%g?",
[rope[remoteInfo.name]],
[rope[DFUtilities.DateToRope[remoteInfo.date]]],
[rope[localInfo.name]],
[rope[
IF localInfo.date.format = $explicit THEN
IO.PutFR[
" (previously: %g, keep: %d) ",
[rope[DFUtilities.DateToRope[localInfo.date]]],
[cardinal[localInfo.keep]]
]
ELSE NIL
]]
],
default: TRUE
] THEN RETURN[$userSaidNo];
IF localInfo.date.format = $explicit AND localInfo.keep = 1 AND
BasicTime.Period[from: remoteInfo.date.gmt, to: localInfo.date.gmt] > 0 AND
DFUtilities.ClassifyFileExtension[localInfo.name] = $source AND
~DFInternal.YesOrNo[
client: client,
message:
IO.PutFR[
"Are you sure? (%g {%g} is older than %g {%g} and will overwrite it)",
[rope[remoteInfo.name]],
[rope[DFUtilities.DateToRope[remoteInfo.date]]],
[rope[localInfo.name]],
[rope[DFUtilities.DateToRope[localInfo.date]]]
],
default: TRUE,
blunder: TRUE
] THEN RETURN[$userSaidNo];
RETURN[$doAttach]
};
SELECT Rope.Length[directory] FROM
0 => {
No directory given, so use the local short name
remote ← DFInternal.ShortName[file: remote, keepVersion: FALSE];
};
Rope.Run[remote, 0, directory, 0, FALSE] => {
Try to preserve the local sub-directory information
remote ← DFUtilities.RemoveVersionNumber[Rope.Substr[remote, Rope.Length[directory]]];
};
ENDCASE => {
This should never happen for a decent god-fearing DF file
DFInternal.DoAbort[client.log, IO.PutFR1["Bogus file name (%g).", [rope[remote]] ]];
};
localInfo ← NEW[DFInternal.LocalFileInfo ← [name: remote]];
DFInternal.GetFileInfo[info: localInfo, notFoundOK: TRUE, client: client
! FS.Error => GO TO quit];
localInfo.name ← DFUtilities.RemoveVersionNumber[localInfo.name];
IF ~(date.format = $explicit AND action = $enter) THEN
DFInternal.GetFileInfo[info: remoteInfo, client: client ! FS.Error => GO TO quit];
SELECT AttachNeeded[] FROM
$alreadyLocal => NULL;
$doAttach => {
DFInternal.SimpleInteraction[
client,
NEW[DFOperations.FileInteraction ← [
remoteFile: remoteInfo.name,
localFile: localInfo.name,
dateFormat:
SELECT remoteInfo.date.format FROM
$explicit => $explicit,
$greaterThan => $greaterThan,
ENDCASE => $notEqual,
date: remoteInfo.date.gmt,
action: IF action = $check THEN $check ELSE $fetch
]]
];
IF action ~= $check THEN
[] ← FS.Copy[
to: localInfo.name,
from: remoteInfo.name, wantedCreatedTime: remoteInfo.date.gmt,
setKeep: FALSE,
keep: IF DFUtilities.ClassifyFileExtension[localInfo.name] = $source THEN 2 ELSE 1,
remoteCheck: action ~= $enter, attach: TRUE
! FS.Error => {DFInternal.ReportFSError[error, localInfo, client]; GO TO quit}
];
filesActedUpon ← filesActedUpon.SUCC;
};
$userSaidNo => dontProcess ← TRUE;
ENDCASE;
IF ~ dontProcess AND action = $fetch THEN {
ENABLE FS.Error => {DFInternal.ReportFSError[error, remoteInfo, client]; GO TO quit};
FS.Close[FS.Open[localInfo.name]];
};
EXITS
quit => {errors ← errors.SUCC; dontProcess ← TRUE};
};
DoOneItem: DFUtilities.ProcessItemProc = {
DFInternal.CheckAbort[client];
IF retrievedFiles ~= NIL AND retrievedFiles.nEntries = retrievedFiles.length THEN
RETURN[TRUE];
WITH item SELECT FROM
directory: REF DFUtilities.DirectoryItem => {
Get the directory in angle-bracket format
directoryPath ← FS.ExpandName[directory.path1].fullFName;
};
file: REF DFUtilities.FileItem => {
remoteName: ROPE = FS.ExpandName[file.name, directoryPath].fullFName;
localInfo: REF DFInternal.LocalFileInfo = Localize[remoteName, file.date, action, directoryPath].localInfo;
IF retrievedFiles ~= NIL THEN {
Some enclosing call of BringOverInner presented a filter with an explicit list, so the number of files to be retrieved at that point was known then ('retrievedFiles.u.length'). We maintain the names of the files actually retrieved to enable intelligent error reporting at the end if not all of the files in the original filter.list are found.
short: ROPE = DFInternal.ShortName[localInfo.name];
retN: NAT = retrievedFiles.nEntries;
IF InRetrievedList[short] THEN GO TO notNew;
retrievedFiles[retN] ← [name: short];
retrievedFiles.nEntries ← retN+1;
DFUtilities.SortUsingList[retrievedFiles, TRUE];
};
IF filter.list ~= NIL AND (retrieved ← retrieved.SUCC) = filter.list.nEntries THEN
RETURN[TRUE];
EXITS notNew => {};
};
imports: REF DFUtilities.ImportsItem => {
newFilter: DFUtilities.Filter ← [
comments: filter.comments, -- comments processing is unaffected by imports
filterA: filter.filterA, -- source/derived distinction is unaffected by imports
filterB: IF imports.form = $exports THEN $public ELSE filter.filterB,
filterC: $all, -- if the top level passes imports, they can come from anywhere
list: IF imports.form = $list THEN imports.list ELSE filter.list
];
SELECT TRUE FROM
newFilter.list = NIL =>
In this case, both filter.list and imports.list are NIL. There are no optimizations available to permit early termination of BringOver, so we need not maintain 'retrieved'.
[] ← BringOverInner[imports.path1, imports.date, newFilter];
newFilter.list.nEntries = 0 =>
We should never get here with filter.list.nEntries = 0 (see next case, below), so it must be the case that imports.list.nEntries = 0. That is, the imports item contained an explicit list, but it had an empty intersection with our filter.list. Accordingly, there is no point in recurring.
NULL;
ENDCASE => {
We are passing a non-NIL, non-empty filter.list to the next lower level. It will therefore return a meaningful count of the number of files it retrieves.
retrievedBelow: NAT ← 0;
newFilter.filterB ← $all; -- force a look at all files
IF retrievedFiles # NIL AND retrievedFiles.nEntries # 0 THEN {
If all of the files in the new filter have already been fetched then we have no need to do the inner bringover. So compute the difference.
newFilter.list
← DFUtilities.DifferenceOfUsingLists[newFilter.list, retrievedFiles];
IF newFilter.list = NIL OR newFilter.list.nEntries = 0 THEN GO TO noNeed;
};
retrievedBelow ← BringOverInner[imports.path1, imports.date, newFilter];
IF filter.list ~= NIL AND retrievedBelow # 0 THEN {
The following statement and a similar one in the 'file' case, above, are the only ones that alter 'retrieved', and are executed iff filter.list ~= NIL. Note that 'requested' is initialized to a meaningful value under the same precondition. The test succeeds iff we have retrieved everything that was requested by our invoker's filter.list.
IF requested = (retrieved ← retrieved + retrievedBelow) THEN RETURN[TRUE];
IF imports.form = $list THEN {
In this case, we were invoked with an explicit filter.list and the imports item had one as well. DFUtilities.ParseFromStream intersected the two and handed it to us as 'imports.list', which we passed down as newFilter.list in the nested call above. As a result of that call, newFilter.list became sorted, and filter.list was sorted by the recursion above us (i.e., by the level that invoked this instance of BringOverInner). Therefore, the precondition (sorted input) for DFUtilities.DifferenceOfUsingLists is met.
filter.list ← DFUtilities.DifferenceOfUsingLists[filter.list, newFilter.list];
If filter.list is now empty, we should abandon this level of BringOver. In the absence of errors, however, the test above (requested = retrieved) will have been TRUE and control wouldn't get here. However, if some file in newFilter.list was not found (e.g., the Imports item mentioned a file that didn't appear in the lower-level DF, or some other part of the filter prevented a match), the test above will fail (`requested' will still exceed `retrieved'), yet filter.list will be empty.
RETURN[filter.list.nEntries = 0]
};
};
EXITS noNeed => {};
};
};
include: REF DFUtilities.IncludeItem =>
retrieved ← BringOverInner[include.path1, include.date, filter] + retrieved;
ENDCASE;
};
dfLocalInfo: REF DFInternal.LocalFileInfo;
dontProcess: BOOL;
[dfLocalInfo, dontProcess] ← Localize[dfFile, date, $fetch];
IF ~dontProcess THEN {
dfStream: STREAM = FS.StreamOpen[dfLocalInfo.name
! FS.Error => {DFInternal.ReportFSError[error, dfLocalInfo, client]; GO TO skip};
];
DFInternal.SimpleInteraction[
client,
NEW[DFOperations.DFInfoInteraction ← [action: $start, dfFile: dfFile]]
];
IF filter.list ~= NIL AND retrievedFiles = NIL THEN {
retrievedFiles ← NEW[DFUtilities.UsingList[filter.list.nEntries]];
retrievedFiles.nEntries ← 0;
iAllocatedRetrievedFiles ← TRUE;
};
DFUtilities.ParseFromStream[dfStream, DoOneItem, filter !
DFUtilities.SyntaxError -- [reason: ROPE]-- => {
errors ← errors.SUCC;
DFInternal.SimpleInteraction[
client,
NEW[DFOperations.InfoInteraction ← [
class: $error,
message: IO.PutFR[
"Syntax error in '%g'[%d]: %g\NProcessing of this DF file aborted.",
[rope[dfLocalInfo.name]], [cardinal[dfStream.GetIndex[]]], [rope[reason]]
]
]]
];
CONTINUE
};
DFInternal.AbortDF => dfStream.Close[];
];
dfStream.Close[];
IF iAllocatedRetrievedFiles THEN {
IF retrievedFiles.nEntries ~= filter.list.nEntries THEN {
There were some files specified in the explicit filter list that weren't retrieved.
diff: REF DFUtilities.UsingList;
diff ← DFUtilities.DifferenceOfUsingLists[filter.list, retrievedFiles];
FOR i: NAT IN [0..diff.nEntries) DO
IF filter.filterA = $all OR DFUtilities.ClassifyFileExtension[diff.u[i].name] = filter.filterA THEN NotFoundWarning[diff.u[i].name];
ENDLOOP;
};
retrievedFiles ← NIL;
};
DFInternal.SimpleInteraction[
client,
NEW[DFOperations.DFInfoInteraction ← [action: $end, dfFile: dfFile]]
];
EXITS
skip => errors ← errors.SUCC;
};
};
The peculiar date specification has the effect of treating a version number on 'dfFile' as truth. See comment in DFInternal.GetFileInfo.
[] ← BringOverInner[dfFile, [format: $explicit], ConvertFilter[filter] !
ABORTED => {
DFInternal.SimpleInteraction[client, NEW[DFOperations.AbortInteraction ← [TRUE]]];
Let this one propagate.
};
DFInternal.AbortDF => {
errors ← errors.SUCC;
DFInternal.SimpleInteraction[client, NEW[DFOperations.DFInfoInteraction ← [action: $abort, dfFile: dfFile]]];
CONTINUE
};
];
};
END.