-- NuthatchDBImpl.mesa
--Last Edited by: Lia, September 30, 1983 11:46 am
--Last Edited by: Swinehart, April 30, 1984 2:15:04 pm PDT
DIRECTORY
DB USING [Aborted, AbortTransaction, B2V, BoolType, CloseTransaction, DeclareAttribute, DeclareDomain, DeclareEntity, DeclareIndex, DeclareRelation, DeclareRelship, DeclareSegment, DestroyRelship, Error, Failure, GetF, GetP, I2V, Initialize, IntType, MarkTransaction, NameOf, OpenTransaction, S2V, SetF, SetP, RopeType, T2V, TimeType, TransactionOf, 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:
BOOL←
TRUE]
RETURNS [success:
BOOL←
FALSE, logReadPoint:
INT] = {
Setting up DB segment. --
abortFirst: BOOL←FALSE;
{
ENABLE {
UNWIND => NULL;
DB.Error, DB.Failure => GOTO Failed;
DB.Aborted => { abortFirst ← TRUE; RETRY; };
};
relship: Relship←NIL;
segmentNoGood: BOOL←FALSE;
alreadyOpen: BOOL←FALSE;
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; RESUME}
];
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. --
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];
-- index1: Index -- []← DB.DeclareIndex[interestRelation, LIST[irUser, irID, irIDType]];
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];
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, noLog: FALSE !
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.TransactionOf[$Nuthatch]#
NIL
THEN
DB.AbortTransaction[DB.TransactionOf[$Nuthatch]!DB.Failure=>CONTINUE];
};
CloseNuthatchTransaction:
INTERNAL PROC = {
pd.transactionOpen ← FALSE;
IF
DB.TransactionOf[$Nuthatch]#
NIL
THEN
DB.CloseTransaction[DB.TransactionOf[$Nuthatch]];
};
CloseTransaction:
PUBLIC ENTRY PROC = {
ENABLE UNWIND => NULL;
CloseNuthatchTransaction[];
};
MarkTransaction:
PUBLIC ENTRY PROC = {
ENABLE UNWIND => NULL;
MarkNuthatchTransaction[];
};
MarkNuthatchTransaction:
INTERNAL PROC = {
IF
DB.TransactionOf[$Nuthatch]#
NIL
THEN
DB.MarkTransaction[DB.TransactionOf[$Nuthatch]];
};
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.ROPE← NIL,
type: Rope.ROPE← NIL,
referenceCount: INT← 0
]
RETURNS [New: BOOL←FALSE] = {
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.ROPE← NIL,
type: Rope.ROPE← NIL,
referenceCount: INT← 0,
close: BOOL←TRUE
] RETURNS [New: BOOL←FALSE] = {
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: BOOL←TRUE]
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[[irUser, DB.S2V[user]], [irIDType, DB.S2V[refIDType]], [irID, DB.S2V[refID]]] , 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: BOOL←TRUE] 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.ROPE←NIL ,
referenceCount: INT← 0,
found: BOOL←FALSE] = 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: BOOL←TRUE
] = {
ENABLE UNWIND => NULL;
entity: Entity;
relship: Relship;
OpenNuthatchTransaction[];
entity ← DB.DeclareEntity[VoiceMessageDomain, voiceFileID];
relship ← DB.DeclareRelship[interestRelation, LIST[[irMsg, entity], [irUser, DB.S2V[user]], [irIDType, DB.S2V[refIDType]], [irID, DB.S2V[refID]]] --, NewOrOld --];
IF close THEN CloseNuthatchTransaction[];
};
AddInterest:
PUBLIC
ENTRY
PROC[
voiceFileID: Nuthatch.VoiceFileID←NIL,
refIDType: Nuthatch.IDType←NIL,
refID: Nuthatch.ID←NIL,
user: GVBasics.RName←NIL,
close: BOOL←TRUE
] = {
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], [irUser, DB.S2V[user]], [irIDType, DB.S2V[refIDType]], [irID, DB.S2V[refID]]] , NewOrOld];
}
ELSE
relship ← DB.DeclareRelship[interestRelation, LIST[[irUser, DB.S2V[user]], [irIDType, DB.S2V[refIDType]], [irID, DB.S2V[refID]]], 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: BOOL← DB.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.ID←NIL,
user: GVBasics.RName←NIL,
close: BOOL←TRUE
] = {
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], [irUser, DB.S2V[user]], [irIDType, DB.S2V[refIDType]], [irID, DB.S2V[refID]]] , NewOrOld];
}
ELSE
relship ← DB.DeclareRelship[interestRelation, LIST[[irUser, DB.S2V[user]], [irIDType, DB.S2V[refIDType]], [irID, DB.S2V[refID]]], 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: BOOL←TRUE
] = {
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], [irUser, DB.S2V[user]], [irIDType, DB.S2V[refIDType]], [irID, DB.S2V[refID]]] , OldOnly];
}
ELSE relship ← DB.DeclareRelship[interestRelation, LIST[[irUser, DB.S2V[user]], [irIDType, DB.S2V[refIDType]], [irID, DB.S2V[refID]]], 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: BOOL←FALSE] = {
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:
BOOL←
TRUE] = {
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[];
};
}.