<> <> <> <> <<>> DIRECTORY Atom USING [ DottedPairNode, GetPName ], Commander USING [ CommandProc, Register ], Convert USING [ CardFromRope ], IO, LarkPlay USING [ MergeToneSpecs, PlayString, ToneSpec, ToneSpecRec ], LarkSmartsMonitorImpl, MBQueue USING [ QueueClientAction ], NamesGV USING [ GVGetAttribute ], Nice, RefID USING [ ID, Reseal, Unseal ], RefTab USING [ Create, Fetch, Ref, Store ], Rope USING [ Concat, ROPE ], ThNet USING [ pd ], ThParty USING [ Advance, ConversationInfo, DescribeParty, GetConversationInfo, GetKeyTable, GetPartyInfo, PartyInfo ], ThPartyPrivate USING [ GetCurrentParty ], Thrush USING [ ActionReport, ConvEvent, ConversationID, PartyID, PartyType, NB, notReallyInConv, nullConvID, nullID, Reason, ROPE, SHHH, SmartsID, StateID, StateInConv ], ThSmartsPrivate USING [ ConvDesc, ConvDescBody, ConvRequest, ConvRequestBody, EnterLarkState, LarkState, OpenConversations, ParseState, SmartsInfo ], ThSmartsRpcControl, Triples USING [ Select ], TU, VoiceUtils USING [ MakeAtom, ProblemFR, Report, ReportFR ] ; LarkSmartsSupImpl: CEDAR MONITOR LOCKS root IMPORTS Atom, Commander, Convert, IO, LarkPlay, root: LarkSmartsMonitorImpl, MBQueue, NamesGV, Nice, RefID, RefTab, Rope, ThNet, ThParty, ThPartyPrivate, ThSmartsPrivate, Triples, TU, VoiceUtils EXPORTS ThSmartsPrivate SHARES LarkSmartsMonitorImpl = { OPEN IO; <> ConversationID: TYPE = Thrush.ConversationID; nullConvID: ConversationID=Thrush.nullConvID; ConvDesc: TYPE = ThSmartsPrivate.ConvDesc; ConvEvent: TYPE = Thrush.ConvEvent; Reseal: PROC[r: REF] RETURNS[RefID.ID] = INLINE {RETURN[RefID.Reseal[r]]; }; NB: TYPE = Thrush.NB; OpenConversations: TYPE = ThSmartsPrivate.OpenConversations; PartyID: TYPE = Thrush.PartyID; nullID: RefID.ID = Thrush.nullID; ROPE: TYPE = Thrush.ROPE; SmartsID: TYPE = Thrush.SmartsID; SmartsInfo: TYPE = ThSmartsPrivate.SmartsInfo; StateInConv: TYPE = Thrush.StateInConv; LORA: TYPE = LIST OF REF ANY; PD: TYPE = RECORD [ timeoutNoAction: INTEGER _ 5, doReports: BOOL_FALSE, larkRegistry: ROPE _ ".lark", doNice: BOOL_FALSE ]; pd: REF PD _ NEW[PD_[]]; <> <<>> LarkProgress: PUBLIC ENTRY PROC[ interface: ThSmartsRpcControl.InterfaceRecord, shh: Thrush.SHHH, convEvent: Thrush.ConvEvent ] = { ENABLE UNWIND => NULL; -- RestoreInvariant; <> <> <> <<(For now, when we don't know about that conversation and already in another)>> <> <<(New conversation and we're idle or a conversation we're in already)>> <> convID: ConversationID = convEvent.self.convID; info: SmartsInfo = GetSmartsInfo[smartsID: convEvent.self.smartsID]; cDesc: ConvDesc; whatNeedsDoing: WhatNeedsDoing; IF info=NIL THEN { Report["No info at Progress", NIL]; RETURN; }; cDesc _ GetConv[ info, convID, TRUE ]; IF convEvent.self.partyID = convEvent.other.partyID THEN { -- own state changed NoteNewState[cDesc, convEvent]; SELECT info.currentConvID FROM convID, nullConvID => NULL; <<(new conversation and we're idle) or (this is the one we like)>> ENDCASE => DoRejectCall[cDesc, convEvent]; <> <> RETURN; }; IF convID#info.currentConvID THEN { <> Report["Strange progress report", cDesc.info]; ForgetConv[cDesc]; RETURN; }; <> whatNeedsDoing _ whatNeedsDoingIf[cDesc.situation.self.state][convEvent.other.state]; SELECT whatNeedsDoing FROM $noop, $ntiy => NULL; -- No action is needed, or we don't know what one to take. $imp => ERROR; -- This is supposed to be impossible! $xrep => Report["Didn't expect state change report", info]; <> $frgt => ForgetConv[cDesc]; <> $invl => { <> Report["Invalid state transition", info]; []_ChangeState[cDesc: cDesc, state: $failed, reason: $error, comment: "System Error: Invalid state transition"]; }; $idle => { <> nb2: NB; reason: Thrush.Reason; cInfo: ThParty.ConversationInfo; [nb2, cInfo] _ ThParty.GetConversationInfo[convID: convEvent.self.convID]; reason _ IF nb2 # $success THEN $error ELSE convEvent.reason; IF nb2 # $success OR (cInfo.numParties-cInfo.numIdle) <= 1 THEN []_ChangeState[cDesc, IF reason=$terminating THEN $idle ELSE $failed, reason]; }; $rback => []_ChangeState[cDesc, $ringback]; -- Provide ring-back signal $actv => []_ChangeState[cDesc, $active]; -- Go active $reac => NULL; -- Will be used to reactivate when somebody else leaves inactive $deac => NULL; -- Will be used to deactivate when everybody else leaves active; ENDCASE => ERROR; }; LarkSubstitution: PUBLIC ENTRY PROC[ interface: ThSmartsRpcControl.InterfaceRecord, shh: Thrush.SHHH, convEvent: Thrush.ConvEvent, oldPartyID: Thrush.PartyID, newPartyID: Thrush.PartyID ] = { ENABLE UNWIND => NULL; -- RestoreInvariant; <> <> info: SmartsInfo = GetSmartsInfo[smartsID: convEvent.self.smartsID]; cDesc: ConvDesc; IF info=NIL THEN { Report["No info at Substitution", NIL]; RETURN; }; cDesc _ GetConv[ info, convEvent.self.convID, FALSE ]; IF cDesc=NIL THEN RETURN; NoteNewState[cDesc, convEvent]; }; LarkReportAction: PUBLIC ENTRY PROC[ interface: ThSmartsRpcControl.InterfaceRecord, shh: Thrush.SHHH, report: Thrush.ActionReport ] = { shh _ shh; shh _ shh; }; -- a Place to stand. <smarts report>> <> <<The only ones left are those that avoid recursive calls on ThParty.Advance and on NoteNewState. Even those could probably be eliminated with a bit more thought. Like maybe Loop with the resulting ConvEvent. >> <<>> QdNotification: ENTRY PROC [r: REF] ~ { <> ENABLE UNWIND => NULL; -- RestoreInvariant; cDesc: ConvDesc = NARROW[r]; { OPEN now: cDesc.situation.self; <> IF now.state # $notified THEN RETURN; -- Nothing to do any more?? []_ChangeState[cDesc, IF VoiceUtils.MakeAtom[DBInfo[now.partyID, $autoanswer].value] = $true THEN $active ELSE $ringing]; }; }; <> <> DoAdvance: INTERNAL PROC[convRequest: ThSmartsPrivate.ConvRequest] RETURNS [nb: NB] = { cDesc: ConvDesc = convRequest.cDesc; DO -- for possible stateMismatch loop OPEN now: cDesc.situation.self, desired: convRequest.desiredSituation.self; convEvent: Thrush.ConvEvent; IF now.state=$idle OR now.state = desired.state THEN RETURN; <<More comfortable if this were a complete check for reasonable transitions. For now, if the initial request wasn't a transition to $idle and we got a state mismatch, we've given up, assuming that what our other smarts did was as reasonable as what we wanted to do. Later queued actions, and later actions in LarkSmartsImpl (synchronous with user actions) will have to detect any anomalies and deal with them.  >> [nb, convEvent] _ ThParty.Advance[ credentials: now, state: desired.state, reason: convRequest.desiredSituation.reason, comment: convRequest.desiredSituation.comment, reportToAll: shouldReportToAll[desired.state] ]; SELECT nb FROM $success => cDesc.info.NoteNewStateP[cDesc, convEvent]; $stateMismatch => { <> cDesc.info.NoteNewStateP[cDesc, convEvent]; IF desired.state = $idle THEN LOOP; }; $noSuchConv => Report["Conv doesn't exist at Advance", cDesc.info]; <> $partyAlreadyActive => NULL; -- Deal with $canActivate here some time ENDCASE => AssessDamage[nb, cDesc, convEvent]; EXIT; ENDLOOP; }; DoRejectCall: INTERNAL PROC[cDesc: ConvDesc, convEvent: ConvEvent] = { []_ChangeState[cDesc, $idle, $busy]; }; <> <> <<>> NoteNewState: PUBLIC INTERNAL PROC[cDesc: ConvDesc, convEvent: ConvEvent] ~ { OPEN now: cDesc.situation.self; nb: NB; larkStateData: LORA _NIL; state: StateInConv _ convEvent.self.state; previousState: StateInConv = cDesc.situation.self.state; savedCurrentConvID: ConversationID; <> cDesc.situation _ convEvent^; IF state = previousState THEN RETURN; -- No conceivable value in acting. IF cDesc.info.currentConvID=nullConvID THEN cDesc.info.currentConvID _ cDesc.situation.self.convID; savedCurrentConvID _ cDesc.info.currentConvID; -- ForgetConv might clear it. SELECT state FROM $notified => { -- Somebody else did the notifying. We have to respond. <<This is the only time a change in our own state has to be reacted to? Still queued up because otherwise the state-management is confusing. >> cDesc.info.requests.QueueClientAction[QdNotification, cDesc]; }; $ringing, $ringback => larkStateData _ LIST[SetupRingTunes[cDesc]]; $idle, $neverWas => { <<If off-hook or speakerphone on, schedule a new dial-tone? I'm sure there's not enough info around to know why the other party quit, at this time.>> ForgetConv[cDesc]; }; $active => { value: ROPE; dbAtom: ATOM; larkStateData _ LIST[ComputeConnection[cDesc]]; [, dbAtom, value] _ DBInfo[now.partyID, $audiosource]; larkStateData _ CONS[NEW[Atom.DottedPairNode_[$audioSource, VoiceUtils.MakeAtom[value]]], larkStateData]; --[[$audioSource,audioSource],...] value _ DBInfo[now.partyID, $transmitonly, dbAtom].value; larkStateData _ CONS[NEW[Atom.DottedPairNode_[$transmitOnly, VoiceUtils.MakeAtom[value]]], larkStateData]; }; ENDCASE; IF state > Thrush.notReallyInConv AND cDesc.keyTable=NIL THEN { <> [nb, cDesc.keyTable] _ ThParty.GetKeyTable[credentials: now]; IF nb # $success THEN cDesc.keyTable_NIL; }; IF state> Thrush.notReallyInConv AND cDesc.keyTable#cDesc.info.larkInfo.keyTable AND cDesc.keyTable#NIL THEN larkStateData _ CONS[cDesc.keyTable, larkStateData]; IF now.convID = savedCurrentConvID THEN SetLarkState[cDesc, larkStateData]; <> }; AssessDamage: PUBLIC INTERNAL PROC [nb: NB, cDesc: ConvDesc, convEvent: ConvEvent] ~ { <<nb # $success after a call to ThParty. It was also not one of the failures that one expects in normal discourse. Decide how bad it is.>> <