<> <> <> 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 = { <> 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; shhh: SHHH; <> <<>> 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]>> <> <> 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 <> $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 <> }; <> <<>> 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_1] 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]; }; <> 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[]]]}; <> InitializeFinchRecording: PUBLIC ENTRY PROC = { FinchSmarts.RegisterForReports[s: NIL, c: ConvStateReport, r: ReportReport]; <> }; }. <> <> <<>> <<>>