<> <> DIRECTORY BasicTime USING [ Now, nullGMT, OutOfRange, Update ], FinchSmarts, IO, Lark USING [ KeyTable, SHHH ], Log USING [Problem, Report ], LupineRuntime USING [ BindingError ], Names USING [ CurrentPasskey, CurrentRName, GetDefaultInstance, GetGVDetails, GVDetails, OwnNetAddress, Results, StartConversation ], Nice, Process USING [ Detach, SecondsToTicks, SetTimeout, Ticks ], Rope, RPC USING [ AuthenticateFailed, CallFailed, EncryptionKey, ImportFailed, VersionRange ], RuntimeError USING [ UNCAUGHT ], ThParty USING [ Advance, Alert, ConversationsForParty, CreateParty, Deregister, DescribeParty, GetJayParty, GetNumbersForRName, GetParty, GetPartyFromNumber, OtherParty, Register, RegisterKey, ReleaseTrunkParty, SameConvClass, SetInterval ], ThPartyRpcControl, Thrush USING[ CallUrgency, ConversationHandle, ConvEvent, Disposition, EncryptionKey, IntervalSpec, IntervalSpecBody, IntSpecType, NB, none, nullConvHandle, nullHandle, nullKey, nullTune, PartyHandle, Reason, ROPE, SmartsHandle, StateID, StateInConv, ThHandle, Tune, unencrypted, VoiceDirection, VoiceInterval ], ThSmarts, ThSmartsRpcControl, ThVersions USING [ GetThrushVR, FinchVersion, FinchVR ], UserProfile USING [ Token] ; FinchSmartsImpl: CEDAR MONITOR IMPORTS BasicTime, IO, Log, LupineRuntime, Names, Nice, Process, RPC, Rope, RuntimeError, ThParty, ThPartyRpcControl, ThSmartsRpcControl, ThVersions, UserProfile EXPORTS FinchSmarts, ThSmarts = { OPEN IO; <> CallUrgency: TYPE = Thrush.CallUrgency; ConvDesc: TYPE = FinchSmarts.ConvDesc; ConversationHandle: TYPE = Thrush.ConversationHandle; nullConvHandle: ConversationHandle = Thrush.nullConvHandle; Disposition: TYPE = Thrush.Disposition; FinchInfo: TYPE = FinchSmarts.FinchInfo; IntervalSpec: TYPE = Thrush.IntervalSpec; NB: TYPE = Thrush.NB; none: SHHH = Thrush.none; nullHandle: Thrush.ThHandle = Thrush.nullHandle; PartyHandle: TYPE = Thrush.PartyHandle; Reason: TYPE = Thrush.Reason; ROPE: TYPE = Thrush.ROPE; SHHH: TYPE = Lark.SHHH; SmartsHandle: TYPE = Thrush.SmartsHandle; StateInConv: TYPE = Thrush.StateInConv; PD: TYPE = RECORD [ cPr: REF Ctr_NEW[Ctr_[0,1000000]], cSp: REF Ctr_NEW[Ctr_[0,1000000]], cRs: REF Ctr_NEW[Ctr_[0,1000000]], cNw: REF Ctr_NEW[Ctr_[0,1000000]], doReports: BOOL_FALSE, doNice: BOOL_FALSE, timeoutNoAction: INTEGER _ 30, noActionTicks: Process.Ticks _ Process.SecondsToTicks[30], timeoutJayConnect: INTEGER _ 1, noJayTicks: Process.Ticks _ Process.SecondsToTicks[1], encryptionRequested: BOOLEAN_TRUE, interfacesAreImported: BOOLEAN_FALSE, smartsIsExported: BOOLEAN_FALSE, waitsForConnect: NAT_6, queueIt: BOOL_FALSE, finchOn: BOOLEAN_FALSE ]; info: FinchInfo; pd: REF PD _ NEW[PD_[]]; stopSpec: Thrush.IntervalSpec = NEW[Thrush.IntervalSpecBody_[ interval: [length: 0], direction: play, queueIt: FALSE] ]; Ctr: TYPE = RECORD[ count: INT, stop: INT ]; Report: PROC[what: ROPE, ctr: REF Ctr] = { IF NOT pd.doReports THEN RETURN; Log.Report[what, $Finch]; ctr.count_ctr.count+1; IF ctr.count>ctr.stop THEN { Problem["Report overflow"]; }; }; BeNice: PROC[r: REF, d: INT] = { IF NOT pd.doNice THEN RETURN; Nice.BeNice[r, d, $Finch, NIL]; }; <> <<>> Progress: PUBLIC ENTRY PROC[ shh: SHHH, smartsID: Thrush.SmartsHandle, event: Thrush.ConvEvent, yourParty: BOOL, latestEvent: BOOL, informationOnly: BOOL ] RETURNS [ d: Thrush.Disposition ] = { cDesc: ConvDesc; IF info=NIL THEN RETURN[pass]; d_pass; <> IF pd.doReports THEN Report[ Rope.Concat[ IO.PutFR["---- FnProg: %g(%d) %g %g yr=%g ", PutFTime[event.credentials.convID], int[event.credentials.stateID], refAny[NEW[StateInConv_event.state]], rope[info.myRName], bool[yourParty]], IO.PutFR["lt=%g in=%g, ky=%g\n", bool[latestEvent], bool[event.intervalSpec#NIL], bool[event.keyTable#NIL]]], pd.cPr]; cDesc _ GetConv[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.intervalSpec#NIL THEN { thisInterval: Thrush.IntervalSpec _ cDesc.currentRecordIntervalSpec; thisRec: BOOL=thisInterval#NIL AND event.intervalSpec.intID = thisInterval.intID; thisType: Thrush.IntSpecType = event.intervalSpec.type; SELECT TRUE FROM thisType = request => GOTO Null; thisType = started AND thisRec => thisInterval^ _ event.intervalSpec^; thisType = finished AND thisRec => { thisInterval^ _ event.intervalSpec^; cDesc.currentRecordIntervalSpec_NIL; }; ENDCASE=> cDesc.currentRecordIntervalSpec_NIL; EnqueueInterval[cDesc, event.intervalSpec]; EXITS Null=>NULL; }; IF event.address#NIL THEN { cDesc.cState.address_event.address; cDesc.newAddress_TRUE; }; <<>> <> IF yourParty THEN { Other: PROC={ oD: ROPE; IF (~latestEvent) OR cDesc.otherPartyID#nullHandle THEN RETURN; [,cDesc.otherPartyID, oD, cDesc.conference] _ ThParty.OtherParty[shhh: info.shh, credentials: cDesc.cState.credentials]; IF oD#NIL THEN cDesc.otherPartyDesc _ oD; }; 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; 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 => cDesc.originator _ us; initiating => { cDesc.originator _ us; Other[]; }; pending => { cDesc.originator _ them; Other[]; }; maybe, ringing, active, canActivate, inactive => Other[]; ENDCASE; }; BeNice[event, 4]; BeNice[cDesc, 6]; <<>> cDesc.newEvent_TRUE; IF latestEvent THEN Apprise[info]; -- Wait for the last report to wake process. }; Supervise: PUBLIC ENTRY PROC[info: FinchInfo ] = { TRUSTED { Process.SetTimeout[@info.thAction, pd.noActionTicks]; }; IF info.apprise THEN DO ENABLE { UNWIND => NULL; < GOTO Failing;>> RuntimeError.UNCAUGHT => Problem["Unknown Finch Smarts Supervisor Failure"]; <> < { Problem["Unknown Finch Smarts Supervisor Failure"]; GOTO Failing; };>> }; cDesc: ConvDesc; prevL: ConvDesc _ NIL; nb: NB_success; trans: Transition; numC, numDead: NAT_0; info.apprise _ FALSE; FOR cDesc _ info.conversations, cDesc.next WHILE cDesc#NIL DO stateNow: StateInConv = cDesc.cState.state; newEvent: BOOL=cDesc.newEvent; ours: BOOL _ (info.currentConvID = cDesc.cState.credentials.convID); cDesc.newEvent_FALSE; -- For things that must be done once-only per new state. IF newEvent OR stateNow#idle THEN { IF pd.doReports THEN Report[ Rope.Concat[ IO.PutFR["**** FnSup: %g(%d) %g %g->%g", PutFTime[cDesc.cState.credentials.convID], int[cDesc.cState.credentials.stateID], rope[info.myRName], 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 ""]]], pd.cSp]; BeNice[cDesc, 6]; }; <<>> IF NOT cDesc.descValid THEN LOOP; <> numC_numC+1; SELECT stateNow FROM idle => IF ours THEN info.currentConvID _ nullConvHandle; parsing, reserved, initiating, maybe, ringing, canActivate, active, inactive => { ours_TRUE; info.currentConvID _ cDesc.cState.credentials.convID; }; pending, any => NULL; ENDCASE => NULL; <> SELECT (trans_transForStates[stateNow][cDesc.desiredState]) FROM noop => NULL; elim => { IF newEvent THEN info.bluejayConnection _ FALSE ELSE numDead_numDead+1; cDesc.currentRecordIntervalSpec_NIL; cDesc.cState.credentials.stateID_0; -- In case re-used. }; alrt => { -- placing call, or reserving conversation convID: ConversationHandle; IF NOT ours THEN Problem["What to do in a race?"]; [nb, convID] _ ThParty.Alert [ credentials: cDesc.cState.credentials, calledPartyID: cDesc.desiredPartyID, state: initiating, reason: cDesc.desiredReason, comment: cDesc.desiredComment ]; IF trans=alrt AND nb#stateMismatch THEN ThParty.ReleaseTrunkParty[partyID: cDesc.desiredPartyID]; <> IF nb=success THEN { cDesc.cState.credentials.convID _ info.currentConvID _ convID; ours_TRUE; }; }; idle, actv => -- Simple transitions nb _ ThParty.Advance [ shhh: info.shh, credentials: cDesc.cState.credentials, state: cDesc.desiredState, reason: cDesc.desiredReason, comment: cDesc.desiredComment ]; spvs => IF cDesc.desireKey THEN {-- Deal with requested intervals. keyIndex: [0..17B]; [nb, keyIndex] _ ThParty.RegisterKey[ shh: info.shh, credentials: cDesc.cState.credentials, key: cDesc.desiredKey ]; IF nb=success THEN { cDesc.desireKey_FALSE; IF cDesc.desiredInterval#NIL THEN { info.apprise_TRUE; cDesc.desiredInterval.keyIndex _ keyIndex; }; }; } ELSE IF cDesc.desiredInterval#NIL AND cDesc.desiredInterval.intID=0 THEN { -- Deal with requested intervals. intID: Thrush.StateID=cDesc.cState.credentials.stateID+1; nb _ ThParty.SetInterval [ shhh: info.shh, credentials: cDesc.cState.credentials, intervalSpec: cDesc.desiredInterval ]; IF nb#success THEN GOTO Null; IF cDesc.desiredInterval.direction=record THEN { cDesc.currentRecordIntervalSpec _ cDesc.desiredInterval; cDesc.currentRecordIntervalSpec.intID _ intID; } ELSE cDesc.desiredInterval_NIL; EXITS Null=>NULL; }; invl => { Problem["FinchSmarts: Invalid state transition request."]; info.apprise_TRUE; cDesc.desiredState _ idle; cDesc.desiredReason _ error; cDesc.desiredComment _ "Invalid state transition"; }; ntiy => { Problem["FinchSmarts: State transition not yet implemented."]; info.apprise_TRUE; cDesc.desiredState _ idle; cDesc.desiredReason _ error; cDesc.desiredComment _ "Unimplemented state transition"; }; ENDCASE => ERROR; <> IF nb#success THEN Report[IO.PutFR[" ** Results: nb=%g\n", refAny[NEW[Thrush.NB_nb]]], pd.cRs]; <<>> SELECT nb FROM success, stateMismatch => NULL; noSuchParty2 => -- Have to get to error tone here. No such party. Complain[info, cDesc, notFound, "Called party not found"]; narcissism => -- Have to get to error tone here. No such party. Complain[info, cDesc, notFound, "Attempt to call self rejected on philosophical grounds"]; partyNotEnabled => { info.ReportConversationState[noSuchSmarts, cDesc, "Your Lark is not registered with the telephone server"]; <<<< How to disarm the cDesc, which will continue to generate noise?>>>> }; invalidTransition, convNotActive, convStillActive => { <> comment: ROPE="Party-level detected invalid state transition request"; Problem[comment]; Complain[info, cDesc, error, comment]; }; notInConv, noSuchConv => { -- Complain, zap, go idle and repeat. comment: ROPE="NotInConv or NoSuchConv"; Problem[comment]; IF ours THEN info.currentConvID _ nullConvHandle; cDesc.cState.credentials.convID _ nullConvHandle; cDesc.cState.credentials.stateID _ 0; Complain[info, cDesc, error, comment]; }; noSuchParty, noSuchSmarts => { -- Complain, deregister, we gone! Needs tuning. Problem["NoSuchParty or NoSuchSmarts reported, must try to go away"]; GOTO Failing; }; ENDCASE => ERROR; <> IF newEvent THEN info.ReportConversationState[success, cDesc, NIL]; cDesc.newIntervals _ NIL; -- in case any were reported. prevL _ cDesc; ENDLOOP; IF info.conversations = NIL OR numC=numDead THEN EXIT; IF NOT info.apprise THEN WAIT info.thAction; IF info.conversations = NIL THEN EXIT; REPEAT Failing => UninitFinchSmarts[]; -- now Failed ENDLOOP; info.thProcess _ NIL; }; <> <<>> <> <> <> <<};>> <<>> GetRname: PUBLIC PROC[partyID: Thrush.PartyHandle] RETURNS [rName: ROPE] = { RETURN[ThParty.DescribeParty[shh: info.shh, partyID: partyID]]; }; DisconnectCall: PUBLIC ENTRY PROC[ convID: Thrush.ConversationHandle, -- not used yet reason: Thrush.Reason_terminating, comment: ROPE_NIL] = { cDesc: ConvDesc; state: StateInConv; IF NOT(([,cDesc, state]_FinchOn[]).on) THEN RETURN; info.bluejayConnection _ FALSE; SELECT state FROM idle, any => { info.ReportConversationState[convNotActive, NIL, "No conversation to disconnect"]; RETURN; }; ringing, pending => IF reason=terminating THEN reason _ busy; ENDCASE; Request[info, cDesc, idle, reason, comment]; }; AnswerCall: PUBLIC ENTRY PROC[convID: Thrush.ConversationHandle -- not used yet -- ] = { cDesc: ConvDesc; state: StateInConv; IF NOT(([,cDesc, state]_FinchOn[]).on) THEN RETURN; SELECT state FROM ringing, pending => NULL; <> <> ENDCASE=>RETURN; Request[info, cDesc, active]; }; PlaceCall: PUBLIC ENTRY PROC [ convID: Thrush.ConversationHandle, -- not used yet -- rName: ROPE, -- rName or description number: ROPE, -- telephone number, if present urgency: Thrush.CallUrgency_normal, useNumber: BOOL_FALSE] = { ENABLE { UNWIND=>NULL; RPC.CallFailed => { UninitFinchSmarts[]; CONTINUE; }; }; <> calledPartyID: PartyHandle; fullRName: ROPE; IF NOT (FinchOn[].on) THEN RETURN; calledPartyID _ SELECT TRUE FROM ~useNumber AND (calledPartyID _ ThParty.GetParty[shh: info.shh, partyID: info.partyID, rName: rName]) #nullHandle => calledPartyID, number#NIL => ThParty.GetPartyFromNumber[shh: info.shh, partyID: info.partyID, phoneNumber: number, description: rName, trunkOK: TRUE], rName=NIL OR useNumber => nullHandle, ([fullRName, number,] _ ThParty.GetNumbersForRName[shh: info.shh, rName: rName]).number # NIL => ThParty.GetPartyFromNumber[ shh: info.shh, partyID: info.partyID, phoneNumber: number, description: fullRName, trunkOK: TRUE], ENDCASE => nullHandle; IF calledPartyID = nullHandle THEN { info.ReportConversationState[noSuchParty2, NIL, IF rName=NIL THEN "You did not supply a call destination" ELSE "No telephone number could be found for called party"]; RETURN; }; []_Connect[calledPartyID, FALSE, urgency]; }; RecordTune: PUBLIC ENTRY PROC [ useTune: Thrush.Tune, useInterval: Thrush.VoiceInterval ] RETURNS[ reason: FinchSmarts.RecordReason_hopeless, tune: Thrush.Tune_Thrush.nullTune, interval: Thrush.VoiceInterval_[], key: Thrush.EncryptionKey_Thrush.nullKey ] = { ENABLE UNWIND=>NULL; <> <> <> <<<>>> <<<<>>>> <<>> ourSpec: Thrush.IntervalSpec; cDesc: ConvDesc; state: StateInConv; started: BOOL_FALSE; IF NOT (([,cDesc,state]_JayConnection[TRUE]).jayOpen) THEN RETURN; <> ourSpec _ NEW[Thrush.IntervalSpecBody _ [ type: request, direction: record, queueIt: pd.queueIt -- inst#NIL--, tune: useTune, interval: useInterval, keyIndex: 1 ]]; <<>> <> TRUSTED { Process.SetTimeout[@info.thAction, pd.noJayTicks]; }; FOR i: NAT IN [0..pd.waitsForConnect) DO IF ourSpec#cDesc.desiredInterval THEN <> IF cDesc.desiredInterval#NIL THEN { WAIT info.thAction; LOOP; } ELSE { cDesc.desiredInterval_ourSpec; Apprise[info]; }; WAIT info.thAction; -- <> state_cDesc.cState.state; IF state=idle OR (started_(ourSpec.type#request)) THEN EXIT; ENDLOOP; cDesc.desiredInterval _ NIL; TRUSTED { Process.SetTimeout[@info.thAction, pd.noActionTicks]; }; IF ~started OR cDesc.cState.keyTable=NIL THEN { <> IF state#idle THEN Complain[info, cDesc, error, "Voice server connection failed"]; RETURN[hopeless, Thrush.nullTune, , ]; }; <<>> <> WHILE cDesc.cState.state#idle AND cDesc.currentRecordIntervalSpec=ourSpec DO WAIT info.thAction; -- <> ENDLOOP; RETURN[ok, ourSpec.tune, ourSpec.interval, cDesc.cState.keyTable[ourSpec.keyIndex]]; <> }; StopTune: PUBLIC ENTRY PROC [reason: FinchSmarts.RecordReason_ok] = { ENABLE UNWIND=>NULL; cDesc: ConvDesc; IF NOT (([,cDesc,]_JayConnection[FALSE]).jayOpen) THEN RETURN; -- Stop only if started cDesc.desiredInterval _ stopSpec; Apprise[info]; }; PlaybackTune: PUBLIC ENTRY PROC [ tune: Thrush.Tune, interval: Thrush.VoiceInterval, key: Thrush.EncryptionKey, queueIt: BOOL_FALSE ] = { ENABLE UNWIND=>NULL; PlBaMe[tune, interval, key, queueIt]; }; PlBaMe: INTERNAL PROC [ tune: Thrush.Tune, interval: Thrush.VoiceInterval, key: Thrush.EncryptionKey, queueIt: BOOL_FALSE]= { cDesc: ConvDesc; state: StateInConv; IF tune<=Thrush.nullTune THEN { info.ReportConversationState[convNotActive, NIL, "No such tune"]; RETURN; }; -- UGH! IF NOT (([,cDesc,state]_JayConnection[TRUE]).jayOpen) THEN RETURN; TRUSTED { Process.SetTimeout[@info.thAction, pd.noJayTicks]; }; FOR i: NAT IN [0..pd.waitsForConnect) WHILE cDesc.desiredInterval#NIL DO WAIT info.thAction; ENDLOOP; TRUSTED { Process.SetTimeout[@info.thAction, pd.noActionTicks]; }; IF cDesc.desiredInterval#NIL THEN RETURN; cDesc.desiredKey _ key; cDesc.desireKey _ TRUE; cDesc.desiredInterval _ NEW[Thrush.IntervalSpecBody _ [ type: request, direction: play, queueIt: queueIt, tune: tune, interval: interval, keyIndex: 0 ] ]; -- will be filled in by Key registration Apprise[info]; }; <> InitFinchSmarts: PUBLIC PROC [ ReportSystemState: PROC[ on: BOOL ], ReportConversationState: PROC[ nb: NB, cDesc: ConvDesc, remark: Rope.ROPE ] ] = { ENABLE RPC.CallFailed => GOTO InitFailed; partyID: Thrush.PartyHandle; smartsID: Thrush.SmartsHandle; details: Names.GVDetails; results: Names.Results; thVR: RPC.VersionRange = ThVersions.GetThrushVR; UninitFinchSmarts[]; info _ NEW[FinchSmarts.FinchInfoBody]; info.ReportSystemState_ReportSystemState; info.ReportConversationState_ReportConversationState; info.myName _ [ type: "ThSmarts.Lark", instance: Names.GetDefaultInstance[netAddress: Names.OwnNetAddress[]], version: ThVersions.FinchVR]; info.myRName _ Names.CurrentRName[]; info.myPassword _ Names.CurrentPasskey[]; info.serverInstanceName _ UserProfile.Token[key: "ThrushClientServerInstance", default: "Strowger.Lark"]; ThSmartsRpcControl.ExportInterface[ interfaceName: info.myName, user: info.myRName, password: info.myPassword]; pd.smartsIsExported_TRUE; info.shh _ IF NOT pd.encryptionRequested THEN Thrush.unencrypted ELSE Names.StartConversation [ caller: info.myRName, callee: info.serverInstanceName, key: info.myPassword, level: --<>--CBCCheck ! RPC.AuthenticateFailed=> GOTO InitFailed]; <> [results, details] _ Names.GetGVDetails[rName: info.serverInstanceName, mode: ok]; IF results#ok OR ~details.valid THEN GOTO InitFailed; info.serverInstance _ details.connect; ThPartyRpcControl.ImportInterface[ interfaceName: [type: "ThParty.Lark", instance: info.serverInstance, version: thVR] ! -- RPC.ImportFailed=> { IF why=wrongVersion THEN Log.Report[IO.PutFR["Finch version %d too old; import failed", card[ThVersions.FinchVersion]], $Finch]; GOTO InitFailed; }]; pd.interfacesAreImported_TRUE; partyID_ThParty.CreateParty[shh: info.shh, type: individual, rName: info.myRName]; IF partyID=Thrush.nullHandle THEN GOTO InitFailed; smartsID_ThParty.Register[ shh: info.shh, partyID: partyID, interface: NEW[ThSmartsRpcControl.InterfaceName_info.myName], properties: [x: manager[machine: Names.OwnNetAddress[]]]] ; IF smartsID=Thrush.nullHandle THEN GOTO InitFailed; -- <> info.smartsID _ smartsID; info.partyID _ partyID; ThParty.ConversationsForParty[shh: info.shh, partyID: partyID]; <> pd.finchOn_TRUE; info.ReportSystemState[pd.finchOn]; EXITS InitFailed => UninitFinchSmarts[]; }; UninitFinchSmarts: PUBLIC PROC[problem: BOOL_FALSE] = { <<<>>> ENABLE RPC.CallFailed => GOTO Failed; IF problem THEN Problem[]; IF info#NIL THEN { IF pd.finchOn AND info.partyID#nullHandle AND info.smartsID#nullHandle THEN ThParty.Deregister[info.shh, info.smartsID!RPC.CallFailed=>CONTINUE]; info.shh_none; info.ReportSystemState[FALSE]; WHILE info.conversations#NIL DO -- a bug?! cDesc: ConvDesc=info.conversations; info.conversations _ cDesc.next; cDesc.next_cDesc.prev_NIL; cDesc.clientData_NIL; ENDLOOP; }; pd.finchOn_FALSE; info_NIL; IF pd.interfacesAreImported THEN ThPartyRpcControl.UnimportInterface[!LupineRuntime.BindingError=>CONTINUE]; pd.interfacesAreImported_FALSE; IF pd.smartsIsExported THEN ThSmartsRpcControl.UnexportInterface[!LupineRuntime.BindingError=>CONTINUE]; pd.smartsIsExported_FALSE; EXITS Failed => pd.interfacesAreImported _ pd.smartsIsExported _ FALSE; }; FinchIsRunning: PUBLIC PROC RETURNS [finchIsRunning: BOOL] = { RETURN[pd.finchOn]; }; <> GetConv: PUBLIC INTERNAL PROC[convID: ConversationHandle, validIfNew: BOOL ] RETURNS [ cDesc: ConvDesc_NIL ] = --INLINE-- { pDesc: ConvDesc_NIL; IF convID#nullConvHandle THEN FOR cDesc _ info.conversations, cDesc.next WHILE cDesc#NIL DO descID: ConversationHandle = cDesc.cState.credentials.convID; IF descID = convID THEN RETURN; IF cDesc.cState.state=idle AND ThParty.SameConvClass[convID, descID] THEN EXIT; pDesc_cDesc; ENDLOOP; IF cDesc=NIL THEN { -- If not, we're reusing a related entry. cDesc _ NEW[FinchSmarts.ConvDescBody_[]]; cDesc.prev _ pDesc; IF pDesc#NIL THEN pDesc.next _ cDesc ELSE info.conversations _ cDesc; cDesc.otherPartyDesc _ "unknown party"; }; cDesc.descValid _ validIfNew; cDesc.startTime _ BasicTime.Now[]; cDesc.desiredState _ any; cDesc.desiredPartyID _ nullHandle; cDesc.desiredReason _ wontSay; cDesc.desiredComment_NIL; cDesc.desiredInterval _ NIL; cDesc.desireKey_FALSE; cDesc.conference_ cDesc.completed _ cDesc.failed _ FALSE; <> cDesc.cState.credentials _ [ convID: convID, smartsID: info.smartsID, partyID: info.partyID, stateID: 0]; IF pd.doReports THEN Report[IO.PutFR[" ** NewConv %g %g, vl=%g\n", PutFTime[convID], rope[info.myRName], bool[validIfNew]], pd.cNw]; }; PutFTime: PROC[convID: ConversationHandle] RETURNS [IO.Value] = { ehsMemorialBool: BOOL_FALSE; -- wouldn't need if could return from catch phrase. IF convID=nullConvHandle OR convID=BasicTime.nullGMT THEN RETURN[rope["(not assigned)"]]; []_BasicTime.Update[convID, 0!BasicTime.OutOfRange=> { ehsMemorialBool_TRUE; CONTINUE; }]; IF ehsMemorialBool THEN RETURN[int[LOOPHOLE[convID, INT]]] ELSE RETURN[time[convID]]; }; GetConvDesc: PUBLIC PROC[convID: ConversationHandle]RETURNS[cDesc: ConvDesc_NIL ] = { <> FOR cDesc _ info.conversations, cDesc.next WHILE cDesc#NIL DO IF cDesc.cState.credentials.convID = convID THEN EXIT; ENDLOOP; RETURN[IF cDesc#NIL AND cDesc.descValid THEN cDesc ELSE NIL]; }; GetCDesc: PROC[] RETURNS [cDesc: ConvDesc_NIL ] = { convID: ConversationHandle=info.currentConvID; IF convID=nullConvHandle THEN RETURN; RETURN[GetConvDesc[convID]]; }; Apprise: PUBLIC INTERNAL PROC[info: FinchInfo] = --INLINE-- { IF info.thProcess=NIL THEN TRUSTED { Process.Detach[info.thProcess _ FORK Supervise[info]]; }; info.apprise _ TRUE; BROADCAST info.thAction; }; EnqueueInterval: INTERNAL PROC[cDesc: ConvDesc, int: IntervalSpec] = INLINE { iL: LIST OF IntervalSpec = LIST[int]; IF cDesc.newIntervals#NIL THEN cDesc.iTail.rest _ iL ELSE cDesc.newIntervals _ iL; cDesc.iTail _ iL; }; DequeueInterval: PUBLIC INTERNAL PROC[cDesc: ConvDesc] RETURNS [ int: IntervalSpec_NIL ] = { IF cDesc.newIntervals=NIL THEN RETURN; int_cDesc.newIntervals.first; cDesc.newIntervals _ cDesc.newIntervals.rest; }; Complain: INTERNAL PROC[info: FinchInfo, cDesc: ConvDesc, reason: Reason_wontSay, comment: ROPE_NIL] = { cDesc.desiredState _ idle; cDesc.desiredReason _ reason; cDesc.desiredComment _ comment; cDesc.desiredPartyID _ nullHandle; cDesc.desiredInterval _ NIL; Apprise[info]; -- BROADCAST is sometimes meaningless (running monitor.) }; FinchOn: INTERNAL PROC RETURNS [ on: BOOL_FALSE, cDesc: ConvDesc_NIL, state: StateInConv_idle] = { IF ~pd.finchOn THEN { Log.Report["Your Finch is not running", $Finch]; RETURN; }; on_TRUE; cDesc _ GetCDesc[]; state_IF cDesc=NIL THEN idle ELSE cDesc.cState.state; }; Request: INTERNAL PROC[ info: FinchInfo, cDesc: ConvDesc, state: StateInConv, reason: Reason_wontSay, comment: ROPE_NIL ] = { cDesc.desiredState _ state; cDesc.desiredReason _ reason; cDesc.desiredComment _ comment; Apprise[info]; }; Connect: INTERNAL PROC [ calledPartyID: PartyHandle, bluejayConnection: BOOL, urgency: Thrush.CallUrgency] RETURNS [ cDesc: ConvDesc ] = { state: StateInConv; [,cDesc, state]_FinchOn[]; SELECT state FROM idle => { cDesc _ GetConv[nullConvHandle, TRUE]; cDesc.cState.state_any; }; reserved => NULL; ENDCASE => { ThParty.ReleaseTrunkParty[shh: info.shh, partyID: calledPartyID]; info.ReportConversationState[convStillActive, NIL, "Conversation already in progress"]; RETURN; }; info.bluejayConnection _ bluejayConnection; cDesc.desiredPartyID _ calledPartyID; cDesc.desiredInterval _ NIL; Request[info, cDesc, active]; }; JayConnection: INTERNAL PROC[newConn: BOOL_TRUE] RETURNS [ jayOpen: BOOL_FALSE, cDesc: ConvDesc_NIL, state: StateInConv_idle ] = { calledPartyID: PartyHandle; IF NOT (([,cDesc, state]_FinchOn[]).on) THEN { jayOpen_RepRet[FALSE, noSuchParty, "Your Finch is not running"]; RETURN; }; SELECT state FROM idle, reserved => IF newConn THEN { calledPartyID _ ThParty.GetJayParty[shh: info.shh, partyID: info.partyID]; IF calledPartyID = Thrush.nullHandle THEN RETURN--[FALSE]--; cDesc _ Connect[calledPartyID, TRUE, normal]; }; ENDCASE; jayOpen_RepRet[info.bluejayConnection, noSuchConv, "Connection to voice server failed"]; }; RepRet: PROC[bool: BOOL, nb: NB, remark: Rope.ROPE] RETURNS[sameBool: BOOL] = { IF bool=FALSE THEN IF info#NIL THEN info.ReportConversationState[nb, NIL, remark] ELSE Log.Report[remark, $Finch]; sameBool_bool; }; Problem: PROC[remark: ROPE_NIL] = { Log.Problem[remark, $Finch]; }; <<>> <> Transition: TYPE = { <> noop, elim, idle, alrt, 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, invl, invl, invl, invl, invl, invl, elim, invl, elim ], -- idle (current) [ idle, invl, invl, invl, invl, invl, invl, invl, alrt, ntiy, noop ], -- reserved [ idle, invl, invl, invl, invl, invl, invl, invl, invl, ntiy, noop ], -- parsing [ idle, invl, invl, invl, invl, invl, invl, invl, noop, ntiy, noop ], -- initiating [ idle, invl, invl, invl, invl, invl, invl, invl, actv, ntiy, noop ], -- pending [ idle, invl, 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, invl, invl, invl, invl, invl, invl, invl, spvs, ntiy, spvs ], -- active [ idle, invl, invl, invl, invl, invl, invl, invl, ntiy, invl, ntiy ], -- inactive [ elim, invl, invl, invl, invl, invl, invl, invl, alrt, ntiy, invl ] -- any (nonex) ]; <> <> Nice.View[pd, "Finch PD"]; }.