<> <> <> <> DIRECTORY Commander USING [ CommandProc, Register ], FinchSmarts, GList USING [ Member, Nconc ], IO, LarkFeepRpcControl USING [ Feep, ImportNewInterface ], LupineRuntime USING [ BindingError ], NamesGV USING [ GVGetAttribute, GVSetAttribute ], NamesGVImpExp USING [ GVImport, UnGVImport ], NamesRPC USING [ StartConversation ], Nice, PrincOpsUtils USING [ IsBound ], Process USING [ SecondsToTicks, SetTimeout, Ticks ], PupSocket USING [ GetUniqueID ], RefID USING [ ID ], RefQ USING [ Dequeue, Enqueue, Map, MapType, Queue ], Rope, RPC USING [ AuthenticateFailed, CallFailed, EncryptionKey, ImportFailed, VersionRange ], ThParty USING [ Advance, Alert, ConversationInfo, CreateConversation, Deregister, Enable, GetConversationInfo, GetKeyTable, GetParty, GetPartyFromNumber, GetPartyInfo, LookupServiceInterface, Register, RegisterKey, Unvisit, Visit ], ThPartyRpcControl, Thrush USING[ ActionReport, ConversationID, ConvEvent, Credentials, EncryptionKey, InterfaceSpec, NB, none, notReallyInConv, nullConvID, nullID, nullKey, PartyID, Reason, ROPE, SHHH, SmartsID, StateInConv, unencrypted ], ThSmarts, ThSmartsRpcControl, ThVersions USING [ GetThrushVR, FinchVersion, FinchVR ], UserProfile USING [ Token], VoiceTemp USING [ IntervalSpec, IntervalSpecBody, IntervalSpecs, nullTuneID, TuneID, VoiceTime ], VoiceTempRpcControl USING [ DescribeInterval, ImportNewInterface, Play, Record, Stop ], VoiceUtils USING [ CurrentPasskey, CurrentRName, InstanceFromNetAddress, MakeRName, NetAddress, NetAddressFromRope, OwnNetAddress, Problem, ProblemFR, Report ] ; FinchSmartsImpl: CEDAR MONITOR IMPORTS Commander, IO, FinchSmarts, GList, LarkFeepRpcControl, LupineRuntime, NamesGV, NamesGVImpExp, NamesRPC, Nice, PrincOpsUtils, Process, PupSocket, RefQ, Rope, RPC, ThParty, ThPartyRpcControl, ThSmartsRpcControl, ThVersions, UserProfile, VoiceTempRpcControl, 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; PD: TYPE = RECORD [ doReports: BOOL_FALSE, doNice: BOOL_FALSE, 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 ]; 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]; }; NconcIntervals: PROC[l1, l2: VoiceTemp.IntervalSpecs] RETURNS [VoiceTemp.IntervalSpecs] = INLINE { RETURN[NARROW[GList.Nconc[l1, l2]]]; }; <> <<>> 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; request: REF; cDesc: ConvDesc _ GetConv[report.self.convID, FALSE]; IF cDesc=NIL THEN { Problem["can't find conversation for report"]; RETURN; }; SELECT report.actionClass FROM $keyDistribution => { BROADCAST cDesc.keysMightBeDistributed; cDesc.keysDistributed _ TRUE; RETURN; }; $recording, $playback => { -- We expect these, add Prose soon FOR rqs: VoiceTemp.IntervalSpecs _ cDesc.pendingIntervals, rqs.rest WHILE rqs#NIL DO IF report.actionID#rqs.first.intID THEN LOOP; request _ rqs.first; SELECT report.actionType FROM <> $finished, $flushed => cDesc.pendingIntervals _ rqs.rest; ENDCASE; EXIT; ENDLOOP; }; ENDCASE=> shh _ shh; -- a place to stand during debugging IF info.ReportRequestState=NIL THEN RETURN; IF request = NIL THEN { Problem["missing reports"]; RETURN; }; info.ReportRequestState[report, request]; 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] = { ENABLE UNWIND => NULL; -- RestoreInvariant; cDesc: ConvDesc; state: StateInConv; IF NOT(([,cDesc, state]_FinchOn[convID]).on) THEN RETURN; SELECT state FROM $idle, $neverWas => { info.ReportConversationState[$convNotActive, NIL, "No conversation to disconnect"]; }; ENDCASE => []_Request[cDesc, $idle, 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 => { info.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]; }; <<>> PlaybackTune: PUBLIC ENTRY PROC [ convID: Thrush.ConversationID, intervalSpec: VoiceTemp.IntervalSpec, key: Thrush.EncryptionKey, queueIt: BOOL_TRUE, failOK: BOOL_FALSE -- playing is optional; leave connection open if tune doesn't exist. ] RETURNS [ started: BOOL_FALSE, newConvID: Thrush.ConversationID_Thrush.nullConvID ] = { -- FALSE if failed ENABLE UNWIND=>NULL; cDesc: ConvDesc; nb: NB; keyIndex: [0..17B]; IF intervalSpec.tuneID<=VoiceTemp.nullTuneID THEN { info.ReportConversationState[$convNotActive, NIL, "No such tune"]; RETURN; }; -- UGH! [nb,cDesc]_VoiceConnect[convID]; IF nb#$success THEN RETURN; -- Complain? newConvID _ cDesc.situation.self.convID; [nb, keyIndex] _ ThParty.RegisterKey[ shh: info.shh, credentials: cDesc.situation.self, key: key, reportNewKeys: TRUE]; {SELECT nb FROM $success => NULL; $newKeys => { <<A quandary. I hate these things that wait until something else has happened. Would rather queue up later tasks to do and return. But haven't come up with a general method yet. The problem here is that we don't want to schedule new voice until the encryption keys for it have been distributed. The Register key function will tell everybody about the keys and then tell us that it's told everybody. We wait one time around a timeout for a special condition variable (ugh) and then give up. >> cDesc.keysDistributed _ FALSE; WAIT cDesc.keysMightBeDistributed; IF ~cDesc.keysDistributed THEN GOTO Fail; }; <<$stateMismatch => ???;>> ENDCASE => GOTO Fail; EXITS Fail => { IF ~failOK THEN nb _ Request[cDesc, $failed, $error, "Could not encode encryption key"]; RETURN; }; }; intervalSpec _ NEW[VoiceTemp.IntervalSpecBody _ intervalSpec^]; intervalSpec.keyIndex _ keyIndex; intervalSpec.intID _ NewID[]; IF (nb_VoiceTempInterface[cDesc]) # $success THEN RETURN; -- sets interface & serviceID nb _ info.voiceTemp.Play[info.shh, cDesc.situation.self, cDesc.voiceTempID, intervalSpec, queueIt]; started _ nb=$success; IF ~started THEN RETURN; cDesc.pendingIntervals _ NconcIntervals[cDesc.pendingIntervals, LIST[intervalSpec]]; }; RecordTune: PUBLIC ENTRY PROC [ convID: Thrush.ConversationID, useIntervalSpec: VoiceTemp.IntervalSpec_NIL, useKey: Thrush.EncryptionKey, queueIt: BOOL_FALSE ] RETURNS[ nb: NB, intervalSpec: VoiceTemp.IntervalSpec_NIL, key: Thrush.EncryptionKey_ Thrush.nullKey, newConvID: Thrush.ConversationID_Thrush.nullConvID ] = { cDesc: ConvDesc; tuneID: VoiceTemp.TuneID; [nb,cDesc]_VoiceConnect[convID]; IF nb#$success THEN RETURN; -- Complain? newConvID _ cDesc.situation.self.convID; intervalSpec _ NEW[VoiceTemp.IntervalSpecBody _ []]; IF useIntervalSpec#NIL THEN intervalSpec^ _ useIntervalSpec^; intervalSpec.keyIndex _ 1; intervalSpec.intID _ NewID[]; IF (nb_VoiceTempInterface[cDesc]) # $success THEN RETURN; -- sets interface & serviceID [nb, tuneID] _ info.voiceTemp.Record[info.shh, cDesc.situation.self, cDesc.voiceTempID, intervalSpec, queueIt]; IF nb#$success THEN RETURN; intervalSpec.tuneID _ tuneID; cDesc.pendingIntervals _ NconcIntervals[cDesc.pendingIntervals, LIST[intervalSpec]]; WHILE cDesc.situation.self.state#idle AND GList.Member[intervalSpec,cDesc.pendingIntervals] DO WAIT info.stateChange; ENDLOOP; IF cDesc.keyTable=NIL THEN [nb, cDesc.keyTable] _ ThParty.GetKeyTable[info.shh, cDesc.situation.self]; IF nb=$success THEN RETURN[nb, intervalSpec, cDesc.keyTable[1], newConvID]; }; StopTune: PUBLIC ENTRY PROC [convID: Thrush.ConversationID] = { ENABLE UNWIND=>NULL; cDesc: ConvDesc; IF ([,cDesc]_VoiceConnect[convID]).nb#$success OR cDesc.situation.self.state#$active OR VoiceTempInterface[cDesc] # $success THEN RETURN; [] _ info.voiceTemp.Stop[info.shh, cDesc.situation.self, cDesc.voiceTempID]; }; DescribeInterval: PUBLIC ENTRY PROC [intervalSpec: VoiceTemp.IntervalSpec, minSilence: VoiceTemp.VoiceTime_1] RETURNS[nb: NB, length: INT, intervals: VoiceTemp.IntervalSpecs] = { ENABLE UNWIND=>NULL; IF (nb_VoiceTempInterface[NIL]) # $success THEN RETURN; [nb, length, intervals] _ info.voiceTemp.DescribeInterval[shhh: info.shh, targetInterval: intervalSpec, minSilence: minSilence, credentials: [], serviceID: nullID]; }; IdentifyVisitor: PUBLIC ENTRY PROC [visitor: Rope.ROPE] ~ { nb: NB; visitingPartyID: PartyID; [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]; IF nb#$success THEN RETURN; info.visitors _ CONS[visitor, info.visitors]; }; ReleaseVisitor: PUBLIC ENTRY PROC [visitor: Rope.ROPE] ~ { <> nb: NB; found: BOOL_FALSE; visitingPartyID: PartyID; [nb, visitingPartyID] _ThParty.GetParty[shh:info.shh, partyID:info.partyID, rName:visitor]; FOR v: LIST OF Rope.ROPE _ info.visitors, v.rest WHILE v#NIL DO IF v.first.Equal[visitor, FALSE] THEN { found_TRUE; info.visitors _ v.rest; } ELSE IF v.rest#NIL AND v.rest.first.Equal[visitor, FALSE] THEN { v.rest _ v.rest.rest; found_TRUE; }; ENDLOOP; IF nb=$success AND found THEN [] _ ThParty.Unvisit[shh: info.shh, visitingParty: visitingPartyID]; }; <> GetConv: PUBLIC PROC[convID: ConversationID, createOK: BOOL_FALSE] RETURNS [ cDesc: ConvDesc_NIL ] = --INLINE-- { IsConv: RefQ.MapType = { cDesc _ NARROW[subqueue.first]; IF cDesc.situation.self.convID = convID THEN RETURN[TRUE]; }; IF convID#nullConvID AND RefQ.Map[info.conversations, IsConv] THEN RETURN; IF ~createOK THEN RETURN; <> cDesc _ NEW[FinchSmarts.ConvDescBody_[ situation: [ self: [ convID: convID, smartsID: info.smartsID, partyID: info.partyID ]]]]; TRUSTED { Process.SetTimeout[@cDesc.keysMightBeDistributed, pd.keyDistTicks]; }; info.conversations _ RefQ.Enqueue[info.conversations, cDesc]; }; 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 _ RefQ.Dequeue[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. >> info.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! -- info.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 => { info.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 TRUSTED { Process.SetTimeout[@info.stateChange, pd.noJayTicks]; }; WAIT info.stateChange; -- <> SELECT cDesc.situation.self.state FROM $active => RETURN; $idle, $neverWas, $failed => EXIT; ENDCASE; ENDLOOP; nb_Request[cDesc, $failed, $error, "Finch failed to connect to voice service."]; IF nb=$success THEN nb_$timedOut; }; VoiceConnect: INTERNAL PROC[convID: ConversationID_nullConvID] RETURNS [ nb: NB_$success, cDesc: ConvDesc_NIL, state: StateInConv_idle ] = { calledPartyID: PartyID; IF NOT (([,cDesc, state]_FinchOn[convID]).on) THEN { Problem["Your Finch is not running"]; RETURN; }; 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: "recording", type: $service]; IF nb=$success THEN [nb, cDesc] _ Connect[calledPartyID, convID, NIL, NIL] }; ENDCASE; IF nb=$success THEN nb _ WaitForActive[cDesc]; }; GetActiveConversation: PROC RETURNS[convID: ConversationID_Thrush.nullConvID, cDesc: ConvDesc_NIL, state: StateInConv_$idle] = { <> FOR convs: LIST OF REF _ 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]]; }; VoiceTempInterface: PROC[cDesc: ConvDesc] RETURNS [nb: NB_$success] = { partyID: Thrush.PartyID_Thrush.nullID; interfaceSpec: Thrush.InterfaceSpec; credentials: Thrush.Credentials; IF cDesc#NIL THEN { IF cDesc.voiceTempID#nullID THEN RETURN; credentials _ cDesc.situation.self; IF cDesc.numParties>1 THEN partyID _ cDesc.partyInfo[1].partyID; } ELSE { IF info.voiceTemp#NIL THEN RETURN; -- want interface only credentials _ [info.partyID, info.smartsID]; [nb, partyID] _ThParty.GetParty[ shh: info.shh, partyID: info.partyID, rName: "recording", type: $service]; IF nb#$success THEN RETURN; }; IF partyID = Thrush.nullID THEN RETURN[$noSuchParty2]; [nb, interfaceSpec] _ ThParty.LookupServiceInterface[ info.shh, credentials, partyID, "VoiceTemp"]; IF nb#$success THEN RETURN; info.voiceTemp _ VoiceTempRpcControl.ImportNewInterface[ interfaceName: interfaceSpec.interfaceName, hostHint: interfaceSpec.hostHint!RPC.ImportFailed => CONTINUE ]; IF info.voiceTemp=NIL THEN RETURN[$noVoiceTempInterface]; IF cDesc#NIL THEN cDesc.voiceTempID _ interfaceSpec.serviceID; }; NewID: PROC RETURNS[LONG CARDINAL] ={RETURN[LOOPHOLE[PupSocket.GetUniqueID[]]]}; <> InitFinchSmarts: PUBLIC PROC [ thrushInstance: Thrush.ROPE_NIL, ReportSystemState: PROC[ on: BOOL ], ReportConversationState: PROC[ nb: NB, cDesc: ConvDesc, remark: Rope.ROPE ], ReportRequestState: PROC[ actionReport: Thrush.ActionReport, actionRequest: REF ] ] = { 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.conversations _ NIL; info.ReportSystemState_ReportSystemState; info.ReportConversationState_ReportConversationState; info.ReportRequestState_ReportRequestState; thrushInstance _ VoiceUtils.MakeRName[style: rName, name: IF thrushInstance#NIL THEN thrushInstance ELSE UserProfile.Token[key: "ThrushClientServerInstance", default: "Strowger.Lark"]]; 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; info.ReportSystemState[pd.finchOn]; 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; info.ReportSystemState[FALSE]; { ClearConvs: RefQ.MapType = { cDesc: ConvDesc=NARROW[subqueue.first]; cDesc.clientData _ NIL; }; []_RefQ.Map[info.conversations, ClearConvs]; 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; }; FinchIsRunning: PUBLIC PROC RETURNS [finchIsRunning: BOOL] = { RETURN[pd.finchOn]; }; <> 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."]; <<>> <> FinchSmarts.Register[NEW[FinchSmarts.ProcsRecord _ [ playbackTune: PlaybackTune, recordTune: RecordTune, stopTune: StopTune, describeInterval: DescribeInterval, <> <> <> finchIsRunning: FinchIsRunning ]]]; }. <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <<>> <> <> <> <> <> <> <> <> <<>> <> <> <> <<>> <> <> <> <<>> <> <> <> <> <> <> <<>>