<> <> <> <> <> DIRECTORY Atom USING [ GetProp ], BasicTime USING [ Now, Period, Update ], Commander USING [ CommandProc, Register ], CommandTool USING [ ParseToList ], FinchSmarts, IO, LarkFeepSunImport USING [ ImportInterface ], MBQueue USING [ Create, QueueClientAction ], NameDB USING [ Error, GetAttribute, SetAttribute ], <> Process USING [ Detach, SecondsToTicks, SetTimeout ], RedBlackTree USING [ EachNode, EnumerateIncreasing, Table ], RefID USING [ ID ], Rope, RPC USING [ EncryptionKey ], ThParty USING [ Advance, Alert, Attributes, ConversationInfo, CreateConversation, DescribeParty, GetConversationFromName, GetConversationInfo, GetParty, GetNumbersForRName, GetPartyFromNumber, GetPartyInfo, LookupServiceInterface, nullIx, PartyInfo, RegisterConversation, Unvisit, UnvisitSelf, Visit ], Thrush USING [ ActionReport, ConversationID, ConvEvent, Credentials, InterfaceSpec, NB, none, notReallyInConv, nullConvID, nullID, PartyID, PartyType, Reason, ROPE, SHHH, SmartsID, StateInConv ], ThSmarts, UserProfile USING [ Boolean ], VoiceUtils USING [ CurrentPasskey, CurrentRName, Problem, ProblemFR, Registrize, Report, ReportFR ] ; FinchSmartsImpl: CEDAR MONITOR LOCKS FinchSmarts.lock IMPORTS Atom, BasicTime, Commander, CommandTool, FinchSmarts, IO, LarkFeepSunImport, MBQueue, NameDB, -- Nice, --Process, RedBlackTree, Rope, ThParty, UserProfile, VoiceUtils EXPORTS FinchSmarts, ThSmarts = { OPEN IO; <> ConvDesc: TYPE = FinchSmarts.ConvDesc; ConversationID: TYPE = Thrush.ConversationID; nullConvID: ConversationID = Thrush.nullConvID; FinchInfo: TYPE = FinchSmarts.FinchInfo; NB: TYPE = Thrush.NB; none: SHHH = Thrush.none; nullID: RefID.ID = Thrush.nullID; PartyID: TYPE = Thrush.PartyID; ROPE: TYPE = Thrush.ROPE; SHHH: TYPE = Thrush.SHHH; SmartsID: TYPE = Thrush.SmartsID; StateInConv: TYPE = Thrush.StateInConv; nullInterfaceSpec: Thrush.InterfaceSpec = [serviceID: nullID]; uniqueSuffix: CARD _ 0; PD: TYPE = FinchSmarts.PD; pd: PUBLIC REF PD _ NEW[PD_[]]; info: PUBLIC FinchInfo_NIL; Report: PROC[what: ROPE] = { IF NOT pd.doReports THEN RETURN; VoiceUtils.Report[what, $Finch]; }; NConcConvs: PROC[l1, l2: LIST OF ConvDesc] RETURNS [LIST OF ConvDesc] = { IF l1=NIL THEN RETURN[l2]; FOR convs: LIST OF ConvDesc _ l1, convs.rest WHILE convs#NIL DO IF convs.rest=NIL THEN { convs.rest_l2; RETURN[l1]; }; ENDLOOP; ERROR; }; DequeueConv: PROC[l1: LIST OF ConvDesc, c: ConvDesc] RETURNS [LIST OF ConvDesc] = { IF l1=NIL THEN RETURN[l1]; -- No list IF l1.first=c THEN RETURN[l1.rest]; -- Head of list FOR convs: LIST OF ConvDesc _ l1, convs.rest WHILE convs.rest#NIL DO IF convs.rest.first=c THEN { convs.rest _ convs.rest.rest; RETURN[l1]; }; -- Internal to list ENDLOOP; RETURN[l1]; -- Not in list }; <> <<>> Progress: PUBLIC ENTRY PROC[ shh: SHHH, convEvent: Thrush.ConvEvent ] = { ENABLE UNWIND => NULL; -- RestoreInvariant; <> <> <> <> <> IF convEvent.self.partyID = convEvent.other.partyID OR convEvent.other.state >= $active -- Possibly a hack, DCS February 26, 1988 THEN [] _ NoteNewState[convEvent]; }; Substitution: PUBLIC ENTRY PROC[ shh: SHHH, convEvent: Thrush.ConvEvent, oldPartyID: Thrush.PartyID, newPartyID: Thrush.PartyID ] = { ENABLE UNWIND => NULL; -- RestoreInvariant; <> <<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. >> cDesc: ConvDesc = GetConv[convEvent.self.convID, FALSE]; IF cDesc#NIL THEN cDesc.numParties _ 0; -- This will force the information update. [] _ NoteNewState[ convEvent ]; }; ReportAction: PUBLIC ENTRY PROC[ shh: SHHH, report: Thrush.ActionReport ] = { ENABLE UNWIND => NULL; ReportRequestState[GetConv[report.self.convID], report]; BROADCAST info.stateChange; -- Sometimes callers wait (Ugh!)} }; CheckIn: PUBLIC ENTRY PROC[ shh: Thrush.SHHH, credentials: Thrush.Credentials, voicePath: BOOL, reason: Thrush.Reason, remark: ROPE, nextScheduledCheck: INT ] = { ENABLE UNWIND => NULL; localRemark: ROPE_NIL; connected: BOOL _ TRUE; enabled: BOOL _ info.enabled; info.nextScheduledCheck _ nextScheduledCheck; SELECT reason FROM $goodbye => { enabled _ connected _ FALSE; localRemark _ "Permanent disconnect requested by server"; }; $trylater => { localRemark _ "Temporary disconnect requested by server"; connected _ FALSE; }; $welcome, $hello => connected _ TRUE; ENDCASE; []_FinchSmarts.RecordSystemStateFromSmartsReport[ remark: localRemark, connected: connected, enabled: enabled, voicePath: voicePath, remoteRemark: remark]; NOTIFY info.pollCondition; }; Poke: PUBLIC ENTRY PROC = { ENABLE UNWIND => NULL; IF info#NIL THEN NOTIFY info.pollCondition; }; <> LimitSignalling: ENTRY PROC [cDesc: ConvDesc, signalState: StateInConv] ~ TRUSTED { <> <> ENABLE UNWIND => NULL; c: CONDITION; Process.SetTimeout[@c, Process.SecondsToTicks[pd.timeoutInitiating]]; WAIT c; IF cDesc.situation.self.state # signalState THEN RETURN; []_Request[cDesc, $failed, $noAnswer, IF signalState=$notified THEN "Other party failed to respond to call." ELSE "We did not respond to call. Notify LarkSupport."]; }; <> <<>> CallInfo: TYPE = REF CallInfoBody; -- needed because MBQueue requires a REF CallInfoBody: TYPE = RECORD [ convID: Thrush.ConversationID, calledPartyID: Thrush.PartyID_nullID, reason: Thrush.Reason_NIL, comment: ROPE_NIL ]; DisconnectCall: PUBLIC ENTRY PROC[ convID: Thrush.ConversationID, reason: Thrush.Reason _ $terminating, comment: ROPE_NIL, newState: StateInConv _ $idle] = { ENABLE UNWIND => NULL; -- RestoreInvariant; cDesc: ConvDesc; state: StateInConv; IF NOT(([,cDesc, state]_FinchOn[convID]).on) THEN RETURN; SELECT state FROM $idle, $neverWas => { ReportConversationState[$convNotActive, NIL, "No conversation to disconnect"]; }; ENDCASE => []_Request[cDesc, newState, reason, comment]; }; AnswerCall: PUBLIC ENTRY PROC[convID: Thrush.ConversationID] = { ENABLE UNWIND => NULL; -- RestoreInvariant; cDesc: ConvDesc; state: StateInConv; IF NOT (([,cDesc, state]_FinchOn[convID]).on) THEN RETURN; SELECT state FROM $ringing, $notified => []_Request[cDesc, $active]; ENDCASE; }; PlaceCall: PUBLIC ENTRY PROC [ convID: Thrush.ConversationID_nullConvID, rName: ROPE, -- rName or description number: ROPE, -- telephone number, if present useNumber: BOOL_FALSE ] = { ENABLE UNWIND=>NULL; -- RestoreInvariant; PlaceCallInt[convID, rName, number, useNumber]; }; PlaceCallInt: INTERNAL PROC [ convID: Thrush.ConversationID_nullConvID, rName: ROPE, -- rName or description number: ROPE, -- telephone number, if present useNumber: BOOL_FALSE, convAttributes: ThParty.Attributes _ NIL, partyAttributes: ThParty.Attributes _ NIL ] = { calledPartyID: PartyID; nb: NB; reason: Thrush.Reason _ $notFound; comment: Rope.ROPE _ NIL; IF ~useNumber THEN { [nb, calledPartyID] _ThParty.GetParty[shh:info.shh, partyID:info.partyID, rName:rName]; SELECT nb FROM $noSuchParty2, $noIdentSupplied => useNumber _ TRUE; $serverDown, $notEnabled, $noSuchParty => RETURN; -- Futile ENDCASE; }; IF useNumber AND number.Length[]>0 THEN [nb, calledPartyID] _ ThParty.GetPartyFromNumber[ shh: info.shh, partyID: info.partyID, phoneNumber: number, description: rName]; SELECT nb FROM $success => reason _ NIL; -- reason: NIL, comment: NIL $noSuchParty2 => NULL; -- reason: $notFound, comment: NIL $narcissism => comment _ "Attempt to call self rejected on philosophical grounds"; ENDCASE => { ReportConversationState[nb, NIL, NIL]; RETURN; }; [--nb, cDesc--]_Connect[calledPartyID, convID, reason, comment, convAttributes, partyAttributes]; }; lowPriority: ThParty.Attributes _ LIST[[$Priority, "300"]]; Join: PUBLIC ENTRY PROC[convName: Rope.ROPE] RETURNS[nb: Thrush.NB_$noSuchConv] = { ENABLE UNWIND => NULL; conversationInfo: ThParty.ConversationInfo; cDesc: FinchSmarts.ConvDesc; rName: Rope.ROPE; [nb, conversationInfo] _ ThParty.GetConversationFromName[ shhh: info.shh, partyID: info.partyID, name: convName]; IF nb=$success AND conversationInfo.numIdle >= conversationInfo.numParties THEN nb _ $noSuchConv; SELECT nb FROM $success => NULL; $noSuchConv => { rName _ NameDB.GetAttribute[ rName: convName, attribute: $rname, key: $conversationname]; IF rName=NIL THEN RETURN; nb _ $unknown; PlaceCallInt[rName: rName, number: NIL, partyAttributes: lowPriority]; nb _ $success; RETURN; }; ENDCASE => RETURN; cDesc _ GetConv[convID: conversationInfo.convID, createOK: TRUE]; nb _ Request[cDesc, $active, $join, "Join conversation in progress", NIL, lowPriority]; IF nb=$stateMismatch THEN -- Try at most twice nb _ Request[cDesc, $active, $join, "Join conversation in progress", NIL, lowPriority]; }; Feep: PUBLIC ENTRY PROC[convID: ConversationID, feepString: ROPE] = { ENABLE UNWIND => NULL; cDesc: ConvDesc; nb: Thrush.NB_$success; { state: StateInConv; IF NOT(([,cDesc, state]_FinchOn[convID]).on) OR state#$active THEN nb _ $convNotActive; IF nb=$success AND cDesc.feepInterface=NIL THEN { [nb, cDesc.feepInterfaceSpec] _ ObtainServiceInterface[NIL, "LarkFeep", cDesc]; IF nb=$success AND cDesc.feepShhh = NIL THEN [cDesc.feepInterface, cDesc.feepShhh] _ LarkFeepSunImport.ImportInterface[ instance: cDesc.feepInterfaceSpec.interfaceName.instance]; <<Really need better generic way to avoid duplicating work and stranding connections, here!>> }; IF nb=$success THEN nb _ cDesc.feepInterface.clientStubFeep[ interface: cDesc.feepInterface, shhh: cDesc.feepShhh, serviceID: cDesc.feepInterfaceSpec.serviceID, convID: cDesc.situation.self.convID, requestingParty: cDesc.situation.self.partyID, number: feepString, noisy: UserProfile.Boolean["Finch.NoisyFeep", TRUE], on: 100, off: 80 ]; }; IF nb # $success THEN VoiceUtils.Report["Sorry, was not able to `feep'.", $Finch]; }; ObtainNumbersForRName: PUBLIC PROC[rName: ROPE] RETURNS [fullRName: ROPE, number: ROPE, homeNumber: ROPE] = { [fullRName, number, homeNumber] _ ThParty.GetNumbersForRName[shh: info.shh, rName: rName]; }; CurrentRName: PUBLIC PROC RETURNS [rName: ROPE] = { RETURN[IF info#NIL THEN info.myRName ELSE NIL]; }; <<>> CurrentConversations: PUBLIC PROC RETURNS [conversations: LIST OF ConvDesc] = { RETURN[IF info#NIL THEN info.conversations ELSE NIL]; }; <<>> IdentifyVisitor: PUBLIC ENTRY PROC [visitor, password: Rope.ROPE, complain: BOOL _ TRUE] RETURNS [nb: NB] ~ { ENABLE UNWIND => NULL; guestPartyID: PartyID; guestPassKey: RPC.EncryptionKey _ VoiceUtils.CurrentPasskey[password]; guest: Rope.ROPE _ VoiceUtils.Registrize[visitor]; IF Rope.Equal[info.myRName, guest, FALSE] THEN { <> ReleaseVisitorSelf[]; RETURN }; [nb, guestPartyID] _ThParty.GetParty[shh:info.shh, partyID:info.partyID, rName:guest]; IF nb=$success THEN nb _ ThParty.Visit[shh: info.shh, hostPartyID: info.partyID, guestPartyID: guestPartyID, guestPassword: guestPassKey]; SELECT nb FROM $success => VoiceUtils.ReportFR["%s visiting %s -- %s calls will ring in both offices", $Finch, NIL, IO.rope[guest], IO.rope[info.myRName], IO.rope[guest]]; $passwordNotValid => IF complain THEN VoiceUtils.ReportFR[ "Invalid password for %s", $Finch, NIL, IO.rope[guest]]; $visitingIllegal => Problem[ "Visitor and/or visitee not suitable for visiting (possibly missing Lark?)"]; $noSuchParty2 => VoiceUtils.ReportFR[ "Couldn't identify visitor %s", $Finch, NIL, IO.rope[guest]]; $noSuchParty => VoiceUtils.ReportFR["Couldn't identify visitee %s", $Finch, NIL, IO.rope[info.myRName]]; -- should never happen ENDCASE => Problem["Server communication problem", nb]; }; ReleaseVisitor: PUBLIC ENTRY PROC [visitor, password: Rope.ROPE] ~ { ENABLE UNWIND => NULL; nb: NB; guestPartyID: PartyID; guestPassword: RPC.EncryptionKey _ VoiceUtils.CurrentPasskey[password]; IF visitor.Length[]=0 THEN { -- assume cancelling visiting at home via "Unvisit" ReleaseVisitorSelf[]; RETURN }; visitor _ VoiceUtils.Registrize[visitor]; IF Rope.Equal[info.myRName, visitor, FALSE] THEN { <> ReleaseVisitorSelf[]; RETURN }; [nb, guestPartyID] _ThParty.GetParty[shh:info.shh, partyID:info.partyID, rName: visitor]; <> IF nb=$noSuchParty2 THEN VoiceUtils.ReportFR[ "Couldn't identify visitor %s", $Finch, NIL, IO.rope[visitor]]; <<Need to swap the nb codes in ThPartyInitImpl>> IF nb = $success THEN { hostPartyID: PartyID; [visitee: hostPartyID] _ ThParty.DescribeParty[partyID: guestPartyID, nameReq: $none]; nb _ IF hostPartyID=info.partyID THEN ThParty.Unvisit[shh: info.shh, hostPartyID: hostPartyID, guestPartyID: guestPartyID, guestPassword: guestPassword] ELSE $noSuchVisiting; }; SELECT nb FROM $success => VoiceUtils.ReportFR["Visiting cancelled for %s", $Finch, NIL, IO.rope[visitor]]; $noSuchVisiting => VoiceUtils.ReportFR[ "%s was not visiting here", $Finch, NIL, IO.rope[visitor]]; $noSuchParty => VoiceUtils.ReportFR[ "Couldn't identify visitor %s", $Finch, NIL, IO.rope[visitor]]; $noSuchParty2 => VoiceUtils.ReportFR["Couldn't identify visitee %s", $Finch, NIL, IO.rope[info.myRName]]; -- should never happen ENDCASE => Problem["Server communication problem", nb]; }; ReleaseVisitorSelf: INTERNAL PROC ~ { nb: NB _ $noSuchVisiting; IF info.partyID # nullID THEN nb _ ThParty.UnvisitSelf[shh: info.shh, guestPartyID: info.partyID]; SELECT nb FROM $success => VoiceUtils.ReportFR[ "Visiting cancelled for %s -- welcome home", $Finch, NIL, IO.rope[info.myRName]]; $noSuchVisiting => VoiceUtils.ReportFR[ "%s was not visiting anywhere", $Finch, NIL, IO.rope[info.myRName]]; $noSuchParty => VoiceUtils.ReportFR["Couldn't identify visitor %s", $Finch, NIL, IO.rope[info.myRName]]; -- should never happen ENDCASE => Problem["Server communication problem", nb]; }; ServiceConnect: PUBLIC ENTRY PROC[ serviceName: ROPE, convID: ConversationID_nullConvID, createOK: BOOL_TRUE, addOK: BOOL_FALSE ] RETURNS [ nb: NB_$success, cDesc: ConvDesc_NIL ] = { ENABLE UNWIND=>NULL; state: StateInConv _ $idle; calledPartyID: PartyID; cCode: ConvCode; backgroundAttributes: ThParty.Attributes _ IF backgrounding THEN lowPriority ELSE NIL; IF NOT (([,cDesc, state]_FinchOn[convID]).on) THEN RETURN[nb: $finchNotRunning]; IF cDesc#NIL AND cDesc.situation.self.state>Thrush.notReallyInConv THEN RETURN; [cCode, cDesc] _ GetActiveConversation[serviceName,addOK]; SELECT cCode FROM $ok => { nb _ WaitForActive[cDesc, FALSE]; RETURN; }; $hld => { nb _ Request[cDesc, $active]; IF nb=$success THEN nb _ WaitForActive[cDesc, FALSE]; RETURN; }; $bzy => RETURN[$convStillActive, NIL]; $idl, $res, $addOK => IF ~createOK THEN RETURN[$noSuchConv, NIL]; ENDCASE => ERROR; IF cDesc#NIL THEN { state_ cDesc.situation.self.state; convID_ cDesc.situation.self.convID; }; IF serviceName = NIL THEN calledPartyID _ nullID ELSE [nb, calledPartyID] _ ThParty.GetParty[ shh: info.shh, partyID: info.partyID, rName: serviceName, type: $service]; IF nb=$success THEN [nb, cDesc] _ Connect[calledPartyID: calledPartyID, convID: convID, partyAttributes: backgroundAttributes, addOK: addOK]; IF nb=$success AND serviceName#NIL THEN nb _ WaitForActive[cDesc, TRUE]; }; <<>> ConvCode: TYPE = { ok, addOK, res, hld, bzy, idl }; GetActiveConversation: INTERNAL PROC[serviceName: ROPE_NIL, addOK: BOOL_FALSE] RETURNS[convCode: ConvCode _ $idl, cDesc: ConvDesc_NIL] = { <> <<$ok) a non-idle conversation that's already established to the specified service.>> <<$addOK) an active conversation that does not include the specified service (addOK only)>> <<$res) a conversation in state $reserved>> <<$hld) an established converation is on hold and waiting.>> <<$bzy) an established conversation that doesn't satisfy the above criteria>> <<$idl) no active conversations exist.>> IF serviceName=NIL THEN RETURN; -- Can't validate FOR convs: LIST OF ConvDesc _ info.conversations, convs.rest WHILE convs#NIL DO possibleCDesc: ConvDesc _ NARROW[convs.first]; pInfo: ThParty.PartyInfo _ possibleCDesc.partyInfo; possibleState: StateInConv _ possibleCDesc.situation.self.state; SELECT possibleState FROM $reserved => {convCode _ $res; cDesc _ possibleCDesc; LOOP;}; <> $inactive => { IF convCode=$idl THEN { convCode _ $hld; cDesc _ possibleCDesc; }; LOOP;}; $initiating, $ringback, $active => NULL; ENDCASE => LOOP; <> IF pInfo#NIL THEN FOR ix: NAT IN [1..pInfo.conversationInfo.numParties] DO IF ix#pInfo.ixSelf AND pInfo[ix].type=$service AND Rope.IsPrefix[serviceName, pInfo[ix].name, FALSE] THEN RETURN[$ok, possibleCDesc]; ENDLOOP; -- There's still presumably at most one active conversation RETURN[ IF addOK AND possibleCDesc.situation.self.state=$active THEN $addOK ELSE $bzy, possibleCDesc]; ENDLOOP; }; ObtainServiceInterface: PUBLIC PROC[ serviceName: ROPE, interfaceName: ROPE, cDesc: ConvDesc_NIL ] RETURNS [ nb: NB_$success, interfaceSpec: Thrush.InterfaceSpec ] = { partyID: Thrush.PartyID_Thrush.nullID; credentials: Thrush.Credentials; IF cDesc#NIL THEN { pInfo: ThParty.PartyInfo _ cDesc.partyInfo; credentials _ cDesc.situation.self; IF pInfo#NIL THEN FOR ix: NAT IN [1..pInfo.conversationInfo.numParties] DO IF ix # pInfo.ixSelf AND ( serviceName=NIL OR ( pInfo[ix].type = $service AND pInfo[ix].state >= $active AND Rope.IsPrefix[serviceName, pInfo[ix].name, FALSE] ) ) THEN { partyID _ pInfo[ix].partyID; EXIT; }; ENDLOOP; } ELSE IF serviceName = NIL THEN partyID _ info.partyID -- get from self! ELSE { -- want interface only credentials _ [info.partyID, info.smartsID]; [nb, partyID] _ThParty.GetParty[ shh: info.shh, partyID: info.partyID, rName: serviceName, type: $service]; IF nb#$success THEN RETURN; }; IF partyID = Thrush.nullID THEN RETURN[nb: $noSuchParty2, interfaceSpec: nullInterfaceSpec]; [nb, interfaceSpec] _ ThParty.LookupServiceInterface[info.shh, credentials, partyID, interfaceName]; }; anyone: LIST OF ROPE _ LIST["*"]; announcement: ThParty.Attributes _ LIST[[$subject, "Announcement"]]; mediumPriority: ThParty.Attributes _ LIST[[$Priority, "400"]]; <> <<>> Announce: ENTRY PROC [rNames: LIST OF ROPE] ~ { ENABLE UNWIND=>NULL; -- RestoreInvariant; [] _ AnnounceInt[rNames]; }; AnnounceInt: INTERNAL PROC [rNames: LIST OF ROPE] RETURNS [nb: NB_$success, cDesc: ConvDesc_NIL] ~ { count: INT _ 0; state: StateInConv; convID: ConversationID_nullConvID; convEvent: Thrush.ConvEvent; IF ~(([,cDesc, state]_FinchOn[convID]).on) THEN { nb _ $notEnabled; RETURN; }; IF cDesc = NIL THEN [cDesc, state] _ GetCurrentConv[]; <> SELECT state FROM $idle => { [nb, convEvent] _ ThParty.CreateConversation[ shhh: info.shh, credentials: [partyID: info.partyID, smartsID: info.smartsID] ]; IF nb = $success THEN { pInfo: ThParty.PartyInfo; cDesc _ NoteNewState[convEvent]; pInfo _ cDesc.partyInfo; IF pInfo=NIL OR ~pInfo[pInfo.ixSelf].voicePath THEN { [] _ Request[cDesc, $failed, $noVoicePath, "Your Etherphone is not running"]; RETURN[$noVoicePath, cDesc]; }; }; }; $reserved, $parsing => nb _ Request[cDesc, $initiating]; ENDCASE => { ReportConversationState[$convStillActive, NIL, "Conversation already in progress"]; RETURN; }; IF nb # $success THEN RETURN; cDesc.originatorRecorded _ TRUE; nb _ ThParty.RegisterConversation[shhh: info.shh, credentials: cDesc.situation.self, name: "Announcement", convAttributes: announcement, accessList: anyone]; VoiceUtils.Report["Notifying listeners...", $Finch]; FOR names: LIST OF ROPE _ rNames, names.rest WHILE names#NIL DO calledPartyID: PartyID; partyType: Thrush.PartyType; IF Rope.Equal[names.first, info.myRName, FALSE] THEN LOOP; -- Don't call self <> [nb, calledPartyID] _ThParty.GetParty[shh:info.shh, partyID:info.partyID, rName:names.first]; SELECT nb FROM $serverDown, $notEnabled => RETURN; -- Futile $noSuchParty2, $noIdentSupplied, $noSuchParty => LOOP; ENDCASE; [nb: nb, type: partyType] _ ThParty.DescribeParty[calledPartyID, $current]; IF partyType=$trunk THEN LOOP; <> nb_ThParty.Alert[shhh: info.shh, credentials: cDesc.situation.self, calledPartyID: calledPartyID, partyAttributes: mediumPriority]; SELECT nb FROM $success => count _ count + 1; $stateMismatch => { nb_Request[cDesc, $failed, $error, "Conversation already in progress"]; <<Some smarts don't do this. They probably should.>> RETURN; }; $narcissism => NULL; -- Should never happen ???see above??? ENDCASE => {Problem["Server communication problem", nb]; RETURN;} <<Do something interesting  see other smarts for more confusion>> ENDLOOP; nb _ WaitForActive[cDesc, FALSE]; IF nb=$success AND count > 0 THEN VoiceUtils.ReportFR["%g parties contacted -- Please begin speaking", $Finch, NIL, IO.int[count]] ELSE VoiceUtils.Report["No listeners found", $Finch, NIL]; }; <> GetConv: INTERNAL PROC[convID: ConversationID, createOK: BOOL_FALSE] RETURNS [ cDesc: ConvDesc_NIL ] = --INLINE-- { cL: LIST OF ConvDesc; IF convID#nullConvID THEN FOR convs: LIST OF ConvDesc _ info.conversations, convs.rest WHILE convs#NIL DO cDesc _ convs.first; IF cDesc.situation.self.convID = convID THEN RETURN; ENDLOOP; cDesc _ NIL; IF ~createOK THEN RETURN; <> cDesc _ NEW[FinchSmarts.ConvDescBody_[ situation: [ self: [ convID: convID, smartsID: info.smartsID, partyID: info.partyID ]]]]; cL _ LIST[cDesc]; info.conversations _ NConcConvs[info.conversations, cL]; }; FinchOn: INTERNAL PROC[convID: ConversationID_nullConvID] RETURNS [ on: BOOL_FALSE, cDesc: ConvDesc_NIL, state: StateInConv_$idle] = { IF info=NIL OR ~info.enabled THEN { VoiceUtils.Report["Your Finch is not running", $Finch]; RETURN; }; on_TRUE; IF convID=nullConvID THEN RETURN; cDesc _ GetConv[convID, FALSE]; IF cDesc#NIL THEN state_cDesc.situation.self.state; }; NoteNewState: INTERNAL PROC[convEvent: Thrush.ConvEvent] RETURNS[cDesc: ConvDesc]= { cInfo: ThParty.ConversationInfo; pInfo: ThParty.PartyInfo; nb: NB; timeAdjustment: INT; prevState, currState: StateInConv; cDesc _ GetConv[convEvent.self.convID, TRUE]; prevState _ cDesc.situation.self.state; cDesc.situation _ convEvent^; [nb, cInfo] _ ThParty.GetConversationInfo[shh: info.shh, convID: convEvent.self.convID]; <> <> <> currState _ cDesc.situation.self.state; SELECT TRUE FROM nb # $success => NULL; -- Problem["Can't obtain conversation information"]; <> currState#prevState, cDesc.numParties#cInfo.numParties, cDesc.numActive#cInfo.numActive, cDesc.numIdle#cInfo.numIdle => { <> cDesc.subject _ FetchAttribute[cInfo.convAttributes, $subject].value; timeAdjustment _ BasicTime.Period[from: convEvent.time, to: BasicTime.Now[]]; cDesc.startTime _ BasicTime.Update[cInfo.startTime, timeAdjustment]; cDesc.numParties _ cInfo.numParties; cDesc.numActive _ cInfo.numActive; cDesc.numIdle _ cInfo.numIdle; [nb, pInfo] _ ThParty.GetPartyInfo[shh: info.shh, credentials: cDesc.situation.self, nameReq: $description, allParties: TRUE]; IF nb#$success THEN NULL -- Problem["Can't obtain conversation information", nb] ELSE { ixOriginator: NAT _ pInfo.ixOriginator; cDesc.partyInfo _ pInfo; IF ixOriginator#ThParty.nullIx AND ixOriginator # pInfo.ixSelf AND pInfo[ixOriginator].type=$trunk THEN pInfo[ixOriginator].intendedName _ "outside line"; }; }; ENDCASE; SELECT currState FROM $idle, $neverWas => info.conversations _ DequeueConv[info.conversations, cDesc]; $initiating => TRUSTED { Process.Detach[FORK LimitSignalling[cDesc, $initiating]]; }; $notified => SELECT filtering FROM -- Needs tests for calls already in progress.  1 => []_Request[cDesc, $idle, $notImportantEnough, "This is a test"]; 2 => []_Request[cDesc, $ringing]; 3 => []_Request[cDesc, $active, $whatever, "Hot line!"]; ENDCASE => TRUSTED { Process.Detach[FORK LimitSignalling[cDesc, $notified]]; }; ENDCASE; IF currState#inactive AND cDesc.ultimateState> ReportConversationState[$success, cDesc, NIL]; <<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. >> BROADCAST info.stateChange; }; maxJoinTries: INT _ 3; Request: INTERNAL PROC[ cDesc: ConvDesc, state: StateInConv, reason: Thrush.Reason_NIL, comment: ROPE_NIL, convAttributes: ThParty.Attributes_NIL, partyAttributes: ThParty.Attributes_NIL ] RETURNS [nb: NB] = { DO convEvent: Thrush.ConvEvent; pInfo: ThParty.PartyInfo _ cDesc.partyInfo; reportToAll: BOOL = SELECT state FROM $idle, $failed, $active, $inactive, $ringing => TRUE, ENDCASE=>FALSE; IF state=$failed AND (pInfo = NIL OR ~pInfo[pInfo.ixSelf].voicePath) THEN state _ $idle; [nb, convEvent] _ ThParty.Advance[shhh: info.shh, credentials: cDesc.situation.self, state: state, reportToAll: reportToAll, reason: reason, comment: comment, convAttributes: convAttributes, partyAttributes: partyAttributes]; SELECT nb FROM $success => []_NoteNewState[convEvent]; $stateMismatch => { cDesc_NoteNewState[convEvent]; IF cDesc.situation.self.state = state THEN nb _ $success <> ELSE IF state=$idle AND cDesc.situation.self.state # $idle THEN LOOP; }; ENDCASE => ReportConversationState[nb, cDesc, "Telephone server action failed"]; <<Real confused about Problem (typeout only) reporting vs. client reporting. The responsibilities are unconvincingly distributed among Smarts and client.>> EXIT; ENDLOOP; }; Connect: INTERNAL PROC [calledPartyID: PartyID, convID: ConversationID_nullConvID, reason: Thrush.Reason _ NIL, comment: ROPE_NIL, convAttributes: ThParty.Attributes _ NIL, partyAttributes: ThParty.Attributes _ NIL, addOK: BOOL_FALSE] RETURNS [ nb: NB_$success, cDesc: ConvDesc_NIL ] = { <> state: StateInConv; newState: StateInConv = SELECT TRUE FROM reason#NIL => $failed, calledPartyID = nullID => $parsing, -- temp until can debug inactive stuff. ENDCASE => $initiating; convEvent: Thrush.ConvEvent; IF ~(([,cDesc, state]_FinchOn[convID]).on) THEN { nb _ $notEnabled; RETURN; }; SELECT state FROM $idle => { [nb, convEvent] _ ThParty.CreateConversation[ shhh: info.shh, credentials: [partyID: info.partyID, smartsID: info.smartsID], state: newState, reason: reason, comment: comment, convAttributes: convAttributes, partyAttributes: partyAttributes ]; IF nb = $success THEN { pInfo: ThParty.PartyInfo; cDesc _ NoteNewState[convEvent]; pInfo _ cDesc.partyInfo; IF pInfo=NIL OR ~pInfo[pInfo.ixSelf].voicePath THEN { [] _ Request[cDesc, $failed, $noVoicePath, "Your Etherphone is not running"]; RETURN[$noVoicePath, cDesc]; }; }; }; $reserved, $parsing => nb _ Request[cDesc, newState, reason, comment, convAttributes, partyAttributes]; $active => IF ~addOK THEN { ReportConversationState[$convStillActive, NIL, "Conversation already in progress"]; RETURN; }; ENDCASE => { ReportConversationState[$convStillActive, NIL, "Conversation already in progress"]; RETURN; }; IF nb # $success OR newState=$failed THEN RETURN; cDesc.originatorRecorded _ TRUE; IF newState # $initiating THEN RETURN; nb_ThParty.Alert[shhh: info.shh, credentials: cDesc.situation.self, calledPartyID: calledPartyID]; SELECT nb FROM $success => NULL; $stateMismatch => { nb_Request[cDesc, $failed, $error, "Conversation already in progress"]; <<Some smarts don't do this. They probably should.>> RETURN; }; $narcissism => { nb_Request[cDesc, $failed, $notFound, "Attempt to call self rejected on philosophical grounds"]; RETURN; }; ENDCASE => Problem["Server communication problem", nb]; <<Do something interesting  see other smarts for more confusion>> }; WaitForActive: INTERNAL PROC[cDesc: ConvDesc, demandNewParty: BOOL_FALSE] RETURNS [nb: NB_$success] = { <> numParties: NAT _ cDesc.numParties; FOR i: NAT IN [0..pd.waitsForConnect) DO SELECT cDesc.situation.self.state FROM $active => IF ~demandNewParty OR cDesc.numParties > numParties THEN RETURN; $failed => EXIT; ENDCASE; TRUSTED{Process.SetTimeout[@info.stateChange,Process.SecondsToTicks[pd.jayTimeout]];}; WAIT info.stateChange; SELECT cDesc.situation.self.state FROM $idle, $neverWas => EXIT; -- something wrong. ENDCASE; ENDLOOP; nb_Request[cDesc, $failed, $error, "Finch failed to connect to voice service."]; IF nb=$success THEN nb_$timedOut; }; Problem: PUBLIC PROC[remark: ROPE_NIL, nb: NB_NIL] = { IF nb=NIL THEN VoiceUtils.Problem[remark, $Finch] ELSE VoiceUtils.ProblemFR["%g, reason: %g", $Finch, NIL, rope[remark], atom[nb]]; }; FetchAttribute: PUBLIC PROC[ attributes: ThParty.Attributes, attribute: ATOM, default: ROPE_NIL] RETURNS [value: ROPE, valueLoc: ThParty.Attributes_NIL] = { value _ default; FOR aL: ThParty.Attributes _ attributes, aL.rest WHILE aL#NIL DO IF aL.first.type = attribute THEN RETURN[aL.first.value, aL]; ENDLOOP; }; GetCurrentConv: INTERNAL PROC RETURNS [ cDesc: ConvDesc_NIL, state: Thrush.StateInConv ] = --INLINE-- { <> FOR convs: LIST OF ConvDesc _ info.conversations, convs.rest WHILE convs#NIL DO ok: BOOL _ TRUE; cDesc _ convs.first; state _ cDesc.situation.self.state; SELECT state FROM $reserved, $parsing => { pInfo: ThParty.PartyInfo _ cDesc.partyInfo; FOR ix: NAT IN [1..pInfo.conversationInfo.numParties] DO IF ix#pInfo.ixSelf AND pInfo[ix].type=$service THEN { ok _ FALSE; EXIT; }; ENDLOOP; IF ok THEN RETURN; }; ENDCASE; ENDLOOP; cDesc _ NIL; state _ $idle; }; <> <> ReportConversationState: PUBLIC PROC [nb: NB, cDesc: ConvDesc, remark: Rope.ROPE] ~ { IF info=NIL THEN RETURN; pd.requests.QueueClientAction[ReallyReportConversationState, NEW[CSBody _ [nb, cDesc, remark]]]; }; CS: TYPE = REF CSBody; CSBody: TYPE = RECORD[nb: NB, cDesc: ConvDesc, remark: Rope.ROPE]; ReallyReportConversationState: PROC[r: REF] = { cS: CS _ NARROW[r]; reportProcsTable: RedBlackTree.Table _ NARROW[Atom.GetProp[$ReportProcs, $rpTable]]; EachRCS: RedBlackTree.EachNode = { procs: FinchSmarts.ReportProcs _ NARROW[data]; IF procs.reportConversationState#NIL THEN procs.reportConversationState[cS.nb, cS.cDesc, cS.remark]; }; IF reportProcsTable#NIL THEN EnumerateIncreasing[reportProcsTable, EachRCS]; }; ReportRequestState: PROC [cDesc: ConvDesc, actionReport: Thrush.ActionReport] ~ { pd.requests.QueueClientAction[ReallyReportRequestState, NEW[RSBody _ [cDesc, actionReport]]]; }; RS: TYPE = REF RSBody; RSBody: TYPE = RECORD[cDesc: ConvDesc, actionReport: Thrush.ActionReport]; ReallyReportRequestState: PROC[r: REF] = { rS: RS _ NARROW[r]; reportProcsTable: RedBlackTree.Table _ NARROW[Atom.GetProp[$ReportProcs, $rpTable]]; actionRequest: REF_NIL; EachRRS: RedBlackTree.EachNode = { procs: FinchSmarts.ReportProcs _ NARROW[data]; IF procs.reportRequestState#NIL THEN actionRequest _ procs.reportRequestState[rS.cDesc, rS.actionReport, actionRequest]; }; IF reportProcsTable#NIL THEN EnumerateIncreasing[reportProcsTable, EachRRS]; }; EnumerateIncreasing: PUBLIC PROC [self: RedBlackTree.Table, procToApply: RedBlackTree.EachNode] = { PList: TYPE = RECORD[s: SEQUENCE m: NAT OF FinchSmarts.ReportProcs]; pList: REF PList _ NEW[PList[20]]; index: NAT _ 0; GetEm: RedBlackTree.EachNode = { pList[index] _ NARROW[data]; index _ index+1; IF index=20 THEN ERROR; }; RedBlackTree.EnumerateIncreasing[self, GetEm]; FOR doIndex: NAT IN [0..index) DO IF procToApply[pList[doIndex]] THEN EXIT; ENDLOOP; }; <<>> <> <<>> SetFiltering: PROC[which: INT, deferAnswer: ROPE_ "true"] RETURNS [didIt: ATOM_NIL, message: Rope.ROPE_NIL] _ { ENABLE NameDB.Error => {didIt _ $Failure; message_"Database access failure"; CONTINUE; }; filtering _ which; NameDB.SetAttribute[VoiceUtils.CurrentRName[], $deferanswer, deferAnswer]; }; filtering: INT_0; backgrounding: BOOL_FALSE; FilterResetCmd: Commander.CommandProc = TRUSTED { [result, msg] _ SetFiltering[0, "false"]; }; FilterZeroCmd: Commander.CommandProc = TRUSTED { [result, msg] _ SetFiltering[0]; }; BackgroundCmd: Commander.CommandProc = TRUSTED { [result, msg] _ SetFiltering[0]; backgrounding _ TRUE; }; NoBackgroundCmd: Commander.CommandProc = TRUSTED { [result, msg] _ SetFiltering[0, "false"]; backgrounding _ FALSE; }; FilterOneCmd: Commander.CommandProc = TRUSTED { [result, msg] _ SetFiltering[1]; }; FilterTwoCmd: Commander.CommandProc = TRUSTED { [result, msg] _ SetFiltering[2]; }; FilterThreeCmd: Commander.CommandProc = TRUSTED { [result, msg] _ SetFiltering[3]; }; InitFiltering: PROC = { filterMsg: ROPE; [message: filterMsg] _ SetFiltering[0]; <> IF filterMsg#NIL THEN VoiceUtils.Report[filterMsg, $Finch]; }; EDLNames: LIST OF ROPE _ LIST["Arnon.pa", "Beach.pa", "Beretta.pa", "Bier.pa", "Crow.pa", "Diebert.pa", "Glassner.pa", "Pier.pa", "Plass.pa", "PolleZ.pa", "Shoemake.pa", "Stone.pa", "Subhana.pa", "Wyatt.pa"]; AnnounceCmd: Commander.CommandProc = TRUSTED { list: LIST OF ROPE; length: INT; [list, length] _ CommandTool.ParseToList[cmd: cmd]; IF list=NIL THEN list _ EDLNames; Announce[list]; }; pd.requests _ MBQueue.Create[]; Commander.Register["FReset", FilterResetCmd, "Don't filter calls. This is a test."]; Commander.Register["F0", FilterZeroCmd, "Notifies will time out. This is a test."]; Commander.Register["F1", FilterOneCmd, "Reject all calls. This is a test."]; Commander.Register["F2", FilterTwoCmd, "Accept (ringing) all calls. This is a test."]; Commander.Register["F3", FilterThreeCmd, "Answer all calls (hot line). This is a test."]; Commander.Register["Background", BackgroundCmd, "FinchTool will supercede background calls."]; Commander.Register["NoBackground", NoBackgroundCmd, "FinchTool will not supercede background calls."]; <<>> <> <> Commander.Register["Announce", AnnounceCmd, "Create a com-line like call to a set of Etherphone users."]; }. <<>> <> Commander.Register["VuFinch", ViewCmd, "Program Management variables Finch"]; <> <> <> <<};>> <<>> ViewCmd: Commander.CommandProc = TRUSTED { <> }; <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> < ServiceConnect, more rigid success requirements.>> <> <> <> <> <> <2 parties in conversation)>> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <<>> <> <> <<>>