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; 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 => { cDesc.keysDistributed _ FALSE; WAIT cDesc.keysMightBeDistributed; IF ~cDesc.keysDistributed THEN GOTO Fail; }; 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; 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"]; 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"]; 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; 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 ]]]; }. FinchSmartsImpl.mesa Copyright c 1984, 1986 by Xerox Corporation. All rights reserved. Last Edited by: Swinehart, August 14, 1986 11:32:31 pm PDT Polle Zellweger (PTZ) October 24, 1985 4:55:57 pm PDT Declarations Supervision Some party has changed state in a conversation we know about (or should). Two cases: Our party has changed state in a conversation, whether or not we're going to deal with it (at present, LarkSmarts makes all such decisions) Another party changed state in a conversation we know about (at present, we don't need to know that; again, LarkSmarts does all the work.) That case is filtered out in Progress, above. It is our duty to obtain all relevant information about the call when the information becomes available, and to notify any Finch Clients. One party has replaced another (they were or are now in a poacher/poachee relationship) in this conversation, so we need to update our information about the conversation. Same as Progress, except that we clear any indication that we know anything about the present state before noting the new state. 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. Throw away pending intervals that are complete. This includes intervals preceding the one being reported on. Client Functions 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.  $stateMismatch => ???; Don't unvisit unless visitor is in your list. This isn't good enough. Utilities New conversation Now gather up additional information that we might not know yet: Who started this? Who the other party(s) is (are) State information guaranteed correct only as of last time one of these numbers changed. 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.  Real confused about Problem (typeout only) reporting vs. client reporting. The responsibilities are unconvincingly distributed among Smarts and client. If there's a reason, the connect request's only purpose is to record an error condition, audibly as well as visually. This requires the creation of a conversation, then its immediate termination. Some smarts don't do this. They probably should. For use in Recording, and so on, where client needs to wait for setup. Hack. Won't work when lots of conversations are the norm Registration info.requests _ MBQueue.Create[]; For the purpose of importing NamesGV, use prior binding of serverInstance if there is one and the server name is the same as before. ThParty.ConversationsForParty[shh: info.shh, partyID: partyID]; Debugging nonsense Registration for use by arms-length systems textToSpeech: TextToSpeech, registerTranslateProc: RegisterTranslateProc, stopSpeech: StopSpeech, Swinehart, May 22, 1985 1:08:51 pm PDT Changes to GetParty. changes to: JayConnection Polle Zellweger (PTZ) July 13, 1985 5:20:26 pm PDT adding Text-to-Speech server changes to: DIRECTORY, ProseSpec, ProseSpecs, stopIntervalSpec (was stopSpec), stopProseSpec, Progress, Supervise, DisconnectCall, PlaceCall, StopSpeech, TextToSpeech, InitFinchSmarts, GetConv, ReportProses, EnqueueProses, Complain, Connect, ProseConnection Swinehart, August 6, 1985 5:43:08 pm PDT Merge PTZ prose changes changes to: DIRECTORY, NB, PD, stopIntervalSpec, stopProseSpec, Report, Progress, Supervise, DisconnectCall, PlaceCall, StopTune, NoiseSpec, PlaybackTune, TextToSpeech, InitFinchSmarts, GetConv, ReportProses, CompareIntID, EnqueueProses, Complain, Connect, JayConnection, ProseConnection, RepRet, FinchSmarts Polle Zellweger (PTZ) August 19, 1985 4:16:40 pm PDT Meter text in TextToSpeech so as to avoid sending large ropes all at once. Allows speech to begin sooner and flush faster. Also flushing changes. changes to: FinchInfo, PD, Supervise, TextToSpeech, StopSpeech, ReportProses (comments only) Polle Zellweger (PTZ) September 3, 1985 6:28:29 pm PDT Allow registration of defaultTranslateProc. changes to: Supervise, defaultTranslateProc, RegisterTranslateProc, TextToSpeech, FinchSmarts Swinehart, September 16, 1985 10:00:59 am PDT If Finch has never been initialized, info is NIL -- don't let that bother you. changes to: GetRname, PlayNoise, FinchOn Polle Zellweger (PTZ) October 22, 1985 5:10:44 pm PDT changes to: Progress (add prose debugging reports), TextToSpeech (report connection failure), ReportProses (fix debugging reports), JayConnection (report connection failure), ProseConnection (report connection failure) Polle Zellweger (PTZ) October 24, 1985 4:53:21 pm PDT Move bluejayConnection and proseConnection from FinchInfo to ConvDesc. changes to: Supervise, DisconnectCall, InitFinchSmarts, Connect, JayConnection, ProseConnection Swinehart, October 28, 1985 12:31:20 pm PST Merge Above changes with other other minor changes (RefQ and the like) changes to: DIRECTORY, FinchSmartsImpl, Progress, Supervise, DisconnectCall, TextToSpeech, InitFinchSmarts, UninitFinchSmarts, IsConv, GetConv, IsConv (local of GetConvDesc), GetConvDesc, ReportProses, FinchOn, Connect, JayConnection, ProseConnection, PlaybackTune, ClearConvs (local of UninitFinchSmarts) Swinehart, December 13, 1985 2:52:25 pm PST Massive change to new Thrush, leaving out proses and intervals for the moment. changes to: ConversationHandle, FinchInfo, Progress, Supervise, DisconnectCall, AnswerCall, PlaceCall, Apprise, Complain, Connect, Problem, FinchSmarts, Progress, QdProgress Swinehart, December 17, 1985 10:05:46 am PST Major revision for new Thrush. Removed all Prose and Bluejay stuff temporarily changes to: QdProgress, NoteNewState, PutFTime, Connect, Problem, Progress, Other, Supervise Swinehart, May 19, 1986 12:22:17 pm PDT Cedar 6.1 changes to: DIRECTORY, SHHH Swinehart, June 1, 1986 7:48:10 pm PDT Add ReportAction, eliminate the MBQueueing of ThParty-provoked actions. Others remain, but should go. changes to: Progress, Substitution, ReportAction Κ“˜Jšœ™šœ Οmœ7™BJšœ:™:Icodešœ5™5—J˜šΟk ˜ Jšœ žœ˜*Jšœ ˜ Jšœžœ˜Jšžœ˜Jšœžœ˜6Jšœžœ˜%Jšœžœ$˜1Jšœžœ˜-Jšœ žœ˜%J˜J˜Jšœžœ ˜ Jšœžœ'˜4Jšœ žœ˜ Jšœžœžœ˜Jšœžœ+˜5Jšœ˜JšžœžœO˜XJšœžœΫ˜θJšœ˜šœžœ˜ JšœTžœGžœžœ'˜Ξ—J˜ Jšœ˜J˜8Jšœ žœ ˜Jšœa˜aJšœW˜WJšœ žœ˜ŸJ˜J˜—šœžœž˜Jšžœ žœžœj˜’Jšžœ˜!Jšžœžœ˜J˜—™ J˜Jšœ žœ˜&šœžœ˜-J˜/—Jšœ žœ˜(Jšžœžœ žœ˜Jšœžœ˜J˜!Jšœ žœ˜Jšžœžœ žœ˜Jšžœžœ žœ˜Jšœ žœ˜!Jšœ žœ˜'J˜šžœžœžœ˜Jšœ žœžœ˜Jšœžœžœ˜Jšœžœ˜Jšœžœ˜Jšœ6˜6J˜9Jšœžœžœ˜"Jšœžœžœ˜%Jšœžœžœ˜ Jšœžœ˜Jšœ žœžœ˜Jšœ žœž˜Jšœžœ˜J˜—J˜Jšœ˜Jš œžœžœžœžœ˜J˜šΟnœžœžœ˜Jšžœžœžœžœ˜ J˜ J˜—J˜šŸœžœžœžœ˜ Jšžœžœ žœžœ˜Jšœžœ˜J˜J˜—š Ÿœžœ"žœžœžœžœ˜ŠJ˜——™ J™šŸœžœžœžœ˜Jšœžœ˜ Jšœ˜Jšœ˜KšžœžœžœΟc˜-J™I™ J™‹J™ΉJ™‰—Jšžœ2žœ˜VJ˜J˜—šŸ œžœžœžœ˜ Jšœžœ˜ Jšœ˜J˜J˜Jšœ˜Kšžœžœžœ ˜-J™¬J™΅Jšœ1žœ˜8Jšžœžœžœ *˜RJšœ˜J˜J˜—šŸ œžœžœžœ˜ Jšœžœ˜ Jšœ˜Jšœ˜Kšžœžœžœ˜Kšœ žœ˜ Kšœ.žœ˜5Kšžœžœžœ2žœ˜Mšžœž˜Kšœž œ7žœžœ˜fšœ "˜=šžœAžœžœž˜TKšžœ!žœžœ˜-K˜šžœž˜K™mKšœ:žœ˜B—Kšžœ˜Kšžœ˜—K˜—Kšžœ $˜9—Kšžœžœžœžœ˜+Jšžœ žœžœžœ˜>Jšœ)˜)Jšž œ  œ˜=Jšœ˜J˜——™J™Jšœ žœžœ (˜Kšœžœžœ˜Jšœ˜J˜%Jšœžœ˜Jšœ žœž˜J˜J˜—šŸœžœžœžœ˜"Jšœ˜J˜%Jšœ žœžœ˜Kšžœžœžœ ˜-J˜$Jšžœžœ'žœžœ˜9šžœž˜šœ˜Jšœ-žœ#˜SJ˜—Jšž œ*˜5—J˜J˜—šŸ œžœžœžœ#˜@Kšžœžœžœ ˜-J˜$Jšœ ˜ Jšžœžœ2žœ˜MJšœ˜J˜—šŸ œžœžœžœ˜Jšœ)˜)Jšœžœ ˜$Jšœžœ ˜-Jšœ žœž˜Jšœ˜šžœ˜Jšžœžœ ˜$Jšžœ?žœ˜NJšœ˜—Jšœ˜Jšœžœ˜Jšœ"˜"Jšœžœžœ˜šžœ žœ˜JšœW˜WJšžœžœ0žœžœ˜LJ˜—šžœ žœž˜'Jšœ1˜1JšœO˜O—šžœž˜Jšœžœ ˜6Jšœžœ "˜9JšœR˜RJšžœ'žœžœžœ˜C—JšœΟtœ ‘œ4˜BJšœ˜J˜—š Ÿœžœžœžœ%žœ˜EJšœ˜Jšœ žœ ˜J˜Jšžœžœ'žœžœ˜Wšžœ žœžœžœ˜1šžœžœ@˜ZJšœG˜G—Jšžœ˜šžœ ž˜šœ<˜Jšœ˜J˜J˜J˜—Jšžœžœ!˜6J˜—JšœF˜Fšžœ˜ Jšœ/žœ&˜XJšžœ˜Jšœ˜——Jšžœžœžœžœ˜1Jšœžœ˜ ˜ J˜A—šžœž˜Jšœ žœ˜šœ˜šœG˜GJ™3—Jšžœ˜Jšœ˜—šžœ˜ Jš Fœžœ˜P——J˜J˜—š Ÿ œžœžœžœžœ˜KJ™Fšžœžœžœž˜(Jšžœ;˜BJšžœ 4˜Kšžœž˜&Jšœ žœ˜Jšœžœ˜"Jšžœ˜—Jšžœ˜—J˜PJšžœ žœ˜!Jšœ˜J˜—šŸ œžœžœ$žœ˜HJšœžœ ˜Jšœžœ˜Jšœ˜Jšœ˜J˜šžœžœ'žœ˜4Jšœ&žœ˜0—Jš žœžœžœ3 ’ ’˜Mšžœž˜šœ˜Jšœq˜qJšžœ žœ.žœžœ˜JJ˜—Jšžœ˜—Jšžœ žœ˜.Jšœ˜J˜—šŸœž˜Jšžœ;žœ˜dJ™9š žœžœžœžœ"žœžœž˜JJšœžœ˜šžœ5žœ˜=JšœJžœ˜S—Jšžœ˜—J˜J˜—š Ÿœžœ žœžœžœžœ˜/šžœžœžœ#˜1Jšžœ0žœ˜Q—Jšœ˜J˜—šŸœžœžœžœ˜GJ˜&J˜$J˜ šžœžœžœž˜Jšžœžœž˜(J˜#Jšžœžœ&˜@J˜—šžœž˜Jš žœžœžœžœ ˜9J˜,šœ ˜ JšœJ˜J—Jšžœ žœž˜J˜—Jšžœžœžœ˜6šœ5˜5Jšœ-˜-—Jšžœ žœžœ˜šœ8˜8J˜+Jšœ!žœž˜=J˜—Jšžœžœžœžœ˜9Jšžœžœžœ-˜>J˜J˜—šŸœžœžœžœžœžœžœ˜PJ˜——™ J˜šŸœžœžœ˜Jšœžœžœ˜ JšŸœžœžœ˜$JšŸœžœžœ žœ˜LJšŸœžœ4žœ˜QJšœ˜Jšœ žœžœžœ˜Jšžœžœ4žœ˜QJ˜ J˜0Jšœžœžœ˜Jšœžœžœ˜!J˜ J˜Jšœžœ˜J˜Jšœžœ ˜;J™!Jšœžœ˜Jšœ)˜)Jšœ5˜5Jšœ+˜+šœ9˜9Jšžœžœžœ˜)JšžœQ˜U—šœ˜J˜J˜—Jšœ)˜)Jšœ.˜.˜#J˜Jšœ˜Jšœ˜—šœžœ˜J˜—J™„J˜Všžœžœžœ)žœ˜pJšœ/žœ˜B—J˜Jšœ žœžœžœ˜@šžœ˜!Jšœ˜Jšœ˜Jšœ˜Jšœ  œ ˜Jšžœ:žœ˜OJ˜—šœ ˜ JšœOžœ˜U—J˜˜"JšœO˜Ošœ žœ˜(šœ˜šœ=˜=Jšœ˜——Jšžœ ˜Jšœ˜——Jšœžœ˜J˜šœ#˜#Jšœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ5˜5Jšœ˜—Jšžœ žœD˜Wšžœ žœ˜Jšœ&žœ˜9—J˜%Jšœ#˜#J™AJšœ žœ˜Jšœ#˜#šž˜J˜-—J˜—J˜šŸœžœžœ žœžœžœžœ˜AJšžœžœžœ˜%Jšžœ žœžœ˜)šžœžœžœ˜šžœ žœžœž˜CJšœ8žœ žœ˜R—J˜Jšœžœ˜šœ˜šŸ œ˜Jšœžœ˜'Jšœžœ˜Jšœ˜—Jšœ,˜,Jšœžœ˜Jšœ˜—J˜—Jšœ žœ˜šžœžœž˜AJšœ˜—Jšœžœ˜šžœž˜JšœBžœ˜L—Jšœžœ˜šž˜Jšœ;žœ˜A—˜J˜——š Ÿœžœž œžœžœ˜VJ˜——™šœ!žœ˜*Jšœ˜Jšœ˜—J˜šŸ œžœžœžœ ˜=J˜˜JšœL˜L—J˜—šœ žœ˜J˜—šœ(žœ˜1Jšœ˜Jšœ˜J˜—šœ'žœ˜0J˜Jšœ˜J˜—šœ&žœ˜/J˜Jšœ˜—J˜šœ&žœ˜/J˜Jšœ˜—J˜šœ(žœ˜1J˜Jšœ˜—JšœM˜MJšœZ˜ZJšœV˜VJšœT˜TJšœU˜UJšœV˜VJ™—™+šœžœ˜4J˜Jšœ˜Jšœ˜J˜#J™J™-J™Jšœ˜J˜——J˜J˜™&K™Kšœ Οr ™—™2K™Kšœ €2œ€΄™—™(K™Kšœ €¨™΄—™4Kšœ€ œx™’Kšœ €P™\—šœ€ ™6Kšœ€œ™+Kšœ €Q™]—™-K™NKšœ €™(—K™šœ5™5Kš œ € œ€œ€œ€œ€œ™Ϊ—šœ5™5KšœF™FKšœ €S™_—™+K™FKšœ €Šœ€gœ™±—K™™+K™NKšœ €‘™­—K™™,K™OKšœ €P™\—K™™'K™ Kšœ €™—™&K™fKšœ €$™0—K™—…—a>—η