<> <> <> <> <> 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, ExpandName, FileInfo, GetInfo, GetName, OpenFile, OpenFileFromStream, Rename, StreamFromOpenFile, StreamOpen], FSPseudoServers USING [TranslateForWrite], IO USING [Close, GetIndex, PutFR, PutFR1, STREAM], Rope USING [Concat, Equal, Length, Match, Replace, ROPE, Run, SkipTo, Substr]; SModelImpl: CEDAR MONITOR IMPORTS BasicTime, DFInternal, DFUtilities, FS, FSPseudoServers, IO, Rope EXPORTS DFOperations = BEGIN ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; <<>> <> <<>> nSModels: NAT _ 0; <> SModel: PUBLIC PROC [dfFile: ROPE, action: DFOperations.SModelAction _ [], interact: DFOperations.InteractionProc _ NIL, clientData: REF ANY _ NIL, log: STREAM _ NIL] RETURNS [errors, warnings, filesActedUpon: INT _ 0] = { client: DFInternal.Client = NEW[DFInternal.ClientDescriptor _ [ (interact _ IF interact = NIL THEN DFInternal.DefaultInteractionProc ELSE interact), clientData, log ]]; tempDF: ROPE = "SModelTemporaryDF$"; NewTemporaryDF: ENTRY PROC [pages: INT] RETURNS [out: 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 DFInternal.LocalFileInfo _ NEW[DFInternal.LocalFileInfo _ [name: tempDF]]; DFInternal.ReportFSError[error, localInfo, client, $abort]; } ]; RETURN[FS.StreamFromOpenFile[outFile, $write]] }; CloseTemporaryDF: PROC [out: 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 DFInternal.LocalFileInfo, remoteInfo: REF DFInternal.RemoteFileInfo] RETURNS [BOOL] = { RETURN[DFInternal.YesOrNo[ client: client, message: IO.PutFR[ "%g {%g} to %g ?", [rope[localInfo.name]], [rope[DFUtilities.DateToRope[localInfo.date]]], [rope[DFUtilities.RemoveVersionNumber[remoteInfo.name]]], ], default: TRUE ]] }; SModelInner: PROC [dfFile: ROPE, date: DFUtilities.Date] RETURNS [REF DFInternal.RemoteFileInfo] = { directoryPath: ROPE _ NIL; writeDirectoryPath: ROPE _ NIL; directoryLen: INT _ 0; somethingChanged: BOOL _ FALSE; dfRemoteInfo: REF DFInternal.RemoteFileInfo = NEW[DFInternal.RemoteFileInfo _ [name: dfFile, date: date]]; dfLocalInfo: REF DFInternal.LocalFileInfo = NEW[DFInternal.LocalFileInfo _ [name: DFInternal.ShortName[dfFile]]]; DoOneItem: DFUtilities.ProcessItemProc = { StoreThisFile: PROC [ localInfo: REF DFInternal.LocalFileInfo, remoteInfo: REF DFInternal.RemoteFileInfo, formatFromDF: DFUtilities.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[ DFUtilities.RemoveVersionNumber[remoteInfo.name], DFUtilities.RemoveVersionNumber[localInfo.attachedTo], FALSE] THEN { <> IF remoteInfo.date = localInfo.date AND Rope.Equal[ DFInternal.ShortName[remoteInfo.name], DFInternal.ShortName[localInfo.attachedTo], FALSE] THEN { [] _ FS.Copy[ to: DFUtilities.RemoveVersionNumber[localInfo.name], from: remoteInfo.name, wantedCreatedTime: remoteInfo.date.gmt, remoteCheck: action.remoteCheck, attach: TRUE ! FS.Error => GO TO storeAnyway ]; localInfo.date.format _ $omitted; <> DFInternal.GetFileInfo[info: localInfo, remoteCheck: FALSE <> ! FS.Error => DFInternal.ReportFSError[error, localInfo, client, $abort] ]; 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; DFInternal.SimpleInteraction[ client, NEW[DFOperations.InfoInteraction _ [ class: $warning, message: IO.PutFR["%g {%g} is referenced with '>' in the DF file but is older than the remote version %g {%g}.", [rope[localInfo.name]], [rope[DFUtilities.DateToRope[localInfo.date]]], [rope[remoteInfo.name]], [rope[DFUtilities.DateToRope[remoteInfo.date]]] ] ]] ]; RETURN[FALSE] }; ENDCASE; ENDCASE; }; ReportMissing: PROC [localInfo: REF DFInternal.LocalFileInfo] = { warnings _ warnings.SUCC; DFInternal.SimpleInteraction[ client, NEW[DFOperations.InfoInteraction _ [ class: $warning, message: IO.PutFR1["'%g' doesn't exist.", [rope[localInfo.name]]] ]] ]; }; SelfReference: PROC [shortName: ROPE] RETURNS [BOOL] = { RETURN[shortName.Equal[dfLocalInfo.name, FALSE]] }; DFInternal.CheckAbort[client]; WITH item SELECT FROM directory: REF DFUtilities.DirectoryItem => { <> directoryPath _ directory.path1 _ FS.ExpandName[directory.path1].fullFName; directoryLen _ Rope.Length[directoryPath]; writeDirectoryPath _ TranslateHost[directoryPath]; }; file: REF DFUtilities.FileItem => { shortName: ROPE = DFUtilities.RemoveVersionNumber[file.name]; localInfo: REF DFInternal.LocalFileInfo _ NEW[DFInternal.LocalFileInfo _ [shortName]]; remoteInfo: REF DFInternal.RemoteFileInfo _ NEW[DFInternal.RemoteFileInfo _ [ name: directoryPath.Concat[file.name], date: file.date ]]; DFInternal.GetFileInfo[info: localInfo, remoteCheck: FALSE ! FS.Error => IF error.group = $user THEN {ReportMissing[localInfo]; GO TO skip} ELSE DFInternal.ReportFSError[error, localInfo, client, $abort] ]; IF action.remoteCheck THEN { <> CheckWriteInfo[Rope.Concat[writeDirectoryPath, file.name], localInfo, remoteInfo ! FS.Error => DFInternal.ReportFSError[error, remoteInfo, client, $abort]; ]; }; remoteInfo.name _ DFUtilities.RemoveVersionNumber[remoteInfo.name]; <> SELECT TRUE FROM SelfReference[shortName] => { <> remoteDFInfo: REF DFInternal.RemoteFileInfo = NEW[DFInternal.RemoteFileInfo _ [name: remoteInfo.name]]; CheckWriteInfo[TranslateHost[remoteDFInfo.name], localInfo, remoteDFInfo ! FS.Error => DFInternal.ReportFSError[error, remoteDFInfo, client, $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 ~DFInternal.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?", [rope[shortName]], [rope[DFUtilities.DateToRope[file.date]]], [rope[remoteDFInfo.name]], [rope[DFUtilities.DateToRope[remoteDFInfo.date]]] ], default: FALSE, blunder: TRUE ] THEN DFInternal.DoAbort[client.log, "Obsolete self-reference; remote version is older than date in DF file."]; 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 { DFInternal.SimpleInteraction[ client, NEW[DFOperations.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 _ FS.Copy[ to: remoteInfo.name, from: localInfo.name, attach: TRUE ! FS.Error => DFInternal.ReportFSError[error, remoteInfo, client, $abort]]; IF Rope.Run[file.name, 0, directoryPath, 0, FALSE] = directoryLen THEN { <> file.name _ Rope.Substr[file.name, directoryLen]; } ELSE { DFInternal.DoAbort[client.log, IO.PutFR1["Bogus file name (%g).", [rope[file.name]] ]]; }; }; filesActedUpon _ filesActedUpon.SUCC; SELECT file.date.format FROM $explicit, $omitted => file.date _ localInfo.date; ENDCASE => file.name _ shortName; }; ENDCASE => <> IF localInfo.attachedTo.Length ~= 0 THEN { remoteVersion: ROPE = DFUtilities.GetVersionNumber[localInfo.attachedTo]; IF ~remoteVersion.Equal[DFUtilities.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 DFUtilities.IncludeItem => { path: ROPE _ include.path1 _ FS.ExpandName[include.path1].fullFName; remoteInfo: REF DFInternal.RemoteFileInfo = SModelInner[path, include.date]; IF ~Rope.Equal[remoteInfo.name, path, FALSE] THEN { include.path1 _ remoteInfo.name; somethingChanged _ TRUE}; SELECT include.date.format FROM $explicit, $omitted => { include.date _ remoteInfo.date; somethingChanged _ TRUE}; ENDCASE => include.path1 _ DFUtilities.RemoveVersionNumber[include.path1]; }; imports: REF DFUtilities.ImportsItem => { localInfo: REF DFInternal.LocalFileInfo = NEW[DFInternal.LocalFileInfo _ [name: DFInternal.ShortName[imports.path1]]]; remoteInfo: REF DFInternal.RemoteFileInfo = NEW[DFInternal.RemoteFileInfo _ [name: imports.path1, date: imports.date]]; DFInternal.GetFileInfo[info: localInfo, remoteCheck: FALSE ! FS.Error => IF error.group = $user THEN {ReportMissing[localInfo]; GO TO skip} ELSE DFInternal.ReportFSError[error, localInfo, client, $abort] ]; SELECT TRUE FROM imports.date.gmt = BasicTime.nullGMT => { warnings _ warnings.SUCC; DFInternal.SimpleInteraction[ client, NEW[DFOperations.InfoInteraction _ [ class: $warning, message: IO.PutFR["Local import %g not found.", [rope[localInfo.name]], [rope[DFUtilities.DateToRope[localInfo.date]]] ] ]] ]; }; imports.date.format = $explicit AND imports.date.gmt # localInfo.date.gmt => { warnings _ warnings.SUCC; DFInternal.SimpleInteraction[ client, NEW[DFOperations.InfoInteraction _ [ class: $warning, message: IO.PutFR["Local import %g does not have requested date.", [rope[localInfo.name]], [rope[DFUtilities.DateToRope[localInfo.date]]] ] ]] ]; }; ENDCASE; IF action.remoteCheck THEN { CheckWriteInfo[TranslateHost[remoteInfo.name], localInfo, remoteInfo ! FS.Error => DFInternal.ReportFSError[error, remoteInfo, client, $abort]; ]; IF localInfo.date # remoteInfo.date THEN { warnings _ warnings.SUCC; DFInternal.SimpleInteraction[ client, NEW[DFOperations.InfoInteraction _ [ class: $warning, message: IO.PutFR["%g {%g} doesn't correspond to %g {%g}.", [rope[localInfo.name]], [rope[DFUtilities.DateToRope[localInfo.date]]], [rope[remoteInfo.name]], [rope[DFUtilities.DateToRope[remoteInfo.date]]] ] ]] ]; }; imports.path1 _ remoteInfo.name; }; SELECT imports.date.format FROM $explicit => NULL; $omitted => {imports.date _ localInfo.date; somethingChanged _ TRUE}; ENDCASE => imports.path1 _ DFUtilities.RemoveVersionNumber[imports.path1]; EXITS skip => NULL; }; ENDCASE; DFUtilities.WriteItemToStream[out, item]; }; in: STREAM = FS.StreamOpen[dfLocalInfo.name, $read ! FS.Error => DFInternal.ReportFSError[error, dfLocalInfo, client, $abort] ]; inFile: FS.OpenFile = FS.OpenFileFromStream[in]; out: STREAM = NewTemporaryDF[inFile.GetInfo[].pages]; outFile: FS.OpenFile = FS.OpenFileFromStream[out]; dfLocalInfo.date _ [$explicit, inFile.GetInfo[].created]; DFInternal.SimpleInteraction[ client, NEW[DFOperations.DFInfoInteraction _ [action: start, dfFile: dfLocalInfo.name]] ]; DFUtilities.ParseFromStream[in, DoOneItem, DFUtilities.Filter[comments: TRUE] ! DFUtilities.SyntaxError -- [reason: ROPE]-- => DFInternal.DoAbort[ log, IO.PutFR["Syntax error in '%g'[%d]: %g", [rope[dfLocalInfo.name]], [cardinal[in.GetIndex[]]], [rope[reason]] ], interact, clientData ]; DFInternal.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: DFUtilities.DateFormat = dfRemoteInfo.date.format; dfRemoteInfo.date _ dfLocalInfo.date; IF action.storeChanged THEN { DFInternal.SimpleInteraction[ client, NEW[DFOperations.FileInteraction _ [ localFile: dfLocalInfo.name, remoteFile: dfRemoteInfo.name, dateFormat: SELECT dfRemoteDateFormat FROM $explicit => $explicit, $greaterThan => $greaterThan, ENDCASE => $notEqual, date: dfLocalInfo.date.gmt, action: $store ]] ]; [] _ FS.Copy[to: dfRemoteInfo.name, from: tempName, attach: TRUE ! FS.Error => DFInternal.ReportFSError[error, dfRemoteInfo, client, $abort]]; FinishWithTemporaryDF[tempName, dfLocalInfo.name]; }; filesActedUpon _ filesActedUpon.SUCC; } ELSE FinishWithTemporaryDF[CloseTemporaryDF[out], NIL, FALSE]; DFInternal.SimpleInteraction[ client, NEW[DFOperations.DFInfoInteraction _ [ action: end, dfFile: dfLocalInfo.name, message: IF somethingChanged THEN NIL ELSE "DF file is unchanged" ]] ]; RETURN[dfRemoteInfo] }; [] _ SModelInner[dfFile, [] ! ABORTED => { DFInternal.SimpleInteraction[client, NEW[DFOperations.AbortInteraction _ [TRUE]]]; <> }; DFInternal.AbortDF => { errors _ errors.SUCC; DFInternal.SimpleInteraction[client, NEW[DFOperations.DFInfoInteraction _ [action: $abort, dfFile: dfFile]]]; CONTINUE }; ]; }; CheckWriteInfo: PROC [path: ROPE, localInfo: REF DFInternal.LocalFileInfo, remoteInfo: REF DFInternal.RemoteFileInfo] = { remoteInfo.date.gmt _ FS.FileInfo[ name: path, wantedCreatedTime: localInfo.date.gmt ! FS.Error => SELECT error.code FROM $unknownCreatedTime, $unknownFile => { <> remoteInfo.date.gmt _ BasicTime.nullGMT; remoteInfo.date.format _ $omitted; CONTINUE; }; ENDCASE; ].created; }; TranslateHost: PROC [path: ROPE] RETURNS [ROPE] = { IF Rope.Match["[*]*", path] THEN { rPos: INT _ Rope.SkipTo[path, 1, "]"]; host: ROPE _ Rope.Substr[path, 1, rPos-1]; writeHost: ROPE _ FSPseudoServers.TranslateForWrite[host]; IF NOT Rope.Equal[host, writeHost, FALSE] THEN RETURN [Rope.Replace[path, 1, rPos-1, writeHost]]; }; RETURN [path]; }; END.