DIRECTORY Atom USING [ GetProp ], BasicTime USING [ Now, Period, Update ], Commander USING [ CommandProc, Register ], CommanderOps USING [ ParseToList ], FinchSmarts, IO, LarkFeepSunImport USING [ ImportInterface ], MBQueue USING [ Create, QueueClientAction ], NameDB USING [ Error, GetAttribute, SetAttribute ], Process USING [ Detach, SecondsToTicks, SetTimeout ], RedBlackTree USING [ EachNode, EnumerateIncreasing, Table ], RefID USING [ ID ], Rope, RPC USING [ EncryptionKey ], ThParty USING [ Advance, Alert, Attributes, ConversationInfo, CreateConversation, DescribeParty, GetConversationFromName, GetConversationInfo, GetParty, GetNumbersForRName, GetPartyFromNumber, GetPartyInfo, LookupServiceInterface, nullIx, PartyInfo, RegisterConversation, Unvisit, UnvisitSelf, Visit ], Thrush USING [ ActionReport, ConversationID, ConvEvent, Credentials, InterfaceSpec, NB, none, notReallyInConv, nullConvID, nullID, PartyID, PartyType, Reason, ROPE, SHHH, SmartsID, StateInConv ], ThSmarts, UserProfile USING [ Boolean ], VoiceUtils USING [ CurrentPasskey, CurrentRName, Problem, ProblemFR, Registrize, Report, ReportFR ] ; FinchSmartsImpl: CEDAR MONITOR LOCKS FinchSmarts.lock IMPORTS Atom, BasicTime, Commander, CommanderOps, FinchSmarts, IO, LarkFeepSunImport, MBQueue, NameDB, -- Nice, --Process, RedBlackTree, Rope, ThParty, 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]; uniqueSuffix: CARD ฌ 0; PD: TYPE = FinchSmarts.PD; pd: PUBLIC REF PD ฌ NEW[PDฌ[]]; info: PUBLIC FinchInfoฌNIL; Report: PROC[what: ROPE] = { IF NOT pd.doReports THEN RETURN; VoiceUtils.Report[what, $Finch]; }; 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 OR convEvent.other.state >= $active -- Possibly a hack, DCS February 26, 1988 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; ReportRequestState[GetConv[report.self.convID], report]; BROADCAST info.stateChange; -- Sometimes callers wait (Ugh!)} }; CheckIn: PUBLIC ENTRY PROC[ shh: Thrush.SHHH, credentials: Thrush.Credentials, voicePath: BOOL, reason: Thrush.Reason, remark: ROPE, nextScheduledCheck: INT ] = { ENABLE UNWIND => NULL; localRemark: ROPEฌNIL; connected: BOOL ฌ TRUE; enabled: BOOL ฌ info.enabled; info.nextScheduledCheck ฌ nextScheduledCheck; SELECT reason FROM $goodbye => { enabled ฌ connected ฌ FALSE; localRemark ฌ "Permanent disconnect requested by server"; }; $trylater => { localRemark ฌ "Temporary disconnect requested by server"; connected ฌ FALSE; }; $welcome, $hello => connected ฌ TRUE; ENDCASE; []ฌFinchSmarts.RecordSystemStateFromSmartsReport[ remark: localRemark, connected: connected, enabled: enabled, voicePath: voicePath, remoteRemark: remark]; NOTIFY info.pollCondition; }; Poke: PUBLIC ENTRY PROC = { ENABLE UNWIND => NULL; IF info#NIL THEN NOTIFY info.pollCondition; }; LimitSignalling: ENTRY PROC [cDesc: ConvDesc, signalState: StateInConv] ~ TRUSTED { ENABLE UNWIND => NULL; c: CONDITION; Process.SetTimeout[@c, Process.SecondsToTicks[pd.timeoutInitiating]]; WAIT c; IF cDesc.situation.self.state # signalState THEN RETURN; []ฌRequest[cDesc, $failed, $noAnswer, IF signalState=$notified THEN "Other party failed to respond to call." ELSE "We did not respond to call. Notify LarkSupport."]; }; 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; PlaceCallInt[convID, rName, number, useNumber]; }; PlaceCallInt: INTERNAL PROC [ convID: Thrush.ConversationIDฌnullConvID, rName: ROPE, -- rName or description number: ROPE, -- telephone number, if present useNumber: BOOLฌFALSE, convAttributes: ThParty.Attributes ฌ NIL, partyAttributes: ThParty.Attributes ฌ NIL ] = { 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, convAttributes, partyAttributes]; }; lowPriority: ThParty.Attributes ฌ LIST[[$Priority, "300"]]; Join: PUBLIC ENTRY PROC[convName: Rope.ROPE] RETURNS[nb: Thrush.NBฌ$noSuchConv] = { ENABLE UNWIND => NULL; conversationInfo: ThParty.ConversationInfo; cDesc: FinchSmarts.ConvDesc; rName: Rope.ROPE; [nb, conversationInfo] ฌ ThParty.GetConversationFromName[ shhh: info.shh, partyID: info.partyID, name: convName]; IF nb=$success AND conversationInfo.numIdle >= conversationInfo.numParties THEN nb ฌ $noSuchConv; SELECT nb FROM $success => NULL; $noSuchConv => { rName ฌ NameDB.GetAttribute[ rName: convName, attribute: $rname, key: $conversationname]; IF rName=NIL THEN RETURN; nb ฌ $unknown; PlaceCallInt[rName: rName, number: NIL, partyAttributes: lowPriority]; nb ฌ $success; RETURN; }; ENDCASE => RETURN; cDesc ฌ GetConv[convID: conversationInfo.convID, createOK: TRUE]; nb ฌ Request[cDesc, $active, $join, "Join conversation in progress", NIL, lowPriority]; IF nb=$stateMismatch THEN -- Try at most twice nb ฌ Request[cDesc, $active, $join, "Join conversation in progress", NIL, lowPriority]; }; Feep: PUBLIC ENTRY PROC[convID: ConversationID, feepString: ROPE] = { ENABLE UNWIND => NULL; 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 { [nb, cDesc.feepInterfaceSpec] ฌ ObtainServiceInterface[NIL, "LarkFeep", cDesc]; IF nb=$success AND cDesc.feepShhh = NIL THEN [cDesc.feepInterface, cDesc.feepShhh] ฌ LarkFeepSunImport.ImportInterface[ instance: cDesc.feepInterfaceSpec.interfaceName.instance]; }; IF nb=$success THEN nb ฌ cDesc.feepInterface.clientStubFeep[ interface: cDesc.feepInterface, shhh: cDesc.feepShhh, serviceID: cDesc.feepInterfaceSpec.serviceID, convID: cDesc.situation.self.convID, requestingParty: cDesc.situation.self.partyID, number: feepString, noisy: UserProfile.Boolean["Finch.NoisyFeep", 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]; }; CurrentRName: PUBLIC PROC RETURNS [rName: ROPE] = { RETURN[IF info#NIL THEN info.myRName ELSE NIL]; }; CurrentConversations: PUBLIC PROC RETURNS [conversations: LIST OF ConvDesc] = { RETURN[IF info#NIL THEN info.conversations ELSE NIL]; }; IdentifyVisitor: PUBLIC ENTRY PROC [visitor, password: Rope.ROPE, complain: BOOL ฌ TRUE] RETURNS [nb: NB] ~ { ENABLE UNWIND => NULL; guestPartyID: PartyID; guestPassKey: RPC.EncryptionKey ฌ VoiceUtils.CurrentPasskey[password]; guest: Rope.ROPE ฌ VoiceUtils.Registrize[visitor]; IF Rope.Equal[info.myRName, guest, FALSE] THEN { ReleaseVisitorSelf[]; RETURN }; [nb, guestPartyID] ฌThParty.GetParty[shh:info.shh, partyID:info.partyID, rName:guest]; IF nb=$success THEN nb ฌ ThParty.Visit[shh: info.shh, hostPartyID: info.partyID, guestPartyID: guestPartyID, guestPassword: guestPassKey]; SELECT nb FROM $success => VoiceUtils.ReportFR["%s visiting %s -- %s calls will ring in both offices", $Finch, NIL, IO.rope[guest], IO.rope[info.myRName], IO.rope[guest]]; $passwordNotValid => IF complain THEN VoiceUtils.ReportFR[ "Invalid password for %s", $Finch, NIL, IO.rope[guest]]; $visitingIllegal => Problem[ "Visitor and/or visitee not suitable for visiting (possibly missing Lark?)"]; $noSuchParty2 => VoiceUtils.ReportFR[ "Couldn't identify visitor %s", $Finch, NIL, IO.rope[guest]]; $noSuchParty => VoiceUtils.ReportFR["Couldn't identify visitee %s", $Finch, NIL, IO.rope[info.myRName]]; -- should never happen ENDCASE => Problem["Server communication problem", nb]; }; ReleaseVisitor: PUBLIC ENTRY PROC [visitor, password: Rope.ROPE] ~ { ENABLE UNWIND => NULL; nb: NB; guestPartyID: PartyID; guestPassword: RPC.EncryptionKey ฌ VoiceUtils.CurrentPasskey[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, guestPartyID] ฌThParty.GetParty[shh:info.shh, partyID:info.partyID, rName: visitor]; IF nb=$noSuchParty2 THEN VoiceUtils.ReportFR[ "Couldn't identify visitor %s", $Finch, NIL, IO.rope[visitor]]; IF nb = $success THEN { hostPartyID: PartyID; [visitee: hostPartyID] ฌ ThParty.DescribeParty[partyID: guestPartyID, nameReq: $none]; nb ฌ IF hostPartyID=info.partyID THEN ThParty.Unvisit[shh: info.shh, hostPartyID: hostPartyID, guestPartyID: guestPartyID, guestPassword: guestPassword] ELSE $noSuchVisiting; }; SELECT nb FROM $success => VoiceUtils.ReportFR["Visiting cancelled for %s", $Finch, NIL, IO.rope[visitor]]; $noSuchVisiting => VoiceUtils.ReportFR[ "%s was not visiting here", $Finch, NIL, IO.rope[visitor]]; $noSuchParty => VoiceUtils.ReportFR[ "Couldn't identify visitor %s", $Finch, NIL, IO.rope[visitor]]; $noSuchParty2 => VoiceUtils.ReportFR["Couldn't identify visitee %s", $Finch, NIL, IO.rope[info.myRName]]; -- should never happen ENDCASE => Problem["Server communication problem", nb]; }; ReleaseVisitorSelf: INTERNAL PROC ~ { nb: NB ฌ $noSuchVisiting; IF info.partyID # nullID THEN nb ฌ ThParty.UnvisitSelf[shh: info.shh, guestPartyID: info.partyID]; SELECT nb FROM $success => VoiceUtils.ReportFR[ "Visiting cancelled for %s -- welcome home", $Finch, NIL, IO.rope[info.myRName]]; $noSuchVisiting => VoiceUtils.ReportFR[ "%s was not visiting anywhere", $Finch, NIL, IO.rope[info.myRName]]; $noSuchParty => VoiceUtils.ReportFR["Couldn't identify visitor %s", $Finch, NIL, IO.rope[info.myRName]]; -- should never happen ENDCASE => Problem["Server communication problem", nb]; }; ServiceConnect: PUBLIC ENTRY PROC[ serviceName: ROPE, convID: ConversationIDฌnullConvID, createOK: BOOLฌTRUE, addOK: BOOLฌFALSE ] RETURNS [ nb: NBฌ$success, cDesc: ConvDescฌNIL ] = { ENABLE UNWIND=>NULL; state: StateInConv ฌ $idle; calledPartyID: PartyID; cCode: ConvCode; backgroundAttributes: ThParty.Attributes ฌ IF backgrounding THEN lowPriority ELSE NIL; IF NOT (([,cDesc, state]ฌFinchOn[convID]).on) THEN RETURN[nb: $finchNotRunning]; IF cDesc#NIL AND cDesc.situation.self.state>Thrush.notReallyInConv THEN RETURN; [cCode, cDesc] ฌ GetActiveConversation[serviceName,addOK]; SELECT cCode FROM $ok => { nb ฌ WaitForActive[cDesc, FALSE]; RETURN; }; $hld => { nb ฌ Request[cDesc, $active]; IF nb=$success THEN nb ฌ WaitForActive[cDesc, FALSE]; RETURN; }; $bzy => RETURN[$convStillActive, NIL]; $idl, $res, $addOK => IF ~createOK THEN RETURN[$noSuchConv, NIL]; ENDCASE => ERROR; IF cDesc#NIL THEN { stateฌ cDesc.situation.self.state; convIDฌ cDesc.situation.self.convID; }; 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: calledPartyID, convID: convID, partyAttributes: backgroundAttributes, addOK: addOK]; IF nb=$success AND serviceName#NIL THEN nb ฌ WaitForActive[cDesc, TRUE]; }; ConvCode: TYPE = { ok, addOK, res, hld, bzy, idl }; GetActiveConversation: INTERNAL PROC[serviceName: ROPEฌNIL, addOK: BOOLฌFALSE] RETURNS[convCode: ConvCode ฌ $idl, cDesc: ConvDescฌNIL] = { 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]; pInfo: ThParty.PartyInfo ฌ possibleCDesc.partyInfo; possibleState: StateInConv ฌ possibleCDesc.situation.self.state; SELECT possibleState FROM $reserved => {convCode ฌ $res; cDesc ฌ possibleCDesc; LOOP;}; $inactive => { IF convCode=$idl THEN { convCode ฌ $hld; cDesc ฌ possibleCDesc; }; LOOP;}; $initiating, $ringback, $active => NULL; ENDCASE => LOOP; IF pInfo#NIL THEN FOR ix: NAT IN [1..pInfo.conversationInfo.numParties] DO IF ix#pInfo.ixSelf AND pInfo[ix].type=$service AND Rope.IsPrefix[serviceName, pInfo[ix].name, FALSE] THEN RETURN[$ok, possibleCDesc]; ENDLOOP; -- There's still presumably at most one active conversation RETURN[ IF addOK AND possibleCDesc.situation.self.state=$active THEN $addOK ELSE $bzy, possibleCDesc]; ENDLOOP; }; ObtainServiceInterface: PUBLIC PROC[ serviceName: ROPE, interfaceName: ROPE, cDesc: ConvDescฌNIL ] RETURNS [ nb: NBฌ$success, interfaceSpec: Thrush.InterfaceSpec ] = { partyID: Thrush.PartyIDฌThrush.nullID; credentials: Thrush.Credentials; IF cDesc#NIL THEN { pInfo: ThParty.PartyInfo ฌ cDesc.partyInfo; credentials ฌ cDesc.situation.self; IF pInfo#NIL THEN FOR ix: NAT IN [1..pInfo.conversationInfo.numParties] DO IF ix # pInfo.ixSelf AND ( serviceName=NIL OR ( pInfo[ix].type = $service AND pInfo[ix].state >= $active AND Rope.IsPrefix[serviceName, pInfo[ix].name, FALSE] ) ) THEN { partyID ฌ pInfo[ix].partyID; EXIT; }; ENDLOOP; } 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, interfaceSpec: nullInterfaceSpec]; [nb, interfaceSpec] ฌ ThParty.LookupServiceInterface[info.shh, credentials, partyID, interfaceName]; }; anyone: LIST OF ROPE ฌ LIST["*"]; announcement: ThParty.Attributes ฌ LIST[[$subject, "Announcement"]]; mediumPriority: ThParty.Attributes ฌ LIST[[$Priority, "400"]]; Announce: ENTRY PROC [rNames: LIST OF ROPE] ~ { ENABLE UNWIND=>NULL; -- RestoreInvariant; [] ฌ AnnounceInt[rNames]; }; AnnounceInt: INTERNAL PROC [rNames: LIST OF ROPE] RETURNS [nb: NBฌ$success, cDesc: ConvDescฌNIL] ~ { count: INT ฌ 0; state: StateInConv; convID: ConversationIDฌnullConvID; convEvent: Thrush.ConvEvent; IF ~(([,cDesc, state]ฌFinchOn[convID]).on) THEN { nb ฌ $notEnabled; RETURN; }; IF cDesc = NIL THEN [cDesc, state] ฌ GetCurrentConv[]; SELECT state FROM $idle => { [nb, convEvent] ฌ ThParty.CreateConversation[ shhh: info.shh, credentials: [partyID: info.partyID, smartsID: info.smartsID] ]; IF nb = $success THEN { pInfo: ThParty.PartyInfo; cDesc ฌ NoteNewState[convEvent]; pInfo ฌ cDesc.partyInfo; IF pInfo=NIL OR ~pInfo[pInfo.ixSelf].voicePath THEN { [] ฌ Request[cDesc, $failed, $noVoicePath, "Your Etherphone is not running"]; RETURN[$noVoicePath, cDesc]; }; }; }; $reserved, $parsing => nb ฌ Request[cDesc, $initiating]; ENDCASE => { ReportConversationState[$convStillActive, NIL, "Conversation already in progress"]; RETURN; }; IF nb # $success THEN RETURN; cDesc.originatorRecorded ฌ TRUE; nb ฌ ThParty.RegisterConversation[shhh: info.shh, credentials: cDesc.situation.self, name: "Announcement", convAttributes: announcement, accessList: anyone]; VoiceUtils.Report["Notifying listeners...", $Finch]; FOR names: LIST OF ROPE ฌ rNames, names.rest WHILE names#NIL DO calledPartyID: PartyID; partyType: Thrush.PartyType; IF Rope.Equal[names.first, info.myRName, FALSE] THEN LOOP; -- Don't call self [nb, calledPartyID] ฌThParty.GetParty[shh:info.shh, partyID:info.partyID, rName:names.first]; SELECT nb FROM $serverDown, $notEnabled => RETURN; -- Futile $noSuchParty2, $noIdentSupplied, $noSuchParty => LOOP; ENDCASE; [nb: nb, type: partyType] ฌ ThParty.DescribeParty[calledPartyID, $current]; IF partyType=$trunk THEN LOOP; nbฌThParty.Alert[shhh: info.shh, credentials: cDesc.situation.self, calledPartyID: calledPartyID, partyAttributes: mediumPriority]; SELECT nb FROM $success => count ฌ count + 1; $stateMismatch => { nbฌRequest[cDesc, $failed, $error, "Conversation already in progress"]; RETURN; }; $narcissism => NULL; -- Should never happen ???see above??? ENDCASE => {Problem["Server communication problem", nb]; RETURN;} ENDLOOP; nb ฌ WaitForActive[cDesc, FALSE]; IF nb=$success AND count > 0 THEN VoiceUtils.ReportFR["%g parties contacted -- Please begin speaking", $Finch, NIL, IO.int[count]] ELSE VoiceUtils.Report["No listeners found", $Finch, NIL]; }; 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 info=NIL OR ~info.enabled 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; timeAdjustment: INT; prevState, currState: StateInConv; cDesc ฌ GetConv[convEvent.self.convID, TRUE]; prevState ฌ cDesc.situation.self.state; cDesc.situation ฌ convEventญ; [nb, cInfo] ฌ ThParty.GetConversationInfo[shh: info.shh, convID: convEvent.self.convID]; currState ฌ cDesc.situation.self.state; SELECT TRUE FROM nb # $success => NULL; -- Problem["Can't obtain conversation information"]; currState#prevState, cDesc.numParties#cInfo.numParties, cDesc.numActive#cInfo.numActive, cDesc.numIdle#cInfo.numIdle => { cDesc.subject ฌ FetchAttribute[cInfo.convAttributes, $subject].value; timeAdjustment ฌ BasicTime.Period[from: convEvent.time, to: BasicTime.Now[]]; cDesc.startTime ฌ BasicTime.Update[cInfo.startTime, timeAdjustment]; 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 { ixOriginator: NAT ฌ pInfo.ixOriginator; cDesc.partyInfo ฌ pInfo; IF ixOriginator#ThParty.nullIx AND ixOriginator # pInfo.ixSelf AND pInfo[ixOriginator].type=$trunk THEN pInfo[ixOriginator].intendedName ฌ "outside line"; }; }; ENDCASE; SELECT currState FROM $idle, $neverWas => info.conversations ฌ DequeueConv[info.conversations, cDesc]; $initiating => TRUSTED { Process.Detach[FORK LimitSignalling[cDesc, $initiating]]; }; $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 => TRUSTED { Process.Detach[FORK LimitSignalling[cDesc, $notified]]; }; ENDCASE; IF currState#inactive AND cDesc.ultimateState TRUE, ENDCASE=>FALSE; IF state=$failed AND (pInfo = NIL OR ~pInfo[pInfo.ixSelf].voicePath) THEN state ฌ $idle; [nb, convEvent] ฌ ThParty.Advance[shhh: info.shh, credentials: cDesc.situation.self, state: state, reportToAll: reportToAll, reason: reason, comment: comment, convAttributes: convAttributes, partyAttributes: partyAttributes]; SELECT nb FROM $success => []ฌNoteNewState[convEvent]; $stateMismatch => { cDescฌNoteNewState[convEvent]; IF cDesc.situation.self.state = state THEN nb ฌ $success ELSE IF state=$idle AND cDesc.situation.self.state # $idle THEN LOOP; }; ENDCASE => ReportConversationState[nb, cDesc, "Telephone server action failed"]; EXIT; ENDLOOP; }; Connect: INTERNAL PROC [calledPartyID: PartyID, convID: ConversationIDฌnullConvID, reason: Thrush.Reason ฌ NIL, comment: ROPEฌNIL, convAttributes: ThParty.Attributes ฌ NIL, partyAttributes: ThParty.Attributes ฌ NIL, addOK: BOOLฌFALSE] 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, convAttributes: convAttributes, partyAttributes: partyAttributes ]; IF nb = $success THEN { pInfo: ThParty.PartyInfo; cDesc ฌ NoteNewState[convEvent]; pInfo ฌ cDesc.partyInfo; IF pInfo=NIL OR ~pInfo[pInfo.ixSelf].voicePath THEN { [] ฌ Request[cDesc, $failed, $noVoicePath, "Your Etherphone is not running"]; RETURN[$noVoicePath, cDesc]; }; }; }; $reserved, $parsing => nb ฌ Request[cDesc, newState, reason, comment, convAttributes, partyAttributes]; $active => IF ~addOK THEN { ReportConversationState[$convStillActive, NIL, "Conversation already in progress"]; RETURN; }; 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"]; RETURN; }; $narcissism => { nbฌRequest[cDesc, $failed, $notFound, "Attempt to call self rejected on philosophical grounds"]; RETURN; }; ENDCASE => Problem["Server communication problem", nb]; }; WaitForActive: INTERNAL PROC[cDesc: ConvDesc, demandNewParty: BOOLฌFALSE] RETURNS [nb: NBฌ$success] = { numParties: NAT ฌ cDesc.numParties; FOR i: NAT IN [0..pd.waitsForConnect) DO SELECT cDesc.situation.self.state FROM $active => IF ~demandNewParty OR cDesc.numParties > numParties THEN 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; }; 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]]; }; FetchAttribute: PUBLIC PROC[ attributes: ThParty.Attributes, attribute: ATOM, default: ROPEฌNIL] RETURNS [value: ROPE, valueLoc: ThParty.AttributesฌNIL] = { value ฌ default; FOR aL: ThParty.Attributes ฌ attributes, aL.rest WHILE aL#NIL DO IF aL.first.type = attribute THEN RETURN[aL.first.value, aL]; ENDLOOP; }; GetCurrentConv: INTERNAL PROC RETURNS [ cDesc: ConvDescฌNIL, state: Thrush.StateInConv ] = --INLINE-- { FOR convs: LIST OF ConvDesc ฌ info.conversations, convs.rest WHILE convs#NIL DO ok: BOOL ฌ TRUE; cDesc ฌ convs.first; state ฌ cDesc.situation.self.state; SELECT state FROM $reserved, $parsing => { pInfo: ThParty.PartyInfo ฌ cDesc.partyInfo; FOR ix: NAT IN [1..pInfo.conversationInfo.numParties] DO IF ix#pInfo.ixSelf AND pInfo[ix].type=$service THEN { ok ฌ FALSE; EXIT; }; ENDLOOP; IF ok THEN RETURN; }; ENDCASE; ENDLOOP; cDesc ฌ NIL; state ฌ $idle; }; 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]; reportProcsTable: RedBlackTree.Table ฌ NARROW[Atom.GetProp[$ReportProcs, $rpTable]]; EachRCS: RedBlackTree.EachNode = { procs: FinchSmarts.ReportProcs ฌ NARROW[data]; IF procs.reportConversationState#NIL THEN procs.reportConversationState[cS.nb, cS.cDesc, cS.remark]; }; IF reportProcsTable#NIL THEN EnumerateIncreasing[reportProcsTable, EachRCS]; }; ReportRequestState: PROC [cDesc: ConvDesc, actionReport: Thrush.ActionReport] ~ { 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]; reportProcsTable: RedBlackTree.Table ฌ NARROW[Atom.GetProp[$ReportProcs, $rpTable]]; actionRequest: REFฌNIL; EachRRS: RedBlackTree.EachNode = { procs: FinchSmarts.ReportProcs ฌ NARROW[data]; IF procs.reportRequestState#NIL THEN actionRequest ฌ procs.reportRequestState[rS.cDesc, rS.actionReport, actionRequest]; }; IF reportProcsTable#NIL THEN EnumerateIncreasing[reportProcsTable, EachRRS]; }; EnumerateIncreasing: PUBLIC PROC [self: RedBlackTree.Table, procToApply: RedBlackTree.EachNode] = { PList: TYPE = RECORD[s: SEQUENCE m: NAT OF FinchSmarts.ReportProcs]; pList: REF PList ฌ NEW[PList[20]]; index: NAT ฌ 0; GetEm: RedBlackTree.EachNode = { pList[index] ฌ NARROW[data]; index ฌ index+1; IF index=20 THEN ERROR; }; RedBlackTree.EnumerateIncreasing[self, GetEm]; FOR doIndex: NAT IN [0..index) DO IF procToApply[pList[doIndex]] THEN EXIT; ENDLOOP; }; 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; backgrounding: BOOLฌFALSE; FilterResetCmd: Commander.CommandProc = TRUSTED { [result, msg] ฌ SetFiltering[0, "false"]; }; FilterZeroCmd: Commander.CommandProc = TRUSTED { [result, msg] ฌ SetFiltering[0]; }; BackgroundCmd: Commander.CommandProc = TRUSTED { [result, msg] ฌ SetFiltering[0]; backgrounding ฌ TRUE; }; NoBackgroundCmd: Commander.CommandProc = TRUSTED { [result, msg] ฌ SetFiltering[0, "false"]; backgrounding ฌ FALSE; }; FilterOneCmd: Commander.CommandProc = TRUSTED { [result, msg] ฌ SetFiltering[1]; }; FilterTwoCmd: Commander.CommandProc = TRUSTED { [result, msg] ฌ SetFiltering[2]; }; FilterThreeCmd: Commander.CommandProc = TRUSTED { [result, msg] ฌ SetFiltering[3]; }; InitFiltering: PROC = { filterMsg: ROPE; [message: filterMsg] ฌ SetFiltering[0]; IF filterMsg#NIL THEN VoiceUtils.Report[filterMsg, $Finch]; }; EDLNames: LIST OF ROPE ฌ LIST["Arnon.pa", "Beach.pa", "Beretta.pa", "Bier.pa", "Crow.pa", "Diebert.pa", "Glassner.pa", "Pier.pa", "Plass.pa", "PolleZ.pa", "Shoemake.pa", "Stone.pa", "Subhana.pa", "Wyatt.pa"]; AnnounceCmd: Commander.CommandProc = TRUSTED { list: LIST OF ROPE; length: INT; [list, length] ฌ CommanderOps.ParseToList[cmd: cmd]; IF list=NIL THEN list ฌ EDLNames; Announce[list]; }; pd.requests ฌ MBQueue.Create[]; Commander.Register["FReset", FilterResetCmd, "Don't filter calls. This is a test."]; Commander.Register["F0", FilterZeroCmd, "Notifies will time out. This is a test."]; Commander.Register["F1", FilterOneCmd, "Reject all calls. This is a test."]; Commander.Register["F2", FilterTwoCmd, "Accept (ringing) all calls. This is a test."]; Commander.Register["F3", FilterThreeCmd, "Answer all calls (hot line). This is a test."]; Commander.Register["Background", BackgroundCmd, "FinchTool will supercede background calls."]; Commander.Register["NoBackground", NoBackgroundCmd, "FinchTool will not supercede background calls."]; Commander.Register["Announce", AnnounceCmd, "Create a com-line like call to a set of Etherphone users."]; }. Commander.Register["VuFinch", ViewCmd, "Program Management variables Finch"]; ViewCmd: Commander.CommandProc = TRUSTED { }; .– FinchSmartsImpl.mesa Copyright ำ 1984, 1986, 1987, 1988, 1990, 1992 by Xerox Corporation. All rights reserved. Last Edited by: Swinehart, June 4, 1992 9:30 pm PDT Doug Terry, September 5, 1986 4:17:06 pm PDT Polle Zellweger (PTZ) August 14, 1990 4:01:15 pm PDT Nice, 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, or when our Finch bombs out in the middle of a conversation.  Polling loop will awaken immediately and if necessary (and not inhibited) try to reconnect. We have entered an unstable state, which should not be allowed to persist too long before we hang up and free our resources. "Too long" is determined by pd.timeoutInitiating. If we ever decide to deal with $initiating states on our own, we may have to accommodate thaat here, too. Client Functions Really need better generic way to avoid duplicating work and stranding connections, here! cancelling visiting at home via "Visit self" cancelling visiting at home via "Unvisit self" If ThParty.Unvisit really cared about passwords (which it does not currently), we would have to keep passwords of relevant visitors, probably in the FinchInfo record. Need to swap the nb codes in ThPartyInitImpl Find, in this order: $ok) a non-idle conversation that's already established to the specified service. $addOK) an active conversation that does not include the specified service (addOK only) $res) a conversation in state $reserved $hld) an established converation is on hold and waiting. $bzy) an established conversation that doesn't satisfy the above criteria $idl) no active conversations exist. Reserved takes precedence over held, but all others take precedence over both We've found a sufficiently advanced conv. to disavow any others. Less than live calls, but more than services. If there's an existing proto-conversation, use it... This isn't quite good enough, because person A might log in at person B's machine and try to announce to person B. not an Etherphone user; can't participate in a multicast call Some smarts don't do this. They probably should. Do something interesting  see other smarts for more confusion Utilities New conversation Now gather up additional information that we might not know yet: Who started this? Who the other party(s) is (are) This will occur during shutdown when Substitute is reporting removal from conversations. Better to avoid complaint and use the information at hand. State information guaranteed correct only as of last time one of these numbers changed. Probably Thrush.StateInConv should order conversation states st inactive < active. 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.  if we're in the state we wanted, ignore stateMismatch 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. Do something interesting  see other smarts for more confusion For use in Recording, and so on, where client needs to wait for setup. demandNewParty asserts that we have invited the recording service to join us, and we need to wait until that has happened. February 26, 1988 DCS Finds an existing proto-conversation (i.e., state = $reserved or $parsing) that is NOT a service conversation. State reports, other than system state (look in FinchRegisterImpl for that) Reports of all kinds are serialized and decoupled from their issuers. Perhaps this is not entirely necessary, but it's hard to prove otherwise. The problems are: (a) There can otherwise be deadlocks when A.entry1 calls B.p, and B.p issues a report proc that turns out to be A.entry2. (b) Reports can be issued from many places and processes, and it appears they should be seen in the order issued, wherever they come from. Action reports may not qualify, except that they should not proceed out of synch of the other two kinds, which do. Debugging nonsense Initial state to permit announcements. InitFiltering[]; -- to permit announcements Disabled October 3, 1989 4:51:16 pm PDT DCS; for further study. Obsolete stuff BeNice: PROC[r: REF, d: INT] = { IF NOT pd.doNice THEN RETURN; Nice.BeNice[r, d, $Finch, NIL]; }; Nice.View[pd, "Finch PD"]; 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 Doug Terry, August 28, 1986 4:59:33 pm PDT Changed report registration; removed key distribution; removed routines for recording/playing, tunes and related operations. changes to: FinchIsRunning, RegisterForReports, UnRegisterForReports, ReportAction, DisconnectCall, PlaceCall, PlaybackTune, NoteNewState, Request, Connect, InitFinchSmarts, UninitFinchSmarts, ReportSystemState, ReportConversationState, ReportRequestState, DIRECTORY, FinchSmartsImpl, GetConv Doug Terry, August 29, 1986 12:14:14 pm PDT New interface procedures for FinchSmarts clients to get at voice services: VoiceConnect and LookupServiceInterface. changes to: ReleaseVisitor, VoiceConnect, WaitForActive, LookupServiceInterface, Problem Swinehart, June 8, 1987 12:03:11 pm PDT Remove GList by copying code. Swinehart, July 14, 1987 4:19:54 pm PDT Numerous changes to error and connection management. ThPartyClientImpl split out. Swinehart, July 19, 1987 5:16:39 pm PDT VoiceConnect => ServiceConnect, more rigid success requirements. changes to: ServiceConnect, GetActiveConversation, ClearInfo, ObtainServiceInterface Polle Zellweger (PTZ) July 27, 1987 5:56:47 pm PDT a bit less rigid success requirements (find $reserved party if offhook when play/record) changes to: GetActiveConversation Polle Zellweger (PTZ) July 27, 1987 11:17:01 pm PDT Allow for better party reports in FinchTool (>2 parties in conversation) changes to: NoteNewState, us, PartyID Swinehart, August 30, 1987 9:23:09 pm PDT Continuing development of connect/disconnect behavior., PollWaiter Polle Zellweger (PTZ) September 15, 1987 11:36:30 am PDT Improved error messages for visiting functions; fix bug in NoteNewState that wasn't updating cDesc.ultimateState properly (inhibited coalescing of outside calls in FinchToolImpl). changes to: IdentifyVisitor, ReleaseVisitor, ReleaseVisitorSelf, NoteNewState Polle Zellweger (PTZ) September 15, 1987 4:28:01 pm PDT changes to: IdentifyVisitor, ReleaseVisitor, ReleaseVisitorSelf Polle Zellweger (PTZ) March 26, 1989 6:48:08 pm PST Add crude announcement capability. added: EDLNames, anyone, announcement, mediumPriority, Announce, AnnounceInt, GetCurrentConv Polle Zellweger (PTZ) March 29, 1989 11:18:43 pm PST Creature comforts, but without changing interfaces. changes to: InitFiltering, AnnounceCmd, initialization Polle Zellweger (PTZ) May 21, 1990 2:23:07 pm PDT Add UNWINDs to some ENTRY procs that were missing them. changes to: PlaceCall, PlaceCallInt, Announce, UninitFinchSmarts Polle Zellweger (PTZ) June 26, 1990 2:27:48 pm PDT Use Sun RPC for ThSmarts communication. changes to: InitFinchSmarts Polle Zellweger (PTZ) August 9, 1990 1:13:08 pm PDT Be a bit more flexible about state mismatch changes to: Request Polle Zellweger (PTZ) August 10, 1990 7:07:40 pm PDT Set up for LarkFeep via Sun RPC changes to: DIRECTORY, FinchSmartsImpl, Feep, Request, InitFinchSmarts Swinehart, September 8, 1990 6:53:41 pm PDT Redistribute some functions between FinchSmartsImpl and the new FinchRegisterImpl; adapt to new error management code. ส&S•NewlineDelimiter –(cedarcode) style™šœ™Jšœ ฯeœO™ZJ™3Icode™,Kšœ4™4—K˜šฯk ˜ Kšœžœ ˜Kšœ žœ˜(Kšœ žœ˜*Kšœ žœ˜#Kšœ ˜ Kšžœ˜Kšœžœ˜,Kšœžœ˜,Kšœžœ'˜3J™Kšœžœ(˜5Kšœ žœ*˜˜>K˜šœžœ˜K˜—Kšžœžœžœ˜š œž œžœžœžœ˜K˜—Kšœžœ žœ˜K˜šฯnœžœžœ˜Kšžœžœžœžœ˜ K˜ K˜K˜—šก œžœ žœžœ žœžœžœ˜IKšžœžœžœžœ˜š žœžœžœžœžœž˜?Kšžœ žœžœžœ˜6Kšžœ˜—Kšžœ˜Kšœ˜K˜—šก œžœžœžœžœžœžœ˜SKš žœžœžœžœ  ˜%Kšžœ žœžœ  ˜3š žœžœžœžœ žœž˜DKšžœžœ!žœ  ˜]Kšžœ˜—Kšžœ ˜Kšœ˜—K˜—™ J™šกœžœžœžœ˜Kšœžœ˜ Kšœ˜Kšœ˜Kšžœžœžœ ˜-J™I™ J™‹J™นJ™‰—šžœ1˜3šžœ" )˜MKšžœ˜"——K˜K˜—šก œžœžœžœ˜ Kšœžœ˜ Kšœ˜K˜K˜Kšœ˜Kšžœžœžœ ˜-J™ฌJ™๓Kšœ1žœ˜8Kšžœžœžœ *˜RK˜K˜K˜—šก œžœžœžœ˜ Kšœžœ˜ Kšœ˜Kšœ˜Kšžœžœžœ˜Kšœ8˜8Kšž œ  œ˜=Kšœ˜K˜—šกœžœžœžœ˜Kšœ žœ˜K˜ Kšœ žœ˜K˜Kšœžœ˜ Kšœž˜K˜Kšžœžœžœ˜Kšœ žœžœ˜Kšœ žœžœ˜Kšœ žœ˜K˜-šžœž˜šœ ˜ Kšœžœ˜K˜9K˜—šœ˜K˜9Kšœ žœ˜K˜—Kšœ žœ˜%Kšžœ˜—˜1K˜i—Kšžœ˜K˜K˜—šกœžœžœžœ˜Kšžœžœžœ˜Kšžœžœžœžœ˜.J™[K˜—šกœžœžœ/žœ˜SK™ฏK™iKšžœžœžœ˜Kšœž œ˜ KšœE˜EKšžœ˜Kšžœ*žœžœ˜8˜%šžœžœ)˜FKšžœ5˜9——K˜—K˜—™J™Kšœ žœžœ (˜Kšœžœžœ˜Kšœ˜K˜%Kšœžœ˜Kšœ žœž˜K˜K˜—šกœžœžœžœ˜"Kšœ˜K˜%Kšœ žœž˜K˜"Kšžœžœžœ ˜-K˜$Kšžœžœ'žœžœ˜9šžœž˜šœ˜Kšœ(žœ#˜NK˜—Kšž œ-˜8—K˜K˜—šก œžœžœžœ#˜@Kšžœžœžœ ˜-K˜$Kšžœžœ'žœžœ˜:Kšžœžœ2žœ˜MKšœ˜K˜—šก œžœžœžœ˜K˜)Kšœžœ ˜$Kšœžœ ˜-Kšœ žœž˜Kšœ˜Kšžœžœžœ ˜+K˜/K˜K˜—šก œžœžœ˜K˜)Kšœžœ ˜$Kšœžœ ˜-Kšœ žœž˜Kšœ%žœ˜)Kšœ&ž˜)Kšœ˜Kšœ˜Kšœžœ˜K˜"Kšœžœžœ˜šžœ žœ˜K˜Wšžœž˜Kšœ/žœ˜4Kšœ*žœ  ˜;Kšžœ˜—K˜—šžœ žœž˜'K˜1KšœO˜O—šžœž˜Kšœžœ ˜6Kšœžœ "˜9K˜RKšžœ"žœžœžœ˜>—Kšœฯtœ ขœU˜cKšœ˜K˜—šœ"žœ˜;K˜—šกœž œžœžœ˜,Kšžœ žœ˜&Kšžœžœžœ˜Kšœ+˜+K˜Kšœ žœ˜˜9Kšœ7˜7—šžœ žœ9ž˜OK˜—šžœž˜Kšœ žœ˜˜˜K˜<—Kšžœžœžœžœ˜K˜Kšœ#žœ ˜FK˜Kšžœ˜K˜—Kšžœžœ˜—Kšœ;žœ˜AKšœEžœ˜Wšžœžœ ˜.KšœEžœ˜W—K˜K˜—š กœžœžœžœ%žœ˜EKšžœžœžœ˜Kšœ˜Kšœ žœ ฯb˜K˜Kšžœžœ'žœžœ˜Wšžœ žœžœžœ˜1Kšœ7žœ˜Ošžœ žœžœž˜,˜JK˜:—JšขœYข™[—K˜—šžœ žœ)˜žœžœ˜mKšขœ,ข™.—šžœžœ˜Kšœ˜K˜Všœžœ˜ šžœ9˜=Kšœ9˜9—Kšžœ˜—K˜—šžœž˜KšœEžœžœ˜\KšœLžœžœ˜cKšœMžœžœ˜dKšœMžœžœ ˜Kšžœ0˜7—Kšœ˜K˜—šกœž œ˜%Kšœžœ˜šžœžœ˜K˜D—šžœž˜šœ ˜ Kšœ5žœžœ˜Q—KšœPžœžœ˜lKšœLžœžœ ˜€Kšžœ0˜7—K˜K˜—šกœžœžœžœ˜"Kšœ žœ˜K˜"Kš œ žœžœ žœžœžœ˜2Kšœžœ ˜Kš œžœžœžœžœ˜.K˜K˜K˜Kš œ+žœžœ žœžœ˜VKšžœžœ'ž œ˜PKš žœžœžœžœžœ˜OK˜:šžœž˜Kšœ#žœžœ˜5šœ ˜ K˜Kšžœ žœžœ˜5Kšžœ˜Kšœ˜—Kšœžœžœ˜&Kš œžœ žœžœžœ˜AKšžœžœ˜—KšžœžœžœM˜^Kšžœžœžœ˜0šžœ(˜,KšœJ˜J—šžœ žœD˜WKšœ5˜5—Kš žœ žœ žœžœžœ˜HKšœ˜J™—šœ žœ%˜3K˜—šกœžœžœžœžœ žœžœ˜NKšžœ,žœ˜;™J™QJ™WJ™'J™8J™IJ™$—Kš žœ žœžœžœ ˜1š žœžœžœ+žœžœž˜OKšœžœ˜.K˜3K˜@šžœž˜šœ6žœ˜=J™M—šœ˜Kšžœžœ.žœ˜J—Kšœ#žœ˜(Kšžœžœ˜—Jšœ@™@š žœžœžœžœžœžœ(ž˜Jšžœžœž˜2Kšœ+žœžœžœ˜R—Kšžœ ;˜D—šžœ˜Kšžœžœ,žœžœ˜NKšœ˜—Kšžœ˜—K˜K˜—šกœžœžœ˜$Kšœ žœ˜Kšœžœ˜Kšœž˜Kšœžœ˜ Kšœžœ ˜K˜#Kšœ˜K˜&K˜ šžœžœ˜K˜+K˜#š žœžœžœžœžœžœ(ž˜Jšžœžœ˜šœ žœžœ˜šœžœž˜K™-K™—š กœžœžœ žœžœžœ˜/Kšžœžœžœ ˜+K˜K˜K˜—šก œžœžœ žœžœžœžœžœžœ˜dKšœžœ˜Kšœ˜K˜"K˜Kšžœ)žœžœ˜Nšžœžœžœ#˜6K™4—šžœž˜šœ ˜ ˜-K˜K˜=K˜—šžœžœ˜K˜K˜ K˜šžœžœžœ žœ˜5˜MKšžœ˜K˜——K˜—K˜—K˜8šžœ˜ Kšœ*žœ&˜SKšžœ˜Kšœ˜——Kšžœžœžœ˜Kšœžœ˜ K˜žK˜4š žœžœžœžœžœžœž˜?Kšœ˜Kšœ˜š žœ'žœžœžœ ˜NK™r—K˜]šžœž˜Kšœžœ  ˜-Kšœ1žœ˜6Kšžœ˜—K˜Kšžœžœžœ˜Kšœ=™=—˜ Kšœb˜b—šžœž˜K˜šœ˜˜GJ™3—Kšžœ˜Kšœ˜—Kšœžœ '˜=šžœ3žœ˜BJšœ@™@——Kšžœ˜—Kšœžœ˜!šžœ žœ ž˜!KšœMžœžœ ˜`—šž˜Kšœ0žœ˜5—K˜—K˜—™ K˜šกœž œ#žœžœ˜DKšžœžœ  œ˜.Kšœžœžœ ˜šžœž˜š žœžœžœ+žœžœž˜OK˜Kšžœ&žœžœ˜4Kšžœ˜——Kšœžœ˜ Kšžœ žœžœ˜Jšœ™šœžœ˜&KšœY˜Y—Kšœžœ˜K˜8K˜K˜—šกœžœžœ$žœ˜CKšœžœžœžœ˜Bšžœžœžœžœ˜#Kšœ8žœ˜B—Kšœžœ˜Kšžœžœžœ˜!Kšœžœ˜Kšžœžœžœ"˜3K˜K˜—šก œžœžœžœ˜TK˜ K˜Kšœžœ˜Kšœžœ˜K˜"Kšœ'žœ˜-K˜'K˜K˜X™@J™J™—K˜'šžœžœž˜šœžœ 4˜KJ™”—˜yK™WK˜EK˜MK˜DK˜$K˜"K˜˜ šœF˜FKšœ#žœ˜)——Kšžœ žœžœ 7˜Pšžœ˜Kšœžœ˜'K˜šžœžœž˜Bšœ ž˜$K˜2——K˜—K˜—Kšžœ˜—šžœ ž˜K˜PKšœžœžœ)˜Uš œ žœ žœ ะct +ค˜SK˜EK˜!K˜8Kšžœžœ'˜O—Kšžœ˜ —šžœžœžœ!˜]KšœR™R—Kšœ)žœ˜.J™๏Kšž œ˜K˜K˜—šœžœ˜K˜—šกœžœžœ˜K˜K˜Kšœžœ˜Kšœ žœž˜Kšœ#žœ˜'Kšœ$ž˜'K˜Kšžœžœ˜šž˜K˜K˜+šœ žœžœž˜%Kšœ0žœžœžœ˜E—Kš žœžœ žœžœ žœ˜X˜KšœP˜PKšœ~˜~—šžœž˜K˜'šœ˜K˜šžœ#žœ˜8K™5—Kšžœ žœ$žœžœ˜EKšœ˜—šžœ˜ KšœE˜EJ™š——Kšžœ˜Kšžœ˜—K˜K˜—šกœžœžœUžœ žœžœ'žœ(žœ žœžœ˜๊Kšžœžœžœ˜4J™ฤKšœ˜šœž˜(Kšœžœžœž˜Kšœ$ '˜KK˜—K˜Kšžœ)žœžœ˜Nšžœž˜šœ ˜ ˜-K˜K˜>Kšœ˜K˜K˜K˜K˜ K˜—šžœžœ˜K˜K˜ K˜šžœžœžœ žœ˜5˜MKšžœ˜K˜——K˜—K˜—K˜gšœ žœžœ˜Kšœ*žœ&˜SKšžœ˜K˜—šžœ˜ Kšœ*žœ&˜SKšžœ˜Kšœ˜——Kšžœžœžœžœ˜1Kšœžœ˜ Kšžœžœžœ˜&˜ K˜A—šžœž˜Kšœ žœ˜šœ˜˜GJ™3—Kšžœ˜Kšœ˜—šœ˜K˜`Kšžœ˜Kšœ˜—šžœ0˜7Jšœ@™@——K˜K˜—š ก œžœžœ"žœžœ˜IKšžœžœ˜J™ูKšœ žœ˜#šžœžœžœž˜(šžœž˜&Kš œ žœžœžœžœ˜KKšœ žœ˜Kšžœ˜—KšžœO˜VKšžœ˜šžœž˜&Kšœžœ ˜-Kšžœ˜—Kšžœ˜—K˜PKšžœ žœ˜!Kšœ˜K˜—š กœž œ žœžœžœžœ˜6šžœžœžœ#˜1Kšžœ0žœ˜Q—Kšœ˜K˜—šกœž œ˜Kšœ+žœ žœžœ˜CKšžœ žœžœ˜;K˜šžœ.žœžœž˜@Kšžœžœžœžœ˜F—K˜K˜—š กœž œžœžœ   œ˜gJšœn™nš žœžœžœ+žœžœž˜OKšœžœžœ˜K˜K˜#šžœž˜šœ˜K˜+šžœžœžœ(ž˜8šžœžœžœ˜5Kšœžœ˜ Kšžœ˜K˜—Kšžœ˜—Kšžœž œ˜K˜—Kšžœ˜—Kšžœ˜—Kšœžœ˜ K˜K˜—K˜—™K™K˜—š กœžœžœžœ žœ˜UKšžœžœžœžœ˜šœ<˜