DIRECTORY Atom USING [ GetPName, MakeAtom ], BasicTime USING [ Now, Update ], Commander USING [ CommandProc, Register ], CommandTool USING [ NextArgument ], IO, LarkPlay USING [ PlayString, Tone, ToneSpec, ToneSpecRec ], LupineRuntime USING [ BindingError ], NamesGV USING [ GVGetAttribute, GVSetAttribute, GVUpdate ], NamesRPC USING [ StartConversation ], Nice, Process USING [ Detach, SecondsToTicks, SetTimeout ], Pup USING [ nullSocket ], Rope USING [ Concat, Cat, Fetch ], RPC USING [ AuthenticateFailed, EncryptionKey, ExportFailed, ImportFailed ], SafeStorage USING [ GetCanonicalType, Type ], Thrush USING [ AlertKind, CallUrgency, ConversationHandle, Enhandle, HandleFault, KillHandle, NB, PartyHandle, PartyType, H, NetAddress, noMachine, nullHandle, pERROR, Priorities, RingEnable, Rname, ROPE, SHHH, SmartsHandle, TBD, ThHandle, unencrypted ], ThNet USING [ CloseWhitePagesDatabase, FeepName, HowToDial, InitWhitePagesDatabase, List, NewListing, pd, WhitePagesEnter, WhitePagesEntry, WPListing, WPState ], ThParty, ThPartyPrivate USING [ CFRef, DehandleParty, DehandleSmarts, DoAdvance, PartyBody, PartyData, SmartsBody, SmartsData, Supervise ], ThPartyRpcControl, ThPartyRpcServerImpl, ThPartyMonitorImpl, ThSmarts USING [ SmartsInterface, SmartsProperties ], ThSmartsRpcControl USING [ ImportNewInterface, InterfaceRecord ], ThVersions USING [ ThrushVR ], Triples USING [ Any, Erase, Foreach, ForeachProc, Make, Select ], TU USING [ MakeUnique, RefAddr ], VoiceUtils USING [ CmdOrToken, CurrentPasskey, InstanceFromNetAddress, MakeAtom, MakeRName, Registrize, FindWhere, Problem, ProblemFR, RegisterWhereToReport, Report, ReportFR, WhereProc ] ; ThPartyInitImpl: CEDAR MONITOR LOCKS root IMPORTS Atom, BasicTime, IO, Commander, CommandTool, LarkPlay, LupineRuntime, NamesGV, NamesRPC, Nice, Process, root: ThPartyMonitorImpl, Rope, RPC, SafeStorage, SmartsRpc: ThSmartsRpcControl, Thrush, ThNet, ThPartyPrivate, ThPartyRpcControl, ThPartyRpcServerImpl, ThVersions, Triples, TU, VoiceUtils EXPORTS ThParty, ThPartyPrivate SHARES ThPartyMonitorImpl, ThPartyRpcServerImpl = { OPEN IO; AlertKind: TYPE = Thrush.AlertKind; CallUrgency: TYPE = Thrush.CallUrgency; CFRef: TYPE = ThPartyPrivate.CFRef; ConversationHandle: TYPE = Thrush.ConversationHandle; PartyBody: TYPE = ThPartyPrivate.PartyBody; -- Concrete RTPartyType: SafeStorage.Type = SafeStorage.GetCanonicalType[CODE[PartyBody]]; PartyData: TYPE = ThPartyPrivate.PartyData; -- REF Concrete PartyHandle: TYPE = Thrush.PartyHandle; -- Handle ROPE: TYPE = Thrush.ROPE; SmartsBody: TYPE = ThPartyPrivate.SmartsBody; -- Concrete SmartsData: TYPE = ThPartyPrivate.SmartsData; -- REF Concrete RTSmartsType: SafeStorage.Type =SafeStorage.GetCanonicalType[CODE[SmartsBody]]; SmartsHandle: TYPE = Thrush.SmartsHandle; -- Handle SmartsInterface: TYPE = ThSmarts.SmartsInterface; LocalSmartsInterface: TYPE = SmartsRpc.InterfaceRecord; nullHandle: Thrush.PartyHandle = Thrush.nullHandle; SHHH: TYPE = Thrush.SHHH; none: SHHH = Thrush.unencrypted; TBD: TYPE = Thrush.TBD; pERROR: ERROR = Thrush.pERROR; H: PROC[r: REF] RETURNS[Thrush.ThHandle] = INLINE {RETURN[Thrush.H[r]]; }; larkRegistry: ROPE=".Lark"; ringTone: LarkPlay.ToneSpec _ NIL; subduedRingTone: LarkPlay.ToneSpec _ NIL; outsideRingTune: PUBLIC LarkPlay.ToneSpec _ NIL; outsideRingTuneRope: ROPE _ "@300;G%>G<%G%>G<%G%>G<%G%>G<%G%>*C"; thPartyExported: BOOL_FALSE; wpState: ThNet.WPState; standardPriorities: Thrush.Priorities = LIST [ LIST [$Supervisor], LIST [$Manager], LIST[$LocatedTerminal], LIST [$AdjacentTerminal], LIST [$VoiceTerminal, $LabPhone], LIST [$Secretary], LIST [$Receptionist] ]; GetParty: PUBLIC ENTRY PROC[shh: SHHH_NIL, partyID: PartyHandle, rName: Thrush.Rname, type: Thrush.PartyType] RETURNS [newPartyID: PartyHandle] = { ENABLE UNWIND=>NULL; RETURN[H[GetActiveParty[partyID, rName, type]]]; }; GetActiveParty: INTERNAL PROC[partyID: PartyHandle, rName: Thrush.Rname, type: Thrush.PartyType] RETURNS [newParty: PartyData] = { rAtom: ATOM; IF rName=NIL THEN RETURN[NIL]; IF type=service THEN RETURN[GetIdleParty[partyID, rName]]; rName _ VoiceUtils.Registrize[rName]; rAtom _ VoiceUtils.MakeAtom[rName]; RETURN[NARROW[Triples.Select[$RnameForParty, --party--, rAtom]]]; }; GetIdleParty: INTERNAL PROC[partyID: PartyHandle, serviceName: ROPE] RETURNS [newParty: PartyData _ NIL] = { ENABLE UNWIND=>NULL; party: PartyData_ ThPartyPrivate.DehandleParty[partyID]; rAtom: ATOM; serviceNameAtom: ATOM = MakeServiceRname[serviceName].serviceRnameAtom; IF party = NIL THEN RETURN; rAtom _ NARROW[Triples.Select[$RnameForParty, party, -- rAtom --]]; rAtom _ VoiceUtils.MakeAtom[Rope.Cat[Atom.GetPName[rAtom], ".", serviceName]]; party _ NARROW[Triples.Select[$RnameForParty, --party--, serviceNameAtom]]; IF party = NIL OR party.numEnabled = 0 THEN RETURN; party.reservedBy _ partyID; -- must still be reserved-by at Alert or no-go. TU.MakeUnique[$RnameForParty, party, rAtom]; TRUSTED { Process.Detach[FORK ReclaimUnusedIdleParty[party, serviceNameAtom, partyID]]; }; RETURN[party]; }; ReclaimUnusedIdleParty: ENTRY PROC[ serviceParty: PartyData, serviceNameAtom: ATOM, reservedBy: PartyHandle] = TRUSTED { giveItTime: CONDITION; Process.SetTimeout[@giveItTime, Process.SecondsToTicks[5]]; WAIT giveItTime; IF serviceParty.numEnabled=0 OR serviceParty.partyFailed THEN RETURN; -- party broken. IF serviceParty.numConvs#0 THEN RETURN; -- all's OK. IF serviceParty.reservedBy#reservedBy OR Triples.Select[$RnameForParty, serviceParty, NIL] = serviceNameAtom THEN RETURN; -- already released by originating party serviceParty.reservedBy _ nullHandle; -- looks unavailable to reserving party. TU.MakeUnique[$RnameForParty, serviceParty, serviceNameAtom]; -- looks available to others VoiceUtils.ProblemFR["Releasing apparently-unused service party: %g", $System, NIL, atom[serviceNameAtom]]; }; GetPartyFromFeepNum: PUBLIC ENTRY PROC[ shh: SHHH_none, partyID: PartyHandle, feepNum: Thrush.ROPE_NIL] RETURNS [newPartyID: PartyHandle_nullHandle] = { ENABLE UNWIND=>NULL; party: PartyData _ NIL; rName, officeNumber: ROPE; listing: ThNet.WPListing; [rName, officeNumber, listing] _ ThNet.WhitePagesEntry [ wpState: wpState, name: feepNum, feep: TRUE, key: $officeNumber]; IF rName=NIL THEN RETURN; party _ GetActiveParty[partyID, rName, individual]; IF party#NIL THEN RETURN[H[party]]; IF officeNumber=NIL THEN RETURN; RETURN[GetPartyFromNumberInt[partyID, officeNumber, rName, TRUE]]; }; GetPartyFromNumber: PUBLIC ENTRY PROC[ shh: SHHH, partyID: PartyHandle, phoneNumber: Thrush.ROPE, description: ROPE, trunkOK: BOOL] RETURNS [newPartyID: PartyHandle] = { ENABLE UNWIND => NULL; RETURN[GetPartyFromNumberInt[partyID, phoneNumber, description, trunkOK]]; }; GetPartyFromNumberInt: INTERNAL PROC[ partyID: PartyHandle, phoneNumber: Thrush.ROPE, description: ROPE, trunkOK: BOOL] RETURNS [newPartyID: PartyHandle] = { isExt: BOOLEAN _ FALSE; num: ROPE _ NIL; party: PartyData = ThPartyPrivate.DehandleParty[partyID]; myExtAtom: ATOM = IF party=NIL THEN NIL ELSE NARROW[Triples.Select[$Extension, party,]]; myExt: ROPE = IF myExtAtom=NIL THEN NIL ELSE Atom.GetPName[myExtAtom]; IF phoneNumber#NIL THEN [num, isExt] _ ThNet.HowToDial[phoneNumber, myExt]; IF isExt THEN { extAtom: ATOM=Atom.MakeAtom[num]; newPartyID _ H[NARROW[Triples.Select[$Extension, --party--, extAtom]]]; IF newPartyID#nullHandle THEN RETURN[newPartyID]; }; IF ~trunkOK THEN RETURN[nullHandle]; RETURN[H[GetTrunkParty[partyID: partyID, description: description, address: num]]]; }; GetTrunkParty: INTERNAL PROC[ partyID: PartyHandle, description: Thrush.ROPE, address: Thrush.ROPE] RETURNS[trunkParty: PartyData_NIL] = { party: PartyData = ThPartyPrivate.DehandleParty[partyID]; IF party#NIL THEN trunkParty _ NARROW[Triples.Select[$TrunkParty, party, Triples.Any]]; IF trunkParty = NIL OR trunkParty.numConvs#0 THEN RETURN; TRUSTED { SELECT trunkParty.type FROM trunk => { trunkParty.outgoing _ address; trunkParty.reservedBy _ partyID; Triples.Erase[$RnameForTrunk, trunkParty, Triples.Any]; IF description#NIL THEN Triples.Make[$RnameForTrunk, trunkParty, Atom.MakeAtom[description]]; }; ENDCASE => ERROR; }; }; GetNumbersForRName: PUBLIC PROC[shh: SHHH_none, rName: Thrush.Rname] RETURNS [fullRName: ROPE, number: ROPE, homeNumber: ROPE] = { listing: ThNet.WPListing; [fullRName, number, listing] _ ThNet.WhitePagesEntry[ wpState: wpState, name: rName, key: $officeNumber]; IF listing#NIL THEN homeNumber _ ThNet.WhitePagesEntry[ wpState: wpState, key: $outsideNumber, listing: listing].entry; }; GetRname: PUBLIC PROC[shh: SHHH_none, partyID: PartyHandle] RETURNS [rName: Thrush.Rname] = { RETURN[GetRnameFromParty[ThPartyPrivate.DehandleParty[partyID]]]; }; CreateParty: PUBLIC PROC[shh: SHHH_NIL, rName: Thrush.Rname, type: Thrush.PartyType] RETURNS [partyID: PartyHandle_nullHandle] = { party: PartyData_NIL; partyRname: Thrush.Rname = IF type#service THEN rName ELSE MakeServiceRname[rName].serviceRname; larkName: ROPE= partyRname.Concat[larkRegistry]; rAtom: ATOM=VoiceUtils.MakeAtom[partyRname]; extension: ATOM_NIL; wpListing: ThNet.WPListing _ NIL; ExistingParty: ENTRY PROC = -- INLINE -- { ENABLE UNWIND=>NULL; party_IF type=service THEN NIL ELSE NARROW[Triples.Select[$RnameForParty, --party--, rAtom]]; IF party#NIL AND ~party.partyFailed AND party.type#type THEN { VoiceUtils.Problem[remark: "Same party name, new type", where: $System]; party_NIL; }; }; NewParty: ENTRY PROC = -- INLINE -- { party _ NEW[PartyBody _ [type: type]]; SELECT type FROM individual, service => { IF extension#NIL THEN TU.MakeUnique[$Extension, party, extension]; party.serviceName _ rName; -- useful in service case only }; ENDCASE; party.supervisor _ NIL; IF partyRname#NIL THEN TU.MakeUnique[$RnameForParty, party, rAtom]; TU.MakeUnique[$Priorities, party, standardPriorities]; IF wpListing#NIL THEN { ThNet.WhitePagesEnter[wpState: wpState, listing: wpListing]; []_ThNet.CloseWhitePagesDatabase[ wpState: wpState]; }; partyID _ Thrush.Enhandle[party]; IF type=individual THEN SetRingEnable[partyID: Thrush.Enhandle[party], update: TRUE]; -- Get defaults }; IF partyRname=NIL AND type#trunk THEN RETURN; ExistingParty[]; IF party#NIL THEN RETURN[H[party]]; SELECT type FROM individual, service => { attr: ROPE_NamesGV.GVGetAttribute[larkName, $extension, NIL]; feepNum: ROPE; IF attr=NIL THEN { attr _ GetNumbersForRName[rName: partyRname].number; IF attr#NIL AND ~(([attr,]_ThNet.HowToDial[attr]).isLocalExtension) THEN attr_NIL; }; IF attr#NIL THEN { extension _ VoiceUtils.MakeAtom[attr]; feepNum _ ThNet.FeepName[partyRname]; IF ThNet.WhitePagesEntry[ wpState: wpState, name: feepNum, feep: TRUE, key: $officeNumber].fullRName = NIL THEN { wpListing _ ThNet.NewListing[]; ThNet.List[wpListing, $rName, partyRname]; ThNet.List[wpListing, $name, partyRname]; -- Just in case needed ThNet.List[wpListing, $officeNumber, Rope.Cat["8923", attr]]; }; }; }; ENDCASE; NewParty[]; VoiceUtils.Report[IO.PutFR["CreateParty[%s, %s] -> [ %g, %g ]", rope[rName], rope[SELECT type FROM individual => "individual", trunk=>"trunk", service=>"service", ENDCASE=>NIL], card[partyID], IF party=NIL THEN atom[$Unknown] ELSE TU.RefAddr[party]], $Party, party]; }; Register: PUBLIC PROC[shh: SHHH, partyID: PartyHandle, interface: SmartsInterface, properties: ThSmarts.SmartsProperties, oldSmartsID: SmartsHandle] RETURNS [smartsID: SmartsHandle] = { RETURN[DoRegister[ThPartyPrivate.DehandleParty[partyID], NIL, interface, properties]]; }; RegisterLocal: PUBLIC PROC[partyID: PartyHandle, interface: LocalSmartsInterface, properties: ThSmarts.SmartsProperties, oldSmartsID: SmartsHandle] RETURNS [smartsID: SmartsHandle] = { RETURN[DoRegister[ThPartyPrivate.DehandleParty[partyID], interface, NIL, properties]]; }; DoRegister: PROC[party: PartyData, localInterface: LocalSmartsInterface, remoteInterface: SmartsInterface, properties: ThSmarts.SmartsProperties] RETURNS [smartsID: Thrush.SmartsHandle_nullHandle] ={ rName: ROPE; smarts: SmartsData; shh: SHHH; interface: SmartsRpc.InterfaceRecord; IF party=NIL THEN RETURN[nullHandle]; IF properties.role = manager THEN { smarts _ NARROW[Triples.Select[$Manager, party, Triples.Any]]; IF smarts#NIL THEN Deregister[shh: none, smartsID: H[smarts]]; }; rName_GetRnameFromParty[IF party.type#trunk THEN party ELSE GetHostParty[party]]; IF rName=NIL THEN { VoiceUtils.Problem["No RName for own party", $System]; RETURN; }; shh _ NamesRPC.StartConversation[caller: serverInterfaceName.instance, callee: rName, key: serverPassword, level: --<>--CBCCheck ! RPC.AuthenticateFailed => { VoiceUtils.Problem["Authenticate failed", $System]; GOTO Failed}]; interface _ IF remoteInterface=NIL THEN localInterface ELSE SmartsRpc.ImportNewInterface[interfaceName: remoteInterface^ ! RPC.ImportFailed => { VoiceUtils.Problem["Import failed", $System]; GOTO Failed}]; smarts_NEW[ThPartyPrivate.SmartsBody_[interface: interface, properties: properties, type: party.type, shh: shh, authenticated: FALSE--, remote: remoteInterface#NIL--]]; smartsID _ EnterSmarts[party, smarts]; VoiceUtils.Report[IO.PutFR["RegisterSmarts[%g, %s] -> [%g, %g]", TU.RefAddr[party], rope[SELECT properties.role FROM voiceTerminal=>"voice", manager=>"mgr", ENDCASE=>"NIY"], card[smartsID], TU.RefAddr[smarts]], $Party, party]; EXITS Failed => smartsID_nullHandle; }; GetHostParty: PROC[party: PartyData] RETURNS [hostParty: PartyData ] = { RETURN[NARROW[Triples.Select[$TrunkParty, Triples.Any, party]]]; }; RegisterClone: PUBLIC PROC[ shh: SHHH_none, partyID: PartyHandle, clonePartyID: PartyHandle, oldSmartsID: SmartsHandle_nullHandle ] RETURNS [smartsID: SmartsHandle] = { cloneParty: PartyData_ThPartyPrivate.DehandleParty[clonePartyID]; party: PartyData _ ThPartyPrivate.DehandleParty[partyID]; cloneSmarts: SmartsData; smData: SmartsData; IF party=NIL OR cloneParty=NIL THEN RETURN[nullHandle]; cloneSmarts _ NARROW[Triples.Select[$VoiceTerminal, cloneParty, -- Smarts --]]; IF cloneSmarts=NIL THEN RETURN[nullHandle]; smData_NEW[ ThPartyPrivate.SmartsBody _ [ interface: cloneSmarts.interface, properties: cloneSmarts.properties, shh: cloneSmarts.shh, authenticated: cloneSmarts.authenticated--, remote: cloneSmarts.remote--]]; smartsID _ EnterSmarts[party, smData]; VoiceUtils.Report[IO.PutFR["RegisterClone[%g, %g] -> [%g, %g]", TU.RefAddr[party], TU.RefAddr[cloneParty], card[smartsID], TU.RefAddr[smData]], $Party, party]; }; Deregister: PUBLIC ENTRY PROC[shh: SHHH, smartsID: SmartsHandle] = { OPEN Triples; ENABLE { UNWIND => NULL; }; party: PartyData; cfRef: CFRef; smarts: SmartsData; CleanupParty: INTERNAL ForeachProc -- [trip: Triples.TripleRec] -- RETURNS [continue: BOOLEAN _ TRUE] -- = { WITH trip.att SELECT FROM r: CFRef => IF (party.numEnabled=0 OR r.voiceSmartsID = smartsID) AND r.event.state#idle THEN { cfRef _ r; [] _ ThPartyPrivate.DoAdvance[ smartsID: smartsID, party: party, conv: NARROW[trip.obj], cfRef: cfRef, state: idle, reason: terminating]; -- Idle party in conversation!! RETURN[FALSE]; }; ENDCASE; }; DeregisterOneParty: INTERNAL ForeachProc -- [trip: TripleRec] -- RETURNS [continue: BOOLEAN _ TRUE] -- = { WITH trip.obj SELECT FROM p: PartyData => party _ p; ENDCASE => RETURN; -- not a party to smarts connection Erase[trip.att, party, smarts -- (trip.val) --]; party.numSmarts _ party.numSmarts - 1; IF smarts.enablesParty THEN party.numEnabled _ party.numEnabled - 1; SELECT NARROW[trip.att, ATOM] FROM $Manager => { adjSmarts: SmartsData=NARROW[Select[$AdjacentTerminal, party, Any]]; Erase[$ManagerOwner, trip.val, Any]; IF adjSmarts#NIL THEN { Erase[$AdjacentTerminal, party, Any]; party.numSmarts _ party.numSmarts - 1; IF adjSmarts.enablesParty THEN party.numEnabled _ party.numEnabled - 1; }; }; ENDCASE; DO cfRef_NIL; Foreach[Any, Any, party, CleanupParty]; IF cfRef = NIL THEN EXIT; ENDLOOP; IF party.numSmarts = 0 THEN { party.partyFailed_TRUE; ThPartyPrivate.Supervise[party]; VoiceUtils.ReportFR["DeleteParty[%g, %g]", $System, NIL, card[H[party]], TU.RefAddr[party]]; }; RETURN[FALSE]; }; smarts _ ThPartyPrivate.DehandleSmarts[smartsID]; IF smarts=NIL THEN RETURN; DO -- Outside loop avoids conflict when Deregister erases things. party_NIL; Foreach[Any, Any, smarts, DeregisterOneParty]; IF party = NIL THEN EXIT; ENDLOOP; VoiceUtils.ReportFR["UnregisterSmarts[%g, %g]", $System, NIL, card[smartsID], TU.RefAddr[smarts]]; Thrush.KillHandle[smartsID, RTSmartsType! Thrush.HandleFault=>{VoiceUtils.Problem["Problem killing smarts handle", $System]; CONTINUE}]; }; EnterSmarts: PROC[party: PartyData, smarts: SmartsData] RETURNS[smartsID: SmartsHandle] = { managerOwnerRAtom: ATOM_NIL; EnterSmartsE: ENTRY PROC RETURNS[smartsID: SmartsHandle] = -- INLINE -- { attr: ATOM=WITH props: smarts.properties SELECT FROM backstop=> $BackStop, manager=> $Manager, supervisor=> $Supervisor, voiceTerminal=> $VoiceTerminal, ENDCASE=>ERROR pERROR; smartsID _ Thrush.Enhandle[smarts]; TU.MakeUnique[attr, party, smarts]; party.numSmarts _ party.numSmarts + 1; IF smarts.enablesParty THEN party.numEnabled _ party.numEnabled + 1; WITH props: smarts.properties SELECT FROM manager => IF managerOwnerRAtom#NIL THEN { OPEN Triples; TU.MakeUnique[$ManagerOwner, smarts, managerOwnerRAtom]; WITH Select[$RnameForParty, --party--, managerOwnerRAtom] SELECT FROM managerOwnerParty: PartyData => { mgrOwnSmarts: SmartsData _ NARROW[Select[$VoiceTerminal, managerOwnerParty, --smarts--]]; IF mgrOwnSmarts#NIL THEN { TU.MakeUnique[$AdjacentTerminal, party, mgrOwnSmarts]; party.numSmarts _ party.numSmarts + 1; IF mgrOwnSmarts.enablesParty THEN party.numEnabled _ party.numEnabled + 1; }; }; ENDCASE; }; voiceTerminal => IF party.type=individual THEN { OPEN Triples; managerOwnerSmarts: REF; managerOwnerParty: PartyData; managerOwnerRAtom _ NARROW[Select[$RnameForParty, party, -- rAtom --]]; IF managerOwnerRAtom=NIL THEN RETURN; -- This is really an Error, though. managerOwnerSmarts _ Select[$ManagerOwner, --smarts--, managerOwnerRAtom]; IF managerOwnerSmarts=NIL THEN RETURN; managerOwnerParty _ NARROW[Select[$Manager, -- party--, managerOwnerSmarts]]; IF managerOwnerParty=NIL THEN RETURN; TU.MakeUnique[$AdjacentTerminal, managerOwnerParty, smarts]; managerOwnerParty.numSmarts _ managerOwnerParty.numSmarts + 1; IF smarts.enablesParty THEN managerOwnerParty.numEnabled _ managerOwnerParty.numEnabled + 1; }; ENDCASE; }; WITH props: smarts.properties SELECT FROM manager => { netAddress: Thrush.NetAddress _ [props.netAddress.net, props.netAddress.host, Pup.nullSocket]; managerOwner: ROPE _ NamesGV.GVGetAttribute[ rName: VoiceUtils.InstanceFromNetAddress[netAddress, larkRegistry], attribute: $owner, default: NIL]; IF managerOwner#NIL THEN managerOwnerRAtom _ VoiceUtils.MakeAtom[managerOwner]; }; ENDCASE; RETURN[EnterSmartsE[]]; }; Enable: PUBLIC ENTRY PROC[shh: SHHH_none, smartsID: SmartsHandle] RETURNS [nb: Thrush.NB] = { smarts: SmartsData=ThPartyPrivate.DehandleSmarts[smartsID]; IF smarts=NIL THEN RETURN[noSuchSmarts]; IF smarts.enablesParty THEN RETURN[success]; smarts.enablesParty_TRUE; RETURN[Able[smarts, 1]]; }; Disable: PUBLIC ENTRY PROC[shh: SHHH_none, smartsID: SmartsHandle] RETURNS [nb: Thrush.NB] = { smarts: SmartsData=ThPartyPrivate.DehandleSmarts[smartsID]; IF smarts=NIL THEN RETURN[noSuchSmarts]; IF ~smarts.enablesParty THEN RETURN[success]; smarts.enablesParty_FALSE; RETURN[Able[smarts, -1]]; }; Able: INTERNAL PROC[smarts: SmartsData, direction: INT] RETURNS[nb: Thrush.NB_success] = { AbleOne: Triples.ForeachProc -- [trip: TripleRec] -- RETURNS [continue: BOOLEAN _ TRUE] -- ={ WITH trip.obj SELECT FROM party: PartyData => { res: INT=party.numEnabled+direction; IF res<0 THEN nb_invalidTransition ELSE party.numEnabled _ res; }; ENDCASE; }; Triples.Foreach[Triples.Any, Triples.Any, smarts, AbleOne]; }; DescribeParty: PUBLIC ENTRY PROC[shh: SHHH_none, partyID: PartyHandle] RETURNS[ description: Thrush.ROPE] = { ENABLE UNWIND=>NULL; RETURN[DoDescribeParty[partyID]]; }; DoDescribeParty: PUBLIC INTERNAL PROC[ partyID: PartyHandle ] RETURNS[ description: Thrush.ROPE] = TRUSTED { party: PartyData = ThPartyPrivate.DehandleParty[partyID]; IF party=NIL THEN RETURN[NIL]; description _ GetRnameFromParty[party]; SELECT party.type FROM individual => RETURN[description]; trunk => { more: ROPE=IF party.outgoing#NIL THEN party.outgoing ELSE "outside line"; IF description=NIL THEN description _ more ELSE description _ IO.PutFR["%s (%s)", IO.rope[description], IO.rope[more]]; RETURN[description]; }; service => RETURN[IO.PutFR["%g service", rope[party.serviceName]]]; ENDCASE; }; GetRnameFromParty: PROC[party: PartyData] RETURNS [rName: Thrush.Rname] = { rRef: REF ANY; IF party=NIL THEN RETURN[NIL]; rRef _ Triples.Select[$RnameForParty, party, --rRef--]; IF rRef=NIL THEN rRef_Triples.Select[$RnameForTrunk, party, --rRef--]; IF rRef=NIL THEN RETURN[NIL]; RETURN[Atom.GetPName[NARROW[rRef]]]; }; MakeServiceRname: PUBLIC PROC[serviceName: Thrush.ROPE] RETURNS [serviceRname: Thrush.ROPE, serviceRnameAtom: ATOM] = { serviceRname _ Rope.Cat[serviceName, larkRegistry]; serviceRnameAtom _ VoiceUtils.MakeAtom[serviceRname]; }; GetCurrentParty: PUBLIC ENTRY PROC[shh: Thrush.SHHH_none, smartsID: Thrush.SmartsHandle] RETURNS [partyID: Thrush.PartyHandle_Thrush.nullHandle] = { ENABLE UNWIND=>NULL; smarts: REF _ ThPartyPrivate.DehandleSmarts[smartsID]; IF smarts=NIL THEN RETURN; FOR p: Thrush.Priorities _ standardPriorities, p.rest WHILE p#NIL DO FOR s: Thrush.Priorities _ NARROW[p.first], s.rest WHILE s#NIL DO att: ATOM_NARROW[s.first]; party: REF_Triples.Select[att, Triples.Any, smarts]; IF party#NIL THEN RETURN[H[party]]; ENDLOOP; ENDLOOP; }; GetPartySmarts: PUBLIC ENTRY PROC[partyID: Thrush.PartyHandle, kind: ATOM ] RETURNS [smartsID: Thrush.SmartsHandle]={ ENABLE UNWIND=>NULL; party: PartyData_ThPartyPrivate.DehandleParty[partyID]; IF party=NIL THEN RETURN [Thrush.nullHandle]; RETURN[H[Triples.Select[kind, party, --smarts--]]]; }; GetStdRingInfo: PUBLIC PROC [partyID: PartyHandle] = TRUSTED { party: PartyData_ThPartyPrivate.DehandleParty[partyID]; larkRname: ROPE; rdAtom: ATOM; IF party=NIL THEN RETURN; SELECT party.type FROM individual => { 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: PartyHandle, update: BOOL_FALSE] = TRUSTED { party: PartyData_ThPartyPrivate.DehandleParty[partyID]; larkRname: ROPE; IF party=NIL THEN RETURN; SELECT party.type FROM 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: PartyHandle, ringEnable: Thrush.RingEnable_noChange, ringInterval: INT_0, update: BOOL_FALSE] = TRUSTED { party: PartyData_ThPartyPrivate.DehandleParty[partyID]; GetStdRingInfo[partyID]; SELECT party.type FROM individual => { IF ringEnable=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] = TRUSTED { 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; }; ReportSystem: VoiceUtils.WhereProc = CHECKED { s_NIL; }; ReportParty: VoiceUtils.WhereProc = TRUSTED { party: PartyData = NARROW[whereData]; smarts: SmartsData; IF s=NIL THEN s _ VoiceUtils.FindWhere[$System, NIL]; IF party=NIL THEN RETURN; smarts _ NARROW[Triples.Select[$VoiceTerminal, party, Triples.Any]]; IF smarts=NIL THEN RETURN; WITH props: smarts.properties SELECT FROM voiceTerminal => { netAddress: Thrush.NetAddress = [ props.netAddress.net, props.netAddress.host, Pup.nullSocket]; s_Nice.LarkConLogStream[netAddress]; }; ENDCASE; IF s#NIL AND s=Nice.LarkConLogStream[Thrush.noMachine] THEN s_VoiceUtils.FindWhere[$System, NIL]; }; WhitePagesInit: Commander.CommandProc = { treeName, intExtName: ROPE; IF wpState#NIL AND ThNet.CloseWhitePagesDatabase[wpState] THEN wpState_NIL; treeName _ CommandTool.NextArgument[cmd]; intExtName _ CommandTool.NextArgument[cmd]; IF wpState=NIL THEN wpState _ ThNet.InitWhitePagesDatabase[ treeName, -- Optional, defaults supplied by WP impl., intExtName, -- so NILs are OK here $write]; }; serverInterfaceName: ThPartyRpcControl.InterfaceName; serverPassword: RPC.EncryptionKey; ThPartyInit: Commander.CommandProc = { ENABLE RPC.ExportFailed => { VoiceUtils.Problem["ThParty export failed", $System]; GOTO Failed; }; instance: ROPE = VoiceUtils.MakeRName[style: rName, name: VoiceUtils.CmdOrToken[cmd: cmd, key: "ThrushServerInstance", default: "Strowger.Lark"]]; serverInterfaceName _ [ type: "ThParty.Lark", instance: instance, version: ThVersions.ThrushVR]; serverPassword _ VoiceUtils.CurrentPasskey[VoiceUtils.CmdOrToken[ cmd: cmd, key: "ThrushServerPassword", default: "MFLFLX"]]; VoiceUtils.RegisterWhereToReport[ReportSystem, $System]; VoiceUtils.RegisterWhereToReport[ReportParty, $Party]; ThPartyRpcControl.UnexportInterface[!LupineRuntime.BindingError=>CONTINUE]; ThPartyRpcControl.ExportInterface[ interfaceName: serverInterfaceName, user: instance, password: serverPassword]; VoiceUtils.ReportFR["Export[ThParty.Lark, %s]", $System, NIL, rope[serverInterfaceName.instance]]; EXITS Failed => ThPartyRpcControl.UnexportInterface[!LupineRuntime.BindingError=>CONTINUE]; }; Commander.Register["WhitePages", WhitePagesInit, "WhitePages > -- Initialize and Open White Pages Database"]; Commander.Register["ThParty", ThPartyInit, "ThParty \nInitialize and Export ThParty"]; }. ìThPartyInitImpl.mesa Copyright c 1985, 1986 by Xerox Corporation. All rights reserved. Last modified by D. Swinehart, July 5, 1986 2:40:22 pm PDT Copies Implementation |Need an NB return on failure| Detect condition where service party is reserved but never released or engaged in conversation. Release it for re-use. myExt is used as Intelnet authorization code. << This is still not right. Trunk parties ought to get invented as callers want them, deleted somehow (how?), connected to smarts when call attempt starts (one only at a time). Problem reserved for future reference (maybe a structure that the GC can hack.>> Return the Rname for a given party |Need NB indicating why null handle returned.| rName is a misnomer; it's the name of a service, without registry, if type is service. Party does not already exist. Make one if possible. Get data base information Telephone system extension Will be entered in NewParty, under monitor Party does not already exist. Make one if possible. Doesn't handle oldHandle (ReRegister) yet. Implementation of ThPartyPrivate |Need NB why failed| Doesn't handle oldHandle (ReRegister) yet. |Need NB why failed| Allow only one Manager. Allow it to be the new one. Take care of encryption, and talking to calling smarts Create Smarts body, link with Party; we're rolling. Remove party from each conversation in which smarts is the voiceSmarts, or to which no enabled smarts remain connected. Outside loop avoids Foreach/Erase conflicts. Party is serving no smarts; destroy the party itself. Discussion: The idea is that as Smarts register with a Party they are connected by links whose attributes are derived from their properties and degree of authentication. When Parties initiate actions that involve Smarts they will select the Smarts to respond in an order determined by the attributes, including SmartsRole, authentication (maybe, see notes), etc. Each kind of Smarts has its own link name to its Party A Manager also needs to know the party to whom the physical workstation belongs, as an aid to locating the corresponding voice terminal. Set up the relationship, then locate the adjacent terminal, if one already exists. Otherwise this workstation is not registered to an Etherphone; DCS, June 24, 1986 7:16:04 am PDT When the Manager registers first, the $Adjacent link must be sent when the Lark smarts arrives or dies and revives. A Manager also needs to know the party to whom the physical workstation belongs, as an aid to locating the corresponding voice terminal. Set up the relationship, then locate the adjacent terminal, if one already exists. See if it works just to get the first instance of the highest priority connection that exists, using standardPriorities. | Should have an NB | Set Defaults Swinehart, May 15, 1985 10:24:49 am PDT Cedar 6.0 changes to: larkRegistry, GetParty, GetActiveParty, GeIdleParty, CreateParty, NewParty Swinehart, May 16, 1985 9:18:28 am PDT CommandToolExtras zapped. Swinehart, May 22, 1985 12:01:41 pm PDT ServiceName changes recording => service Jay => serviceName, to generalize individual and service treated similarly in places changes to: GetIdleParty, CreateParty, NewParty, DoDescribeParty, MakeServiceRname changes to: CreateParty, ExistingParty, NewParty, DoDescribeParty changes to: GetIdleParty changes to: CreateParty, ExistingParty, NewParty Swinehart, May 16, 1986 4:19:57 pm PDT Cedar 6.1 changes to: DIRECTORY, ThPartyInitImpl, GetActiveParty, GetIdleParty, CreateParty, ExistingParty, DoRegister, RegisterClone, Deregister, EnterSmarts, MakeServiceRname, GetStdRingInfo, ReportSystem, ReportParty, ThPartyInit Swinehart, June 24, 1986 7:16:19 am PDT Eliminate long-standing crash when registering FinchSmarts at non-registered workstation. changes to: EnterSmartsE (local of EnterSmarts) Ê•˜šœ™Jšœ Ïmœ7™BJšœ:™:—J˜šÏk ˜ Jšœžœ˜"Jšœ žœ˜ Jšœ žœ˜*Jšœ žœ˜#J˜Jšœ žœ-˜;Jšœžœ˜%Jšœžœ.˜;Jšœ žœ˜%J˜Jšœžœ(˜5Jšœžœ˜Jšœžœ˜"JšžœžœC˜LJšœ žœ˜-šœžœ˜Jš œOžœžœLžœžœžœ˜ï—Jšœžœžœ“˜¡J˜Jšœžœn˜‚Jšœ˜J˜J˜Jšœ žœ'˜5Jšœžœ)˜AJ˜Jšœžœ4˜AJšžœžœ˜!Jšœ žœ«˜»J˜J˜—šœž œžœ˜)šžœ˜ J˜ J˜Jšœ ˜ Jšœ ˜ J˜ J˜J˜Jšœ ˜ J˜J˜J˜J˜Jšžœ˜J˜ J˜Jšœ˜J˜J˜J˜J˜J˜ J˜Jšžœ˜J˜ —Jšžœ˜šžœ-˜3Jšžœžœ˜—J˜—šœ™Jšœ žœ˜#Jšœ žœ˜'Jšœžœ˜#Jšœžœ˜5šœ žœÏc ˜7Jšœ=žœ ˜N—Jšœ žœŸ˜;Jšœ žœŸ ˜1Jšžœžœ žœ˜Jšœ žœŸ ˜9šœ žœŸ˜=Jšœ=žœ˜O—JšœžœŸ ˜3Jšœžœ˜1Jšœžœ˜7J˜3Jšžœžœ žœ˜Jšœžœ˜ Jšžœžœ žœ˜Jšœžœ˜Jšžœžœžœžœžœžœžœ˜JJ˜Jšœžœ ˜J˜Jšœžœ˜"Jšœ%žœ˜)Jšœžœžœ˜0Jšœžœ(˜AJ˜Jšœžœžœ˜J˜J˜šœ(žœ˜.Jšžœžœ žœžœ˜VJšžœžœžœ˜LJ˜——J™J˜š Ïnœžœž œžœžœ˜@Jšœ,˜,Jšžœ˜%Jšžœžœžœ˜J™Jšžœ-˜3J˜—š œž œ+˜HJšœ˜Jšžœ˜!Jšœžœ˜ Jš žœžœžœžœžœ˜Jšžœžœžœ˜:J˜%Jšœ#˜#Jšžœžœ Ÿ œ ˜AJ˜J˜—š  œžœžœ$žœ˜DJšžœžœ˜'Jšž œžœ˜Jšœ8˜8Jšœžœ˜ Jšœžœ2˜GJšžœ žœžœžœ˜Jšœžœ'Ÿ œ˜CJšœN˜NJšœžœ Ÿ œ˜KJš žœ žœžœžœžœ˜3J˜KJ˜,Jšžœžœ=˜ZJšžœ˜Jšœ˜J˜—š œžœžœ˜#Jšœ*žœžœ˜TJ™_J™Jšœ ž œ˜J˜;Jšžœ ˜Jš žœžœžœžœŸ˜VJšžœžœžœŸ ˜4Jš žœ$žœ.žœžœžœŸ(˜¢Jšœ&Ÿ(˜NJšžœ<Ÿ˜ZJšœOžœ˜kJ˜J˜—š œžœž œ˜'Jšœžœ-žœžœ˜?Jšžœ)˜0Jšžœžœžœ˜Jšœžœ˜Jšœžœ˜Jšœ˜šœ8˜8Jšœ'žœ˜A—Jšžœžœžœžœ˜J˜3Jš žœžœžœžœžœ ˜#Jšžœžœžœžœ˜ Jšžœ5žœ˜BJšœ˜J˜—š œžœžœžœ˜&Jšœžœ,žœ˜:Jšœ žœ žœ˜!Jšžœ˜%Jšžœžœžœ˜JšžœD˜JJ˜—J˜š œž œ˜%Jšœ*žœ˜/Jšœ žœ žœ˜!Jšžœ˜%Jšœžœžœ˜Jšœžœžœ˜J˜9J™-Jšœ žœžœžœžœžœžœžœ%˜XJš œžœžœ žœžœžœžœ˜FJšžœ žœžœ4˜Kšžœžœ˜Jšœ žœ˜!Jšœ žœžœŸ œ ˜GJšžœžœžœ˜4—Jšžœ žœžœ ˜$JšžœžœK˜SJšœ˜J˜—š  œž œ˜Jšœ*žœžœ˜EJšžœžœ˜&Jšœ9˜9Jšžœžœžœžœ2˜Wš žœžœžœžœžœ˜9J™ƒ—Jšž ˜ šžœž˜šœ ˜ Jšœ˜Jšœ ˜ Jšœ7˜7šžœ žœž˜JšœE˜E—Jšœ˜—Jšžœžœ˜J˜—˜J˜——š œžœžœžœ˜DJšžœ žœ žœžœ˜=J˜šœ5˜5Jšœ3˜3—šžœ žœžœ$˜7J˜?—J˜J™—šœ"™"š œžœžœžœ˜˜DJ˜———š   œžœžœžœžœ.˜TJšžœ&˜-J™.J™VJšœžœ˜Jšœžœžœžœ&˜`Jšœ žœžœ ˜0Jšœžœ!˜,Jšœ žœžœ˜Jšœžœ˜!š  œžœžœŸ œ˜*Jšžœžœžœ˜Jš œžœžœžœžœžœ Ÿ œ ˜]š žœžœžœžœžœ˜>JšœOžœ˜V—J˜—š œžœžœŸ œ˜%Jšœ4™4Jšœžœ˜&šžœž˜˜Jšžœ žœžœ-˜BJšœŸ˜9J˜—Jšžœ˜—Jšœžœ˜Jšžœ žœžœ-˜CJ˜6šžœ žœžœ˜J˜žœ˜YJ˜—š  œžœ8˜HJ˜HJšžœ.˜5J™Jšœžœ˜ J˜Jšœžœ˜ J˜%Jšžœžœžœžœ ˜%šžœžœ˜#J™3Jšœ žœ/˜>Jšžœžœžœ!žœ ˜>J˜—Jšœžœžœžœ˜QJšžœžœžœ:žœ˜UJ˜Jšœ6™6šœU˜UJšœŸ œ žœ˜KJšœ6žœ ˜D—šœ žœžœžœ˜6šžœ@žœ˜WJšœ0žœ ˜>—J˜—Jšœ3™3šœžœJ˜TJšœ+žœžœ˜T—Jšœ&˜&˜@J˜Jšœžœžœ)žœ ˜YJšœ4˜4—šž˜J˜—Jšœ˜J˜—š  œžœžœ˜HJšžœžœ6˜CJ˜—š  œžœžœ˜Jšœžœ˜%Jšœ?˜?Jšœžœ˜&JšœA˜AJšœ9˜9J˜J˜Jš žœžœžœ žœžœžœ ˜7Jšœžœ,Ÿ œ˜OJšžœ žœžœžœ ˜+šœžœ˜)˜FJ˜a——Jšœ&˜&˜?JšœO˜OJšœ˜—Jšœ˜J˜—š   œžœžœžœžœ˜DJšžœ ˜ šžœž˜Jšžœžœ˜J˜—J˜J˜ J˜J•StartOfExpansionD -- [trip: Triples.TripleRec] RETURNS [continue: BOOLEAN _ TRUE] -- ˜š  œžœ Ðck˜?JšŸ(œ˜-šžœ žœž˜šœ ˜ šžœžœžœžœ˜SJ˜ šœ˜Jšœ˜Jšœ ˜ Jšœžœ ˜Jšœ ˜ J˜ JšœŸ˜5—Jšžœžœ˜J˜——Jšžœ˜—J˜J˜—–D -- [trip: Triples.TripleRec] RETURNS [continue: BOOLEAN _ TRUE] -- š œžœ ¡˜=Jš¡(œ˜-šžœ žœž˜J˜JšžœžœŸ#˜6—J˜1J˜&Jšžœžœ)˜Dšžœžœ žœž˜"˜ Jšœžœ(˜DJ˜$šžœ žœžœ˜J˜%J˜&Jšœžœ)˜GJ˜—J˜—Jšžœ˜—šž˜J™¥Jšœžœ˜ J˜'Jšžœ žœžœžœ˜Jšžœ˜—šžœžœ˜J™5Jšœžœ˜Jšœ ˜ Jšœ4žœžœ žœ˜\J˜—Jšžœžœ˜J˜—J˜J˜1Jšžœžœžœžœ˜šžœŸ>˜AJšœžœ˜ Jšœ.˜.Jšžœ žœžœžœ˜Jšžœ˜—Jšœ9žœžœ˜bšœ)˜)JšœSžœ˜^—J˜J™—š  œžœ'žœ˜[Jšœ ™ Jšœ™JšœÀ™ÀJšœžœžœ˜š   œžœžœžœŸ œ˜IJ™6šœžœžœžœž˜4J˜Jšœ˜J˜J˜Jšžœžœ˜—J˜#J˜#Jšœ&˜&Jšžœžœ)˜DJ˜šžœžœž˜)J™Üšœ žœžœžœ˜*Jšœ`™`Jšžœ ˜ Jšœ8˜8šžœŸ œžœž˜Ešœ!˜!šœ˜Jšžœ+Ÿ œ˜>—šžœžœžœ˜Jšœ6˜6J˜&Jšžœžœ)˜JJ˜—J˜—Jšžœ˜—J˜—J™sšœžœžœ˜0Jšžœ ˜ Jšœžœ˜Jšœ˜JšœžœŸ œ˜GJš žœžœžœžœŸ#˜IJšœ+Ÿ œ˜JJšžœžœžœžœ˜&JšœžœŸ œ˜MJšžœžœžœžœ˜%Jšœ<˜˜>šžœž˜Jšœ@˜@—J˜—Jšžœ˜—J˜J˜—šžœžœž˜)J™Ü˜ J˜^šœžœ˜,JšœC˜CJšœžœ˜!—Jšžœžœžœ7˜OJ˜—Jšžœ˜—Jšžœ˜J˜—J˜š  œžœžœžœžœ˜AJšžœ žœ˜J˜;Jšžœžœžœžœ˜(Jšžœžœžœ ˜,Jšœžœ˜Jšžœ˜J˜J™—š  œžœžœžœžœ˜BJšžœ žœ˜J˜;Jšžœžœžœžœ˜(Jšžœžœžœ ˜-Jšœžœ˜Jšžœ˜J˜—J˜š œžœžœ žœ˜7Jšžœ žœ ˜"š œ¡˜1Jš¡(œ˜,šžœ žœž˜˜Jšœžœ˜$Jšžœžœ˜"Jšžœ˜J˜—Jšžœ˜—J˜—J˜;J˜—J˜š   œžœžœžœžœ˜0J˜Jšžœžœ˜&Jšžœžœžœ˜Jšžœ˜!J˜—J˜š œžœžœžœ˜=Jšžœžœžœ˜.J˜9Jš žœžœžœžœžœ˜Jšœ'˜'šžœ ž˜Jšœžœ˜"šœ ˜ Jš œžœžœžœžœžœ˜IJšžœ žœžœ˜*Jšžœžœžœžœ ˜LJšžœ˜J˜—Jšœ žœžœ/˜CJšžœ˜—J˜J™—š œžœžœ˜KJšœžœžœ˜Jš žœžœžœžœžœ˜Jšœ-Ÿœ˜7Jšžœžœžœ,Ÿœ˜FJš žœžœžœžœžœ˜Jšžœžœ ˜'—J˜š œžœžœžœ˜7Jšžœžœžœ˜?Jšœ3˜3Jšœ5˜5Jšœ˜—J˜J™xš œžœž œ žœ%˜XJšžœ4˜;J™Jšžœžœžœ˜Jšœžœ+˜6Jšžœžœžœžœ˜šžœ3žœžœž˜Dš žœžœžœžœž˜AJšœžœžœ ˜Jšœžœ*˜4Jšžœžœžœžœ ˜#Jšžœ˜—Jšžœ˜ J˜——š  œžœžœžœ$ž˜IJšœžœ"˜+Jšžœžœžœ˜Jšœ7˜7Jšžœžœžœžœ˜-JšžœžœŸ œ˜6J˜—š œžœžœžœ˜>Jšœ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˜—š  œžœžœžœžœžœ˜QJšœ7˜7Jšœ žœ˜Jšžœžœžœžœ˜šžœ ž˜J˜Jšœžœ˜Jšœ:˜:Jšžœžœžœ˜&Jš œžœžœ(žœžœ˜WJšžœžœžœ1˜>Jšžœžœ˜+J˜Jšžœ˜—J˜J˜—š  œžœžœžœSžœ žœžœžœ˜Jšœ7˜7J˜šžœ ž˜˜Jšœ:˜:Jšœ˜JšœA˜AJšœ˜šžœ ž˜šœ˜šžœžœ$ž˜5Jšœ/˜/—Jšœ%˜%J˜—Jšžœ˜ —J˜—Jšžœ˜—J˜—J˜š œžœžœ žœ˜XJšžœ ž˜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˜J˜—šœ%žœ˜.Jšœžœ˜J˜J˜—šœ$žœ˜-Jšœžœ ˜%Jšœ˜Jšžœžœžœ(˜5Jšžœžœžœžœ˜Jšœ žœ5˜DJšžœžœžœžœ˜šžœžœž˜)šœ˜˜J˜?—Jšœ$˜$J˜—Jšžœ˜—Jš žœžœžœ+žœ!žœ˜aJ˜J˜—š œ˜)Jšœžœ˜Jš žœ žœžœ(žœ žœ˜KJšœ)˜)Jšœ+˜+šžœ žœžœ(˜;Jšœ Ÿ+˜5Jšœ Ÿ˜#J˜—J˜—J˜Jšœ5˜5Jšœžœ˜"J˜š  œ˜&šž˜JšžœIžœ ˜[—šœ žœ+˜9JšœX˜X—šœ˜J˜Jšœ˜J˜—šœA˜AJ˜;—Jšœ8˜8Jšœ6˜6JšœAžœ˜K˜"Jšœ#˜#J˜J˜—Jšœ9žœ&˜bšž˜JšœKžœ˜U—J˜—J˜Jšœ—˜—šœ*˜*Jšœ[˜[—J˜Icode™™'K™ Kšœ ÏrJ™V—™&K™—™'K™K™K™!K™2Kšœ ¢F™RKšœ ¢5™AKšœ ¢ ™Kšœ ¢$™0—™&K™ Kšœ ¢Ò™Þ—™'K™YKšœ ¢ œ™/—K™—…—l0š±