<> <> <> <> <> DIRECTORY Commander USING [ CommandProc, Register ], FinchSmarts, GVBasics USING [ MakeKey, Password ], IO, LarkFeepRpcControl USING [ Feep, ImportNewInterface ], LupineRuntime USING [ BindingError ], MBQueue USING [ Create, QueueClientAction ], NameDB USING [ Error, SetAttribute ], NamesRPC USING [ StartConversation ], Nice, Process USING [ Detach, SecondsToTicks, SetTimeout ], PupSocket USING [ GetUniqueID ], RefID USING [ ID ], Rope, RPC USING [ AuthenticateFailed, CallFailed, EncryptionKey, ImportFailed, SetMaxTransmissions, TimeoutEnable, VersionRange ], ThParty USING [ Advance, Alert, CheckIn, ConversationInfo, CreateConversation, DescribeParty, GetConversationInfo, GetParty, GetNumbersForRName, GetPartyFromNumber, GetPartyInfo, LookupServiceInterface, PartyInfo, Unvisit, Visit ], Thrush USING[ ActionReport, ConversationID, ConvEvent, Credentials, InterfaceSpec, NB, none, nullConvID, nullID, PartyID, Reason, ROPE, SHHH, SmartsID, StateInConv, unencrypted ], ThSmarts, ThSmartsRpcControl, ThVersions USING [ FinchVR ], UserProfile USING [ Token], VoiceUtils USING [ CurrentPasskey, CurrentPassword, CurrentRName, InstanceFromNetAddress, MakeRName, NetAddress, OwnNetAddress, Problem, ProblemFR, Registrize, Report, ReportFR ] ; FinchSmartsImpl: CEDAR MONITOR IMPORTS Commander, FinchSmarts, GVBasics, IO, LarkFeepRpcControl, LupineRuntime, MBQueue, NameDB, NamesRPC, Nice, Process, PupSocket, Rope, RPC, ThParty, ThSmartsRpcControl, ThVersions, UserProfile, VoiceUtils EXPORTS FinchSmarts, ThSmarts = { OPEN IO; <> ConvDesc: TYPE = FinchSmarts.ConvDesc; ConversationID: TYPE = Thrush.ConversationID; nullConvID: ConversationID = Thrush.nullConvID; FinchInfo: TYPE = FinchSmarts.FinchInfo; NB: TYPE = Thrush.NB; none: SHHH = Thrush.none; nullID: RefID.ID = Thrush.nullID; us: FinchSmarts.WhoOriginated = FinchSmarts.us; PartyID: TYPE = Thrush.PartyID; ROPE: TYPE = Thrush.ROPE; SHHH: TYPE = Thrush.SHHH; SmartsID: TYPE = Thrush.SmartsID; StateInConv: TYPE = Thrush.StateInConv; nullInterfaceSpec: Thrush.InterfaceSpec = [serviceID: nullID]; PD: TYPE = FinchSmarts.PD; pd: PUBLIC REF PD _ NEW[PD_[]]; info: FinchInfo; Report: PROC[what: ROPE] = { IF NOT pd.doReports THEN RETURN; VoiceUtils.Report[what, $Finch]; }; BeNice: PROC[r: REF, d: INT] = { IF NOT pd.doNice THEN RETURN; Nice.BeNice[r, d, $Finch, NIL]; }; NConcConvs: PROC[l1, l2: LIST OF ConvDesc] RETURNS [LIST OF ConvDesc] = { IF l1=NIL THEN RETURN[l2]; FOR convs: LIST OF ConvDesc _ l1, convs.rest WHILE convs#NIL DO IF convs.rest=NIL THEN { convs.rest_l2; RETURN[l1]; }; ENDLOOP; ERROR; }; DequeueConv: PROC[l1: LIST OF ConvDesc, c: ConvDesc] RETURNS [LIST OF ConvDesc] = { IF l1=NIL THEN RETURN[l1]; -- No list IF l1.first=c THEN RETURN[l1.rest]; -- Head of list FOR convs: LIST OF ConvDesc _ l1, convs.rest WHILE convs.rest#NIL DO IF convs.rest.first=c THEN { convs.rest _ convs.rest.rest; RETURN[l1]; }; -- Internal to list ENDLOOP; RETURN[l1]; -- Not in list }; <> <<>> Progress: PUBLIC ENTRY PROC[ shh: SHHH, convEvent: Thrush.ConvEvent ] = { ENABLE UNWIND => NULL; -- RestoreInvariant; <> <> <> <> <> IF convEvent.self.partyID = convEvent.other.partyID THEN [] _ NoteNewState[convEvent]; }; Substitution: PUBLIC ENTRY PROC[ shh: SHHH, convEvent: Thrush.ConvEvent, oldPartyID: Thrush.PartyID, newPartyID: Thrush.PartyID ] = { ENABLE UNWIND => NULL; -- RestoreInvariant; <> <<If this notice indicates that our own party is the oldPartyID, we should find a way to inform our ReportConversationState clients that we are no longer involved in this conversation. We should also pull the conv. from our active list. This happens when the same user logs in and runs Finch somewhere else, or when our Finch bombs out in the middle of a conversation. >> cDesc: ConvDesc = GetConv[convEvent.self.convID, FALSE]; IF cDesc#NIL THEN cDesc.numParties _ 0; -- This will force the information update. [] _ NoteNewState[ convEvent ]; }; ReportAction: PUBLIC ENTRY PROC[ shh: SHHH, report: Thrush.ActionReport ] = { ENABLE UNWIND => NULL; ReportRequestState[GetConv[report.self.convID], report]; BROADCAST info.stateChange; -- Sometimes callers wait (Ugh!)} }; CheckIn: PUBLIC ENTRY PROC[ shh: Thrush.SHHH, credentials: Thrush.Credentials, reason: Thrush.Reason, nextScheduledCheck: INT ] = { ENABLE UNWIND => NULL; info.nextScheduledCheck _ nextScheduledCheck; SELECT reason FROM $goodbye => IF pd.connected THEN [] _ FinchSmarts.QUit[$serverDown, "Disconnect requested by server"]; $welcome => IF ~pd.connected THEN -- Earlier disconnected assumption was wrong. [] _ FinchSmarts.QUit[$success]; ENDCASE; NOTIFY info.pollCondition; }; Poke: PUBLIC ENTRY PROC = { ENABLE UNWIND => NULL; IF info#NIL THEN NOTIFY info.pollCondition; }; <> LimitSignalling: ENTRY PROC [cDesc: ConvDesc] ~ TRUSTED { <> <> ENABLE UNWIND => NULL; c: CONDITION; Process.SetTimeout[@c, Process.SecondsToTicks[pd.timeoutInitiating]]; WAIT c; IF cDesc.situation.self.state # $initiating THEN RETURN; []_Request[cDesc, $failed, $noAnswer, "Other party failed to respond to call."]; }; <> <<>> CallInfo: TYPE = REF CallInfoBody; -- needed because MBQueue requires a REF CallInfoBody: TYPE = RECORD [ convID: Thrush.ConversationID, calledPartyID: Thrush.PartyID_nullID, reason: Thrush.Reason_NIL, comment: ROPE_NIL ]; DisconnectCall: PUBLIC ENTRY PROC[ convID: Thrush.ConversationID, reason: Thrush.Reason _ $terminating, comment: ROPE_NIL, newState: StateInConv _ $idle] = { ENABLE UNWIND => NULL; -- RestoreInvariant; cDesc: ConvDesc; state: StateInConv; IF NOT(([,cDesc, state]_FinchOn[convID]).on) THEN RETURN; SELECT state FROM $idle, $neverWas => { ReportConversationState[$convNotActive, NIL, "No conversation to disconnect"]; }; ENDCASE => []_Request[cDesc, newState, reason, comment]; }; AnswerCall: PUBLIC ENTRY PROC[convID: Thrush.ConversationID] = { ENABLE UNWIND => NULL; -- RestoreInvariant; cDesc: ConvDesc; state: StateInConv; IF NOT (([,cDesc, state]_FinchOn[convID]).on) THEN RETURN; SELECT state FROM $ringing, $notified => []_Request[cDesc, $active]; ENDCASE; }; PlaceCall: PUBLIC ENTRY PROC [ convID: Thrush.ConversationID_nullConvID, rName: ROPE, -- rName or description number: ROPE, -- telephone number, if present useNumber: BOOL_FALSE ] = { ENABLE UNWIND=>NULL; -- RestoreInvariant; calledPartyID: PartyID; nb: NB; reason: Thrush.Reason _ $notFound; comment: Rope.ROPE _ NIL; IF ~useNumber THEN { [nb, calledPartyID] _ThParty.GetParty[shh:info.shh, partyID:info.partyID, rName:rName]; SELECT nb FROM $noSuchParty2, $noIdentSupplied => useNumber _ TRUE; $serverDown, $notEnabled, $noSuchParty => RETURN; -- Futile ENDCASE; }; IF useNumber AND number.Length[]>0 THEN [nb, calledPartyID] _ ThParty.GetPartyFromNumber[ shh: info.shh, partyID: info.partyID, phoneNumber: number, description: rName]; SELECT nb FROM $success => reason _ NIL; -- reason: NIL, comment: NIL $noSuchParty2 => NULL; -- reason: $notFound, comment: NIL $narcissism => comment _ "Attempt to call self rejected on philosophical grounds"; ENDCASE => { ReportConversationState[nb, NIL, NIL]; RETURN; }; [--nb, cDesc--]_Connect[calledPartyID, convID, reason, comment]; }; Feep: PUBLIC ENTRY PROC[convID: ConversationID, feepString: ROPE] = { ENABLE UNWIND => NULL; cDesc: ConvDesc; nb: Thrush.NB_$success; { ENABLE { RPC.CallFailed => { IF cDesc=NIL THEN CONTINUE; cDesc.feepInterface _ NIL; RETRY; -- Will attempt reconnection via ThParty }; RPC.ImportFailed => { nb _ $noFeepService; CONTINUE; }; }; state: StateInConv; IF NOT(([,cDesc, state]_FinchOn[convID]).on) OR state#$active THEN nb _ $convNotActive; IF nb=$success AND cDesc.feepInterface=NIL THEN { [nb, , cDesc.feepInterfaceSpec] _ ObtainServiceInterface[NIL, "LarkFeep", cDesc]; IF nb=$success THEN -- Import every time, for luck. cDesc.feepInterface _ LarkFeepRpcControl.ImportNewInterface[ interfaceName: cDesc.feepInterfaceSpec.interfaceName, hostHint: cDesc.feepInterfaceSpec.hostHint!RPC.ImportFailed => CONTINUE ]; }; IF nb=$success THEN nb _ LarkFeepRpcControl.Feep[ interface: cDesc.feepInterface, shhh: info.shh, serviceID: cDesc.feepInterfaceSpec.serviceID, convID: cDesc.situation.self.convID, requestingParty: cDesc.situation.self.partyID, number: feepString, noisy: TRUE, on: 100, off: 80 ]; }; IF nb # $success THEN VoiceUtils.Report["Sorry, was not able to `feep'.", $Finch]; }; <<>> ObtainNumbersForRName: PUBLIC PROC[rName: ROPE] RETURNS [fullRName: ROPE, number: ROPE, homeNumber: ROPE] = { [fullRName, number, homeNumber] _ ThParty.GetNumbersForRName[shh: info.shh, rName: rName]; }; <<>> IdentifyVisitor: PUBLIC ENTRY PROC [visitor, password: Rope.ROPE, complain: BOOL _ TRUE] RETURNS [nb: NB] ~ { ENABLE UNWIND => NULL; <<Perhaps allow a NIL password as default param - GVBasics.MakeKey does the right thing with NIL -- also with ReleaseVisitor, below.>> visitingPartyID: PartyID; visitingPassKey: GVBasics.Password _ GVBasics.MakeKey[password]; visitor _ VoiceUtils.Registrize[visitor]; IF Rope.Equal[info.myRName, visitor, FALSE] THEN { <> ReleaseVisitorSelf[]; RETURN }; [nb, visitingPartyID] _ThParty.GetParty[shh:info.shh, partyID:info.partyID, rName:visitor]; IF nb=$success THEN nb _ ThParty.Visit[shh: info.shh, visitedParty: info.partyID, visitingParty: visitingPartyID, visitingPassword: visitingPassKey]; SELECT nb FROM $success => { VoiceUtils.ReportFR["%s visiting %s -- %s calls will ring in both offices.", $Finch, NIL, IO.rope[visitor], IO.rope[info.myRName], IO.rope[visitor]]; }; $passwordNotValid => IF complain THEN VoiceUtils.ReportFR["Invalid password for %s.", $Finch, NIL, IO.rope[visitor]]; $noSuchParty2 => IF complain THEN VoiceUtils.ReportFR["Couldn't identify visiting party %s.", $Finch, NIL, IO.rope[visitor]]; ENDCASE => Problem["Server communication problem", nb]; }; ReleaseVisitor: PUBLIC ENTRY PROC [visitor, password: Rope.ROPE] ~ { ENABLE UNWIND => NULL; nb: NB; visitingPartyID: PartyID; visitingPassKey: GVBasics.Password _ GVBasics.MakeKey[password]; IF visitor.Length[]=0 THEN { -- assume cancelling visiting at home via "Unvisit" ReleaseVisitorSelf[]; RETURN }; visitor _ VoiceUtils.Registrize[visitor]; IF Rope.Equal[info.myRName, visitor, FALSE] THEN { <> ReleaseVisitorSelf[]; RETURN }; [nb, visitingPartyID] _ThParty.GetParty[shh:info.shh, partyID:info.partyID, rName:visitor]; <> IF nb = $success THEN { visitee: PartyID; [visitee: visitee] _ ThParty.DescribeParty[partyID: visitingPartyID, nameReq: $none]; IF visitee=info.partyID THEN { nb _ ThParty.Unvisit[shh: info.shh, visitedParty: visitee, visitingParty: visitingPartyID, visitingPassword: visitingPassKey]; }; }; SELECT nb FROM $success => VoiceUtils.ReportFR["Visiting cancelled for %s.", $Finch, NIL, IO.rope[visitor]]; $noSuchParty2, $noSuchVisiting => VoiceUtils.ReportFR["%s was not visiting here", $Finch, NIL, IO.rope[visitor]] ENDCASE => Problem["Server communication problem", nb]; }; ReleaseVisitorSelf: INTERNAL PROC ~ { nb: NB _ $noSuchParty; visitee: PartyID; [visitee: visitee] _ ThParty.DescribeParty[partyID: info.partyID, nameReq: $none]; IF visitee # nullID THEN nb _ ThParty.Unvisit[shh: info.shh, visitedParty: visitee, visitingParty: info.partyID, visitingPassword: VoiceUtils.CurrentPassword[]]; SELECT nb FROM $success => VoiceUtils.ReportFR[ "Visiting cancelled for %s. Welcome home.", $Finch, NIL, IO.rope[info.myRName]]; $noSuchParty2, $noSuchVisiting => VoiceUtils.ReportFR["%s was not visiting anywhere", $Finch, NIL, IO.rope[info.myRName]]; ENDCASE => Problem["Server communication problem", nb]; }; ServiceConnect: PUBLIC ENTRY PROC[ serviceName: ROPE, convID: ConversationID_nullConvID, createOK: BOOL_TRUE ] RETURNS [ nb: NB_$success, cDesc: ConvDesc_NIL ] = { ENABLE UNWIND=>NULL; state: StateInConv; calledPartyID: PartyID; IF NOT (([,cDesc, state]_FinchOn[convID]).on) THEN RETURN[nb: $finchNotRunning]; IF cDesc=NIL THEN [convID, cDesc, state] _ GetActiveConversation[serviceName]; -- Hack SELECT state FROM idle, reserved => { IF ~createOK THEN RETURN[$noSuchConv, NIL]; IF serviceName = NIL THEN calledPartyID _ nullID ELSE [nb, calledPartyID] _ ThParty.GetParty[ shh: info.shh, partyID: info.partyID, rName: serviceName, type: $service]; IF nb=$success THEN [nb, cDesc] _ Connect[calledPartyID, convID, NIL, NIL]; }; ENDCASE => IF cDesc=NIL THEN nb _ $convStillActive; -- Another service already in use. IF nb=$success AND serviceName#NIL THEN nb _ WaitForActive[cDesc]; }; <<>> ObtainServiceInterface: PUBLIC PROC[ serviceName: ROPE, interfaceName: ROPE, cDesc: ConvDesc_NIL ] RETURNS [ nb: NB_$success, shhh: Thrush.SHHH, interfaceSpec: Thrush.InterfaceSpec ] = { partyID: Thrush.PartyID_Thrush.nullID; credentials: Thrush.Credentials; IF cDesc#NIL THEN { credentials _ cDesc.situation.self; IF cDesc.numParties>1 THEN partyID _ cDesc.partyInfo[1].partyID; } ELSE IF serviceName = NIL THEN partyID _ info.partyID -- get from self! ELSE { -- want interface only credentials _ [info.partyID, info.smartsID]; [nb, partyID] _ThParty.GetParty[ shh: info.shh, partyID: info.partyID, rName: serviceName, type: $service]; IF nb#$success THEN RETURN; }; IF partyID = Thrush.nullID THEN RETURN[nb: $noSuchParty2, shhh: none, interfaceSpec: nullInterfaceSpec]; [nb, interfaceSpec] _ ThParty.LookupServiceInterface[info.shh, credentials, partyID, interfaceName]; shhh _ info.shh; }; <> GetConv: INTERNAL PROC[convID: ConversationID, createOK: BOOL_FALSE] RETURNS [ cDesc: ConvDesc_NIL ] = --INLINE-- { cL: LIST OF ConvDesc; IF convID#nullConvID THEN FOR convs: LIST OF ConvDesc _ info.conversations, convs.rest WHILE convs#NIL DO cDesc _ convs.first; IF cDesc.situation.self.convID = convID THEN RETURN; ENDLOOP; cDesc _ NIL; IF ~createOK THEN RETURN; <> cDesc _ NEW[FinchSmarts.ConvDescBody_[ situation: [ self: [ convID: convID, smartsID: info.smartsID, partyID: info.partyID ]]]]; cL _ LIST[cDesc]; info.conversations _ NConcConvs[info.conversations, cL]; }; FinchOn: INTERNAL PROC[convID: ConversationID_nullConvID] RETURNS [ on: BOOL_FALSE, cDesc: ConvDesc_NIL, state: StateInConv_$idle] = { IF ~pd.enabled OR info=NIL THEN { VoiceUtils.Report["Your Finch is not running", $Finch]; RETURN; }; on_TRUE; IF convID=nullConvID THEN RETURN; cDesc _ GetConv[convID, FALSE]; IF cDesc#NIL THEN state_cDesc.situation.self.state; }; NoteNewState: INTERNAL PROC[convEvent: Thrush.ConvEvent] RETURNS[cDesc: ConvDesc]= { cInfo: ThParty.ConversationInfo; pInfo: ThParty.PartyInfo; nb: NB; cDesc _ GetConv[convEvent.self.convID, TRUE]; cDesc.situation _ convEvent^; [nb, cInfo] _ ThParty.GetConversationInfo[shh: info.shh, convID: convEvent.self.convID]; <> <> <> SELECT TRUE FROM nb # $success => NULL; -- Problem["Can't obtain conversation information"]; <> cDesc.numParties#cInfo.numParties, cDesc.numActive#cInfo.numActive, cDesc.numIdle#cInfo.numIdle => { <> cDesc.subject _ cInfo.subject; cDesc.startTime _ cInfo.startTime; cDesc.numParties _ cInfo.numParties; cDesc.numActive _ cInfo.numActive; cDesc.numIdle _ cInfo.numIdle; [nb, pInfo] _ ThParty.GetPartyInfo[shh: info.shh, credentials: cDesc.situation.self, nameReq: $description, allParties: TRUE]; IF nb#$success THEN NULL -- Problem["Can't obtain conversation information", nb] ELSE { cDesc.partyInfo _ pInfo; IF cDesc.numParties>=1 THEN IF pInfo[0].partyID=cInfo.originator THEN cDesc.whoOriginated _ us ELSE FOR i: INT IN [1..cDesc.numParties) DO IF pInfo[i].partyID=cInfo.originator THEN { cDesc.whoOriginated _ i; IF pInfo[i].type=$trunk THEN pInfo[i].intendedName _ "outside line"; }; ENDLOOP; }; }; ENDCASE; SELECT cDesc.situation.self.state FROM $idle, $neverWas => info.conversations _ DequeueConv[info.conversations, cDesc]; $initiating => TRUSTED { Process.Detach[FORK LimitSignalling[cDesc]]; }; $notified => SELECT filtering FROM -- Needs tests for calls already in progress.  1 => []_Request[cDesc, $idle, $notImportantEnough, "This is a test"]; 2 => []_Request[cDesc, $ringing]; 3 => []_Request[cDesc, $active, $whatever, "Hot line!"]; ENDCASE; ENDCASE => cDesc.ultimateState _ cDesc.situation.self.state; <<If we've been called due to a Substitution that's kicking us out (cf Subst.), we should find a way to inform our ReportConversationState clients that we are no longer involved in this conversation. We should also do a Dequeue as above. >> ReportConversationState[$success, cDesc, NIL]; BROADCAST info.stateChange; }; Request: INTERNAL PROC[ cDesc: ConvDesc, state: StateInConv, reason: Thrush.Reason_NIL, comment: ROPE_NIL ] RETURNS [nb: NB] = { DO convEvent: Thrush.ConvEvent; reportToAll: BOOL = SELECT state FROM $idle, $failed, $active, $inactive, $ringing => TRUE, ENDCASE=>FALSE; IF state=$failed AND cDesc.partyInfo.numParties>=1 AND ~cDesc.partyInfo[0].voicePath THEN state _ $idle; [nb, convEvent] _ ThParty.Advance[shhh: info.shh, credentials: cDesc.situation.self, state: state, reportToAll: reportToAll, reason: reason, comment: comment]; SELECT nb FROM $success => []_NoteNewState[convEvent]; $stateMismatch => { cDesc_NoteNewState[convEvent]; IF state=$idle AND cDesc.situation.self.state # $idle THEN LOOP; }; ENDCASE => ReportConversationState[nb, cDesc, "Telephone server action failed"]; <<Real confused about Problem (typeout only) reporting vs. client reporting. The responsibilities are unconvincingly distributed among Smarts and client.>> EXIT; ENDLOOP; }; Connect: INTERNAL PROC [calledPartyID: PartyID, convID: ConversationID_nullConvID, reason: Thrush.Reason _ NIL, comment: ROPE_NIL] RETURNS [ nb: NB_$success, cDesc: ConvDesc_NIL ] = { <> state: StateInConv; newState: StateInConv = SELECT TRUE FROM reason#NIL => $failed, calledPartyID = nullID => $parsing, -- temp until can debug inactive stuff. ENDCASE => $initiating; convEvent: Thrush.ConvEvent; IF ~(([,cDesc, state]_FinchOn[convID]).on) THEN { nb _ $notEnabled; RETURN; }; SELECT state FROM $idle => { [nb, convEvent] _ ThParty.CreateConversation[ shhh: info.shh, credentials: [partyID: info.partyID, smartsID: info.smartsID], state: newState, reason: reason, comment: comment ]; IF nb = $success THEN { cDesc _ NoteNewState[convEvent]; IF cDesc.partyInfo=NIL OR cDesc.partyInfo.numParties=0 OR ~cDesc.partyInfo[0].voicePath THEN { [] _ Request[cDesc, $failed, $noVoicePath, "Your Etherphone is not running"]; RETURN[$noVoicePath, cDesc]; }; }; }; $reserved, $parsing => nb _ Request[cDesc, newState, reason, comment]; ENDCASE => { ReportConversationState[$convStillActive, NIL, "Conversation already in progress"]; RETURN; }; IF nb # $success OR newState=$failed THEN RETURN; cDesc.originatorRecorded _ TRUE; IF newState # $initiating THEN RETURN; nb_ThParty.Alert[shhh: info.shh, credentials: cDesc.situation.self, calledPartyID: calledPartyID]; SELECT nb FROM $success => NULL; $stateMismatch => { nb_Request[cDesc, $failed, $error, "Conversation already in progress"]; <<Some smarts don't do this. They probably should.>> RETURN; }; $narcissism => { nb_Request[cDesc, $failed, $notFound, "Attempt to call self rejected on philosophical grounds"]; RETURN; }; ENDCASE => Problem["Server communication problem", nb]; <<Do something interesting  see other smarts for more confusion>> }; WaitForActive: INTERNAL PROC[cDesc: ConvDesc] RETURNS [nb: NB_$success] = { <> FOR i: NAT IN [0..pd.waitsForConnect) DO SELECT cDesc.situation.self.state FROM $active => RETURN; $failed => EXIT; ENDCASE; TRUSTED{Process.SetTimeout[@info.stateChange,Process.SecondsToTicks[pd.jayTimeout]];}; WAIT info.stateChange; -- <> SELECT cDesc.situation.self.state FROM $idle, $neverWas => EXIT; -- something wrong. ENDCASE; ENDLOOP; nb_Request[cDesc, $failed, $error, "Finch failed to connect to voice service."]; IF nb=$success THEN nb_$timedOut; }; GetActiveConversation: INTERNAL PROC[serviceName: ROPE_NIL] RETURNS[convID: ConversationID_Thrush.nullConvID, cDesc: ConvDesc_NIL, state: StateInConv_$idle] = { <> IF serviceName=NIL THEN RETURN; -- Can't validate FOR convs: LIST OF ConvDesc _ info.conversations, convs.rest WHILE convs#NIL DO possibleCDesc: ConvDesc _ NARROW[convs.first]; possibleState: StateInConv _ possibleCDesc.situation.self.state; SELECT possibleState FROM $reserved => {cDesc _ possibleCDesc; LOOP;}; $initiating, $ringback, $active => NULL; ENDCASE => LOOP; IF possibleCDesc.partyInfo=NIL OR possibleCDesc.partyInfo.numParties<2 OR possibleCDesc.partyInfo[1].type#$service OR ~Rope.IsPrefix[serviceName, possibleCDesc.partyInfo[1].name, FALSE] THEN LOOP; <> cDesc _ possibleCDesc; EXIT; ENDLOOP; IF cDesc#NIL THEN { convID _ cDesc.situation.self.convID; state _ cDesc.situation.self.state; }; }; Problem: PUBLIC PROC[remark: ROPE_NIL, nb: NB_NIL] = { IF nb=NIL THEN VoiceUtils.Problem[remark, $Finch] ELSE VoiceUtils.ProblemFR["%g, reason: %g", $Finch, NIL, rope[remark], atom[nb]]; }; NewID: PROC RETURNS[LONG CARDINAL] ={RETURN[LOOPHOLE[PupSocket.GetUniqueID[]]]}; <> InitFinchSmarts: PUBLIC ENTRY PROC [thrushInstance: Thrush.ROPE_NIL, s: FinchSmarts.ReportSystemStateProc _ NIL, c: FinchSmarts.ReportConversationStateProc _ NIL, r: FinchSmarts.ReportRequestStateProc _ NIL, before: BOOL _ TRUE] = { problem: ROPE_NIL; nb: NB_$serverDown; { ENABLE { RPC.CallFailed => { problem _ "Communication Failure"; GOTO InitFailed; }; UNWIND => NULL; }; prevInfo: FinchSmarts.FinchInfo _ info; IF pd.connected THEN UninitFinchSmartsInt[]; info _ NEW[FinchSmarts.FinchInfoBody]; -- Dump any old one! IF prevInfo#NIL THEN NOTIFY prevInfo.pollCondition; info.conversations _ NIL; RegisterForReportsInt[s: ClearInfo]; RegisterForReportsInt[s, c, r, before]; thrushInstance _ VoiceUtils.MakeRName[style: rName, name: SELECT TRUE FROM thrushInstance#NIL => thrushInstance, prevInfo#NIL AND prevInfo.prevThrushInstance#NIL => prevInfo.prevThrushInstance, ENDCASE=> UserProfile.Token["ThrushClientServerInstance", "Strowger.Lark"]]; info.prevThrushInstance _ thrushInstance; info.myName _ [ type: "ThSmarts.Lark", instance: VoiceUtils.InstanceFromNetAddress[netAddress: VoiceUtils.OwnNetAddress[], suffix: "0"], version: ThVersions.FinchVR]; info.myRName _ VoiceUtils.CurrentRName[]; info.myPassword _ VoiceUtils.CurrentPasskey[]; ThSmartsRpcControl.UnexportInterface[!LupineRuntime.BindingError=>CONTINUE]; ThSmartsRpcControl.ExportInterface[ interfaceName: info.myName, user: info.myRName, password: info.myPassword]; info.shh _ IF NOT pd.encryptionRequested THEN Thrush.unencrypted ELSE NamesRPC.StartConversation [ caller: info.myRName, callee: thrushInstance, key: info.myPassword, level: --<>--CBCCheck ! RPC.AuthenticateFailed=> { problem_"Could not authenticate"; GOTO InitFailed}]; IF info.shh # Thrush.unencrypted THEN RPC.SetMaxTransmissions[info.shh, pd.maxTransmissions, pd.timeoutEnable]; TRUSTED { Process.Detach[FORK PollWaiter[info]]; }; nb _ FinchSmarts.FinchRegister[ info: info, interfaceName: [type: "ThParty.Lark", instance: thrushInstance]]; EXITS InitFailed => Problem[problem, nb]; };}; ClearInfo: ENTRY FinchSmarts.ReportSystemStateProc = { ENABLE UNWIND => NULL; IF connected OR info=NIL THEN RETURN; info.shh_none; FOR convs: LIST OF ConvDesc _ info.conversations, convs.rest WHILE convs#NIL DO convs.first.clientData _ NIL; convs.first.props _ NIL; -- probably unnecessary. ENDLOOP; info.conversations _ NIL; }; UninitFinchSmarts: PUBLIC ENTRY PROC = { UninitFinchSmartsInt[]; }; UninitFinchSmartsInt: INTERNAL PROC = { IF pd.enabled AND info=NIL THEN ERROR; IF info#NIL THEN [] _ FinchSmarts.FinchDeregister[info]; }; PollWaiter: ENTRY PROC[myInfo: FinchSmarts.FinchInfo] = { ENABLE UNWIND => NULL; nextSched: INT _ ThSmarts.noneScheduled; WHILE myInfo = info DO nSC: INT _ IF nextSched=ThSmarts.noneScheduled THEN pd.pollDefault ELSE nextSched; TRUSTED { Process.SetTimeout[@myInfo.pollCondition, Process.SecondsToTicks[nSC]]; }; WAIT info.pollCondition; IF myInfo#info THEN RETURN; IF info.nextScheduledCheck#0 THEN { nextSched _ info.nextScheduledCheck; info.nextScheduledCheck _ 0; LOOP; }; <> IF ~pd.enabled OR ~pd.doPollingTimeouts OR (pd.connected AND nextSched=ThSmarts.noneScheduled) THEN LOOP; <> [] _ ThParty.CheckIn[shh: info.shh, credentials: [ partyID: info.partyID, smartsID: info.smartsID, convID: Thrush.nullConvID]]; ENDLOOP; }; FinchIsRunning: PUBLIC PROC RETURNS [finchIsEnabled: BOOL, finchIsRunning: BOOL] = { RETURN[pd.enabled, pd.connected]; }; RegisterForReports: PUBLIC ENTRY PROC [s: FinchSmarts.ReportSystemStateProc _ NIL, c: FinchSmarts.ReportConversationStateProc _ NIL, r: FinchSmarts.ReportRequestStateProc _ NIL, before: BOOL _ TRUE] = { ENABLE UNWIND => NULL; RegisterForReportsInt[s, c, r, before]; }; RegisterForReportsInt: INTERNAL PROC [s: FinchSmarts.ReportSystemStateProc _ NIL, c: FinchSmarts.ReportConversationStateProc _ NIL, r: FinchSmarts.ReportRequestStateProc _ NIL, before: BOOL _ TRUE] = { IF info=NIL THEN RETURN; UnRegisterForReportsInt[s, c, r]; -- Multiple registrations do not hurt anything. <<Warning: This facility has the same risks as most registration mechanisms: running multiple versions of the client code can cause unintended multiple registrations.>> IF s # NIL THEN -- System state registrations are always treated as "before". info.systemStateSubscribers _ CONS[s, info.systemStateSubscribers]; IF before THEN { IF c # NIL THEN info.convStateSubscribers _ CONS[c, info.convStateSubscribers]; IF r # NIL THEN info.requestStateSubscribers _ CONS[r, info.requestStateSubscribers]; } ELSE { IF c # NIL THEN IF info.convStateSubscribers=NIL THEN info.convStateSubscribers _ LIST[c] ELSE FOR l: LIST OF FinchSmarts.ReportConversationStateProc _ info.convStateSubscribers, l.rest DO IF l.rest=NIL THEN { l.rest _ LIST[c]; EXIT }; ENDLOOP; IF r # NIL THEN IF info.requestStateSubscribers=NIL THEN info.requestStateSubscribers _ LIST[r] ELSE FOR l: LIST OF FinchSmarts.ReportRequestStateProc _ info.requestStateSubscribers, l.rest DO IF l.rest=NIL THEN { l.rest _ LIST[r]; EXIT }; ENDLOOP; }; }; UnRegisterForReports: PUBLIC ENTRY PROC [s: FinchSmarts.ReportSystemStateProc _ NIL, c: FinchSmarts.ReportConversationStateProc _ NIL, r: FinchSmarts.ReportRequestStateProc _ NIL] = { ENABLE UNWIND => NULL; UnRegisterForReportsInt[s, c, r]; }; UnRegisterForReportsInt: INTERNAL PROC [s: FinchSmarts.ReportSystemStateProc _ NIL, c: FinchSmarts.ReportConversationStateProc _ NIL, r: FinchSmarts.ReportRequestStateProc _ NIL] = { IF info=NIL THEN RETURN; IF s # NIL AND info.systemStateSubscribers # NIL THEN { prev: LIST OF FinchSmarts.ReportSystemStateProc _ NIL; FOR list: LIST OF FinchSmarts.ReportSystemStateProc _ info.systemStateSubscribers, list.rest UNTIL list=NIL DO IF list.first=s THEN { IF prev = NIL THEN info.systemStateSubscribers _ list.rest ELSE prev.rest _ list.rest; EXIT }; prev _ list; ENDLOOP; }; IF c # NIL AND info.convStateSubscribers # NIL THEN { prev: LIST OF FinchSmarts.ReportConversationStateProc _ NIL; FOR list: LIST OF FinchSmarts.ReportConversationStateProc _ info.convStateSubscribers, list.rest UNTIL list=NIL DO IF list.first=c THEN { IF prev = NIL THEN info.convStateSubscribers _ list.rest ELSE prev.rest _ list.rest; EXIT }; prev _ list; ENDLOOP; }; IF r # NIL AND info.requestStateSubscribers # NIL THEN { prev: LIST OF FinchSmarts.ReportRequestStateProc _ NIL; FOR list: LIST OF FinchSmarts.ReportRequestStateProc _ info.requestStateSubscribers, list.rest UNTIL list=NIL DO IF list.first=r THEN { IF prev = NIL THEN info.requestStateSubscribers _ list.rest ELSE prev.rest _ list.rest; EXIT }; prev _ list; ENDLOOP; }; }; <> <<>> <> ReportConversationState: PUBLIC PROC [nb: NB, cDesc: ConvDesc, remark: Rope.ROPE] ~ { IF info=NIL THEN RETURN; pd.requests.QueueClientAction[ReallyReportConversationState, NEW[CSBody _ [nb, cDesc, remark]]]; }; CS: TYPE = REF CSBody; CSBody: TYPE = RECORD[nb: NB, cDesc: ConvDesc, remark: Rope.ROPE]; ReallyReportConversationState: PROC[r: REF] = { cS: CS _ NARROW[r]; IF info=NIL THEN RETURN; FOR l: LIST OF FinchSmarts.ReportConversationStateProc _ info.convStateSubscribers, l.rest UNTIL l=NIL DO l.first[cS.nb, cS.cDesc, cS.remark]; ENDLOOP; }; ReportRequestState: PROC [cDesc: ConvDesc, actionReport: Thrush.ActionReport] ~ { IF info=NIL THEN RETURN; pd.requests.QueueClientAction[ReallyReportRequestState, NEW[RSBody _ [cDesc, actionReport]]]; }; RS: TYPE = REF RSBody; RSBody: TYPE = RECORD[cDesc: ConvDesc, actionReport: Thrush.ActionReport]; ReallyReportRequestState: PROC[r: REF] = { rS: RS _ NARROW[r]; actionRequest: REF_NIL; IF info=NIL THEN RETURN; FOR l: LIST OF FinchSmarts.ReportRequestStateProc _ info.requestStateSubscribers, l.rest UNTIL l=NIL DO actionRequest _ l.first[rS.cDesc, rS.actionReport, actionRequest]; ENDLOOP; }; <> ViewCmd: Commander.CommandProc = TRUSTED { Nice.View[pd, "Finch PD"]; }; SetFiltering: PROC[which: INT, deferAnswer: ROPE_ "true"] RETURNS [didIt: ATOM_NIL, message: Rope.ROPE_NIL] _ { ENABLE NameDB.Error => {didIt _ $Failure; message_"Database access failure"; CONTINUE; }; filtering _ which; NameDB.SetAttribute[VoiceUtils.CurrentRName[], $deferanswer, deferAnswer]; }; filtering: INT_0; FilterResetCmd: Commander.CommandProc = TRUSTED { [result, msg] _ SetFiltering[0, "false"]; }; FilterZeroCmd: Commander.CommandProc = TRUSTED { [result, msg] _ SetFiltering[0]; }; FilterOneCmd: Commander.CommandProc = TRUSTED { [result, msg] _ SetFiltering[1]; }; FilterTwoCmd: Commander.CommandProc = TRUSTED { [result, msg] _ SetFiltering[2]; }; FilterThreeCmd: Commander.CommandProc = TRUSTED { [result, msg] _ SetFiltering[3]; }; pd.requests _ MBQueue.Create[]; Commander.Register["VuFinch", ViewCmd, "Program Management variables Finch"]; Commander.Register["FReset", FilterResetCmd, "Reset filtration method. This is a test."]; Commander.Register["F0", FilterZeroCmd, "Zeroth filtration method. This is a test."]; Commander.Register["F1", FilterOneCmd, "First filtration method. This is a test."]; Commander.Register["F2", FilterTwoCmd, "Second filtration method. This is a test."]; Commander.Register["F3", FilterThreeCmd, "Third filtration method. This is a test."]; <<>> }. <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> < ServiceConnect, more rigid success requirements.>> <> <> <> <> <> <2 parties in conversation)>> <> <<>> <<>>