DIRECTORY
Atom USING [ GetPropFromList, PutPropOnList ],
FinchRecording,
FinchSmarts USING [ ConvDesc, DisconnectCall, LookupServiceInterface, RegisterForReports, ReportConversationState, ReportConversationStateProc, ReportRequestStateProc, VoiceConnect ],
GList USING [ Member, Nconc ],
PupSocket USING [ GetUniqueID ],
RefID USING [ ID ],
RPC USING [ ImportFailed ],
ThParty USING [ GetKeyTable, RegisterKey ],
Thrush USING [ ConversationID, EncryptionKey, InterfaceSpec, NB, nullConvID, nullID, nullKey, ROPE, SHHH ],
VoiceTemp USING [ IntervalSpec, IntervalSpecBody, IntervalSpecs, nullTuneID, TuneID, VoiceTime ],
VoiceTempRpcControl USING [ DescribeInterval, ImportNewInterface, InterfaceRecord, Play, Record, Stop ]
;
FinchRecordingImpl:
CEDAR
MONITOR
Confusion about locks here and in FinchSmartsImpl; careful!
IMPORTS GList, VoiceTempRpcControl, Atom, FinchSmarts, PupSocket, RPC, ThParty
EXPORTS FinchRecording = {
Declarations
ConvDesc: TYPE = FinchSmarts.ConvDesc;
ConversationID: TYPE = Thrush.ConversationID;
NB: TYPE = Thrush.NB;
nullID: RefID.ID = Thrush.nullID;
ROPE: TYPE = Thrush.ROPE;
SHHH: TYPE = Thrush.SHHH;
NconcIntervals: PROC[l1, l2: VoiceTemp.IntervalSpecs] RETURNS [VoiceTemp.IntervalSpecs] = INLINE { RETURN[NARROW[GList.Nconc[l1, l2]]]; };
voiceTemp: VoiceTempRpcControl.InterfaceRecord ← NIL;
Supervision
ConvStateReport:
ENTRY FinchSmarts.ReportConversationStateProc = {
[nb: NB, cDesc: ConvDesc, remark: ROPE]
rCDesc: FinchRecording.RConvDesc = GetRCDesc[cDesc];
IF rCDesc=NIL THEN ERROR;
BROADCAST rCDesc.stateChange; -- This will be enough to alert interested parties
};
ReportReport:
ENTRY FinchSmarts.ReportRequestStateProc = {
[cDesc: ConvDesc, actionReport: Thrush.ActionReport, actionRequest: REF]
RETURNS[betterActionRequest: REF]
This one should get the reports first. Then the client that requested action reports can get them too.
rCDesc: FinchRecording.RConvDesc = GetRCDesc[cDesc];
IF rCDesc=NIL THEN ERROR;
BROADCAST rCDesc.stateChange;
SELECT actionReport.actionClass
FROM
$keyDistribution => {
BROADCAST rCDesc.keysMightBeDistributed; rCDesc.keysDistributed ← TRUE; };
$recording, $playback => {
FOR rqs: VoiceTemp.IntervalSpecs ← rCDesc.pendingIntervals, rqs.rest
WHILE rqs#
NIL
DO
IF actionReport.actionID#rqs.first.intID THEN LOOP;
betterActionRequest ← rqs.first;
SELECT actionReport.actionType
FROM
Throw away pending intervals that are complete. This includes intervals preceding the one being reported on.
$finished, $flushed => rCDesc.pendingIntervals ← rqs.rest; ENDCASE;
EXIT;
ENDLOOP;
IF betterActionRequest=NIL THEN ERROR; -- Complain?["missing reports"]
};
ENDCASE=> cDesc ← cDesc;
-- a place to stand during debugging
Endcase events are now expected, since there is no event-filtering in FinchSmarts; these will be events for other applications.
};
Client Functions
PlaybackTune:
PUBLIC ENTRY PROC [
convID: Thrush.ConversationID,
intervalSpec: VoiceTemp.IntervalSpec,
key: Thrush.EncryptionKey,
queueIt: BOOL←TRUE,
failOK: BOOL←FALSE -- playing is optional; leave connection open if tune doesn't exist.
] RETURNS [ started: BOOL←FALSE, newConvID: Thrush.ConversationID←Thrush.nullConvID ] = { -- FALSE if failed
ENABLE UNWIND=>NULL;
cDesc: ConvDesc;
rCDesc: FinchRecording.RConvDesc;
nb: NB;
keyIndex: [0..17B];
IF intervalSpec.tuneID<=VoiceTemp.nullTuneID
THEN {
FinchSmarts.ReportConversationState[$convNotActive, NIL, "No such tune"]; RETURN; }; -- UGH!
[nb,cDesc,rCDesc]←VoiceConnect[convID];
IF nb#$success THEN RETURN; -- Complain?
newConvID ← cDesc.situation.self.convID;
[nb, keyIndex] ← ThParty.RegisterKey[
shh: shhh, credentials: cDesc.situation.self, key: key, reportNewKeys: TRUE];
{
SELECT nb
FROM
$success => NULL;
$newKeys =>
{
A quandary. I hate these things that wait until something else has happened. Would rather queue up later tasks to do and return. But haven't come up with a general method yet. The problem here is that we don't want to schedule new voice until the encryption keys for it have been distributed. The Register key function will tell everybody about the keys and then tell us that it's told everybody. We wait one time around a timeout for a special condition variable (ugh) and then give up.
rCDesc.keysDistributed ← FALSE;
WAIT rCDesc.keysMightBeDistributed;
IF ~rCDesc.keysDistributed THEN GOTO Fail;
};
$stateMismatch => ???;
ENDCASE => GOTO Fail;
EXITS Fail => {
IF ~failOK
THEN
FinchSmarts.DisconnectCall[newConvID, $error, "Could not encode encryption key", $failed];
RETURN;
}; };
intervalSpec ← NEW[VoiceTemp.IntervalSpecBody ← intervalSpec^];
intervalSpec.keyIndex ← keyIndex;
intervalSpec.intID ← NewID[];
-- sets interface & serviceID
nb ← voiceTemp.Play[shhh, cDesc.situation.self, rCDesc.voiceTempID, intervalSpec, queueIt];
started ← nb=$success;
IF ~started THEN RETURN;
rCDesc.pendingIntervals ← NconcIntervals[rCDesc.pendingIntervals, LIST[intervalSpec]];
};
RecordTune:
PUBLIC
ENTRY
PROC [
convID: Thrush.ConversationID,
useIntervalSpec: VoiceTemp.IntervalSpec←NIL,
useKey: Thrush.EncryptionKey,
queueIt: BOOL←FALSE
]
RETURNS[
nb: NB,
intervalSpec: VoiceTemp.IntervalSpec←NIL,
key: Thrush.EncryptionKey← Thrush.nullKey,
newConvID: Thrush.ConversationID←Thrush.nullConvID
] = {
cDesc: ConvDesc;
rCDesc: FinchRecording.RConvDesc;
tuneID: VoiceTemp.TuneID;
[nb,cDesc, rCDesc]←VoiceConnect[convID];
IF nb#$success THEN RETURN; -- Complain?
newConvID ← cDesc.situation.self.convID;
intervalSpec ← NEW[VoiceTemp.IntervalSpecBody ← []];
IF useIntervalSpec#NIL THEN intervalSpec^ ← useIntervalSpec^;
intervalSpec.keyIndex ← 1;
intervalSpec.intID ← NewID[];
[nb, tuneID] ← voiceTemp.Record[shhh, cDesc.situation.self, rCDesc.voiceTempID, intervalSpec, queueIt];
IF nb#$success THEN RETURN;
intervalSpec.tuneID ← tuneID;
rCDesc.pendingIntervals ← NconcIntervals[rCDesc.pendingIntervals, LIST[intervalSpec]];
WHILE cDesc.situation.self.state#idle
AND
GList.Member[intervalSpec,rCDesc.pendingIntervals] DO
WAIT rCDesc.stateChange;
ENDLOOP;
IF rCDesc.keyTable=
NIL
THEN
[nb, rCDesc.keyTable] ← ThParty.GetKeyTable[shhh, cDesc.situation.self];
IF nb=$success THEN RETURN[nb, intervalSpec, rCDesc.keyTable[1], newConvID];
};
StopTune:
PUBLIC
ENTRY
PROC [convID: Thrush.ConversationID] = {
ENABLE UNWIND=>NULL;
cDesc: ConvDesc;
rCDesc: FinchRecording.RConvDesc;
IF ([,cDesc, rCDesc]←VoiceConnect[convID]).nb#$success OR cDesc.situation.self.state#$active THEN RETURN;
[] ← voiceTemp.Stop[shhh, cDesc.situation.self, rCDesc.voiceTempID];
};
DescribeInterval:
PUBLIC
ENTRY
PROC [intervalSpec: VoiceTemp.IntervalSpec, minSilence: VoiceTemp.VoiceTime𡤁]
RETURNS[nb: NB, length: INT, intervals: VoiceTemp.IntervalSpecs] = {
ENABLE UNWIND=>NULL;
IF (nb←VoiceTempInterface[NIL,NIL]) # $success THEN RETURN;
[nb, length, intervals] ← voiceTemp.DescribeInterval[shhh: shhh, targetInterval: intervalSpec, minSilence: minSilence, credentials: [], serviceID: nullID];
};
Utilities
VoiceConnect:
INTERNAL
PROC[convID: Thrush.ConversationID]
RETURNS [nb: NB, cDesc: ConvDesc, rCDesc: FinchRecording.RConvDesc] = {
[nb, cDesc] ← FinchSmarts.VoiceConnect["recording", convID];
IF nb#$success THEN RETURN;
rCDesc ← GetRCDesc[cDesc];
nb ← VoiceTempInterface[cDesc, rCDesc];
};
GetRCDesc:
INTERNAL
PROC[
cDesc: ConvDesc ] RETURNS [rCDesc: FinchRecording.RConvDesc] = {
IF cDesc=NIL THEN RETURN[NIL];
rCDesc ← NARROW[Atom.GetPropFromList[cDesc.props, $RConvDesc]];
IF rCDesc#NIL THEN RETURN;
rCDesc ← NEW[FinchRecording.RConvDescBody ← []];
cDesc.props ← Atom.PutPropOnList[cDesc.props, $RConvDesc, rCDesc];
};
VoiceTempInterface:
PROC[cDesc: ConvDesc, rCDesc: FinchRecording.RConvDesc]
RETURNS [nb: NB←$success] = {
interfaceSpec: Thrush.InterfaceSpec;
IF rCDesc#NIL THEN { IF rCDesc.voiceTempID#nullID THEN RETURN }
ELSE IF voiceTemp#NIL THEN RETURN; -- want interface only
[nb, shhh, interfaceSpec] ← FinchSmarts.LookupServiceInterface["recording", "VoiceTemp", cDesc];
IF nb#$success THEN RETURN;
voiceTemp ← VoiceTempRpcControl.ImportNewInterface[
interfaceName: interfaceSpec.interfaceName,
hostHint: interfaceSpec.hostHint!RPC.ImportFailed => CONTINUE
];
IF voiceTemp=NIL THEN RETURN[$noVoiceTempInterface];
IF rCDesc#NIL THEN rCDesc.voiceTempID ← interfaceSpec.serviceID;
};
NewID: PROC RETURNS[LONG CARDINAL] ={RETURN[LOOPHOLE[PupSocket.GetUniqueID[]]]};
}.