DFCachingImpl.mesa
Copyright Ó 1990, 1991 by Xerox Corporation. All rights reserved.
Last tweaked by Mike Spreitzer on May 1, 1990 4:02:34 pm PDT
Chauser, November 8, 1990 2:17 pm PST
Willie-s, February 14, 1991 6:38 pm PST
Michael Plass, January 23, 1992 10:37 am PST
DIRECTORY BasicTime, DFCachingOperations, DFCachingPrivate, DFCachingUtilities, DFInternal, DFOperations, DFUtilities, FS, IO, PFS, Rope, SymTab;
DFCachingImpl: CEDAR PROGRAM
IMPORTS BasicTime, DFInternal, DFUtilities, FS, IO, PFS, Rope, SymTab
EXPORTS DFCachingOperations, DFCachingUtilities
=
BEGIN OPEN DFCachingOperations, DFCachingUtilities, DFCachingPrivate, DFI: DFInternal, DFO: DFOperations, DFU: DFUtilities;
BringOverCache: TYPE ~ REF BringOverCachePrivate;
BringOverCachePrivate: PUBLIC TYPE ~ DFCachingPrivate.BringOverCachePrivate;
EnumerationCache: TYPE ~ REF EnumerationCachePrivate;
EnumerationCachePrivate: PUBLIC TYPE ~ DFCachingPrivate.EnumerationCachePrivate;
RopeList: TYPE ~ LIST OF ROPE;
SyntaxError: PUBLIC SIGNAL [reason, dfFileName: ROPE, position: INT] ~ CODE;
Miss: PUBLIC SIGNAL [dfFileName, dataFileName: ROPE] ~ CODE;
FSErrorOnDF: PUBLIC SIGNAL [error: FS.ErrorDesc] ~ CODE;
allFilesV: PUBLIC FileFilter ¬ allFiles;
CreateCache: PUBLIC PROC RETURNS [cache: BringOverCache] ~ {
cache ¬ NEW [BringOverCachePrivate ¬ [
wDirToState: SymTab.Create[case: FALSE]
]];
};
CacheSize: PUBLIC PROC [cache: BringOverCache] RETURNS [size: INT] ~ {
PerEnumCache: PROC [key, val: REF ANY] RETURNS [quit: BOOL ¬ FALSE] --SymTab.EachPairAction-- ~ {
state: State ~ NARROW[val];
size ¬ size + state.ec.nameToStatus.GetSize[];
};
size ¬ 0;
IF cache.wDirToState.Pairs[PerEnumCache] THEN ERROR;
size ¬ size;
};
GetEnumCache: PROC [cache: BringOverCache, wDir: ROPE, action: BringOverAction] RETURNS [ec: EnumerationCache] ~ {
IF cache=NIL THEN RETURN [NIL];
{state: State ¬ NARROW[cache.wDirToState.Fetch[wDir].val];
IF state=NIL AND NOT cache.wDirToState.Insert[wDir, state ¬ NEW [StatePrivate ¬ [action, CreateEnumerationCache[]]]] THEN ERROR;
IF state.action # action THEN {state.ec ¬ CreateEnumerationCache[]; state.action ¬ action};
RETURN [state.ec];
}};
CreateEnumerationCache: PUBLIC PROC RETURNS [cache: EnumerationCache] ~ {
cache ¬ NEW [EnumerationCachePrivate ¬ [
nameToStatus: SymTab.Create[case: FALSE]
]];
};
GetStatus: PROC [cache: EnumerationCache, dfFileName: ROPE] RETURNS [status: Status] ~ {
status ¬ IF cache#NIL THEN NARROW[cache.nameToStatus.Fetch[dfFileName].val] ELSE NIL;
IF status=NIL THEN {
status ¬ NEW [StatusPrivate ¬ []];
IF cache#NIL THEN {
status.doneFiles ¬ SymTab.Create[case: FALSE];
IF NOT cache.nameToStatus.Insert[dfFileName, status] THEN ERROR;
};
};
};
TranslateFilter: PUBLIC PROC [dfo: DFOperations.BringOverFilter] RETURNS [ff: FileFilter] ~ {
ff ¬ [];
FOR p: Publicity IN Publicity DO FOR o: Ownership IN Ownership DO FOR d: Derivation IN Derivation DO
ff.fileTypes[p][o][d] ¬
(SELECT dfo.filterA FROM source=>d=source, derived=>d=derived, all=>TRUE, ENDCASE=>ERROR) AND
(SELECT dfo.filterB FROM public=>p=public, private=>p=private, all=>TRUE, ENDCASE => ERROR) AND
(SELECT dfo.filterC FROM defining=>o=defining, imported=>o=imported, all=>TRUE, ENDCASE => ERROR);
ENDLOOP ENDLOOP ENDLOOP;
IF dfo.list#NIL THEN {
ff.files ¬ SymTab.Create[case: FALSE];
FOR rl: RopeList ¬ dfo.list, rl.rest WHILE rl#NIL DO
[] ¬ ff.files.Insert[rl.first, $Needed];
ENDLOOP;
ff.strict ¬ ff.fileTypes=allFileTypes;
}
ELSE ff.strict ¬ FALSE;
};
TranslateAction: PUBLIC PROC [da: DFO.BringOverAction] RETURNS [BringOverAction] ~ {
RETURN [[
enter: da.enter,
suspicious: da.check,
fetch: da.fetch,
confirmEarlier: da.confirmEarlier,
confirmGlobalDestination: TRUE,
dontDoit: NOT (da.enter OR da.fetch),
dfsToo: TRUE]];
};
SubtractStatus: PROC [f: DFFilter, s: Status, ConsumeFile: FileConsumer] RETURNS [DFFilter] ~ {
types: FileTypeFilter ~ FTFDifference[f.file.fileTypes, s.doneTypes];
comments: BOOL ~ f.comments;
strict: BOOL ~ f.file.strict AND types=allFileTypes;
Subtract: PROC [key: ROPE, val: REF ANY] RETURNS [quit: BOOL ¬ FALSE] ~ {
IF s.doneFiles.Fetch[key].found THEN {
IF ConsumeFile#NIL THEN ConsumeFile[key];
[] ¬ f.file.files.Delete[key]};
RETURN};
IF f.file.files#NIL AND s.doneFiles#NIL THEN IF f.file.files.Pairs[Subtract] THEN ERROR;
IF types=noFileTypes THEN RETURN [[comments, [types, NIL, strict]]];
RETURN [[comments, [types, f.file.files, strict]]]};
FTFDifference: PROC [a, b: FileTypeFilter] RETURNS [c: FileTypeFilter] ~ {
FOR p: Publicity IN Publicity DO FOR o: Ownership IN Ownership DO FOR d: Derivation IN Derivation DO c[p][o][d] ¬ a[p][o][d] AND NOT b[p][o][d] ENDLOOP ENDLOOP ENDLOOP;
};
FTFUnion: PROC [a, b: FileTypeFilter] RETURNS [c: FileTypeFilter] ~ {
FOR p: Publicity IN Publicity DO FOR o: Ownership IN Ownership DO FOR d: Derivation IN Derivation DO c[p][o][d] ¬ a[p][o][d] OR b[p][o][d] ENDLOOP ENDLOOP ENDLOOP;
};
EmptyFilter: PROC [dff: DFFilter] RETURNS [BOOL] ~ {
IF dff.file.fileTypes=noFileTypes THEN RETURN [TRUE];
IF dff.file.files=NIL THEN RETURN [FALSE];
RETURN [dff.file.files.GetSize[]=0];
};
DFPrint: PROC [item: REF ANY] RETURNS [stop: BOOL ¬ FALSE] --DFUtilities.ProcessItemProc-- ~ {
printLog.PutF1["%g\n", [refAny[item]]];
RETURN};
DFCPrint: PROC [item: REF ANY] RETURNS [stop, clip, dontCache: BOOL ¬ FALSE] --DFConsumer-- ~ {
printLog.PutF1["%g\n", [refAny[item]]];
RETURN};
printLog: IO.STREAM ¬ NIL;
EnumerateDFContents: PUBLIC PROC [df: FileSpec, Consumer: DFConsumer, filter: DFFilter ¬ everything, cache: EnumerationCache ¬ NIL, ConsumeFile: FileConsumer ¬ NIL] RETURNS [stopped, clipped, uncacheable: BOOL ¬ FALSE] ~ {
RETURN FullEnumerateDFContents[df, remote, Consumer, filter, cache, ConsumeFile]};
FullEnumerateDFContents: PUBLIC PROC [df: FileSpec, side: Side, Consumer: DFConsumer, filter: DFFilter ¬ everything, cache: EnumerationCache ¬ NIL, ConsumeFile: FileConsumer ¬ NIL] RETURNS [stopped, clipped, uncacheable: BOOL ¬ FALSE] ~ {
--if the filter has an explicit file table, upon exit files that have been enumerated in the past (including before this invocation) have been deleted from that table, and the deleted files have been enumerated to ConsumeFile-- NULL;
IF EmptyFilter[filter] THEN RETURN;
{rspec: FileSpec ~ RefineFileSpec[df !FS.Error => {SIGNAL FSErrorOnDF[error]; GOTO SkipDF}];
dfFileName: ROPE ~ rspec.name;
status: Status ~ GetStatus[cache, dfFileName];
rem: DFFilter ~ SubtractStatus[filter, status, ConsumeFile];
subStop, clip, dontCache: BOOL ¬ FALSE;
PassNest: PROC [bracket: Bracket] RETURNS [BOOL] ~ {
[subStop, clip, dontCache] ¬ Consumer[NEW [NestItem ¬ [df, rem, bracket]]];
IF subStop THEN stopped ¬ TRUE;
IF clip THEN clipped ¬ TRUE;
IF dontCache THEN uncacheable ¬ TRUE;
RETURN [subStop OR clip];
};
IF EmptyFilter[rem] THEN RETURN;
IF NOT PassNest[begin] THEN {
SubConsume: PROC [item: REF ANY] RETURNS [stop, clip, dontCache: BOOL ¬ FALSE] ~ {
WITH item SELECT FROM
x: REF DFU.FileItem => {
name: ROPE ~ DFU.RemoveVersionNumber[x.name];
IF status.doneFiles#NIL THEN [] ¬ status.doneFiles.Insert[name, $Done];
cache ¬ cache};
ENDCASE => NULL;
RETURN Consumer[item]};
SubConsumeFile: PROC [name: ROPE] ~ {
[] ¬ rem.file.files.Delete[name];
IF ConsumeFile#NIL THEN ConsumeFile[name];
RETURN};
queue: REF ANY ¬ NIL;
dirpub: Publicity;
dirown: Ownership;
okDirectory: BOOL ¬ FALSE;
PerItem: PROC [item: REF ANY] RETURNS [stop: BOOL ¬ FALSE] --DFU.ProcessItemProc-- ~ {
newQueue: REF ANY ¬ NIL;
PassQueue: PROC RETURNS [BOOL] ~ INLINE {
IF queue=NIL THEN RETURN [FALSE] ELSE {
[stop, clip, dontCache] ¬ Consumer[queue];
IF stop THEN stopped ¬ TRUE;
IF clip THEN stop ¬ clipped ¬ TRUE;
IF dontCache THEN uncacheable ¬ TRUE;
RETURN [stop]}
};
PassItem: PROC RETURNS [BOOL] ~ INLINE {
[stop, clip, dontCache] ¬ Consumer[item];
IF stop THEN stopped ¬ TRUE;
IF clip THEN stop ¬ clipped ¬ TRUE;
IF dontCache THEN uncacheable ¬ TRUE;
RETURN [stop];
};
{WITH item SELECT FROM
x: REF DFU.DirectoryItem => IF (okDirectory ¬ DirectoryPasses[dirpub ¬ IF x.exported THEN public ELSE private, dirown ¬ IF x.readOnly THEN imported ELSE defining, rem]) THEN {
IF PassQueue[] OR PassItem[] THEN GOTO Dun;
};
x: REF DFU.FileItem => IF okDirectory AND FilePasses[x, dirpub, dirown, rem] AND NOT PassQueue[] THEN {
name: ROPE ~ DFU.RemoveVersionNumber[x.name];
IF rem.file.files=NIL THEN {
IF status.doneFiles#NIL THEN [] ¬ status.doneFiles.Insert[name, $Done];
[] ¬ PassItem[];
}
ELSE IF rem.file.files.Delete[name] THEN {
IF status.doneFiles#NIL THEN [] ¬ status.doneFiles.Insert[name, $Done];
[] ¬ PassItem[];
IF ConsumeFile#NIL THEN ConsumeFile[name];
stop ¬ stop OR rem.file.files.GetSize[]=0;
};
};
x: REF DFU.ImportsItem => IF ImportPasses[x, rem] THEN {
IF PassQueue[] OR PassItem[] THEN GOTO Dun;
IF clip THEN ERROR;
{subDF: FileSpec ~ [x.path1, x.date];
subFilter: DFFilter ~ SubFilter[rem, x­];
subUncacheable: BOOL;
IF NOT EmptyFilter[subFilter] THEN {
[stop, , subUncacheable] ¬ FullEnumerateDFContents[subDF, remote, SubConsume, subFilter, cache, IF rem.file.files#NIL AND rem.file.files#subFilter.file.files THEN SubConsumeFile ELSE ConsumeFile];
IF stop THEN stopped ¬ TRUE;
IF subUncacheable THEN uncacheable ¬ TRUE;
};
}};
x: REF DFU.IncludeItem => IF NOT (PassQueue[] OR PassItem[]) THEN {
IF clip THEN ERROR;
{subDF: FileSpec ~ SELECT side FROM
local => [ShortPart[x.path1]],
remote => [x.path1, x.date],
ENDCASE => ERROR;
subFilter: DFFilter ¬ rem;
subUncacheable: BOOL;
subFilter.file.strict ¬ FALSE;
[stop, , subUncacheable] ¬ FullEnumerateDFContents[subDF, side, SubConsume, subFilter, cache, ConsumeFile];
IF stop THEN stopped ¬ TRUE;
IF subUncacheable THEN uncacheable ¬ TRUE;
}};
x: REF DFU.CommentItem => IF rem.comments THEN {
IF PassQueue[] OR PassItem[] THEN GOTO Dun;
};
x: REF DFU.WhiteSpaceItem => IF rem.comments THEN {
newQueue ¬ x;
};
ENDCASE => ERROR;
EXITS Dun => item ¬ item;
};
queue ¬ newQueue;
};
DFU.ParseFromFile[dfFileName, PerItem, ConsDFUFilter[rem], rspec.created.gmt !
DFU.FileSyntaxError => {SIGNAL SyntaxError[reason, dfFileName, position]; clipped ¬ TRUE; CONTINUE};
FS.Error => {SIGNAL FSErrorOnDF[error]; clipped ¬ TRUE; CONTINUE}];
IF stopped THEN uncacheable ¬ TRUE;
IF NOT (stopped OR clipped) THEN {
Complain: PROC [key: ROPE, val: REF ANY] RETURNS [quit: BOOL ¬ FALSE] --SymTab.EachPairAction-- ~ {
SIGNAL Miss[dfFileName, key];
};
IF rem.file.strict AND rem.file.files#NIL AND rem.file.files.Pairs[Complain] THEN ERROR;
};
IF rem.file.files=NIL AND NOT uncacheable THEN status.doneTypes ¬ FTFUnion[status.doneTypes, rem.file.fileTypes];
};
[] ¬ PassNest[end];
EXITS SkipDF => clipped ¬ TRUE};
RETURN};
DirectoryPasses: PROC [p: Publicity, o: Ownership, f: DFFilter] RETURNS [BOOL]
~ {RETURN [f.file.fileTypes[p][o] # ALL[FALSE]]};
FilePasses: PROC [x: REF DFU.FileItem, p: Publicity, o: Ownership, f: DFFilter] RETURNS [BOOL] ~ {
IF f.file.fileTypes[p][o] = ALL[TRUE] THEN RETURN [TRUE];
{d: Derivation ~ SELECT DFU.ClassifyFileExtension[x.name] FROM source => source, derived => derived, ENDCASE => ERROR;
RETURN [f.file.fileTypes[p][o][d]]}};
ImportPasses: PROC [x: REF DFU.ImportsItem, f: DFFilter] RETURNS [BOOL] ~ {
p: Publicity ~ IF x.exported THEN public ELSE private;
RETURN [f.file.fileTypes[p][imported] # ALL[FALSE]];
};
SubFilter: PROC [parent: DFFilter, ii: DFU.ImportsItem] RETURNS [sub: DFFilter] ~ {
pp: Publicity ~ IF ii.exported THEN public ELSE private;
need: ARRAY Derivation OF BOOL ¬ ALL[FALSE];
sub.comments ¬ parent.comments;
FOR p: Publicity IN Publicity DO
x: BOOL ~ SELECT ii.form FROM
exports => p=public,
all, list => TRUE,
ENDCASE => ERROR;
FOR o: Ownership IN Ownership DO FOR d: Derivation IN Derivation DO
IF (sub.file.fileTypes[p][o][d] ¬ x AND parent.file.fileTypes[pp][imported][d]) THEN need[d] ¬ TRUE;
ENDLOOP ENDLOOP;
ENDLOOP;
IF (ii.list#NIL) # (ii.form=list) THEN ERROR;
SELECT ii.form FROM
list => {filterA: DFU.FilterA ~ IF need[source]=need[derived] THEN all ELSE IF need[source] THEN source ELSE derived;
sub.file.files ¬ SymTab.Create[case: FALSE];
FOR i: NATURAL IN [0 .. ii.list.nEntries) DO
name: ROPE ~ ii.list[i].name;
take: BOOL ~ (parent.file.files=NIL OR parent.file.files.Fetch[name].found) AND (filterA=all OR filterA=DFU.ClassifyFileExtension[name]);
IF take THEN [] ¬ sub.file.files.Insert[name, $Needed];
ENDLOOP;
IF filterA#all THEN FOR p: Publicity IN Publicity DO FOR o: Ownership IN Ownership DO
sub.file.fileTypes[p][o][source] ¬ sub.file.fileTypes[p][o][derived] ¬ sub.file.fileTypes[p][o][source] OR sub.file.fileTypes[p][o][derived];
ENDLOOP ENDLOOP;
sub.file.strict ¬ sub.file.fileTypes=allFileTypes;
};
exports, all => {
sub.file.files ¬ parent.file.files;
sub.file.strict ¬ FALSE;
};
ENDCASE => ERROR;
RETURN};
ConsDFUFilter: PROC [rem: DFFilter] RETURNS [f: DFU.Filter] ~ {
need: ARRAY Attribute OF BOOL ¬ ALL[FALSE];
f ¬ [comments: rem.comments];
<<don't waste time in lower levels CONSing intersected lists: IF rem.file.files#NIL THEN {
MoveEntry: PROC [key: ROPE, val: REF ANY] RETURNS [quit: BOOL ¬ FALSE] --SymTab.EachPairAction-- ~ {
f.list.u[f.list.nEntries] ¬ [name: key];
f.list.nEntries ¬ f.list.nEntries + 1;
};
f.list ¬ NEW [DFU.UsingList[rem.file.files.GetSize[]]];
f.list.nEntries ¬ 0;
IF rem.file.files.Pairs[MoveEntry] THEN ERROR;
};>>
FOR d: Derivation IN Derivation DO FOR p: Publicity IN Publicity DO FOR o: Ownership IN Ownership DO
IF rem.file.fileTypes[p][o][d] THEN need[d] ¬ need[p] ¬ need[o] ¬ TRUE;
ENDLOOP ENDLOOP ENDLOOP;
f.filterA ¬ all <<don't waste time checking this twice: IF NOT need[source] THEN derived ELSE IF need[derived] THEN all ELSE source>>;
f.filterB ¬ all <<don't trust lower level software to do this right: IF NOT need[public] THEN private ELSE IF need[private] THEN all ELSE public>>;
f.filterC ¬ IF NOT need[defining] THEN imported ELSE IF need[imported] THEN all ELSE defining;
};
RefineFileSpec: PROC [fs: FileSpec] RETURNS [rfs: FileSpec] ~ {
rfs ¬ [NIL, [explicit]];
[fullFName: rfs.name, created: rfs.created.gmt] ¬ FS.FileInfo[name: fs.name, wantedCreatedTime: fs.created.gmt];
RETURN};
PrintCache: PROC [out: IO.STREAM, cache: EnumerationCache] ~ {
PerEntry: PROC [key: ROPE, val: REF ANY] RETURNS [quit: BOOL ¬ FALSE] --SymTab.EachPairAction-- ~ {
status: Status ~ NARROW[val];
out.PutRope[key.Concat[": "]];
FOR p: Publicity IN Publicity DO FOR o: Ownership IN Ownership DO FOR d: Derivation IN Derivation DO
out.PutChar[IF status.doneTypes[p][o][d] THEN 'X ELSE '.];
ENDLOOP ENDLOOP ENDLOOP;
out.PutRope["\n"];
};
IF cache.nameToStatus.Pairs[PerEntry] THEN ERROR;
};
PrintSymbolTable: PROC [out: IO.STREAM, table: SymbolTable] ~ {
PerEntry: PROC [key: ROPE, val: REF ANY] RETURNS [quit: BOOL ¬ FALSE] --SymTab.EachPairAction-- ~ {
status: Status ~ NARROW[val];
out.PutRope[key.Concat[" "]];
};
IF table.Pairs[PerEntry] THEN ERROR;
};
BringOver: PUBLIC PROC [dfFile: ROPE, filter: FileFilter ¬ allFiles, action: BringOverAction ¬ [], interact: InteractionProc ¬ NIL, clientData: REF ¬ NIL, log, errlog: IO.STREAM ¬ NIL, workingDir: ROPE ¬ NIL, cache: BringOverCache ¬ NIL] RETURNS [errors, warnings, filesActedUpon: INT] ~ {
doSomething: BOOL ~ action.enter OR action.fetch;
client: DFI.Client ~ NEW [DFI.ClientDescriptor ¬ [
proc: IF interact = NIL THEN DFI.DefaultInteractionProc ELSE interact,
data: clientData,
log: log,
errlog: errlog,
workingDir: workingDir]];
GiveInfo: PROC [class: DFO.InfoClass, fmt: ROPE, v: LIST OF IO.Value ¬ NIL] ~ {
DFI.SimpleInteraction[client, NEW [DFO.InfoInteraction ¬ [class, IO.PutFLR[fmt, v]]]];
SELECT class FROM
info, abort => NULL;
warning => warnings ¬ warnings+1;
error => errors ¬ errors+1;
ENDCASE => ERROR;
};
fromDir: ROPE ¬ NIL;
stack: RopeList ¬ NIL;
stopped: BOOL ¬ FALSE;
PerItem: PROC [item: REF ANY] RETURNS [stop, clip, dontCache: BOOL ¬ FALSE] --DFConsumer-- ~ {
ENABLE {
DFI.AbortDF => {stopped ¬ stop ¬ TRUE; CONTINUE};
FS.Error => {
GiveInfo[error, "FS.Error%g --- skipping", LIST[[rope[FmtFSError[error]]]]];
CONTINUE};
};
Work: PROC [fromFileName, nameOhneVersion: ROPE, date: Date] ~ {
toFileName: ROPE ~ FS.ExpandName[nameOhneVersion, workingDir !FS.Error => {GiveInfo[error, "Bad file name %g for local directory %g in %g", LIST[[rope[nameOhneVersion]], [rope[workingDir]], [rope[stack.first]]]]; GOTO DunWithFile}].fullFName;
fromCreated: BasicTime.GMT ¬ date.gmt;
toCreated: BasicTime.GMT ¬ BasicTime.nullGMT;
attachment: ROPE ¬ NIL;
[attachedTo: attachment, created: toCreated] ¬ FS.FileInfo[name: toFileName, remoteCheck: FALSE !FS.Error => CONTINUE];
attachment ¬ PFS.RopeFromPath[PFS.PathFromRope[attachment], slashes];
IF action.suspicious OR date.format#explicit THEN {
[fullFName: fromFileName, created: fromCreated] ¬ FS.FileInfo[name: fromFileName, wantedCreatedTime: date.gmt, remoteCheck: TRUE--because version variables must be bound relative to server, not local cache-- !FS.Error => {
IF date.format=explicit THEN GiveInfo[error, "%g of %g not accessible", LIST[[rope[fromFileName]], [time[date.gmt]]]]
ELSE GiveInfo[error, "%g not accessible", LIST[[rope[fromFileName]]]];
GOTO DunWithFile}];
};
IF doSomething THEN {
fromUName: ROPE ¬ PFS.PFSNameToUnixName[PFS.PathFromRope[fromFileName]];
wrongAttachment: BOOL ~ IF action.enter
THEN
SELECT Rope.Run[s1: fromUName, s2: attachment, case: TRUE] FROM
< attachment.Index[s2: "!"] => TRUE,
< fromFileName.Index[s2: "!"] => TRUE,
ENDCASE => FALSE
ELSE attachment.Length[]#0;
wrongDate: BOOL ~ SELECT date.format FROM
explicit, notEqual, omitted => BasicTime.Period[fromCreated, toCreated]#0,
greaterThan => BasicTime.Period[fromCreated, toCreated]<0,
ENDCASE => ERROR;
IF wrongDate OR wrongAttachment THEN {
old: BOOL ~ action.confirmEarlier AND toCreated#BasicTime.nullGMT AND BasicTime.Period[from: fromCreated, to: toCreated] > 0;
global: BOOL ~ action.confirmGlobalDestination AND (FALSE); -- for PCedar, these days, we want to treat all files as local when a local file is required, global when a global file is required.
blunder: BOOL ~ old OR global;
descr: ROPE ~ IF toCreated=BasicTime.nullGMT THEN " (previously non-existant)"
ELSE IF attachment.Length[]#0 THEN IO.PutFR[" (previously %g, => %g)", [time[toCreated]], [rope[attachment]]]
ELSE IO.PutFR1[" (previously %g)", [time[toCreated]]];
IF NOT DFI.YesOrNo[
client,
IO.PutFLR["%g%g to %g%g ?", LIST[
[rope[fromFileName]],
[rope[IF fromCreated#BasicTime.nullGMT THEN IO.PutFR1[" {%g}", [time[fromCreated]]] ELSE ""]],
[rope[toFileName]],
[rope[descr]]
]],
NOT old,
old]
THEN {
GiveInfo[warning, IF old THEN "%g NOT updated: userSaidNo (probably date mixup)" ELSE "%g NOT updated: userSaidNo", LIST[[rope[toFileName]]]];
}
ELSE IF global AND NOT DFI.YesOrNo[
client,
"Are you sure you want to store into a global directory ?",
FALSE,
TRUE]
THEN {
GiveInfo[warning, "%g NOT updated: userSaidNo (probably because to global directory)" , LIST[[rope[toFileName]]]];
}
ELSE {
filesActedUpon ¬ filesActedUpon + 1;
DFI.SimpleInteraction[client, NEW [DFO.FileInteraction ¬ [
localFile: toFileName,
remoteFile: fromFileName,
dateFormat: SELECT date.format FROM
explicit => explicit,
omitted => notEqual,
greaterThan => greaterThan,
notEqual => notEqual,
ENDCASE => ERROR,
date: fromCreated,
action: IF action.dontDoit THEN check ELSE fetch]]];
IF NOT action.dontDoit THEN {
[] ¬ FS.Copy[
to: toFileName,
from: fromFileName,
wantedCreatedTime: fromCreated,
setKeep: FALSE,
keep: IF DFU.ClassifyFileExtension[toFileName] = source THEN 2 ELSE 1,
remoteCheck: action.suspicious,
attach: action.enter
! FS.Error => {GiveInfo[error, "FS.Error%g while copying %g to %g", LIST[[rope[FmtFSError[error]]], [rope[fromFileName]], [rope[toFileName]]]]; GOTO DunWithFile}
];
IF action.fetch AND action.enter THEN {
ENABLE FS.Error => {GiveInfo[error, "FS.Error%g while fetching %g to %g", LIST[[rope[FmtFSError[error]]], [rope[fromFileName]], [rope[toFileName]]]]; GOTO DunWithFile};
FS.Close[FS.Open[toFileName]];
};
};
};
};
};
EXITS DunWithFile => date ¬ date;
};
WITH item SELECT FROM
x: REF DFU.DirectoryItem => {
fromDir ¬ FS.ExpandName[x.path1 !FS.Error => {GiveInfo[error, "FS.Error%g canonizing directory; skipping DF file", LIST[[rope[FmtFSError[error]]]]]; clip ¬ TRUE}].fullFName;
};
x: REF DFU.FileItem => {
Work[
fromFileName: FS.ExpandName[x.name, fromDir !FS.Error => {GiveInfo[error, "Bad file name %g in directory %g in %g", LIST[[rope[x.name]], [rope[fromDir]], [rope[stack.first]]]]; CONTINUE}].fullFName,
nameOhneVersion: DFU.RemoveVersionNumber[x.name],
date: x.date];
};
x: REF DFU.ImportsItem => NULL;
x: REF DFU.IncludeItem => NULL;
x: REF DFU.CommentItem => ERROR;
x: REF DFU.WhiteSpaceItem => ERROR;
x: REF NestItem => {
DFI.SimpleInteraction[client, NEW [DFO.DFInfoInteraction ¬ [
action: SELECT x.bracket FROM begin => start, end => IF stopped THEN abort ELSE end, ENDCASE => ERROR,
dfFile: x.df.name
]]];
SELECT x.bracket FROM
begin => stack ¬ CONS[x.df.name, stack];
end => stack ¬ stack.rest;
ENDCASE => ERROR;
IF action.dfsToo AND x.bracket=begin THEN Work[
fromFileName: x.df.name,
nameOhneVersion: ShortPart[x.df.name !FS.Error => {GiveInfo[error, "Bad DF file name %g", LIST[[rope[x.df.name]]]]; CONTINUE}],
date: x.df.created];
};
ENDCASE => ERROR;
};
wDir: ROPE ~ FS.GetWDir[workingDir];
ec: EnumerationCache ~ GetEnumCache[cache, wDir, action];
errors ¬ warnings ¬ filesActedUpon ¬ 0;
NULL; {
ENABLE {
FSErrorOnDF => {
GiveInfo[error, "FS.Error%g --- aborting that DF file", LIST[[rope[FmtFSError[error]]]]];
RESUME};
SyntaxError => {
GiveInfo[error, "Syntax error in '%g'[%d]: %g\NProcessing of this DF file aborted.", LIST[[rope[dfFileName]], [cardinal[position]], [rope[reason]]]];
RESUME};
Miss => {
GiveInfo[warning, "'%g' could not be found in %g", LIST[[rope[dataFileName]], [rope[dfFileName]]]];
RESUME};
};
[] ¬ EnumerateDFContents[[FS.ExpandName[dfFile, workingDir].fullFName], PerItem, [comments: FALSE, file: filter], ec, NIL];
errors ¬ errors};
};
ShortPart: PROC [given: ROPE] RETURNS [short: ROPE] ~ {
fullFName: ROPE;
cp: FS.ComponentPositions;
[fullFName, cp] ¬ FS.ExpandName[given];
short ¬ fullFName.Substr[start: cp.base.start, len: cp.ext.start+cp.ext.length-cp.base.start];
};
FmtFSError: PROC [error: FS.ErrorDesc] RETURNS [asRope: ROPE] ~ {
asRope ¬ IO.PutFR["[%g, %g, %g]", [rope[groupName[error.group]]], [atom[error.code]], [rope[error.explanation]]];
};
groupName: ARRAY FS.ErrorGroup OF ROPE ~ [
ok: "ok",
bug: "bug",
environment: "environment",
client: "client",
user: "user"];
END.