<> <> <<>> DIRECTORY Commander USING [ CommandProc, Register ], IO, Lark, LarkSmartsMonitorImpl, Nice, Process USING [ Detach, SetTimeout, SecondsToTicks ], Rope USING [ Concat ], Log USING [ Problem, ProblemFR, Report, ReportFR, SLOG ], ThParty USING [ Advance, Alert ], ThPartyPrivate USING [ DehandleSmarts, GetCurrentParty, SmartsBody, SmartsData ], Thrush USING [ CallUrgency, ConvEvent, ConversationHandle, Disposition, H, IntervalSpec, IntervalSpecs, PartyHandle, NB, nullConvHandle, nullHandle, Reason, ROPE, SmartsHandle, StateID, StateInConv, ThHandle ], ThSmartsPrivate USING [ ConvDesc, ConvDescBody, Deregister, EnterLarkState, LarkFailed, LarkState, OpenConversations, ParseState, SmartsInfo, SmartsInfoBody ], Triples USING [ Select ], TU ; LarkSmartsSupImpl: CEDAR MONITOR LOCKS root IMPORTS Commander, IO, Process, root: LarkSmartsMonitorImpl, Log, Nice, Rope, ThParty, ThPartyPrivate, Thrush, ThSmartsPrivate, Triples, TU EXPORTS ThSmartsPrivate SHARES LarkSmartsMonitorImpl = { OPEN IO; <> CallUrgency: TYPE = Thrush.CallUrgency; CommandEvents: TYPE = Lark.CommandEvents; ConversationHandle: TYPE = Thrush.ConversationHandle; nullConvHandle: ConversationHandle=Thrush.nullConvHandle; ConvDesc: TYPE = ThSmartsPrivate.ConvDesc; ConvEvent: TYPE = Thrush.ConvEvent; disabled: Lark.Event = Lark.disabled; enabled: Lark.Event = Lark.enabled; endNum: Lark.Event = Lark.endNum; H: PROC[r: REF] RETURNS[Thrush.ThHandle] = INLINE {RETURN[Thrush.H[r]]; }; IntervalSpec: TYPE = Thrush.IntervalSpec; IntervalSpecs: TYPE = Thrush.IntervalSpecs; LarkState: TYPE = ThSmartsPrivate.LarkState; NB: TYPE = Thrush.NB; nullHandle: Thrush.ThHandle = Thrush.nullHandle; OpenConversations: TYPE = ThSmartsPrivate.OpenConversations; PartyHandle: TYPE = Thrush.PartyHandle; Reason: TYPE = Thrush.Reason; ROPE: TYPE = Thrush.ROPE; SHHH: TYPE = Lark.SHHH; -- Encrypts conv. if first arg to RPC PROC SmartsHandle: TYPE = Thrush.SmartsHandle; SmartsInfo: TYPE = ThSmartsPrivate.SmartsInfo; SmartsInfoBody: TYPE = ThSmartsPrivate.SmartsInfoBody; StateInConv: TYPE = Thrush.StateInConv; StatusEvents: TYPE = Lark.StatusEvents; <<>> Ctr: TYPE = RECORD[ count: INT, stop: INT ]; PD: TYPE = RECORD [ defaultRecordLength: INT _ -1, timeoutNoAction: INTEGER _ 5, cPr: REF Ctr_NEW[Ctr_[0,1000000]], cSp: REF Ctr_NEW[Ctr_[0,1000000]], cRs: REF Ctr_NEW[Ctr_[0,1000000]], cZp: REF Ctr_NEW[Ctr_[0,1000000]], cNw: REF Ctr_NEW[Ctr_[0,1000000]], doReports: BOOL_FALSE, doNice: BOOL_FALSE ]; pd: REF PD _ NEW[PD_[]]; Report: PROC[what: ROPE, info: SmartsInfo, ctr: REF Ctr] = { IF NOT pd.doReports THEN RETURN; Log.Report[what, $Lark, info.larkInfo]; ctr.count_ctr.count+1; IF ctr.count>ctr.stop THEN { Log.Problem["Report overflow", $Lark, info.larkInfo]; }; }; ReportFR: PROC[what: ROPE, info: SmartsInfo, ctr: REF Ctr, a1, a2: IO.Value_rope[NIL]] = { IF NOT pd.doReports THEN RETURN; Log.ReportFR[what, $Lark, info.larkInfo, a1, a2]; ctr.count_ctr.count+1; IF ctr.count>ctr.stop THEN { Log.Problem["Report overflow", $Lark, info.larkInfo]; }; }; BeNice: PROC[r: REF, d: INT, info: SmartsInfo] = { IF NOT pd.doNice THEN RETURN; Nice.BeNice[r, d, $Lark, info.larkInfo]; }; <> <<>> LarkProgress: PUBLIC ENTRY PROC[ shh: SHHH, smartsID: Thrush.SmartsHandle, event: Thrush.ConvEvent, yourParty: BOOL, latestEvent: BOOL, informationOnly: BOOL ] RETURNS [ d: Thrush.Disposition ] = { info: SmartsInfo _ GetSmartsInfo[smartsID: smartsID]; cDesc: ThSmartsPrivate.ConvDesc; IF info=NIL THEN RETURN[pass]; d_actedAndStop; <> Log.SLOG[]; IF pd.doReports THEN Report[ Rope.Concat[ IO.PutFR["---- LkProg: %t(%d) %g %g yr=%g ", time[event.credentials.convID], int[event.credentials.stateID], refAny[NEW[StateInConv_event.state]], TU.RefAddr[info], bool[yourParty]], IO.PutFR["lt=%g in=%g, ky=%g\n", bool[latestEvent], bool[event.intervalSpecs#NIL], bool[event.keyTable#NIL]]], info, pd.cPr]; cDesc _ GetConv[info, event.credentials.convID, FALSE]; IF event.credentials.stateID <= cDesc.cState.credentials.stateID THEN RETURN[pass]; -- Old news! <<>> <> cDesc.cState.credentials.smartsID _ smartsID; cDesc.cState.credentials.stateID _ event.credentials.stateID; <<<> <>>> <> IF event.keyTable#NIL THEN { cDesc.cState.keyTable _ event.keyTable; cDesc.newKeys_TRUE; }; IF event.intervalSpecs#NIL THEN <> EnqueueIntervals[cDesc, event.intervalSpecs]; IF event.address#NIL THEN { cDesc.cState.address_event.address; cDesc.newAddress_TRUE; }; <<>> <> IF yourParty THEN { cDesc.descValid _ TRUE; cDesc.cState.credentials.partyID _ event.credentials.partyID; cDesc.cState.state _ event.state; cDesc.cState.comment _ event.comment; cDesc.cState.reason _ event.reason; <<<< When Conferencing is added, put this back in.>>>> <> IF event.spec#NIL THEN { cDesc.cState.spec _ event.spec; cDesc.newSpec_TRUE; }; <> IF event.comment#NIL THEN cDesc.cState.comment _ event.comment; IF event.urgency#normal THEN cDesc.cState.urgency _ event.urgency; IF event.alertKind#standard THEN cDesc.cState.alertKind _ event.alertKind; SELECT event.state FROM reserved, parsing, initiating => cDesc.originator _ us; maybe => { cDesc.originator _ us; <> IF info.Supervise = LarkSupervise THEN info.larkInfo.ringTune _ event.ringTune; }; pending => cDesc.originator _ them; ringing => { cDesc.originator _ them; info.larkInfo.ringTune _ event.ringTune; }; ENDCASE; }; BeNice[event, 4, info]; BeNice[cDesc, 6, info]; <<>> cDesc.newEvent _ TRUE; IF latestEvent THEN Apprise[info]; -- Wait for the last report to wake process. }; <<>> <> LarkSupervise: PUBLIC ENTRY PROC[info: SmartsInfo ] = { TRUSTED { Process.SetTimeout[@info.thAction, Process.SecondsToTicks[pd.timeoutNoAction]]; }; IF info.apprise THEN DO ENABLE { UNWIND => NULL; ThSmartsPrivate.LarkFailed => GOTO Failing; }; prevL: OpenConversations _ NIL; nb: NB_success; convL: OpenConversations; trans: Transition; info.apprise _ FALSE; FOR convL _ info.conversations, convL.rest WHILE convL#NIL DO cDesc: ConvDesc = convL.first; stateNow: StateInConv = cDesc.cState.state; ours: BOOL _ ( cDesc.cState.credentials.convID = info.currentConvID ); Log.SLOG[]; IF pd.doReports THEN Report[ Rope.Concat[ IO.PutFR["**** LkSup: %t(%d) %g %g->%g", time[cDesc.cState.credentials.convID], int[cDesc.cState.credentials.stateID], TU.RefAddr[info], refAny[NEW[StateInConv_stateNow]], refAny[NEW[StateInConv_cDesc.desiredState]]], IO.PutFR[" %g%g\n", refAny[NEW[Transition_transForStates[stateNow][cDesc.desiredState]]], rope[IF ours THEN " (ours)" ELSE ""]]], info, pd.cSp]; BeNice[cDesc, 6, info]; <<>> IF NOT cDesc.descValid THEN LOOP; IF info.currentConvID = nullConvHandle --AND stateNow#idle-- THEN { <<<>>> ours_TRUE; info.currentConvID _ cDesc.cState.credentials.convID; }; IF stateNow=idle THEN { <> <> IF pd.doReports THEN ReportFR[" ** Zap %t\n", info, pd.cZp, time[cDesc.cState.credentials.convID]]; IF prevL#NIL THEN { prevL.rest _ convL.rest; convL _ prevL; } ELSE info.conversations_convL.rest; IF ours THEN { reason: Reason = cDesc.cState.reason; newConvID: ConversationHandle_cDesc.cState.credentials.convID; newDesc: ConvDesc; info.currentConvID _ nullConvHandle; SELECT info.larkInfo.hookState FROM onhook, spkr => SELECT reason FROM terminating, wontSay => GOTO Exit; ENDCASE => IF cDesc.originator#us THEN GOTO Exit; ENDCASE => SELECT TRUE FROM reason=terminating, reason=wontSay, cDesc.originator#us => { <> newConvID _ nullConvHandle; info.currentConvID _ nullConvHandle; }; ENDCASE; newDesc _ GetConv[info, newConvID, TRUE]; ChangeState[info, newDesc, reserved, cDesc.cState.reason, cDesc.cState.comment]; -- Now go around the loop once, if only to coerce Lark into idle state. EXITS Exit => NULL; }; } ELSE IF NOT ours THEN <> <> nb _ ThParty.Advance[ credentials: cDesc.cState.credentials, state: idle, reason: busy, comment: "One conversation at a time, please." ] ELSE { <> SELECT (trans_transForStates[stateNow][cDesc.desiredState]) FROM noop => NULL; elim => ERROR; -- handled in stateNow=idle case above. rsrv, rers, prsg, alrt => { -- placing call, or reserving conversation convID: ConversationHandle; calledPartyID: PartyHandle = IF trans#alrt THEN nullHandle ELSE cDesc.desiredPartyID; nb _ IF trans=alrt AND calledPartyID=nullHandle THEN noSuchParty2 ELSE success; IF NOT ours THEN Problem["What to do in a race?", info]; IF nb=success THEN [nb, convID] _ ThParty.Alert [ credentials: cDesc.cState.credentials, calledPartyID: calledPartyID, state: IF trans = alrt THEN initiating ELSE cDesc.desiredState, reason: cDesc.desiredReason, newConv: trans=rsrv, comment: cDesc.desiredComment ]; IF nb=success THEN { cDesc.cState.credentials.convID _ info.currentConvID _ convID; ours_TRUE; }; }; idle, actv => -- Simple transitions nb _ ThParty.Advance [ credentials: cDesc.cState.credentials, state: cDesc.desiredState, reason: cDesc.desiredReason, comment: cDesc.desiredComment ]; spvs => { -- Supervision while active. cDesc.desiredState_active; -- Reflect reality FOR iL: IntervalSpecs _ cDesc.newIntervals, iL.rest WHILE iL#NIL DO <> iS: IntervalSpec = iL.first; IF iS.interval.length#0 OR iS.queueIt THEN -- else flush: ignore SELECT iS.type FROM started => cDesc.desiredIntID _ iS.intID; finished => IF iL.rest=NIL AND iS.intID = cDesc.desiredIntID THEN SELECT info.larkInfo.hookState FROM spkr, spKr => ChangeState[info, cDesc, idle]; ENDCASE; ENDCASE; ENDLOOP; cDesc.newIntervals _ NIL; }; <> <> ring => -- This is how you say you'll ring an incoming call IF info.larkInfo.hotLine THEN { info.apprise_TRUE; cDesc.desiredState _ active; } ELSE nb _ ThParty.Advance [ credentials: cDesc.cState.credentials, state: ringing ]; invl => { comment: ROPE="Invalid state transition"; Problem[comment, info]; ChangeState[info, cDesc, idle, error, comment]; }; ntiy => { comment: ROPE= "Unimplemented state transition"; Problem[comment, info]; ChangeState[info, cDesc, idle, error, comment]; }; ENDCASE => ERROR; }; <> IF nb#success THEN ReportFR[" ** Results: nb=%g\n", info, pd.cRs, refAny[NEW[Thrush.NB_nb]]]; <<>> SELECT nb FROM success, stateMismatch => NULL; noSuchParty2, narcissism => -- Have to get to error tone here. No such party. ChangeState[info, cDesc, idle, notFound, "Called party not found, or calling self"]; partyNotEnabled => ours_FALSE; invalidTransition, convNotActive, convStillActive => { <> comment: ROPE="Party-level detected invalid state transition request"; Problem[comment, info]; ChangeState[info, cDesc, idle, error, comment]; }; notInConv, noSuchConv => { -- Complain, zap, go idle and repeat. comment: ROPE="NotInConv or NoSuchConv"; Problem[comment, info]; IF ours THEN info.currentConvID _ nullConvHandle; cDesc.cState.credentials.convID _ nullConvHandle; cDesc.cState.credentials.stateID _ 0; ChangeState[info, cDesc, idle, error, comment]; }; noSuchParty, noSuchSmarts => { -- Complain, deregister, we gone! Needs tuning. Problem[ "LkSmts: NoSuchParty or NoSuchSmarts reported, must try to go away", info]; GOTO Failing; }; ENDCASE => ERROR; <> IF ours THEN ThSmartsPrivate.EnterLarkState[ info.larkInfo, LarkStateForState[cDesc, stateNow], info]; prevL _ convL; ENDLOOP; IF NOT info.apprise THEN WAIT info.thAction; IF info.conversations = NIL THEN EXIT; REPEAT Failing => ThSmartsPrivate.Deregister[info]; -- now Failed ENDLOOP; info.thProcess _ NIL; }; <<>> <> GetConv: PUBLIC INTERNAL PROC[info: SmartsInfo, convID: ConversationHandle, validIfNew: BOOL ] RETURNS [ cDesc: ConvDesc_NIL ] = --INLINE-- { FOR convs: OpenConversations _ info.conversations, convs.rest WHILE convs#NIL DO IF convs.first.cState.credentials.convID = convID THEN RETURN[convs.first]; ENDLOOP; cDesc _ NEW[ThSmartsPrivate.ConvDescBody_[]]; cDesc.descValid _ validIfNew; cDesc.cState.state _ any; cDesc.cState.credentials _ [ convID: convID, smartsID: H[info.smarts], partyID: ThPartyPrivate.GetCurrentParty[smartsID: H[info.smarts]], stateID: 0 ]; info.conversations _ CONS[cDesc, info.conversations]; IF pd.doReports THEN Report[IO.PutFR[" ** NewConv %t %g, vl=%g\n", time[convID], TU.RefAddr[info], bool[validIfNew]], info, pd.cNw]; }; ChangeState: PUBLIC INTERNAL PROC[ info: SmartsInfo, cDesc: ConvDesc, state: StateInConv _ idle, reason: Reason _ wontSay, comment: ROPE_NIL ] = { IF info=NIL OR cDesc=NIL THEN RETURN; cDesc.desiredState _ state; cDesc.desiredReason _ reason; cDesc.desiredComment _ comment; Apprise[info]; }; Apprise: PUBLIC INTERNAL PROC[info: SmartsInfo] = TRUSTED --INLINE-- { IF info.thProcess=NIL THEN Process.Detach[info.thProcess _ FORK info.Supervise[info]]; info.apprise _ TRUE; NOTIFY info.thAction; }; EnqueueIntervals: INTERNAL PROC[cDesc: ConvDesc, int: IntervalSpecs] = INLINE { IF cDesc.newIntervals#NIL THEN cDesc.iTail.rest _ int ELSE cDesc.newIntervals _ int; FOR iL: IntervalSpecs _ int, iL.rest WHILE iL#NIL DO cDesc.iTail _ iL; ENDLOOP; }; <<>> <> GetSmartsInfo: PUBLIC PROC[smartsID: SmartsHandle_nullHandle, smarts: ThPartyPrivate.SmartsData_NIL] RETURNS [info: SmartsInfo_NIL] = { r: REF_smarts; IF smartsID#nullHandle THEN r_ThPartyPrivate.DehandleSmarts[smartsID]; IF r#NIL THEN RETURN[NARROW[Triples.Select[$SmartsData, r, --info--]]]; }; Problem: PROC[comment: ROPE, info: SmartsInfo] = { Log.ProblemFR[Rope.Concat["LarkSmarts(%g): ", comment], $Smarts, info, TU.RefAddr[info]]; }; <> larkStateForState: ARRAY StateInConv OF LarkState _ [ <> idle, dialTone, silence, silence, silence, ringBack, ringing, silence, talking, <> silence, silence ]; LarkStateForState: INTERNAL PROC[cDesc: ConvDesc, state: StateInConv] RETURNS [larkState: LarkState] = -- INLINE -- { SELECT (larkState_larkStateForState[state]) FROM talking => { spec: Lark.ConnectionSpec = cDesc.cState.spec; IF spec=NIL THEN ERROR; IF spec.localSocket.net = spec.remoteSocket.net AND spec.localSocket.host = spec.remoteSocket.host THEN larkState _ trunkTalking; }; dialTone => larkState _ SELECT cDesc.cState.reason FROM busy, notImportantEnough => busyTone, absent, noCircuits, noParticular, notFound, error => errorTone, ENDCASE => dialTone; ENDCASE; }; Transition: TYPE = { <> noop, elim, idle, rsrv, rers, prsg, alrt, ring, actv, spvs, invl, ntiy }; transForStates: ARRAY StateInConv OF ARRAY StateInConv OF Transition _ [ << idle resrv pars init pend mayb ring canAc activ inact any -- desired>> <<>> [ elim, invl, prsg, invl, invl, invl, invl, invl, elim, elim, elim ], -- idle (current) [ idle, noop, prsg, invl, invl, invl, invl, invl, alrt, ntiy, noop ], -- reserved [ idle, rers, noop, invl, invl, invl, invl, invl, alrt, ntiy, invl ], -- parsing [ idle, noop, invl, invl, invl, invl, invl, invl, noop, ntiy, noop ], -- initiating [ idle, invl, invl, invl, invl, invl, invl, invl, actv, ntiy, ring ], -- pending [ idle, noop, invl, invl, invl, invl, invl, invl, noop, ntiy, noop ], -- maybe [ idle, invl, invl, invl, invl, invl, invl, invl, actv, ntiy, noop ], -- ringing [ idle, invl, invl, invl, invl, invl, invl, invl, ntiy, ntiy, ntiy ], -- canActivate [ idle, spvs, invl, invl, invl, invl, invl, invl, spvs, ntiy, spvs ], -- active [ idle, invl, invl, invl, invl, invl, invl, invl, ntiy, noop, ntiy ], -- inactive [ noop, rsrv, prsg, invl, invl, invl, invl, invl, alrt, ntiy, invl ] -- any (nonex) ]; <> <> ViewCmd: Commander.CommandProc = TRUSTED { Nice.View[pd, "Lark PD"]; }; Commander.Register["VuLarkSmarts", ViewCmd, "Program Management variables for Lark Smarts"]; }.