DIRECTORY Commander USING [ CommandProc, Register ], FinchSmarts, GVBasics USING [ MakeKey, Password ], IO, LarkFeepRpcControl USING [ Feep, ImportNewInterface ], LupineRuntime USING [ BindingError ], MBQueue USING [ Create, QueueClientAction ], NameDB USING [ Error, SetAttribute ], NamesRPC USING [ StartConversation ], Nice, Process USING [ Detach, SecondsToTicks, SetTimeout ], PupSocket USING [ GetUniqueID ], RefID USING [ ID ], Rope, RPC USING [ AuthenticateFailed, CallFailed, EncryptionKey, ImportFailed, SetMaxTransmissions, TimeoutEnable, VersionRange ], ThParty USING [ Advance, Alert, CheckIn, ConversationInfo, CreateConversation, DescribeParty, GetConversationInfo, GetParty, GetNumbersForRName, GetPartyFromNumber, GetPartyInfo, LookupServiceInterface, PartyInfo, Unvisit, Visit ], Thrush USING[ ActionReport, ConversationID, ConvEvent, Credentials, InterfaceSpec, NB, none, nullConvID, nullID, PartyID, Reason, ROPE, SHHH, SmartsID, StateInConv, unencrypted ], ThSmarts, ThSmartsRpcControl, ThVersions USING [ FinchVR ], UserProfile USING [ Token], VoiceUtils USING [ CurrentPasskey, CurrentPassword, CurrentRName, InstanceFromNetAddress, MakeRName, NetAddress, OwnNetAddress, Problem, ProblemFR, Registrize, Report, ReportFR ] ; FinchSmartsImpl: CEDAR MONITOR IMPORTS Commander, FinchSmarts, GVBasics, IO, LarkFeepRpcControl, LupineRuntime, MBQueue, NameDB, NamesRPC, Nice, Process, PupSocket, Rope, RPC, ThParty, ThSmartsRpcControl, ThVersions, UserProfile, VoiceUtils EXPORTS FinchSmarts, ThSmarts = { OPEN IO; ConvDesc: TYPE = FinchSmarts.ConvDesc; ConversationID: TYPE = Thrush.ConversationID; nullConvID: ConversationID = Thrush.nullConvID; FinchInfo: TYPE = FinchSmarts.FinchInfo; NB: TYPE = Thrush.NB; none: SHHH = Thrush.none; nullID: RefID.ID = Thrush.nullID; us: FinchSmarts.WhoOriginated = FinchSmarts.us; PartyID: TYPE = Thrush.PartyID; ROPE: TYPE = Thrush.ROPE; SHHH: TYPE = Thrush.SHHH; SmartsID: TYPE = Thrush.SmartsID; StateInConv: TYPE = Thrush.StateInConv; nullInterfaceSpec: Thrush.InterfaceSpec = [serviceID: nullID]; PD: TYPE = FinchSmarts.PD; pd: PUBLIC REF PD _ NEW[PD_[]]; info: FinchInfo; Report: PROC[what: ROPE] = { IF NOT pd.doReports THEN RETURN; VoiceUtils.Report[what, $Finch]; }; BeNice: PROC[r: REF, d: INT] = { IF NOT pd.doNice THEN RETURN; Nice.BeNice[r, d, $Finch, NIL]; }; NConcConvs: PROC[l1, l2: LIST OF ConvDesc] RETURNS [LIST OF ConvDesc] = { IF l1=NIL THEN RETURN[l2]; FOR convs: LIST OF ConvDesc _ l1, convs.rest WHILE convs#NIL DO IF convs.rest=NIL THEN { convs.rest_l2; RETURN[l1]; }; ENDLOOP; ERROR; }; DequeueConv: PROC[l1: LIST OF ConvDesc, c: ConvDesc] RETURNS [LIST OF ConvDesc] = { IF l1=NIL THEN RETURN[l1]; -- No list IF l1.first=c THEN RETURN[l1.rest]; -- Head of list FOR convs: LIST OF ConvDesc _ l1, convs.rest WHILE convs.rest#NIL DO IF convs.rest.first=c THEN { convs.rest _ convs.rest.rest; RETURN[l1]; }; -- Internal to list ENDLOOP; RETURN[l1]; -- Not in list }; Progress: PUBLIC ENTRY PROC[ shh: SHHH, convEvent: Thrush.ConvEvent ] = { ENABLE UNWIND => NULL; -- RestoreInvariant; IF convEvent.self.partyID = convEvent.other.partyID THEN [] _ NoteNewState[convEvent]; }; Substitution: PUBLIC ENTRY PROC[ shh: SHHH, convEvent: Thrush.ConvEvent, oldPartyID: Thrush.PartyID, newPartyID: Thrush.PartyID ] = { ENABLE UNWIND => NULL; -- RestoreInvariant; cDesc: ConvDesc = GetConv[convEvent.self.convID, FALSE]; IF cDesc#NIL THEN cDesc.numParties _ 0; -- This will force the information update. [] _ NoteNewState[ convEvent ]; }; ReportAction: PUBLIC ENTRY PROC[ shh: SHHH, report: Thrush.ActionReport ] = { ENABLE UNWIND => NULL; ReportRequestState[GetConv[report.self.convID], report]; BROADCAST info.stateChange; -- Sometimes callers wait (Ugh!)} }; CheckIn: PUBLIC ENTRY PROC[ shh: Thrush.SHHH, credentials: Thrush.Credentials, reason: Thrush.Reason, nextScheduledCheck: INT ] = { ENABLE UNWIND => NULL; info.nextScheduledCheck _ nextScheduledCheck; SELECT reason FROM $goodbye => IF pd.connected THEN [] _ FinchSmarts.QUit[$serverDown, "Disconnect requested by server"]; $welcome => IF ~pd.connected THEN -- Earlier disconnected assumption was wrong. [] _ FinchSmarts.QUit[$success]; ENDCASE; NOTIFY info.pollCondition; }; Poke: PUBLIC ENTRY PROC = { ENABLE UNWIND => NULL; IF info#NIL THEN NOTIFY info.pollCondition; }; LimitSignalling: ENTRY PROC [cDesc: ConvDesc] ~ TRUSTED { ENABLE UNWIND => NULL; c: CONDITION; Process.SetTimeout[@c, Process.SecondsToTicks[pd.timeoutInitiating]]; WAIT c; IF cDesc.situation.self.state # $initiating THEN RETURN; []_Request[cDesc, $failed, $noAnswer, "Other party failed to respond to call."]; }; CallInfo: TYPE = REF CallInfoBody; -- needed because MBQueue requires a REF CallInfoBody: TYPE = RECORD [ convID: Thrush.ConversationID, calledPartyID: Thrush.PartyID_nullID, reason: Thrush.Reason_NIL, comment: ROPE_NIL ]; DisconnectCall: PUBLIC ENTRY PROC[ convID: Thrush.ConversationID, reason: Thrush.Reason _ $terminating, comment: ROPE_NIL, newState: StateInConv _ $idle] = { ENABLE UNWIND => NULL; -- RestoreInvariant; cDesc: ConvDesc; state: StateInConv; IF NOT(([,cDesc, state]_FinchOn[convID]).on) THEN RETURN; SELECT state FROM $idle, $neverWas => { ReportConversationState[$convNotActive, NIL, "No conversation to disconnect"]; }; ENDCASE => []_Request[cDesc, newState, reason, comment]; }; AnswerCall: PUBLIC ENTRY PROC[convID: Thrush.ConversationID] = { ENABLE UNWIND => NULL; -- RestoreInvariant; cDesc: ConvDesc; state: StateInConv; IF NOT (([,cDesc, state]_FinchOn[convID]).on) THEN RETURN; SELECT state FROM $ringing, $notified => []_Request[cDesc, $active]; ENDCASE; }; PlaceCall: PUBLIC ENTRY PROC [ convID: Thrush.ConversationID_nullConvID, rName: ROPE, -- rName or description number: ROPE, -- telephone number, if present useNumber: BOOL_FALSE ] = { ENABLE UNWIND=>NULL; -- RestoreInvariant; calledPartyID: PartyID; nb: NB; reason: Thrush.Reason _ $notFound; comment: Rope.ROPE _ NIL; IF ~useNumber THEN { [nb, calledPartyID] _ThParty.GetParty[shh:info.shh, partyID:info.partyID, rName:rName]; SELECT nb FROM $noSuchParty2, $noIdentSupplied => useNumber _ TRUE; $serverDown, $notEnabled, $noSuchParty => RETURN; -- Futile ENDCASE; }; IF useNumber AND number.Length[]>0 THEN [nb, calledPartyID] _ ThParty.GetPartyFromNumber[ shh: info.shh, partyID: info.partyID, phoneNumber: number, description: rName]; SELECT nb FROM $success => reason _ NIL; -- reason: NIL, comment: NIL $noSuchParty2 => NULL; -- reason: $notFound, comment: NIL $narcissism => comment _ "Attempt to call self rejected on philosophical grounds"; ENDCASE => { ReportConversationState[nb, NIL, NIL]; RETURN; }; [--nb, cDesc--]_Connect[calledPartyID, convID, reason, comment]; }; Feep: PUBLIC ENTRY PROC[convID: ConversationID, feepString: ROPE] = { ENABLE UNWIND => NULL; cDesc: ConvDesc; nb: Thrush.NB_$success; { ENABLE { RPC.CallFailed => { IF cDesc=NIL THEN CONTINUE; cDesc.feepInterface _ NIL; RETRY; -- Will attempt reconnection via ThParty }; RPC.ImportFailed => { nb _ $noFeepService; CONTINUE; }; }; state: StateInConv; IF NOT(([,cDesc, state]_FinchOn[convID]).on) OR state#$active THEN nb _ $convNotActive; IF nb=$success AND cDesc.feepInterface=NIL THEN { [nb, , cDesc.feepInterfaceSpec] _ ObtainServiceInterface[NIL, "LarkFeep", cDesc]; IF nb=$success THEN -- Import every time, for luck. cDesc.feepInterface _ LarkFeepRpcControl.ImportNewInterface[ interfaceName: cDesc.feepInterfaceSpec.interfaceName, hostHint: cDesc.feepInterfaceSpec.hostHint!RPC.ImportFailed => CONTINUE ]; }; IF nb=$success THEN nb _ LarkFeepRpcControl.Feep[ interface: cDesc.feepInterface, shhh: info.shh, serviceID: cDesc.feepInterfaceSpec.serviceID, convID: cDesc.situation.self.convID, requestingParty: cDesc.situation.self.partyID, number: feepString, noisy: TRUE, on: 100, off: 80 ]; }; IF nb # $success THEN VoiceUtils.Report["Sorry, was not able to `feep'.", $Finch]; }; ObtainNumbersForRName: PUBLIC PROC[rName: ROPE] RETURNS [fullRName: ROPE, number: ROPE, homeNumber: ROPE] = { [fullRName, number, homeNumber] _ ThParty.GetNumbersForRName[shh: info.shh, rName: rName]; }; IdentifyVisitor: PUBLIC ENTRY PROC [visitor, password: Rope.ROPE, complain: BOOL _ TRUE] RETURNS [nb: NB] ~ { ENABLE UNWIND => NULL; visitingPartyID: PartyID; visitingPassKey: GVBasics.Password _ GVBasics.MakeKey[password]; visitor _ VoiceUtils.Registrize[visitor]; IF Rope.Equal[info.myRName, visitor, FALSE] THEN { ReleaseVisitorSelf[]; RETURN }; [nb, visitingPartyID] _ThParty.GetParty[shh:info.shh, partyID:info.partyID, rName:visitor]; IF nb=$success THEN nb _ ThParty.Visit[shh: info.shh, visitedParty: info.partyID, visitingParty: visitingPartyID, visitingPassword: visitingPassKey]; SELECT nb FROM $success => { VoiceUtils.ReportFR["%s visiting %s -- %s calls will ring in both offices.", $Finch, NIL, IO.rope[visitor], IO.rope[info.myRName], IO.rope[visitor]]; }; $passwordNotValid => IF complain THEN VoiceUtils.ReportFR["Invalid password for %s.", $Finch, NIL, IO.rope[visitor]]; $noSuchParty2 => IF complain THEN VoiceUtils.ReportFR["Couldn't identify visiting party %s.", $Finch, NIL, IO.rope[visitor]]; ENDCASE => Problem["Server communication problem", nb]; }; ReleaseVisitor: PUBLIC ENTRY PROC [visitor, password: Rope.ROPE] ~ { ENABLE UNWIND => NULL; nb: NB; visitingPartyID: PartyID; visitingPassKey: GVBasics.Password _ GVBasics.MakeKey[password]; IF visitor.Length[]=0 THEN { -- assume cancelling visiting at home via "Unvisit" ReleaseVisitorSelf[]; RETURN }; visitor _ VoiceUtils.Registrize[visitor]; IF Rope.Equal[info.myRName, visitor, FALSE] THEN { ReleaseVisitorSelf[]; RETURN }; [nb, visitingPartyID] _ThParty.GetParty[shh:info.shh, partyID:info.partyID, rName:visitor]; IF nb = $success THEN { visitee: PartyID; [visitee: visitee] _ ThParty.DescribeParty[partyID: visitingPartyID, nameReq: $none]; IF visitee=info.partyID THEN { nb _ ThParty.Unvisit[shh: info.shh, visitedParty: visitee, visitingParty: visitingPartyID, visitingPassword: visitingPassKey]; }; }; SELECT nb FROM $success => VoiceUtils.ReportFR["Visiting cancelled for %s.", $Finch, NIL, IO.rope[visitor]]; $noSuchParty2, $noSuchVisiting => VoiceUtils.ReportFR["%s was not visiting here", $Finch, NIL, IO.rope[visitor]] ENDCASE => Problem["Server communication problem", nb]; }; ReleaseVisitorSelf: INTERNAL PROC ~ { nb: NB _ $noSuchParty; visitee: PartyID; [visitee: visitee] _ ThParty.DescribeParty[partyID: info.partyID, nameReq: $none]; IF visitee # nullID THEN nb _ ThParty.Unvisit[shh: info.shh, visitedParty: visitee, visitingParty: info.partyID, visitingPassword: VoiceUtils.CurrentPassword[]]; SELECT nb FROM $success => VoiceUtils.ReportFR[ "Visiting cancelled for %s. Welcome home.", $Finch, NIL, IO.rope[info.myRName]]; $noSuchParty2, $noSuchVisiting => VoiceUtils.ReportFR["%s was not visiting anywhere", $Finch, NIL, IO.rope[info.myRName]]; ENDCASE => Problem["Server communication problem", nb]; }; ServiceConnect: PUBLIC ENTRY PROC[ serviceName: ROPE, convID: ConversationID_nullConvID, createOK: BOOL_TRUE ] RETURNS [ nb: NB_$success, cDesc: ConvDesc_NIL ] = { ENABLE UNWIND=>NULL; state: StateInConv; calledPartyID: PartyID; IF NOT (([,cDesc, state]_FinchOn[convID]).on) THEN RETURN[nb: $finchNotRunning]; IF cDesc=NIL THEN [convID, cDesc, state] _ GetActiveConversation[serviceName]; -- Hack SELECT state FROM idle, reserved => { IF ~createOK THEN RETURN[$noSuchConv, NIL]; IF serviceName = NIL THEN calledPartyID _ nullID ELSE [nb, calledPartyID] _ ThParty.GetParty[ shh: info.shh, partyID: info.partyID, rName: serviceName, type: $service]; IF nb=$success THEN [nb, cDesc] _ Connect[calledPartyID, convID, NIL, NIL]; }; ENDCASE => IF cDesc=NIL THEN nb _ $convStillActive; -- Another service already in use. IF nb=$success AND serviceName#NIL THEN nb _ WaitForActive[cDesc]; }; ObtainServiceInterface: PUBLIC PROC[ serviceName: ROPE, interfaceName: ROPE, cDesc: ConvDesc_NIL ] RETURNS [ nb: NB_$success, shhh: Thrush.SHHH, interfaceSpec: Thrush.InterfaceSpec ] = { partyID: Thrush.PartyID_Thrush.nullID; credentials: Thrush.Credentials; IF cDesc#NIL THEN { credentials _ cDesc.situation.self; IF cDesc.numParties>1 THEN partyID _ cDesc.partyInfo[1].partyID; } ELSE IF serviceName = NIL THEN partyID _ info.partyID -- get from self! ELSE { -- want interface only credentials _ [info.partyID, info.smartsID]; [nb, partyID] _ThParty.GetParty[ shh: info.shh, partyID: info.partyID, rName: serviceName, type: $service]; IF nb#$success THEN RETURN; }; IF partyID = Thrush.nullID THEN RETURN[nb: $noSuchParty2, shhh: none, interfaceSpec: nullInterfaceSpec]; [nb, interfaceSpec] _ ThParty.LookupServiceInterface[info.shh, credentials, partyID, interfaceName]; shhh _ info.shh; }; GetConv: INTERNAL PROC[convID: ConversationID, createOK: BOOL_FALSE] RETURNS [ cDesc: ConvDesc_NIL ] = --INLINE-- { cL: LIST OF ConvDesc; IF convID#nullConvID THEN FOR convs: LIST OF ConvDesc _ info.conversations, convs.rest WHILE convs#NIL DO cDesc _ convs.first; IF cDesc.situation.self.convID = convID THEN RETURN; ENDLOOP; cDesc _ NIL; IF ~createOK THEN RETURN; cDesc _ NEW[FinchSmarts.ConvDescBody_[ situation: [ self: [ convID: convID, smartsID: info.smartsID, partyID: info.partyID ]]]]; cL _ LIST[cDesc]; info.conversations _ NConcConvs[info.conversations, cL]; }; FinchOn: INTERNAL PROC[convID: ConversationID_nullConvID] RETURNS [ on: BOOL_FALSE, cDesc: ConvDesc_NIL, state: StateInConv_$idle] = { IF ~pd.enabled OR info=NIL THEN { VoiceUtils.Report["Your Finch is not running", $Finch]; RETURN; }; on_TRUE; IF convID=nullConvID THEN RETURN; cDesc _ GetConv[convID, FALSE]; IF cDesc#NIL THEN state_cDesc.situation.self.state; }; NoteNewState: INTERNAL PROC[convEvent: Thrush.ConvEvent] RETURNS[cDesc: ConvDesc]= { cInfo: ThParty.ConversationInfo; pInfo: ThParty.PartyInfo; nb: NB; cDesc _ GetConv[convEvent.self.convID, TRUE]; cDesc.situation _ convEvent^; [nb, cInfo] _ ThParty.GetConversationInfo[shh: info.shh, convID: convEvent.self.convID]; SELECT TRUE FROM nb # $success => NULL; -- Problem["Can't obtain conversation information"]; cDesc.numParties#cInfo.numParties, cDesc.numActive#cInfo.numActive, cDesc.numIdle#cInfo.numIdle => { cDesc.subject _ cInfo.subject; cDesc.startTime _ cInfo.startTime; cDesc.numParties _ cInfo.numParties; cDesc.numActive _ cInfo.numActive; cDesc.numIdle _ cInfo.numIdle; [nb, pInfo] _ ThParty.GetPartyInfo[shh: info.shh, credentials: cDesc.situation.self, nameReq: $description, allParties: TRUE]; IF nb#$success THEN NULL -- Problem["Can't obtain conversation information", nb] ELSE { cDesc.partyInfo _ pInfo; IF cDesc.numParties>=1 THEN IF pInfo[0].partyID=cInfo.originator THEN cDesc.whoOriginated _ us ELSE FOR i: INT IN [1..cDesc.numParties) DO IF pInfo[i].partyID=cInfo.originator THEN { cDesc.whoOriginated _ i; IF pInfo[i].type=$trunk THEN pInfo[i].intendedName _ "outside line"; }; ENDLOOP; }; }; ENDCASE; SELECT cDesc.situation.self.state FROM $idle, $neverWas => info.conversations _ DequeueConv[info.conversations, cDesc]; $initiating => TRUSTED { Process.Detach[FORK LimitSignalling[cDesc]]; }; $notified => SELECT filtering FROM -- Needs tests for calls already in progress.  1 => []_Request[cDesc, $idle, $notImportantEnough, "This is a test"]; 2 => []_Request[cDesc, $ringing]; 3 => []_Request[cDesc, $active, $whatever, "Hot line!"]; ENDCASE; ENDCASE => cDesc.ultimateState _ cDesc.situation.self.state; ReportConversationState[$success, cDesc, NIL]; BROADCAST info.stateChange; }; Request: INTERNAL PROC[ cDesc: ConvDesc, state: StateInConv, reason: Thrush.Reason_NIL, comment: ROPE_NIL ] RETURNS [nb: NB] = { DO convEvent: Thrush.ConvEvent; reportToAll: BOOL = SELECT state FROM $idle, $failed, $active, $inactive, $ringing => TRUE, ENDCASE=>FALSE; IF state=$failed AND cDesc.partyInfo.numParties>=1 AND ~cDesc.partyInfo[0].voicePath THEN state _ $idle; [nb, convEvent] _ ThParty.Advance[shhh: info.shh, credentials: cDesc.situation.self, state: state, reportToAll: reportToAll, reason: reason, comment: comment]; SELECT nb FROM $success => []_NoteNewState[convEvent]; $stateMismatch => { cDesc_NoteNewState[convEvent]; IF state=$idle AND cDesc.situation.self.state # $idle THEN LOOP; }; ENDCASE => ReportConversationState[nb, cDesc, "Telephone server action failed"]; EXIT; ENDLOOP; }; Connect: INTERNAL PROC [calledPartyID: PartyID, convID: ConversationID_nullConvID, reason: Thrush.Reason _ NIL, comment: ROPE_NIL] RETURNS [ nb: NB_$success, cDesc: ConvDesc_NIL ] = { state: StateInConv; newState: StateInConv = SELECT TRUE FROM reason#NIL => $failed, calledPartyID = nullID => $parsing, -- temp until can debug inactive stuff. ENDCASE => $initiating; convEvent: Thrush.ConvEvent; IF ~(([,cDesc, state]_FinchOn[convID]).on) THEN { nb _ $notEnabled; RETURN; }; SELECT state FROM $idle => { [nb, convEvent] _ ThParty.CreateConversation[ shhh: info.shh, credentials: [partyID: info.partyID, smartsID: info.smartsID], state: newState, reason: reason, comment: comment ]; IF nb = $success THEN { cDesc _ NoteNewState[convEvent]; IF cDesc.partyInfo=NIL OR cDesc.partyInfo.numParties=0 OR ~cDesc.partyInfo[0].voicePath THEN { [] _ Request[cDesc, $failed, $noVoicePath, "Your Etherphone is not running"]; RETURN[$noVoicePath, cDesc]; }; }; }; $reserved, $parsing => nb _ Request[cDesc, newState, reason, comment]; ENDCASE => { ReportConversationState[$convStillActive, NIL, "Conversation already in progress"]; RETURN; }; IF nb # $success OR newState=$failed THEN RETURN; cDesc.originatorRecorded _ TRUE; IF newState # $initiating THEN RETURN; nb_ThParty.Alert[shhh: info.shh, credentials: cDesc.situation.self, calledPartyID: calledPartyID]; SELECT nb FROM $success => NULL; $stateMismatch => { nb_Request[cDesc, $failed, $error, "Conversation already in progress"]; 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] RETURNS [nb: NB_$success] = { FOR i: NAT IN [0..pd.waitsForConnect) DO SELECT cDesc.situation.self.state FROM $active => RETURN; $failed => EXIT; ENDCASE; TRUSTED{Process.SetTimeout[@info.stateChange,Process.SecondsToTicks[pd.jayTimeout]];}; WAIT info.stateChange; -- <> SELECT cDesc.situation.self.state FROM $idle, $neverWas => EXIT; -- something wrong. ENDCASE; ENDLOOP; nb_Request[cDesc, $failed, $error, "Finch failed to connect to voice service."]; IF nb=$success THEN nb_$timedOut; }; GetActiveConversation: INTERNAL PROC[serviceName: ROPE_NIL] RETURNS[convID: ConversationID_Thrush.nullConvID, cDesc: ConvDesc_NIL, state: StateInConv_$idle] = { IF serviceName=NIL THEN RETURN; -- Can't validate FOR convs: LIST OF ConvDesc _ info.conversations, convs.rest WHILE convs#NIL DO possibleCDesc: ConvDesc _ NARROW[convs.first]; possibleState: StateInConv _ possibleCDesc.situation.self.state; SELECT possibleState FROM $reserved => {cDesc _ possibleCDesc; LOOP;}; $initiating, $ringback, $active => NULL; ENDCASE => LOOP; IF possibleCDesc.partyInfo=NIL OR possibleCDesc.partyInfo.numParties<2 OR possibleCDesc.partyInfo[1].type#$service OR ~Rope.IsPrefix[serviceName, possibleCDesc.partyInfo[1].name, FALSE] THEN LOOP; cDesc _ possibleCDesc; EXIT; ENDLOOP; IF cDesc#NIL THEN { convID _ cDesc.situation.self.convID; state _ cDesc.situation.self.state; }; }; Problem: PUBLIC PROC[remark: ROPE_NIL, nb: NB_NIL] = { IF nb=NIL THEN VoiceUtils.Problem[remark, $Finch] ELSE VoiceUtils.ProblemFR["%g, reason: %g", $Finch, NIL, rope[remark], atom[nb]]; }; NewID: PROC RETURNS[LONG CARDINAL] ={RETURN[LOOPHOLE[PupSocket.GetUniqueID[]]]}; InitFinchSmarts: PUBLIC ENTRY PROC [thrushInstance: Thrush.ROPE_NIL, s: FinchSmarts.ReportSystemStateProc _ NIL, c: FinchSmarts.ReportConversationStateProc _ NIL, r: FinchSmarts.ReportRequestStateProc _ NIL, before: BOOL _ TRUE] = { problem: ROPE_NIL; nb: NB_$serverDown; { ENABLE { RPC.CallFailed => { problem _ "Communication Failure"; GOTO InitFailed; }; UNWIND => NULL; }; prevInfo: FinchSmarts.FinchInfo _ info; IF pd.connected THEN UninitFinchSmartsInt[]; info _ NEW[FinchSmarts.FinchInfoBody]; -- Dump any old one! IF prevInfo#NIL THEN NOTIFY prevInfo.pollCondition; info.conversations _ NIL; RegisterForReportsInt[s: ClearInfo]; RegisterForReportsInt[s, c, r, before]; thrushInstance _ VoiceUtils.MakeRName[style: rName, name: SELECT TRUE FROM thrushInstance#NIL => thrushInstance, prevInfo#NIL AND prevInfo.prevThrushInstance#NIL => prevInfo.prevThrushInstance, ENDCASE=> UserProfile.Token["ThrushClientServerInstance", "Strowger.Lark"]]; info.prevThrushInstance _ thrushInstance; info.myName _ [ type: "ThSmarts.Lark", instance: VoiceUtils.InstanceFromNetAddress[netAddress: VoiceUtils.OwnNetAddress[], suffix: "0"], version: ThVersions.FinchVR]; info.myRName _ VoiceUtils.CurrentRName[]; info.myPassword _ VoiceUtils.CurrentPasskey[]; ThSmartsRpcControl.UnexportInterface[!LupineRuntime.BindingError=>CONTINUE]; ThSmartsRpcControl.ExportInterface[ interfaceName: info.myName, user: info.myRName, password: info.myPassword]; info.shh _ IF NOT pd.encryptionRequested THEN Thrush.unencrypted ELSE NamesRPC.StartConversation [ caller: info.myRName, callee: thrushInstance, key: info.myPassword, level: --<>--CBCCheck ! RPC.AuthenticateFailed=> { problem_"Could not authenticate"; GOTO InitFailed}]; IF info.shh # Thrush.unencrypted THEN RPC.SetMaxTransmissions[info.shh, pd.maxTransmissions, pd.timeoutEnable]; TRUSTED { Process.Detach[FORK PollWaiter[info]]; }; nb _ FinchSmarts.FinchRegister[ info: info, interfaceName: [type: "ThParty.Lark", instance: thrushInstance]]; EXITS InitFailed => Problem[problem, nb]; };}; ClearInfo: ENTRY FinchSmarts.ReportSystemStateProc = { ENABLE UNWIND => NULL; IF connected OR info=NIL THEN RETURN; info.shh_none; FOR convs: LIST OF ConvDesc _ info.conversations, convs.rest WHILE convs#NIL DO convs.first.clientData _ NIL; convs.first.props _ NIL; -- probably unnecessary. ENDLOOP; info.conversations _ NIL; }; UninitFinchSmarts: PUBLIC ENTRY PROC = { UninitFinchSmartsInt[]; }; UninitFinchSmartsInt: INTERNAL PROC = { IF pd.enabled AND info=NIL THEN ERROR; IF info#NIL THEN [] _ FinchSmarts.FinchDeregister[info]; }; PollWaiter: ENTRY PROC[myInfo: FinchSmarts.FinchInfo] = { ENABLE UNWIND => NULL; nextSched: INT _ ThSmarts.noneScheduled; WHILE myInfo = info DO nSC: INT _ IF nextSched=ThSmarts.noneScheduled THEN pd.pollDefault ELSE nextSched; TRUSTED { Process.SetTimeout[@myInfo.pollCondition, Process.SecondsToTicks[nSC]]; }; WAIT info.pollCondition; IF myInfo#info THEN RETURN; IF info.nextScheduledCheck#0 THEN { nextSched _ info.nextScheduledCheck; info.nextScheduledCheck _ 0; LOOP; }; IF ~pd.enabled OR ~pd.doPollingTimeouts OR (pd.connected AND nextSched=ThSmarts.noneScheduled) THEN LOOP; [] _ ThParty.CheckIn[shh: info.shh, credentials: [ partyID: info.partyID, smartsID: info.smartsID, convID: Thrush.nullConvID]]; ENDLOOP; }; FinchIsRunning: PUBLIC PROC RETURNS [finchIsEnabled: BOOL, finchIsRunning: BOOL] = { RETURN[pd.enabled, pd.connected]; }; RegisterForReports: PUBLIC ENTRY PROC [s: FinchSmarts.ReportSystemStateProc _ NIL, c: FinchSmarts.ReportConversationStateProc _ NIL, r: FinchSmarts.ReportRequestStateProc _ NIL, before: BOOL _ TRUE] = { ENABLE UNWIND => NULL; RegisterForReportsInt[s, c, r, before]; }; RegisterForReportsInt: INTERNAL PROC [s: FinchSmarts.ReportSystemStateProc _ NIL, c: FinchSmarts.ReportConversationStateProc _ NIL, r: FinchSmarts.ReportRequestStateProc _ NIL, before: BOOL _ TRUE] = { IF info=NIL THEN RETURN; UnRegisterForReportsInt[s, c, r]; -- Multiple registrations do not hurt anything. IF s # NIL THEN -- System state registrations are always treated as "before". info.systemStateSubscribers _ CONS[s, info.systemStateSubscribers]; IF before THEN { IF c # NIL THEN info.convStateSubscribers _ CONS[c, info.convStateSubscribers]; IF r # NIL THEN info.requestStateSubscribers _ CONS[r, info.requestStateSubscribers]; } ELSE { IF c # NIL THEN IF info.convStateSubscribers=NIL THEN info.convStateSubscribers _ LIST[c] ELSE FOR l: LIST OF FinchSmarts.ReportConversationStateProc _ info.convStateSubscribers, l.rest DO IF l.rest=NIL THEN { l.rest _ LIST[c]; EXIT }; ENDLOOP; IF r # NIL THEN IF info.requestStateSubscribers=NIL THEN info.requestStateSubscribers _ LIST[r] ELSE FOR l: LIST OF FinchSmarts.ReportRequestStateProc _ info.requestStateSubscribers, l.rest DO IF l.rest=NIL THEN { l.rest _ LIST[r]; EXIT }; ENDLOOP; }; }; UnRegisterForReports: PUBLIC ENTRY PROC [s: FinchSmarts.ReportSystemStateProc _ NIL, c: FinchSmarts.ReportConversationStateProc _ NIL, r: FinchSmarts.ReportRequestStateProc _ NIL] = { ENABLE UNWIND => NULL; UnRegisterForReportsInt[s, c, r]; }; UnRegisterForReportsInt: INTERNAL PROC [s: FinchSmarts.ReportSystemStateProc _ NIL, c: FinchSmarts.ReportConversationStateProc _ NIL, r: FinchSmarts.ReportRequestStateProc _ NIL] = { IF info=NIL THEN RETURN; IF s # NIL AND info.systemStateSubscribers # NIL THEN { prev: LIST OF FinchSmarts.ReportSystemStateProc _ NIL; FOR list: LIST OF FinchSmarts.ReportSystemStateProc _ info.systemStateSubscribers, list.rest UNTIL list=NIL DO IF list.first=s THEN { IF prev = NIL THEN info.systemStateSubscribers _ list.rest ELSE prev.rest _ list.rest; EXIT }; prev _ list; ENDLOOP; }; IF c # NIL AND info.convStateSubscribers # NIL THEN { prev: LIST OF FinchSmarts.ReportConversationStateProc _ NIL; FOR list: LIST OF FinchSmarts.ReportConversationStateProc _ info.convStateSubscribers, list.rest UNTIL list=NIL DO IF list.first=c THEN { IF prev = NIL THEN info.convStateSubscribers _ list.rest ELSE prev.rest _ list.rest; EXIT }; prev _ list; ENDLOOP; }; IF r # NIL AND info.requestStateSubscribers # NIL THEN { prev: LIST OF FinchSmarts.ReportRequestStateProc _ NIL; FOR list: LIST OF FinchSmarts.ReportRequestStateProc _ info.requestStateSubscribers, list.rest UNTIL list=NIL DO IF list.first=r THEN { IF prev = NIL THEN info.requestStateSubscribers _ list.rest ELSE prev.rest _ list.rest; EXIT }; prev _ list; ENDLOOP; }; }; ReportConversationState: PUBLIC PROC [nb: NB, cDesc: ConvDesc, remark: Rope.ROPE] ~ { IF info=NIL THEN RETURN; pd.requests.QueueClientAction[ReallyReportConversationState, NEW[CSBody _ [nb, cDesc, remark]]]; }; CS: TYPE = REF CSBody; CSBody: TYPE = RECORD[nb: NB, cDesc: ConvDesc, remark: Rope.ROPE]; ReallyReportConversationState: PROC[r: REF] = { cS: CS _ NARROW[r]; IF info=NIL THEN RETURN; FOR l: LIST OF FinchSmarts.ReportConversationStateProc _ info.convStateSubscribers, l.rest UNTIL l=NIL DO l.first[cS.nb, cS.cDesc, cS.remark]; ENDLOOP; }; ReportRequestState: PROC [cDesc: ConvDesc, actionReport: Thrush.ActionReport] ~ { IF info=NIL THEN RETURN; pd.requests.QueueClientAction[ReallyReportRequestState, NEW[RSBody _ [cDesc, actionReport]]]; }; RS: TYPE = REF RSBody; RSBody: TYPE = RECORD[cDesc: ConvDesc, actionReport: Thrush.ActionReport]; ReallyReportRequestState: PROC[r: REF] = { rS: RS _ NARROW[r]; actionRequest: REF_NIL; IF info=NIL THEN RETURN; FOR l: LIST OF FinchSmarts.ReportRequestStateProc _ info.requestStateSubscribers, l.rest UNTIL l=NIL DO actionRequest _ l.first[rS.cDesc, rS.actionReport, actionRequest]; ENDLOOP; }; ViewCmd: Commander.CommandProc = TRUSTED { Nice.View[pd, "Finch PD"]; }; SetFiltering: PROC[which: INT, deferAnswer: ROPE_ "true"] RETURNS [didIt: ATOM_NIL, message: Rope.ROPE_NIL] _ { ENABLE NameDB.Error => {didIt _ $Failure; message_"Database access failure"; CONTINUE; }; filtering _ which; NameDB.SetAttribute[VoiceUtils.CurrentRName[], $deferanswer, deferAnswer]; }; filtering: INT_0; FilterResetCmd: Commander.CommandProc = TRUSTED { [result, msg] _ SetFiltering[0, "false"]; }; FilterZeroCmd: Commander.CommandProc = TRUSTED { [result, msg] _ SetFiltering[0]; }; FilterOneCmd: Commander.CommandProc = TRUSTED { [result, msg] _ SetFiltering[1]; }; FilterTwoCmd: Commander.CommandProc = TRUSTED { [result, msg] _ SetFiltering[2]; }; FilterThreeCmd: Commander.CommandProc = TRUSTED { [result, msg] _ SetFiltering[3]; }; pd.requests _ MBQueue.Create[]; Commander.Register["VuFinch", ViewCmd, "Program Management variables Finch"]; Commander.Register["FReset", FilterResetCmd, "Reset filtration method. This is a test."]; Commander.Register["F0", FilterZeroCmd, "Zeroth filtration method. This is a test."]; Commander.Register["F1", FilterOneCmd, "First filtration method. This is a test."]; Commander.Register["F2", FilterTwoCmd, "Second filtration method. This is a test."]; Commander.Register["F3", FilterThreeCmd, "Third filtration method. This is a test."]; }. $.FinchSmartsImpl.mesa Copyright Σ 1984, 1986, 1987 by Xerox Corporation. All rights reserved. Last Edited by: Swinehart, July 24, 1987 2:21:15 pm PDT Doug Terry, September 5, 1986 4:17:06 pm PDT Polle Zellweger (PTZ) July 27, 1987 11:17:00 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, 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 Perhaps allow a NIL password as default param - GVBasics.MakeKey does the right thing with NIL -- also with ReleaseVisitor, below. 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. 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. 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. Do something interesting  see other smarts for more confusion 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 Conversation well-advanced, but not the right kind. Registration Ordinary case: received another CheckIn report before timeout. Timeout occurred before next CheckIn report, and either we're disconnected or we've been told to worry if we haven't heard from the server. Calling any ThParty function will either succeed or the failure will trigger the best available recovery activities. Warning: This facility has the same risks as most registration mechanisms: running multiple versions of the client code can cause unintended multiple registrations. 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. ReportSystemState: See ThPartyClientImpl.ReportSystemState Debugging nonsense 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 Κ#β˜Jšœ™šœH™HJšœ7™7Icode™,Kšœ3™3—J˜šΟk ˜ Jšœ œ˜*Jšœ ˜ Jšœ œ˜%Jšœ˜Jšœœ˜6Jšœœ˜%Jšœœ˜,Jšœœ˜%Jšœ œ˜%J˜Jšœœ(˜5Jšœ œ˜ Jšœœœ˜Jšœ˜Jšœœs˜|JšœœΪ˜ηšœœ˜ JšœEœ-œœ'˜₯—J˜ Jšœ˜J˜Jšœ œ ˜Jšœ œ’˜²J˜J˜—šœœ˜Jšœ!œ`œB˜ΡJšœ˜!Jšœœ˜J˜—™ J˜Jšœ œ˜&šœœ˜-J˜/—Jšœ œ˜(Jšœœ œ˜Jšœœ˜J˜!Jšœ/˜/Jšœ œ˜Jšœœ œ˜Jšœœ œ˜Jšœ œ˜!Jšœ œ˜'Jšœ>˜>J˜Jšœœœ˜Jš œ œœœœ˜J˜Jšœ˜J˜šΟnœœœ˜Jšœœœœ˜ J˜ J˜—J˜šžœœœœ˜ Jšœœ œœ˜Jšœœ˜J˜J˜—šž œœ œœ œœœ˜IKšœœœœ˜š œœœœœ˜?Kšœ œœœ˜6Kšœ˜—Kšœ˜Jšœ˜J˜—šž œœœœœœœ˜SJš œœœœΟc ˜%Jšœ œœ Ÿ˜3š œœœœ œ˜DJšœœ!œ Ÿ˜]Jšœ˜—JšœŸ˜Jšœ˜J˜——™ J™šžœœœœ˜Jšœœ˜ Jšœ˜Jšœ˜KšœœœŸ˜-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šœœœ˜Jšœ8˜8Jš œŸ œ˜=Jšœ˜J˜—šžœœœœ˜Jšœ œ˜J˜ J˜Jšœ˜J˜Jšœœœ˜Jšœ-˜-šœ˜šœ œ˜ JšœE˜E—šœ œœŸ-˜OJšœ ˜ —Jšœ˜—Jšœ˜J˜J˜—šžœœœœ˜Jšœœœ˜Jšœœœœ˜.J™[J˜—šžœœœœ˜9K™―K™iJšœœœ˜Kšœ œ˜ KšœE˜EKšœ˜Kšœ*œœ˜8K˜QK˜J˜——™J™Jšœ œœŸ(˜Kšœœœ˜Jšœ˜J˜%Jšœœ˜Jšœ œ˜J˜J˜—šžœœœœ˜"Jšœ˜J˜%Jšœ œ˜Jšœ"˜"KšœœœŸ˜-J˜$Jšœœ'œœ˜9šœ˜šœ˜Jšœ(œ#˜NJ˜—Jš œ-˜8—J˜J˜—šž œœœœ#˜@KšœœœŸ˜-J˜$Jšœœ'œœ˜:Jšœœ2œ˜MJšœ˜J˜—šž œœœœ˜Jšœ)˜)JšœœŸ˜$JšœœŸ˜-Jšœ œ˜Jšœ˜JšœœœŸ˜+Jšœ˜Jšœœ˜Jšœ"˜"Jšœœœ˜šœ œ˜JšœW˜Wšœ˜Jšœ/œ˜4Jšœ*œŸ ˜;Jšœ˜—J˜—šœ œ˜'Jšœ1˜1JšœO˜O—šœ˜JšœœŸ˜6JšœœŸ"˜9JšœR˜RJšœ"œœœ˜>—JšœΟtœ  œ4˜BJšœ˜J˜—š žœœœœ%œ˜EJšœœœ˜Jšœ˜Jšœ œ Οb˜šœ˜šœ˜Jšœœœœ˜JšœœœŸ(˜JJšœ˜—Jšœ(œ˜7J˜—J˜Jšœœ'œœ˜Wšœ œœœ˜1J˜Qšœ œŸ˜3šœ<˜ŸΠctŸ’˜Xšœ˜šœ˜Jšœ œœœ˜+Jšœœœ˜0šœ(˜,JšœJ˜J—Jšœ œ.œœ˜KJ˜—Jš œœœœŸ"˜V—Jšœ œ œœ˜BJšœ˜—J™šžœœœ˜$Jšœ œ˜Jšœœ˜Jšœ˜Jšœœ˜ Jšœœ ˜Jšœ œ˜J˜#Jšœ˜J˜&J˜ šœœœ˜J˜#Jšœœ&˜@J˜—Jš œœœœŸ˜GšœœŸ˜J˜,šœ ˜ JšœJ˜J—Jšœ œ˜J˜—šœ˜JšœB˜H—šœ˜KšœO˜O—Kšœ˜J˜—J˜—™ J˜šžœ œ#œœ˜DJšœœŸ œ˜.Jšœœœ ˜šœ˜š œœœ+œœ˜OJšœ˜Jšœ&œœ˜4Jšœ˜——Jšœœ˜ Jšœ œœ˜Jšœ™šœœ˜&JšœY˜Y—Jšœœ˜Jšœ8˜8J˜J˜—šžœœœ$œ˜CJšœœœœ˜Bšœ œœœ˜!Jšœ8œ˜B—Jšœœ˜Jšœœœ˜!Jšœœ˜Jšœœœ"˜3J˜J˜—šž œœœœ˜TJ˜ J˜Jšœœ˜Jšœ'œ˜-J˜JšœX˜X™@J™J™—šœœ˜šœœŸ4˜KJ™”—šœd˜dK™WJ˜J˜"Jšœ$˜$Jšœ"˜"Jšœ˜šœ ˜ šœF˜FJšœ#œ˜)——Jšœ œœŸ7˜Pšœ˜Jšœ˜šœ˜Jšœ#œ˜Cš œœœœ˜+šœ#œ˜+Kšœ˜Kšœœ(˜DK˜—Kšœ˜——J˜—J˜—Jšœ˜—šœ˜&JšœP˜PJšœœœ˜Hš œ œ œŸ’Ÿ+’˜SJ˜EJ˜!J˜8Jšœ˜—Jšœ5˜<—J™οJšœ)œ˜.Jš œ˜J˜—J˜šžœœœ˜J˜J˜Jšœœ˜Jšœ œ˜J˜Jšœœ˜š˜J˜šœ œœ˜%Jšœ0œœœ˜E—šœœœ˜TJšœ˜—šœ˜JšœP˜PJšœ<˜<—šœ˜Jšœ'˜'šœ˜Jšœ˜Jšœ œ$œœ˜@Jšœ˜—šœ˜ JšœE˜EJ™š——Jšœ˜Jšœ˜—J˜J˜—š žœœœUœ œœ˜‚Jšœœœ˜4J™ΔJšœ˜šœ˜(Jšœœœ˜Jšœ$Ÿ'˜KJ˜—J˜Jšœ)œœ˜Nšœ˜šœ ˜ ˜-J˜J˜>Jšœ˜J˜J˜J˜—šœœ˜Jšœ ˜ šœœœ˜9šœœ˜$JšœM˜MJšœ˜J˜——J˜—J˜—JšœF˜Fšœ˜ Jšœ*œ&˜SJšœ˜Jšœ˜——Jšœœœœ˜1Jšœœ˜ Jšœœœ˜&˜ J˜A—šœ˜Jšœ œ˜šœ˜šœG˜GJ™3—Jšœ˜Jšœ˜—šœ˜Jšœ`˜`Jšœ˜Jšœ˜—šœ0˜7Jšœ@™@——J˜J˜—š ž œœœœœ˜KJ™Fšœœœ˜(šœ˜&Jšœ œ˜Jšœ œ˜Jšœ˜—JšœO˜VJšœŸ4˜Kšœ˜&JšœœŸ˜-Jšœ˜—Jšœ˜—J˜PJšœ œ˜!Jšœ˜J˜—š žœœœœœ˜;Jšœ;œ˜dJ™9Jš œ œœœŸ˜1š œœœ+œœ˜OJšœœ˜.Jšœ@˜@šœ˜Jšœ%œ˜,Jšœ#œ˜(Jšœœ˜—šœœœ&˜IJšœ)˜+šœ=œœœ˜NJšœ3™3——Jšœ˜Jšœ˜Jšœ˜—šœœœ˜Kšœ%˜%Kšœ#˜#K˜—J˜J˜—š žœ œ œœœœ˜6šœœœ#˜1Jšœ0œ˜Q—Jšœ˜J˜—šžœœœœœœœ˜PJ˜——™ J˜šžœœ œœœ'œ/œ*œ œœ˜θJšœ œœœ˜(šœ˜Jšœ4œ˜JJšœœ˜J˜—J˜'J˜Jšœœ˜,J˜JšœœŸ˜;Jšœ œœœ˜3Jšœœ˜J˜$J˜'J˜šœ:œœ˜JJšœœœ˜%Jšœ œœœ ˜PJšœE˜L—Jšœ)˜)šœ˜J˜J˜—Jšœ)˜)Jšœ.˜.JšœBœ˜L˜#J˜Jšœ˜Jšœ˜—Jšœ œœœ˜@šœ˜!Jšœ˜Jšœ˜Jšœ˜JšœŸ œ ˜Jšœ:œ˜O—šœ˜%JšœF˜I—Jšœœ˜3J˜šœ˜JšœM˜M—š˜Jšœ#˜#—J˜—J˜šž œœ&˜6Jšœœœ˜Jš œ œœœœ˜%J˜š œœœ+œœ˜OJšœœ˜JšœœŸ˜1Jšœ˜—Jšœœ˜Jšœ˜Jšœ˜—šžœœœœ˜(Jšœ˜˜J˜——šžœœœ˜'Jš œ œœœœ˜&Jšœœœ(˜8˜J˜——šž œœœ#˜9Jšœœœ˜Jšœ œ˜(šœ˜Jš œœœ"œœ ˜RJšœM˜TJšœ˜Jšœ œœ˜šœœ˜#JšœBœ˜JJ™>—šœ œ˜*Jšœœ#œœ˜>—J™šœ2˜2JšœL˜L—Jšœ˜—˜J˜——šžœœ˜Jšœœœ˜8Jšœ˜!Jšœ˜—J˜šžœœœœΟr œœ£ œœ£ œœ œœ˜ΚJšœœœ˜Jšœ*˜*J˜—šžœ œ£ œœ£ œœ£ œœ œœ˜ΙJšœœœœ˜šœ"Ÿ/˜QJš ‘œ ™¦—J˜šœœœŸ=˜MJšœœ!˜CJ˜—šœœ˜šœœ˜Jšœœ˜?—šœœ˜Jšœœ"˜E—J˜—šœ˜šœœ˜Kšœœœœ˜Iš œœœœ£ œA˜bKš œœœ œœ˜.Kšœ˜——šœœ˜Kšœœœ œ˜Oš œœœœ£ œ?˜`Kš œœœ œœ˜.Kšœ˜——K˜—J˜J˜—šžœœ£ œœ£ œœ£ œœ˜·Jšœœœ˜Jšœ$˜$J˜—šžœ œ£ œœ£ œœ£ œœ˜ΆJšœœœœ˜š œœœœœ˜7Kš œœœ£ œœ˜6š œœœ£ œ@œœ˜nšœœ˜Kšœœœ(˜:Kšœ˜Kšœ˜—K˜ Kšœ˜—Kšœ˜—š œœœœœ˜5Kš œœœ£ œœ˜<š œœœ£ œDœœ˜ršœœ˜Kšœœœ&˜8Kšœ˜Kšœ˜—K˜ Kšœ˜—Kšœ˜—š œœœ œœ˜8Kš œœœ£ œœ˜7š œœœ£ œBœœ˜pšœœ˜Kšœœœ)˜;Kšœ˜Kšœ˜—K˜ Kšœ˜—Kšœ˜—J˜J˜—K™K™šžœž™:K˜—š žœœœœ œ˜UKšœœœœ˜šœ<˜