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 }; 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. *WalnutOpsImpl.mesa Copyright c 1984, 1986 by Xerox Corporation. All rights reserved. Willie-Sue, September 17, 1986 11:15:42 am PDT Donahue, May 12, 1986 2:25:09 pm PDT Implementation of (some of) WalnutOps Last Edited by: Willie-sue, January 10, 1985 3:45:31 pm PST Last Edited by: Donahue, December 11, 1984 8:51:42 pm PST Types Public variables Private types Variables Procedures These procedures are all of the primitive atomic actions out of which Walnut is built. More complex operations will be found in WalnutClientOps (someday). Information about the Walnut database Is the database readonly? What is the name of the file storing the database? Adding new messages to the database Log a message from some agent other than Grapevine and add it to the database. Body is expected to conform to the syntax for messages. The first message on the log with a given name wins. Any subsequent messages with the same name are dropped on the floor. Primitive message set operations Create a message set with name msgSet. the named message set already exists (return with exists TRUE). Remove all messages from msgSet. Messages that are no longer in any message set will be added to "Deleted". msgSet is "Deleted" (do nothing). messages added to "Deleted" (return with someInDeleted TRUE). Remove the specified message set from the database. msgSet must exist (ignore otherwise). msgSet is "Active" (same as EmptyMsgSet). msgSet is "Deleted" (do nothing). Create an enumeration for a message set. Create an enumeration for the messages in a message set. Parsing a message proc is called for each fieldName encountered in the headers; if proc is NIL then all headers are returned. It is ok to take the REF TEXT returned by NextMsg and use it, wrapped in a RefText.TrustTextAsRope. Primitive message operations More complex message set / message operations Add the message (msg) to the message set (to). msg must exist (ignore). to must exist (ignore). to must not be "Deleted" (ignore). msg is already in to (return with exists TRUE) msg had been in "Deleted" (was taken out) (return with undeleted TRUE) Move the message (msg) from one message set (from) to another (to). msg must exist (ignore). from, to must exist (ignore). msg must be in from (ignore). msg is already in to (return with exists TRUE). msg was in "Deleted" (return with undeleted TRUE) msg is now in "Deleted" (return with deleted TRUE) Remove the message (msg) from the message set (from). If this action results in the message not belonging to any message set, it will be placed in the Deleted message set. msg must exist (ignore). from must exist (ignore). from must not be "Deleted" (ignore). msg must be in from (ignore). msg was added to "Deleted" (return with nowInDeleted TRUE). Adding new messages from grapevine to the database used by newMail process used by higher levels Starting and stopping Walnut Start up Walnut with a reference to a Walnut log file. This may involve replaying any actions specified in the "tail" of the log (that portion beyond the lastCommit) so that the database and log agree (in fact, if the database does not exist, it may involve reconstruction of the database in its entirety). If fullScavenge is TRUE, the database is emptied and reconstructed using information from the log. If wantReadOnly is TRUE, the log and the database are opened for reading only. If the log or the database cannot be written, readOnly is TRUE upon return. already started ($AlreadyStarted). database or log has become inaccessible ($DatabaseInaccessible, $LogInaccessible) cannot write the log or the database (return with isReadOnly TRUE). check for unaccepted mail Save the Walnut state and shutdown Walnut. Procedures Space conservation (removing unreferenced messages) Producing and reading archive files Reading an archive file is a long operation; it is resilient, so that it will complete when restarted. The file is first copied to the current log (if the archive file exists) and then the tail of the log is "replayed", which may involve creating new messages and shuffling them around in the database. Write a "readArchiveFile" log entry; if the archiveFile exists, parses it and writes the appropriate entries (new msgs and moves if msgSet is not "Active" (NIL defaults to categories specified in the file). Then replays the log. If the file couldn't be read, numNew = -1; Write an archive file that contains the messages from the given message sets. No log entry is written and no updates are made to the database (we just hold the monitor to guarantee that no changes to the message sets occur). -- the -2 below are because the bytecount within the prefix item does not include the surrounding @'s -- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Communicating with WalnutOpsInternal * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Κ.Ϋ– "cedar" style˜šΟb™Jšœ Οmœ7™BJ™.Icode™$—J™Jšœ%™%J™J™;J™9J˜J™šΟk ˜ Jšœ ŸœŸœ˜$JšŸœŸœ˜"Jšœ Ÿœ ˜JšŸœ˜JšœŸœ˜'JšœŸœ˜Jšœ˜Jšœ Ÿœ˜!JšœŸœ ˜Jšœ Ÿœ˜"J˜Jšœ Οcœ˜šœ Ÿœ>˜NJšœ+˜+—JšœŸœ*˜@Jšœ  œ˜Jšœ œ˜"šœŸœ˜+Jšœ6˜6Jšœ˜—Jšœ ˜ JšœŸœ˜šœŸœ-˜DJ˜3Jšœ<˜˜>Jšœ"˜"Jšœ˜—Jšœ$˜$Jšœ&Ÿœ˜,JšŸœ Ÿœ;˜JJ˜—J˜š‘ œŸœŸœŸœŸœ"Ÿœ Ÿœ Ÿœ˜sJšŸœŸœŸœ˜š‘œŸ œ˜JšœE˜E—Jšœ$˜$Jšœ&Ÿœ˜-J˜—J˜š ‘ œŸœŸœŸœŸœ˜OJšŸœŸœŸœ˜š‘œŸ œ˜Jšœ5˜5—Jšœ$˜$Jšœ&Ÿœ˜-J˜—J˜š‘ œŸœŸ œŸœ˜QJšœk™kJ™Jšœ!™!Jšœ=™=JšŸœŸœŸœ˜š‘œŸœŸœ Ÿœ˜(JšœŸœ˜JšŸœ4ŸœŸœ˜BšŸœ Ÿœ˜JšœŸœ˜JšŸœ ŸœŸœ˜.Jšœ+˜+J˜ J˜—Jšœ:˜:J˜—Jšœ$˜$Jšœ(˜(J˜—J˜š‘ œŸœ1ŸœŸœ˜sJšœ3™3J™J™%J™)Jšœ!™!JšŸœŸœŸœ˜Jšœ Ÿœ˜š‘œŸœŸœ Ÿœ˜(JšœŸœ˜šŸœ Ÿœ˜JšœŸœ˜JšŸœ ŸœŸœ˜.Jšœ.˜.šœŸœ Ÿœ&˜;JšŸœ)˜-—J˜ J˜—šŸœ Ÿœ:˜JšŸœ˜Jšœ=˜=——J˜—J˜Jšœ$˜$Jš Ÿœ&ŸœŸœŸœŸœ ˜]Jšœ/Ÿœ˜6Jšœ(˜(JšŸœ ŸœD˜UJ˜—J™š ‘ œŸœŸœŸœŸœ˜EJšŸœŸœŸœ˜Jšœ Ÿœ!˜.š‘œŸœŸœ˜Jšœ4˜4Jšœ(˜(Jšœ˜—Jšœ$˜$Jšœ Ÿœ"˜0Jšœ%Ÿœ˜,JšŸœ ˜J˜—J˜š ‘œŸœŸœŸœŸ˜;Jš œ Ÿœ ŸœŸœŸœ ŸœŸœ˜;JšŸœŸœŸœ˜JšœŸœŸœ˜š‘œŸœŸœ˜Jšœ Ÿœ˜ JšœŸœ˜ JšœŸœ˜ J˜šŸœŸœŸœŸœ˜*˜ šŸœ5Ÿ˜;JšŸœH˜M—J˜2šŸ˜J˜JšœŸœ˜J˜"Jš ŸœŸœŸœŸœŸœŸœ˜BšŸœŸœ ŸœŸ˜#šœ˜Jšœ !˜;Jšœ"˜"Jšœ+˜+Jš Ÿœ ŸœŸœŸœ ˜FJšœ'˜'šŸœŸœŸœ%Ÿ˜EJšœ'˜'—JšœU˜UJšœ˜Jšœ&˜&JšŸœ˜J˜—JšŸœŸœ˜J˜—JšŸœ˜—J˜—˜JšœŸœ˜ Jšœ=˜=Jš ŸœŸœŸœ ŸœŸœŸœ˜(Jšœ+˜+Jšœ'˜'Jšœ!˜!Jšœ˜šŸœŸœ ŸœŸ˜#Jšœ˜Jšœ˜šŸœŸœŸœ%Ÿ˜EJšœ'˜'—JšœL˜LJšŸœ˜ —Jšœ˜—šŸœ˜ JšŸœG˜L—J˜—Jšœ˜—Jšœ$˜$Jšœ%Ÿœ˜,Jš ŸœŸœŸœŸœŸœŸœ˜"J˜—J˜š ‘œŸœŸœŸœŸœŸœ˜YJšŸœŸœŸœ˜š‘œŸœŸœ˜J˜Jšœ Ÿœ˜ JšœŸœ˜ JšœR˜RJšŸœŸœŸœŸœ˜Jšœ-˜-JšœŸœi˜s—Jšœ$˜$Jšœ%Ÿœ˜,Jšœ˜—J˜š‘œŸœŸœŸœ ŸœŸœŸœŸœŸœ˜RJšŸœŸœŸœ˜š‘œŸœŸœ˜Jšœ.˜.—Jšœ$˜$Jšœ%Ÿœ˜,J˜—J™š‘ œŸœŸœŸœ ŸœŸœŸœŸœŸœ%˜nJšœ(™(JšŸœŸœŸœ˜š‘œŸœŸœ˜Jšœ>˜>—Jšœ$˜$Jšœ&Ÿœ˜-J˜—J˜š‘œŸœŸœŸœŸœ ŸœŸœŸœŸœŸœ˜}Jšœ8™8JšŸœŸœŸœ˜š‘œŸœŸœ˜JšœE˜E—Jšœ$˜$Jšœ'Ÿœ˜.J˜—J˜š‘œŸœŸœŸœŸœŸœŸœŸœ˜‚JšŸœŸœŸœ˜š‘œŸœŸœ˜Jšœ<˜<—Jšœ$˜$Jšœ'Ÿœ˜.J˜—J˜š‘œŸœŸœŸœ Ÿœ ŸœŸœŸœŸœŸœŸœ˜΄JšŸœŸœŸœ˜š‘œŸœŸœ˜JšœC˜C—Jšœ$˜$Jšœ&Ÿœ˜-J˜—J˜—šœ™š ‘ œŸœŸœŸœ Ÿœ˜JJšœŸœ&˜.KšœΠ™ΠJšŸœŸœŸœ˜Jšœ$˜$Jšœ@˜@J˜J˜——šœ™J™š‘ œŸœŸœŸœŸœŸœ Ÿœ˜BJšŸœŸœŸœ˜š‘œŸ œ˜Jšœ%˜%—Jšœ$˜$Jšœ%Ÿœ˜,J˜—J˜š‘ œŸœŸœŸœŸœŸœŸœŸœŸœ˜KJšŸœŸœŸœ˜š‘œŸ œ˜Jšœ%˜%—Jšœ$˜$Jšœ'Ÿœ˜.Jšœ˜—J˜š‘œŸœŸœŸœŸœŸœŸœ ŸœŸœ˜qJšŸœŸœŸœ˜š‘œŸ œ˜JšœL˜L—Jšœ$˜$Jšœ(Ÿœ˜/J˜—J˜š‘ œŸœŸœŸœŸœŸœŸœ˜JJšŸœŸœŸœ˜Jš‘œŸ œ'˜;Jšœ$˜$Jšœ(Ÿœ˜/J˜—J˜š‘œŸœŸœŸœŸœŸœ-Ÿœ˜cJšŸœŸœŸœ˜š‘œŸ œ˜Jšœ-Ÿœ˜1JšœQ˜QJšŸœŸœŸœ˜JšœE˜EJšœ+˜+Jšœ˜—Jšœ$˜$Jšœ(Ÿœ˜/J˜—J˜š‘ œŸœŸœŸœŸœŸœŸœ˜9JšœŸœ ŸœŸœ˜ JšŸœŸœŸœ˜Jš ‘ œŸœŸœŸœŸœ˜>š‘œŸ œ˜JšœŸœ˜JšœŸœ˜ Jšœ2˜2šŸœŸœ˜Jšœ˜JšŸœ ŸœŸœ˜+JšŸœ˜—šŸœŸœŸœ˜3Jšœ˜šŸœŸœŸœŸ˜/JšœŸœ˜4—Jšœ:˜:—šŸ˜˜JšŸœG˜L——JšŸœ˜Jšœ˜—Jšœ$˜$Jšœ&Ÿœ˜-J˜—J˜š‘ œŸœŸœŸœŸœŸœŸœ˜Jšœ%Ÿœ˜*J˜—J˜š‘ œŸœŸ œ˜!JšŸœŸœŸœ˜JšœŸœŸœ ˜/Jšœ$˜$JšœŸœ˜#šŸœŸœŸ˜Jšœ4ŸœŸœ Ÿœ˜T—šŸœ$Ÿ˜*Jšœ/˜/—J˜—J˜Jšœ™J™š‘ œŸœŸœŸœŸœŸœŸœŸœŸœ ŸœŸœ!Ÿœ˜­JšŸœŸœŸœ˜Jšœ Ÿœ˜š‘œŸœ˜ šŸœ2Ÿ˜8JšœA˜A—Jšœ1˜1J˜J˜—š‘œŸœ Ÿœ˜ Jšœ Ÿœ˜Jšœ}˜}JšŸœ ŸœŸœ˜šŸœ Ÿœ˜JšœŸœ#˜*J˜ Jšœ˜J˜šŸœ˜JšœŸœ ˜'JšœF˜FJšœ˜Jšœ œ˜4Jšœ5˜5J˜——JšœŸœ˜#šœe˜eJš ˜—Jšœ$˜$JšœŸœ˜%Jšœ7˜7J˜ Jšœ˜—š‘œŸœ˜ JšŸœŸœŸœ7˜IJšœ(˜(J˜—J˜Jšœ$˜$Jšœ&Ÿœ˜-Jš Ÿœ ŸœŸœŸœŸœŸœ ˜OJ˜šŸœ Ÿœ˜Jšœ)˜)šŸœ Ÿœ˜J˜J˜JšœD˜DJšœ Ÿœ ˜9J˜—J˜JšŸœ Ÿœ˜—J˜Jšœ&Ÿœ˜-J˜—J˜š ‘ œŸœŸœŸœŸœ˜8JšŸœŸœŸœ˜š‘œŸœ Ÿœ˜JšœŸœ˜šŸœ Ÿœ˜Jšœ>˜>Jšœ"˜"J˜ J˜JšŸœ%˜)—Jšœ*˜*Jšœ˜—Jšœ$˜$Jšœ(˜(Jšœ%Ÿœ˜+J˜——J˜™š‘œŸœŸœŸœ ŸœŸœŸœŸœŸœ Ÿœ˜˜Jšœ±™±J™J™"J™RJ™CJ˜šŸœ˜Jšœ(ŸœŸœ˜6JšŸœŸœ˜Jšœ˜—J˜JšœŸœ˜JšœŸœŸœ˜J˜š‘ œŸœŸœ˜JšœŸœ˜JšœŸ˜!šŸœŸœ˜JšœF˜FšŸœ$Ÿ˜(šœ%Ÿ˜(JšœŸ˜šœŸœ˜"Jšœ7˜7J˜J˜———šŸœ˜šŸœŸœŸ˜&šœ%˜%šŸœ/˜1JšŸœ Ÿœ˜%———šŸœŸœŸ˜,šœ%˜%šŸœ4˜6JšŸœŸœ˜+———šŸœ%Ÿ˜+šœ%˜%šŸœ0˜2JšŸœŸœ˜7———J˜—J˜—šŸœJ˜LJšŸœŸœ˜ —Jšœ™šŸ˜Jšœ ŸœŸœ*˜=š ŸœŸœŸœ"ŸœŸœŸ˜BJšŸœ7ŸœŸœ˜EJšŸœ˜—JšŸœ˜—Jšœ˜—J˜JšŸœ Ÿ œŸœŸœ(˜IJšœ˜J˜šœE˜EJšœ<˜<—J˜Jšœ˜šŸ˜JšœŸœ˜ JšŸ˜šœE˜EJšŸœŸœŸœ˜)Jšœ˜JšŸœ ˜Jšœ˜—šŸ˜JšœŸœŸœŸœ-˜L—JšŸœ˜—JšŸœ˜J˜šœ-Ÿ˜.J˜—J˜Jšœ˜Jšœ˜J˜šŸ˜JšœŸœ˜ JšŸ˜šœ1˜1Jšœ2Ÿœ ˜B—Jšœ Ÿœ6Ÿœ˜XšŸ˜Jšœ ŸœŸœŸœ-˜J—JšŸœ˜—JšŸœ˜J˜Jšœ Ÿœ˜JšœŸœ˜JšœŸœ˜$Jšœ˜Jšœ4˜4Jšœ.˜.J˜—J˜š‘œŸœ˜Jšœ*™*šŸœ˜JšœŸœŸœŸœ˜6JšŸœŸœ˜Jšœ˜—Jšœ,˜,Jšœ! ˜7Jšœ ˜5Jšœ˜Jšœ ˜1Jšœ˜JšŸœŸœŸœ$Ÿœ˜IJšŸœŸœŸœ&Ÿœ˜EšŸœŸœŸ˜!Jšœ5Ÿœ˜;—Jšœ Ÿœ˜JšœŸœ˜Jšœ Ÿœ˜JšœŸœ˜Jšœ Ÿœ˜Jšœ.˜.J˜—J˜š ‘œŸœŸœŸœ Ÿœ˜+JšŸœŸœ Ÿœ˜DšŸœ˜Jšœ(ŸœŸœ˜6JšŸœŸœ˜Jšœ˜—J˜JšœŸœ˜J˜J˜J˜J˜Jšœ˜J˜šœE˜EJšœ.ŸœŸœ˜I—šŸœŸ˜JšŸœG˜L—J˜Jšœ˜Jšœ)Ÿœ "˜TJ˜J˜Jšœ Ÿœ˜JšœŸœ˜J˜Jšœ˜Jšœ#˜#Jšœ7˜7JšœŸœ˜%Jšœ˜J˜Jšœ%Ÿœ˜+J˜Jšœ Ÿœ˜&J˜0Jšœ3˜3J˜———J˜š ™ ™3š‘œŸœŸœ.Ÿœ˜_JšŸœŸœŸœ˜š‘œŸœ˜ JšœN˜N—Jšœ$˜$Jšœ&Ÿœ˜,J˜—J˜š‘ œŸœŸœŸœŸœŸœŸœ˜ZJšŸœŸœŸœ˜š‘œŸœŸœ Ÿœ˜)šŸœ Ÿœ˜JšœŸœ˜JšœJ˜JJšœ ˜ Jšœ ˜ J˜—Jšœ2˜2JšœE˜EJšœ˜—Jšœ$˜$J˜Jšœ)˜)Jšœ˜—J˜š‘œŸœŸ œ˜'JšŸœŸœŸœ˜š‘œŸ œ Ÿœ˜)šŸœ Ÿœ˜JšœŸœ% ˜HJšœ ˜ J˜—Jšœ!˜!Jšœ˜—Jšœ$˜$Jšœ)˜)Jšœ˜—J˜š ‘œŸœŸœŸœŸœŸœ˜HJšŸœŸœŸœ˜š‘œŸœ˜ Jšœ+˜+—Jšœ$˜$Jšœ'Ÿœ˜-J˜——L™™#J™―J™š ‘œŸœŸœŸœŸœŸœ˜UJšœŸœ Ÿœ˜J™JšŸœŸœŸœ˜JšœŸœŸœ˜Jšœ Ÿœ˜JšœŸœ˜ J˜š‘œŸœ˜ JšœŸœ˜JšŸœŸœŸœ$˜=Jšœ:˜:Jšœ˜J˜—J˜š‘œŸœ˜Jšœ$˜$Jšœ˜J˜—J˜š‘œŸœ Ÿœ˜Jšœ Ÿœ˜šŸœ Ÿœ˜JšœŸœ˜Jšœ{˜{JšŸœŸœŸœ˜Jšœ)˜)J˜#J˜ J˜J˜šŸœ ˜JšœŸœ˜$JšœŸœ˜!Jšœ0˜0šŸœ)Ÿ˜/šŸœ!Ÿœ˜@JšŸœ˜——Jšœ ˜4Jšœ#˜#Jšœ!˜!J˜——šœ ˜ šŸœD˜FJšŸœ˜——šœ˜JšœE˜E—Jšœ˜JšœŸœ˜%Jšœ7˜7J˜ J˜—J˜Jšœ$˜$šœ2ŸœŸœ ˜EJšœ,ŸœŸœ ˜B—JšŸœ ŸœŸœŸœ˜!J˜Jšœ7Ÿœ˜=J˜šŸœŸœŸ˜JšŸœ(Ÿœ˜6šŸœ ˜Jšœ Ÿœ%˜0JšŸœ ŸœŸœ.˜BJšŸœ˜ Jšœ˜—JšŸœ˜J˜šœ˜JšœF˜F—šŸ˜Jšœ Ÿœ˜——JšŸœ˜JšœŸœŸœ Ÿœ˜1J˜šŸœŸœ˜ Jšœ Ÿœ$Ÿœ˜CJšŸœ ŸœŸœ-˜AJšŸœ˜ J˜JšŸœ˜ —J˜Jšœ(˜(šŸœŸœ˜ šœ ŸœA˜OJšŸœ˜—JšŸœ˜ J˜—Jšœ/˜/Jšœ$Ÿœ˜*J˜—J˜š‘œŸœ˜$Jš œŸœŸœŸœŸœŸœŸœ˜UJ™αJšŸœŸœŸœ˜Jšœ Ÿ˜JšœŸœŸœ˜JšœŸœ ˜KJšœŸœ˜3J˜JšœŸœ˜JšœŸœ˜JšœŸœŸœ˜J˜š ‘ œŸœŸœŸœŸœ˜PJšœ*Ÿœ˜.JšœŸœ˜Jšœ Ÿœ˜Jšœ?˜?Jšœ3˜3šœ˜JšœF˜F—JšŸœŸœ$  ˜YJšœ0˜0Jšœ# ˜9J˜šŸœŸœ˜šœ Ÿœ=˜KJšŸœ Ÿœ˜ —JšœŸœ˜JšŸ˜J˜—J™ešœ˜JšŸœŸœŸœ˜?—šœ˜JšŸœŸœŸœ˜G—Jšœ˜šŸœŸœ˜JšœD˜DJšœ˜J˜—Jšœ˜Jšœ:˜:Jšœ˜J˜—J˜JšœŸœ˜ Jšœ$˜$J˜šŸœŸœŸ˜šœ˜Jš Ÿœ ŸœŸœŸœŸœ Ÿœ˜GJšŸœ˜J˜—šœ˜Jš Ÿœ ŸœŸœŸœŸœ Ÿœ˜GJšŸœ˜J˜—JšŸœŸœ'Ÿœ˜TJšŸœ2Ÿœ˜?JšŸœ˜J˜šŸ˜šœ˜Jšœ.ŸœŸœ ˜@Jšœ"Ÿœ˜4—šŸ˜JšœŸœ˜—JšŸœ˜—J˜šŸœ ŸœŸœ˜Jšœ ŸœŸœ˜J˜ JšŸœ˜—J˜—J˜š‘ œŸ œŸ œ˜9JšŸœŸœŸœŸœ˜"šŸœŸœŸœŸœŸœŸœŸœŸ˜AJšŸœŸœŸœ˜*JšŸœŸœŸœ˜*JšŸœŸœŸœ˜*JšŸœ˜—J˜———™$˜š‘œŸœŸœ˜&JšŸœŸœŸœ˜(šŸ˜JšŸœ˜šŸœ Ÿœ˜šŸœŸœŸœŸ˜4šŸœŸœ Ÿœ˜Jšœ Ÿœ˜J˜J˜Jšœ Ÿœ˜J˜8J˜——J˜—JšŸœ˜—J˜——J˜—J˜šœE™EJšœŸœ˜JšŸœŸœ˜7J˜—JšŸœ˜J˜—…—ˆxΚ}