<> <> DIRECTORY BasicTime USING [Period], DFInternal USING [ AbortDF, CheckAbort, Client, ClientDescriptor, DefaultInteractionProc, GetFileInfo, 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, Open, StreamOpen], IO USING [card, Close, GetIndex, PutFR, rope, STREAM], Rope USING [Cat, Concat, Equal, ROPE]; BringOverImpl: CEDAR PROGRAM IMPORTS BasicTime, DFInternal, DFUtilities, FS, IO, Rope EXPORTS DFOperations = BEGIN OPEN Int: DFInternal, Ops: DFOperations, Utils: DFUtilities; ROPE: TYPE = Rope.ROPE; BringOver: PUBLIC PROC [ dfFile: ROPE, filter: Ops.BringOverFilter _ [], action: Ops.BringOverAction _ $enter, interact: Ops.InteractionProc _ NIL, clientData: REF ANY _ NIL, log: IO.STREAM _ NIL] RETURNS [errors, warnings, filesActedUpon: INT _ 0] = { client: Int.Client = NEW[Int.ClientDescriptor _ [ (interact _ IF interact = NIL THEN Int.DefaultInteractionProc ELSE interact), clientData, log ]]; retrievedFiles: REF Utils.UsingList _ NIL; -- for error reporting only ConvertFilter: PROC [f: Ops.BringOverFilter] RETURNS [filter: Utils.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[Utils.UsingList[nEntries]]; FOR l: LIST OF ROPE _ f.list, l.rest UNTIL l = NIL DO file: ROPE = Utils.RemoveVersionNumber[l.first]; IF ~(filter.filterA = $all OR Utils.ClassifyFileExtension[file] = filter.filterA) THEN { warnings _ warnings.SUCC; Int.SimpleInteraction[ client, NEW[Ops.InfoInteraction _ [ class: $warning, message: Rope.Cat["'", file, "' could not be found in any nested DF file."] ]] ]; }; filter.list.u[i] _ Utils.UsingEntry[name: file]; i _ i.SUCC; ENDLOOP; filter.list.nEntries _ nEntries; Utils.SortUsingList[usingList: filter.list, nearlySorted: FALSE]; }; }; BringOverInner: PROC [dfFile: ROPE, date: Utils.Date, filter: Utils.Filter] RETURNS [retrieved: NAT _ 0] = { <> requested: NAT = IF filter.list ~= NIL THEN filter.list.nEntries ELSE NAT.LAST; <> iAllocatedRetrievedFiles: BOOL _ FALSE; directoryPath: ROPE _ NIL; Localize: PROC [remote: ROPE, date: Utils.Date, action: Ops.BringOverAction] RETURNS [localInfo: REF Int.LocalFileInfo, dontProcess: BOOL _ FALSE] = { remoteInfo: REF Int.RemoteFileInfo _ NEW[Int.RemoteFileInfo _ [remote, date]]; AttachNeeded: PROC RETURNS [action: {alreadyLocal, doAttach, userSaidNo}] = { attach: BOOL _ TRUE; sameDate: BOOL = (localInfo.date.gmt = remoteInfo.date.gmt); IF Int.LocalFile[remoteInfo.name] THEN RETURN[$alreadyLocal]; <> <> IF localInfo.date.format = $explicit AND Rope.Equal[ Utils.RemoveVersionNumber[localInfo.attachedTo], Utils.RemoveVersionNumber[remoteInfo.name], FALSE] THEN <> SELECT date.format FROM $greaterThan => attach _ BasicTime.Period[from: localInfo.date.gmt, to: remoteInfo.date.gmt] > 0; ENDCASE => attach _ ~sameDate; IF ~attach THEN RETURN[$alreadyLocal]; IF ~sameDate AND ~Int.YesOrNo[ client: client, message: IO.PutFR[ "%g {%g} to %g%g?", IO.rope[remoteInfo.name], IO.rope[Utils.DateToRope[remoteInfo.date]], IO.rope[localInfo.name], IO.rope[ IF localInfo.date.format = $explicit THEN IO.PutFR[ " (previously: %g, keep: %d) ", IO.rope[Utils.DateToRope[localInfo.date]], IO.card[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 Utils.ClassifyFileExtension[localInfo.name] = $source AND ~Int.YesOrNo[ client: client, message: IO.PutFR[ "Are you sure? (%g {%g} is older than %g {%g} and will overwrite it)", IO.rope[remoteInfo.name], IO.rope[Utils.DateToRope[remoteInfo.date]], IO.rope[localInfo.name], IO.rope[Utils.DateToRope[localInfo.date]] ], default: TRUE, blunder: TRUE ] THEN RETURN[$userSaidNo]; RETURN[$doAttach] }; localInfo _ NEW[Int.LocalFileInfo _ [name: Int.ShortName[remote ! FS.Error => {Int.ReportFSError[error, remoteInfo, client]; GO TO quit} ]]]; Int.GetFileInfo[info: localInfo, notFoundOK: TRUE, client: client ! FS.Error => GO TO quit]; localInfo.name _ Utils.RemoveVersionNumber[localInfo.name]; IF ~(date.format = $explicit AND action = $enter) THEN Int.GetFileInfo[info: remoteInfo, client: client ! FS.Error => GO TO quit]; SELECT AttachNeeded[] FROM $alreadyLocal => NULL; $doAttach => { Int.SimpleInteraction[ client, NEW[Ops.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 Utils.ClassifyFileExtension[localInfo.name] = $source THEN 2 ELSE 1, remoteCheck: action ~= $enter, attach: TRUE ! FS.Error => {Int.ReportFSError[error, localInfo, client]; GO TO quit} ]; filesActedUpon _ filesActedUpon.SUCC; }; $userSaidNo => dontProcess _ TRUE; ENDCASE; IF ~ dontProcess AND action = $fetch THEN { ENABLE FS.Error => {Int.ReportFSError[error, remoteInfo, client]; GO TO quit}; FS.Close[FS.Open[localInfo.name]]; }; EXITS quit => {errors _ errors.SUCC; dontProcess _ TRUE}; }; DoOneItem: Utils.ProcessItemProc = { Int.CheckAbort[client]; IF retrievedFiles ~= NIL AND retrievedFiles.nEntries = retrievedFiles.length THEN RETURN[TRUE]; WITH item SELECT FROM directory: REF Utils.DirectoryItem => directoryPath _ directory.path1; file: REF Utils.FileItem => { remoteName: ROPE = Rope.Concat[directoryPath, file.name]; localInfo: REF Int.LocalFileInfo = Localize[remoteName, file.date, action].localInfo; IF retrievedFiles ~= NIL THEN { <> retrievedFiles.u[retrievedFiles.nEntries] _ [name: Int.ShortName[localInfo.name]]; retrievedFiles.nEntries _ retrievedFiles.nEntries.SUCC; }; IF filter.list ~= NIL AND (retrieved _ retrieved.SUCC) = filter.list.nEntries THEN RETURN[TRUE]; }; imports: REF Utils.ImportsItem => { newFilter: Utils.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 => <> [] _ BringOverInner[imports.path1, imports.date, newFilter]; newFilter.list.nEntries = 0 => <> NULL; ENDCASE => { <> retrievedBelow: NAT = BringOverInner[imports.path1, imports.date, newFilter]; IF filter.list ~= NIL THEN { <> IF requested = (retrieved _ retrieved + retrievedBelow) THEN RETURN[TRUE]; IF imports.form = $list THEN { <> filter.list _ Utils.DifferenceOfUsingLists[filter.list, newFilter.list]; <> RETURN[filter.list.nEntries = 0] }; }; }; }; include: REF Utils.IncludeItem => retrieved _ BringOverInner[include.path1, include.date, filter] + retrieved; ENDCASE; }; dfLocalInfo: REF Int.LocalFileInfo; dontProcess: BOOL; [dfLocalInfo, dontProcess] _ Localize[dfFile, date, $fetch]; IF ~dontProcess THEN { dfStream: IO.STREAM = FS.StreamOpen[dfLocalInfo.name ! FS.Error => {Int.ReportFSError[error, dfLocalInfo, client]; GO TO skip}; ]; Int.SimpleInteraction[ client, NEW[Ops.DFInfoInteraction _ [action: $start, dfFile: dfFile]] ]; IF filter.list ~= NIL AND retrievedFiles = NIL THEN { retrievedFiles _ NEW[Utils.UsingList[filter.list.nEntries]]; retrievedFiles.nEntries _ 0; iAllocatedRetrievedFiles _ TRUE; }; Utils.ParseFromStream[dfStream, DoOneItem, filter ! Utils.SyntaxError -- [reason: ROPE]-- => { errors _ errors.SUCC; Int.SimpleInteraction[ client, NEW[Ops.InfoInteraction _ [ class: $error, message: IO.PutFR[ "Syntax error in '%g'[%d]: %g\NProcessing of this DF file aborted.", IO.rope[dfLocalInfo.name], IO.card[dfStream.GetIndex[]], IO.rope[reason] ] ]] ]; CONTINUE }; Int.AbortDF => dfStream.Close[]; ]; dfStream.Close[]; IF iAllocatedRetrievedFiles THEN { IF retrievedFiles.nEntries ~= filter.list.nEntries THEN { <> diff: REF Utils.UsingList; Utils.SortUsingList[retrievedFiles, FALSE]; diff _ Utils.DifferenceOfUsingLists[filter.list, retrievedFiles]; FOR i: NAT IN [0..diff.nEntries) DO IF filter.filterA = $all OR Utils.ClassifyFileExtension[diff.u[i].name] = filter.filterA THEN { warnings _ warnings.SUCC; Int.SimpleInteraction[ client, NEW[Ops.InfoInteraction _ [ class: $warning, message: Rope.Cat["'", diff.u[i].name, "' could not be found in any nested DF file."] ]] ]; }; ENDLOOP; }; retrievedFiles _ NIL; }; Int.SimpleInteraction[ client, NEW[Ops.DFInfoInteraction _ [action: $end, dfFile: dfFile]] ]; EXITS skip => errors _ errors.SUCC; }; }; <> [] _ BringOverInner[dfFile, [format: $explicit], ConvertFilter[filter] ! ABORTED => { Int.SimpleInteraction[client, NEW[Ops.AbortInteraction _ [TRUE]]]; <> }; Int.AbortDF => { errors _ errors.SUCC; Int.SimpleInteraction[client, NEW[Ops.DFInfoInteraction _ [action: $abort, dfFile: dfFile]]]; CONTINUE }; ]; }; END.