<> <> <> <> <> <> <> <<>> DIRECTORY FinchSmarts USING [ConvDesc, CurrentRName, ObtainServiceInterface, ReportSystemStateProc, ReportConversationStateProc, ReportRequestStateProc, RegisterForReports, ServiceConnect], Atom USING [GetPName, GetPropFromList, PutPropOnList], IO, Process USING [SecondsToTicks, SetTimeout, Ticks], RefID USING [ID, nullID], Rope USING [ROPE, Concat], ThParty USING [ PartyInfo ], Thrush USING [ActionClass, ActionType, ConversationID, InterfaceSpec, none, nullConvID, nullID, PartyID, SHHH], VoiceRopeServer, VoiceRopeServerSunImport USING [ImportInterface, ReportProblem], VoiceUtils USING [FindWhere, Problem, ProblemFR, RegisterWhereToReport, WhereProc ], VoiceUtilsExtras USING [GetUniqueID], VoiceRope; VoiceRopeImpl: CEDAR MONITOR IMPORTS Atom, FinchSmarts, IO, Process, Rope, VoiceRopeServer, VoiceRopeServerSunImport, VoiceUtils, VoiceUtilsExtras EXPORTS VoiceRope ~ BEGIN OPEN IO; ROPE: TYPE ~ Rope.ROPE; recordingService: ROPE _ "recording"; recordingServiceInterface: ROPE _ "VoiceRopeServer"; RequestID: TYPE = VoiceRope.RequestID; PendingRequest: TYPE = REF PendingRequestBody; PendingRequestBody: TYPE = RECORD [ id: RequestID, class: Thrush.ActionClass _ $unknown, state: Thrush.ActionType _ $unknown ]; PendingRequests: TYPE = LIST OF PendingRequest; VoiceRopeInfo: TYPE = REF VoiceRopeInfoBody; VoiceRopeInfoBody: TYPE = RECORD [ serviceID: RefID.ID _ RefID.nullID, shhh: Thrush.SHHH _ Thrush.none ]; VoiceRopeDesc: TYPE = REF VoiceRopeDescBody; VoiceRopeDescBody: TYPE = RECORD [ cDesc: FinchSmarts.ConvDesc _ NIL, -- only valid during operations below. serviceID: RefID.ID _ RefID.nullID, shhh: Thrush.SHHH _ Thrush.none, pendingRequests: PendingRequests _ NIL, reportArrived: CONDITION, clientData: REF_NIL, paused: BOOL _ FALSE ]; info: VoiceRopeInfo _ NEW[VoiceRopeInfoBody]; <<>> <<Probably needs more reporting when RPC calls fail and reconnects fail, too.>> <> Handle: TYPE = VoiceRope.Handle; -- handles are no longer used for anything Open: PUBLIC PROC [voiceRopeDBName: ROPE _ NIL, localName: ROPE _ NIL, Complain: PROC[complaint: ROPE] _ NIL] RETURNS [handle: Handle] ~ { <> RETURN[NIL]; -- this routine is no longer used, but it probably should be! }; <<>> Record: PUBLIC PROC[handle: Handle_NIL] RETURNS [voiceRope: VoiceRope.VoiceRope_NIL] ~ { [voiceRope: voiceRope] _ RecordNB[handle: handle]; }; Play: PUBLIC PROC[handle: Handle_NIL, voiceRope: VoiceRope.VoiceRope, queueIt: BOOL_TRUE, wait: BOOL_FALSE] ~ { [] _ PlayNB[handle: handle, voiceRope: voiceRope, queueIt: queueIt, wait: wait]; }; Stop: PUBLIC PROC[handle: Handle_NIL] ~ { [] _ StopNB[handle: handle]; }; RecordNB: PUBLIC PROC [handle: Handle_NIL, requestID: RequestID_VoiceRope.nullRequestID, convID: Thrush.ConversationID_ Thrush.nullConvID, clientData: REF_NIL, selfElseOther: BOOL_TRUE] RETURNS [nb: VoiceRope.NB, voiceRope: VoiceRope.VoiceRope_NIL, newConvID: Thrush.ConversationID_ Thrush.nullConvID] ~ { <> vrDesc: VoiceRopeDesc; recordedPartyID: Thrush.PartyID _ Thrush.nullID; pInfo: ThParty.PartyInfo; [nb, vrDesc] _ GetConversation[convID: convID, clientData: clientData, addOK: TRUE]; <> <> IF nb # $success THEN RETURN; newConvID _ vrDesc.cDesc.situation.self.convID; requestID _ AddRequest[vrDesc, requestID, $record]; pInfo _ vrDesc.cDesc.partyInfo; IF pInfo#NIL THEN { recordedPartyID _ pInfo[pInfo.ixSelf].partyID; IF ~selfElseOther THEN FOR ix: NAT IN [1..pInfo.numParties] DO IF ix=pInfo.ixSelf OR pInfo[ix].type=$service THEN LOOP; recordedPartyID _ pInfo[ix].partyID; EXIT; ENDLOOP; }; [nb, voiceRope] _ VoiceRopeServer.Record[shhh: vrDesc.shhh, credentials: vrDesc.cDesc.situation.self, recordedParty: recordedPartyID, serviceID: vrDesc.serviceID, intID: requestID, queueIt: TRUE]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to record failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; <<Perhaps this should result in the termination of the conversation? It doesn't, automatically, now. This comment should be read implicitly after all other nb tests.>> }; <> WaitForRequestState[vrDesc: vrDesc, state: $finished, id: requestID]; [nb, voiceRope.length] _ VoiceRopeServer.Length[vrDesc.shhh, voiceRope]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to get voicerope length failed - ",Atom.GetPName[nb]], $VoiceRope]; nb _ $noLength; RETURN; }; <> }; <<>> PlayNB: PUBLIC PROC [handle: Handle_NIL, voiceRope: VoiceRope.VoiceRope, queueIt: BOOL_TRUE, wait: BOOL_FALSE, requestID: RequestID_VoiceRope.nullRequestID, convID: Thrush.ConversationID_Thrush.nullConvID, clientData: REF_NIL] RETURNS [nb: VoiceRope.NB, newConvID: Thrush.ConversationID _ Thrush.nullConvID] ~ { <> < play after all other record/playback requests are satisfied.>> < wait until things appear to be started properly, or have failed.>> vrDesc: VoiceRopeDesc; [nb, vrDesc] _ GetConversation[convID, clientData]; IF nb # $success THEN RETURN; newConvID _ vrDesc.cDesc.situation.self.convID; requestID _ AddRequest[vrDesc, requestID, $playback]; nb _ VoiceRopeServer.Play[ vrDesc.shhh, voiceRope, vrDesc.cDesc.situation.self, vrDesc.serviceID, requestID, queueIt]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to play voicerope failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; IF wait THEN WaitForRequestState[vrDesc: vrDesc, state: $started, id: requestID]; <> }; <<>> StopNB: PUBLIC PROC [handle: Handle _ NIL, convID: Thrush.ConversationID_ Thrush.nullConvID] RETURNS [nb: VoiceRope.NB] ~ { <> vrDesc: VoiceRopeDesc; [nb, vrDesc] _ GetConversation[convID, NIL, FALSE]; IF nb#$success THEN RETURN; nb _ VoiceRopeServer.Stop[vrDesc.shhh, vrDesc.cDesc.situation.self, vrDesc.serviceID]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to stop failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; <> }; <<>> PauseNB: PUBLIC PROC [handle: Handle _ NIL, convID: Thrush.ConversationID_ Thrush.nullConvID] RETURNS [nb: VoiceRope.NB] ~ { <> vrDesc: VoiceRopeDesc; [nb, vrDesc] _ GetConversation[convID, NIL, FALSE]; IF nb#$success THEN RETURN; nb _ VoiceRopeServer.Pause[vrDesc.shhh, vrDesc.cDesc.situation.self, vrDesc.serviceID]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to pause failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; vrDesc.paused _ TRUE; }; <<>> ResumeNB: PUBLIC PROC [handle: Handle _ NIL, convID: Thrush.ConversationID_ Thrush.nullConvID] RETURNS [nb: VoiceRope.NB] ~ { <> vrDesc: VoiceRopeDesc; [nb, vrDesc] _ GetConversation[convID, NIL, FALSE]; IF nb#$success THEN RETURN; nb _ VoiceRopeServer.Resume[vrDesc.shhh, vrDesc.cDesc.situation.self, vrDesc.serviceID]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to resume failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; vrDesc.paused _ FALSE; }; <<>> Retain: PUBLIC PROC [handle: Handle _ NIL, vr: VoiceRope.VoiceRope, class: VoiceRope.InterestClass, refID: ROPE, other: ROPE _ NIL] ~ { <> nb: VoiceRope.NB; IF NOT RecordingServiceInterface[] THEN RETURN; nb _ VoiceRopeServer.Retain[info.shhh, FinchSmarts.CurrentRName[], vr, class, refID, other]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to retain voicerope failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; }; <<>> Forget: PUBLIC PROC [handle: Handle _ NIL, vr: VoiceRope.VoiceRope, class: VoiceRope.InterestClass, refID: ROPE] ~ { <> nb: VoiceRope.NB; IF NOT RecordingServiceInterface[] THEN RETURN; nb _ VoiceRopeServer.Forget[info.shhh, vr, class, refID]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to forget voicerope failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; }; <<>> GetByInterest: PUBLIC PROC [handle: Handle _ NIL, class: VoiceRope.InterestClass, refID: ROPE] RETURNS [voiceRope: VoiceRope.VoiceRope] ~ { <> nb: VoiceRope.NB; IF NOT RecordingServiceInterface[] THEN RETURN; [nb, voiceRope] _ VoiceRopeServer.GetByInterest[info.shhh, class, refID]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to lookup voicerope by interest failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; }; <<>> Cat: PUBLIC PROC [handle: Handle _ NIL, vr1, vr2, vr3, vr4, vr5: VoiceRope.VoiceRope _ NIL] RETURNS [new: VoiceRope.VoiceRope_NIL] ~ { <> nb: VoiceRope.NB; IF NOT RecordingServiceInterface[] THEN RETURN; [nb, new] _ VoiceRopeServer.Cat[info.shhh, FinchSmarts.CurrentRName[], vr1, vr2, vr3, vr4, vr5]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to cat voiceropes failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; }; <<>> Substr: PUBLIC PROC [handle: Handle _ NIL, vr: VoiceRope.VoiceRope, start: INT _ 0, len: INT _ LAST[INT]] RETURNS [new: VoiceRope.VoiceRope_NIL] ~ { <> nb: VoiceRope.NB; IF NOT RecordingServiceInterface[] THEN RETURN; [nb, new] _ VoiceRopeServer.Substr[info.shhh, FinchSmarts.CurrentRName[], vr, start, len]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to substring voicerope failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; }; <<>> Replace: PUBLIC PROC [handle: Handle _ NIL, vr: VoiceRope.VoiceRope, start: INT _ 0, len: INT _ LAST[INT], with: VoiceRope.VoiceRope _ NIL] RETURNS [new: VoiceRope.VoiceRope_NIL] ~ { <> nb: VoiceRope.NB; IF NOT RecordingServiceInterface[] THEN RETURN; [nb, new] _ VoiceRopeServer.Replace[info.shhh, FinchSmarts.CurrentRName[], vr, start, len, with]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to replace part of a voicerope failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; }; <<>> Length: PUBLIC PROC [handle: Handle _ NIL, vr: VoiceRope.VoiceRope] RETURNS [len: INT_1] ~ { <> nb: VoiceRope.NB; IF NOT RecordingServiceInterface[] THEN RETURN; [nb, len] _ VoiceRopeServer.Length[info.shhh, vr]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to get voicerope length failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; }; <<>> DescribeRope: PUBLIC PROC [ handle: Handle _ NIL, vr: VoiceRope.VoiceRope, minSilence: INT _ -1] RETURNS [noise: VoiceRope.IntervalSpecs_NIL] ~ { nb: VoiceRope.NB; len: INT; IF NOT RecordingServiceInterface[] THEN RETURN; [nb, len, noise] _ VoiceRopeServer.DescribeRope[info.shhh, vr, minSilence]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to describe voicerope failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; }; <<>> <> <> <<>> <> SystemReport: FinchSmarts.ReportSystemStateProc ~ { <> NULL; }; ConversationReport: FinchSmarts.ReportConversationStateProc ~ { <<[ nb: NB, cDesc: ConvDesc, remark: ROPE_NIL ]>> vrDesc: VoiceRopeDesc _ GetVRDesc[cDesc]; playbacksPending: BOOL _ FALSE; IF vrDesc = NIL THEN RETURN; IF vrDesc.pendingRequests#NIL THEN FOR pR: PendingRequests _ vrDesc.pendingRequests, pR.rest WHILE pR#NIL DO IF pR.first.class = $record THEN EXIT; REPEAT FINISHED => playbacksPending _ TRUE; ENDLOOP; SELECT cDesc.situation.self.state FROM $active => IF playbacksPending THEN [] _ ResumeNB[convID: cDesc.situation.self.convID]; $inactive => IF playbacksPending THEN [] _ PauseNB[convID: cDesc.situation.self.convID]; $idle, $neverWas, $failed => { -- throw away pending requests RemoveRequests[vrDesc, NIL]; NotifyWaiters[vrDesc]; }; ENDCASE; }; RequestReport: FinchSmarts.ReportRequestStateProc ~ { <<[ cDesc: ConvDesc, actionReport: Thrush.ActionReport, actionRequest: REF ] RETURNS [betterActionRequest: REF]>> request: PendingRequest; vrDesc: VoiceRopeDesc _ GetVRDesc[cDesc]; betterActionRequest _ actionRequest; IF vrDesc = NIL THEN RETURN; -- leave actionRequest alone SELECT actionReport.actionClass FROM $recording, $playback => { request _ GetRequestByID[vrDesc, actionReport.actionID]; IF request = NIL THEN RETURN[NIL]; request.state _ actionReport.actionType; SELECT request.state FROM $scheduled, $started => NULL; <> $finished, $flushed => RemoveRequests[vrDesc, request]; ENDCASE; NotifyWaiters[vrDesc]; }; ENDCASE; RETURN; -- leave actionRequest alone }; RelayProblems: VoiceRopeServerSunImport.ReportProblem ~ { VoiceUtils.ProblemFR["%g: %g", $VoiceRope, NIL, atom[ec], rope[expl]]; }; <> GetRequestByID: ENTRY PROC [vrDesc: VoiceRopeDesc, id: RequestID] RETURNS [request: PendingRequest _ NIL] ~ { IF vrDesc=NIL THEN RETURN; FOR q: PendingRequests _ vrDesc.pendingRequests, q.rest WHILE q # NIL DO IF q.first.id = id THEN RETURN[q.first]; ENDLOOP; }; AddRequest: ENTRY PROC [vrDesc: VoiceRopeDesc, id: RequestID, class: Thrush.ActionClass _ $unknown] RETURNS [requestID: RequestID] ~ { request: PendingRequest _ NEW[PendingRequestBody _ [requestID _ IF id=VoiceRope.nullRequestID THEN NewRequestID[] ELSE id, class]]; vrDesc.pendingRequests _ CONS[request, vrDesc.pendingRequests]; }; RemoveRequests: ENTRY PROC [vrDesc: VoiceRopeDesc, request: PendingRequest] RETURNS [] ~ { IF vrDesc=NIL THEN RETURN; IF request = NIL OR vrDesc.pendingRequests = NIL OR vrDesc.pendingRequests.first = request THEN vrDesc.pendingRequests _ NIL ELSE FOR q: PendingRequests _ vrDesc.pendingRequests, q.rest WHILE q.rest # NIL DO IF q.rest.first = request THEN { q.rest _ NIL; -- truncate queue to remove this and all previous requests EXIT; }; ENDLOOP; }; NotifyWaiters: ENTRY PROC [vrDesc: VoiceRopeDesc] RETURNS [] ~ { ENABLE UNWIND => NULL; IF vrDesc=NIL THEN RETURN; BROADCAST vrDesc.reportArrived; }; Wait: ENTRY PROC [vrDesc: VoiceRopeDesc] RETURNS [] ~ { ENABLE UNWIND => NULL; IF vrDesc=NIL THEN RETURN; WAIT vrDesc.reportArrived; }; WaitForRequestState: PROC [vrDesc: VoiceRopeDesc, state: Thrush.ActionType, id: RequestID] RETURNS [] ~ { request: PendingRequest; timeout: Process.Ticks _ IF state = $started THEN Process.SecondsToTicks[2] ELSE Process.SecondsToTicks[10]; IF vrDesc=NIL THEN RETURN; TRUSTED { Process.SetTimeout[@vrDesc.reportArrived, timeout]; }; DO request _ GetRequestByID[vrDesc, id]; IF request = NIL THEN EXIT; -- completed or conv went idle SELECT state FROM $scheduled => SELECT request.state FROM $scheduled, $started, $finished, $flushed => EXIT; ENDCASE => NULL; -- unknown? $started => SELECT request.state FROM $started, $finished, $flushed => EXIT; ENDCASE => NULL; -- probably $scheduled $finished => SELECT request.state FROM $finished, $flushed => EXIT; ENDCASE => NULL; -- probably $scheduled or $started ENDCASE => EXIT; Wait[vrDesc]; ENDLOOP; }; GetClientData: PUBLIC PROC[handle: Handle_NIL, cDesc: FinchSmarts.ConvDesc] RETURNS[clientData: REF_NIL] = { vrDesc: VoiceRopeDesc _ GetVRDesc[cDesc]; IF vrDesc#NIL THEN { -- vrDesc.cDesc_NIL; -- RETURN[vrDesc.clientData]; }; }; <> GetVRDesc: PROC[cDesc: FinchSmarts.ConvDesc] RETURNS[vrDesc: VoiceRopeDesc_NIL] = { IF cDesc#NIL THEN vrDesc _ NARROW[Atom.GetPropFromList[cDesc.props, $VoiceRopeDesc]]; IF vrDesc#NIL THEN vrDesc.cDesc _ cDesc; }; GetConversation: PROC [ convID: Thrush.ConversationID, clientData: REF_NIL, createOK: BOOL_TRUE, addOK: BOOL_FALSE] RETURNS [nb: VoiceRope.NB_ $success, vrDesc: VoiceRopeDesc _ NIL] ~ { cDesc: FinchSmarts.ConvDesc; [nb, cDesc] _ FinchSmarts.ServiceConnect[recordingService, convID, createOK, addOK]; IF nb#$success THEN { VoiceUtils.Problem[ Rope.Concat["Can't establish conversation - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; vrDesc _ GetVRDesc[cDesc]; IF vrDesc=NIL THEN { vrDesc _ NEW[VoiceRopeDescBody _ []]; cDesc.props _ Atom.PutPropOnList[cDesc.props, $VoiceRopeDesc, vrDesc]; vrDesc.cDesc _ cDesc; }; IF clientData#NIL THEN vrDesc.clientData _ clientData; IF ~RecordingServiceInterface[vrDesc] THEN RETURN[$noServiceInterface, NIL]; }; RecordingServiceInterface: PROC[vrDesc: VoiceRopeDesc _ NIL] RETURNS [imported: BOOLEAN _ TRUE] = { nb: VoiceRope.NB; interfaceSpec: Thrush.InterfaceSpec; cDesc: FinchSmarts.ConvDesc _ IF vrDesc=NIL THEN NIL ELSE vrDesc.cDesc; IF vrDesc#NIL AND vrDesc.serviceID#RefID.nullID THEN RETURN[TRUE]; [nb, interfaceSpec] _ FinchSmarts.ObtainServiceInterface[recordingService, recordingServiceInterface, cDesc]; IF nb#$success THEN RETURN[FALSE]; IF vrDesc=NIL AND interfaceSpec.serviceID=info.serviceID THEN RETURN[TRUE]; info.shhh _ VoiceRopeServerSunImport.ImportInterface[instance: interfaceSpec.interfaceName.instance, reportProblem: RelayProblems, reportData: info]; info.serviceID _ interfaceSpec.serviceID; IF vrDesc#NIL THEN { vrDesc.serviceID _ info.serviceID; vrDesc.shhh _ info.shhh; }; RETURN[TRUE]; }; NewRequestID: PUBLIC PROC RETURNS [VoiceRope.RequestID] = { RETURN[LOOPHOLE[VoiceUtilsExtras.GetUniqueID[]]]}; VoiceRopeWhere: VoiceUtils.WhereProc = { RETURN[VoiceUtils.FindWhere[$Finch, whereData]]; }; <> FinchSmarts.RegisterForReports[key: $VoiceRope, c: ConversationReport, r: RequestReport]; VoiceUtils.RegisterWhereToReport[proc: VoiceRopeWhere, where: $VoiceRope]; END. <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <NewRequestID>> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <>