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 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 = { 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 = { 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 => { rCDesc.keysDistributed _ FALSE; WAIT rCDesc.keysMightBeDistributed; IF ~rCDesc.keysDistributed THEN GOTO Fail; }; 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]; }; }.  FinchRecordingImpl.mesa Copyright c 1986 by Xerox Corporation. All rights reserved. Last Edited by: Swinehart, September 27, 1986 5:45:16 pm PDT Confusion about locks here and in FinchSmartsImpl; careful! Declarations Supervision [nb: NB, cDesc: ConvDesc, remark: ROPE] [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. Throw away pending intervals that are complete. This includes intervals preceding the one being reported on. Endcase events are now expected, since there is no event-filtering in FinchSmarts; these will be events for other applications. Client Functions 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.  $stateMismatch => ???; Utilities Initialization Ask Finch to inform us of changes in conversation state, and action reports. Swinehart, September 25, 1986 11:33:39 am PDT Extracted from FinchSmartsImpl, to handle workstation-controlled voice recording and playback. To be replaced soon by VoiceRopes. ΚY˜Jšœ™šœ Οmœ1™