<> <> <> <> <> <<>> <> <<>> DIRECTORY FinchSmarts USING [ConvDesc, ObtainServiceInterface, ReportSystemStateProc, ReportConversationStateProc, ReportRequestStateProc, RegisterForReports, ServiceConnect], Atom USING [GetPName, GetPropFromList, PutPropOnList], Process USING [SecondsToTicks, SetTimeout, Ticks], PupSocket USING [GetUniqueID], RefID USING [ID, nullID], Rope USING [ROPE, Concat], RPC USING [CallFailed, ImportFailed], Thrush USING [ActionType, ConversationID, InterfaceSpec, none, nullConvID, SHHH], VoiceRopeServer, VoiceRopeServerRpcControl USING [ImportInterface], VoiceUtils USING [Problem], VoiceRope; VoiceRopeImpl: CEDAR MONITOR IMPORTS Atom, FinchSmarts, Process, PupSocket, Rope, RPC, VoiceRopeServer, VoiceRopeServerRpcControl, VoiceUtils EXPORTS VoiceRope ~ BEGIN ROPE: TYPE ~ Rope.ROPE; recordingService: ROPE = "recording"; recordingServiceInterface: ROPE = "VoiceRopeServer"; RequestID: TYPE = VoiceRope.RequestID; PendingRequest: TYPE = REF PendingRequestBody; PendingRequestBody: TYPE = RECORD [ id: RequestID, state: Thrush.ActionType _ $unknown ]; PendingRequests: TYPE = LIST OF PendingRequest; VoiceRopeInfo: TYPE = REF VoiceRopeInfoBody; VoiceRopeInfoBody: TYPE = RECORD [ imported: BOOLEAN _ FALSE, 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 ]; 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 needed! }; <<>> 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] RETURNS [nb: VoiceRope.NB, voiceRope: VoiceRope.VoiceRope_NIL, newConvID: Thrush.ConversationID_ Thrush.nullConvID] ~ { <> ENABLE RPC.CallFailed => { IF ~info.imported THEN CONTINUE; info.imported_FALSE; RETRY; }; vrDesc: VoiceRopeDesc _ GetConversation[convID, clientData]; <> IF vrDesc = NIL THEN RETURN [$noConv, NIL]; newConvID _ vrDesc.cDesc.situation.self.convID; requestID _ AddRequest[vrDesc, requestID]; [nb, voiceRope] _ VoiceRopeServer.Record[vrDesc.shhh, vrDesc.cDesc.situation.self, vrDesc.serviceID, requestID, TRUE]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to record failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; <> WaitForRequestState[vrDesc: vrDesc, state: $finished, id: requestID]; [nb, voiceRope.length] _ VoiceRopeServer.Length[vrDesc.shhh, voiceRope ! RPC.CallFailed => {nb _ $callFailed; CONTINUE}]; 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.>> ENABLE RPC.CallFailed => { IF ~info.imported THEN CONTINUE; info.imported_FALSE; RETRY; }; vrDesc: VoiceRopeDesc _ GetConversation[convID, clientData]; IF vrDesc = NIL THEN RETURN [$noConv]; newConvID _ vrDesc.cDesc.situation.self.convID; requestID _ AddRequest[vrDesc, requestID]; 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] ~ { <> ENABLE RPC.CallFailed => { IF ~info.imported THEN CONTINUE; info.imported_FALSE; RETRY; }; vrDesc: VoiceRopeDesc _ GetConversation[convID, NIL, FALSE]; IF vrDesc=NIL THEN RETURN [$noConv]; 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; }; <> }; <<>> Retain: PUBLIC PROC [handle: Handle _ NIL, vr: VoiceRope.VoiceRope, class: VoiceRope.InterestClass, refID: ROPE, other: ROPE _ NIL] ~ { <> ENABLE RPC.CallFailed => { IF ~info.imported THEN CONTINUE; info.imported_FALSE; RETRY; }; nb: VoiceRope.NB; IF NOT RecordingServiceInterface[] THEN RETURN; nb _ VoiceRopeServer.Retain[info.shhh, 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] ~ { <> ENABLE RPC.CallFailed => { IF ~info.imported THEN CONTINUE; info.imported_FALSE; RETRY; }; 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] ~ { <> ENABLE RPC.CallFailed => { IF ~info.imported THEN CONTINUE; info.imported_FALSE; RETRY; }; 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] ~ { <> ENABLE RPC.CallFailed => { IF ~info.imported THEN CONTINUE; info.imported_FALSE; RETRY; }; nb: VoiceRope.NB; IF NOT RecordingServiceInterface[] THEN RETURN; [nb, new] _ VoiceRopeServer.Cat[info.shhh, 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] ~ { <> ENABLE RPC.CallFailed => { IF ~info.imported THEN CONTINUE; info.imported_FALSE; RETRY; }; nb: VoiceRope.NB; IF NOT RecordingServiceInterface[] THEN RETURN; [nb, new] _ VoiceRopeServer.Substr[info.shhh, 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] ~ { <> ENABLE RPC.CallFailed => { IF ~info.imported THEN CONTINUE; info.imported_FALSE; RETRY; }; nb: VoiceRope.NB; IF NOT RecordingServiceInterface[] THEN RETURN; [nb, new] _ VoiceRopeServer.Replace[info.shhh, 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] ~ { <> ENABLE RPC.CallFailed => { IF ~info.imported THEN CONTINUE; info.imported_FALSE; RETRY; }; nb: VoiceRope.NB; IF NOT RecordingServiceInterface[] THEN RETURN; [nb, len] _ VoiceRopeServer.Length[info.shhh, vr ! RPC.CallFailed => {nb _ $callFailed; CONTINUE}]; 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] ~ { ENABLE RPC.CallFailed => { IF ~info.imported THEN CONTINUE; info.imported_FALSE; RETRY; }; 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]; IF vrDesc = NIL THEN RETURN; SELECT cDesc.situation.self.state FROM $active => NULL; $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]; IF vrDesc = NIL THEN RETURN; 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[NIL]; -- leave actionRequest alone }; <> 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] RETURNS [requestID: RequestID] ~ { request: PendingRequest _ NEW[PendingRequestBody _ [requestID _ IF id=VoiceRope.nullRequestID THEN NewRequestID[] ELSE id]]; 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] RETURNS [vrDesc: VoiceRopeDesc _ NIL] ~ { nb: VoiceRope.NB; cDesc: FinchSmarts.ConvDesc; [nb, cDesc] _ FinchSmarts.ServiceConnect[recordingService, convID, createOK]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Can't establish conversation - ",Atom.GetPName[nb]], $VoiceRope]; RETURN[NIL]; }; 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 cDesc _ 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 info.imported AND vrDesc#NIL AND vrDesc.serviceID#RefID.nullID THEN RETURN[TRUE]; [nb, info.shhh, interfaceSpec] _ FinchSmarts.ObtainServiceInterface[recordingService, recordingServiceInterface, cDesc]; IF nb#$success THEN RETURN[FALSE]; info.serviceID _ interfaceSpec.serviceID; IF vrDesc#NIL THEN { vrDesc.serviceID _ info.serviceID; vrDesc.shhh _ info.shhh; }; IF info.imported THEN RETURN[TRUE]; info.imported _ TRUE; VoiceRopeServerRpcControl.ImportInterface[interfaceName: interfaceSpec.interfaceName, hostHint: interfaceSpec.hostHint ! RPC.ImportFailed => { info.imported _ FALSE; VoiceUtils.Problem["Can't import recording service.", $VoiceRope]; CONTINUE}]; RETURN[info.imported]; }; NewRequestID: PUBLIC PROC RETURNS [VoiceRope.RequestID] = { RETURN[LOOPHOLE[PupSocket.GetUniqueID[]]]}; <> FinchSmarts.RegisterForReports[c: ConversationReport, r: RequestReport]; END. <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <NewRequestID>> <> <> <> <> <<>> <> <> <> <> <> <<>> <<>>