<> <> <> <> <<>> DIRECTORY Atom USING [ DottedPairNode, GetPName ], Commander USING [ CommandProc, Register ], Convert USING [ IntFromRope ], GList USING [ DRemove ], IO, LarkPlay USING [ MergeToneSpecs, PlayString, ToneSpec, ToneSpecRec ], LarkSmartsMonitorImpl, MBQueue USING [ QueueClientAction ], NamesGV USING [ GVGetAttribute, GVSetAttribute ], Nice, Process USING [ Detach, SecondsToTicks, SetTimeout ], RefID USING [ ID, Reseal, Unseal ], RefTab USING [ Create, Fetch, Ref, Store ], Rope USING [ Concat, Fetch, Length, 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, LarkAvailable, LarkState, OpenConversations, ParseState, SmartsInfo ], ThSmartsRpcControl, Triples USING [ Select ], TU, VoiceUtils USING [ MakeAtom, ProblemFR, Report, ReportFR ] ; LarkSmartsSupImpl: CEDAR MONITOR LOCKS root IMPORTS Atom, Commander, Convert, GList, IO, LarkPlay, root: LarkSmartsMonitorImpl, MBQueue, NamesGV, Nice, Process, 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 are already in another)>> <> <<(New conversation and we're idle or it's a conversation we're in already)>> <> convID: ConversationID = convEvent.self.convID; info: SmartsInfo = GetSmartsInfo[smartsID: convEvent.self.smartsID]; cDesc: ConvDesc; nb2: NB; reason: Thrush.Reason; cInfo: ThParty.ConversationInfo; 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]; -- Report on existing conv. or notification of new one. 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, cInfo] _ ThParty.GetConversationInfo[convID: convEvent.self.convID]; reason _ IF nb2 # $success THEN $error ELSE IF convEvent.reason # NIL THEN convEvent.reason ELSE $terminating; -- NIL => $terminating IF nb2 # $success OR (cInfo.numParties-cInfo.numIdle) <= 1 THEN []_ChangeState[cDesc, IF reason=$terminating THEN $idle ELSE $failed, reason]; }; $idlerg => { <> [nb2, cInfo] _ ThParty.GetConversationInfo[convID: convEvent.self.convID]; reason _ IF nb2 # $success THEN $error ELSE IF convEvent.reason # NIL THEN convEvent.reason ELSE $terminating; IF nb2 # $success OR cInfo.originator = convEvent.other.partyID 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 $ckrg => CheckRing[cDesc]; -- We're ringing; Some other party that was ringing has now become active; determine if we should continue ringing. $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 ] = { ENABLE UNWIND => NULL; nb: NB; info: SmartsInfo = GetSmartsInfo[smartsID: report.self.smartsID]; cDesc: ConvDesc; IF info=NIL THEN { Report["No info at ReportAction", NIL]; RETURN; }; cDesc _ GetConv[ info, report.self.convID, FALSE ]; IF cDesc = NIL THEN RETURN; SELECT report.actionClass FROM $keyDistribution => { SELECT report.actionType FROM $newKeys => NULL; ENDCASE => RETURN; [nb, cDesc.keyTable] _ ThParty.GetKeyTable[credentials: report.self]; IF nb#$success THEN { cDesc.keyTable _ NIL; RETURN; }; IF cDesc.situation.self.state <= Thrush.notReallyInConv THEN RETURN; SetLarkState[cDesc, LIST[cDesc.keyTable]]; }; $recording, $playback, $synthesizer => <> SELECT report.actionType FROM $scheduled => cDesc.lastActionID _ report.actionID; $started => NULL; -- always follows $scheduled $finished, $flushed => { IF cDesc.lastActionID = report.actionID AND cDesc.situation.self.state > Thrush.notReallyInConv AND (SELECT cDesc.info.larkInfo.switchState FROM speaker, monitor =>TRUE, ENDCASE =>FALSE) THEN [--nb--] _ ChangeState[cDesc, $idle] }; ENDCASE; ENDCASE; }; <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; dbAtom: ATOM; dbRname: ROPE; val: ROPE; <> IF now.state # $notified THEN RETURN; -- Nothing to do any more?? IF ~ThSmartsPrivate.LarkAvailable[cDesc.info] THEN GOTO Reject; <> <> [dbRname, dbAtom, val] _ DBInfo[now.partyID, $deferanswer]; IF VoiceUtils.MakeAtom[val]=$true THEN TRUSTED { Process.Detach[FORK DeferAnswer[cDesc, dbRname]]; RETURN; }; <> IF cDesc.info.currentConvID#cDesc.situation.self.convID THEN GOTO Reject; []_ChangeState[cDesc, IF VoiceUtils.MakeAtom[DBInfo[now.partyID, $autoanswer, dbAtom].value] = $true THEN $active ELSE $ringing]; IF now.state=$ringing THEN CheckRing[cDesc]; EXITS Reject => DoRejectCall[cDesc, NIL]; -- couldn't accept call, for whatever reason. }; }; DeferAnswer: ENTRY PROC [cDesc: ConvDesc, dbRname: ROPE] ~ TRUSTED { c: CONDITION; Process.SetTimeout[@c, Process.SecondsToTicks[pd.timeoutNoAction]]; WAIT c; IF cDesc.situation.self.state # $notified THEN RETURN; <> NamesGV.GVSetAttribute[dbRname, $deferanswer, NIL]; cDesc.info.requests.QueueClientAction[QdNotification, cDesc]; }; <> <> 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; <> <<We're going to have to learn to deal with multiple conversations. Soon. Right now they sail right through under some circumstances. See Poachnotes.>> savedCurrentConvID _ cDesc.info.currentConvID; -- ForgetConv might clear it. IF ~ThSmartsPrivate.LarkAvailable[cDesc.info] THEN <<Crowbar when back door is not available. Our workstation representative probably launched the call. Called party may hear brief ring in this instance.>> SELECT state FROM $idle, $neverWas => NULL; ENDCASE => { DoRejectCall[cDesc, convEvent]; RETURN; }; 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 => 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]; <> }; CheckRing: PUBLIC INTERNAL PROC [cDesc: ConvDesc] ~ { nb: NB; cInfo: ThParty.ConversationInfo; [nb, cInfo] _ ThParty.GetConversationInfo[convID: cDesc.situation.self.convID]; IF nb # $success THEN { []_ChangeState[cDesc, $failed, $error, "System Error: can't monitor ringing situation"]; RETURN; }; IF cDesc.ringCheckProcess#NIL OR -- Already handling cInfo.numActive = 0 THEN RETURN; -- Not in the interesting situation yet. <> TRUSTED { Process.Detach[FORK RingProcess[cDesc]]; }; }; RingProcess: ENTRY PROC[cDesc: ConvDesc] = { ENABLE UNWIND => { cDesc.ringCheckProcess _ NIL; }; numWaited: INT_0; numToWait: INT_0; condition: CONDITION; TRUSTED { Process.SetTimeout[@condition, Process.SecondsToTicks[1]]; }; WHILE TRUE DO value: ROPE_NIL; valueAtom: ATOM; IF cDesc.situation.self.state # $ringing THEN EXIT; value _ DBInfo[cDesc.situation.self.partyID, $multiring].value; valueAtom _ VoiceUtils.MakeAtom[value]; SELECT TRUE FROM valueAtom=NIL, value.Length[]=0, valueAtom=$false => numToWait_numWaited; -- quit valueAtom=$true => { numToWait_numWaited+1; }; -- forever value.Fetch[0] IN ['0..'9] => numToWait_Convert.IntFromRope[value]; ENDCASE => numToWait _ numWaited; -- invalid database IF numWaited >= numToWait THEN GOTO Idle; WAIT condition; numWaited _ numWaited+1; REPEAT Idle => []_ChangeState[cDesc, $idle]; -- We're a third wheel. ENDLOOP; cDesc.ringCheckProcess _ NIL; }; 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.>> <