<> <> <> <> <> DIRECTORY Commander USING [ CommandProc, Register ], FinchSmarts, GList, GVBasics USING [ MakeKey, Password ], IO, LarkFeepRpcControl USING [ Feep, ImportNewInterface ], LupineRuntime USING [ BindingError ], MBQueue USING [ Create, QueueClientAction ], NamesGV USING [ GVGetAttribute, GVSetAttribute ], NamesGVImpExp USING [ GVImport, UnGVImport ], NamesRPC USING [ StartConversation ], Nice, PrincOpsUtils USING [ IsBound ], Process USING [ Detach, SecondsToTicks, SetTimeout, Ticks ], PupSocket USING [ GetUniqueID ], RefID USING [ ID ], Rope, RPC USING [ AuthenticateFailed, CallFailed, EncryptionKey, ImportFailed, VersionRange ], ThParty USING [ Advance, Alert, CheckIn, ConversationInfo, CreateConversation, Deregister, DescribeParty, Enable, GetConversationInfo, GetParty, GetNumbersForRName, GetPartyFromNumber, GetPartyInfo, LookupServiceInterface, Register, Unvisit, Visit ], ThPartyRpcControl, Thrush USING[ ActionReport, ConversationID, ConvEvent, Credentials, InterfaceSpec, NB, none, notReallyInConv, nullConvID, nullID, PartyID, Reason, ROPE, SHHH, SmartsID, StateInConv, unencrypted ], ThSmarts, ThSmartsRpcControl, ThVersions USING [ GetThrushVR, FinchVersion, FinchVR ], UserProfile USING [ Token], VoiceUtils USING [ CurrentPasskey, CurrentPassword, CurrentRName, InstanceFromNetAddress, MakeRName, NetAddress, NetAddressFromRope, OwnNetAddress, Problem, ProblemFR, Registrize, Report, ReportFR ] ; FinchSmartsImpl: CEDAR MONITOR IMPORTS Commander, GList, GVBasics, IO, LarkFeepRpcControl, LupineRuntime, MBQueue, NamesGV, NamesGVImpExp, NamesRPC, Nice, PrincOpsUtils, Process, PupSocket, Rope, RPC, ThParty, ThPartyRpcControl, 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; 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]; finchPollerWait: CONDITION; PD: TYPE = RECORD [ doReports: BOOL_FALSE, doNice: BOOL_FALSE, timeoutPoller: INTEGER _ 15, timeoutNoAction: INTEGER _ 30, timeoutJayConnect: INTEGER _ 1, noJayTicks: Process.Ticks _ Process.SecondsToTicks[1], keyDistTicks: Process.Ticks _ Process.SecondsToTicks[10], encryptionRequested: BOOLEAN_TRUE, interfacesAreImported: BOOLEAN_FALSE, smartsIsExported: BOOLEAN_FALSE, waitsForConnect: NAT_6, queueIt: BOOL_FALSE, finchOn: BOOLEAN_FALSE, maxProseLength: INT_1000, <> systemStateSubscribers: LIST OF FinchSmarts.ReportSystemStateProc, convStateSubscribers: LIST OF FinchSmarts.ReportConversationStateProc, requestStateSubscribers: LIST OF FinchSmarts.ReportRequestStateProc ]; info: FinchInfo; pd: REF PD _ NEW[PD_[]]; 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] = INLINE { RETURN[NARROW[GList.Nconc[l1, l2]]]; }; 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 l1.rest#NIL DO IF l1.rest.first=c THEN { l1.rest _ l1.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.>> 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!)} }; <> <<>> 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; [,cDesc, state]_FinchOn[convID]; 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; RPC.CallFailed => { UninitFinchSmarts["Communication Failure"]; CONTINUE; }; }; 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; 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] = { cDesc: ConvDesc; nb: Thrush.NB_$success; state: StateInConv; IF NOT(([,cDesc, state]_FinchOn[convID]).on) OR state#$active THEN nb _ $convNotActive; IF nb=$success AND cDesc.feepInterface=NIL THEN { IF cDesc.numParties>1 THEN [nb, cDesc.feepInterfaceSpec] _ ThParty.LookupServiceInterface[ info.shh, cDesc.situation.self, cDesc.partyInfo[1].partyID, "LarkFeep"] ELSE nb_$noFeepingParty; IF nb=$success THEN 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]; }; <<>> GetNumbersForRName: 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] ~ { <<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 RETURN; 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]]; ENDCASE; <<Need to handle noSuchParty & noSuchParty2?>> }; ReleaseVisitor: PUBLIC ENTRY PROC [visitor, password: Rope.ROPE] ~ { 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]; IF nb=$success THEN { VoiceUtils.ReportFR["Visiting cancelled for %s.", $Finch, NIL, IO.rope[visitor]]; RETURN; }; }; }; VoiceUtils.ReportFR["%s was not visiting here.", $Finch, NIL, IO.rope[visitor]]; }; -- Correct error management ReleaseVisitorSelf: INTERNAL PROC ~ { nb: NB; visitee: PartyID; [visitee: visitee] _ ThParty.DescribeParty[partyID: info.partyID, nameReq: $none]; nb _ ThParty.Unvisit[shh: info.shh, visitedParty: visitee, visitingParty: info.partyID, visitingPassword: VoiceUtils.CurrentPassword[]]; VoiceUtils.ReportFR[ IF nb=$success THEN "Visiting cancelled for %s. Welcome home." ELSE "%s was not visiting elsewhere.", $Finch, NIL, IO.rope[info.myRName] ]; }; VoiceConnect: PUBLIC ENTRY PROC[ serviceName: ROPE, convID: ConversationID_nullConvID ] RETURNS [ nb: NB_$success, cDesc: ConvDesc_NIL ] = { ENABLE UNWIND=>NULL; state: StateInConv; calledPartyID: PartyID; IF NOT (([,cDesc, state]_FinchOn[convID]).on) THEN { Problem["Your Finch is not running"]; RETURN[nb: $finchNotRunning]; }; IF cDesc=NIL THEN [convID, cDesc, state] _ GetActiveConversation[]; -- Hack SELECT state FROM idle, reserved => { [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 nb=$success THEN nb _ WaitForActive[cDesc]; }; <<>> LookupServiceInterface: 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 { -- 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: PUBLIC 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.finchOn 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; 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 => 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, cDesc.partyInfo] _ ThParty.GetPartyInfo[shh: info.shh, credentials: cDesc.situation.self, nameReq: $description, allParties: TRUE]; IF nb#$success THEN Problem["Can't obtain conversation information", nb] ELSE IF cDesc.numParties>=1 THEN cDesc.whoOriginated _ IF cDesc.partyInfo[0].partyID=cInfo.originator THEN $us ELSE $them; }; ENDCASE; SELECT cDesc.situation.self.state FROM $idle, $neverWas => info.conversations _ DequeueConv[info.conversations, 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, $active, $inactive, $ringing => TRUE, ENDCASE=>FALSE; [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 => -- Some cases are fatal, requiring re-registration! -- 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 =IF reason=NIL THEN $initiating ELSE $failed; convEvent: Thrush.ConvEvent; IF ~(([,cDesc, state]_FinchOn[convID]).on) THEN { nb _ $finchInactive; 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]; }; $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; 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; }; ENDCASE => { -- Do something interesting  see other smarts for more confusion -- ERROR; }; }; 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, pd.noJayTicks]; }; 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: PROC RETURNS[convID: ConversationID_Thrush.nullConvID, cDesc: ConvDesc_NIL, state: StateInConv_$idle] = { <> FOR convs: LIST OF ConvDesc _ info.conversations, convs.rest WHILE convs#NIL DO cDesc _ NARROW[convs.first]; IF cDesc.situation.self.state > Thrush.notReallyInConv THEN { convID _ cDesc.situation.self.convID; state _ cDesc.situation.self.state; RETURN; } ENDLOOP; }; Problem: 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 PROC [thrushInstance: Thrush.ROPE_NIL] = { InitFinchSmartsDo[thrushInstance]; IF pd.finchOn THEN StartServerPoller[]; }; InitFinchSmartsDo: PROC [thrushInstance: Thrush.ROPE_NIL] = { problem: ROPE_NIL; nb: NB; { ENABLE RPC.CallFailed => { problem _ "Communication Failure"; GOTO InitFailed; }; credentials: Thrush.Credentials; thVR: RPC.VersionRange = ThVersions.GetThrushVR; problem: Thrush.ROPE _ NIL; namesGVInstance: Thrush.ROPE_NIL; hostHint: VoiceUtils.NetAddress; UninitFinchSmarts[NIL]; info _ NEW[FinchSmarts.FinchInfoBody]; -- Dump any old one! info.requests _ MBQueue.Create[]; info.conversations _ NIL; thrushInstance _ VoiceUtils.MakeRName[style: rName, name: IF thrushInstance#NIL THEN thrushInstance ELSE UserProfile.Token[key: "ThrushClientServerInstance", default: "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.ExportInterface[ interfaceName: info.myName, user: info.myRName, password: info.myPassword]; pd.smartsIsExported_TRUE; <> namesGVInstance _ UserProfile.Token[key: "NamesGVInstance", default: "Strowger.lark"]; IF ~(PrincOpsUtils.IsBound[LOOPHOLE[NamesGVImpExp.GVImport]] AND NamesGVImpExp.GVImport[namesGVInstance]) THEN { problem _ "Couldn't import Grapevine Package"; GOTO InitFailed; }; 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}]; hostHint _ VoiceUtils.NetAddressFromRope[NamesGV.GVGetAttribute[thrushInstance, $connect, NIL]]; ThPartyRpcControl.ImportInterface[ interfaceName: [type: "ThParty.Lark", instance: thrushInstance, version: thVR], hostHint: hostHint! RPC.ImportFailed=> { IF why=wrongVersion THEN problem _ IO.PutFR["Finch version %d too old; import failed", card[ThVersions.FinchVersion]]; GOTO InitFailed; }]; pd.interfacesAreImported_TRUE; [nb, credentials]_ThParty.Register[ shh: info.shh, rName: info.myRName, type: $individual, interface: info.myName, properties: [$controller, VoiceUtils.OwnNetAddress[]] ]; IF nb=$success THEN nb _ ThParty.Enable[shh: info.shh, smartsID: credentials.smartsID]; IF nb#$success THEN { problem_"Can't register with server"; GOTO InitFailed; }; info.smartsID _ credentials.smartsID; info.partyID _ credentials.partyID; <<ThParty.ConversationsForParty[shh: info.shh, partyID: partyID];>> pd.finchOn_TRUE; ReportSystemState[TRUE]; EXITS InitFailed => UninitFinchSmarts[problem, nb]; };}; UninitFinchSmarts: PUBLIC PROC[problem: ROPE_NIL, nb: NB_NIL] = { ENABLE RPC.CallFailed => GOTO Failed; IF problem#NIL THEN Problem[problem, nb]; IF info#NIL THEN { IF pd.finchOn AND info.partyID#nullID AND info.smartsID#nullID THEN [-- nb --]_ThParty.Deregister[info.shh, info.smartsID!RPC.CallFailed=>CONTINUE]; info.shh_none; ReportSystemState[FALSE]; FOR convs: LIST OF ConvDesc _ info.conversations, convs.rest WHILE convs#NIL DO convs.first.clientData _ NIL; ENDLOOP; info.conversations _ NIL; }; pd.finchOn_FALSE; IF PrincOpsUtils.IsBound[LOOPHOLE[NamesGVImpExp.UnGVImport]] THEN NamesGVImpExp.UnGVImport[]; pd.interfacesAreImported_FALSE; IF pd.smartsIsExported THEN ThSmartsRpcControl.UnexportInterface[!LupineRuntime.BindingError=>CONTINUE]; pd.smartsIsExported_FALSE; EXITS Failed => pd.interfacesAreImported _ pd.smartsIsExported _ FALSE; }; StartServerPoller: PUBLIC PROC = TRUSTED { Process.Detach[FORK PollServer[]]; }; PollServer: ENTRY PROC = { ENABLE { UNWIND=>NULL; -- RestoreInvariant; RPC.CallFailed => GOTO PollFailed; }; nb: NB; credentials: Thrush.Credentials _ [partyID: info.partyID, smartsID: info.smartsID, convID: Thrush.nullConvID]; TRUSTED {Process.SetTimeout[@finchPollerWait, Process.SecondsToTicks[pd.timeoutPoller]];}; WAIT finchPollerWait; nb _ ThParty.CheckIn[shh: info.shh, credentials: credentials]; IF nb#$success THEN GOTO PollFailed; EXITS PollFailed => InitFinchSmartsDo[info.prevThrushInstance]; }; FinchIsRunning: PUBLIC PROC RETURNS [finchIsRunning: BOOL] = { RETURN[pd.finchOn]; }; RegisterForReports: PUBLIC ENTRY PROC [s: FinchSmarts.ReportSystemStateProc _ NIL, c: FinchSmarts.ReportConversationStateProc _ NIL, r: FinchSmarts.ReportRequestStateProc _ NIL, before: BOOL _ TRUE] = { 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 before THEN { IF s # NIL THEN pd.systemStateSubscribers _ CONS[s, pd.systemStateSubscribers]; IF c # NIL THEN pd.convStateSubscribers _ CONS[c, pd.convStateSubscribers]; IF r # NIL THEN pd.requestStateSubscribers _ CONS[r, pd.requestStateSubscribers]; } ELSE { IF s # NIL THEN IF pd.systemStateSubscribers=NIL THEN pd.systemStateSubscribers _ LIST[s] ELSE FOR l: LIST OF FinchSmarts.ReportSystemStateProc _ pd.systemStateSubscribers, l.rest DO IF l.rest=NIL THEN { l.rest _ LIST[s]; EXIT }; ENDLOOP; IF c # NIL THEN IF pd.convStateSubscribers=NIL THEN pd.convStateSubscribers _ LIST[c] ELSE FOR l: LIST OF FinchSmarts.ReportConversationStateProc _ pd.convStateSubscribers, l.rest DO IF l.rest=NIL THEN { l.rest _ LIST[c]; EXIT }; ENDLOOP; IF r # NIL THEN IF pd.requestStateSubscribers=NIL THEN pd.requestStateSubscribers _ LIST[r] ELSE FOR l: LIST OF FinchSmarts.ReportRequestStateProc _ pd.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] = { UnRegisterForReportsInt[s, c, r]; }; UnRegisterForReportsInt: INTERNAL PROC [s: FinchSmarts.ReportSystemStateProc _ NIL, c: FinchSmarts.ReportConversationStateProc _ NIL, r: FinchSmarts.ReportRequestStateProc _ NIL] = { IF s # NIL AND pd.systemStateSubscribers # NIL THEN { prev: LIST OF FinchSmarts.ReportSystemStateProc _ NIL; FOR list: LIST OF FinchSmarts.ReportSystemStateProc _ pd.systemStateSubscribers, list.rest UNTIL list=NIL DO IF list.first=s THEN { IF prev = NIL THEN pd.systemStateSubscribers _ list.rest ELSE prev.rest _ list.rest; EXIT }; prev _ list; ENDLOOP; }; IF c # NIL AND pd.convStateSubscribers # NIL THEN { prev: LIST OF FinchSmarts.ReportConversationStateProc _ NIL; FOR list: LIST OF FinchSmarts.ReportConversationStateProc _ pd.convStateSubscribers, list.rest UNTIL list=NIL DO IF list.first=c THEN { IF prev = NIL THEN pd.convStateSubscribers _ list.rest ELSE prev.rest _ list.rest; EXIT }; prev _ list; ENDLOOP; }; IF r # NIL AND pd.requestStateSubscribers # NIL THEN { prev: LIST OF FinchSmarts.ReportRequestStateProc _ NIL; FOR list: LIST OF FinchSmarts.ReportRequestStateProc _ pd.requestStateSubscribers, list.rest UNTIL list=NIL DO IF list.first=r THEN { IF prev = NIL THEN pd.requestStateSubscribers _ list.rest ELSE prev.rest _ list.rest; EXIT }; prev _ list; ENDLOOP; }; }; ReportSystemState: PROC [on: BOOL] ~ { FOR l: LIST OF FinchSmarts.ReportSystemStateProc _ pd.systemStateSubscribers, l.rest UNTIL l=NIL DO l.first[on]; ENDLOOP; }; ReportConversationState: PUBLIC PROC [nb: NB, cDesc: ConvDesc, remark: Rope.ROPE] ~ { <> info.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]; FOR l: LIST OF FinchSmarts.ReportConversationStateProc _ pd.convStateSubscribers, l.rest UNTIL l=NIL DO l.first[cS.nb, cS.cDesc, cS.remark]; ENDLOOP; }; ReportRequestState: PROC [cDesc: ConvDesc, actionReport: Thrush.ActionReport] ~ { actionRequest: REF_NIL; FOR l: LIST OF FinchSmarts.ReportRequestStateProc _ pd.requestStateSubscribers, l.rest UNTIL l=NIL DO actionRequest _ l.first[cDesc, actionReport, actionRequest]; ENDLOOP; }; <> ViewCmd: Commander.CommandProc = TRUSTED { Nice.View[pd, "Finch PD"]; }; SetFiltering: PROC[which: INT, deferAnswer: ROPE_ "true"] _ { filtering _ which; NamesGV.GVSetAttribute[ VoiceUtils.MakeRName[VoiceUtils.CurrentRName[]], $deferanswer, deferAnswer]; }; filtering: INT_0; FilterResetCmd: Commander.CommandProc = TRUSTED { SetFiltering[0, "false"]; }; FilterZeroCmd: Commander.CommandProc = TRUSTED { SetFiltering[0]; }; FilterOneCmd: Commander.CommandProc = TRUSTED { SetFiltering[1]; }; FilterTwoCmd: Commander.CommandProc = TRUSTED { SetFiltering[2]; }; FilterThreeCmd: Commander.CommandProc = TRUSTED { SetFiltering[3]; }; 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."]; <<>> }. <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <>