Primitive message set operations
CreateMsgSet:
PUBLIC
ENTRY
PROC[opsHandle: WalnutOpsHandle, name:
ROPE, msDomainVersion: DomainVersion] = {
Create a message set with name msgSet.
the named message set already exists (return with exists TRUE).
ENABLE UNWIND => NULL;
exists: BOOL ¬ FALSE;
Cme:
INTERNAL
PROC = {
exists ¬ WalnutDB.CreateMsgSet[opsHandle, name, msDomainVersion].existed;
[] ¬ WalnutLog.CreateMsgSet[opsHandle, name];
};
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, Cme, TRUE];
IF ~exists THEN WalnutRegistryPrivate.NotifyForMsgSetEvent[created, name];
};
MsgSetExists:
PUBLIC
ENTRY
PROC[opsHandle: WalnutOpsHandle, name:
ROPE, msDomainVersion: DomainVersion]
RETURNS[exists:
BOOL, version:
INT] = {
ENABLE UNWIND => NULL;
Mse:
INTERNAL
PROC =
{ [exists, version] ¬ WalnutDB.MsgSetExists[opsHandle, name, msDomainVersion] };
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, Mse, FALSE];
};
SizeOfMsgSet:
PUBLIC
ENTRY
PROC[opsHandle: WalnutOpsHandle, name:
ROPE]
RETURNS[messages, version:
INT] = {
ENABLE UNWIND => NULL;
Sms:
INTERNAL
PROC =
{ [messages, version] ¬ WalnutDB.NumInMsgSet[opsHandle, name] };
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, Sms, FALSE];
};
EmptyMsgSet:
PUBLIC
ENTRY
PROC [opsHandle: WalnutOpsHandle, msgSet: MsgSet]
RETURNS [someInDeleted:
BOOL] = {
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).
ENABLE UNWIND => NULL;
Ems:
INTERNAL
PROC[inProgress:
BOOL] = {
someInDeleted ¬ FALSE;
IF WalnutDB.EqMsgSets[msgSet.name, DeletedName] THEN RETURN;
IF ~inProgress
THEN {
at: INT;
IF ~WalnutDB.VerifyMsgSet[opsHandle, msgSet] THEN RETURN;
at ¬ WalnutLog.EmptyMsgSet[opsHandle, msgSet.name].at;
WalnutDB.SetOpInProgressPos[opsHandle, at];
};
someInDeleted ¬ WalnutDB.EmptyMsgSet[opsHandle, msgSet, CheckReport];
};
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.LongRunningApply[opsHandle, Ems];
};
DestroyMsgSet:
PUBLIC
ENTRY
PROC [opsHandle: WalnutOpsHandle, msgSet: MsgSet, msDomainVersion: DomainVersion]
RETURNS [someInDeleted:
BOOL] = {
Remove the specified message set from the database.
msgSet must exist (ignore otherwise).
msgSet is "Active" (same as EmptyMsgSet).
msgSet is "Deleted" (do nothing).
ENABLE UNWIND => NULL;
isActive: BOOL;
Dms:
INTERNAL
PROC[inProgress:
BOOL] = {
someInDeleted ¬ FALSE;
IF ~inProgress
THEN {
at: INT;
IF ~WalnutDB.VerifyMsgSet[opsHandle, msgSet] THEN RETURN;
WalnutDB.VerifyDomainVersion[opsHandle, msDomainVersion];
at ¬
IF isActive
THEN WalnutLog.EmptyMsgSet[opsHandle, msgSet.name].at
ELSE WalnutLog.DestroyMsgSet[opsHandle, msgSet.name].at;
WalnutDB.SetOpInProgressPos[opsHandle, at];
};
IF isActive
THEN someInDeleted ¬ WalnutDB.EmptyMsgSet[opsHandle, msgSet, CheckReport]
ELSE someInDeleted ¬
WalnutDB.DestroyMsgSet[opsHandle, msgSet, msDomainVersion, CheckReport];
};
WalnutOpsInternal.CheckInProgress[opsHandle];
IF msgSet.name.Equal[DeletedName, FALSE] THEN RETURN[FALSE]; -- may not destroy Deleted
isActive ¬ msgSet.name.Equal[ActiveName, FALSE];
WalnutOpsInternal.LongRunningApply[opsHandle, Dms];
IF ~isActive THEN WalnutRegistryPrivate.NotifyForMsgSetEvent[destroyed, msgSet.name];
};
EnumerateMsgs:
PUBLIC
ENTRY
PROC[opsHandle: WalnutOpsHandle]
RETURNS[enum: EnumeratorForMsgs] = {
ENABLE UNWIND => NULL;
thisEnum: REF FromLog EnumeratorForMsgsObject;
Em:
INTERNAL
PROC = {
thisEnum.wH ¬ opsHandle;
thisEnum.createDate ¬ opsHandle.rootHandle.createDate;
thisEnum.endPos ¬ WalnutLog.LogLength[opsHandle];
};
WalnutOpsInternal.CheckInProgress[opsHandle];
thisEnum ¬ NEW[FromLog EnumeratorForMsgsObject];
WalnutOpsInternal.CarefullyApply[opsHandle, 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;
wH: WalnutOpsHandle = enum.wH;
Nm:
INTERNAL
PROC = {
skipped: INT;
isAt: INT;
natLen: NAT;
wle: WalnutLog.LogEntry;
TRUSTED {
WITH thisEnum: enum
SELECT
FROM
FromLog => {
IF thisEnum.createDate # wH.rootHandle.createDate
THEN
ERROR WalnutDefs.Error[$db, $InvalidEnumerator, "Wrong rootfile referenced"];
skipped ¬ WalnutLog.SetPosition[wH, thisEnum.scanPos];
DO
wle: WalnutLog.LogEntry;
at: INT;
[wle, at] ¬ WalnutLog.QuickScan[wH];
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 ¬ mle.msg;
isAt ¬ WalnutDB.GetMsgEntryPosition[wH, msgID];
IF isAt = -1 OR isAt > mle.entryStart THEN LOOP; -- oops or duplicate
msList ¬ WalnutDB.GetCategories[wH, msgID];
IF thisEnum.headers =
NIL
OR thisEnum.headers.maxLength < natLen
THEN
thisEnum.headers ¬ RefText.New[natLen];
WalnutLog.GetRefTextFromLog[wH, mle.entryStart+mle.textOffset, natLen, thisEnum.headers];
headers ¬ thisEnum.headers;
thisEnum.scanPos ¬ WalnutLog.NextAt[wH];
EXIT;
};
ENDCASE => LOOP;
};
ENDLOOP;
};
FromMsgSet => {
valid: BOOL;
[msgID, valid] ¬ WalnutDB.NextMsgInMsgSet[thisEnum.lazyEnum];
IF NOT valid OR msgID = NIL THEN RETURN;
isAt ¬ WalnutDB.GetMsgEntryPosition[wH, msgID];
msList ¬ WalnutDB.GetCategories[wH, msgID];
[] ¬ WalnutLog.SetPosition[wH, isAt];
wle ¬ WalnutLog.QuickScan[wH].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[wH, isAt+mle.textOffset, natLen, thisEnum.headers] }
ENDCASE };
headers ¬ thisEnum.headers };
ENDCASE =>
ERROR WalnutDefs.Error[$db, $InvalidEnumerator, "Wrong type of enumerator"];
};
};
WalnutOpsInternal.CheckInProgress[wH];
WalnutOpsInternal.CarefullyApply[wH, Nm, FALSE];
IF ~ok THEN RETURN[NIL, NIL, NIL];
};
EnumerateMsgsInMsgSet:
PUBLIC
ENTRY
PROC[opsHandle: WalnutOpsHandle, name:
ROPE]
RETURNS[enum: EnumeratorForMsgs] = {
ENABLE UNWIND => NULL;
Me:
INTERNAL
PROC = {
msgSet: WalnutDB.MsgSet;
version: INT;
exists: BOOL;
[exists, version] ¬ WalnutDB.MsgSetExists[opsHandle, name, WalnutDefs.dontCareDomainVersion];
IF NOT exists THEN RETURN;
msgSet.name ¬ name; msgSet.version ¬ version;
enum ¬ NEW[EnumeratorForMsgsObject ¬ [enumerator: FromMsgSet[lazyEnum: WalnutDB.EnumerateMsgsInMsgSet[opsHandle, msgSet]]]];
enum.wH ¬ opsHandle;
};
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, Me, FALSE];
};
MsgsEnumeration:
PUBLIC
ENTRY
PROC[opsHandle: WalnutOpsHandle, alphaOrder:
BOOL]
RETURNS[mL:
LIST
OF
ROPE] = {
ENABLE UNWIND => NULL;
Me:
INTERNAL
PROC =
{ mL ¬ WalnutDB.MsgsEnumeration[opsHandle, alphaOrder] };
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, Me, FALSE];
};
MsgSetNames:
PUBLIC
ENTRY
PROC[opsHandle: WalnutOpsHandle, alphaOrder:
BOOL]
RETURNS[mL:
LIST
OF
ROPE, msDomainVersion: DomainVersion] = {
Create an enumeration for a message set.
ENABLE UNWIND => NULL;
Ems:
INTERNAL
PROC =
{ [mL, msDomainVersion] ¬ WalnutDB.MsgSetsNames[opsHandle, alphaOrder] };
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, Ems, FALSE];
};
MsgsInSetEnumeration:
PUBLIC
ENTRY
PROC[opsHandle: WalnutOpsHandle, name:
ROPE, fromStart:
BOOL]
RETURNS [mL:
LIST
OF
ROPE, msVersion: MsgSetVersion] = {
Create an enumeration for the messages in a message set.
ENABLE UNWIND => NULL;
Emis:
INTERNAL
PROC = {
[mL, msVersion] ¬ WalnutDB.MsgsInSetEnumeration[opsHandle, name, fromStart]
};
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, Emis, FALSE];
};
EnumerateMsgSets:
PUBLIC
ENTRY
PROC [
opsHandle: WalnutOpsHandle,
alphaOrder:
BOOL ¬
TRUE, proc:
PROC[msgSet: MsgSet]
RETURNS[continue:
BOOL] ]
RETURNS [msVersion: MsgSetVersion] = {
ENABLE UNWIND => NULL;
Emss:
INTERNAL
PROC =
{ msVersion ¬ WalnutDB.EnumerateMsgSets[opsHandle, alphaOrder, proc] };
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, Emss, FALSE];
};
EnumerateMsgsInSet:
PUBLIC
ENTRY
PROC [
opsHandle: WalnutOpsHandle,
name:
ROPE,
fromStart:
BOOL,
proc:
PROC[msg, tocEntry:
ROPE, hasBeenRead:
BOOL, startOfSubject:
INT]
RETURNS[continue:
BOOL] ]
RETURNS [msVersion: MsgSetVersion] = {
ENABLE UNWIND => NULL;
Ems:
INTERNAL
PROC =
{ msVersion ¬ WalnutDB.EnumerateMsgsInSet[opsHandle, name, fromStart, proc] };
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, Ems, FALSE];
};
GenerateEntriesPlusDate:
PUBLIC
ENTRY
PROC [
opsHandle: WalnutOpsHandle,
attr:
ATOM, start:
ROPE ¬
NIL, end:
ROPE ¬
NIL,
dateStart:
ROPE ¬
NIL, dateEnd:
ROPE ¬
NIL]
RETURNS [genEnum: GeneralEnumerator] = {
ENABLE UNWIND => NULL;
GenEnt:
INTERNAL
PROC =
{ genEnum ¬ WalnutDB.GenerateEntriesPlusDate[opsHandle, attr, start, end, dateStart, dateEnd] };
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, GenEnt, FALSE];
};
NextEntry:
PUBLIC
ENTRY
PROC[genEnum: GeneralEnumerator]
RETURNS
[entry: EntryRef] = {
ENABLE UNWIND => NULL;
Ne:
INTERNAL
PROC = {
entryWalDB: WalnutDB.EntryRef ¬ WalnutDB.NextEntry[genEnum];
textStart, textLen, formatLen: INT;
contents: WalnutLog.TiogaContents;
IF entryWalDB#NIL THEN {
entry ¬ NEW[EntryObject];
entry.seFromToCcSuDaMid ¬ entryWalDB.seFromToCcSuDaMid;
entry.msgSetName ¬ entryWalDB.msgSetName;
[textStart: textStart, textLen: textLen, formatLen: formatLen] ¬ WalnutDB.GetMsgTextInfo[opsH: genEnum.opsH, msg: entryWalDB.seFromToCcSuDaMid.msgID];
contents ¬ WalnutLog.GetTiogaContents[opsH: genEnum.opsH, textStart: textStart, textLen: textLen, formatLen: formatLen];
entry.msgText ¬ contents.contents;
};
};
WalnutOpsInternal.CheckInProgress[genEnum.opsH];
WalnutOpsInternal.CarefullyApply[genEnum.opsH, Ne, FALSE];
};
Primitive message operations
MsgExists:
PUBLIC
ENTRY
PROC [opsHandle: WalnutOpsHandle, msg:
ROPE]
RETURNS[exists: BOOL] = {
ENABLE UNWIND => NULL;
Me:
INTERNAL
PROC =
{ exists ¬ WalnutDB.MsgExists[opsHandle, msg] };
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, Me, FALSE];
};
GetCategories:
PUBLIC
ENTRY
PROC [opsHandle: WalnutOpsHandle, msg:
ROPE]
RETURNS[msL: LIST OF ROPE] = {
ENABLE UNWIND => NULL;
GetC:
INTERNAL
PROC =
{ msL¬ WalnutDB.GetCategories[opsHandle, msg] };
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, GetC, FALSE];
};
GetDisplayProps:
PUBLIC
ENTRY
PROC[opsHandle: WalnutOpsHandle, msg:
ROPE]
RETURNS[hasBeenRead: BOOL, tocEntry: ROPE, startOfSubject: INT] = {
ENABLE UNWIND => NULL;
GetDP:
INTERNAL
PROC = {
[hasBeenRead, tocEntry, startOfSubject] ¬ WalnutDB.GetDisplayProps[opsHandle, msg]
};
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, GetDP, FALSE];
};
GetMsgDate:
PUBLIC
ENTRY
PROC [opsHandle: WalnutOpsHandle, msg:
ROPE]
RETURNS[date: BasicTime.GMT] = {
ENABLE UNWIND => NULL;
GetMD: INTERNAL PROC = { date ¬ WalnutDB.GetMsgDate[opsHandle, msg] };
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, GetMD, FALSE];
};
GetMsg:
PUBLIC
ENTRY
PROC [opsHandle: WalnutOpsHandle, 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[opsHandle, msg];
IF textStart = 0 THEN RETURN;
contents ¬ WalnutLog.GetTiogaContents[opsHandle, textStart, textLen, formatLen];
shortName ¬ herald.Substr[0, shortNameLen];
};
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, GetMT, FALSE];
};
GetMsgText:
PUBLIC
ENTRY
PROC [opsHandle: WalnutOpsHandle, 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[opsHandle, 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[opsHandle, textStart, textLen, contents];
EXITS
oops =>
ERROR WalnutDefs.Error[$log, $MsgTooLong, "Msg will not fit in a REF TEXT"];
END;
};
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, Gmt, FALSE];
};
GetMsgHeaders:
PUBLIC
ENTRY
PROC [opsHandle: WalnutOpsHandle, msg:
ROPE, text:
REF
TEXT]
RETURNS[headers: REF TEXT] = {
ENABLE UNWIND => NULL;
natLen: NAT ;
Gmh:
INTERNAL
PROC = {
wle: WalnutLog.LogEntry;
isAt: INT = WalnutDB.GetMsgEntryPosition[opsHandle, msg];
headers ¬ text;
IF isAt = -1
THEN {
IF headers # NIL THEN headers.length ¬ 0;
RETURN};
[] ¬ WalnutLog.SetPosition[opsHandle, isAt];
[wle, ] ¬ WalnutLog.QuickScan[opsHandle];
BEGIN ENABLE RuntimeError.BoundsFault => GOTO oops;
TRUSTED {
WITH mle: wle
SELECT
FROM
CreateMsg => {
natLen ¬ mle.headersLen;
IF headers =
NIL
OR headers.maxLength < natLen
THEN
headers ¬ RefText.New[natLen];
WalnutLog.GetRefTextFromLog[
opsHandle, mle.entryStart+mle.textOffset, natLen, headers];
};
ENDCASE => NULL;
};
EXITS
oops =>
ERROR WalnutDefs.Error[$log, $MsgHeadersTooLong,
IO.PutFR1["Msg headers (%g bytes) will not fit in a REF TEXT", [integer[natLen]]] ];
END;
};
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, Gmh, FALSE];
};
GetMsgShortName:
PUBLIC
ENTRY
PROC [opsHandle: WalnutOpsHandle, msg:
ROPE]
RETURNS[shortName: ROPE] = {
ENABLE UNWIND => NULL;
Gmsn:
INTERNAL
PROC = {
shortNameLen: INT;
herald: ROPE;
[, , , herald, shortNameLen] ¬ WalnutDB.GetMsgText[opsHandle, msg];
shortName ¬ herald.Substr[0, shortNameLen];
};
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, Gmsn, FALSE];
};
GetMsgSize:
PUBLIC
ENTRY
PROC [opsHandle: WalnutOpsHandle, msg:
ROPE]
RETURNS[textLen, formatLen: INT] = {
ENABLE UNWIND => NULL;
Gms:
INTERNAL
PROC =
{ [, textLen, formatLen] ¬ WalnutDB.GetMsgTextInfo[opsHandle, msg] };
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, Gms, FALSE];
};
GetIsInReplyTo:
PUBLIC
ENTRY
PROC [opsHandle: WalnutOpsHandle, msg:
ROPE]
RETURNS[isInReplyTo: BOOL] = {
ENABLE UNWIND => NULL;
Giirt:
INTERNAL
PROC =
{ isInReplyTo ¬ WalnutDB.GetIsInReplyTo[opsHandle, msg] };
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, Giirt, FALSE];
};
GetHasBeenRead:
PUBLIC
ENTRY
PROC [opsHandle: WalnutOpsHandle, msg:
ROPE]
RETURNS[hadBeenRead: BOOL] = {
ENABLE UNWIND => NULL;
Ghbr:
INTERNAL
PROC = {
hadBeenRead ¬ WalnutDB.GetHasBeenRead[opsHandle, msg];
};
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, Ghbr, FALSE];
};
SetHasBeenRead:
PUBLIC
ENTRY
PROC [opsHandle: WalnutOpsHandle, msg:
ROPE] = {
ENABLE UNWIND => NULL;
Shbr:
INTERNAL
PROC = {
[] ¬ WalnutLog.HasBeenRead[opsHandle, msg];
WalnutDB.SetHasBeenRead[opsHandle, msg];
};
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, Shbr, TRUE];
WalnutRegistryPrivate.NotifyForMsgEvent[firstRead, msg];
};
GenerateUniqueMsgName:
PUBLIC
PROC[opsHandle: WalnutOpsHandle]
RETURNS[msg:
ROPE] = {
ENABLE UNWIND => NULL;
Gumn:
INTERNAL
PROC = {
DO
-- this should never loop more than once
msg ¬ MailUtils.GeneratePostmark[machine: ThisMachine.Name[] ];
IF NOT WalnutDB.MsgExists[opsHandle, msg] THEN EXIT;
ENDLOOP;
};
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, Gumn, FALSE];
};
More complex message set / message operations
AddMsg:
PUBLIC
ENTRY
PROC[opsHandle: WalnutOpsHandle, msg:
ROPE, from, to: MsgSet]
RETURNS [exists: BOOL ¬ FALSE] = {
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)
ENABLE UNWIND => NULL;
Am:
INTERNAL
PROC = {
exists ¬ WalnutDB.AddMsg[opsHandle, msg, from, to];
[] ¬ WalnutLog.AddMsg[opsHandle, msg, to.name];
};
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, Am, TRUE];
IF NOT exists THEN WalnutRegistryPrivate.NotifyForMove[msg: msg, to: to.name, from: NIL];
};
MoveMsg:
PUBLIC
ENTRY
PROC[opsHandle: WalnutOpsHandle, msg:
ROPE, from, to: MsgSet]
RETURNS [exists: BOOL ¬ FALSE] = {
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)
ENABLE UNWIND => NULL;
Mm:
INTERNAL
PROC = {
exists ¬ WalnutDB.MoveMsg[opsHandle, msg, from, to];
[] ¬ WalnutLog.MoveMsg[opsHandle, msg, from.name, to.name];
};
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, Mm, TRUE];
IF
NOT exists
THEN
WalnutRegistryPrivate.NotifyForMove[msg: msg, to: to.name, from: from.name];
};
RemoveMsg:
PUBLIC
ENTRY
PROC [opsHandle: WalnutOpsHandle, msg:
ROPE, from: MsgSet, deletedVersion: MsgSetVersion]
RETURNS [deleted: BOOL ¬ FALSE] = {
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).
ENABLE UNWIND => NULL;
Rm:
INTERNAL
PROC = {
deleted ¬ WalnutDB.RemoveMsg[opsHandle, msg, from, deletedVersion];
[] ¬ WalnutLog.RemoveMsg[opsHandle, msg, from.name];
};
WalnutOpsInternal.CheckInProgress[opsHandle];
WalnutOpsInternal.CarefullyApply[opsHandle, Rm, TRUE];
IF deleted
THEN WalnutRegistryPrivate.NotifyForMsgEvent[deleted, msg]
ELSE WalnutRegistryPrivate.NotifyForMove[msg: msg, to: NIL, from: from.name];
};
Starting and stopping Walnut
ValidOpsHandle:
PUBLIC
ENTRY PROC[opsHandle: WalnutOpsHandle]
RETURNS[isValid:
BOOL] = {
ENABLE UNWIND => NULL;
IF opsHandle = NIL THEN RETURN[FALSE];
RETURN[opsHandle.kernelHandle # NIL];
};
CreateWalnutOpsHandle:
PUBLIC
ENTRY
PROC[rootFile:
ROPE, wantReadOnly:
BOOL ¬
FALSE]
RETURNS[opsHandle: WalnutOpsHandle] = {
ENABLE UNWIND => NULL;
canonicalName: ROPE ¬ FS.ExpandName[rootFile].fullFName;
opsHL: LIST OF WalnutOpsHandle = WalnutRoot.GetOpsHandleList[];
tries: INT ¬ 0;
ExistingHandle:
PROC[file:
ROPE]
RETURNS[opsH: WalnutOpsHandle] = {
FOR wL:
LIST
OF WalnutOpsHandle ¬ opsHL, wL.rest
UNTIL wL =
NIL
DO
IF file.Equal[wL.first.rootName, FALSE] THEN RETURN[wL.first];
ENDLOOP;
RETURN[NIL];
};
RemoveOldHandle:
PROC[ohL:
LIST
OF WalnutOpsHandle]
RETURNS[new:
LIST
OF WalnutOpsHandle] = {
prev: LIST OF WalnutOpsHandle ¬ NIL;
new ¬ ohL; -- in case fall out of loop
FOR wL:
LIST
OF WalnutOpsHandle ¬ ohL, wL.rest
UNTIL wL =
NIL
DO
IF
NOT canonicalName.Equal[wL.first.rootName,
FALSE]
THEN
{ prev ¬ wL; LOOP };
WalnutOpsInternal.InternalShutdown[wL.first ! WalnutDefs.Error => CONTINUE];
wL.first.kernelHandle ¬ NIL;
wL.first.logHandle ¬ NIL;
wL.first.expungeHandle ¬ NIL;
wL.first.schemaHandle ¬ NIL;
wL.first.rootHandle ¬ NIL;
IF prev = NIL THEN RETURN[wL.rest];
prev.rest ¬ wL.rest;
RETURN[ohL];
ENDLOOP;
};
WalnutRoot.ExamineHandleList[RemoveOldHandle];
opsHandle ¬
NEW[WalnutOpsHandleRec ¬ [rootName: canonicalName, readOnly: wantReadOnly] ];
opsHandle.kernelHandle ¬ NEW[KernelHandleRec];
DO
dupRoot: ROPE ¬ NIL;
prevH: WalnutOpsHandle;
[] ¬ WalnutRoot.Open[opsH: opsHandle, newHandle:
TRUE ! WalnutDefs.Error => {
IF NOT ((code = $DuplicateSegmentName) OR (code = $DuplicateSegmentIndex)) THEN REJECT;
IF tries = 2 THEN REJECT; -- don't loop forever
dupRoot ¬ explanation;
prevH ¬ ExistingHandle[dupRoot];
IF prevH = NIL THEN REJECT;
IF ( prevH.kernelHandle # NIL ) AND ( NOT prevH.kernelHandle.isShutdown ) THEN REJECT;
CONTINUE;
};
]; -- checks for duplicate segments
IF dupRoot = NIL THEN RETURN;
canonicalName ¬ dupRoot;
WalnutRoot.ExamineHandleList[RemoveOldHandle];
tries ¬ tries + 1;
ENDLOOP;
};
GetHandleForRootfile:
PUBLIC
ENTRY
PROC[rootFile:
ROPE]
RETURNS[opsHandle: WalnutOpsHandle] = {
canonicalName: ROPE = FS.ExpandName[rootFile].fullFName;
FOR wL:
LIST
OF WalnutOpsHandle ¬ WalnutRoot.GetOpsHandleList[], wL.rest
UNTIL wL =
NIL
DO
IF canonicalName.Equal[wL.first.rootName, FALSE] THEN RETURN[wL.first];
ENDLOOP;
RETURN[NIL];
};
Startup:
PUBLIC
ENTRY
PROC [opsHandle: WalnutOpsHandle]
RETURNS[newMailExists:
BOOL] = {
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).
kH: KernelHandle;
IF (kH ¬ opsHandle.kernelHandle) =
NIL
THEN
kH ¬ opsHandle.kernelHandle ¬ NEW[KernelHandleRec];
BEGIN
ENABLE {
WalnutDefs.Error => { kH.errorInProgress ¬ TRUE; REJECT};
UNWIND => NULL;
};
schemaInvalid: BOOL ¬ TRUE;
InitialCheck:
INTERNAL
PROC = {
dbRootCreateDate: GMT;
dbFileKey, dbMailFor: ROPE;
IF opsHandle.key.Length[] # 0
THEN {
[dbRootCreateDate, dbFileKey, dbMailFor] ¬ WalnutDB.GetRootInfo[opsHandle];
IF ( dbRootCreateDate = BasicTime.nullGMT )
OR
( dbFileKey.Length[] = 0 )
OR ( dbMailFor.Length[] = 0)
THEN {
WalnutDB.SetRootInfo[opsHandle, opsHandle.rootHandle.createDate, opsHandle.key, opsHandle.mailFor];
WalnutRoot.CommitAndContinue[opsHandle];
}
ELSE {
IF dbRootCreateDate # opsHandle.rootHandle.createDate
THEN
WalnutDefs.Error[$db, $WrongRootFile,
IO.PutFR["RootFile has date %g, database says %g",
[time[opsHandle.rootHandle.createDate]], [time[dbRootCreateDate]]] ];
IF ~dbFileKey.Equal[opsHandle.key,
FALSE]
THEN
WalnutDefs.Error[$db, $WrongRootFile,
IO.PutFR["RootFile has key %g, database says %g",
[rope[opsHandle.key]], [rope[dbFileKey]]] ];
IF ~dbMailFor.Equal[opsHandle.mailFor,
FALSE]
THEN
WalnutDefs.Error[$db, $WrongRootFile,
IO.PutFR["RootFile says mailFor %g, database says %g",
[rope[opsHandle.mailFor]], [rope[dbMailFor]]] ];
};
};
IF (kH.newMailSomewhere ¬ WalnutDB.GetNewMailLogLength[opsHandle] # 0)
THEN RETURN;
check for unaccepted mail
BEGIN
serverList: LIST OF ServerInfo ¬ WalnutDB.EnumerateServers[opsHandle];
FOR sL:
LIST
OF ServerInfo ¬ serverList, sL.rest
UNTIL sL =
NIL
DO
IF (kH.newMailSomewhere ¬ sL.first.num#0) THEN RETURN;
ENDLOOP;
END;
};
IF kH.started THEN RETURN WITH ERROR WalnutDefs.Error[$db, $AlreadyStarted];
IF opsHandle.rootHandle = NIL THEN [] ¬ WalnutRoot.Open[opsHandle];
StartStatsReporting[opsHandle];
BEGIN
exp: ROPE;
BEGIN
schemaInvalid ¬ WalnutRoot.StartTransaction[opsHandle ! WalnutDefs.Error => {
IF code # $MismatchedSegment THEN REJECT;
exp ¬ explanation;
GOTO mismatched;
}];
EXITS
mismatched => RETURN WITH ERROR WalnutDefs.Error[$db, $SchemaMismatch, exp];
END;
END;
this second StartTransaction is to really test for schemaInvalid - Cypress does some weird stuff here; the first StartTransaction always returns schemaInvalid if this database hasn't been opened before; the second StartTransaction will return schemaInvalid = FALSE
schemaInvalid ¬ WalnutRoot.StartTransaction[opsHandle];
opsHandle.kernelHandle.isShutdown ¬ FALSE;
WalnutLog.OpenLogStreams[opsHandle];
BEGIN
exp: ROPE;
BEGIN
[] ¬ WalnutDB.DeclareDB[opsHandle, schemaInvalid !
WalnutDefs.SchemaMismatch => { exp ¬ explanation; GOTO mismatch}];
StatsReport[opsHandle, "\n\n ***** Startup called with rootFile: %g", [rope[opsHandle.rootName]] ];
EXITS
mismatch => RETURN WITH ERROR WalnutDefs.Error[$db, $SchemaMismatch, exp];
END;
END;
kH.started ¬ TRUE;
kH.errorInProgress ¬ FALSE;
CarefullyApply[opsHandle, InitialCheck, FALSE];
CheckInProgress[opsHandle];
WalnutRegistryPrivate.NotifyForEvent[started]; -- has to be done by higher levels
RETURN[kH.newMailSomewhere];
END;
};
Shutdown:
PUBLIC
ENTRY
PROC[opsHandle: WalnutOpsHandle] = {
Save the Walnut state and shutdown Walnut.
ENABLE {
WalnutDefs.Error => { opsHandle.kernelHandle.errorInProgress ¬ TRUE; REJECT};
UNWIND => NULL;
};
WalnutOpsInternal.InternalShutdown[opsHandle];
};
Scavenge:
PUBLIC
ENTRY
PROC[opsHandle: WalnutOpsHandle]
RETURNS[newMailExists: BOOL] = {
kH: KernelHandle;
IF (kH ¬ opsHandle.kernelHandle) =
NIL
THEN
kH ¬ opsHandle.kernelHandle ¬ NEW[KernelHandleRec];
opsHandle.readOnly ¬ FALSE;
BEGIN
ENABLE {
WalnutDefs.Error => { kH.errorInProgress ¬ TRUE; REJECT};
UNWIND => NULL;
};
WalnutLog.ShutdownLog[opsHandle];
WalnutRoot.Shutdown[opsHandle];
[] ¬ WalnutRoot.Open[opsH: opsHandle, newSegmentOk: TRUE];
StartStatsReporting[opsHandle];
StatsReport[opsHandle, "\n *** Scavenge"];
IF opsHandle.readOnly
THEN
ERROR WalnutDefs.Error[$DB, $IsReadOnly, "Can't erase a readonly database"];
[] ¬ WalnutRoot.StartTransaction[opsHandle, FALSE]; -- don't care if schema is invalid
opsHandle.kernelHandle.isShutdown ¬ FALSE;
WalnutLog.OpenLogStreams[opsHandle];
WalnutRoot.AcquireWriteLock[opsHandle];
kH.started ¬ TRUE;
kH.errorInProgress ¬ FALSE;
WalnutRoot.EraseDB[opsHandle];
[] ¬ WalnutDB.InitSchema[opsHandle];
WalnutDB.SetRootInfo[opsHandle, opsHandle.rootHandle.createDate, opsHandle.key, opsHandle.mailFor];
WalnutDB.SetParseLogInProgress[opsHandle, TRUE];
WalnutDB.SetParseLogPos[opsHandle, 0];
WalnutRoot.CommitAndContinue[opsHandle];
kH.newMailSomewhere ¬ FALSE;
[] ¬ WalnutOpsInternal.ParseLog[opsHandle, TRUE];
WalnutDB.SetTimeOfLastScavenge[opsHandle, BasicTime.Now[]];
newMailExists ¬ kH.newMailSomewhere;
END;
};