<<-- 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] = { <> 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]Strowger>Nuthatch.Segment"]; IF abortFirst THEN AbortNuthatchTransaction[]; abortFirst_FALSE; logReadPoint_0; <> DB.Initialize[]; <> 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 = { <> 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]]; }; <> <<>> 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]; <> <> <> 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]]; <> } 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] = { <> <> ENABLE UNWIND => NULL; relship: Relship; OpenNuthatchTransaction[]; relship _ DB.DeclareRelship[interestRelation, LIST[[irUser, DB.S2V[user]], [irIDType, DB.S2V[refIDType]], [irID, DB.S2V[refID]]] , OldOnly]; <> 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; <> 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; <> 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 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 ] = { <> <<<> <>>> 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] = { <> 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; <> [] _ Catalog[msg: voiceFileID, referenceCount: 1]; }; }; DecrementReferenceCount: INTERNAL PROC [ voiceFileID: Nuthatch.VoiceFileID_NIL] RETURNS [Old: BOOL_FALSE] = { <> oldRefCount: INT; msg: Entity _ DB.DeclareEntity[VoiceMessageDomain, voiceFileID, OldOnly]; IF msg=NIL THEN RETURN; -- There's no entry to up ref count for <> Old_TRUE; oldRefCount_ DB.V2I[DB.GetP[msg, miReferenceCount]]; IF oldRefCount<=0 THEN RETURN; <> [] _ DB.SetP[msg, miReferenceCount, DB.I2V[DB.V2I[DB.GetP[msg, miReferenceCount]]-1]]; <> }; SetReadPoint: PUBLIC ENTRY PROC [nuthatchUserHandle: Nuthatch.NuthatchUserHandle, close: BOOL_TRUE] = { ENABLE UNWIND => NULL; <> 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[]; }; }.