IF pd.doReports THEN Report[ Rope.Concat[ IO.PutFR["---- LkProg: %t(%d) %g %g yr=%g ", time[event.credentials.convID], int[event.credentials.stateID], refAny[NEW[StateInConv_event.state]], TU.RefAddr[info], bool[yourParty]], IO.PutFR["lt=%g in=%g, ky=%g\n", bool[latestEvent], bool[event.intervalSpecs#NIL OR event.proseSpecs#NIL], bool[event.keyTable#NIL]]], info]; cDesc _ GetConv[info, event.credentials.convID, FALSE]; IF event.credentials.stateID <= cDesc.cState.credentials.stateID THEN RETURN[pass]; -- Old news! cDesc.cState.credentials.smartsID _ smartsID; cDesc.cState.credentials.stateID _ event.credentials.stateID; IF event.keyTable#NIL THEN { cDesc.cState.keyTable _ event.keyTable; cDesc.newKeys_TRUE; }; IF event.intervalSpecs#NIL THEN EnqueueIntervals[cDesc, event.intervalSpecs]; IF event.proseSpecs#NIL THEN { req: BOOL _ FALSE; FOR pSL: ProseSpecs _ event.proseSpecs, pSL.rest WHILE pSL#NIL DO IF pSL.first.type=request THEN { req _ TRUE; }; ENDLOOP; IF req OR ~info.larkInfo.textToSpeech THEN EnqueueProses[cDesc, event.proseSpecs]; }; IF event.address#NIL THEN { cDesc.cState.address_event.address; cDesc.newAddress_TRUE; }; IF yourParty THEN { cDesc.descValid _ TRUE; cDesc.cState.credentials.partyID _ event.credentials.partyID; cDesc.cState.state _ event.state; cDesc.cState.comment _ event.comment; cDesc.cState.reason _ event.reason; IF event.spec#NIL THEN { cDesc.cState.spec _ event.spec; cDesc.newSpec_TRUE; }; IF event.comment#NIL THEN cDesc.cState.comment _ event.comment; IF event.urgency#normal THEN cDesc.cState.urgency _ event.urgency; IF event.alertKind#standard THEN cDesc.cState.alertKind _ event.alertKind; SELECT event.state FROM reserved, parsing, initiating => cDesc.originator _ us; maybe => { cDesc.originator _ us; IF info.Supervise = LarkSupervise THEN info.larkInfo.ringTune _ event.ringTune; }; pending => cDesc.originator _ them; ringing => { cDesc.originator _ them; info.larkInfo.ringTune _ event.ringTune; }; ENDCASE; }; BeNice[event, 4, info]; BeNice[cDesc, 6, info]; cDesc.newEvent _ TRUE; IF latestEvent THEN Apprise[info]; -- Wait for the last report to wake process. }; Apprise: PUBLIC INTERNAL PROC[info: SmartsInfo] = TRUSTED --INLINE-- { IF info.thProcess=NIL THEN Process.Detach[info.thProcess _ FORK info.Supervise[info]]; info.apprise _ TRUE; NOTIFY info.thAction; }; LarkSupervise: PUBLIC ENTRY PROC[info: SmartsInfo ] = { TRUSTED { Process.SetTimeout[@info.thAction, Process.SecondsToTicks[pd.timeoutNoAction]]; }; IF info.apprise THEN DO ENABLE { UNWIND => NULL; ThSmartsPrivate.LarkFailed => GOTO Failing; }; prevL: OpenConversations _ NIL; nb: NB_success; convL: OpenConversations; trans: Transition; info.apprise _ FALSE; FOR convL _ info.conversations, convL.rest WHILE convL#NIL DO cDesc: ConvDesc = convL.first; stateNow: StateInConv = cDesc.cState.state; ours: BOOL _ ( cDesc.cState.credentials.convID = info.currentConvID ); IF pd.doReports THEN Report[ Rope.Concat[ IO.PutFR["**** LkSup: %t(%d) %g %g->%g", time[cDesc.cState.credentials.convID], int[cDesc.cState.credentials.stateID], TU.RefAddr[info], refAny[NEW[StateInConv_stateNow]], refAny[NEW[StateInConv_cDesc.desiredState]]], IO.PutFR[" %g%g\n", refAny[NEW[Transition_transForStates[stateNow][cDesc.desiredState]]], rope[IF ours THEN " (ours)" ELSE ""]]], info]; BeNice[cDesc, 6, info]; IF NOT cDesc.descValid THEN LOOP; IF info.currentConvID = nullConvID --AND stateNow#idle-- THEN { ours_TRUE; info.currentConvID _ cDesc.cState.credentials.convID; }; IF stateNow=idle THEN { IF pd.doReports THEN ReportFR[" ** Zap %t\n", info, time[cDesc.cState.credentials.convID]]; IF prevL#NIL THEN { prevL.rest _ convL.rest; convL _ prevL; } ELSE info.conversations_convL.rest; IF ours THEN { reason: Thrush.Reason = cDesc.cState.reason; newConvID: ConversationID_cDesc.cState.credentials.convID; newDesc: ConvDesc; info.currentConvID _ nullConvID; SELECT info.larkInfo.hookState FROM onhook, spkr => SELECT reason FROM terminating, wontSay => GOTO Exit; ENDCASE => IF cDesc.originator#us THEN GOTO Exit; ENDCASE => SELECT TRUE FROM reason=terminating, reason=wontSay, cDesc.originator#us => { newConvID _ nullConvID; info.currentConvID _ nullConvID; }; ENDCASE; newDesc _ GetConv[info, newConvID, TRUE]; ChangeState[info, newDesc, reserved, cDesc.cState.reason, cDesc.cState.comment]; -- Now go around the loop once, if only to coerce Lark into idle state. EXITS Exit => NULL; }; } ELSE IF NOT ours THEN nb _ ThParty.Advance[ credentials: cDesc.cState.credentials, state: idle, reason: busy, comment: "One conversation at a time, please." ] ELSE { SELECT (trans_transForStates[stateNow][cDesc.desiredState]) FROM noop => NULL; elim => ERROR; -- handled in stateNow=idle case above. rsrv, rers, prsg, alrt => { -- placing call, or reserving conversation convID: ConversationID; calledPartyID: PartyID = IF trans#alrt THEN nullID ELSE cDesc.desiredPartyID; nb _ IF trans=alrt AND calledPartyID=nullID THEN noSuchParty2 ELSE success; IF NOT ours THEN Problem["What to do in a race?", info]; IF nb=success THEN [nb, convID] _ ThParty.Alert [ credentials: cDesc.cState.credentials, calledPartyID: calledPartyID, state: IF trans = alrt THEN initiating ELSE cDesc.desiredState, reason: cDesc.desiredReason, newConv: trans=rsrv, comment: cDesc.desiredComment ]; IF nb=success THEN { cDesc.cState.credentials.convID _ info.currentConvID _ convID; ours_TRUE; }; }; idle, actv => -- Simple transitions nb _ ThParty.Advance [ credentials: cDesc.cState.credentials, state: cDesc.desiredState, reason: cDesc.desiredReason, comment: cDesc.desiredComment ]; spvs => { -- Supervision while active. cDesc.desiredState_active; -- Reflect reality FOR iL: IntervalSpecs _ cDesc.newIntervals, iL.rest WHILE iL#NIL DO iS: Thrush.IntervalSpec = iL.first; IF iS.interval.length#0 OR iS.queueIt THEN -- else flush: ignore SELECT iS.type FROM started => cDesc.desiredIntID _ iS.intID; finished => IF iL.rest=NIL AND iS.intID = cDesc.desiredIntID THEN SELECT info.larkInfo.hookState FROM spkr => ChangeState[info, cDesc, idle]; ENDCASE; ENDCASE; ENDLOOP; cDesc.newIntervals _ NIL; IF cDesc.newProses#NIL AND info.larkInfo.textToSpeech THEN { copyNewProses: ProseSpecs _ cDesc.newProses; cDesc.newProses _ NIL; EnqueueProses[cDesc, copyNewProses]; ThSmartsPrivate.EnterLarkState[ info.larkInfo, LarkStateForState[cDesc, stateNow, info.larkInfo.larkState], info]; cDesc.newProses _ copyNewProses; }; FOR pL: ProseSpecs _ cDesc.newProses, pL.rest WHILE pL#NIL DO pS: Thrush.ProseSpec = pL.first; SELECT pS.type FROM request => IF info.larkInfo.textToSpeech THEN { pS.type _ started; pS.prose _ ""; }; started => cDesc.desiredProseID _ pS.intID; finished => IF ~info.larkInfo.textToSpeech AND pL.rest=NIL AND pS.intID = cDesc.desiredProseID THEN SELECT info.larkInfo.hookState FROM spkr => ChangeState[info, cDesc, idle]; ENDCASE; ENDCASE; ENDLOOP; IF cDesc.newProses#NIL AND info.larkInfo.textToSpeech THEN { nb _ ThParty.SetProse[ shhh: info.larkInfo.shh, credentials: cDesc.cState.credentials, proseSpecs: cDesc.newProses ]; IF nb=success THEN cDesc.newProses_NIL; } ELSE cDesc.newProses _ NIL; }; ring => -- This is how you say you'll ring an incoming call IF info.larkInfo.autoAnswer THEN { info.apprise_TRUE; cDesc.desiredState _ active; } ELSE nb _ ThParty.Advance [ credentials: cDesc.cState.credentials, state: ringing ]; invl => { comment: ROPE="Invalid state transition"; Problem[comment, info]; ChangeState[info, cDesc, idle, error, comment]; }; ntiy => { comment: ROPE= "Unimplemented state transition"; Problem[comment, info]; ChangeState[info, cDesc, idle, error, comment]; }; ENDCASE => ERROR; }; IF nb#success THEN ReportFR[" ** Results: nb=%g\n", info, refAny[NEW[Thrush.NB_nb]]]; SELECT nb FROM success, stateMismatch => NULL; noSuchParty2, narcissism => -- Have to get to error tone here. No such party. ChangeState[info, cDesc, idle, notFound, "Called party not found, or calling self"]; partyNotEnabled => ours_FALSE; invalidTransition, convNotActive, convStillActive => { comment: ROPE="Party-level detected invalid state transition request"; Problem[comment, info]; ChangeState[info, cDesc, idle, error, comment]; }; notInConv, noSuchConv => { -- Complain, zap, go idle and repeat. comment: ROPE="NotInConv or NoSuchConv"; Problem[comment, info]; IF ours THEN info.currentConvID _ nullConvID; cDesc.cState.credentials.convID _ nullConvID; cDesc.cState.credentials.stateID _ 0; ChangeState[info, cDesc, idle, error, comment]; }; noSuchParty, noSuchSmarts => { -- Complain, deregister, we gone! Needs tuning. Problem[ "LkSmts: NoSuchParty or NoSuchSmarts reported, must try to go away", info]; GOTO Failing; }; ENDCASE => ERROR; IF ours THEN ThSmartsPrivate.EnterLarkState[ info.larkInfo, LarkStateForState[cDesc, stateNow, info.larkInfo.larkState], info]; prevL _ convL; ENDLOOP; IF NOT info.apprise THEN WAIT info.thAction; IF info.conversations = NIL THEN EXIT; REPEAT Failing => ThSmartsPrivate.Deregister[info]; -- now Failed ENDLOOP; info.thProcess _ NIL; }; DoGoIdle: INTERNAL PROC[ cDesc: ConvDesc, reason: Thrush.Reason_$terminating, comment: Rope.ROPE_NIL, conditions: { $unconditional, $orphan } _ $orphan] = { nb: NB; SELECT conditions FROM $unconditional => NULL; $orphan => { nb: NB; cInfo: ThParty.ConversationInfo; [nb, cInfo] _ ThParty.GetConversationInfo[credentials]; IF nb = $success AND (cInfo.numParties-cInfo.numIdle) > 1 THEN RETURN; -- We're not alone IF reason # NIL THEN { reason _ $error; comment _ "System Error: Inconsistent information"; Report["Inconsistent information", info]; }; }; ENDCASE => ERROR; ChangeState[cDesc, $idle, reason, comment]; }; RingMethod: TYPE = ATOM; -- { $standard=NIL, $ownTune, $bothTunes }; ringEnable: Thrush.RingEnable _ $on, -- are we noisily accepting calls? defaultRingEnable: Thrush.RingEnable _ $on, -- are we noisily accepting calls? ringDo: RingMethod _ NIL, -- ($standard) we use the ring tune? ringTime: BasicTime.GMT_NULL, -- if not, when again? ringTune: LarkPlay.ToneSpec, -- Specification of ring tune ringTuneRope: Thrush.ROPE_NIL, GetStdRingInfo: PUBLIC PROC [partyID: PartyID] = { party: PartyData_ThPartyPrivate.DehandleParty[partyID]; larkRname: ROPE; rdAtom: ATOM; IF party=NIL THEN RETURN; SELECT party.type FROM $individual, $telephone => { larkRname _ GetRnameFromParty[party].Concat[larkRegistry]; IF larkRname=larkRegistry THEN RETURN; party.ringEnable _ SELECT NamesGV.GVGetAttribute[rName: larkRname, attribute: $ringmode, default: "R"].Fetch[0] FROM 'R => $on, 'S => $subdued, 'O => $off, ENDCASE => $on; rdAtom _ VoiceUtils.MakeAtom[NamesGV.GVGetAttribute[rName: larkRname, attribute: $dotune, default: "false"]]; party.ringDo _ SELECT rdAtom FROM $true => ownTune, $false => standard, $tune => ownTune, $both => bothTunes, ENDCASE => standard; party.ringTuneRope _ Rope.Cat[NamesGV.GVGetAttribute[larkRname, $tunea, NIL], NamesGV.GVGetAttribute[larkRname, $tuneb, NIL], NamesGV.GVGetAttribute[larkRname, $tunec, NIL]]; }; ENDCASE; }; SetStdRingInfo: PUBLIC PROC[partyID: PartyID, update: BOOL_FALSE] = { party: PartyData_ThPartyPrivate.DehandleParty[partyID]; larkRname: ROPE; IF party=NIL THEN RETURN; SELECT party.type FROM $telephone, $individual => { r: ROPE; larkRname _ GetRnameFromParty[party].Concat[larkRegistry]; IF larkRname=larkRegistry THEN RETURN; r _ SELECT party.ringEnable FROM $on => "R", $subdued => "S", $off => "O", ENDCASE => NIL; IF r#NIL THEN NamesGV.GVSetAttribute[larkRname, $ringmode, r]; IF update THEN NamesGV.GVUpdate[larkRname]; }; ENDCASE; }; SetRingEnable: PUBLIC PROC[shh: SHHH_none, partyID: PartyID, ringEnable: Thrush.RingEnable_$noChange, ringInterval: INT_0, update: BOOL_FALSE] = { party: PartyData_ThPartyPrivate.DehandleParty[partyID]; IF party=NIL THEN RETURN; GetStdRingInfo[partyID]; SELECT party.type FROM $telephone, $individual => { IF ringEnable=$telephone, $noChange THEN ringEnable _ party.ringEnable; party.ringEnable _ ringEnable; party.ringTime _ BasicTime.Update[BasicTime.Now[], ringInterval]; [] _ PrepareRingTune[party]; SELECT ringEnable FROM $on, $subdued, $off => { IF update AND ringEnable#party.defaultRingEnable THEN SetStdRingInfo[partyID, ThNet.pd.autoGVUpdate]; party.defaultRingEnable _ ringEnable; }; ENDCASE; }; ENDCASE; }; PrepareRingTune: PROC[party: PartyData] RETURNS[ringTune: LarkPlay.ToneSpec] = { SELECT party.type FROM individual => { IF ThNet.pd.ringsInvalid THEN { ThNet.pd.ringsInvalid _ FALSE; ringTone _ NEW[LarkPlay.ToneSpecRec _ [ repeatIndefinitely: TRUE, volume: ThNet.pd.defaultRingVolume, tones: LIST[LIST[ [f1: 440, f2: 480, on: 2000, off: 4000], [f1: 440, f2: 480, on: 2000, off: 4000]]]]]; subduedRingTone _ NEW[LarkPlay.ToneSpecRec _ [ repeatIndefinitely: FALSE, volume: ThNet.pd.defaultRingVolume+ThNet.pd.subduedVolumeInterval, tones: LIST[LIST[[f1: 440, f2: 480, on: 500, off: 0]]] ]]; outsideRingTune _ LarkPlay.PlayString[outsideRingTuneRope, FALSE, ThNet.pd.defaultRingVolume]; }; IF party.ringDo#standard AND party.ringTuneRope#NIL THEN ringTune _ LarkPlay.PlayString[music: party.ringTuneRope, file: FALSE, volume: ThNet.pd.defaultRingVolume] ELSE { pattern: LarkPlay.ToneSpec = SELECT party.ringEnable FROM subdued, subduedTimed => subduedRingTone, ENDCASE => ringTone; ringTune _ NEW[LarkPlay.ToneSpecRec_pattern^]; }; IF ringTune#NIL THEN SELECT party.ringEnable FROM subdued, subduedTimed => { ringTune.volume _ ringTune.volume + ThNet.pd.subduedVolumeInterval; ringTune.repeatIndefinitely _ FALSE; }; ENDCASE; party.ringTune _ ringTune; }; ENDCASE; }; creatorPartyID: Thrush.PartyID _ Thrush.nullID, -- now expressed in Triple newKeyTable: BOOL_FALSE, -- IF key table has changed but not yet been posted log: Thrush.EventSequence -- no longer needed; MBQueues form log, and once-only events are no longer reported that way. CFRef: TYPE = REF CFRec; -- Ref to party/conversation record CFRec: TYPE = RECORD [ -- One such CFRec for each Party-Conversation pair event: Thrush.ConvEvent, -- describes a state and the event that put it there. voiceSmartsID: Thrush.SmartsID_Thrush.nullID, -- the one that will carry the conversation load for this Party in this conversation sockID: LONG CARDINAL_NULL, -- local socket ID for this participant. lastPostedID: Thrush.StateID_0, -- last time event referring to this party was posted lastNotedID: Thrush.StateID_0 -- last time this party's smarts were informed. ]; Supervisor: PROC[party: PartyData]; -- supervision process root. Supervise: PROC[party: PartyData]; -- Creates or joggles a Supervisor. Use where one would normally do a NOTIFY party.actionNeeded DistributionProc: TYPE = PROC[ smarts: SmartsData ] RETURNS [ d: Thrush.Disposition ]; -- controls whether distribution will progress. Distribute: PROC[ party: PartyData, proc: DistributionProc] RETURNS [ d: Thrush.Disposition ]; -- accept/reject/pass, depending on whether anybody did. GetEvent: PROC[conv: ConversationData, stateID: Thrush.StateID] RETURNS [ event: Thrush.ConvEvent ]; Activate: INTERNAL PROC[conv: ConversationData, myCfRef: CFRef, myParty: PartyData] RETURNS[isSpec: BOOL_FALSE] = { sockets: ARRAY[0..2) OF Lark.VoiceSocket; parties: ARRAY[0..2) OF PartyData; specIndex: NAT_0; GetOneActive: INTERNAL Triples.ForeachProc = { cfRef: CFRef; smarts: SmartsData; sockID: PupTypes.PupSocketID; IF specIndex>=2 THEN RETURN[FALSE]; WITH trip.att SELECT FROM r: CFRef => cfRef _ r; ENDCASE=> RETURN[TRUE]; IF cfRef.voiceSmartsID = nullID THEN RETURN[TRUE]; smarts _ UnsealSmarts[cfRef.voiceSmartsID]; IF smarts=NIL THEN RETURN--[FALSE]--; -- fail back sockID _ LOOPHOLE[cfRef.sockID]; WITH smarts.properties SELECT FROM $voiceTerminal => sockets[specIndex] _ [ [ machine.net], [machine.host], sockID ]; ENDCASE => VoiceUtils.Problem["Non-voice terminal trying to be voice smarts", $System]; parties[specIndex] _ NARROW[trip.val]; specIndex _ specIndex+1; }; myParty.partyActive_TRUE; conv.numActive _ conv.numActive + 1; Triples.Foreach[Triples.Any, conv, Triples.Any, GetOneActive]; IF specIndex#2 THEN RETURN[FALSE]; SELECT myParty FROM =parties[0] => specIndex _ 0; =parties[1] => specIndex _ 1; ENDCASE => VoiceUtils.Problem["Impossible", $System]; myCfRef.event.spec _ NEW[Lark.ConnectionSpecRec _ [ protocol: interactive, encoding: $muLaw, sampleRate: 8000, packetSize: 160, -- sample bytes, that is. buffer: out1, keyIndex: IF ThNet.pd.encryptVoice THEN 1 ELSE 0, localSocket: sockets[specIndex], remoteSocket: sockets[1-specIndex], blankA: 0 ]]; RETURN[TRUE]; }; OtherProc: Triples.ForeachProc _ SELECT state FROM $idle => IdleLast, $ringing, $active => FindCaller, ENDCASE => NIL; IdleLast: Triples.ForeachProc = { WITH trip.att SELECT FROM r: CFRef => { otherState _ $idle; SELECT r.event.state FROM $idle => RETURN[TRUE]; ENDCASE=>NULL; IF otherParty#NIL THEN { otherParty_NIL; RETURN[FALSE]; }; otherParty_NARROW[trip.val]; otherCfRef _ r; }; ENDCASE; }; FindCaller: Triples.ForeachProc = { WITH trip.att SELECT FROM r: CFRef => { otherState _ IF state = $ringing THEN $maybe ELSE $active; SELECT r.event.state FROM $initiating, $maybe => NULL; ENDCASE => RETURN[TRUE]; otherParty _ NARROW[trip.val]; otherCfRef _ r; RETURN[FALSE]; }; ENDCASE; }; IF OtherProc#NIL THEN Triples.Foreach[Triples.Any, conv, Triples.Any, OtherProc]; IF otherParty = NIL THEN RETURN; SELECT validTransitions[otherState][otherCfRef.event.state] FROM $no => { VoiceUtils.Problem[IO.PutFR["%g, %g, %g: State mismatch among parties", TU.RefAddr[party], TU.RefAddr[otherParty], TU.RefAddr[cfRef]], $System]; RETURN[nb: $stateMismatch]; }; $cantReq, $valid => NULL; $noop => RETURN; ENDCASE => ERROR; []_PostConvEvent[ conv: conv, party: otherParty, state: otherState, reason: reason, comment: comment]; validTransitions: ARRAY Thrush.StateInConv OF ARRAY Thrush.StateInConv OF Validity = [ [ noop, valid, valid, valid, valid, valid, valid, valid, valid, valid, no ], -- idle [ valid, noop, valid, no, no, no, no, no, no, no, no ], -- reserved [ valid, valid, noop, no, no, no, no, no, no, no, no ], -- parsing [ valid, valid, valid, noop, no, no, no, no, no, no, no ], -- initiating [ cantReq, cantReq, cantReq, no, noop, no, no, no, no, no, no ], -- pending [ no, no, no, cantReq, no, noop, no, no, no, no, no ], -- maybe [ no, no, no, no, valid, no, noop, no, no, no, no ], -- ringing [ cantReq, cantReq, cantReq, cantReq, cantReq, cantReq, cantReq, noop, no, cantReq, no ], -- canAct [ valid, valid, valid, cantReq, valid, cantReq, valid, valid, noop, valid, no ], -- active [ no, no, no, no, no, no, no, valid, valid, noop, no ], -- inactive [ no, no, no, no, no, no, no, no, no, no, no ] -- any ]; Validity: TYPE = { $noop, -- Already in this state; no transition action required $valid, -- This transition is all right if other tests check out. $no, -- This transition is not allowed, nohow. $cantReq -- This transition may not be requested by the Party-level client. }; SELECT state FROM $initiating, $active, $canActivate => IF cfRef.voiceSmartsID=nullID THEN { smarts: SmartsData _ UnsealSmarts[smartsID]; IF smarts#NIL THEN WITH smarts.properties SELECT FROM $backstop, $supervisor => NULL; -- << ERROR? >> voiceTerminal => cfRef.voiceSmartsID _ smartsID; $manager => { smarts _ NARROW[Triples.Select[$AdjacentTerminal, party, -- smarts --]]; IF smarts # NIL THEN cfRef.voiceSmartsID _ H[smarts]; }; ENDCASE; }; ENDCASE; lastEvent _ cfRef.event; lastState _ IF lastEvent=NIL THEN $idle ELSE lastEvent.state; cfRef.lastPostedID _ conv.currentStateID; IF state=$active THEN { IF lastState#$active THEN IF party.partyActive THEN event.state _ $canActivate ELSE [] _ Activate[conv, cfRef, party] } --<> GetEvent: PUBLIC INTERNAL PROC[conv: ConversationData, stateID: Thrush.StateID] RETURNS [ Thrush.ConvEvent_NIL ] = { IF conv=NIL THEN VoiceUtils.ProblemFR["%g: No such conversation", $System, NIL, TU.RefAddr[conv]] ELSE IF stateID=0 THEN RETURN[NIL] ELSE IF stateID>=conv.log.size THEN VoiceUtils.Problem["State ID out of range", $System] ELSE RETURN[conv.log[stateID]]; }; InsertEvent: INTERNAL PROC[conv: ConversationData, entry: ConvEvent] = { IF conv.currentStateID >= conv.log.size THEN { oldLog: Thrush.EventSequence = conv.log; newLog: Thrush.EventSequence = NEW[Thrush.EventSequenceBody[oldLog.size+ThPartyPrivate.logSizeIncrement]]; FOR i: NAT IN [0..oldLog.size) DO newLog[i]_oldLog[i]; ENDLOOP; conv.log _ newLog; }; conv.log[conv.currentStateID] _ entry; }; SetupRingTunes: PROC[event: Thrush.ConvEvent] = TRUSTED { partyID: Thrush.PartyID = event.credentials.partyID; party: PartyData = ThPartyPrivate.UnsealParty[partyID]; otherParty: PartyData; otherTune: LarkPlay.ToneSpec; otherMethod: ThPartyPrivate.RingMethod _ standard; divisor: NAT _ 1; SELECT event.state FROM ringing, maybe => NULL; ENDCASE => RETURN; SELECT party.type FROM individual => { SELECT party.ringEnable FROM offTimed, subduedTimed => IF BasicTime.Period[from: party.ringTime, to: BasicTime.Now[]] > 0 THEN ThParty.SetRingEnable[partyID: partyID, ringEnable: party.defaultRingEnable]; ENDCASE; SELECT party.ringEnable FROM offTimed, off => RETURN; ENDCASE; otherParty _ ThPartyPrivate.UnsealParty[ ThPartyPrivate.FindOtherParty[partyID, ThPartyPrivate.UnsealConv[event.credentials.convID]].partyID]; IF otherParty#NIL THEN SELECT otherParty.type FROM individual => { divisor _ 2; otherMethod _ otherParty.ringDo; IF otherMethod # standard THEN otherTune _ otherParty.ringTune; }; trunk => IF event.state=ringing THEN { otherTune _ ThPartyPrivate.outsideRingTune; otherMethod _ bothTunes; }; ENDCASE; SELECT event.state FROM maybe => { IF otherTune=NIL THEN RETURN; event.ringTune _ NEW[LarkPlay.ToneSpecRec _ otherTune^]; event.ringTune.volume _ ThNet.pd.tonesVolume+2; event.ringTune.repeatIndefinitely _ TRUE; RETURN; }; ringing => NULL; ENDCASE => RETURN; event.ringTune _ party.ringTune; IF party.ringDo # bothTunes OR otherMethod # bothTunes OR otherTune=NIL THEN RETURN; event.ringTune _ LarkPlay.MergeToneSpecs[event.ringTune, otherTune, divisor, ringTuneDelay]; }; ENDCASE; }; ringTuneDelay: NAT _ 2400; -- ms. delay between tune starts. IF event.keyTable=NIL AND event.intervalSpecs=NIL AND (event.proseSpecs=NIL OR (event.proseSpecs.first.type=request AND party.type=individual)) AND (NOT yourParty) AND (event.state#initiating) THEN RETURN; IF yourParty AND smarts.properties.role=voiceTerminal THEN SetupRingTunes[event]; EndParty[party]; IF r.lastNotedID party.outgoing _ NIL; service => TU.MakeUnique[$RnameForParty, party, ThPartyPrivate.MakeServiceRname[party.serviceName].serviceRnameAtom]; ENDCASE; }; }; EndParty: ENTRY PROC[party: PartyData] = { []_RefID.Release[Reseal[party]]; Triples.Erase[Triples.Any, party, Triples.Any]; party.supervisor _ NIL; }; larkInfo: NEW[ThSmartsPrivate.LarkInfoBody _ [ interface: larkInterface, shh: larkSh, netAddress: netAddress, model: model, autoAnswer: NamesGV.GVGetAttribute[dbRname, $autoanswer, "FALSE"].Equal["TRUE", FALSE], radio: NamesGV.GVGetAttribute[dbRname, $radio, "FALSE"].Equal["TRUE", FALSE], textToSpeech: Rope.Equal[serviceName, "Text-to-Speech", FALSE] ]] ]]; autoAnswer: BOOL_FALSE, -- answers when called. radio: BOOL_FALSE, -- used with hotline; connects line in instead of telset when called. monitor: BOOL_FALSE, -- speaker repeats telset receiver in telset mode. urgency: CallUrgency_ NIL, -- ($normal) urgency supplied in connection attempt during this event. alertKind: AlertKind _ NIL, -- ($standard) connection type hint keyTable: Lark.KeyTable _ NIL, -- whenever there's a spec spec: Lark.ConnectionSpec _ NIL, -- machine, really address: ROPE_NIL, -- calling trunks and the like: external addressing information. intervalSpecs: IntervalSpecs _ NIL, -- if non-NIL, a SetInterval is being specified. proseSpecs: ProseSpecs _ NIL, -- if non-NIL, a SetProse is being specified. ringTune: LarkPlay.ToneSpec _ NIL, -- if non-NIL, the ringing tune to use (to voiceTerminal smarts only) 0δNotYet.mesa Swinehart, December 8, 1985 4:20:32 pm PST Here's some incomplete descriptions of how creation and registration work now: Registering an Etherphone LarkSmarts.Register[netAddress, authenticated, netAddressAsRope] => smartsHandle: Obtain RName from caller ident in RPC call (!) We now know both the machine identification and the assumed RName. Unregister if registered party_CreateParty[individual or service, RName]: Muck with RNames, since they're a mess smarts_ThParty.RegisterLocal[party, ..., netAddress]: Make a smartsInfo and put a bunch of autoAnswer, radio, textToSpeech bits in it $SmartsData[smarts]=smartsInfo ThSmartsPrivate.RegisterTrunk[ party, smarts, info]: Create the trunk party (always NEW!) $TrunkParty[party]=trunkParty Make trunkSmarts, trunkSmartsInfo, $SmartsData[trunkSmarts]=trunkSmartsInfo RegisterLocal the trunk smarts Enable if on-hook From SupImpl.progress More of same Update local state information Fields always extracted <> Extracted if present May accept more later. IF ~pSL.first.queueIt AND info.larkInfo.textToSpeech THEN { Flush proseSpecs that haven't been sent yet. It doesn't matter if we remove started and finished reports here; the server doesn't care about those anyway. But what about REFs? Will no one else get to see these either? event.proseSpecs _ pSL; cDesc.newProses _ NIL; }; Extracted if the event changes our state. << When Conferencing is added, put this back in.>> cDesc.cState.conferenceHost _ event.conferenceHost; Extracted if non-standard? Yucchh! Progress is shared between Trunk and Lark smarts. Want to do it only in the Lark case. This is the only way to tell, at present! Supervisor, other functions from LarkSmartsSupImpl EnqueueIntervals: INTERNAL PROC[cDesc: ConvDesc, int: IntervalSpecs] = INLINE { IF cDesc.newIntervals#NIL THEN cDesc.iTail.rest _ int ELSE cDesc.newIntervals _ int; FOR iL: IntervalSpecs _ int, iL.rest WHILE iL#NIL DO cDesc.iTail _ iL; ENDLOOP; }; EnqueueProses: INTERNAL PROC[cDesc: ConvDesc, pro: ProseSpecs] = { FOR pL: ProseSpecs _ pro, pL.rest WHILE pL#NIL DO Copies, so that changes can be made in order to report back to ThParty. newSpec: ProseSpecs _ LIST[NEW[Thrush.ProseSpecBody _ pL.first^]]; IF cDesc.newProses#NIL THEN cDesc.pTail.rest _ newSpec ELSE cDesc.newProses _ newSpec; cDesc.pTail _ newSpec; ENDLOOP; }; ReportProseDone: PUBLIC ENTRY PROC[info: SmartsInfo, proseSpec: Thrush.ProseSpec] = { cDesc: ConvDesc _ GetConv[info, info.currentConvID, FALSE]; proseSpec.type _ finished; -- Check this one! EnqueueProses[cDesc, LIST[proseSpec]]; cDesc.pTail.first.type _ finished; -- make sure this is okay! Apprise[info]; }; Old Supervisor procedure <> Conv. is now idle: forget about it or re-use it, sort of. Remove dead conversation from list. Set to look like reserving brand new conversation Conv isn't the one we're interested in, and doesn't look like it's going idle: idle it. Should probably consult transForStates for validity. State and substate (below) and desired state indicate there's something to do. Do it: May do more later. For now, record the intervals that start; when you see the last one that started end, it's time to shut down the connection. EnterLarkState, below, may cause new keys to be distributed. When voice messages again supported, will need to initiate intervals here. Make another copy of the newProses -- this copy will belong only to the Lark (but it may include started and finished reports, which the Lark must ignore). We can then smash the prose ROPE of the original newProses inside the loop below. May do more later. For now, record the requests that start; when you see the last one that started end, it's time to shut down the connection. Avoid sending long RPC packets back to acknowledge things EnterLarkState, below, may cause new keys to be distributed. When voice messages again supported, will need to initiate intervals here. Analyze any results of trying to reach a different state. Complain and transition to idle Set up switching and tones to match state. Terminate involvement in conversation. From ThPartyPrivate: From ThPartyPrivate.PartyBody: Fields used by parties representing telephones, individuals, and some services From ThPartyInitImpl: Set Defaults Things removed from ThPartyPrivate.ConversationBody Old version of CFRef/CFRec Will be computed when needed Now a constant of the conversation as a whole MBQueue stuff eliminates the need. Old Party->smarts distribution declarations Activation code, other things removed from OpsImpl. Generate a Lark.ConnectionSpec for this conversant. Other Stuff, removed from OpsImpl.DoAdvance If there is exactly one remaining party that is not idle . . . now it will be. If callee is answering or threatening to answer, advance caller, too. Transition second party too, in small number of matched cases Must match the other, valid, one. If another party to be changed was found; do it. Columns represent present state: rows represent proposed new states Ferreting out voice smarts, activation in PostConvEvent Ferret out voice smarts, if need be. Maintain active party counts, generate connection specifications. Old conversation log event management Almost certainly obsolete stuff from SupervisorImpl GetHistory: PUBLIC ENTRY PROC[ shhh: Thrush.SHHH, credentials: Credentials, firstState: StateID, lastState: StateID -- default: get latest ] RETURNS [ nb: Thrush.NB, events: Thrush.EventSequence_NIL ] = { ENABLE UNWIND => NULL; conv: ConversationData; numStates: NAT; [conv, , , nb] _ ThPartyPrivate.Verify[credentials]; IF nb#success OR conv=NIL OR conv.currentStateID=0 THEN RETURN; IF lastState=0 OR lastState>conv.currentStateID THEN lastState_conv.currentStateID; SELECT firstState FROM <1 => firstState_1; >conv.currentStateID => RETURN; ENDCASE; IF lastState>) When a recording party leaves, it reverts to available. Kill Party structures after party has died. From LarkSmartsInitImpl, autoAnswer and such And corresponding bits from ThSmartsPrivate.LarkInfoBody: Special attributes. << Not clear how set >> Things removed from Thrush.ConvEvent (=> ConvState) These can be obtained on demand by calling other ThParty functions. They are states of the conversation set (usually) by the originator. Will also need to store the unique socket number for the conversation. KeyTable is derived from all the key provisions during the conversation. It is provided on demand. Conversation socket number plus machine number yields the information needed for a spec. These are party/smarts values. ThParty functions will return the necessary values. These will be presented in separate notifications through ThSmarts. This will be obtained directly from the database From ThPartyOpsImpl OtherParty: PUBLIC ENTRY PROC[ shhh: SHHH_none, credentials: Credentials ] RETURNS[ nb: NB, partyID: PartyID_nullID, description: Thrush.ROPE, conference: BOOL_FALSE ] = { conv: ConversationData; [conv, , , nb] _ Verify[credentials]; IF nb=$stateMismatch THEN nb_$success; IF nb#$success THEN RETURN; [partyID, conference] _ FindOtherParty[credentials.partyID, conv]; description _ SELECT TRUE FROM conference => "Group of parties", partyID#nullID => ThPartyPrivate.DoDescribeParty[partyID], ENDCASE => NIL; }; FindOtherParty: PUBLIC PROC[myPartyID: PartyID, conv: ConversationData] RETURNS[partyID: PartyID_nullID, conference: BOOL_FALSE ] = { FOR i: NAT IN [1..conv.currentStateID] DO ce: ConvEvent = conv.log[i]; IF myPartyID=ce.credentials.partyID THEN LOOP; partyID _ ce.credentials.partyID; IF conv.numParties>2 THEN { conference_TRUE; RETURN; }; EXIT; ENDLOOP; }; MergeConversations: PUBLIC PROC[ shhh: SHHH, credentials: Credentials, -- of surviving conversation otherStateID: Thrush.StateID, -- of dissolving conversation otherConvID: ConversationID ] RETURNS [ nb: NB ] = {NULL}; SetIntervals: PUBLIC ENTRY PROC[ shhh: SHHH _ none, credentials: Credentials, intervalSpecs: Thrush.IntervalSpecs ] RETURNS [ nb: NB ] = { ENABLE UNWIND => NULL; conv: ConversationData; party: PartyData; cfRef: CFRef; WachetAuf: INTERNAL Triples.ForeachProc = { WITH trip.att SELECT FROM r: CFRef => { party_NARROW[trip.val]; ThPartyPrivate.Supervise[party]; }; ENDCASE; }; [conv, party, cfRef, nb] _ Verify[credentials]; IF nb#$success THEN RETURN; IF cfRef=NIL OR cfRef.event=NIL OR cfRef.event.state#$active THEN RETURN[$convNotActive]; [] _ PostConvEvent[conv, party, credentials.smartsID, cfRef.event.state, , ,intervalSpecs]; FOR iSL: Thrush.IntervalSpecs _ intervalSpecs, iSL.rest WHILE iSL#NIL DO iS: Thrush.IntervalSpec = iSL.first; IF ~ThNet.pd.encryptVoice THEN iS.keyIndex_0; IF iS.type=$request THEN iS.intID.stateID _ conv.currentStateID; ENDLOOP; Triples.Foreach[Triples.Any, conv, Triples.Any, WachetAuf]; Wake all parties to conversation, now. Potential new key. }; SetProse: PUBLIC ENTRY PROC[ shhh: SHHH _ none, credentials: Credentials, proseSpecs: Thrush.ProseSpecs ] RETURNS [ nb: NB ] = { ENABLE UNWIND => NULL; conv: ConversationData; party: PartyData; cfRef: CFRef; WachetAuf: INTERNAL Triples.ForeachProc = { WITH trip.att SELECT FROM r: CFRef => { party_NARROW[trip.val]; ThPartyPrivate.Supervise[party]; }; ENDCASE; }; [conv, party, cfRef, nb] _ Verify[credentials]; IF nb#$success THEN RETURN; IF cfRef=NIL OR cfRef.event=NIL OR cfRef.event.state#$active THEN RETURN[$convNotActive]; [] _ PostConvEvent[conv, party, credentials.smartsID, cfRef.event.state, , , , proseSpecs]; FOR pSL: Thrush.ProseSpecs _ proseSpecs, pSL.rest WHILE pSL#NIL DO pS: Thrush.ProseSpec = pSL.first; IF pS.type=$request THEN pS.intID.stateID _ conv.currentStateID; ENDLOOP; Triples.Foreach[Triples.Any, conv, Triples.Any, WachetAuf]; Wake all parties to conversation, now. Potential new key. }; DescribeInterval: PUBLIC PROC[ shhh: SHHH _ none, credentials: Credentials, targetInterval: Thrush.IntervalSpec, minSilence: Thrush.VoiceTime _ 1 -- Smallest silent interval that will be considered silence. ] RETURNS [ nb: NB, exists: BOOL_FALSE, intervals: Thrush.IntervalSpecs_NIL ] = { conv: ConversationData; party: PartyData; cfRef: CFRef; [conv, party, cfRef, nb] _ VerifyEnt[credentials]; IF nb#$success AND nb#$stateMismatch THEN RETURN; [exists, intervals] _ BluejayUtils.DescribeInterval[targetInterval.tune, targetInterval.interval, minSilence]; }; SetSubject: PUBLIC ENTRY PROC[ shh: SHHH, convID: ConversationID, subject: ROPE ] = { ENABLE UNWIND => NULL; conv: ConversationData = UnsealConv[convID]; IF conv=NIL THEN RETURN ELSE conv.subject _ subject; }; GetSubject: PUBLIC ENTRY PROC[ shh: SHHH, convID: ConversationID ] RETURNS [subject: ROPE] = { ENABLE UNWIND => NULL; conv: ConversationData = UnsealConv[convID]; RETURN[IF conv=NIL THEN NIL ELSE conv.subject]; }; ConversationsForParty: PUBLIC ENTRY PROC [ shh: SHHH_none, partyID: PartyID ] = { party: PartyData _ UnsealParty[partyID]; ReallyWachetAuf: INTERNAL Triples.ForeachProc = { WITH trip.att SELECT FROM r: CFRef => { conv: ConversationData _ NARROW[trip.obj]; r.lastNotedID _ 0; ThPartyPrivate.Supervise[party]; }; ENDCASE; }; IF party#NIL THEN Triples.Foreach[Triples.Any, Triples.Any, party, ReallyWachetAuf]; }; Κ"§˜™ code™*J˜—blockšΟiN™Nheadš™šQ™Qš.™.KšB™B—Kš™š0™0Kš&™&—š5™5K™—KšO™OKš™š4™4Kš$™$Kš™KšK™KKš™—Kš™K™———™šΟkœžœ˜šœ ˜ šžœ*˜,J˜?Jšœžœ?˜I—šžœ˜ Jš œ,žœžœžœžœ˜e——Jšœ˜—J˜—J™ ™J˜J˜Jšœ0žœ˜7Jš žœ?žœžœžœΟc ˜`J™J™J˜-˜=J™PJ™—J˜J™šžœžœžœ˜Jšœ6žœ˜>—šžœžœž˜J™Jšœ-˜-—šžœžœžœ˜Kšœžœžœ˜šžœ.žœžœž˜Ašžœžœ˜ Kšœžœ˜ šžœžœžœ™;Kšœά™άKšœ™Kšœžœ™K™—K˜—Kšžœ˜—šžœžœž˜*Jšœ'˜'—J˜—Jšžœžœžœ8žœ˜YJ™J™*šžœ žœ˜Jšœžœ˜Jšœ=˜=Jšœ!˜!Jšœ%˜%Jšœ#˜#J™2J™3Jšžœ žœžœ1žœ˜OJ™Jšžœžœžœ&˜?Jšžœžœ&˜BJšžœžœ*˜Jšžœ ž˜J˜7˜ J˜J™ŠJšžœ žœ)˜OJ˜—J˜#JšœQ˜QJšžœ˜—J˜—J˜J˜J˜J™Jšœžœ˜Jšžœ žœŸ,˜OJ˜J˜—™2šΟnœžœžœ)žœ™PJšžœžœžœžœ™TJš žœ"žœžœžœžœ™OJ™—J™š  œžœžœ'™Cšžœžœžœž™1J™GJšœžœžœ$™BJšžœžœžœ™6Jšžœ™J™Jšžœ™—J™J™—š œž œžœ3™UJšœ4žœ™;JšœŸ™.Jšœžœ ™&Jšœ$Ÿ™>Jšœ™J™—J™—™J˜š  œžœžœžœžŸ œ˜Fšžœžœž˜Jšœ žœ˜;—Jšœžœ˜Jšžœ˜J˜J˜—š  œž œžœ˜7šžœ˜ J˜R—šžœžœž˜šžœ˜Jšžœžœ˜Jšœžœ ˜+J˜—Jšœžœ˜Jšœžœ ˜Jšœ˜J˜Jšœžœ˜šžœ˜Jšœ žœžœž˜J˜Jšœ+˜+Jšœžœ<˜FJ˜šžœžœ˜šœ ˜ šžœ&˜(J˜MJšœžœ žœ#˜b—šžœ˜Jšœžœ;˜EJšœžœžœ žœ˜'——Jšœ˜—J˜J™Jšžœžœžœžœ˜!šžœ#žœžœ˜?J™gJšœžœ7˜@Jšœ˜—šžœžœ˜J™:J™#šžœž˜JšœG˜G—šžœžœžœ˜Jšœ)˜)—Jšžœ˜#J˜šžœžœ˜J˜,J˜:J˜Jšœ ˜ šžœž˜#˜šžœž˜Jšœžœ˜"Jšžœžœžœžœ˜1——šžœ˜ šžœžœž˜šœ<˜Jšœžœ˜ J˜—J˜—šœŸ˜#šœ˜J˜&Jšœ˜J˜Jšœ˜Jšœ˜——šœ Ÿ˜&JšœŸ˜-šžœ1žœžœž˜CJ™J˜#šžœžœ žœŸ˜@šžœ ž˜Jšœ)˜)˜ šžœ žœžœž˜5šžœž˜#Jšœ(žœ˜0———Jšžœ˜——Jšžœ˜—Jšœžœ˜Jšœ<™JšœžœžœŸ˜4JšœŸ˜:Jšœžœž˜—J˜—™J˜š œžœžœ˜2Jšœ7˜7Jšœ žœ˜Jšœžœ˜ Jšžœžœžœžœ˜šžœ ž˜J˜Jšœ:˜:Jšžœžœžœ˜&šœž˜šœVž˜ZJ˜ J˜J˜ Jšžœ˜——Jšœm˜mšœžœž˜!Jšœ˜J˜J˜J˜Jšžœ ˜—šœHžœ˜MJšœ*žœ˜/Jšœ*žœ˜0—J˜Jšžœ˜—J˜J˜—š  œžœžœžœžœ˜EJšœ7˜7Jšœ žœ˜Jšžœžœžœžœ˜šžœ ž˜J˜Jšœžœ˜Jšœ:˜:Jšžœžœžœ˜&Jš œžœžœ+žœžœ˜ZJšžœžœžœ1˜>Jšžœžœ˜+J˜Jšžœ˜—J˜J˜—š  œžœžœžœPžœ žœžœ˜’Jšœ7˜7Jšžœžœžœžœ˜J˜šžœ ž˜˜JšœG˜GJšœ˜JšœA˜AJšœ˜šžœ ž˜šœ˜šžœžœ$ž˜5Jšœ/˜/—Jšœ%˜%J˜—Jšžœ˜ —J˜—Jšžœ˜—J˜J˜—š œžœžœ!˜Pšžœ ž˜J˜J™ šžœžœ˜Jšœžœ˜šœ žœ˜'Jšœžœ%˜=šœžœžœ˜Jšœ(˜(Jšœ-˜-——šœžœ˜.Jšœžœ˜JšœB˜BJšœž œ&˜6Jšœ˜—Jšœ;žœ˜^J˜—šžœžœžœž˜8Jšœj˜j—šžœ˜šœžœž˜9Jšœ*žœ ˜>—Jšœ žœ ˜.J˜—š žœ žœžœžœž˜1šœ˜JšœC˜CJšœžœ˜$J˜—Jšžœ˜—Jšœ˜J˜Jšžœ˜J˜———™3Jšœ0Ÿ˜JJšœ žœžœŸ3˜MJšœŸ]˜wJ™—™JšœžœžœŸ#˜<šœžœžœŸ2˜JJ˜NJšΟb™šœ-˜-JšŸT˜T—Jš‘-™-JšœžœžœžœŸ(˜EJš‘"™"Jšœ Ÿ5˜UJšœŸ/˜NJ˜J˜——™+J™Jš  œžœŸ˜@Jš  œžœŸ`˜ƒJ˜š œžœžœ˜3JšžœŸ/˜RJ˜—š  œžœ+˜;JšžœŸ8˜[—J™š œžœ1˜?Jšžœ˜$—J™J™—™3J™š œžœžœ<˜SJ™3Jšžœ žœžœ˜Jšœ žœžœ˜)Jšœ žœžœ ˜"Jšœ žœ˜J˜šœžœ˜.J˜ J˜J˜Jšžœžœžœžœ˜#Jš žœ žœžœžœžœžœ˜HJšžœžœžœžœ˜2Jšœ+˜+Jš žœžœžœžŸ œŸ ˜2Jšœ žœ˜ šžœžœž˜"JšœR˜RJšžœP˜W—Jšœžœ ˜&J˜—J˜Jšœžœ˜Jšœ$˜$J˜>Jšžœ žœžœžœ˜"šžœ ž˜Jšœ˜Jšœ˜Jšžœ.˜5—šœžœ˜3J˜J˜Jšœ˜JšœŸ˜*J˜ Jšœ žœžœžœ˜1J˜ J˜#J˜ Jšœ˜—Jšžœžœ˜ J˜J˜—J˜—™+J™šœ!žœž˜2J˜J˜ Jšžœžœ˜—J™J˜!™Nšžœ žœž˜˜ J˜šžœž˜Jšœ žœžœ˜Jšžœžœ˜—Jš žœ žœžœžœžœžœ˜:Jšœ žœ ˜Jšœ˜—Jšžœ˜ J˜——J˜#™Ešžœ žœž˜˜ Jšœ žœžœžœ ˜:šžœž˜Jšœžœ˜Jšžœžœžœ˜—Jšœ žœ ˜Jšœ˜Jšžœžœ˜—Jšžœ˜ J˜——Jšžœ žœžœ<˜QJšžœžœžœžœ˜ J˜J™=šžœ6ž˜@šœ˜šœžœ2˜GJšžœžœžœ˜H—Jšžœ˜Jšœ˜JšœŸ ™!—Jšœžœ˜Jšœ žœ˜Jšžœžœ˜—˜J™0JšœT˜T—J˜Jš œžœžœžœžœ ˜VšŸC™CJšžMΠck˜TJšž7’ ˜CJšž7’ ˜BJšž;’ ˜HJšžA’ ˜KJšž7’˜?Jšž5’ ˜?JšžZ’ ˜cJšžQ’ ˜ZJšž8’ ˜CJšž/’˜5J˜J˜—šœ žœ˜Jšœ Ÿ7˜@Jšœ Ÿ9˜CJšœŸ)˜0Jšœ ŸB˜LJ˜—J˜—™7J˜J™$šžœž˜šœ&žœžœ˜JJšœ,˜,š žœžœžœžœžœž˜5JšœžœŸ˜/J˜0˜ Jšœ žœ*Ÿ œ˜HJšžœ žœžœ$˜8—Jšžœ˜ ——Jšžœ˜—J™AJ˜Jš œ žœ žœžœžœ˜=J˜)šžœžœ˜šžœž˜Jšžœžœ˜4Jšžœ%Ÿ/˜X——J˜—™%J˜š œžœžœžœ1˜OJšžœžœ˜$JšžœžœžœQ˜aJšžœ žœžœžœ˜"Jšžœžœ5˜XJšžœžœ˜"—J˜š  œžœžœ.˜Hšžœ&žœ˜.J˜(šœ˜JšžœH˜K—Jš žœžœžœžœžœ˜?J˜—J˜)J˜—J˜—™3J˜š  œžœžœžœ™Jšœ žœ™Jšœ™J™JšœŸ™)Jšœžœžœžœ™AJšžœžœžœ™J™Jšœ žœ™Jšœ4™4Jš žœ žœžœžœžœžœ™?Jšžœ žœžœ™Sšžœ ž™J™Jšœžœ™Jšžœ™—Jšžœžœžœ™$J™#Jšœžœ&™0Jš žœžœžœžœ%žœ™O—J˜—™4J˜š œžœžœ˜9J˜4J˜7J˜Jšœ˜Jšœ2˜2Jšœ žœ˜J™KJš žœ žœžœžœžœ˜Bšžœ ž˜˜šžœž˜šœ˜šžœAž˜GJšœM˜M——Jšžœ˜—šžœž˜Jšœžœ˜Jšžœ˜—˜(˜&J˜>——š žœ žœžœžœž˜2šœ˜Jšœ-˜-Jšžœžœ!˜?Jšœ˜—šœ žœžœ˜&JšœD˜DJšœ˜—Jšžœ˜—šžœ ž˜˜ Jšžœ žœžœžœ˜Jšœžœ$˜8J˜/Jšœ$žœ˜)Jšžœ˜Jšœ˜—Jšœ žœ˜Jšžœžœ˜—J˜ Jš žœžœžœ žœžœžœ˜T˜J˜K—J˜——Jšžœ˜Jšœ˜—J˜Jšœžœ Ÿ!˜J˜—J™™9J™,Jšœ žœžœŸ˜/JšœžœžœŸE˜XJšœ žœžœŸ2˜GJ™——™4J™J™‰JšœžœŸF˜aJšœžœŸ#˜?J™FJ˜J™cJšœžœŸ˜9J˜J™­Jšœžœ˜3Jšœ žœžœŸ@˜UJ˜J™CJšœžœŸ0˜TJšœžœŸ-˜KJ˜J™0JšœžœŸE˜hJ™——™š  œžœžœžœ™Jšœžœ™J™šœžœ™ Jšœžœ™Jšœ™Jšœžœ™Jšœ žœžœ™—J™J™%Jšžœžœ ™&Jšžœ žœžœ™J™Bšœžœžœž™Jšœ!™!J™:Jšžœžœ™—J™J™—š œžœžœ,™GJšžœ&žœžœ™=šžœžœžœž™)J™Jšžœ"žœžœ™.Jšœ!™!Jšžœžœžœžœ™7Jšžœžœ™—J™J™—š œž œ™ Jšœžœ™ JšœŸ™6JšœŸ™;J™Jšœžœžœžœ™J™—š  œžœ™ Jšœžœ™Jšœ™šœ#™#Jšœžœžœ™—Jšžœžœžœ™J™J™J™ šœ žœ™+šžœ žœž™Jšœžœ0žœ™U——Jšœ/™/Jšžœ žœžœ™Jšžœžœžœ žœžœžœžœ™YJšœ[™[šžœ5žœžœž™HJ™$Jšžœžœ™-Jšžœžœ(™@Jšžœ™—™;J™9—J™J™—š œžœ™Jšœžœ™Jšœ™šœ™Jšœžœžœ™—Jšžœžœžœ™J™J™J™ šœ žœ™+šžœ žœž™Jšœžœ0žœ™U——Jšœ/™/Jšžœ žœžœ™Jšžœžœžœ žœžœžœžœ™YJšœ[™[šžœ/žœžœž™BJšœ!™!Jšžœžœ(™@Jšžœ™—™;J™9—J™J™—š œžœžœ™Jšœžœ™Jšœ™J™$J™]Jšœ™Jš žœžœ žœžœ"žœ™OJ™J™J™ Jšœ2™2Jšžœ žœžœžœ™1J™nJ™—J™š   œžœžœžœžœ#žœ™UJšžœžœžœ™J™,Jš žœžœžœžœžœ™7—J™š   œžœžœžœ žœ™^Jšžœžœžœ™J™,Jš žœžœžœžœžœžœ™2—J˜Jš  œžœžœžœžœ™QJ™(šœžœ™1šžœ žœž™šœ ™ Jšœžœ ™*Jšœ™Jšœ ™ Jšœ™—Jšžœ™Jšœ™——JšžœžœžœC™TJ™J™J™——…—_²