<> <> <> <> <<>> <> <<>> <> <> <<>> DIRECTORY BasicTime USING [GMT, nullGMT, Now], FS USING [Error, ErrorFromStream], GVBasics USING [RName], IO, Process USING [Detach, SecondsToTicks], RefText USING [New], Rope, RuntimeError USING [BoundsFault], ViewerClasses USING [Viewer], ViewerTools USING [TiogaContents], WalnutDB -- USING lots -- , WalnutDefs USING [Error, MsgSet, dontCareMsgSetVersion, dontCareDomainVersion, SchemaMismatch, Segment, VersionMismatch], WalnutKernelDefs USING [LogEntry, LogExpungePhase, MsgLogEntry], WalnutLog -- USING lots -- , WalnutLogExpunge --using lots-- , WalnutMiscLog USING [walnutItemFixedLength, CloseNewMailLog, CreateReadArchiveLog, GetNewMailLog, MiscShutdown], WalnutOps, WalnutOpsExtras USING [], WalnutOpsInternal USING [rootFileName, mailStream, newMailSomewhere, statsStream, statsProgressStream, statsProgressTS, CarefullyApply, CheckInProgress, CheckReport, DoLogExpunge, LongRunningApply, ParseLog, StartStatsReporting, StatsReport], WalnutParseMsg USING [ParseHeadersFromRope], WalnutRegistryPrivate USING [NotifyForEvent, NotifyForMove, NotifyForMsgEvent, NotifyForMsgSetEvent], WalnutRoot USING [CloseTransaction, CommitAndContinue, EraseDB, Open, Shutdown, StartTransaction, UnregisterStatsProc], WalnutStream USING [Open]; WalnutOpsImpl: CEDAR MONITOR IMPORTS BasicTime, FS, IO, Process, RefText, Rope, RuntimeError, WalnutDefs, WalnutDB, WalnutOps, WalnutOpsInternal, WalnutLog, WalnutLogExpunge, WalnutMiscLog, WalnutParseMsg, WalnutRegistryPrivate, WalnutRoot, WalnutStream EXPORTS WalnutOps, WalnutOpsExtras, WalnutOpsInternal = BEGIN OPEN WalnutOps, WalnutOpsInternal; <> TiogaContents: TYPE = ViewerTools.TiogaContents; ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; GMT: TYPE = BasicTime.GMT; MsgSet: TYPE = WalnutDefs.MsgSet; DomainVersion: TYPE = WalnutOps.DomainVersion; MsgSetVersion: TYPE = WalnutOps.MsgSetVersion; <> DeletedMsgSetName: PUBLIC ROPE _ "Deleted"; ActiveMsgSetName: PUBLIC ROPE _ "Active"; <> EnumeratorForMsgs: TYPE = REF EnumeratorForMsgsObject; EnumeratorForMsgsObject: PUBLIC TYPE = RECORD[ headers: REF TEXT _ NIL, enumerator: SELECT type: * FROM FromLog => [ createDate: BasicTime.GMT _ BasicTime.nullGMT, scanPos: INT _ 0, endPos: INT _ 0 ], FromMsgSet => [lazyEnum: WalnutDB.LazyEnumerator], ENDCASE ]; <> Active: PUBLIC ROPE _ "Active"; Deleted: PUBLIC ROPE _ "Deleted"; reporterList: LIST OF IO.STREAM_ NIL; checkActivityCondition: CONDITION _ [timeout: Process.SecondsToTicks[5*60]]; replayInProgress: BOOL _ FALSE; interference: BOOL _ FALSE; walnutRootFile: ROPE; recentActivity: PUBLIC BOOL _ FALSE; started: PUBLIC BOOL _ FALSE; isShutdown: PUBLIC BOOL _ FALSE; errorInProgress: PUBLIC BOOL _ FALSE; walnutSegment: PUBLIC WalnutDefs.Segment; systemIsReadOnly: PUBLIC BOOL _ FALSE; rootFileName: PUBLIC ROPE; newMailSomewhere: PUBLIC BOOL _ FALSE; <> <> <> ReadOnly: PUBLIC ENTRY PROC RETURNS[readonly: BOOL] = { <> ENABLE UNWIND => NULL; WalnutOpsInternal.CheckInProgress[]; readonly _ WalnutDB.IsReadOnly[]; -- doesn't really read DB }; GetRootInfo: PUBLIC ENTRY PROC RETURNS[createDate: BasicTime.GMT, rootFile, mailFor: ROPE] = { <> ENABLE UNWIND => NULL; Gri: PROC = { [ createDate, , mailFor] _ WalnutDB.GetRootInfo[] }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Gri, FALSE]; rootFile _ WalnutOpsInternal.rootFileName; }; FileName: PUBLIC ENTRY PROC RETURNS[fileName: ROPE] = { ENABLE UNWIND => NULL; WalnutOpsInternal.CheckInProgress[]; fileName _ WalnutDB.GetDBName[]; -- doesn't read DB }; SizeOfDatabase: PUBLIC ENTRY PROC RETURNS[messages, msgSets: INT] = { ENABLE UNWIND => NULL; Sd: PROC = { [messages, msgSets] _ WalnutDB.SizeOfDatabase[] }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Sd, FALSE]; }; LogLength: PUBLIC ENTRY PROC RETURNS[length: INT] = { ENABLE UNWIND => NULL; LLen: PROC = { length _ WalnutLog.LogLength[] }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[LLen, FALSE]; }; MsgSetsInfo: PUBLIC ENTRY PROC RETURNS[version, num: INT] = { ENABLE UNWIND => NULL; Msv: PROC = { [version, num] _ WalnutDB.MsgSetsInfo[] }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Msv, FALSE]; }; <> CreateMsg: PUBLIC ENTRY PROC [msgName: ROPE, body: TiogaContents] = { <> ENABLE UNWIND => NULL; Cm: PROC = { at: INT _ WalnutLog.WriteMessage[msgName, body]; le: WalnutKernelDefs.LogEntry; mle: WalnutKernelDefs.MsgLogEntry; WalnutLog.SetIndex[at]; le _ WalnutLog.NextEntry[].le; mle _ NARROW[le]; mle.show _ TRUE; [] _ WalnutDB.AddNewMsg[mle]; }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Cm, TRUE]; }; <> CreateMsgSet: PUBLIC ENTRY PROC[name: ROPE, msDomainVersion: DomainVersion] = { <> <> ENABLE UNWIND => NULL; exists: BOOL; Cme: INTERNAL PROC = { exists _ WalnutDB.CreateMsgSet[name, msDomainVersion].existed; [] _ WalnutLog.CreateMsgSet[name]; }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Cme, TRUE]; IF ~exists THEN WalnutRegistryPrivate.NotifyForMsgSetEvent[created, name]; }; MsgSetExists: PUBLIC ENTRY PROC[name: ROPE, msDomainVersion: DomainVersion] RETURNS[exists: BOOL, version: INT] = { ENABLE UNWIND => NULL; Mse: INTERNAL PROC = { [exists, version] _ WalnutDB.MsgSetExists[name, msDomainVersion] }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Mse, FALSE]; }; SizeOfMsgSet: PUBLIC ENTRY PROC[name: ROPE] RETURNS[messages, version: INT] = { ENABLE UNWIND => NULL; Sms: INTERNAL PROC = { [messages, version] _ WalnutDB.NumInMsgSet[name] }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Sms, FALSE]; }; EmptyMsgSet: PUBLIC ENTRY PROC [msgSet: MsgSet] RETURNS [someInDeleted: BOOL] = { <> <<>> <> <> ENABLE UNWIND => NULL; Ems: INTERNAL PROC[inProgress: BOOL] = { someInDeleted _ FALSE; IF WalnutDB.EqMsgSets[msgSet.name, DeletedMsgSetName] THEN RETURN; IF ~inProgress THEN { at: INT; IF ~WalnutDB.VerifyMsgSet[msgSet] THEN RETURN; at _ WalnutLog.EmptyMsgSet[msgSet.name].at; WalnutDB.SetOpInProgressPos[at]; }; someInDeleted _ WalnutDB.EmptyMsgSet[msgSet, CheckReport]; }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.LongRunningApply[Ems]; }; DestroyMsgSet: PUBLIC ENTRY PROC [msgSet: MsgSet, msDomainVersion: DomainVersion] RETURNS [someInDeleted: BOOL] = { <> <<>> <> <> <> ENABLE UNWIND => NULL; isActive: BOOL; Dms: INTERNAL PROC[inProgress: BOOL] = { someInDeleted _ FALSE; IF ~inProgress THEN { at: INT; IF ~WalnutDB.VerifyMsgSet[msgSet] THEN RETURN; WalnutDB.VerifyDomainVersion[msDomainVersion]; at _ IF isActive THEN WalnutLog.EmptyMsgSet[msgSet.name].at ELSE WalnutLog.DestroyMsgSet[msgSet.name].at; WalnutDB.SetOpInProgressPos[at]; }; IF isActive THEN someInDeleted _ WalnutDB.EmptyMsgSet[msgSet, CheckReport] ELSE someInDeleted _ WalnutDB.DestroyMsgSet[msgSet, msDomainVersion, CheckReport]; }; WalnutOpsInternal.CheckInProgress[]; IF msgSet.name.Equal[DeletedMsgSetName, FALSE] THEN RETURN[FALSE]; -- may not destroy Deleted isActive _ msgSet.name.Equal[ActiveMsgSetName, FALSE]; WalnutOpsInternal.LongRunningApply[Dms]; IF ~isActive THEN WalnutRegistryPrivate.NotifyForMsgSetEvent[destroyed, msgSet.name]; }; <<>> EnumerateMsgs: PUBLIC ENTRY PROC RETURNS[enum: EnumeratorForMsgs] = { ENABLE UNWIND => NULL; thisEnum: REF FromLog EnumeratorForMsgsObject; Em: INTERNAL PROC = { thisEnum.createDate _ WalnutDB.GetRootFileVersion[]; thisEnum.endPos _ WalnutLog.LogLength[]; }; WalnutOpsInternal.CheckInProgress[]; thisEnum _ NEW[FromLog EnumeratorForMsgsObject]; WalnutOpsInternal.CarefullyApply[Em, FALSE]; RETURN[thisEnum]; }; NextMsg: PUBLIC ENTRY PROC[enum: EnumeratorForMsgs] RETURNS [msgID: ROPE, msList: LIST OF ROPE, headers: REF TEXT] = { ENABLE UNWIND => NULL; ok: BOOL _ TRUE; Nm: INTERNAL PROC = { skipped: INT; isAt: INT; natLen: NAT; wle: WalnutLog.LogEntry; TRUSTED { WITH thisEnum: enum SELECT FROM FromLog => { IF thisEnum.createDate # WalnutDB.GetRootFileVersion[] THEN ERROR WalnutDefs.Error[$db, $InvalidEnumerator, "Wrong rootfile referenced"]; skipped _ WalnutLog.SetPosition[thisEnum.scanPos]; DO wle: WalnutLog.LogEntry; at: INT; [wle, at] _ WalnutLog.QuickScan[]; IF at >= thisEnum.endPos OR wle = NIL THEN { ok _ FALSE; RETURN }; TRUSTED { WITH mle: wle SELECT FROM CreateMsg => { natLen _ mle.headersLen; -- this better not cause an error msgID _ Rope.FromRefText[mle.msg]; isAt _ WalnutDB.GetMsgEntryPosition[msgID]; IF isAt = -1 OR isAt > mle.entryStart THEN LOOP; -- oops or duplicate msList _ WalnutDB.GetCategories[msgID]; IF thisEnum.headers = NIL OR thisEnum.headers.maxLength < natLen THEN thisEnum.headers _ RefText.New[natLen]; WalnutLog.GetRefTextFromLog[mle.entryStart+mle.textOffset, natLen, thisEnum.headers]; headers _ thisEnum.headers; thisEnum.scanPos _ WalnutLog.NextAt[]; EXIT; }; ENDCASE => LOOP; }; ENDLOOP; }; FromMsgSet => { valid: BOOL; [msgID, valid] _ WalnutDB.NextMsgInMsgSet[thisEnum.lazyEnum]; IF NOT valid OR msgID = NIL THEN RETURN; isAt _ WalnutDB.GetMsgEntryPosition[msgID]; msList _ WalnutDB.GetCategories[msgID]; [] _ WalnutLog.SetPosition[isAt]; wle _ WalnutLog.QuickScan[].le; TRUSTED { WITH mle: wle SELECT FROM CreateMsg => { natLen _ mle.headersLen; IF thisEnum.headers = NIL OR thisEnum.headers.maxLength < natLen THEN thisEnum.headers _ RefText.New[natLen]; WalnutLog.GetRefTextFromLog[isAt+mle.textOffset, natLen, thisEnum.headers] } ENDCASE }; headers _ thisEnum.headers }; ENDCASE => ERROR WalnutDefs.Error[$db, $InvalidEnumerator, "Wrong type of enumerator"]; }; }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Nm, FALSE]; IF ~ok THEN RETURN[NIL, NIL, NIL]; }; EnumerateMsgsInMsgSet: PUBLIC ENTRY PROC[name: ROPE] RETURNS[enum: EnumeratorForMsgs] = { ENABLE UNWIND => NULL; Me: INTERNAL PROC = { msgSet: WalnutDB.MsgSet; version: INT; exists: BOOL; [exists, version] _ WalnutDB.MsgSetExists[name, WalnutDefs.dontCareDomainVersion]; IF NOT exists THEN RETURN; msgSet.name _ name; msgSet.version _ version; enum _ NEW[EnumeratorForMsgsObject _ [enumerator: FromMsgSet[lazyEnum: WalnutDB.EnumerateMsgsInMsgSet[msgSet]]]] }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Me, FALSE]; }; MsgsEnumeration: PUBLIC ENTRY PROC[alphaOrder: BOOL] RETURNS[mL: LIST OF ROPE] = { ENABLE UNWIND => NULL; Me: INTERNAL PROC = { mL _ WalnutDB.MsgsEnumeration[alphaOrder] }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Me, FALSE]; }; <<>> MsgSetNames: PUBLIC ENTRY PROC[alphaOrder: BOOL] RETURNS[mL: LIST OF ROPE, msDomainVersion: DomainVersion] = { <> ENABLE UNWIND => NULL; Ems: INTERNAL PROC = { [mL, msDomainVersion] _ WalnutDB.MsgSetsNames[alphaOrder] }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Ems, FALSE]; }; MsgsInSetEnumeration: PUBLIC ENTRY PROC[name: ROPE, fromStart: BOOL] RETURNS [mL: LIST OF ROPE, msVersion: MsgSetVersion] = { <> ENABLE UNWIND => NULL; Emis: INTERNAL PROC = { [mL, msVersion] _ WalnutDB.MsgsInSetEnumeration[name, fromStart] }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Emis, FALSE]; }; EnumerateMsgSets: PUBLIC ENTRY PROC [ alphaOrder: BOOL _ TRUE, proc: PROC[msgSet: MsgSet] ] RETURNS [msVersion: MsgSetVersion] = { ENABLE UNWIND => NULL; Emss: INTERNAL PROC = { msVersion _ WalnutDB.EnumerateMsgSets[alphaOrder, proc] }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Emss, FALSE]; }; EnumerateMsgsInSet: PUBLIC ENTRY PROC [ name: ROPE, fromStart: BOOL, proc: PROC[msg, TOCentry: ROPE, hasBeenRead: BOOL, startOfSubject: INT]] RETURNS [msVersion: MsgSetVersion] = { ENABLE UNWIND => NULL; Ems: INTERNAL PROC = { msVersion _ WalnutDB.EnumerateMsgsInSet[name, fromStart, proc] }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Ems, FALSE]; }; <> ParseHeaders: PUBLIC ENTRY PROC [headers: ROPE, proc: WalnutOps.ParseProc] RETURNS[msgHeaders: WalnutOps.MsgHeaders] = { <> ENABLE UNWIND => NULL; WalnutOpsInternal.CheckInProgress[]; msgHeaders _ WalnutParseMsg.ParseHeadersFromRope[headers, proc]; }; <> <<>> MsgExists: PUBLIC ENTRY PROC [msg: ROPE] RETURNS[exists: BOOL] = { ENABLE UNWIND => NULL; Me: INTERNAL PROC = { exists _ WalnutDB.MsgExists[msg] }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Me, FALSE]; }; GetCategories: PUBLIC ENTRY PROC [msg: ROPE] RETURNS[msL: LIST OF ROPE] = { ENABLE UNWIND => NULL; GetC: INTERNAL PROC = { msL_ WalnutDB.GetCategories[msg] }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[GetC, FALSE]; }; GetDisplayProps: PUBLIC ENTRY PROC[msg: ROPE] RETURNS[hasBeenRead: BOOL, TOCentry: ROPE, startOfSubject: INT] = { ENABLE UNWIND => NULL; GetDP: INTERNAL PROC = { [hasBeenRead, TOCentry, startOfSubject] _ WalnutDB.GetDisplayProps[msg] }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[GetDP, FALSE]; }; GetMsgDate: PUBLIC ENTRY PROC [msg: ROPE] RETURNS[date: BasicTime.GMT] = { ENABLE UNWIND => NULL; GetMD: INTERNAL PROC = { date _ WalnutDB.GetMsgDate[msg] }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[GetMD, FALSE]; }; GetMsg: PUBLIC ENTRY PROC [msg: ROPE] RETURNS[contents: TiogaContents, herald, shortName: ROPE] = { ENABLE UNWIND => NULL; GetMT: INTERNAL PROC = { textStart, textLen, formatLen, shortNameLen: INT; [textStart, textLen, formatLen, herald, shortNameLen] _ WalnutDB.GetMsgText[msg]; IF textStart = 0 THEN RETURN; contents _ WalnutLog.GetTiogaContents[textStart, textLen, formatLen]; shortName _ herald.Substr[0, shortNameLen]; }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[GetMT, FALSE]; }; GetMsgText: PUBLIC ENTRY PROC [msg: ROPE, text: REF TEXT] RETURNS[contents: REF TEXT] = { ENABLE UNWIND => NULL; CheckForNat: PROC[len: INT] RETURNS[nat: NAT] = { nat _ len }; Gmt: INTERNAL PROC = { textStart, textLen: INT; natLen: NAT; [textStart, textLen, ] _ WalnutDB.GetMsgText[msg]; IF textStart = 0 THEN { contents _ text; IF contents # NIL THEN contents.length _ 0; RETURN}; BEGIN ENABLE RuntimeError.BoundsFault => GOTO oops; natLen _ CheckForNat[textLen]; IF text = NIL OR (natLen > text.maxLength) THEN contents _ RefText.New[natLen] ELSE contents _ text; WalnutLog.GetRefTextFromLog[textStart, textLen, contents]; EXITS oops => ERROR WalnutDefs.Error[$log, $MsgTooLong, "Msg will not fit in a REF TEXT"]; END; }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Gmt, FALSE]; }; GetMsgHeaders: PUBLIC ENTRY PROC [msg: ROPE, text: REF TEXT] RETURNS[headers: REF TEXT] = { ENABLE UNWIND => NULL; natLen: NAT; Gmh: INTERNAL PROC = { wle: WalnutLog.LogEntry; isAt: INT = WalnutDB.GetMsgEntryPosition[msg]; headers _ text; IF isAt = -1 THEN { IF headers # NIL THEN headers.length _ 0; RETURN}; [] _ WalnutLog.SetPosition[isAt]; [wle, ] _ WalnutLog.QuickScan[]; BEGIN ENABLE RuntimeError.BoundsFault => GOTO oops; TRUSTED { WITH mle: wle SELECT FROM CreateMsg => { natLen: NAT _ mle.headersLen; IF headers = NIL OR headers.maxLength < natLen THEN headers _ RefText.New[natLen]; WalnutLog.GetRefTextFromLog[ mle.entryStart+mle.textOffset, natLen, headers]; }; ENDCASE => NULL; }; EXITS oops => ERROR WalnutDefs.Error[$log, $MsgHeadersTooLong, IO.PutFR["Msg headers (%g bytes) will not fit in a REF TEXT", IO.int[natLen]]]; END; }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Gmh, FALSE]; }; GetMsgShortName: PUBLIC ENTRY PROC [msg: ROPE] RETURNS[shortName: ROPE] = { ENABLE UNWIND => NULL; Gmsn: INTERNAL PROC = { shortNameLen: INT; herald: ROPE; [, , , herald, shortNameLen] _ WalnutDB.GetMsgText[msg]; shortName _ herald.Substr[0, shortNameLen]; }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Gmsn, FALSE]; }; GetMsgSize: PUBLIC ENTRY PROC [msg: ROPE] RETURNS[textLen, formatLen: INT] = { ENABLE UNWIND => NULL; Gms: INTERNAL PROC = { [, textLen, formatLen] _ WalnutDB.GetMsgTextInfo[msg] }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Gms, FALSE]; }; GetHasBeenRead: PUBLIC ENTRY PROC [msg: ROPE] RETURNS[hadBeenRead: BOOL] = { ENABLE UNWIND => NULL; Ghbr: INTERNAL PROC = { hadBeenRead _ WalnutDB.GetHasBeenRead[msg]; }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Ghbr, FALSE]; }; SetHasBeenRead: PUBLIC ENTRY PROC [msg: ROPE] = { ENABLE UNWIND => NULL; Shbr: INTERNAL PROC = { [] _ WalnutLog.HasBeenRead[msg]; WalnutDB.SetHasBeenRead[msg]; }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Shbr, TRUE]; WalnutRegistryPrivate.NotifyForMsgEvent[firstRead, msg]; }; <> AddMsg: PUBLIC ENTRY PROC [msg: ROPE, from, to: MsgSet] RETURNS [exists: BOOL] = { <> <<>> <> <> <> <> <> ENABLE UNWIND => NULL; Am: INTERNAL PROC = { exists _ WalnutDB.AddMsg[msg, from, to]; [] _ WalnutLog.AddMsg[msg, to.name]; }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Am, TRUE]; IF ~exists THEN WalnutRegistryPrivate.NotifyForMove[msg: msg, to: to.name, from: NIL]; }; MoveMsg: PUBLIC ENTRY PROC [msg: ROPE, from, to: MsgSet] RETURNS [exists: BOOL] = { <> <<>> <> <> <> <> <> <> ENABLE UNWIND => NULL; Mm: INTERNAL PROC = { exists _ WalnutDB.MoveMsg[msg, from, to]; [] _ WalnutLog.MoveMsg[msg, from.name, to.name]; }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Mm, TRUE]; IF ~exists THEN WalnutRegistryPrivate.NotifyForMove[msg: msg, to: to.name, from: from.name]; }; RemoveMsg: PUBLIC ENTRY PROC [msg: ROPE, from: MsgSet, deletedVersion: MsgSetVersion] RETURNS [deleted: BOOL] = { <> <<>> <> <> <> <> <> ENABLE UNWIND => NULL; Rm: INTERNAL PROC = { deleted _ WalnutDB.RemoveMsg[msg, from, deletedVersion]; [] _ WalnutLog.RemoveMsg[msg, from.name]; }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Rm, TRUE]; IF deleted THEN WalnutRegistryPrivate.NotifyForMsgEvent[deleted, msg] ELSE WalnutRegistryPrivate.NotifyForMove[msg: msg, to: NIL, from: from.name]; }; <> <> <<>> StartNewMail: PUBLIC ENTRY PROC[] RETURNS[newMailStream: IO.STREAM] = { ENABLE UNWIND => NULL; newMailLogLength: INT _ -1; expungeInProgress: BOOL _ FALSE; Gnml: PROC = { newMailLogLength _ WalnutDB.GetNewMailLogLength[]; expungeInProgress _ WalnutDB.GetLogExpungePhase[] # idle; }; WalnutOpsInternal.CheckInProgress[]; IF WalnutOpsInternal.mailStream # NIL THEN RETURN[NIL]; WalnutOpsInternal.CarefullyApply[proc: Gnml, didUpdate: FALSE]; IF newMailLogLength = -1 OR expungeInProgress THEN RETURN[NIL]; RETURN[WalnutOpsInternal.mailStream _ WalnutMiscLog.GetNewMailLog[newMailLogLength, -1]]; }; <<>> RecordNewMailInfo: PUBLIC ENTRY PROC[logLen: INT, server: ROPE, num: INT] = { ENABLE UNWIND => NULL; when: BasicTime.GMT; Snml: PROC = { now: INT _ WalnutDB.GetServerInfo[server] + num; [] _ WalnutLog.RecordNewMailInfo[logLen, when _ BasicTime.Now[], server, now]; WalnutDB.SetNewMailInfo[logLen, when, server, now]; }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[proc: Snml, didUpdate: TRUE]; WalnutOpsInternal.newMailSomewhere _ TRUE; }; EndNewMail: PUBLIC ENTRY PROC = { ENABLE UNWIND => NULL; strm: IO.STREAM _ WalnutOpsInternal.mailStream; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.mailStream _ NIL; IF strm # NIL THEN WalnutMiscLog.CloseNewMailLog[ ! WalnutDefs.Error , IO.Error, FS.Error => CONTINUE]; IF WalnutOpsInternal.newMailSomewhere THEN WalnutRegistryPrivate.NotifyForEvent[mailRead]; }; <> <<>> GetNewMail: PUBLIC ENTRY PROC[ activeVersion: INT, proc: PROC[msg, TOCentry: ROPE, startOfSubject: INT]] RETURNS[responses: LIST OF WalnutOps.ServerInfo, complete: BOOL] = { ENABLE UNWIND => NULL; someEntries: BOOL; Cml: PROC = { IF activeVersion # WalnutDefs.dontCareMsgSetVersion THEN [] _ WalnutDB.VerifyMsgSet[ [ActiveMsgSetName, activeVersion] ]; someEntries _ WalnutDB.GetNewMailLogLength[] # 0; }; Cml2: PROC[inProgress: BOOL] = { fromPos: INT _ 0; complete _ WalnutLog.PrepareToCopyTempLog[which: newMail, pagesAlreadyCopied: 0, reportProc: WalnutOpsInternal.CheckReport]; IF ~complete THEN RETURN; IF ~inProgress THEN { at: INT _ WalnutLog.StartCopyNewMail[].at; WalnutDB.SetOpInProgressPos[at]; WalnutDB.SetCopyMailLogPos[at]; } ELSE { at: INT = WalnutDB.GetCopyMailLogPos[]; WalnutOpsInternal.CheckReport["\n Continue copying the newMailLog\n"]; [] _ WalnutLog.SetPosition[at]; [] _ WalnutLog.NextEntry[]; -- skip the copy entry fromPos _ WalnutLog.LogLength[] - WalnutLog.NextAt[]; }; WalnutDB.SetAddingServerMsgs[TRUE]; WalnutLog.CopyTempLog[newMail, WalnutDB.GetCopyMailLogPos[], fromPos, WalnutOpsInternal.CheckReport]; -- raises error if problem WalnutOpsInternal.CheckReport["\n"]; WalnutDB.SetParseLogInProgress[TRUE]; WalnutDB.SetParseLogPos[WalnutDB.GetOpInProgressPos[]]; WalnutDB.SetOpInProgressPos[-1]; }; Gnm: PROC = { IF proc # NIL THEN WalnutDB.EnumerateUnacceptedMsgs[activeVersion, proc]; responses _ WalnutDB.EnumerateServers[]; }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Cml, FALSE]; IF WalnutOpsInternal.mailStream # NIL THEN RETURN[NIL, FALSE]; -- file is busy IF someEntries THEN { WalnutOpsInternal.LongRunningApply[Cml2]; IF complete THEN { WalnutLog.ForgetLogStreams[]; WalnutLog.OpenLogStreams[]; WalnutOpsInternal.CheckReport["Adding new mail to the database\n"]; [] _ WalnutOpsInternal.ParseLog[TRUE]; -- "see" messages }; } ELSE complete _ TRUE; WalnutOpsInternal.CarefullyApply[Gnm, FALSE]; }; AcceptNewMail: PUBLIC ENTRY PROC[activeVersion: INT] = { ENABLE UNWIND => NULL; Anm: PROC[inProgress: BOOL] = { at: INT; IF ~inProgress THEN { [] _ WalnutDB.VerifyMsgSet[[ActiveMsgSetName, activeVersion]]; at _ WalnutLog.AcceptNewMail[].at; WalnutDB.SetOpInProgressPos[at]; } ELSE at _ WalnutDB.GetAcceptNewMailPos[]; WalnutDB.AcceptNewMail[at, activeVersion]; }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.LongRunningApply[Anm]; WalnutOpsInternal.newMailSomewhere _ FALSE; }; <> Startup: PUBLIC ENTRY PROC [rootFile: ROPE, wantReadOnly: BOOL _ FALSE] RETURNS[isReadOnly, newMailExists: BOOL, mailFor: GVBasics.RName, key: ROPE] = { <> <<>> <> <> <> ENABLE { WalnutDefs.Error => { errorInProgress _ TRUE; REJECT}; UNWIND => NULL; }; rootFileCreateDate: GMT; schemaInvalid: BOOL _ TRUE; InitialCheck: INTERNAL PROC = { rootCreateDate: GMT; rootFileKey, mailForFromDB: ROPE; IF key.Length[] # 0 THEN { [rootCreateDate, rootFileKey, mailForFromDB] _ WalnutDB.GetRootInfo[]; IF rootCreateDate = BasicTime.nullGMT OR (rootCreateDate = rootFileCreateDate AND rootFileKey.Length[] = 0 AND mailForFromDB.Length[] = 0) THEN { WalnutDB.SetRootInfo[rootFileCreateDate, key, mailFor]; WalnutRoot.CommitAndContinue[]; } ELSE { IF ~rootFileKey.Equal[key, FALSE] THEN WalnutDefs.Error[$db, $WrongRootFile, IO.PutFR["RootFile has key %g, database says %g", IO.rope[key], IO.rope[rootFileKey]]]; IF ~mailForFromDB.Equal[mailFor, FALSE] THEN WalnutDefs.Error[$db, $WrongRootFile, IO.PutFR["RootFile says mailFor %g, database says %g", IO.rope[mailFor], IO.rope[mailForFromDB]]]; IF rootCreateDate # rootFileCreateDate THEN WalnutDefs.Error[$db, $WrongRootFile, IO.PutFR["RootFile has date %g, database says %g", IO.time[rootFileCreateDate], IO.time[rootCreateDate]]]; }; }; IF (WalnutOpsInternal.newMailSomewhere _ WalnutDB.GetNewMailLogLength[] # 0) THEN RETURN; <> BEGIN serverList: LIST OF ServerInfo _ WalnutDB.EnumerateServers[]; FOR sL: LIST OF ServerInfo _ serverList, sL.rest UNTIL sL = NIL DO IF (WalnutOpsInternal.newMailSomewhere _ sL.first.num#0) THEN RETURN; ENDLOOP; END; }; IF started THEN RETURN WITH ERROR WalnutDefs.Error[$db, $AlreadyStarted]; StartStatsReporting[]; [key, mailFor, rootFileCreateDate, walnutSegment, systemIsReadOnly] _ WalnutRoot.Open[rootName: rootFile, readOnly: wantReadOnly]; rootFileName _ rootFile; BEGIN exp: ROPE; BEGIN schemaInvalid _ WalnutRoot.StartTransaction[ ! WalnutDefs.Error => { IF code # $MismatchedSegment THEN REJECT; exp _ explanation; GOTO mismatched; }]; EXITS mismatched => RETURN WITH ERROR WalnutDefs.Error[$db, $SchemaMismatch, exp]; END; END; schemaInvalid _ WalnutRoot.StartTransaction[]; WalnutLog.OpenLogStreams[]; walnutRootFile _ rootFile; isReadOnly _ systemIsReadOnly; BEGIN exp: ROPE; BEGIN WalnutDB.DeclareDB[walnutSegment, schemaInvalid ! WalnutDefs.SchemaMismatch => { exp _ explanation; GOTO mismatch}]; StatsReport[IO.PutFR["\n\n ***** Startup called with rootFile: %g", IO.rope[rootFile]]]; EXITS mismatch => RETURN WITH ERROR WalnutDefs.Error[$db, $SchemaMismatch, exp]; END; END; started _ TRUE; errorInProgress _ FALSE; CarefullyApply[InitialCheck, FALSE]; CheckInProgress[]; newMailExists _ WalnutOpsInternal.newMailSomewhere; WalnutRegistryPrivate.NotifyForEvent[started]; }; Shutdown: PUBLIC ENTRY PROC = { <> ENABLE { WalnutDefs.Error => { errorInProgress _ TRUE; REJECT}; UNWIND => NULL; }; WalnutRoot.UnregisterStatsProc[StatsReport]; WalnutLogExpunge.ExpShutdown[]; -- clear its variables WalnutMiscLog.MiscShutdown[]; -- clear its variables WalnutLog.ShutdownLog[]; WalnutRoot.Shutdown[]; -- takes care of database StatsReport["\n *** Shutdown"]; IF statsProgressTS # NIL THEN { statsProgressTS.inhibitDestroy _ FALSE }; IF statsStream # NIL THEN { statsStream.Close[]; statsStream _ NIL }; IF statsProgressStream # NIL THEN { statsProgressStream.Close[]; statsProgressStream _ NIL }; started _ FALSE; recentActivity_ FALSE; isShutdown _ FALSE; errorInProgress _ FALSE; mailStream _ NIL; WalnutRegistryPrivate.NotifyForEvent[stopped]; }; Scavenge: PUBLIC ENTRY PROC[rootFile: ROPE] RETURNS[newMailExists: BOOL, mailFor: GVBasics.RName, key: ROPE] = { ENABLE { WalnutDefs.Error => { errorInProgress _ TRUE; REJECT}; UNWIND => NULL; }; rootFileCreateDate: GMT; WalnutLog.ShutdownLog[]; WalnutRoot.Shutdown[]; StartStatsReporting[]; StatsReport["\n *** Scavenge"]; [key, mailFor, rootFileCreateDate, walnutSegment, systemIsReadOnly] _ WalnutRoot.Open[rootName: rootFile, readOnly: FALSE, newSegmentOk: TRUE]; IF systemIsReadOnly THEN ERROR WalnutDefs.Error[$DB, $IsReadOnly, "Can't erase a readonly database"]; rootFileName _ rootFile; [] _ WalnutRoot.StartTransaction[openDB: FALSE]; -- don't care if schema is invalid WalnutLog.OpenLogStreams[]; WalnutLog.AcquireWriteLock[]; started _ TRUE; errorInProgress _ FALSE; WalnutRoot.EraseDB[]; WalnutDB.InitSchema[walnutSegment]; WalnutDB.SetRootInfo[rootFileCreateDate, key, mailFor]; WalnutDB.SetParseLogInProgress[TRUE]; WalnutDB.SetParseLogPos[0]; WalnutRoot.CommitAndContinue[]; WalnutOpsInternal.newMailSomewhere _ FALSE; [] _ WalnutOpsInternal.ParseLog[TRUE]; WalnutDB.SetTimeOfLastScavenge[BasicTime.Now[]]; newMailExists _ WalnutOpsInternal.newMailSomewhere; }; <> <> GetExpungeInfo: PUBLIC ENTRY PROC RETURNS[ firstDestroyedMsgPos, bytesInDestroyedMsgs: INT] = { ENABLE UNWIND => NULL; Gei: PROC = { [ firstDestroyedMsgPos, bytesInDestroyedMsgs] _ WalnutDB.GetExpungeInfo[] }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Gei, FALSE] }; ExpungeMsgs: PUBLIC ENTRY PROC[deletedVersion: INT] RETURNS[bytesInDestroyedMsgs: INT] = { ENABLE UNWIND => NULL; Expm: INTERNAL PROC[inProgress: BOOL] = { IF ~inProgress THEN { at: INT; [] _ WalnutDB.VerifyMsgSet[[WalnutOps.DeletedMsgSetName, deletedVersion]]; at _ WalnutLog.ExpungeMsgs[].at; WalnutDB.SetOpInProgressPos[at]; }; WalnutDB.ExpungeMsgs[deletedVersion, CheckReport]; bytesInDestroyedMsgs _ WalnutDB.GetExpungeInfo[].bytesInDestroyedMsgs }; WalnutOpsInternal.CheckInProgress[]; CheckReport["\nDeleting msgs"]; WalnutOpsInternal.LongRunningApply[Expm]; }; CopyToExpungeLog: PUBLIC ENTRY PROC = { ENABLE UNWIND => NULL; ExpL: INTERNAL PROC[inProgress: BOOL] = { IF ~inProgress THEN { at: INT _ WalnutLog.WriteExpungeLog[].at; -- write log entry of intent WalnutDB.SetOpInProgressPos[at]; }; WalnutOpsInternal.DoLogExpunge[0] }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.LongRunningApply[ExpL]; }; GetTimeOfLastExpunge: PUBLIC ENTRY PROC RETURNS[when: BasicTime.GMT] = { ENABLE UNWIND => NULL; Gdle: PROC = { when _ WalnutDB.GetTimeOfLastExpunge[] }; WalnutOpsInternal.CheckInProgress[]; WalnutOpsInternal.CarefullyApply[Gdle, FALSE] }; <<>> <> <> <<>> ReadArchiveFile: PUBLIC ENTRY PROC[file: ROPE, msgSet: WalnutDefs.MsgSet _ [NIL, -1]] RETURNS[numNew: INT] = { <> ENABLE UNWIND => NULL; ok: BOOL _ FALSE; fStream: STREAM; reason: ROPE; Raf: PROC = { at: INT; IF msgSet.name # NIL THEN [] _ WalnutDB.VerifyMsgSet[msgSet]; at _ WalnutLog.StartReadArchiveFile[file, msgSet.name].at; WalnutDB.SetReadArchivePos[at]; }; Raf2: PROC = { [] _ WalnutLog.EndReadArchiveFile[]; WalnutDB.SetReadArchivePos[0]; }; Caf: PROC[inProgress: BOOL] = { fromPos: INT _ 0; IF ~inProgress THEN { at: INT; ok _ WalnutLog.PrepareToCopyTempLog[ which: readArchive, pagesAlreadyCopied: 0, reportProc: WalnutOpsInternal.CheckReport]; IF ~ok THEN RETURN; at _ WalnutLog.StartCopyReadArchive[].at; WalnutDB.SetCopyReadArchivePos[at]; WalnutDB.SetOpInProgressPos[at]; WalnutRoot.CommitAndContinue[]; } ELSE { -- calculate fromPos logLen: INT _ WalnutLog.LogLength[]; startedCopyAt, startCopyPos: INT; startCopyPos _ WalnutDB.GetCopyReadArchivePos[]; IF WalnutLog.SetPosition[startCopyPos] # 0 THEN ERROR WalnutDefs.Error[$log, $BadLog, IO.PutFR["no entry at %g", IO.int[startCopyPos]]]; [] _ WalnutLog.NextEntry[]; -- skip the copy entry startedCopyAt _ WalnutLog.NextAt[]; fromPos _ logLen - startedCopyAt; }; CheckReport[ IO.PutFR["\nCopying the ReadArchiveTempLog, starting at bytePos %g\n", IO.int[fromPos]]]; WalnutLog.CopyTempLog[ readArchive, WalnutDB.GetCopyReadArchivePos[], fromPos, CheckReport]; CheckReport["\n"]; WalnutDB.SetParseLogInProgress[TRUE]; WalnutDB.SetParseLogPos[WalnutDB.GetOpInProgressPos[]]; WalnutDB.SetOpInProgressPos[-1]; }; WalnutOpsInternal.CheckInProgress[]; fStream _ WalnutStream.Open[name: file, readOnly: TRUE ! FS.Error => { CheckReport[error.explanation]; fStream _ NIL; CONTINUE} ].strm; IF fStream = NIL THEN RETURN[-1]; WalnutOpsInternal.CarefullyApply[proc: Raf, didUpdate: TRUE]; BEGIN ENABLE BEGIN FS.Error => { reason _ error.explanation; GOTO exit }; IO.Error => { reason _ FS.ErrorFromStream[stream].explanation; IF reason = NIL THEN reason _ "IO Error creating readArchiveLog"; GOTO exit }; END; [ok, reason] _ WalnutMiscLog.CreateReadArchiveLog[fStream, msgSet.name, CheckReport]; EXITS exit => ok _ FALSE; END; fStream.Close[ ! IO.Error, FS.Error => CONTINUE]; IF ~ok THEN { CheckReport[IO.PutFR[" Archive Read of %g failed", IO.rope[file]]]; IF reason # NIL THEN CheckReport[" Error reported as: ", reason]; RETURN[-1]; } ELSE Raf2[]; WalnutOpsInternal.LongRunningApply[Caf]; IF ~ok THEN { CheckReport[IO.PutFR[" Out of space trying to copy readArchiveLog for file %g", IO.rope[file]]]; RETURN[-1]; }; CheckReport["\nAdding messages to database\n"]; numNew _ WalnutOpsInternal.ParseLog[TRUE]; }; WriteArchiveFile: PUBLIC ENTRY PROC[ file: ROPE, msgSetList: LIST OF WalnutDefs.MsgSet, append: BOOL] RETURNS[ok: BOOL]= { <> ENABLE UNWIND => NULL; wStream: STREAM; someMsgWasTooBig: BOOL _ FALSE; walnutItemForm: ROPE = "@%05d 00525 %05d\n"; -- 20 chars, tioga formatting startHeaderForm: ROPE = "*start*\n%05d %05d US \n"; thisMsgSet, exp: ROPE; startHeaderFixedLen: INT = 24; first: BOOL _ TRUE; WriteProc: PROC[msg, TOCentry: ROPE, hasBeenRead: BOOL, startOfSubject: INT] = { textStart, textLen, formatLen, prefixLen: INT; length, walnutItemLen: INT _ 0; walnutItem: ROPE; [textStart, textLen, formatLen, , ] _ WalnutDB.GetMsgText[msg]; walnutItem _ Rope.Cat[msg, "\n", thisMsgSet, "\n"]; walnutItemLen _ WalnutMiscLog.walnutItemFixedLength + walnutItem.Length[] + formatLen; IF formatLen # 0 THEN walnutItemLen _ walnutItemLen + 1; -- for extra CR after formatting prefixLen _ startHeaderFixedLen + walnutItemLen; length _ prefixLen + textLen + 1; -- extra CR after text IF length > 99999 THEN { CheckReport[IO.PutFR["\nLength of msg %g is too big (%g bytes) - skipping", IO.rope[msg], IO.int[length]] ]; someMsgWasTooBig _ TRUE; RETURN }; <<-- the -2 below are because the bytecount within the prefix item does not include the surrounding @'s>> wStream.PutRope[ IO.PutFR[startHeaderForm, IO.int[length], IO.int[prefixLen]] ]; wStream.PutRope[ IO.PutFR[walnutItemForm, IO.int[walnutItemLen-2], IO.int[formatLen] ]]; wStream.PutRope[walnutItem]; IF formatLen # 0 THEN { WalnutLog.CopyBytesToArchive[wStream, textStart+textLen, formatLen]; wStream.PutChar['\n]; }; wStream.PutChar['@]; WalnutLog.CopyBytesToArchive[wStream, textStart, textLen]; wStream.PutChar['\n]; }; ok _ FALSE; WalnutOpsInternal.CheckInProgress[]; BEGIN ENABLE BEGIN WalnutDefs.Error => { IF wStream # NIL THEN wStream.Close[ ! IO.Error, FS.Error => CONTINUE]; REJECT; }; WalnutDefs.VersionMismatch => { IF wStream # NIL THEN wStream.Close[ ! IO.Error, FS.Error => CONTINUE]; REJECT; }; IO.Error => { CheckReport[exp _ FS.ErrorFromStream[stream].explanation]; GOTO err }; FS.Error => { CheckReport[exp _ error.explanation]; GOTO err }; END; BEGIN wStream _ WalnutStream.Open[ name: file, useOldIfFound: append, exclusive: TRUE ! FS.Error => { CheckReport[error.explanation]; GOTO none }].strm; EXITS none => wStream _ NIL; END; IF wStream = NIL THEN { CheckReport[IO.PutFR["\nCould not open %g", IO.rope[file]]]; RETURN }; IF append THEN wStream.SetIndex[wStream.GetLength[]] ELSE { wStream.SetIndex[0]; wStream.SetLength[0]; }; CheckReport["\n Archiving: "]; FOR mL: LIST OF WalnutDefs.MsgSet _ msgSetList, mL.rest UNTIL mL=NIL DO thisMsgSet _ mL.first.name; IF ~WalnutDB.VerifyMsgSet[mL.first] THEN { CheckReport["\n MsgSet ", thisMsgSet, " doesn't exist - continuing"]; LOOP; }; IF first THEN first _ FALSE ELSE CheckReport[", "]; CheckReport[thisMsgSet]; [] _ WalnutDB.EnumerateMsgsInSet[name: thisMsgSet, proc: WriteProc]; ENDLOOP; EXITS err => { wStream.Close[]; ERROR WalnutDefs.Error[$log, $ErrorDuringWriteArchive, exp]; }; END; wStream.Close[]; ok _ TRUE; CheckReport["\n Finished writing archive file\n"]; }; <<-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *>> RegisterReporter: PUBLIC ENTRY PROC[reportStream: IO.STREAM] = { ENABLE UNWIND => NULL; FOR rL: LIST OF IO.STREAM_ reporterList, rL.rest UNTIL rL= NIL DO IF rL.first = reportStream THEN RETURN; ENDLOOP; reporterList_ CONS[reportStream, reporterList]; }; UnregisterReporter: PUBLIC ENTRY PROC[reportStream: IO.STREAM] = { ENABLE UNWIND => NULL; prev: LIST OF IO.STREAM; IF reporterList = NIL OR reportStream = NIL THEN RETURN; IF reporterList.first = reportStream THEN {reporterList_ reporterList.rest; RETURN}; prev_ reporterList; FOR rL: LIST OF IO.STREAM_ reporterList, rL.rest UNTIL rL= NIL DO IF rL.first = reportStream THEN { prev.rest_ rL.rest; RETURN}; prev_ rL; ENDLOOP; }; CheckReport: PUBLIC PROC[msg1, msg2, msg3: ROPE_ NIL] = { IF reporterList = NIL THEN RETURN; FOR rL: LIST OF IO.STREAM_ reporterList, rL.rest UNTIL rL= NIL DO IF msg1 # NIL THEN rL.first.PutRope[msg1]; IF msg2 # NIL THEN rL.first.PutRope[msg2]; IF msg3 # NIL THEN rL.first.PutRope[msg3]; ENDLOOP; }; <> CheckForRecentActivity: ENTRY PROC = { ENABLE UNWIND => recentActivity _ FALSE; DO WAIT checkActivityCondition; IF started THEN { IF recentActivity = TRUE THEN recentActivity _ FALSE ELSE IF ~isShutdown THEN { isShutdown _ TRUE; WalnutLog.ForgetLogStreams[]; WalnutRoot.CloseTransaction[]; isShutdown _ TRUE; StatsReport[" $$ Close transactions due to inactivity"]; }; }; ENDLOOP; }; <<* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *>> recentActivity _ FALSE; TRUSTED {Process.Detach[FORK CheckForRecentActivity] }; END.