<> <> <> <<>> DIRECTORY IO, Lark, LarkSmartsMonitorImpl, Rope USING [ Concat, Equal, Fetch, FromChar ], Log USING [ ProblemFR, ProblemHandle, SLOG ], Process USING [ Detach ], ThParty USING [ GetPartyFromNumber, GetPartyFromFeepNum, SetRingEnable ], Thrush USING [ ConversationHandle, H, IntervalSpec, PartyHandle, nullConvHandle, nullHandle, RingEnable, ROPE, StateInConv, ThHandle ], ThSmartsPrivate USING [ Apprise, ChangeState, ConvDesc, Deregister, EnableSmarts, EnterLarkState, GetConv, InterpretHookState, LarkFailed, LarkInfo, OpenConversations, ParseState, SmartsInfo ], TU USING [ RefAddr ] ; LarkSmartsImpl: CEDAR MONITOR LOCKS root IMPORTS IO, root: LarkSmartsMonitorImpl, Rope, Log, Process, ThParty, Thrush, ThSmartsPrivate, TU EXPORTS ThSmartsPrivate SHARES LarkSmartsMonitorImpl = { OPEN IO; <> CommandEvents: TYPE = Lark.CommandEvents; ConversationHandle: TYPE = Thrush.ConversationHandle; nullConvHandle: ConversationHandle=Thrush.nullConvHandle; ConvDesc: TYPE = ThSmartsPrivate.ConvDesc; 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; LarkInfo: TYPE = ThSmartsPrivate.LarkInfo; nullHandle: Thrush.ThHandle = Thrush.nullHandle; OpenConversations: TYPE = ThSmartsPrivate.OpenConversations; PartyHandle: TYPE = Thrush.PartyHandle; ROPE: TYPE = Thrush.ROPE; SHHH: TYPE = Lark.SHHH; -- Encrypts conv. if first arg to RPC PROC SmartsInfo: TYPE = ThSmartsPrivate.SmartsInfo; StateInConv: TYPE = Thrush.StateInConv; StatusEvents: TYPE = Lark.StatusEvents; larkRegistry: ROPE_".lark"; <<>> defaultRecordLength: INT _ -1; <<>> <> <<>> <> LarkParseEvent: PUBLIC ENTRY PROC[ smartsInfo: SmartsInfo, sEvent: Lark.StatusEvent] = { ENABLE { UNWIND=>NULL; ThSmartsPrivate.LarkFailed => { Log.ProblemFR["%g: Lark Failed", $Smarts, smartsInfo, TU.RefAddr[smartsInfo]]; GOTO Failed; }; }; cDesc: ConvDesc; IF ~LarkEnabled[smartsInfo] THEN RETURN; -- don't interfere with reverted call! sEvent _ ThSmartsPrivate.InterpretHookState[smartsInfo.larkInfo, sEvent]; IF sEvent.device=nothing THEN RETURN; Log.SLOG[]; DoParse[smartsInfo, sEvent.event]; IF smartsInfo.haveArguments THEN { val: INT_0; IF smartsInfo.Command=NIL THEN smartsInfo.Command _ CmdCall; SELECT smartsInfo.parseState FROM inNum => val _ IO.GetInt[IO.RIS[smartsInfo.arguments]! IO.Error => SELECT ec FROM Failure, SyntaxError, Overflow => CONTINUE; ENDCASE]; ENDCASE; smartsInfo.Command[smartsInfo, val]; []_SetParserIdle[smartsInfo, TRUE]; }; <> <<<> <>>> cDesc _ GetCDesc[smartsInfo]; IF cDesc#NIL THEN { SELECT cDesc.cState.state FROM # cDesc.desiredState => RETURN; -- some request already in the works. reserved => IF smartsInfo.haveOne THEN cDesc.desiredState_parsing ELSE RETURN; parsing => IF ~smartsInfo.haveOne THEN cDesc.desiredState_reserved ELSE RETURN; ENDCASE=>RETURN; ThSmartsPrivate.Apprise[smartsInfo]; }; EXITS Failed => ThSmartsPrivate.Deregister[smartsInfo]; }; LarkEnabled: INTERNAL PROC[info: SmartsInfo] RETURNS [enabled: BOOL] = INLINE { RETURN[SELECT info.larkInfo.larkState FROM none => ThSmartsPrivate.EnableSmarts[info], failed, recovering => FALSE, ENDCASE => TRUE]; }; DoParse: INTERNAL PROC[info: SmartsInfo, event: Lark.Event] = { <> info.haveOne _ TRUE; SELECT event FROM <<>> <> disabled => { info.haveArguments _ TRUE; info.Command _ CmdOnhook; }; enabled => { info.haveArguments _ TRUE; info.Command _ CmdOffhook; }; <> endNum => { info.haveArguments _ TRUE; <> }; IO.DEL, IO.ESC => []_SetParserIdle[info, TRUE]; <> '* => { IF info.cmdOrRecip THEN info.offset_10; info.cmdOrRecip _ TRUE; info.parseState _ idle; }; <> ENDCASE => { IF info.cmdOrRecip THEN { info.cmdOrRecip _ FALSE; event_event+info.offset; info.offset_0; IF event = '0 THEN { []_SetParserIdle[info, FALSE]; info.parseState _ getFeep; RETURN;}; []_SetParserIdle[info, TRUE]; SELECT event FROM <> '1, 'H, 'h => -- hold << for now, toggle "radio" state >> { info.Command _ CmdRadio; info.haveArguments _ TRUE; }; '3, 'C, 'c => -- conference << for now, toggle "hot line" state >> { info.Command _ CmdHotline; info.haveArguments _ TRUE; }; '7, 'F, 'f => -- forward << for now, toggle "monitored" state >> { info.Command _ CmdMonitor; info.haveArguments _ TRUE; }; '*, -- esc endNum -- del -- => []_SetParserIdle[info, TRUE]; <> <<'2, 'R, 'r => { info.Command _ CmdRecord; info.haveArguments _ TRUE; };>> <<'4, 'P, 'p => { info.Command _ CmdPlayOwn; info.haveArguments _ TRUE; };>> <<'5, 'D, 'd => { info.Command _ CmdDelete; info.haveArguments _ TRUE; };>> <<'6, 'G, 'g => { info.Command _ CmdPlayRecd; info.parseState _ getNum; };>> <<'8, 'M, 'm => info.Command _ CmdMail;>> '0+10, 'T, 't => { info.Command _ CmdBackDoor; info.haveArguments _ TRUE; }; <<**0>> '1+10, 'O, 'o => { info.Command _ CmdRingOff; info.haveArguments _ TRUE; }; '2+10, 'N, 'n => { info.Command _ CmdRingOn; info.haveArguments _ TRUE; }; '3+10, 'L, 'l => { info.Command _ CmdRingOffTimed; info.parseState _ getNum; }; '4+10, 'A, 'a => { info.Command _ CmdRingOnce; info.haveArguments _ TRUE; }; '5+10, 'B, 'b => { info.Command_CmdRingOnceTimed; info.parseState _ getNum; }; '9+10, 'R, 'r => info.Command _ CmdFlash; ENDCASE => -- unassigned same as DEL -- NULL; RETURN; }; <> ParseArgument[info, event]; }; }; ParseArgument: INTERNAL PROC[info: SmartsInfo, event: Lark.Event] = { AppendEvent[info, event]; info.parseState _ SELECT event FROM IN ['0..'9] => SELECT info.parseState FROM idle => inSeq, getFeep => inFeep, getNum => inNum, getStr => inStr, ENDCASE => info.parseState, ENDCASE => SELECT info.parseState FROM idle, getStr => inStr, getFeep, getNum, inFeep, inNum => inTossStr, inSeq => SetParserIdle[info, TRUE], ENDCASE => info.parseState; IF info.parseState = inSeq THEN { e1: Lark.Event = info.arguments.Fetch[0]; SELECT info.argLength FROM 1 => SELECT event FROM '0 => info.haveArguments_TRUE; '4, '5, '6, '8, '9 => NULL; ENDCASE => info.parseState _ inNum; <> 2 => IF info.arguments.Equal["90"] THEN info.parseState _ inNum; <> 3 => IF info.arguments.Equal["911"] THEN info.haveArguments_TRUE; 4 => SELECT e1 FROM '9 => IF info.arguments.Equal["9911"] THEN info.haveArguments _ TRUE; '4, '5, '6 => info.haveArguments _ TRUE; ENDCASE; 8 => IF info.arguments.Fetch[2] > '1 THEN info.haveArguments _ TRUE; 11 => info.haveArguments _ TRUE; ENDCASE; }; }; AppendEvent: INTERNAL PROC[info: SmartsInfo, event: Lark.Event] = { info.arguments _ Rope.Concat[info.arguments, Rope.FromChar[event]]; info.argLength _ info.argLength + 1; }; SetParserIdle: INTERNAL PROC[info: SmartsInfo, clearCmd: BOOL] RETURNS [ ThSmartsPrivate.ParseState ] = { info.haveArguments _ info.haveOne _ FALSE; info.parseState _ idle; info.arguments _ NIL; info.offset _ 0; info.cmdOrRecip _ FALSE; info.argLength _ 0; IF clearCmd THEN info.Command _ NIL; RETURN[idle]; }; <> <<>> CmdCall: INTERNAL PROC[info: SmartsInfo, val: INT] = { partyID: PartyHandle; cDesc: ConvDesc; IF info.argLength=0 OR info.parseState=inTossStr THEN RETURN; SELECT GetSIC[info] FROM reserved, parsing => NULL; ENDCASE => RETURN; cDesc _ ThSmartsPrivate.GetConv[info, info.currentConvID, TRUE]; partyID _ cDesc.cState.credentials.partyID; cDesc.desiredPartyID _ GetPartiesForCall[info, partyID, info.parseState, info.arguments, TRUE]; ThSmartsPrivate.ChangeState[info, cDesc, active]; }; CmdBackDoor: INTERNAL PROC[info: SmartsInfo, val: INT] = { IF GetSIC[info]=active AND info.larkInfo.larkState = trunkTalking THEN { -- Hack ThSmartsPrivate.EnterLarkState[info.larkInfo, trunkFlashing, info]; RETURN; }; info.arguments _ NIL; info.parseState _ inSeq; info.argLength _ 1; CmdCall[info, val]; }; CmdOnhook: INTERNAL PROC[info: SmartsInfo, val: INT] = { ThSmartsPrivate.ChangeState[info, GetCDesc[info], idle, terminating]; }; CmdOffhook: INTERNAL PROC[info: SmartsInfo, val: INT] = { cDesc: ConvDesc; desiredState: StateInConv_active; Log.SLOG[]; SELECT GetSIC[info] FROM idle => { cDesc _ ThSmartsPrivate.GetConv[info, nullConvHandle, TRUE]; desiredState _ reserved; }; ringing => cDesc _ GetCDesc[info]; ENDCASE => RETURN; ThSmartsPrivate.ChangeState[info, cDesc, desiredState]; }; CmdRingOff: INTERNAL PROC[info: SmartsInfo, val: INT] = { CmdRingDo[info,off,val]; }; CmdRingOn: INTERNAL PROC[info: SmartsInfo, val: INT] = { CmdRingDo[info,on,val]; }; CmdRingOffTimed: INTERNAL PROC[info: SmartsInfo, val: INT] = { CmdRingDo[info,offTimed,val]; }; CmdRingOnce: INTERNAL PROC[info: SmartsInfo, val: INT] = { CmdRingDo[info,subdued,val]; }; CmdRingOnceTimed: INTERNAL PROC[info: SmartsInfo, val: INT] = { CmdRingDo[info,subduedTimed,val]; }; CmdRingDo: INTERNAL PROC[info: SmartsInfo, enable: Thrush.RingEnable, val: INT] = TRUSTED INLINE { Process.Detach[FORK CmdRingDoProc[info, enable, val]]; }; CmdRingDoProc: ENTRY PROC[info: SmartsInfo, enable: Thrush.RingEnable, val: INT] = { partyID: PartyHandle; cDesc: ConvDesc = ThSmartsPrivate.GetConv[info, info.currentConvID, FALSE]; IF cDesc=NIL THEN RETURN; partyID _ cDesc.cState.credentials.partyID; ThParty.SetRingEnable[ partyID: partyID, ringEnable: enable, ringInterval: (IF val#0 THEN val ELSE 30)*60, update: TRUE]; }; CmdFlash: INTERNAL PROC[info: SmartsInfo, val: INT] = { NULL; }; CmdRadio: INTERNAL PROC[info: SmartsInfo, val: INT] = { info.larkInfo.radio _ NOT info.larkInfo.radio; }; CmdHotline: INTERNAL PROC[info: SmartsInfo, val: INT] = { info.larkInfo.hotLine _ NOT info.larkInfo.hotLine; }; CmdMonitor: INTERNAL PROC[info: SmartsInfo, val: INT] = { info.larkInfo.monitor _ NOT info.larkInfo.monitor; }; <> GetConvDesc: PUBLIC PROC[info: SmartsInfo] RETURNS [ cDesc: ConvDesc_NIL ] = { RETURN[GetCDesc[info]]; }; GetCDesc: PROC[info: SmartsInfo] RETURNS [ cDesc: ConvDesc_NIL ] = { <> convID: ConversationHandle=info.currentConvID; IF convID=nullConvHandle THEN RETURN; FOR convs: OpenConversations _ info.conversations, convs.rest WHILE convs#NIL DO IF convs.first.cState.credentials.convID = convID THEN { cDesc _ convs.first; EXIT; }; ENDLOOP; RETURN[IF cDesc#NIL AND cDesc.descValid THEN cDesc ELSE NIL]; }; GetSIC: PUBLIC INTERNAL PROC[info: SmartsInfo] RETURNS [ state: StateInConv ] = { cDesc: ConvDesc = GetCDesc[info]; RETURN[IF cDesc=NIL THEN idle ELSE cDesc.cState.state]; }; GetPartiesForCall: PROC[info: SmartsInfo, partyID: PartyHandle, parseState: ThSmartsPrivate.ParseState, args: ROPE, trunkOK: BOOL] RETURNS[calledPartyID: PartyHandle _ nullHandle ] = { IF partyID = nullHandle THEN RETURN; calledPartyID _ SELECT parseState FROM inSeq, inNum => ThParty.GetPartyFromNumber[ partyID: partyID, phoneNumber: args, trunkOK: trunkOK], inFeep => ThParty.GetPartyFromFeepNum[partyID: partyID, feepNum: args], inStr => Log.ProblemHandle[NIL, $Smarts, nullHandle, info], -- not these days! ENDCASE => Log.ProblemHandle[NIL, $Smarts, nullHandle, info]; }; }.