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; RuntimeError.UNCAUGHT => Problem["Unknown Finch Smarts Supervisor Failure"]; }; 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"]; }; 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 _ [ [ 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"]; }. âFinchSmartsImpl.mesa Last Edited by: Swinehart, November 14, 1983 4:56 pm Declarations Supervision Update local state information Fields always extracted <> Extracted if present Extracted if the event changes our state. Extracted if non-standard? Specific fatal error condition, if any => GOTO Failing; When you know what ones occur, catch them, and emulate... ANY => { Problem["Unknown Finch Smarts Supervisor Failure"]; GOTO Failing; }; Record whether or not this is now "our" conversation State and substate (below) and desired state indicate there's something to do. Do it: Will release iff attempt to alert failed to get into conversation. Analyze any results of trying to reach a different state. << How to disarm the cDesc, which will continue to generate noise?>> Complain and transition to idle Set up switching and tones to match state. Client Functions GetHistory: PUBLIC PROC[ convID: Thrush.ConversationHandle, toState: INT_0 -- i.e., all-- ] RETURNS [ s: Thrush.EventSequence ] = { s_ThParty.GetHistory[shhh: info.shh, convID: convID, firstState: 1, lastState: toState]; }; IF convID#info.currentConvID THEN { info.ReportConversationState[noSuchConv, NIL, "Can't answer that call"]; RETURN; }; Get fone for dest inst: ThMessageDB.MessageInstance = ThMessageDB.GetMessage["Beep.Lark", 1]; Out of luck, for a while . . . no way to talk about it here!!!! IF inst#NIL THEN PlBaMe[tune, key: extract from equiv. of inst.]; <> <<>> That's all, folks . . . for now. Wait for recording to (fail to) begin. Wait for previous request to be initiated Recording didn't start or can't be decrypted; return with error value. Wait for recording or conversation to end. If state is idle, possibly the interval doesn't know its actual duration. Registration Implement the GV lookup portion of importing the Thrush instance explicitly, in order to get the benefit of the local Grapevine cache. Will shortly update our knowledge of what's already going on!! <> Utilities Other fields are intentionally retained. Not at present protected by monitor -- FinchToolImpl back-pointer problem. State Transition Tables Just codes to dispatch on in Supervisor; explained there idle resrv pars init pend mayb ring canAc activ inact any -- desired NB: examine relationship to validity table in PartyOpsImpl someday. Debugging nonsense ʼ˜™J™4—J˜šÏk ˜ Jšœ œ&˜5Jšœ ˜ Jšœ˜Jšœœ œ˜Jšœœ˜Jšœœ˜%Jšœœz˜…J˜J˜J˜Jšœœ/˜—šœœœ˜ JšœD˜DJšœ œœœ/˜QJšœ7˜7šœœ˜Jšœœ˜ Jšœœ0˜Fšœœ ˜$JšœEœ˜L—Jšœ#œ˜.—Jšœ+˜+Jšœœ˜J˜—Jšœœœ8œ˜YJ™J™*šœ œ˜J˜šžœœ˜ Jšœœ˜ Jšœœœœ˜?˜-JšœJ˜J—Jšœœœ˜)J˜—J˜Jšœœ˜Jšœ=˜=Jšœ!˜!Jšœ%˜%Jšœ#˜#Jšœ œœ1œ˜OJ™Jšœœœ&˜?Jšœœ&˜BJšœœ*˜Jšœ ˜J˜+J˜2J˜1J˜9Jšœ˜—J˜—J˜J˜J˜J™Jšœœ˜Jšœ œŸ,˜OJ˜J˜—šž œ œœ˜2Jšœ;˜Bšœœ˜šœ˜Jšœœ˜Jšœ*œ ™7Jšœ œ7˜LJ™9Jšœ:œ ™MJ˜—Jšœ˜Jšœœ˜Jšœœ ˜J˜J˜Jšœœ˜šœ˜Jšœ œœ˜Jšœ+˜+Jšœ œ˜Jšœœ:˜DJšœœŸ8˜NJ˜šœ œœ˜#šœœ˜šœ ˜ šœ&˜(J˜QJšœœ œ#˜d—šœ˜Jšœœ;˜EJšœœœ œ˜'——Jšœ˜—J˜J˜—J™Jšœœœœ˜!J™4J˜ šœ ˜Jšœœœ%˜9˜QJ˜ Jšœ5˜5J˜—Jšœœ˜Jšœœ˜—J™Ušœ6˜@Jšœœ˜ šœ ˜ Jšœ œœœ˜GJšœ œ˜$Jšœ$Ÿ˜7J˜—šœ Ÿ+˜4J˜Jšœœœ"˜2˜J˜&Jšœ$˜$Jšœ˜J˜Jšœ˜Jšœ˜—šœ œ˜'Jšœ9˜9J™B—šœ œ˜J˜>Jšœœ˜ J˜—J˜—šœŸ˜#šœ˜J˜J˜&Jšœ˜J˜Jšœ˜Jšœ˜——šœ˜šœœŸ!˜:J˜šœ%˜%Jšœ˜Jšœ&˜&Jšœ˜Jšœ˜—šœ œ˜Jšœœ˜šœœœ˜#Jšœ œ˜J˜*J˜—J˜—J˜—šœœœœ˜&Jšœœ˜$JšŸ!˜!Jšœ9˜9šœ˜J˜J˜&Jšœ#˜#Jšœ˜—Jšœ œœ˜šœ(œ˜0Jšœ8˜8Jšœ.˜.Jšœ˜—Jšœœ˜Jšœœ˜——˜ J˜:Jšœ œ˜J˜J˜J˜2J˜—˜ J˜>Jšœ œ˜J˜J˜J˜8J˜—Jšœ ˜—J™9J˜šœ ˜Jšœœ'œœ˜M—J™šœ˜Jšœœ˜šœŸ2˜BJšœ:˜:—šœŸ2˜@JšœZ˜Z—šœ˜Jšœk˜kJ™DJ˜—šœ6˜6Jšœ™Jšœ œ9˜FJ˜Jšœ&˜&Jšœ˜—šœŸ%˜@Jšœ œ˜(Jšœ˜Jšœœ%˜1Jšœ1˜1J˜%Jšœ&˜&Jšœ˜—šœŸ/˜NJšœE˜EJšœ ˜ J˜—Jšœ ˜—J™*Jšœ œ.œ˜CJšœœŸ˜7Jšœ˜Jšœ˜—Jš œœœœœ˜6Jšœœœœ˜,Jšœœœœ˜&Jšœ!Ÿ ˜4Jšœ˜—Jšœœ˜J˜J˜——™J™šž œ œ.œŸœ™[Jšœ ™'J™XJ™J™—š žœœœœ œ˜LJšœ9˜?J˜J˜—šžœœœœ˜"Jšœ#Ÿ˜2J˜"Jšœ œœ˜J˜$Jšœœ!œœ˜3Jšœœ˜šœ˜šœ˜Jšœ,œ#˜RJšœ˜J˜—Jšœœœ˜=Jšœ˜—J˜,Jšœ˜J˜—š ž œœœœ#Ÿœ˜XJ˜$Jšœœ!œœ˜3šœ˜šœœ˜šœœ™#Jšœ)œœ™S——Jšœœ˜—Jšœ˜Jšœ˜J˜—šž œœœœ˜Jšœ#Ÿ˜5JšœœŸ˜$JšœœŸ˜-Jšœ#˜#Jšœ œœ˜Jš œœœœ&œ˜OJšœ™J˜Jšœ œ˜Jšœœœœ˜"šœœœ˜ šœ ˜šœe˜eJšœ˜——šœœ˜ Jšœsœ˜y—Jšœœœ˜%šœZœ˜`šœ˜Jšœ:˜:Jšœ!œ˜'——Jšœ˜—šœœ˜$šœ+œ˜/Jšœœœ(˜9Jšœ8˜<—Jšœ˜J˜—Jšœœ ˜*Jšœ˜J˜—šž œœœœ˜J˜J˜!Jšœ˜Jšœ˜Jšœ*˜*J˜"J˜"J˜(J˜Jšœœœ˜J™KJ™?J™AJ™LJ™DJ™Jšœ˜J˜$Jšœ œœ˜š œœ œ œœ˜BJ™ —šœ œ˜)Jšœ6Ÿ œ˜DJšœ2˜2Jšœ˜—J™J™&Jšœ8˜?šœœœ˜(šœ˜%J™)Jš œœœœœ˜@Jšœ3˜7—JšœŸ4˜HJ˜Jšœ œ"œœ˜˜>Jšœ(˜(——Jšœ ˜Jšœ˜——Jšœœ˜J˜JšœR˜RJšœœœ ˜2˜Jšœ,œ/˜^J˜;—J˜Jšœœœ Ÿ˜SJ˜Jšœ˜˜?J™>—Jšœ œ˜Jšœ#˜#š˜J˜"—J˜—J˜š žœœœ œœ˜7J™ÂJšœœœ˜%Jšœ œ ˜šœœœ˜šœ œœ˜KJšœ+œ œ˜E—J˜Jšœœ˜šœœœŸ ˜*J˜#J˜ Jšœœ˜Jšœœ˜Jšœ˜—J˜—Jšœ œ˜Jšœœ˜ šœ˜ JšœAœ˜K—Jšœœ˜šœ˜JšœBœ˜L—Jšœœ˜š˜Jšœ;œ˜A—˜J˜——š žœœ œœœ˜VJ˜——™ J˜šžœœœ)˜JJšœœœŸ œ˜0Jšœœ˜Jšœ˜šœ(œœ˜=Jšœ=˜=Jšœœœ˜Jšœœ'œœ˜OJšœ œ˜—šœœœŸ)˜=Jšœœ˜)J˜Jšœœœœ˜EJšœ'˜'J˜—J˜J˜"J˜J˜"J˜Jšœœ˜Jšœœ˜Jšœœ˜šœ3œ˜9J™(—˜J˜L—šœ˜Jšœœg˜p—J˜—J˜šžœœœœ ˜BJšœœœŸ3˜Pšœœ˜4Jšœœ˜$—˜6Jšœœœ˜#—Jš œœœœ œ˜:Jšœœ˜J˜J˜—šž œ œœœ˜UJ™Jšœ(œœ˜=Jšœ*œœœ˜?—Jšœœœœœœœ˜=J˜J˜—šžœœœœ˜3Jšœ.˜.Jšœœœ˜%Jšœ˜J˜—J˜šžœœœŸ œ˜=šœœ˜Jšœ#œ˜C—Jšœœ˜Jš œ˜J˜J˜—šžœœœ(œ˜NJšœœœœ˜%Jšœœœœ˜RJ˜J˜—J˜š žœœœœœœ˜]Jšœœœœ˜&J˜J˜-J˜—J˜š žœœœDœœ˜hJ˜J˜J˜J˜"Jšœœ˜JšœŸ8˜GJ˜J˜—šžœœœœ˜ Jšœœœœ˜Ašœ œ˜Jšœ1œ˜;—Jšœœ˜J˜Jš œœœœœ˜5J˜J˜—šžœœœ˜J˜J˜J˜J˜Jšœ œ˜J˜J˜Jšœ˜Jšœ˜J˜J˜J˜—šžœœœ˜Jšœ/œ˜QJšœ˜Jšœ˜J˜šœ˜Jšœ*œ˜KJšœ œ˜šœ˜ JšœA˜AJšœ.œ&˜WJšœ˜—Jšœ˜—Jšœ+˜+J˜%Jšœœ˜J˜Jšœ˜J˜—š ž œœœ œœœ˜:Jšœ ˜Jšœ˜Jšœ˜Jšœ˜J˜šœœ!œ˜.Jšœœ-œ˜K—šœ˜šœœ œ˜#JšœJ˜JJšœ#œŸ œ˜Jšœ˜ —J˜J˜J˜—šžœœ œ%˜CJ™——™J˜˜J™8J˜.J˜—J˜š œœ œœ œ˜HJšœO™OJ™JšœLŸ˜^JšœOŸ ˜ZJšœMŸ ˜WJšœMŸ ˜ZJ˜JšœNŸ ˜XJšœMŸ˜UJšœNŸ ˜XJšœNŸ˜\J˜JšœMŸ ˜VJšœMŸ ˜XJšœMŸ˜[J˜JšÏsC™C——J˜™Jšœ˜—J˜J˜J˜J˜—…—d⌀