-- NuthatchDBImpl.mesa
--Last Edited by: Lia, September 30, 1983 11:46 am
--Last Edited by: Swinehart, October 17, 1985 1:46:30 pm PDT
DIRECTORY
DB USING [Aborted, AbortTransaction, B2V, BoolType, CloseTransaction, DeclareAttribute, DeclareDomain, DeclareEntity, DeclareIndex, DeclareRelation, DeclareRelship, DeclareSegment, DestroyRelship, Error, Failure, GetF, GetP, GetSegmentInfo, I2V, Initialize, IntType, MarkTransaction, NameOf, Null, OpenTransaction, S2V, SetF, SetP, RopeType, T2V, TimeType, V2B, V2E, V2I, V2S, V2T],
GVBasics USING [RName],
IO USING [Close, GetCard, RIS, STREAM],
Log USING [ Problem ],
Nuthatch USING [EncryptionKey, ID, IDType, NHTime, NuthatchUserHandle, pd, Tune, VoiceFileID],
NuthatchDB,
Rope USING [ROPE],
BasicTime USING [earliestGMT, Now, Update, GMT, nullGMT],
Thrush USING [nullKey],
UserCredentials USING [Get],
UserProfile USING [ Token ]
;
NuthatchDBImpl: CEDAR MONITOR
IMPORTS
DB, IO, BasicTime, Log, Nuthatch, UserCredentials, UserProfile
EXPORTS NuthatchDB = {
OPEN NuthatchDB, pd: Nuthatch.pd;
VoiceMessageDomain: PUBLIC Domain;
interestRelation: PUBLIC Relation;
irMsg: PUBLIC Attribute;
irUser: PUBLIC Attribute;
irID: PUBLIC Attribute;
irIDType: PUBLIC Attribute;
irInterest: PUBLIC Attribute;
msgInfo: PUBLIC Relation;
miMsg: PUBLIC Attribute;
miTuneNumber: PUBLIC Attribute;
miRecordTime: PUBLIC Attribute;
miCreator: PUBLIC Attribute;
miSamples: PUBLIC Attribute;
miStartSample: PUBLIC Attribute;
miExpirationDate: PUBLIC Attribute;
miEncryptionKey: PUBLIC Attribute;
miType: PUBLIC Attribute;
miReferenceCount: PUBLIC Attribute;
nuthatchLogInfo: PUBLIC Relation;
nlLogReadPoint: PUBLIC Attribute;
nlLogFileName: PUBLIC Attribute;
nlUser: PUBLIC Attribute;
InitializeDB: PUBLIC ENTRY PROC[close: BOOLTRUE] RETURNS [success: BOOLFALSE, logReadPoint: INT] = {
Setting up DB segment. --
abortFirst: BOOLFALSE;
{
ENABLE {
UNWIND => NULL;
DB.Error, DB.Failure => GOTO Failed;
DB.Aborted => { abortFirst ← TRUE; RETRY; };
};
relship: Relship←NIL;
segmentNoGood: BOOLFALSE;
alreadyOpen: BOOLFALSE;
filePath: Rope.ROPE=UserProfile.Token[
key: "NuthatchSegment",
default: "[Luther.Alpine]<Nuthatch>Strowger>Nuthatch.Segment"];
IF abortFirst THEN AbortNuthatchTransaction[];
abortFirst←FALSE;
logReadPoint𡤀
start up DB code --
DB.Initialize[];
Check out DB segment. --
DB.DeclareSegment[filePath: filePath, segment: $Nuthatch, number: 250B ! DB.Error =>
IF code=$FileNotFound OR code=$IllegalFileName THEN {segmentNoGood←TRUE; CONTINUE}
ELSE IF code=$TransactionAlreadyOpen THEN {alreadyOpen← TRUE; CONTINUE}
];
IF segmentNoGood THEN RETURN;
IF alreadyOpen THEN CloseNuthatchTransaction[!DB.Error=>CONTINUE];
OpenNuthatchTransaction[];
relship←DB.DeclareRelship[nuthatchLogInfo, LIST[[nlUser, DB.S2V[UserCredentials.Get[].name]]]];
IF relship#NIL THEN logReadPoint←DB.V2I[DB.GetF[relship, nlLogReadPoint]];
IF close THEN CloseNuthatchTransaction[];
success←TRUE;
EXITS
Failed => {
AbortNuthatchTransaction[];
success ← FALSE;
Log.Problem["Problem initializing Nuthatch Database", $System];
};
};
};
InitializeDBVars: PROC = {
Define the schema. --
IF ~DB.Null[VoiceMessageDomain] THEN RETURN; -- Still valid.
VoiceMessageDomain← DB.DeclareDomain["VoiceMessage", $Nuthatch];
interestRelation← DB.DeclareRelation["interestRelation", $Nuthatch];
irMsg← DB.DeclareAttribute[interestRelation, "irMsg", VoiceMessageDomain];
irUser← DB.DeclareAttribute[interestRelation, "irUser", DB.RopeType];
irID← DB.DeclareAttribute[interestRelation, "irID", DB.RopeType];
irIDType← DB.DeclareAttribute[interestRelation, "irIDType", DB.RopeType];
irInterest← DB.DeclareAttribute[interestRelation, "irInterest", DB.BoolType];
IF pd.buildInterestIndices THEN {
[]← DB.DeclareIndex[interestRelation, LIST[irMsg]];
[]← DB.DeclareIndex[interestRelation, LIST[irID]];
};
msgInfo← DB.DeclareRelation["msgInfo", $Nuthatch];
miMsg← DB.DeclareAttribute[msgInfo, "miMsg", VoiceMessageDomain, Key];
miTuneNumber← DB.DeclareAttribute[msgInfo, "TuneNumber", DB.IntType];
miRecordTime← DB.DeclareAttribute[msgInfo, "miRecordTime", DB.TimeType];
miCreator← DB.DeclareAttribute[msgInfo, "miCreator", DB.RopeType];
miSamples← DB.DeclareAttribute[msgInfo, "miSamples", DB.IntType];
miStartSample← DB.DeclareAttribute[msgInfo, "miStartSample", DB.IntType];
miExpirationDate← DB.DeclareAttribute[msgInfo, "miexpirationDate", DB.TimeType];
miEncryptionKey← DB.DeclareAttribute[msgInfo, "miEncryptionKey", DB.RopeType];
miType← DB.DeclareAttribute[msgInfo, "miType", DB.RopeType];
miReferenceCount← DB.DeclareAttribute[msgInfo, "miReferenceCount", DB.IntType];
IF pd.buildMsgInfoIndices THEN []← DB.DeclareIndex[msgInfo, LIST[miMsg]];
nuthatchLogInfo← DB.DeclareRelation["nuthatchLogInfo", $Nuthatch];
nlLogReadPoint← DB.DeclareAttribute[nuthatchLogInfo, "nlLogReadPoint", DB.IntType];
nlUser← DB.DeclareAttribute[nuthatchLogInfo, "nlUser", DB.RopeType];
};
OpenNuthatchTransaction: PROC = {
IF pd.transactionOpen THEN RETURN;
pd.transactionOpen ← FALSE;
DB.OpenTransaction[segment: $Nuthatch, userName: UserCredentials.Get[].name,
password: UserCredentials.Get[].password, useTrans: NIL !
DB.Error =>
IF code=TransactionAlreadyOpen THEN { pd.transactionOpen← TRUE; CONTINUE } ];
IF ~pd.transactionOpen THEN InitializeDBVars[];
pd.transactionOpen ← TRUE;
};
AbortTransaction: PUBLIC ENTRY PROC = {
ENABLE UNWIND => NULL;
AbortNuthatchTransaction[];
};
AbortNuthatchTransaction: INTERNAL PROC = {
pd.transactionOpen ← FALSE;
IF DB.GetSegmentInfo[$Nuthatch].trans#NIL THEN
DB.AbortTransaction[DB.GetSegmentInfo[$Nuthatch].trans!DB.Failure=>CONTINUE];
};
CloseNuthatchTransaction: INTERNAL PROC = {
pd.transactionOpen ← FALSE;
IF DB.GetSegmentInfo[$Nuthatch].trans#NIL THEN
DB.CloseTransaction[DB.GetSegmentInfo[$Nuthatch].trans];
};
CloseTransaction: PUBLIC ENTRY PROC = {
ENABLE UNWIND => NULL;
CloseNuthatchTransaction[];
};
MarkTransaction: PUBLIC ENTRY PROC = {
ENABLE UNWIND => NULL;
MarkNuthatchTransaction[];
};
MarkNuthatchTransaction: INTERNAL PROC = {
IF DB.GetSegmentInfo[$Nuthatch].trans#NIL THEN
DB.MarkTransaction[DB.GetSegmentInfo[$Nuthatch].trans];
};
Clients are responsible for all transaction restart management (catching DB.various)
Catalog: INTERNAL PROC[
msg: Nuthatch.VoiceFileID← NIL,
tuneNumber: INT← -1,
recordTime: BasicTime.GMT ← BasicTime.nullGMT,
creator: GVBasics.RName← NIL,
samples: INT← -1,
startSample: INT← -1,
expirationDate: BasicTime.GMT ← BasicTime.nullGMT,
encryptionKeyRope: Rope.ROPENIL,
type: Rope.ROPENIL,
referenceCount: INT← 0
]
RETURNS [New: BOOLFALSE] = {
entity: Entity;
OpenNuthatchTransaction[];
entity ← DB.DeclareEntity[VoiceMessageDomain, msg, OldOnly];
recordTime←Nuthatch.NHTime[recordTime];
cardKey: LONG POINTER TO ARRAY[0..2) OF LONG CARDINAL=LOOPHOLE [LONG[@encryptionKey]];
keyRope: Rope.ROPE ←IO.PutFR["%bB %bB", IO.card[cardKey[0]], IO.card[cardKey[1]]];--
If the entry is new, updates all of its attributes. If it's old, just update the non-defaulted ones.
IF entity=NIL THEN { -- entry is new --
entity ← DB.DeclareEntity[VoiceMessageDomain, msg];
New←TRUE;
[] ← DB.SetP[entity, miTuneNumber, DB.I2V[tuneNumber]];
[] ← DB.SetP[entity, miRecordTime, DB.T2V[[recordTime]]];
[] ← DB.SetP[entity, miCreator, DB.S2V[creator]];
[] ← DB.SetP[entity, miSamples, DB.I2V[samples]];
[] ← DB.SetP[entity, miStartSample, DB.I2V[startSample]];
IF expirationDate=BasicTime.nullGMT THEN expirationDate←
BasicTime.Update[BasicTime.Now[], LONG[14] * 24 * 60 * 60]; -- Two weeks from now, in seconds
[] ← DB.SetP[entity, miExpirationDate, DB.T2V[[expirationDate]]];
[] ← DB.SetP[entity, miEncryptionKey, DB.S2V[encryptionKeyRope]];
[] ← DB.SetP[entity, miType, DB.S2V[type]];
[] ← DB.SetP[entity, miReferenceCount, DB.I2V[referenceCount]];
get tune number and recording time from the voiceFileId? --
}
ELSE { -- entry already exists. Only update attributes with interesting parameters. --
IF tuneNumber#-1 THEN [] ← DB.SetP[entity, miTuneNumber, DB.I2V[tuneNumber]];
[] ← DB.SetP[entity, miRecordTime, DB.T2V[[recordTime]]];
IF creator#NIL THEN [] ← DB.SetP[entity, miCreator, DB.S2V[creator]];
IF samples#-1 THEN [] ← DB.SetP[entity, miSamples, DB.I2V[samples]];
IF startSample#-1 THEN [] ← DB.SetP[entity, miStartSample, DB.I2V[startSample]];
[] ← DB.SetP[entity, miExpirationDate, DB.T2V[[expirationDate]]];
IF encryptionKeyRope#NIL THEN [] ← DB.SetP[entity, miEncryptionKey, DB.S2V[encryptionKeyRope]];
IF type#NIL THEN [] ← DB.SetP[entity, miType, DB.S2V[type]];
IF referenceCount#0 THEN [] ← DB.SetP[entity, miReferenceCount, DB.I2V[referenceCount + DB.V2I[DB.GetP[entity, miReferenceCount]]]];
New←FALSE;
};
};
CatalogVoiceFile: PUBLIC ENTRY PROC[
msg: Nuthatch.VoiceFileID← NIL,
tuneNumber: INT← -1,
recordTime: BasicTime.GMT ← BasicTime.nullGMT,
creator: GVBasics.RName← NIL,
samples: INT← -1,
startSample: INT← -1,
expirationDate: BasicTime.GMT ← BasicTime.nullGMT,
encryptionKeyRope: Rope.ROPENIL,
type: Rope.ROPENIL,
referenceCount: INT← 0,
close: BOOLTRUE
] RETURNS [New: BOOLFALSE] = {
ENABLE UNWIND => NULL;
New ← Catalog[
msg,
tuneNumber,
recordTime,
creator,
samples,
startSample,
expirationDate,
encryptionKeyRope,
type,
referenceCount];
IF close THEN CloseNuthatchTransaction[];
};
GetVoiceFileID: PUBLIC ENTRY PROC[
user: GVBasics.RName, refIDType: Rope.ROPE, refID: Rope.ROPE, close: BOOLTRUE]
RETURNS [voiceFileID: Nuthatch.VoiceFileID← NIL] = {
This routine looks in the interest relation to see if there is an interest entry relating this user, refIDType, and refID to a VoiceFileID.
IF so, it is returned.
ENABLE UNWIND => NULL;
relship: Relship;
OpenNuthatchTransaction[];
relship ← DB.DeclareRelship[interestRelation,LIST[
[irID, DB.S2V[refID]],
[irIDType, DB.S2V[refIDType]],
[irUser, DB.S2V[user]]
] , OldOnly];
Convert entity to VoiceFileID
IF relship#NIL THEN voiceFileID ← DB.NameOf[DB.V2E[DB.GetF[relship, irMsg]]];
IF close THEN CloseNuthatchTransaction[];
};
GetVoiceFileEntry: PUBLIC ENTRY PROC[
msg: Nuthatch.VoiceFileID← NIL, close: BOOLTRUE] RETURNS [
tuneNumber: Nuthatch.Tune← -1,
recordTime: BasicTime.GMT,
creator: GVBasics.RName← NIL,
samples: INT← -1,
startSample: INT← -1,
expirationDate: BasicTime.GMT,
encryptionKey: Nuthatch.EncryptionKey←Thrush.nullKey,
type: Rope.ROPENIL ,
referenceCount: INT← 0,
found: BOOLFALSE] = TRUSTED {
ENABLE UNWIND=>NULL;
This routine is called to read an entry in the voice file directory. If there is an entry with its VoiceFileID equal to msg, then the entry is returned and found is set to TRUE. Otherwise, found is set to FALSE and the default parameters are returned. --
cardKey: LONG POINTER TO ARRAY[0..2) OF LONG CARDINAL=LOOPHOLE[LONG[@encryptionKey]];
keyStream: IO.STREAM;
entity: Entity;
OpenNuthatchTransaction[];
entity ← DB.DeclareEntity[VoiceMessageDomain, msg, OldOnly];
recordTime ← BasicTime.earliestGMT;
expirationDate ← BasicTime.earliestGMT;
IF entity#NIL THEN {
tuneNumber← DB.V2I[DB.GetP[entity, miTuneNumber]];
recordTime← DB.V2T[DB.GetP[entity, miRecordTime]];
creator← DB.V2S[DB.GetP[entity, miCreator]];
samples← DB.V2I[DB.GetP[entity, miSamples]];
startSample← DB.V2I[DB.GetP[entity, miStartSample]];
expirationDate← DB.V2T[DB.GetP[entity, miExpirationDate]];
type← DB.V2S[DB.GetP[entity, miType]];
referenceCount← DB.V2I[DB.GetP[entity, miReferenceCount]];
found← TRUE;
Open DB.V2S[DB.GetP[entity, miEncryptionKey]] as a stream keyStream. --
keyStream ← IO.RIS[DB.V2S[DB.GetP[entity, miEncryptionKey]]];
MarkNuthatchTransaction[];
cardKey[0] ← IO.GetCard[keyStream];
cardKey[1] ← IO.GetCard[keyStream];
IO.Close[keyStream];
};
IF close THEN CloseNuthatchTransaction[];
};
MakeInterestEntry: PUBLIC ENTRY PROC[
voiceFileID: Nuthatch.VoiceFileID,
refIDType: Nuthatch.IDType,
refID: Nuthatch.ID,
user: GVBasics.RName,
close: BOOLTRUE
] = {
ENABLE UNWIND => NULL;
entity: Entity;
relship: Relship;
OpenNuthatchTransaction[];
entity ← DB.DeclareEntity[VoiceMessageDomain, voiceFileID];
relship ← DB.DeclareRelship[interestRelation, LIST[
[irMsg, entity],
[irID, DB.S2V[refID]],
[irIDType, DB.S2V[refIDType]],
[irUser, DB.S2V[user]]
] --, NewOrOld --];
IF close THEN CloseNuthatchTransaction[];
};
AddInterest: PUBLIC ENTRY PROC[
voiceFileID: Nuthatch.VoiceFileID←NIL,
refIDType: Nuthatch.IDType←NIL,
refID: Nuthatch.IDNIL,
user: GVBasics.RName←NIL,
close: BOOLTRUE
] = {
ENABLE UNWIND => NULL;
old: BOOL;
relship: Relship;
OpenNuthatchTransaction[];
IF voiceFileID#NIL THEN {
entity: Entity ← DB.DeclareEntity[VoiceMessageDomain, voiceFileID];
relship ← DB.DeclareRelship[interestRelation, LIST[
[irMsg, entity],
[irID, DB.S2V[refID]],
[irIDType, DB.S2V[refIDType]],
[irUser, DB.S2V[user]]
] , NewOrOld];
}
ELSE
relship ← DB.DeclareRelship[interestRelation, LIST[
[irID, DB.S2V[refID]],
[irIDType, DB.S2V[refIDType]],
[irUser, DB.S2V[user]]
], OldOnly];
IF relship=NIL THEN NULL -- New entry, so message was never received! Error! --
ELSE { -- Old entry; only bump reference count if interest is new --
interested: BOOLDB.V2B[DB.GetF[relship, irInterest]];
IF NOT interested THEN {
DB.SetF[relship, irInterest, DB.B2V[TRUE]];
voiceFileID ← DB.NameOf[DB.V2E[DB.GetF[relship, irMsg]]];
IF voiceFileID#NIL THEN old←IncrementReferenceCount[voiceFileID: voiceFileID];
};
};
IF close THEN CloseNuthatchTransaction[];
};
LoseInterest: PUBLIC ENTRY PROC[
voiceFileID: Nuthatch.VoiceFileID←NIL,
refIDType: Nuthatch.IDType←NIL,
refID: Nuthatch.IDNIL,
user: GVBasics.RName←NIL,
close: BOOLTRUE
] = {
ENABLE UNWIND => NULL;
relship: Relship;
old: BOOL;
OpenNuthatchTransaction[];
IF voiceFileID#NIL THEN {
entity: Entity ← DB.DeclareEntity[VoiceMessageDomain, voiceFileID];
relship ← DB.DeclareRelship[interestRelation, LIST[
[irMsg, entity],
[irID, DB.S2V[refID]],
[irIDType, DB.S2V[refIDType]],
[irUser, DB.S2V[user]]
] , NewOrOld];
}
ELSE
relship ← DB.DeclareRelship[interestRelation, LIST[
[irID, DB.S2V[refID]],
[irIDType, DB.S2V[refIDType]],
[irUser, DB.S2V[user]]
], OldOnly];
If there's no relship, it's a new entry, so message was never received! Unhandled Error!
Otherwise it's an old entry; only decrease reference count if interest is old.
IF relship#NIL AND DB.V2B[DB.GetF[relship, irInterest]] THEN {
voiceFileID ← DB.NameOf[DB.V2E[DB.GetF[relship, irMsg]]];
DB.SetF[relship, irInterest, DB.B2V[FALSE]];
IF voiceFileID#NIL THEN old ← DecrementReferenceCount[voiceFileID: voiceFileID];
};
-- voiceFileID=NIL is an Error, shouldn't happen. All interest entries have voiceFileID.
IF close THEN CloseNuthatchTransaction[];
};
RemoveInterestEntry: PUBLIC ENTRY PROC[
voiceFileID: Nuthatch.VoiceFileID,
refIDType: Nuthatch.IDType,
refID: Nuthatch.ID,
user: GVBasics.RName,
close: BOOLTRUE
] = {
RemoveInterestEntry eliminates the interest-describing relationship if it exists.
<<Should it check for a zero reference-count or be a no-op? Don't rightly know. Think it thru later.
Also see log notes from earlier attempt to understand all this. >>
ENABLE UNWIND => NULL;
entity: Entity;
relship: Relship←NIL;
OpenNuthatchTransaction[];
IF voiceFileID#NIL THEN {
entity ← DB.DeclareEntity[VoiceMessageDomain, voiceFileID];
IF entity#NIL THEN relship ← DB.DeclareRelship[interestRelation, LIST[
[irMsg, entity],
[irID, DB.S2V[refID]],
[irIDType, DB.S2V[refIDType]],
[irUser, DB.S2V[user]]
] , OldOnly];
}
ELSE relship ← DB.DeclareRelship[interestRelation, LIST[
[irID, DB.S2V[refID]],
[irIDType, DB.S2V[refIDType]],
[irUser, DB.S2V[user]]
], OldOnly];
IF relship#NIL THEN {
IF DB.V2B[DB.GetF[relship, irInterest]] THEN {
IF voiceFileID = NIL THEN voiceFileID ← DB.NameOf[DB.V2E[DB.GetF[relship, irMsg]]];
IF voiceFileID # NIL THEN [] ← DecrementReferenceCount[voiceFileID: voiceFileID];
};
DB.DestroyRelship[relship];
};
IF close THEN CloseNuthatchTransaction[];
};
IncrementReferenceCount: INTERNAL PROC [
voiceFileID: Nuthatch.VoiceFileID←NIL]
RETURNS [Old: BOOL←TRUE] = {
Look for the directory entry for the voice message whose VoiceFileID is voiceFileID. If there is one, increment its reference count. If not, do what??? Create one and wait for the entry to appear. (Could happen if logs are being played back, and the receiver's log precedes the recorder's.
msg: Entity ← DB.DeclareEntity[VoiceMessageDomain, voiceFileID, OldOnly];
IF msg#NIL THEN -- voice message exists --
[] ← DB.SetP[msg, miReferenceCount, DB.I2V[1+DB.V2I[DB.GetP[msg, miReferenceCount]]]]
ELSE { -- there's no entry to up ref count for --
Old←FALSE;
create entry for this voice message, with 1 as reference count. --
[] ← Catalog[msg: voiceFileID, referenceCount: 1];
};
};
DecrementReferenceCount: INTERNAL PROC [
voiceFileID: Nuthatch.VoiceFileID←NIL]
RETURNS [Old: BOOLFALSE] = {
Look for the directory entry for the voice message whose VoiceFileID is voiceFileID. If there is one, decrement its reference count. There must already be some entry for this voice file, since no log can contain a decrement entry without a preceding increment entry. --
oldRefCount: INT;
msg: Entity ← DB.DeclareEntity[VoiceMessageDomain, voiceFileID, OldOnly];
IF msg=NIL THEN RETURN; -- There's no entry to up ref count for
Create entry for this voice message, with 1 as reference count. Modify CatalogVoiceFile?--
Old←TRUE;
oldRefCount← DB.V2I[DB.GetP[msg, miReferenceCount]];
IF oldRefCount<=0 THEN RETURN;
Makes sense to decrement --
[] ← DB.SetP[msg, miReferenceCount, DB.I2V[DB.V2I[DB.GetP[msg, miReferenceCount]]-1]];
Create entry for this voice message, with 1 as reference count. Modify CatalogVoiceFile?--
};
SetReadPoint: PUBLIC ENTRY PROC [nuthatchUserHandle: Nuthatch.NuthatchUserHandle, close: BOOLTRUE] = {
ENABLE UNWIND => NULL;
Get the current read point for the user's log.--
relship: Relship;
OpenNuthatchTransaction[];
relship←DB.DeclareRelship[nuthatchLogInfo, LIST[[nlUser,DB.S2V[UserCredentials.Get[].name]]]];
IF relship#NIL THEN
DB.SetF[relship, nlLogReadPoint, DB.I2V[nuthatchUserHandle.logReadPoint]];
IF close THEN CloseNuthatchTransaction[];
};
}.