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, 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 ] 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; }; }; uFilter: Utils.Filter = ConvertFilter[filter]; IF interact = NIL THEN interact _ Int.DefaultInteractionProc; [] _ BringOverInner[dfFile, [], uFilter ! 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. PBringOverImpl.mesa last edited by Levin on November 22, 1983 11:22 pm The following is a cheat; it assumes that Ops.BringOverFilterN and Utils.FilterN are in the same order, for N = A, B, C. LOOPHOLE would also work. Note: 'retrieved' is maintained, and therefore meaningful, only if filter.list ~= NIL. 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. Assert: remoteInfo.date.gmt ~= BasicTime.nullGMT Assert: (localInfo.date.format = $explicit and localInfo.date.gmt ~= BasicTime.nullGMT) iff local file exists. 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. 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. 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'. 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. 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. 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. In this case, we were invoked with an explicit filter.list and the imports item had one as well. Utils.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 Utils.DifferenceOfUsingLists is met. 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. There were some files specified in the explicit filter list that weren't retrieved. We would like to RESUME at this point, but until ABORTED is redeclared as a SIGNAL, it won't work. Ê ò˜Jšœ™Jšœ2™2J˜šÏk ˜ Jšœ œ ˜šœ œ˜Jšœ´˜´—šœ œ˜Jšœ†˜†—šœ œ˜Jšœæ˜æ—Jšœœ(˜0Jšœœ&œ˜6Jšœœœ˜&—J˜šœœ˜Jšœ%œœ˜8Jšœ˜—J˜Jš˜J˜Jšœ8˜˜>Jšœ œ˜Jšœœ7œœ˜MJšœ'˜+Jšœœ8œœ˜GJšœ˜——Jšœ œ˜%J˜—Jšœœ˜"Jšœ˜—šœœœ˜+Jšœœ9œœ˜NJšœœ˜"J˜—š˜Jšœœœ˜3—J˜—šœ$˜$Jšœ˜šœœœ1˜QJšœœ˜ —šœœœ˜šœ œ˜%Jšœ ˜ —šœœ˜Jšœ œ)˜9Jšœ œG˜Ušœœœ˜Jšœ×™×JšœR˜RJšœ2œ˜7J˜—š œœœœ˜RJšœœ˜ —J˜—šœ œ˜#šœ˜JšœŸ/˜KJšœŸ6˜QJšœ œœ œ˜EJšœŸ?˜QJšœœœ œ ˜@J˜—šœœ˜šœœ˜Jšœ­™­Jšœ<˜<—šœ˜Jšœ¡™¡Jšœ˜—šœ˜ Jšœœ‚™šJšœœ:˜Mšœœœ˜Jšœ“œ¿™ÕJšœ6œœœ˜Jšœœ˜Jšœù™ùJšœH˜HJšœ¢œ¦œ›™éJšœ˜ J˜—J˜—J˜——J˜—šœ œ˜!JšœL˜L—Jšœ˜—J˜—Jšœ œ˜#Jšœ œ˜Jšœ<˜<šœœ œ˜šœ œœœ˜4Jšœœ:œœ˜JJšœ˜—šœ˜Jšœ˜Jšœ:˜=Jšœ˜—š œœœœœ˜5Jšœœ(˜œ˜_Jšœœ˜˜J˜šœ˜J˜J˜UJ˜—J˜—J˜—Jšœ˜—J˜—Jšœœ˜J˜—šœ˜Jšœ˜Jšœ8˜;Jšœ˜—š˜Jšœœ˜—J˜—J˜—Jšœ.˜.Jšœ œœ'˜=šœ)˜)šœ˜ Jšœœœ˜BJšœœœœ™bJšœ˜—šœ˜Jšœœ˜Jšœœ<˜]Jš˜Jšœ˜—J˜—J˜—J˜Jšœ˜—…—%x>º