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.