<> <> <> <> <> <> 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]; <> 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 <>; f.filterB ¬ all <>; 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.