FinchSmartsImpl.mesa
Copyright Ó 1984, 1986, 1987 by Xerox Corporation. All rights reserved.
Last Edited by: Swinehart, April 6, 1987 2:27:41 pm PDT
Doug Terry, September 5, 1986 4:17:06 pm PDT
Polle Zellweger (PTZ) March 2, 1987 7:15:18 pm PST
DIRECTORY
Commander USING [ CommandProc, Register ],
FinchSmarts,
GList,
GVBasics USING [ MakeKey, Password ],
IO,
LarkFeepRpcControl USING [ Feep, ImportNewInterface ],
LupineRuntime USING [ BindingError ],
MBQueue USING [ Create, QueueClientAction ],
NameDB USING [ GetAttribute, SetAttribute ],
NamesRPC USING [ StartConversation ],
Nice,
Process USING [ Detach, SecondsToTicks, SetTimeout, Ticks ],
PupSocket USING [ GetUniqueID ],
RefID USING [ ID ],
Rope,
RPC USING [ AuthenticateFailed, CallFailed, EncryptionKey, ImportFailed, VersionRange ],
ThParty USING [ Advance, Alert, CheckIn, ConversationInfo, CreateConversation, Deregister, DescribeParty, Enable, GetConversationInfo, GetParty, GetNumbersForRName, GetPartyFromNumber, GetPartyInfo, LookupServiceInterface, Register, Unvisit, Visit ],
ThPartyRpcControl,
Thrush
USING[
ActionReport, ConversationID, ConvEvent, Credentials, InterfaceSpec, NB, none, notReallyInConv, nullConvID, nullID, PartyID, Reason, ROPE, SHHH, SmartsID, StateInConv, unencrypted ],
ThSmarts,
ThSmartsRpcControl,
ThVersions USING [ GetThrushVR, FinchVersion, FinchVR ],
UserProfile USING [ Token],
VoiceUtils USING [ CurrentPasskey, CurrentPassword, CurrentRName, InstanceFromNetAddress, MakeRName, NetAddress, NetAddressFromRope, OwnNetAddress, Problem, ProblemFR, Registrize, Report, ReportFR ]
;
FinchSmartsImpl:
CEDAR
MONITOR
IMPORTS Commander, GList, GVBasics, IO, LarkFeepRpcControl, LupineRuntime, MBQueue, NameDB, NamesRPC, Nice, Process, PupSocket, Rope, RPC, ThParty, ThPartyRpcControl, ThSmartsRpcControl, ThVersions, UserProfile, VoiceUtils
EXPORTS FinchSmarts, ThSmarts = {
OPEN IO;
Declarations
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];
finchPollerWait: CONDITION;
PD:
TYPE =
RECORD [
doReports: BOOL←FALSE,
doNice: BOOL←FALSE,
timeoutPoller: INTEGER ← 15,
timeoutNoAction: INTEGER ← 30,
timeoutJayConnect: INTEGER ← 1,
noJayTicks: Process.Ticks ← Process.SecondsToTicks[1],
keyDistTicks: Process.Ticks ← Process.SecondsToTicks[10],
encryptionRequested: BOOLEAN←TRUE,
interfacesAreImported: BOOLEAN←FALSE,
smartsIsExported: BOOLEAN←FALSE,
waitsForConnect: NAT𡤆,
queueIt: BOOL←FALSE,
finchOn: BOOLEAN←FALSE,
maxProseLength: INT,
Call backs
systemStateSubscribers: LIST OF FinchSmarts.ReportSystemStateProc,
convStateSubscribers: LIST OF FinchSmarts.ReportConversationStateProc,
requestStateSubscribers: LIST OF FinchSmarts.ReportRequestStateProc
];
info: FinchInfo;
pd: REF PD ← NEW[PD←[]];
Report:
PROC[what:
ROPE] = {
IF NOT pd.doReports THEN RETURN;
VoiceUtils.Report[what, $Finch];
};
BeNice:
PROC[r:
REF, d:
INT] = {
IF NOT pd.doNice THEN RETURN;
Nice.BeNice[r, d, $Finch, NIL];
};
NConcConvs:
PROC[l1, l2:
LIST
OF ConvDesc]
RETURNS [
LIST
OF ConvDesc] =
INLINE {
RETURN[
NARROW[GList.Nconc[l1, l2]]]; };
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 l1.rest#
NIL
DO
IF l1.rest.first=c THEN { l1.rest ← l1.rest.rest; RETURN[l1]; }; -- Internal to list
ENDLOOP;
RETURN[l1]; -- Not in list
};
Supervision
Progress:
PUBLIC
ENTRY
PROC[
shh: SHHH,
convEvent: Thrush.ConvEvent
] = {
ENABLE UNWIND => NULL; -- RestoreInvariant;
Some party has changed state in a conversation we know about (or should).
Two cases:
Our party has changed state in a conversation, whether or not we're going to deal with it (at present, LarkSmarts makes all such decisions)
Another party changed state in a conversation we know about (at present, we don't need to know that; again, LarkSmarts does all the work.) That case is filtered out in Progress, above.
It is our duty to obtain all relevant information about the call when the information becomes available, and to notify any Finch Clients.
IF convEvent.self.partyID = convEvent.other.partyID THEN [] ← NoteNewState[convEvent];
};
Substitution:
PUBLIC
ENTRY
PROC[
shh: SHHH,
convEvent: Thrush.ConvEvent,
oldPartyID: Thrush.PartyID,
newPartyID: Thrush.PartyID
] = {
ENABLE UNWIND => NULL; -- RestoreInvariant;
One party has replaced another (they were or are now in a poacher/poachee relationship) in this conversation, so we need to update our information about the conversation. Same as Progress, except that we clear any indication that we know anything about the present state before noting the new state.
If this notice indicates that our own party is the oldPartyID, we should find a way to inform our ReportConversationState clients that we are no longer involved in this conversation. We should also pull the conv. from our active list. This happens when the same user logs in and runs Finch somewhere else.
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!)}
};
Client Functions
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]𡤏inchOn[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;
[,cDesc, state]𡤏inchOn[convID];
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;
RPC.CallFailed => { UninitFinchSmarts["Communication Failure"]; CONTINUE; };
};
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; 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--]𡤌onnect[calledPartyID, convID, reason, comment];
};
Feep:
PUBLIC
ENTRY
PROC[convID: ConversationID, feepString:
ROPE] = {
cDesc: ConvDesc;
nb: Thrush.NB←$success;
state: StateInConv;
IF NOT(([,cDesc, state]𡤏inchOn[convID]).on) OR state#$active THEN nb ← $convNotActive;
IF nb=$success
AND cDesc.feepInterface=
NIL
THEN {
IF cDesc.numParties>1
THEN [nb, cDesc.feepInterfaceSpec] ← ThParty.LookupServiceInterface[
info.shh, cDesc.situation.self, cDesc.partyInfo[1].partyID, "LarkFeep"]
ELSE nb←$noFeepingParty;
IF nb=$success
THEN
cDesc.feepInterface ← LarkFeepRpcControl.ImportNewInterface[
interfaceName: cDesc.feepInterfaceSpec.interfaceName,
hostHint: cDesc.feepInterfaceSpec.hostHint!RPC.ImportFailed => CONTINUE
];
};
IF nb=$success
THEN nb ← LarkFeepRpcControl.Feep[
interface: cDesc.feepInterface, shhh: info.shh,
serviceID: cDesc.feepInterfaceSpec.serviceID,
convID: cDesc.situation.self.convID, requestingParty: cDesc.situation.self.partyID,
number: feepString, noisy: TRUE, on: 100, off: 80
];
IF nb # $success THEN VoiceUtils.Report["Sorry, was not able to `feep'.", $Finch];
};
GetNumbersForRName:
PUBLIC
PROC[rName:
ROPE]
RETURNS [fullRName: ROPE, number: ROPE, homeNumber: ROPE] = {
[fullRName, number, homeNumber] ←
ThParty.GetNumbersForRName[shh: info.shh, rName: rName];
};
IdentifyVisitor:
PUBLIC
ENTRY
PROC [visitor, password: Rope.
ROPE, complain:
BOOL ←
TRUE]
RETURNS [nb:
NB] ~ {
Perhaps allow a NIL password as default param - GVBasics.MakeKey does the right thing with NIL -- also with ReleaseVisitor, below.
visitingPartyID: PartyID;
visitingPassKey: GVBasics.Password ← GVBasics.MakeKey[password];
visitor ← VoiceUtils.Registrize[visitor];
IF Rope.Equal[info.myRName, visitor,
FALSE]
THEN {
cancelling visiting at home via "Visit self"
ReleaseVisitorSelf[]; RETURN
};
[nb, visitingPartyID] ←ThParty.GetParty[shh:info.shh, partyID:info.partyID, rName:visitor];
IF nb#$success THEN RETURN;
nb ← ThParty.Visit[shh: info.shh, visitedParty: info.partyID, visitingParty: visitingPartyID, visitingPassword: visitingPassKey];
SELECT nb
FROM
$success => {
VoiceUtils.ReportFR["%s visiting %s -- %s calls will ring in both offices.", $Finch, NIL, IO.rope[visitor], IO.rope[info.myRName], IO.rope[visitor]];
};
$passwordNotValid => IF complain THEN VoiceUtils.ReportFR["Invalid password for %s.", $Finch, NIL, IO.rope[visitor]];
ENDCASE;
Need to handle noSuchParty & noSuchParty2?
};
ReleaseVisitor:
PUBLIC ENTRY PROC [visitor, password: Rope.
ROPE] ~ {
nb: NB;
visitingPartyID: PartyID;
visitingPassKey: GVBasics.Password ← GVBasics.MakeKey[password];
IF visitor.Length[]=0
THEN {
-- assume cancelling visiting at home via "Unvisit"
ReleaseVisitorSelf[]; RETURN
};
visitor ← VoiceUtils.Registrize[visitor];
IF Rope.Equal[info.myRName, visitor,
FALSE]
THEN {
cancelling visiting at home via "Unvisit self"
ReleaseVisitorSelf[]; RETURN
};
[nb, visitingPartyID] ←ThParty.GetParty[shh:info.shh, partyID:info.partyID, rName:visitor];
If ThParty.Unvisit really cared about passwords (which it does not currently), we would have to keep passwords of relevant visitors, probably in the FinchInfo record.
IF nb=$success
THEN {
visitee: PartyID;
[visitee: visitee] ← ThParty.DescribeParty[partyID: visitingPartyID, nameReq: $none];
IF visitee=info.partyID
THEN {
nb ← ThParty.Unvisit[shh: info.shh, visitedParty: visitee, visitingParty: visitingPartyID, visitingPassword: visitingPassKey];
IF nb=$success
THEN {
VoiceUtils.ReportFR["Visiting cancelled for %s.", $Finch, NIL, IO.rope[visitor]];
RETURN;
};
};
};
VoiceUtils.ReportFR["%s was not visiting here.", $Finch, NIL, IO.rope[visitor]];
}; -- Correct error management
ReleaseVisitorSelf:
INTERNAL PROC ~ {
nb: NB;
visitee: PartyID;
[visitee: visitee] ← ThParty.DescribeParty[partyID: info.partyID, nameReq: $none];
nb ← ThParty.Unvisit[shh: info.shh, visitedParty: visitee, visitingParty: info.partyID, visitingPassword: VoiceUtils.CurrentPassword[]];
VoiceUtils.ReportFR[
IF nb=$success THEN "Visiting cancelled for %s. Welcome home."
ELSE "%s was not visiting elsewhere.",
$Finch, NIL, IO.rope[info.myRName]
];
};
VoiceConnect:
PUBLIC
ENTRY
PROC[
serviceName: ROPE,
convID: ConversationID←nullConvID
] RETURNS [
nb: NB←$success,
cDesc: ConvDesc←NIL
] = {
ENABLE UNWIND=>NULL;
state: StateInConv;
calledPartyID: PartyID;
IF
NOT (([,cDesc, state]𡤏inchOn[convID]).on)
THEN {
Problem["Your Finch is not running"]; RETURN[nb: $finchNotRunning]; };
IF cDesc=NIL THEN [convID, cDesc, state] ← GetActiveConversation[]; -- Hack
SELECT state
FROM
idle, reserved => {
[nb, calledPartyID] ← ThParty.GetParty[shh: info.shh, partyID: info.partyID, rName: serviceName, type: $service];
IF nb=$success THEN [nb, cDesc] ← Connect[calledPartyID, convID, NIL, NIL]
};
ENDCASE;
IF nb=$success THEN nb ← WaitForActive[cDesc];
};
LookupServiceInterface:
PUBLIC
PROC[
serviceName: ROPE,
interfaceName: ROPE,
cDesc: ConvDesc←NIL
] RETURNS [
nb: NB←$success,
shhh: Thrush.SHHH,
interfaceSpec: Thrush.InterfaceSpec
] = {
partyID: Thrush.PartyID←Thrush.nullID;
credentials: Thrush.Credentials;
IF cDesc#
NIL
THEN
{
credentials ← cDesc.situation.self;
IF cDesc.numParties>1 THEN partyID ← cDesc.partyInfo[1].partyID;
}
ELSE
{
-- want interface only
credentials ← [info.partyID, info.smartsID];
[nb, partyID] ←ThParty.GetParty[
shh: info.shh, partyID: info.partyID, rName: serviceName, type: $service];
IF nb#$success THEN RETURN;
};
IF partyID = Thrush.nullID THEN RETURN[nb: $noSuchParty2, shhh: none, interfaceSpec: nullInterfaceSpec];
[nb, interfaceSpec] ← ThParty.LookupServiceInterface[info.shh, credentials, partyID, interfaceName];
shhh ← info.shh;
};
Utilities
GetConv:
PUBLIC
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;
New conversation
cDesc ←
NEW[FinchSmarts.ConvDescBody←[
situation: [ self: [ convID: convID, smartsID: info.smartsID, partyID: info.partyID ]]]];
cL ← LIST[cDesc];
info.conversations ← NConcConvs[info.conversations, cL];
};
FinchOn:
INTERNAL
PROC[convID: ConversationID←nullConvID]
RETURNS [
on: BOOL←FALSE, cDesc: ConvDesc←NIL, state: StateInConv←$idle] = {
IF ~pd.finchOn
OR info=
NIL
THEN {
VoiceUtils.Report["Your Finch is not running", $Finch]; RETURN; };
on←TRUE;
IF convID=nullConvID THEN RETURN;
cDesc ← GetConv[convID, FALSE];
IF cDesc#NIL THEN statesc.situation.self.state;
};
NoteNewState:
INTERNAL
PROC[convEvent: Thrush.ConvEvent]
RETURNS[cDesc: ConvDesc]= {
cInfo: ThParty.ConversationInfo;
nb: NB;
cDesc ← GetConv[convEvent.self.convID, TRUE];
cDesc.situation ← convEvent^;
[nb, cInfo] ← ThParty.GetConversationInfo[shh: info.shh, convID: convEvent.self.convID];
Now gather up additional information that we might not know yet:
Who started this?
Who the other party(s) is (are)
SELECT
TRUE
FROM
nb # $success => Problem["Can't obtain conversation information"];
cDesc.numParties#cInfo.numParties, cDesc.numActive#cInfo.numActive,
cDesc.numIdle#cInfo.numIdle => {
State information guaranteed correct only as of last time one of these numbers changed.
cDesc.subject ← cInfo.subject;
cDesc.startTime ← cInfo.startTime;
cDesc.numParties ← cInfo.numParties;
cDesc.numActive ← cInfo.numActive;
cDesc.numIdle ← cInfo.numIdle;
[nb, cDesc.partyInfo] ←
ThParty.GetPartyInfo[shh: info.shh, credentials: cDesc.situation.self,
nameReq: $description, allParties: TRUE];
IF nb#$success THEN Problem["Can't obtain conversation information", nb]
ELSE
IF cDesc.numParties>=1
THEN
cDesc.whoOriginated ←
IF cDesc.partyInfo[0].partyID=cInfo.originator THEN $us ELSE $them;
};
ENDCASE;
SELECT cDesc.situation.self.state
FROM
$idle, $neverWas => info.conversations ← DequeueConv[info.conversations, cDesc];
$notified =>
SELECT filtering
FROM
--
Needs tests for calls already in progress.
1 => []←Request[cDesc, $idle, $notImportantEnough, "This is a test"];
2 => []←Request[cDesc, $ringing];
3 => []←Request[cDesc, $active, $whatever, "Hot line!"];
ENDCASE;
ENDCASE => cDesc.ultimateState ← cDesc.situation.self.state;
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.
ReportConversationState[$success, cDesc, NIL];
BROADCAST info.stateChange;
};
Request:
INTERNAL
PROC[
cDesc: ConvDesc,
state: StateInConv,
reason: Thrush.Reason←NIL,
comment: ROPE←NIL
]
RETURNS [nb: NB] = {
DO
convEvent: Thrush.ConvEvent;
reportToAll:
BOOL =
SELECT state
FROM
$idle, $active, $inactive, $ringing => TRUE, ENDCASE=>FALSE;
[nb, convEvent] ←
ThParty.Advance[shhh: info.shh, credentials: cDesc.situation.self, state: state,
reportToAll: reportToAll, reason: reason, comment: comment];
SELECT nb
FROM
$success => []←NoteNewState[convEvent];
$stateMismatch => {
cDesc←NoteNewState[convEvent];
IF state=$idle AND cDesc.situation.self.state # $idle THEN LOOP;
};
ENDCASE =>
-- Some cases are fatal, requiring re-registration! --
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]
RETURNS [ nb: NB←$success, cDesc: ConvDesc←NIL ] = {
If there's a reason, the connect request's only purpose is to record an error condition, audibly as well as visually. This requires the creation of a conversation, then its immediate termination.
state: StateInConv;
newState: StateInConv =IF reason=NIL THEN $initiating ELSE $failed;
convEvent: Thrush.ConvEvent;
IF ~(([,cDesc, state]𡤏inchOn[convID]).on) THEN { nb ← $finchInactive; RETURN; }; --
SELECT state
FROM
$idle => {
[nb, convEvent] ← ThParty.CreateConversation[
shhh: info.shh,
credentials: [partyID: info.partyID, smartsID: info.smartsID],
state: newState,
reason: reason,
comment: comment
];
IF nb = $success THEN cDesc ← NoteNewState[convEvent];
};
$reserved, $parsing => nb ← Request[cDesc, newState, reason, comment];
ENDCASE => {
ReportConversationState[$convStillActive, NIL, "Conversation already in progress"];
RETURN;
};
IF nb # $success OR newState=$failed THEN RETURN;
cDesc.originatorRecorded ← TRUE;
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;
};
ENDCASE => {
-- Do something interesting — see other smarts for more confusion -- ERROR; };
};
WaitForActive:
INTERNAL
PROC[cDesc: ConvDesc]
RETURNS [nb:
NB←$success] = {
For use in Recording, and so on, where client needs to wait for setup.
FOR i:
NAT
IN [0..pd.waitsForConnect)
DO
SELECT cDesc.situation.self.state
FROM
$active => RETURN;
$failed => EXIT;
ENDCASE;
TRUSTED { Process.SetTimeout[@info.stateChange, pd.noJayTicks]; };
WAIT info.stateChange; -- <<The supervisor condition vbl; will this work?>>
SELECT cDesc.situation.self.state
FROM
$idle, $neverWas => EXIT; -- something wrong.
ENDCASE;
ENDLOOP;
nb←Request[cDesc, $failed, $error, "Finch failed to connect to voice service."];
IF nb=$success THEN nb←$timedOut;
};
GetActiveConversation:
PROC
RETURNS[convID: ConversationID←Thrush.nullConvID, cDesc: ConvDesc←NIL, state: StateInConv←$idle] = {
Hack. Won't work when lots of conversations are the norm
FOR convs:
LIST
OF ConvDesc ← info.conversations, convs.rest
WHILE convs#
NIL
DO
cDesc ← NARROW[convs.first];
IF cDesc.situation.self.state > Thrush.notReallyInConv
THEN {
convID ← cDesc.situation.self.convID; state ← cDesc.situation.self.state; RETURN; }
ENDLOOP;
};
Problem:
PROC[remark:
ROPE←
NIL, nb:
NB←
NIL] = {
IF nb=
NIL
THEN VoiceUtils.Problem[remark, $Finch]
ELSE VoiceUtils.ProblemFR["%g, reason: %g", $Finch, NIL, rope[remark], atom[nb]];
};
NewID:
PROC
RETURNS[
LONG
CARDINAL] ={
RETURN[
LOOPHOLE[PupSocket.GetUniqueID[]]]};
Registration
InitFinchSmarts:
PUBLIC
PROC [thrushInstance: Thrush.
ROPE←
NIL] = {
InitFinchSmartsDo[thrushInstance];
IF pd.finchOn THEN StartServerPoller[];
};
InitFinchSmartsDo:
PROC [thrushInstance: Thrush.
ROPE←
NIL] = {
problem: ROPE←NIL; nb: NB; {
ENABLE RPC.CallFailed => { problem ← "Communication Failure"; GOTO InitFailed; };
credentials: Thrush.Credentials;
thVR: RPC.VersionRange = ThVersions.GetThrushVR;
problem: Thrush.ROPE ← NIL;
namesGVInstance: Thrush.ROPE←NIL;
hostHint: VoiceUtils.NetAddress;
UninitFinchSmarts[NIL];
info ← NEW[FinchSmarts.FinchInfoBody]; -- Dump any old one!
info.requests ← MBQueue.Create[];
info.conversations ← NIL;
thrushInstance ← VoiceUtils.MakeRName[style: rName, name:
IF thrushInstance#NIL THEN thrushInstance
ELSE UserProfile.Token[key: "ThrushClientServerInstance", default: "Strowger.Lark"]];
info.prevThrushInstance ← thrushInstance;
info.myName ← [
type: "ThSmarts.Lark",
instance: VoiceUtils.InstanceFromNetAddress[netAddress: VoiceUtils.OwnNetAddress[], suffix: "0"],
version: ThVersions.FinchVR];
info.myRName ← VoiceUtils.CurrentRName[];
info.myPassword ← VoiceUtils.CurrentPasskey[];
ThSmartsRpcControl.ExportInterface[
interfaceName: info.myName,
user: info.myRName,
password: info.myPassword];
pd.smartsIsExported←
TRUE;
info.shh ← IF NOT pd.encryptionRequested THEN Thrush.unencrypted
ELSE NamesRPC.StartConversation [
caller: info.myRName,
callee: thrushInstance,
key: info.myPassword,
level: --<<ECB>>--CBCCheck !
RPC.AuthenticateFailed=> { problem←"Could not authenticate"; GOTO InitFailed}];
hostHint←VoiceUtils.NetAddressFromRope[NameDB.GetAttribute[thrushInstance, $connect]];
ThPartyRpcControl.ImportInterface[
interfaceName: [type: "ThParty.Lark", instance: thrushInstance, version: thVR],
hostHint: hostHint!
RPC.ImportFailed=> {
IF why=wrongVersion THEN
problem ← IO.PutFR["Finch version %d too old; import failed",
card[ThVersions.FinchVersion]];
GOTO InitFailed;
}];
pd.interfacesAreImported←TRUE;
[nb, credentials]←ThParty.Register[
shh: info.shh,
rName: info.myRName,
type: $individual,
interface: info.myName,
properties: [$controller, VoiceUtils.OwnNetAddress[]]
];
IF nb=$success THEN nb ← ThParty.Enable[shh: info.shh, smartsID: credentials.smartsID];
IF nb#$success
THEN {
problem←"Can't register with server"; GOTO InitFailed; };
info.smartsID ← credentials.smartsID;
info.partyID ← credentials.partyID;
ThParty.ConversationsForParty[shh: info.shh, partyID: partyID];
pd.finchOn←TRUE;
ReportSystemState[TRUE];
EXITS
InitFailed => UninitFinchSmarts[problem, nb];
};};
UninitFinchSmarts:
PUBLIC
PROC[problem:
ROPE←
NIL, nb:
NB←
NIL] = {
ENABLE RPC.CallFailed => GOTO Failed;
IF problem#NIL THEN Problem[problem, nb];
IF info#
NIL
THEN {
IF pd.finchOn
AND info.partyID#nullID
AND info.smartsID#nullID
THEN
[-- nb --]←ThParty.Deregister[info.shh, info.smartsID!RPC.CallFailed=>CONTINUE];
info.shh←none;
ReportSystemState[FALSE];
FOR convs:
LIST
OF ConvDesc ← info.conversations, convs.rest
WHILE convs#
NIL
DO
convs.first.clientData ← NIL;
ENDLOOP;
info.conversations ← NIL;
};
pd.finchOn←FALSE;
pd.interfacesAreImported←FALSE;
IF pd.smartsIsExported
THEN
ThSmartsRpcControl.UnexportInterface[!LupineRuntime.BindingError=>CONTINUE];
pd.smartsIsExported←FALSE;
EXITS
Failed => pd.interfacesAreImported ← pd.smartsIsExported ← FALSE;
StartServerPoller:
PUBLIC PROC =
TRUSTED {
Process.Detach[FORK PollServer[]];
};
PollServer:
ENTRY
PROC = {
ENABLE {
UNWIND=>NULL; -- RestoreInvariant;
RPC.CallFailed => GOTO PollFailed;
};
nb: NB;
credentials: Thrush.Credentials ← [partyID: info.partyID, smartsID: info.smartsID, convID: Thrush.nullConvID];
TRUSTED {Process.SetTimeout[@finchPollerWait, Process.SecondsToTicks[pd.timeoutPoller]];};
WAIT finchPollerWait;
nb ← ThParty.CheckIn[shh: info.shh, credentials: credentials];
IF nb#$success THEN GOTO PollFailed;
EXITS
PollFailed => InitFinchSmartsDo[info.prevThrushInstance];
};
FinchIsRunning: PUBLIC PROC RETURNS [finchIsRunning: BOOL] = { RETURN[pd.finchOn]; };
RegisterForReports:
PUBLIC
ENTRY PROC [s:
FinchSmarts.ReportSystemStateProc ←
NIL, c:
FinchSmarts.ReportConversationStateProc ←
NIL, r:
FinchSmarts.ReportRequestStateProc ←
NIL, before:
BOOL ←
TRUE] = {
UnRegisterForReportsInt[s, c, r];
-- Multiple registrations do not hurt anything.
Warning: This facility has the same risks as most registration mechanisms: running multiple versions of the client code can cause unintended multiple registrations.
IF before
THEN {
IF s #
NIL
THEN
pd.systemStateSubscribers ← CONS[s, pd.systemStateSubscribers];
IF c #
NIL
THEN
pd.convStateSubscribers ← CONS[c, pd.convStateSubscribers];
IF r #
NIL
THEN
pd.requestStateSubscribers ← CONS[r, pd.requestStateSubscribers];
}
ELSE {
IF s #
NIL
THEN
IF pd.systemStateSubscribers=NIL THEN pd.systemStateSubscribers ← LIST[s]
ELSE
FOR l:
LIST
OF
FinchSmarts.ReportSystemStateProc ← pd.systemStateSubscribers, l.rest
DO
IF l.rest=NIL THEN { l.rest ← LIST[s]; EXIT };
ENDLOOP;
IF c #
NIL
THEN
IF pd.convStateSubscribers=NIL THEN pd.convStateSubscribers ← LIST[c]
ELSE
FOR l:
LIST
OF
FinchSmarts.ReportConversationStateProc ← pd.convStateSubscribers, l.rest
DO
IF l.rest=NIL THEN { l.rest ← LIST[c]; EXIT };
ENDLOOP;
IF r #
NIL
THEN
IF pd.requestStateSubscribers=NIL THEN pd.requestStateSubscribers ← LIST[r]
ELSE
FOR l:
LIST
OF
FinchSmarts.ReportRequestStateProc ← pd.requestStateSubscribers, l.rest
DO
IF l.rest=NIL THEN { l.rest ← LIST[r]; EXIT };
ENDLOOP;
};
};
UnRegisterForReports:
PUBLIC ENTRY PROC [s:
FinchSmarts.ReportSystemStateProc ←
NIL, c:
FinchSmarts.ReportConversationStateProc ←
NIL, r:
FinchSmarts.ReportRequestStateProc ←
NIL] = { UnRegisterForReportsInt[s, c, r]; };
UnRegisterForReportsInt:
INTERNAL PROC [s:
FinchSmarts.ReportSystemStateProc ←
NIL, c:
FinchSmarts.ReportConversationStateProc ←
NIL, r:
FinchSmarts.ReportRequestStateProc ←
NIL] = {
IF s #
NIL
AND pd.systemStateSubscribers #
NIL
THEN {
prev: LIST OF FinchSmarts.ReportSystemStateProc ← NIL;
FOR list:
LIST
OF
FinchSmarts.ReportSystemStateProc ← pd.systemStateSubscribers, list.rest
UNTIL list=
NIL
DO
IF list.first=s
THEN {
IF prev = NIL THEN pd.systemStateSubscribers ← list.rest
ELSE prev.rest ← list.rest;
EXIT };
prev ← list;
ENDLOOP;
};
IF c #
NIL
AND pd.convStateSubscribers #
NIL
THEN {
prev: LIST OF FinchSmarts.ReportConversationStateProc ← NIL;
FOR list:
LIST
OF
FinchSmarts.ReportConversationStateProc ← pd.convStateSubscribers, list.rest
UNTIL list=
NIL
DO
IF list.first=c
THEN {
IF prev = NIL THEN pd.convStateSubscribers ← list.rest
ELSE prev.rest ← list.rest;
EXIT };
prev ← list;
ENDLOOP;
};
IF r #
NIL
AND pd.requestStateSubscribers #
NIL
THEN {
prev: LIST OF FinchSmarts.ReportRequestStateProc ← NIL;
FOR list:
LIST
OF
FinchSmarts.ReportRequestStateProc ← pd.requestStateSubscribers, list.rest
UNTIL list=
NIL
DO
IF list.first=r
THEN {
IF prev = NIL THEN pd.requestStateSubscribers ← list.rest
ELSE prev.rest ← list.rest;
EXIT };
prev ← list;
ENDLOOP;
};
};
ReportSystemState:
PROC [on:
BOOL] ~ {
FOR l:
LIST
OF
FinchSmarts.ReportSystemStateProc ← pd.systemStateSubscribers, l.rest
UNTIL l=
NIL
DO
l.first[on];
ENDLOOP;
};
ReportConversationState:
PUBLIC
PROC [nb:
NB, cDesc: ConvDesc, remark: Rope.
ROPE] ~ {
Queued so that reports do not deadlock with the callers of procedure that invoked the reports. ReportSystemState would cause this trouble, too, but it has been avoided in a different way. Perhaps this approach should be used there, too. Action reports are already decoupled from requests through the auspices of ThParty.
info.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];
FOR l:
LIST
OF
FinchSmarts.ReportConversationStateProc ← pd.convStateSubscribers, l.rest
UNTIL l=
NIL
DO
l.first[cS.nb, cS.cDesc, cS.remark];
ENDLOOP;
};
ReportRequestState:
PROC [cDesc: ConvDesc, actionReport: Thrush.ActionReport] ~ {
actionRequest: REF←NIL;
FOR l:
LIST
OF
FinchSmarts.ReportRequestStateProc ← pd.requestStateSubscribers, l.rest
UNTIL l=
NIL
DO
actionRequest ← l.first[cDesc, actionReport, actionRequest];
ENDLOOP;
};
Debugging nonsense
ViewCmd: Commander.CommandProc =
TRUSTED {
Nice.View[pd, "Finch PD"];
};
SetFiltering:
PROC[which:
INT, deferAnswer:
ROPE← "true"] ← {
filtering ← which;
NameDB.SetAttribute[VoiceUtils.CurrentRName[], $deferanswer, deferAnswer];
};
FilterResetCmd: Commander.CommandProc =
TRUSTED {
SetFiltering[0, "false"];
};
FilterZeroCmd: Commander.CommandProc =
TRUSTED {
SetFiltering[0];
};
FilterOneCmd: Commander.CommandProc =
TRUSTED {
SetFiltering[1];
};
FilterTwoCmd: Commander.CommandProc =
TRUSTED {
SetFiltering[2];
};
FilterThreeCmd: Commander.CommandProc =
TRUSTED {
SetFiltering[3];
};
Commander.Register["VuFinch", ViewCmd, "Program Management variables Finch"];
Commander.Register["FReset", FilterResetCmd, "Reset filtration method. This is a test."];
Commander.Register["F0", FilterZeroCmd, "Zeroth filtration method. This is a test."];
Commander.Register["F1", FilterOneCmd, "First filtration method. This is a test."];
Commander.Register["F2", FilterTwoCmd, "Second filtration method. This is a test."];
Commander.Register["F3", FilterThreeCmd, "Third filtration method. This is a test."];
}.
Swinehart, May 22, 1985 1:08:51 pm PDT
Changes to GetParty.
changes to: JayConnection
Polle Zellweger (PTZ) July 13, 1985 5:20:26 pm PDT
adding Text-to-Speech server
changes to: DIRECTORY, ProseSpec, ProseSpecs, stopIntervalSpec (was stopSpec), stopProseSpec, Progress, Supervise, DisconnectCall, PlaceCall, StopSpeech, TextToSpeech, InitFinchSmarts, GetConv, ReportProses, EnqueueProses, Complain, Connect, ProseConnection
Swinehart, August 6, 1985 5:43:08 pm PDT
Merge PTZ prose changes
changes to: DIRECTORY, NB, PD, stopIntervalSpec, stopProseSpec, Report, Progress, Supervise, DisconnectCall, PlaceCall, StopTune, NoiseSpec, PlaybackTune, TextToSpeech, InitFinchSmarts, GetConv, ReportProses, CompareIntID, EnqueueProses, Complain, Connect, JayConnection, ProseConnection, RepRet, FinchSmarts
Polle Zellweger (PTZ) August 19, 1985 4:16:40 pm PDT
Meter text in TextToSpeech so as to avoid sending large ropes all at once. Allows speech to begin sooner and flush faster. Also flushing changes.
changes to: FinchInfo, PD, Supervise, TextToSpeech, StopSpeech, ReportProses (comments only)
Polle Zellweger (PTZ) September 3, 1985 6:28:29 pm PDT
Allow registration of defaultTranslateProc.
changes to: Supervise, defaultTranslateProc, RegisterTranslateProc, TextToSpeech, FinchSmarts
Swinehart, September 16, 1985 10:00:59 am PDT
If Finch has never been initialized, info is NIL -- don't let that bother you.
changes to: GetRname, PlayNoise, FinchOn
Polle Zellweger (PTZ) October 22, 1985 5:10:44 pm PDT
changes to: Progress (add prose debugging reports), TextToSpeech (report connection failure), ReportProses (fix debugging reports), JayConnection (report connection failure), ProseConnection (report connection failure)
Polle Zellweger (PTZ) October 24, 1985 4:53:21 pm PDT
Move bluejayConnection and proseConnection from FinchInfo to ConvDesc.
changes to: Supervise, DisconnectCall, InitFinchSmarts, Connect, JayConnection, ProseConnection
Swinehart, October 28, 1985 12:31:20 pm PST
Merge Above changes with other other minor changes (RefQ and the like)
changes to: DIRECTORY, FinchSmartsImpl, Progress, Supervise, DisconnectCall, TextToSpeech, InitFinchSmarts, UninitFinchSmarts, IsConv, GetConv, IsConv (local of GetConvDesc), GetConvDesc, ReportProses, FinchOn, Connect, JayConnection, ProseConnection, PlaybackTune, ClearConvs (local of UninitFinchSmarts)
Swinehart, December 13, 1985 2:52:25 pm PST
Massive change to new Thrush, leaving out proses and intervals for the moment.
changes to: ConversationHandle, FinchInfo, Progress, Supervise, DisconnectCall, AnswerCall, PlaceCall, Apprise, Complain, Connect, Problem, FinchSmarts, Progress, QdProgress
Swinehart, December 17, 1985 10:05:46 am PST
Major revision for new Thrush. Removed all Prose and Bluejay stuff temporarily
changes to: QdProgress, NoteNewState, PutFTime, Connect, Problem, Progress, Other, Supervise
Swinehart, May 19, 1986 12:22:17 pm PDT
Cedar 6.1
changes to: DIRECTORY, SHHH
Swinehart, June 1, 1986 7:48:10 pm PDT
Add ReportAction, eliminate the MBQueueing of ThParty-provoked actions. Others remain, but should go.
changes to: Progress, Substitution, ReportAction
Doug Terry, August 28, 1986 4:59:33 pm PDT
Changed report registration; removed key distribution; removed routines for recording/playing, tunes and related operations.
changes to: FinchIsRunning, RegisterForReports, UnRegisterForReports, ReportAction, DisconnectCall, PlaceCall, PlaybackTune, NoteNewState, Request, Connect, InitFinchSmarts, UninitFinchSmarts, ReportSystemState, ReportConversationState, ReportRequestState, DIRECTORY, FinchSmartsImpl, GetConv
Doug Terry, August 29, 1986 12:14:14 pm PDT
New interface procedures for FinchSmarts clients to get at voice services: VoiceConnect and LookupServiceInterface.
changes to: ReleaseVisitor, VoiceConnect, WaitForActive, LookupServiceInterface, Problem