<> <> <> <> <<>> DIRECTORY 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 ], SafeStorage USING [ NarrowRefFault ], ThNet USING [ pd ], ThParty USING [ Advance, ConversationInfo, DescribeParty, GetConversationInfo, GetKeyTable, GetPartyInfo, PartyInfo ], ThPartyPrivate USING [ GetCurrentParty ], Thrush USING [ 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 Commander, Convert, IO, LarkPlay, root: LarkSmartsMonitorImpl, MBQueue, NamesGV, Nice, RefID, RefTab, Rope, SafeStorage, 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; 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; <> info: SmartsInfo = GetSmartsInfo[smartsID: convEvent.self.smartsID]; IF info=NIL THEN { Report["No info at Progress", NIL]; RETURN; }; info.requests.QueueClientAction[QdLarkProgress, convEvent]; }; <smarts report>> <> <<>> QdLarkProgress: ENTRY PROC[r: REF] = { <> <> <<(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)>> <> ENABLE UNWIND => NULL; -- RestoreInvariant; convEvent: Thrush.ConvEvent = NARROW[r]; info: SmartsInfo = GetSmartsInfo[smartsID: convEvent.self.smartsID]; cDesc: ConvDesc; convID: ConversationID = convEvent.self.convID; whatNeedsDoing: WhatNeedsDoing; IF info=NIL THEN { Report["Smarts Info Missing 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; }; QdNotification: ENTRY PROC [r: REF] ~ { <> ENABLE UNWIND => NULL; -- RestoreInvariant; cDesc: ConvDesc = NARROW[r]; { OPEN now: cDesc.situation.self; <> autoAnswer: BOOL = VoiceUtils.MakeAtom[DBInfo[now.partyID, $autoanswer].value] = $true; IF now.state # $notified THEN RETURN; -- Nothing to do any more?? []_ChangeState[cDesc, IF autoAnswer 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: REF_NIL; state: StateInConv _ convEvent.self.state; savedCurrentConvID: ConversationID; <> cDesc.situation _ convEvent^; 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? Could possibly do the ChangeState here, but feel more comfortable queueing it.>> cDesc.info.requests.QueueClientAction[QdNotification, cDesc]; }; $ringing, $ringback => larkStateData _ 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 => larkStateData _ ComputeConnection[cDesc]; 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 larkStateData=NIL THEN larkStateData _ cDesc.keyTable; -- which might still be NIL: OK 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.>> <