DIRECTORY BasicTime USING [GMT, nullGMT, Period], DFInternal USING [ AbortDF, CheckAbort, Client, ClientDescriptor, DefaultInteractionProc, DoAbort, GetFileInfo, LocalFileInfo, RemoteFileInfo, ReportFSError, ShortName, SimpleInteraction, YesOrNo], DFOperations USING [ AbortInteraction, DFInfoInteraction, FileAction, FileInteraction, InfoInteraction, InteractionProc, SModelAction], DFUtilities USING [ Date, DateFormat, DateToRope, DirectoryItem, FileItem, Filter, GetVersionNumber, ImportsItem, IncludeItem, ParseFromStream, ProcessItemProc, RemoveVersionNumber, SyntaxError, WriteItemToStream], FS USING [ Copy, Create, Delete, Error, GetInfo, GetName, OpenFile, OpenFileFromStream, Rename, StreamFromOpenFile, StreamOpen], FSExtras USING [NewCopy], IO USING [card, Close, GetIndex, PutFR, rope, STREAM], Rope USING [Concat, Equal, Length, ROPE]; SModelImpl: CEDAR MONITOR IMPORTS BasicTime, DFInternal, DFUtilities, FS, FSExtras, IO, Rope EXPORTS DFOperations = BEGIN OPEN Int: DFInternal, Ops: DFOperations, Utils: DFUtilities; ROPE: TYPE = Rope.ROPE; nSModels: NAT _ 0; SModel: PUBLIC PROC [ dfFile: ROPE, action: Ops.SModelAction _ [], 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]]; tempDF: ROPE = "SModelTemporaryDF$"; NewTemporaryDF: ENTRY PROC [pages: INT] RETURNS [out: IO.STREAM] = { outFile: FS.OpenFile; nSModels _ nSModels.SUCC; outFile _ FS.Create[ name: tempDF, pages: pages, setKeep: TRUE, keep: IF nSModels = 1 THEN 1 ELSE CARDINAL.LAST ! FS.Error => { localInfo: REF Int.LocalFileInfo _ NEW[Int.LocalFileInfo _ [name: tempDF]]; Int.ReportFSError[error, localInfo, client, $abort]; } ]; RETURN[FS.StreamFromOpenFile[outFile, $write]] }; CloseTemporaryDF: PROC [out: IO.STREAM] RETURNS [tempName: ROPE] = { outFile: FS.OpenFile = FS.OpenFileFromStream[out]; tempName _ outFile.GetName[].fullFName; -- includes version number out.Close[]; }; FinishWithTemporaryDF: ENTRY PROC [ tempName: ROPE, localName: ROPE, commit: BOOL _ TRUE] = { IF commit THEN FS.Rename[from: tempName, to: localName] ELSE FS.Delete[tempName]; nSModels _ nSModels.PRED; }; UserConfirms: PROC [localInfo: REF Int.LocalFileInfo, remoteInfo: REF Int.RemoteFileInfo] RETURNS [BOOL] = { RETURN[Int.YesOrNo[ client: client, message: IO.PutFR[ "%g {%g} to %g ?", IO.rope[localInfo.name], IO.rope[Utils.DateToRope[localInfo.date]], IO.rope[Utils.RemoveVersionNumber[remoteInfo.name]], ], default: TRUE ]] }; SModelInner: PROC [dfFile: ROPE, date: Utils.Date] RETURNS [REF Int.RemoteFileInfo] = { directoryPath: ROPE _ NIL; somethingChanged: BOOL _ FALSE; dfRemoteInfo: REF Int.RemoteFileInfo = NEW[Int.RemoteFileInfo _ [name: dfFile, date: date]]; dfLocalInfo: REF Int.LocalFileInfo = NEW[Int.LocalFileInfo _ [name: Int.ShortName[dfFile]]]; DoOneItem: Utils.ProcessItemProc = { StoreThisFile: PROC [ localInfo: REF Int.LocalFileInfo, remoteInfo: REF Int.RemoteFileInfo, formatFromDF: Utils.DateFormat] RETURNS [store: BOOL] = { lGMT: BasicTime.GMT = localInfo.date.gmt; -- can't be nullGMT rGMT: BasicTime.GMT = remoteInfo.date.gmt; -- may be nullGMT IF localInfo.attachedTo.Length[] = 0 THEN RETURN[TRUE]; IF ~Rope.Equal[ Utils.RemoveVersionNumber[remoteInfo.name], Utils.RemoveVersionNumber[localInfo.attachedTo], FALSE] THEN { IF remoteInfo.date = localInfo.date AND Rope.Equal[ Int.ShortName[remoteInfo.name], Int.ShortName[localInfo.attachedTo], FALSE] THEN { FS.Copy[ to: Utils.RemoveVersionNumber[localInfo.name], from: remoteInfo.name, wantedCreatedTime: remoteInfo.date.gmt, remoteCheck: action.remoteCheck, attach: TRUE ! FS.Error => GO TO storeAnyway ]; RETURN[FALSE]; EXITS storeAnyway => NULL; }; RETURN[TRUE] }; SELECT formatFromDF FROM $explicit, $omitted => RETURN[lGMT ~= rGMT]; $notEqual => RETURN[action.remoteCheck AND lGMT ~= rGMT]; $greaterThan => SELECT TRUE FROM ~action.remoteCheck => RETURN[FALSE]; remoteInfo.date.format ~= $explicit => RETURN[TRUE]; -- no extant remote file ENDCASE => SELECT BasicTime.Period[from: lGMT, to: rGMT] FROM < 0 => RETURN[TRUE]; = 0 => RETURN[FALSE]; > 0 => { warnings _ warnings.SUCC; Int.SimpleInteraction[ client, NEW[Ops.InfoInteraction _ [ class: $warning, message: IO.PutFR["%g {%g} is referenced with '>' in the DF file but is older than the remote version %g {%g}.", IO.rope[localInfo.name], IO.rope[Utils.DateToRope[localInfo.date]], IO.rope[remoteInfo.name], IO.rope[Utils.DateToRope[remoteInfo.date]] ] ]] ]; RETURN[FALSE] }; ENDCASE; ENDCASE; }; ReportMissing: PROC [localInfo: REF Int.LocalFileInfo] = { warnings _ warnings.SUCC; Int.SimpleInteraction[ client, NEW[Ops.InfoInteraction _ [ class: $warning, message: IO.PutFR["'%g' doesn't exist.", IO.rope[localInfo.name]] ]] ]; }; SelfReference: PROC [shortName: ROPE] RETURNS [BOOL] = { RETURN[shortName.Equal[dfLocalInfo.name, FALSE]] }; Int.CheckAbort[client]; WITH item SELECT FROM directory: REF Utils.DirectoryItem => directoryPath _ directory.path1; file: REF Utils.FileItem => { shortName: ROPE = Utils.RemoveVersionNumber[file.name]; localInfo: REF Int.LocalFileInfo _ NEW[Int.LocalFileInfo _ [shortName]]; remoteInfo: REF Int.RemoteFileInfo _ NEW[Int.RemoteFileInfo _ [ name: directoryPath.Concat[file.name], date: file.date ]]; Int.GetFileInfo[localInfo ! FS.Error => IF error.group = $user THEN {ReportMissing[localInfo]; GO TO skip} ELSE Int.ReportFSError[error, localInfo, client, $abort] ]; IF action.remoteCheck THEN { remoteInfo.date _ localInfo.date; Int.GetFileInfo[info: remoteInfo, notFoundOK: TRUE, client: client, errorLevel: $abort]; }; remoteInfo.name _ Utils.RemoveVersionNumber[remoteInfo.name]; SELECT TRUE FROM SelfReference[shortName] => { remoteDFInfo: REF Int.RemoteFileInfo = NEW[Int.RemoteFileInfo _ [name: remoteInfo.name]]; Int.GetFileInfo[ info: remoteDFInfo, notFoundOK: TRUE, client: client, errorLevel: $abort]; IF remoteDFInfo.date.gmt ~= BasicTime.nullGMT AND file.date.format = $explicit AND BasicTime.Period[from: file.date.gmt, to: remoteDFInfo.date.gmt] > 0 AND ~Int.YesOrNo[ client: client, message: IO.PutFR[ "%g may be obsolete, since the remote version from which it is derived {%g} is older than %g {%g} (the present newest remote one); continue anyway?", IO.rope[shortName], IO.rope[Utils.DateToRope[file.date]], IO.rope[remoteDFInfo.name], IO.rope[Utils.DateToRope[remoteDFInfo.date]] ], default: FALSE ] THEN Int.DoAbort[client.log]; IF StoreThisFile[localInfo, remoteInfo, file.date.format] THEN somethingChanged _ TRUE; -- we'll actually store it later, if user approves. dfRemoteInfo.name _ remoteInfo.name; -- redundant unless top-level DF file.name _ shortName; -- ensure no version number SELECT file.date.format FROM $explicit, $omitted => file.date _ [$explicit, outFile.GetInfo[].created]; ENDCASE; }; StoreThisFile[localInfo, remoteInfo, file.date.format] AND UserConfirms[localInfo, remoteInfo] => { somethingChanged _ TRUE; IF action.storeChanged THEN { Int.SimpleInteraction[ client, NEW[Ops.FileInteraction _ [ localFile: localInfo.name, remoteFile: remoteInfo.name, dateFormat: SELECT file.date.format FROM $explicit => $explicit, $greaterThan => $greaterThan, ENDCASE => $notEqual, date: localInfo.date.gmt, action: store ]] ]; file.name _ Int.ShortName[ file: FSExtras.NewCopy[ to: remoteInfo.name, from: localInfo.name, attach: TRUE ! FS.Error => Int.ReportFSError[error, remoteInfo, client, $abort]], keepVersion: TRUE ]; }; filesActedUpon _ filesActedUpon.SUCC; SELECT file.date.format FROM $explicit, $omitted => file.date _ localInfo.date; ENDCASE => file.name _ Utils.RemoveVersionNumber[file.name]; }; ENDCASE => IF localInfo.attachedTo.Length ~= 0 THEN { remoteVersion: ROPE = Utils.GetVersionNumber[localInfo.attachedTo]; IF ~remoteVersion.Equal[Utils.GetVersionNumber[file.name]] THEN { file.name _ shortName.Concat[remoteVersion]; somethingChanged _ TRUE; }; SELECT file.date.format FROM $omitted => { file.date _ localInfo.date; somethingChanged _ TRUE; }; $explicit => IF file.date ~= localInfo.date THEN { file.date _ localInfo.date; somethingChanged _ TRUE; }; ENDCASE; }; EXITS skip => NULL; }; include: REF Utils.IncludeItem => { remoteInfo: REF Int.RemoteFileInfo = SModelInner[include.path1, include.date]; IF ~Rope.Equal[remoteInfo.name, include.path1, FALSE] THEN { include.path1 _ remoteInfo.name; somethingChanged _ TRUE}; SELECT include.date.format FROM $explicit, $omitted => {include.date _ remoteInfo.date; somethingChanged _ TRUE}; ENDCASE => include.path1 _ Utils.RemoveVersionNumber[include.path1]; }; imports: REF Utils.ImportsItem => { localInfo: REF Int.LocalFileInfo = NEW[Int.LocalFileInfo _ [name: Int.ShortName[imports.path1]]]; remoteInfo: REF Int.RemoteFileInfo = NEW[Int.RemoteFileInfo _ [name: imports.path1, date: imports.date]]; Int.GetFileInfo[localInfo ! FS.Error => IF error.group = $user THEN {ReportMissing[localInfo]; GO TO skip} ELSE Int.ReportFSError[error, localInfo, client, $abort] ]; IF action.remoteCheck THEN { Int.GetFileInfo[info: remoteInfo, notFoundOK: TRUE, client: client, errorLevel: $abort]; IF StoreThisFile[localInfo, remoteInfo, imports.date.format] THEN { warnings _ warnings.SUCC; Int.SimpleInteraction[ client, NEW[Ops.InfoInteraction _ [ class: $warning, message: IO.PutFR["%g {%g} doesn't correspond to %g {%g}.", IO.rope[localInfo.name], IO.rope[Utils.DateToRope[localInfo.date]], IO.rope[remoteInfo.name], IO.rope[Utils.DateToRope[remoteInfo.date]] ] ]] ]; }; imports.path1 _ remoteInfo.name; }; SELECT imports.date.format FROM $explicit => NULL; $omitted => {imports.date _ localInfo.date; somethingChanged _ TRUE}; ENDCASE => imports.path1 _ Utils.RemoveVersionNumber[imports.path1]; EXITS skip => NULL; }; ENDCASE; Utils.WriteItemToStream[out, item]; }; in: IO.STREAM = FS.StreamOpen[dfLocalInfo.name, $read ! FS.Error => Int.ReportFSError[error, dfLocalInfo, client, $abort] ]; inFile: FS.OpenFile = FS.OpenFileFromStream[in]; out: IO.STREAM = NewTemporaryDF[inFile.GetInfo[].pages]; outFile: FS.OpenFile = FS.OpenFileFromStream[out]; dfLocalInfo.date _ [$explicit, inFile.GetInfo[].created]; Int.SimpleInteraction[ client, NEW[Ops.DFInfoInteraction _ [action: start, dfFile: dfLocalInfo.name]] ]; Utils.ParseFromStream[in, DoOneItem, Utils.Filter[comments: TRUE] ! Utils.SyntaxError -- [reason: ROPE]-- => Int.DoAbort[ log, IO.PutFR["Syntax error in '%g'[%d]: %g", IO.rope[dfLocalInfo.name], IO.card[in.GetIndex[]], IO.rope[reason] ], interact, clientData ]; Int.AbortDF => { in.Close[]; FinishWithTemporaryDF[CloseTemporaryDF[out], NIL, FALSE]; }; ]; in.Close[]; dfLocalInfo.date.gmt _ outFile.GetInfo[].created; IF somethingChanged AND UserConfirms[dfLocalInfo, dfRemoteInfo] THEN { tempName: ROPE = CloseTemporaryDF[out]; dfRemoteDateFormat: Utils.DateFormat = dfRemoteInfo.date.format; dfRemoteInfo.date _ dfLocalInfo.date; IF action.storeChanged THEN { Int.SimpleInteraction[ client, NEW[Ops.FileInteraction _ [ localFile: dfLocalInfo.name, remoteFile: dfRemoteInfo.name, dateFormat: SELECT dfRemoteDateFormat FROM $explicit => $explicit, $greaterThan => $greaterThan, ENDCASE => $notEqual, date: dfLocalInfo.date.gmt, action: $store ]] ]; [] _ FSExtras.NewCopy[to: dfRemoteInfo.name, from: tempName, attach: TRUE ! FS.Error => Int.ReportFSError[error, dfRemoteInfo, client, $abort]]; FinishWithTemporaryDF[tempName, dfLocalInfo.name]; }; filesActedUpon _ filesActedUpon.SUCC; } ELSE FinishWithTemporaryDF[CloseTemporaryDF[out], NIL, FALSE]; Int.SimpleInteraction[ client, NEW[Ops.DFInfoInteraction _ [ action: end, dfFile: dfLocalInfo.name, message: IF somethingChanged THEN NIL ELSE "DF file is unchanged" ]] ]; RETURN[dfRemoteInfo] }; IF interact = NIL THEN interact _ Int.DefaultInteractionProc; [] _ SModelInner[dfFile, [] ! 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. 0SModelImpl.mesa last edited by Levin on November 23, 1983 12:55 am Global (monitored) variables Exported to DFOperations This procedure returns TRUE if, in principle, the file should be stored. It does not alter either 'localInfo' or 'remoteInfo' and does not let the user influence its decision. If the local file is not attached, we store it anyway, so that the attachment will be made. If the local file is attached to a remote file whose name differs from the one specified in the DF, we store it even if the dates match, so that the proper attachment will be made. If the short names of the two files are the same and they have the same date, we assume that the remote file is good enough. We need to make an attachment "the other way", however, and prevent the main loop from doing anything. The following tests need to be done delicately, since we may not know the actual truth about the remote file, but only what the DF file claims is true. In particular, it is quite possible that remoteDate.gmt = nullGMT when a remote file actually exists. (This case arises if formatFromDF ~= $explicit and ~action.remoteCheck.) The local file is known to exist. Any remote file with whose date matches will be satisfactory. Note that remoteInfo.name will typically have a version (hint). At this point, remoteInfo.date.format will be $explicit if we are supposed to believe that there is a remote file with create date remoteInfo.date.gmt, and remoteInfo.date.format will be $omitted if we are supposed to believe no such file exists. Furthermore, remoteInfo.name has no version number. This file item is a reference to the DF file we are updating. We check to see if the !H version on the remote server is newer than the input version of the DF file. If so, the user is warned, since he is probably using an obsolete DF file. If the file's date and/or (remote) version number is missing or incorrect, we may wish to update them/it. Because of the logic on the earlier call of GetFileInfo, the local file is known to exist. However, it may or may not be attached to a remote file, since the user may have explicitly prohibited a Copy in the preceding arm of the SELECT. If it isn't attached, we don't want to disturb the DF file entry. 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šœr˜r—šœ œ˜JšœÂ˜Â—šœœ˜ Jšœu˜u—Jšœ œ ˜Jšœœ&œ˜6Jšœœœ˜)—J˜šœ œ˜Jšœ%œ œ˜BJšœ˜—J˜Jš˜J˜Jšœ8˜šœœ ˜Jšœ œœ%˜KJšœ4˜4J˜——J˜—Jšœœ%˜.Jšœ˜—š žœœœœœ œ˜DJšœ œ œ˜2JšœœÏc˜CJ˜ Jšœ˜—šžœœœ˜#Jš œ œ œ œœ˜9Jšœœœ&˜7Jšœ˜Jšœœ˜Jšœ˜—šž œœ œ œ˜YJšœœ˜šœ ˜Jšœ˜šœ ˜ šœ˜ Jšœ˜Jšœ˜Jšœ(˜*Jšœ2˜4Jšœ˜——Jšœ ˜ Jšœ˜—J˜—š ž œœ œœœ˜WJšœœœ˜Jšœœœ˜Jšœœœ2˜\Jšœ œœ4˜\šœ$˜$šž œœ˜Jšœ œ œ˜EJšœ˜Jšœ œ˜Jšœ°™°JšœœŸ˜>JšœœŸ˜=Jšœ[™[Jšœ#œœœ˜7Jšœ´™´šœ ˜Jšœ+˜+Jšœ0˜0šœœ˜ Jšœä™äšœ"œ ˜3Jšœ˜Jšœ$˜$šœœ˜ šœ˜Jšœ.˜.Jšœ>˜>Jšœ ˜ Jšœ˜ Jšœœ œœ ˜Jšœ˜—Jšœœ˜š˜Jšœœ˜—J˜——Jšœœ˜ J˜——JšœÈ™Èšœ˜Jšœœ˜,Jšœ œœ˜9šœ˜šœœ˜Jšœœœ˜%Jšœ'œœŸ˜Nšœ˜ šœ(˜2Jšœœœ˜Jšœœœ˜šœ˜Jšœœ˜šœ˜Jšœ˜šœ˜J˜šœ œe˜pJšœ˜Jšœ(˜*Jšœ˜Jšœ(˜*J˜—J˜—Jšœ˜—Jšœœ˜ J˜—Jšœ˜————Jšœ˜—J˜—šž œœ œ˜:Jšœœ˜šœ˜Jšœ˜šœ˜J˜Jšœ œœ˜AJ˜—Jšœ˜—J˜—š ž œœ œœœ˜8Jšœ#œ˜1J˜—Jšœ˜šœœœ˜šœ œ˜%Jšœ ˜ —šœœ˜Jšœ œ(˜7Jšœ œœ"˜Hšœ œ˜$šœ˜Jšœ&˜&Jšœ˜Jšœ˜——šœ˜šœœ ˜ Jšœœœœ˜BJšœ4˜8—Jšœ˜—šœœ˜Jšœ¡™¡Jšœ!˜!Jšœ.œ&˜XJ˜—J˜=Jšœ«™«šœœ˜šœ˜Jšœñ™ñšœœ˜&Jšœ/˜2—šœ˜Jšœ œ&˜J—šœ,˜1Jšœ˜ JšœE˜Hšœ ˜ Jšœ˜šœ ˜ šœ˜ Jšœ•˜•Jšœ˜Jšœ#˜%Jšœ˜Jšœ*˜,Jšœ˜——Jšœ ˜Jšœœ˜——šœ8˜>JšœœŸ3˜M—Jšœ&Ÿ ˜FJšœŸ˜3šœ˜JšœJ˜JJšœ˜—J˜—Jšœ7˜:šœ(˜(Jšœœ˜šœœ˜˜J˜šœ˜Jšœ˜Jšœ˜šœ ˜ šœ˜Jšœ˜Jšœ˜Jšœ˜——Jšœ˜J˜ Jšœ˜—J˜—šœ˜šœ˜Jšœ3˜7Jšœœ@˜D—Jšœ ˜J˜—J˜—Jšœ œ˜%šœ˜Jšœ2˜2Jšœ5˜<—J˜—šœ˜ JšœÑœ6œ ™›šœ"œ˜*Jšœœ0˜Cšœ9œ˜AJšœ,˜,Jšœœ˜J˜—šœ˜šœ ˜ Jšœ˜Jšœœ˜Jšœ˜—šœ ˜ šœœ˜%Jšœ˜Jšœœ˜Jšœ˜——Jšœ˜—J˜———š˜Jšœœ˜ —J˜—šœ œ˜#Jšœ œ?˜Nšœ-œœ˜—šœ œ˜$JšœA˜D—šœ˜šœœ ˜ Jšœœœœ˜BJšœ4˜8—Jšœ˜—šœœ˜Jšœ.œ&˜Xšœ;œ˜CJšœœ˜šœ˜Jšœ˜šœ˜J˜šœ œ0˜;Jšœœ(˜CJšœœ(˜DJ˜—J˜—Jšœ˜—J˜—J˜ J˜—šœ˜Jšœ œ˜Jšœ?œ˜EJšœ=˜D—š˜Jšœœ˜ —J˜—Jšœ˜—Jšœ#˜#J˜—šœœœœ#˜5Jšœœ?˜CJ˜—Jšœœ œ˜0Jšœœœ*˜8Jšœ œ œ˜2Jšœ9˜9šœ˜Jšœ˜JšœC˜FJšœ˜—šœ<œ˜CšœŸœ˜(šœ ˜ Jšœ˜šœ&˜(Jšœœœ ˜BJ˜—Jšœ ˜ J˜ Jšœ˜——šœ˜Jšœ ˜ Jšœ-œœ˜9Jšœ˜—Jšœ˜—J˜ Jšœ1˜1šœœ)œ˜FJšœ œ˜'Jšœ@˜@Jšœ%˜%šœœ˜˜J˜šœ˜Jšœ˜Jšœ˜šœ ˜ šœ˜Jšœ˜Jšœ˜Jšœ˜——Jšœ˜J˜Jšœ˜—J˜—šœE˜IJšœœB˜F—Jšœ2˜2J˜—Jšœ œ˜%J˜—Jšœ.œœ˜>šœ˜Jšœ˜šœ˜Jšœ ˜ Jšœ˜Jš œ œœœœ˜AJšœ˜—Jšœ˜—Jšœ˜J˜—Jšœ œœ'˜=šœ˜šœ˜ Jšœœœ˜BJšœœœœ™bJšœ˜—šœ˜Jšœœ˜Jšœœ<˜]Jš˜J˜—J˜—J˜—J˜Jšœ˜—…—.øFù