FinchSmartsImpl.mesa
Copyright © 1984 by Xerox Corporation. All rights reserved.
Polle Zellweger (PTZ) October 24, 1985 4:55:57 pm PDT
Last Edited by: Swinehart, September 16, 1985 10:01:33 am PDT
DIRECTORY
Basics USING [ Comparison ],
BasicTime USING [ Now, nullGMT, OutOfRange, Update ],
Commander USING [ CommandProc, Register ],
Convert USING [ IntFromRope ],
FinchSmarts,
IO,
Lark USING [ KeyTable, SHHH ],
Log USING [Problem, Report ],
LupineRuntime USING [ BindingError ],
Names USING [ CurrentPasskey, CurrentRName, InstanceFromNetAddress, OwnNetAddress ],
NamesGV USING [ AttributeSeq, GVGetAttribute, GVGetAttributeSeq ],
NamesGVImpExp USING [ GVImport, UnGVImport ],
NamesRPC USING [ StartConversation ],
Nice,
PrincOpsUtils USING [ IsBound ],
Process USING [ Detach, SecondsToTicks, SetTimeout, Ticks ],
RefTab USING [Create, Fetch, Ref, Store ],
Rope,
RPC USING [ AuthenticateFailed, CallFailed, EncryptionKey, ImportFailed, VersionRange ],
RuntimeError USING [ UNCAUGHT ],
ThParty USING [ Advance, Alert, ConversationsForParty, CreateParty, Deregister, DescribeInterval, DescribeParty, GetNumbersForRName, GetParty, GetPartyFromNumber, OtherParty, Register, RegisterKey, SameConvClass, SetIntervals, SetProse ],
ThPartyRpcControl,
Thrush USING[
CallUrgency, ConversationHandle, ConvEvent, Disposition, EncryptionKey, IntervalSpec, IntervalSpecs, IntervalSpecBody, IntID, IntSpecType, NB, none, nullConvHandle, nullHandle, nullKey, nullTune, PartyHandle, ProseSpec, ProseSpecs, ProseSpecBody, Reason, ROPE, SmartsHandle, StateID, StateInConv, ThHandle, Tune, unencrypted, VoiceDirection, VoiceInterval ],
ThSmarts,
ThSmartsRpcControl,
ThVersions USING [ GetThrushVR, FinchVersion, FinchVR ],
UserProfile USING [ Token]
;
FinchSmartsImpl: CEDAR MONITOR
IMPORTS BasicTime, Commander, Convert, IO, FinchSmarts, Log, LupineRuntime, Names, NamesGV, NamesGVImpExp, NamesRPC, Nice, PrincOpsUtils, Process, RefTab, RPC, Rope, RuntimeError, ThParty, ThPartyRpcControl, ThSmartsRpcControl, ThVersions, UserProfile
EXPORTS FinchSmarts, ThSmarts = {
OPEN IO;
Declarations
CallUrgency: TYPE = Thrush.CallUrgency;
ConvDesc: TYPE = FinchSmarts.ConvDesc;
ConversationHandle: TYPE = Thrush.ConversationHandle;
nullConvHandle: ConversationHandle = Thrush.nullConvHandle;
Disposition: TYPE = Thrush.Disposition;
FinchInfo: TYPE = FinchSmarts.FinchInfo;
IntervalSpec: TYPE = Thrush.IntervalSpec;
IntervalSpecs: TYPE = Thrush.IntervalSpecs;
ProseSpec: TYPE = Thrush.ProseSpec;
ProseSpecs: TYPE = Thrush.ProseSpecs;
NB: TYPE = Thrush.NB;
none: SHHH = Thrush.none;
nullHandle: Thrush.ThHandle = Thrush.nullHandle;
PartyHandle: TYPE = Thrush.PartyHandle;
Reason: TYPE = Thrush.Reason;
ROPE: TYPE = Thrush.ROPE;
SHHH: TYPE = Lark.SHHH;
SmartsHandle: TYPE = Thrush.SmartsHandle;
StateInConv: TYPE = Thrush.StateInConv;
PD: TYPE = RECORD [
doReports: BOOLFALSE,
doNice: BOOLFALSE,
timeoutNoAction: INTEGER ← 30,
noActionTicks: Process.Ticks ← Process.SecondsToTicks[30],
timeoutJayConnect: INTEGER ← 1,
noJayTicks: Process.Ticks ← Process.SecondsToTicks[1],
encryptionRequested: BOOLEANTRUE,
interfacesAreImported: BOOLEANFALSE,
smartsIsExported: BOOLEANFALSE,
waitsForConnect: NAT𡤆,
queueIt: BOOLFALSE,
finchOn: BOOLEANFALSE,
maxProseLength: INT
];
info: FinchInfo;
pd: REF PDNEW[PD←[]];
stopIntervalSpec: Thrush.IntervalSpec = NEW[Thrush.IntervalSpecBody←[
interval: [length: 0], direction: play, queueIt: FALSE] ];
stopProseSpec: Thrush.ProseSpec = NEW[Thrush.ProseSpecBody←[
prose: "", direction: play, queueIt: FALSE] ];
resetProseSpec: Thrush.ProseSpec = NEW[Thrush.ProseSpecBody←[
prose: "\022", direction: record, queueIt: FALSE] ]; -- \022 is ControlR
Would prefer BOOLEAN filter field to control whether resets can be sent, this hack encodes it in direction=record. August 20, 1985
Report: PROC[what: ROPE] = {
IF NOT pd.doReports THEN RETURN;
Log.Report[what, $Finch];
};
BeNice: PROC[r: REF, d: INT] = {
IF NOT pd.doNice THEN RETURN;
Nice.BeNice[r, d, $Finch, NIL];
};
Supervision
Progress: PUBLIC ENTRY PROC[
shh: SHHH,
smartsID: Thrush.SmartsHandle,
event: Thrush.ConvEvent,
yourParty: BOOL,
latestEvent: BOOL,
informationOnly: BOOL
] RETURNS [ d: Thrush.Disposition ] = {
ENABLE UNWIND => NULL;
cDesc: ConvDesc;
IF info=NIL THEN RETURN[pass];
d←pass;
Update local state information
IF pd.doReports THEN Report[
Rope.Concat[
IO.PutFR["---- FnProg: %g(%d) %g %g yr=%g ",
PutFTime[event.credentials.convID], int[event.credentials.stateID],
refAny[NEW[StateInConv𡤎vent.state]], rope[info.myRName], bool[yourParty]],
IO.PutFR["lt=%g in=%g, pr=%g, ky=%g\n",
bool[latestEvent], bool[event.intervalSpecs#NIL], bool[event.proseSpecs#NIL], bool[event.keyTable#NIL]]]];
cDesc ← GetConv[event.credentials.convID, FALSE];
IF event.credentials.stateID <= cDesc.cState.credentials.stateID THEN RETURN[pass]; -- Old news!
Fields always extracted
cDesc.cState.credentials.smartsID ← smartsID;
cDesc.cState.credentials.stateID ← event.credentials.stateID;
<<Need to check for increasing stateID here, and reject old ones? If so, enable
test in alrt case below.>>
Extracted if present
IF event.keyTable#NIL THEN {
cDesc.cState.keyTable ← event.keyTable; cDesc.newKeys←TRUE; };
IF event.intervalSpecs#NIL THEN ReportIntervals[cDesc, event];
IF event.proseSpecs#NIL THEN ReportProses[cDesc, event];
IF event.address#NIL THEN { cDesc.cState.address𡤎vent.address; cDesc.newAddress←TRUE; };
Extracted if the event changes our state.
IF yourParty THEN {
Other: PROC={
oD: ROPE;
IF (~latestEvent) OR cDesc.otherPartyID#nullHandle THEN RETURN;
[,cDesc.otherPartyID, oD, cDesc.conference] ←
ThParty.OtherParty[shhh: info.shh, credentials: cDesc.cState.credentials];
IF oD#NIL THEN cDesc.otherPartyDesc ← oD;
};
cDesc.descValid ← TRUE;
cDesc.cState.credentials.partyID ← event.credentials.partyID;
cDesc.cState.state ← event.state;
cDesc.cState.comment ← event.comment;
cDesc.cState.reason ← event.reason;
IF event.spec#NIL THEN { cDesc.cState.spec ← event.spec; cDesc.newSpec←TRUE; };
Extracted if non-standard?
IF event.comment#NIL THEN cDesc.cState.comment ← event.comment;
IF event.urgency#normal THEN cDesc.cState.urgency ← event.urgency;
IF event.alertKind#standard THEN cDesc.cState.alertKind ← event.alertKind;
SELECT event.state FROM
reserved, parsing => cDesc.originator ← us;
initiating => { cDesc.originator ← us; Other[]; };
pending => { cDesc.originator ← them; Other[]; };
maybe, ringing, active, canActivate, inactive => Other[];
ENDCASE;
};
BeNice[event, 4];
BeNice[cDesc, 6];
cDesc.newEvent←TRUE;
IF latestEvent THEN Apprise[info]; -- Wait for the last report to wake process.
};
Supervise: PUBLIC ENTRY PROC[info: FinchInfo ] = {
problem: ROPENIL;
TRUSTED { Process.SetTimeout[@info.thAction, pd.noActionTicks]; };
IF info.apprise THEN DO
ENABLE {
UNWIND => NULL;
Specific fatal error condition, if any => GOTO Failing;
RuntimeError.UNCAUGHT => Problem["Unknown Finch Smarts Supervisor Failure"];
When you know what ones occur, catch them, and emulate...
ANY => { problem ← "Unknown Finch Smarts Supervisor Failure"; GOTO Failing; };
};
cDesc: ConvDesc;
prevL: ConvDesc ← NIL;
nb: NB←success;
trans: Transition;
numC, numDead: NAT𡤀
info.apprise ← FALSE;
FOR cDesc ← info.conversations,
cDesc.next WHILE cDesc#NIL DO
stateNow: StateInConv = cDesc.cState.state;
newEvent: BOOL=cDesc.newEvent;
ours: BOOL ← (info.currentConvID = cDesc.cState.credentials.convID);
cDesc.newEvent←FALSE; -- For things that must be done once-only per new state.
IF newEvent OR stateNow#idle THEN {
IF pd.doReports THEN Report[
Rope.Concat[
IO.PutFR["**** FnSup: %g(%d) %g %g->%g",
PutFTime[cDesc.cState.credentials.convID], int[cDesc.cState.credentials.stateID],
rope[info.myRName], refAny[NEW[StateInConv←stateNow]], refAny[NEW[StateInConv�sc.desiredState]]],
IO.PutFR[" %g%g\n",
refAny[NEW[Transition←transForStates[stateNow][cDesc.desiredState]]],
rope[IF ours THEN " (ours)" ELSE ""]]]];
BeNice[cDesc, 6];
};
IF NOT cDesc.descValid THEN LOOP;
Record whether or not this is now "our" conversation
numC←numC+1;
SELECT stateNow FROM
idle => IF ours THEN info.currentConvID ← nullConvHandle;
parsing, reserved, initiating, maybe, ringing, canActivate, active, inactive => {
ours←TRUE;
info.currentConvID ← cDesc.cState.credentials.convID;
};
pending, any => NULL;
ENDCASE => NULL;
State and substate (below) and desired state indicate there's something to do. Do it:
SELECT (trans←transForStates[stateNow][cDesc.desiredState]) FROM
noop => NULL;
elim => {
IF newEvent THEN cDesc.bluejayConnection ← cDesc.proseConnection ← FALSE
ELSE numDead←numDead+1;
cDesc.requestedIntervals�sc.pendingIntervals←NIL;
cDesc.requestedProses�sc.pendingProses←NIL;
cDesc.cState.credentials.stateID𡤀 -- In case re-used.
};
alrt => { -- placing call, or reserving conversation
convID: ConversationHandle;
IF NOT ours THEN Problem["What to do in a race?"];
IF cDesc.desiredState#reserved THEN cDesc.weOriginated ← TRUE;
[nb, convID] ← ThParty.Alert [
credentials: cDesc.cState.credentials,
calledPartyID: cDesc.desiredPartyID,
state: IF cDesc.desiredState=reserved THEN reserved ELSE initiating,
reason: cDesc.desiredReason,
comment: cDesc.desiredComment
];
IF nb=success THEN {
cDesc.cState.credentials.convID ← info.currentConvID ← convID;
ours←TRUE;
};
};
idle, actv => -- Simple transitions
nb ← ThParty.Advance [
shhh: info.shh,
credentials: cDesc.cState.credentials,
state: cDesc.desiredState,
reason: cDesc.desiredReason,
comment: cDesc.desiredComment
];
spvs => {
IF cDesc.pendingIntervals#NIL THEN {
-- Issue any new requests, then record the resulting stateID so that later comparisons will work.
stateID: Thrush.StateID=cDesc.cState.credentials.stateID+1;
reqID: CARDINAL ← 177777B;
FOR iSL: IntervalSpecs ← cDesc.pendingIntervals, iSL.rest WHILE iSL#NIL DO
iSL.first.intID ← [stateID, reqID←reqID+1];
ENDLOOP;
nb ← ThParty.SetIntervals[
shhh: info.shh,
credentials: cDesc.cState.credentials,
intervalSpecs: cDesc.pendingIntervals
];
IF nb=success THEN cDesc.pendingIntervals←NIL;
};
IF cDesc.pendingProses#NIL THEN {
Issue any new requests, then record the resulting stateID so that later comparisons will work. Send multiple proseSpecs as long as they aren't too long. When that happens, partition cDesc.pendingProses temporarily while calling SetProse. Can get short, long, short, long misbehavior here, but need semantics of separate requests?
nextPSL, prevPSL: ProseSpecs ← NIL;
stateID: Thrush.StateID=cDesc.cState.credentials.stateID+1;
reqID: CARDINAL ← 177777B;
textLen: INT ← 0;
FOR pSL: ProseSpecs ← cDesc.pendingProses, pSL.rest WHILE pSL#NIL DO
IF (textLen ← textLen+pSL.first.prose.Length[]) <= pd.maxProseLength THEN {
pSL.first.intID ← [stateID, reqID←reqID+1];
prevPSL ← pSL;
}
ELSE {
nextPSL ← pSL;
prevPSL.rest ← NIL;
EXIT;
};
ENDLOOP;
nb ← ThParty.SetProse[
shhh: info.shh,
credentials: cDesc.cState.credentials,
proseSpecs: cDesc.pendingProses
];
prevPSL.rest ← nextPSL; -- put the list back together so that cDesc.requestedProses is still connected
IF nb=success THEN {
cDesc.pendingProses ← nextPSL;
IF ~cDesc.proseConnection THEN cDesc.requestedProses ← NIL;
Using SetProse to request "feeps" over trunk connection; no feedback.
};
};
};
invl => {
Problem["FinchSmarts: Invalid state transition request."];
info.apprise←TRUE;
cDesc.desiredState ← idle;
cDesc.desiredReason ← error;
cDesc.desiredComment ← "Invalid state transition";
};
ntiy => {
Problem["FinchSmarts: State transition not yet implemented."];
info.apprise←TRUE;
cDesc.desiredState ← idle;
cDesc.desiredReason ← error;
cDesc.desiredComment ← "Unimplemented state transition";
};
ENDCASE => ERROR;
Analyze any results of trying to reach a different state.
IF nb#success AND pd.doReports THEN
Report[IO.PutFR[" ** Results: nb=%g\n", refAny[NEW[Thrush.NB←nb]]]];
SELECT nb FROM
success, stateMismatch => NULL;
noSuchParty2 => -- Have to get to error tone here. No such party.
Complain[info, cDesc, notFound, "Called party not found"];
narcissism => -- Have to get to error tone here. No such party.
Complain[info, cDesc, notFound, "Attempt to call self rejected on philosophical grounds"];
partyNotEnabled => {
info.ReportConversationState[noSuchSmarts, cDesc, "Your Etherphone is not connected to the telephone server"];
<< How to disarm the cDesc, which will continue to generate noise?>>
};
invalidTransition, convNotActive, convStillActive => {
Complain and transition to idle
comment: ROPE="Party-level detected invalid state transition request";
Problem[comment];
Complain[info, cDesc, error, comment];
};
notInConv, noSuchConv => { -- Complain, zap, go idle and repeat.
comment: ROPE="NotInConv or NoSuchConv";
Problem[comment];
IF ours THEN info.currentConvID ← nullConvHandle;
cDesc.cState.credentials.convID ← nullConvHandle;
cDesc.cState.credentials.stateID ← 0;
Complain[info, cDesc, error, comment];
};
noSuchParty, noSuchSmarts => { -- Complain, deregister, we gone! Needs tuning.
problem ← "NoSuchParty or NoSuchSmarts reported, must try to go away";
GOTO Failing;
};
ENDCASE => ERROR;
Set up switching and tones to match state.
IF newEvent THEN info.ReportConversationState[success, cDesc, NIL];
After reporting state of conversation (changeNoted set), there is no further use for finished record or playback intervals; delete the leading ones (should be all of the finished ones!)
FOR iSs: IntervalSpecs ← cDesc.requestedIntervals, iSs.rest WHILE iSs#NIL DO
IF iSs.first.type=finished AND iSs.first.changeNoted THEN
cDesc.requestedIntervals ← iSs.rest
ELSE EXIT;
ENDLOOP;
FOR pSs: ProseSpecs ← cDesc.requestedProses, pSs.rest WHILE pSs#NIL DO
IF pSs.first.type=finished AND pSs.first.changeNoted THEN
cDesc.requestedProses ← pSs.rest
ELSE EXIT;
ENDLOOP;
prevL ← cDesc;
ENDLOOP;
IF info.conversations = NIL OR numC=numDead THEN EXIT;
IF NOT info.apprise THEN WAIT info.thAction;
IF info.conversations = NIL THEN EXIT;
REPEAT Failing => UninitFinchSmarts[problem]; -- now Failed
ENDLOOP;
info.thProcess ← NIL;
};
Client Functions
GetHistory: PUBLIC PROC[ convID: Thrush.ConversationHandle, toState: INT𡤀 -- i.e., all-- ]
RETURNS [ s: Thrush.EventSequence ] = {
s←ThParty.GetHistory[shhh: info.shh, convID: convID, firstState: 1, lastState: toState];
};
GetRname: PUBLIC PROC[partyID: Thrush.PartyHandle] RETURNS [rName: ROPE] = {
RETURN[IF info=NIL THEN NIL ELSE ThParty.DescribeParty[shh: info.shh, partyID: partyID]];
};
DisconnectCall: PUBLIC ENTRY PROC[
convID: Thrush.ConversationHandle, -- not used yet
reason: Thrush.Reason←terminating,
comment: ROPENIL] = {
ENABLE UNWIND => NULL;
cDesc: ConvDesc; state: StateInConv;
IF NOT(([,cDesc, state]𡤏inchOn[]).on) THEN RETURN;
cDesc.bluejayConnection ← cDesc.proseConnection ← FALSE;
SELECT state FROM
idle, any => {
info.ReportConversationState[convNotActive, NIL, "No conversation to disconnect"];
RETURN;
};
ringing, pending => IF reason=terminating THEN reason ← busy;
ENDCASE;
Request[info, cDesc, idle, reason, comment];
};
AnswerCall: PUBLIC ENTRY PROC[convID: Thrush.ConversationHandle -- not used yet -- ] = {
ENABLE UNWIND => NULL;
cDesc: ConvDesc; state: StateInConv;
IF NOT(([,cDesc, state]𡤏inchOn[]).on) THEN RETURN;
SELECT state FROM
ringing, pending => NULL;
IF convID#info.currentConvID THEN {
info.ReportConversationState[noSuchConv, NIL, "Can't answer that call"]; RETURN; };
ENDCASE=>RETURN;
Request[info, cDesc, active];
};
PlaceCall: PUBLIC ENTRY PROC [
convID: Thrush.ConversationHandle, -- not used yet --
rName: ROPE, -- rName or description
number: ROPE, -- telephone number, if present
urgency: Thrush.CallUrgency←normal,
useNumber: BOOLFALSE] = {
ENABLE { UNWIND=>NULL; RPC.CallFailed => { UninitFinchSmarts["Communication Failure"]; CONTINUE; }; };
Get fone for dest
calledPartyID: PartyHandle;
fullRName: ROPE;
IF NOT (FinchOn[].on) THEN RETURN;
calledPartyID ← SELECT TRUE FROM
~useNumber AND
(calledPartyID ← ThParty.GetParty[shh: info.shh, partyID: info.partyID, rName: rName]) #nullHandle =>
calledPartyID,
number#NIL =>
ThParty.GetPartyFromNumber[shh: info.shh, partyID: info.partyID, phoneNumber: number, description: rName, trunkOK: TRUE],
rName=NIL OR useNumber => nullHandle,
([fullRName, number,] ← ThParty.GetNumbersForRName[shh: info.shh, rName: rName]).number # NIL =>
ThParty.GetPartyFromNumber[
shh: info.shh, partyID: info.partyID, phoneNumber: number,
description: fullRName, trunkOK: TRUE],
ENDCASE => nullHandle;
IF calledPartyID = nullHandle THEN {
info.ReportConversationState[noSuchParty2, NIL,
IF rName=NIL THEN "You did not supply a call destination"
ELSE "No telephone number could be found for called party"];
};
[]𡤌onnect[calledPartyID, FALSE, FALSE, urgency];
};
RecordTune: PUBLIC ENTRY PROC [
useTune: Thrush.Tune,
useInterval: Thrush.VoiceInterval,
queueIt: BOOLFALSE
]
RETURNS[
reason: FinchSmarts.RecordReason←hopeless,
tune: Thrush.Tune←Thrush.nullTune,
interval: Thrush.VoiceInterval←[],
key: Thrush.EncryptionKey←Thrush.nullKey
] = {
ENABLE UNWIND=>NULL;
inst: ThMessageDB.MessageInstance = ThMessageDB.GetMessage["Beep.Lark", 1];
Out of luck, for a while . . . no way to talk about it here!!!!
IF inst#NIL THEN PlBaMe[tune, key: extract from equiv. of inst.];
<<If useTune#newTune, probably won't work -- no key. Nuthatch will solve.>>
<<<Won't work (key troubles) if new tunes are supplied. Vestigial>>>
ourSpec: Thrush.IntervalSpec;
cDesc: ConvDesc; state: StateInConv;
started: BOOLFALSE;
IF NOT (([,cDesc,state]←JayConnection[TRUE]).jayOpen) THEN RETURN;
That's all, folks . . . for now.
ourSpec ← NEW[Thrush.IntervalSpecBody ← [
type: request, direction: record, queueIt: queueIt,
tune: useTune, interval: useInterval, keyIndex: 1
]];
Wait for recording to (fail to) begin.
EnqueueIntervals[cDesc, LIST[ourSpec]];
started ← WaitForStartOrFail[cDesc, ourSpec];
state ← cDesc.cState.state;
IF ~started OR cDesc.cState.keyTable=NIL THEN {
Recording didn't start or can't be decrypted; return with error value.
IF state#idle THEN Complain[info, cDesc, error, "Voice server connection failed"];
RETURN[hopeless, Thrush.nullTune, , ];
};
Wait for recording or conversation to end.
WHILE cDesc.cState.state#idle AND ourSpec.type#finished DO
WAIT info.thAction;
ENDLOOP;
RETURN[ok, ourSpec.tune, ourSpec.interval, cDesc.cState.keyTable[ourSpec.keyIndex]];
If state is idle, possibly the interval doesn't know its actual duration.
};
WaitForStartOrFail: INTERNAL PROC[cDesc: ConvDesc, spec: Thrush.IntervalSpec]
RETURNS[started: BOOLFALSE] = {
ENABLE UNWIND => NULL;
TRUSTED { Process.SetTimeout[@info.thAction, pd.noJayTicks]; };
FOR i: NAT IN [0..pd.waitsForConnect) DO
WAIT info.thAction; -- <<The supervisor condition vbl; will this work?>>
IF cDesc.cState.state=idle OR (started←(spec.type#request)) THEN EXIT;
ENDLOOP;
TRUSTED { Process.SetTimeout[@info.thAction, pd.noActionTicks]; };
};
StopTune: PUBLIC ENTRY PROC [reason: FinchSmarts.RecordReason←ok] = {
ENABLE UNWIND=>NULL;
cDesc: ConvDesc;
IF NOT (([,cDesc,]←JayConnection[FALSE]).jayOpen) THEN RETURN; -- Stop only if started
EnqueueIntervals[cDesc, LIST[NEW[Thrush.IntervalSpecBody←stopIntervalSpec^]]];
};
StopSpeech: PUBLIC ENTRY PROC [reason: FinchSmarts.RecordReason←ok] = {
ENABLE UNWIND=>NULL;
cDesc: ConvDesc;
IF NOT (([,cDesc,]←ProseConnection[FALSE]).proseOpen) THEN RETURN; -- Stop only if started
ClearPendingProses[cDesc];
EnqueueProses[cDesc, LIST[NEW[Thrush.ProseSpecBody←stopProseSpec^]]];
};
ResetProse: PUBLIC ENTRY PROC [reason: FinchSmarts.RecordReason←ok] = {
ENABLE UNWIND=>NULL;
cDesc: ConvDesc;
IF NOT (([,cDesc,]←ProseConnection[FALSE]).proseOpen) THEN RETURN; -- Stop only if started
ClearPendingProses[cDesc];
EnqueueProses[cDesc, LIST[NEW[Thrush.ProseSpecBody←resetProseSpec^]]];
};
NoiseSpec: TYPE = REF NoiseSpecRec;
NoiseSpecRec: TYPE = RECORD [
tune: Thrush.Tune←-1,
key: Thrush.EncryptionKey← Thrush.nullKey
];
PlayNoise: PUBLIC PROC [
convID: Thrush.ConversationHandle ← Thrush.nullConvHandle, --vestigial until I figger it out
noiseName: ATOM,
queueIt: BOOL,
serverInstance: ROPE,
failOK: BOOL, -- playing is optional; leave connection open if tune doesn't exist.
wait: BOOL -- wait for noise to begin playing or the connection to fail.
] RETURNS [ started: BOOLFALSE ] = TRUSTED {
ENABLE ANY => CONTINUE;
noiseSpec: NoiseSpec;
IF info=NIL THEN RETURN[FALSE];
noiseSpec ← NARROW[RefTab.Fetch[info.sysNoises, noiseName].val];
testInterval: Thrush.IntervalSpec;
IF noiseSpec = NIL THEN {
key: Thrush.EncryptionKey;
cardKey: LONG POINTER TO ARRAY[0..2) OF LONG CARDINAL = LOOPHOLE[LONG[@key]];
val: NamesGV.AttributeSeq;
s: IO.STREAM;
noiseSpec ← NEW[NoiseSpecRec ← []];
IF serverInstance = NIL THEN
serverInstance ← UserProfile.Token["ThrushClientServerInstance", "Strowger.lark"];
val ← NamesGV.GVGetAttributeSeq[serverInstance, noiseName];
IF val=NIL OR val.length#2 THEN RETURN;
noiseSpec.tune ← Convert.IntFromRope[val[0].attributeValue];
IF noiseSpec.tune=-1 THEN RETURN;
s←IO.RIS[val[1].attributeValue];
cardKey[0] ← s.GetCard[]; cardKey[1] ← s.GetCard[];
noiseSpec.key ← key;
[]←RefTab.Store[info.sysNoises, noiseName, noiseSpec];
};
started ← PlaybackTune[tune: noiseSpec.tune, interval: [], key: noiseSpec.key, queueIt: queueIt, failOK: failOK, wait: wait];
};
FlushNoiseCacheCmd: Commander.CommandProc = {
info.sysNoises ← RefTab.Create[];
};
PlaybackTune: PUBLIC ENTRY PROC [
tune: Thrush.Tune,
interval: Thrush.VoiceInterval,
key: Thrush.EncryptionKey,
queueIt: BOOLFALSE,
failOK: BOOL, -- playing is optional; leave connection open if tune doesn't exist.
wait: BOOLFALSE
] RETURNS[ started: BOOLFALSE ] = { -- FALSE if failed or if didn't wait to find out
ENABLE UNWIND=>NULL;
cDesc: ConvDesc; state: StateInConv;
nb: NB; keyIndex: [0..17B];
exists: BOOL;
specs: Thrush.IntervalSpecs;
spec: Thrush.IntervalSpec;
IF tune<=Thrush.nullTune THEN {
info.ReportConversationState[convNotActive, NIL, "No such tune"]; RETURN; }; -- UGH!
IF NOT (([,cDesc,state]←JayConnection[TRUE]).jayOpen) THEN RETURN;
[nb, keyIndex] ←
ThParty.RegisterKey[shh: info.shh, credentials: cDesc.cState.credentials, key: key];
IF nb#success AND nb#stateMismatch THEN {
IF ~failOK THEN Complain[info, cDesc, error, "Could not encode encryption key"];
RETURN;
};
[, exists, specs] ← ThParty.DescribeInterval[shhh: info.shh, credentials: cDesc.cState.credentials,
targetInterval: NEW[Thrush.IntervalSpecBody ← [ tune: tune, interval: interval, direction: play ] ],
minSilence: LAST[INT]]; -- Just trim off surrounding silences.
IF ~exists THEN {
IF ~failOK THEN Complain[info, cDesc, error, "Could not play utterance"];
RETURN;
};
IF specs.rest#NIL THEN ERROR;
spec ← specs.first;
spec.keyIndex ← keyIndex; spec.type ← request; spec.direction ← play; spec.queueIt ← queueIt;
EnqueueIntervals[cDesc, specs ];
IF wait THEN started ← WaitForStartOrFail[cDesc, spec];
};
defaultTranslateProc: FinchSmarts.ProseTranslateProc←NIL;
RegisterTranslateProc: PUBLIC PROC [translate: FinchSmarts.ProseTranslateProc←NIL] ~ {
defaultTranslateProc ← translate;
};
TextToSpeech: PUBLIC ENTRY PROC [
text: Rope.ROPE, queueIt: BOOLTRUE,
proseTranslateProc: FinchSmarts.ProseTranslateProc←NIL]
RETURNS [reason: FinchSmarts.RecordReason←ok] = {
ENABLE UNWIND => NULL;
Modelled after RecordTune, but it breaks text up into manageable chunks so as to allow speech to start sooner and flush quicker...
ourSpec: Thrush.ProseSpec;
cDesc: ConvDesc; state: StateInConv;
started: BOOLFALSE;
IF proseTranslateProc#NIL THEN text ← proseTranslateProc[text]
ELSE IF defaultTranslateProc#NIL THEN text ← defaultTranslateProc[text];
IF NOT (([,cDesc,state]←ProseConnection[TRUE]).proseOpen) THEN
RETURN[hopeless];
IF ~queueIt THEN ClearPendingProses[cDesc]; -- need to generate reports??
WHILE text.Length[] > pd.maxProseLength DO
Scan backward from the end of the text for a place to break between words. The Prose treats an index marker as a word terminator. Keep punctuation with its preceding word for correct prosodics. Except - need to handle $, Dr., ft. specially both here and in LarkOutImpl. Where to put a common procedure? They run on different machines!
index: INT ← pd.maxProseLength;
WHILE IO.TokenProc[Rope.Fetch[text, index-1]] = other DO
index ← index-1;
IF index=0 THEN {index ← pd.maxProseLength; EXIT};
Too long between break chars; just make progress somehow.
ENDLOOP;
ourSpec ← NEW[Thrush.ProseSpecBody ← [
type: request, direction: play, queueIt: queueIt, prose: text.Substr[len: index]
]];
text ← text.Substr[start: index];
EnqueueProses[cDesc, LIST[ourSpec]];
queueIt ← TRUE;
ENDLOOP;
ourSpec ← NEW[Thrush.ProseSpecBody ← [
type: request, direction: play, queueIt: queueIt, prose: text
]];
EnqueueProses[cDesc, LIST[ourSpec]];
For now, assume that everything will work okay from here.
};
Registration
InitFinchSmarts: PUBLIC PROC [
thrushInstance: Thrush.ROPENIL,
ReportSystemState: PROC[ on: BOOL ],
ReportConversationState: PROC[ nb: NB, cDesc: ConvDesc, remark: Rope.ROPE ]
] = {
problem: ROPENIL; {
ENABLE RPC.CallFailed => { problem ← "Communication Failure"; GOTO InitFailed; };
partyID: Thrush.PartyHandle;
smartsID: Thrush.SmartsHandle;
thVR: RPC.VersionRange = ThVersions.GetThrushVR;
problem: Thrush.ROPENIL;
namesGVInstance: Thrush.ROPENIL;
UninitFinchSmarts[NIL];
IF info=NIL THEN info ← NEW[FinchSmarts.FinchInfoBody];
info.conversations ← NIL;
info.thProcess ← NIL;
info.currentConvID ← nullConvHandle;
info.apprise ← FALSE;
info.ReportSystemState←ReportSystemState;
info.ReportConversationState←ReportConversationState;
IF info.sysNoises=NIL THEN info.sysNoises ← RefTab.Create[];
IF thrushInstance = NIL THEN thrushInstance ← UserProfile.Token[
key: "ThrushClientServerInstance", default: "Strowger.Lark"];
info.myName ← [
type: "ThSmarts.Lark",
instance: Names.InstanceFromNetAddress[netAddress: Names.OwnNetAddress[], suffix: "0"],
version: ThVersions.FinchVR];
info.myRName ← Names.CurrentRName[];
info.myPassword ← Names.CurrentPasskey[];
info.serverInstanceName ← thrushInstance;
ThSmartsRpcControl.ExportInterface[
interfaceName: info.myName,
user: info.myRName,
password: info.myPassword];
pd.smartsIsExported←TRUE;
For the purpose of importing NamesGV, use prior binding of serverInstance if there is one and the server name is the same as before.
namesGVInstance ← UserProfile.Token[key: "NamesGVInstance", default: "Strowger.lark"];
IF info.namesGVInstance=NIL OR ~namesGVInstance.Equal[s2: info.namesGVInstanceName, case: FALSE] THEN info.namesGVInstance ← namesGVInstance;
info.namesGVInstanceName ← namesGVInstance;
IF PrincOpsUtils.IsBound[LOOPHOLE[NamesGVImpExp.GVImport]] AND ~NamesGVImpExp.GVImport[info.namesGVInstance] AND
~NamesGVImpExp.GVImport[info.namesGVInstanceName] THEN {
problem ← "Couldn't import Grapevine Package"; GOTO InitFailed; };
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}];
Implement the GV lookup portion of importing the Thrush instance explicitly, in order to get the benefit of the local Grapevine cache.
IF (info.namesGVInstance ← NamesGV.GVGetAttribute[rName: info.namesGVInstanceName, attribute: $connect, default: NIL]) = NIL THEN { problem←"Telephone server not found"; GOTO InitFailed};
IF (info.serverInstance ← NamesGV.GVGetAttribute[rName: info.serverInstanceName, attribute: $connect, default: NIL]) = NIL THEN { problem←"Telephone server not found"; GOTO InitFailed};
ThPartyRpcControl.ImportInterface[
interfaceName: [type: "ThParty.Lark", instance: info.serverInstance, version: thVR] ! --
RPC.ImportFailed=> {
IF why=wrongVersion THEN
problem ← IO.PutFR["Finch version %d too old; import failed",
card[ThVersions.FinchVersion]];
GOTO InitFailed;
}];
pd.interfacesAreImported←TRUE;
partyID←ThParty.CreateParty[shh: info.shh, type: individual, rName: info.myRName];
IF partyID=Thrush.nullHandle THEN { problem←"Can't register with server"; GOTO InitFailed; };
smartsID←ThParty.Register[
shh: info.shh, partyID: partyID, interface: NEW[ThSmartsRpcControl.InterfaceName←info.myName],
properties: [x: manager[machine: Names.OwnNetAddress[]]]] ;
IF smartsID=Thrush.nullHandle THEN { problem←"Can't register with server"; GOTO InitFailed; }; -- <<try to delete the Party?>>
info.smartsID ← smartsID;
info.partyID ← partyID;
ThParty.ConversationsForParty[shh: info.shh, partyID: partyID];
Will shortly update our knowledge of what's already going on!!
pd.finchOn←TRUE;
info.ReportSystemState[pd.finchOn];
EXITS
InitFailed => UninitFinchSmarts[problem];
};};
UninitFinchSmarts: PUBLIC PROC[problem: ROPENIL] = {
<<Must eliminate participation in any conversation that only we know enough about to get rid of, and eliminate any evidence of our partipation in any others. For now, do only the latter thing>>
ENABLE RPC.CallFailed => GOTO Failed;
IF problem#NIL THEN Problem[problem];
IF info#NIL THEN {
IF pd.finchOn AND info.partyID#nullHandle AND info.smartsID#nullHandle THEN
ThParty.Deregister[info.shh, info.smartsID!RPC.CallFailed=>CONTINUE];
info.shh←none;
info.ReportSystemState[FALSE];
WHILE info.conversations#NIL DO -- a bug?!
cDesc: ConvDesc=info.conversations;
info.conversations ← cDesc.next;
cDesc.next�sc.prev←NIL;
cDesc.clientData←NIL;
ENDLOOP;
};
pd.finchOn←FALSE;
IF PrincOpsUtils.IsBound[LOOPHOLE[NamesGVImpExp.UnGVImport]] THEN
NamesGVImpExp.UnGVImport[];
IF pd.interfacesAreImported THEN -- We don't do this any more.
ThPartyRpcControl.UnimportInterface[!LupineRuntime.BindingError=>CONTINUE];
pd.interfacesAreImported←FALSE;
IF pd.smartsIsExported THEN
ThSmartsRpcControl.UnexportInterface[!LupineRuntime.BindingError=>CONTINUE];
pd.smartsIsExported←FALSE;
EXITS
Failed => pd.interfacesAreImported ← pd.smartsIsExported ← FALSE;
};
FinchIsRunning: PUBLIC PROC RETURNS [finchIsRunning: BOOL] = { RETURN[pd.finchOn]; };
Utilities
GetConv: PUBLIC INTERNAL PROC[convID: ConversationHandle, validIfNew: BOOL
] RETURNS [ cDesc: ConvDesc←NIL ] = --INLINE-- {
pDesc: ConvDesc←NIL;
IF convID#nullConvHandle THEN
FOR cDesc ← info.conversations, cDesc.next WHILE cDesc#NIL DO
descID: ConversationHandle = cDesc.cState.credentials.convID;
IF descID = convID THEN RETURN;
IF cDesc.cState.state=idle AND ThParty.SameConvClass[convID, descID] THEN EXIT;
pDesc�sc; ENDLOOP;
IF cDesc=NIL THEN { -- If not, we're reusing a related entry.
cDesc ← NEW[FinchSmarts.ConvDescBody←[]];
cDesc.prev ← pDesc;
IF pDesc#NIL THEN pDesc.next ← cDesc ELSE info.conversations ← cDesc;
cDesc.otherPartyDesc ← "unknown party";
};
cDesc.descValid ← validIfNew;
cDesc.startTime ← BasicTime.Now[];
cDesc.desiredState ← any;
cDesc.desiredPartyID ← nullHandle;
cDesc.desiredReason ← wontSay;
cDesc.desiredComment←NIL;
cDesc.requestedIntervals ← cDesc.pendingIntervals ← NIL;
cDesc.requestedProses ← cDesc.pendingProses ← NIL;
cDesc.conference← cDesc.completed ← cDesc.failed ← FALSE;
Other fields are intentionally retained.
cDesc.cState.credentials ← [
convID: convID, smartsID: info.smartsID, partyID: info.partyID, stateID: 0];
IF pd.doReports THEN
Report[IO.PutFR[" ** NewConv %g %g, vl=%g\n", PutFTime[convID], rope[info.myRName], bool[validIfNew]]];
};
PutFTime: PROC[convID: ConversationHandle] RETURNS [IO.Value] = {
ehsMemorialBool: BOOLFALSE; -- wouldn't need if could return from catch phrase.
IF convID=nullConvHandle OR convID=BasicTime.nullGMT
THEN RETURN[rope["(not assigned)"]];
[]�sicTime.Update[convID, 0!BasicTime.OutOfRange=> {
ehsMemorialBool←TRUE; CONTINUE; }];
IF ehsMemorialBool THEN RETURN[int[LOOPHOLE[convID, INT]]]
ELSE RETURN[time[convID]];
};
GetConvDesc: PUBLIC PROC[convID: ConversationHandle]RETURNS[cDesc: ConvDesc←NIL ] = {
Not at present protected by monitor -- FinchToolImpl back-pointer problem.
FOR cDesc ← info.conversations, cDesc.next WHILE cDesc#NIL DO
IF cDesc.cState.credentials.convID = convID THEN EXIT; ENDLOOP;
RETURN[IF cDesc#NIL AND cDesc.descValid THEN cDesc ELSE NIL];
};
GetCDesc: PROC[] RETURNS [cDesc: ConvDesc←NIL ] = {
convID: ConversationHandle=info.currentConvID;
IF convID=nullConvHandle THEN RETURN;
RETURN[GetConvDesc[convID]];
};
Apprise: PUBLIC INTERNAL PROC[info: FinchInfo] = --INLINE-- {
IF info.thProcess=NIL THEN
TRUSTED { Process.Detach[info.thProcess ← FORK Supervise[info]]; };
info.apprise ← TRUE;
BROADCAST info.thAction;
};
ReportIntervals: INTERNAL PROC[cDesc: ConvDesc, event: Thrush.ConvEvent] = {
n^2 match (worst-case) of reported interval information matched against the requests that we have made. This gives us needed progress information.
FOR eSs: IntervalSpecs ← event.intervalSpecs, eSs.rest WHILE eSs#NIL DO
eS: IntervalSpec = eSs.first;
eSID: Thrush.IntID = eS.intID;
FOR rSs: IntervalSpecs ← cDesc.requestedIntervals, rSs.rest WHILE rSs#NIL DO
rS: IntervalSpec = rSs.first;
SELECT CompareIntID[eSID, rS.intID] FROM
less => EXIT; -- Possibly an error: interval exists that we didn't request.
greater => LOOP;
ENDCASE => NULL; -- Equal, deal with it.
rS^ ← eS^; -- Update status.
cDesc.requestedIntervals records all those intervals that have been requested but not denoted finished. They must finish in order. If an embedded entry in this list finishes, then the finish notifications for any preceding entries have been lost; here we simulate them. <<Consider various abnormal finish codes augmenting finish>>
IF rS.type=finished THEN -- Truncate requests
FOR fSs: IntervalSpecs ← cDesc.requestedIntervals, fSs.rest WHILE fSs#rSs DO
IF fSs.first.type # finished THEN {
fSs.first.type ← finished; fSs.first.changeNoted←FALSE; };
ENDLOOP;
EXIT;
ENDLOOP;
ENDLOOP;
};
ReportProses: INTERNAL PROC[cDesc: ConvDesc, event: Thrush.ConvEvent] = {
n^2 match (worst-case) of reported prose information matched against the requests that we have made. This gives us needed progress information.
FOR eSs: ProseSpecs ← event.proseSpecs, eSs.rest WHILE eSs#NIL DO
eS: ProseSpec = eSs.first;
eSID: Thrush.IntID = eS.intID;
FOR rSs: ProseSpecs ← cDesc.requestedProses, rSs.rest WHILE rSs#cDesc.pendingProses AND rSs#NIL DO
rS: ProseSpec = rSs.first;
SELECT CompareIntID[eSID, rS.intID] FROM
less => EXIT; -- Possibly an error: prose exists that we didn't request.
greater => LOOP;
ENDCASE => NULL; -- Equal, deal with it.
IF rS.type = eS.type THEN NULL
Avoids duplicate request reports during debugging (caused by a process race)
ELSE rS^ ← eS^; -- Update status.
cDesc.requestedProses records all those intervals that have been requested but not denoted finished. They must finish in order. If an embedded entry in this list finishes, then the finish notifications for any preceding entries have been lost; here we simulate them. <<Consider various abnormal finish codes augmenting finish>>
NOTE: ALL ProseSpecs should be reported explicitly (except ones that were flushed)!!!
IF rS.type=finished THEN -- Truncate requests
FOR fSs: ProseSpecs ← cDesc.requestedProses, fSs.rest WHILE fSs#rSs DO
IF fSs.first.type # finished THEN {
rS.queueIt should be FALSE; all of these requests have actually been flushed.
fSs.first.type ← finished; fSs.first.changeNoted←FALSE; };
ENDLOOP;
EXIT;
ENDLOOP;
ENDLOOP;
};
CompareIntID: PROC[i1, i2: Thrush.IntID] RETURNS [ Basics.Comparison ] = {
RETURN[
IF i1=i2 THEN equal
ELSE SELECT i1.stateID FROM
<i2.stateID => less,
>i2.stateID => greater,
ENDCASE => SELECT i1.reqID FROM
<i2.reqID => less,
>i2.reqID => greater,
ENDCASE => ERROR
];
};
EnqueueIntervals: INTERNAL PROC[cDesc: ConvDesc, int: IntervalSpecs] = {
Splice int to the end of cDesc.requestedIntervals, which is guaranteed to be a superset of pendingIntervals. When pendingIntervals is a trivial subset (NIL), it needs special attention.
IF cDesc.pendingIntervals=NIL THEN cDesc.pendingIntervals←int;
IF cDesc.requestedIntervals=NIL THEN cDesc.requestedIntervals←int
ELSE FOR iSs: IntervalSpecs ← cDesc.requestedIntervals, iSs.rest WHILE iSs#NIL DO
IF iSs.rest=NIL THEN { iSs.rest ← int; EXIT; };
ENDLOOP;
Apprise[info];
};
EnqueueProses: INTERNAL PROC[cDesc: ConvDesc, prose: ProseSpecs] = {
Splice prose to the end of cDesc.requestedProses, which is guaranteed to be a superset of pendingProses. When pendingProses is a trivial subset (NIL), it needs special attention.
IF cDesc.pendingProses=NIL THEN cDesc.pendingProses←prose;
IF cDesc.requestedProses=NIL THEN cDesc.requestedProses←prose
ELSE FOR pSs: ProseSpecs ← cDesc.requestedProses, pSs.rest WHILE pSs#NIL DO
cDesc.pendingProses is closer to the end of the list than cDesc.requestedProses is...
Yeah, but maybe it's a different list.
IF pSs.rest=NIL THEN { pSs.rest ← prose; EXIT; };
ENDLOOP;
Apprise[info];
};
ClearPendingProses: INTERNAL PROC [cDesc: ConvDesc] ~ {
FOR pSs: ProseSpecs ← cDesc.requestedProses, pSs.rest WHILE pSs#NIL DO
IF pSs.rest=cDesc.pendingProses THEN { pSs.rest ← NIL; EXIT; };
ENDLOOP;
cDesc.pendingProses←NIL;
};
Complain: INTERNAL PROC[info: FinchInfo, cDesc: ConvDesc, reason: Reason←wontSay, comment: ROPENIL] = {
cDesc.desiredState ← IF cDesc.cState.state#any THEN idle ELSE reserved;
cDesc.desiredReason ← reason;
cDesc.desiredComment ← comment;
cDesc.desiredPartyID ← nullHandle;
cDesc.pendingIntervals ← NIL;
ClearPendingProses[cDesc];
Apprise[info]; -- BROADCAST is sometimes meaningless (running monitor.)
};
FinchOn: INTERNAL PROC RETURNS [
on: BOOLFALSE, cDesc: ConvDesc←NIL, state: StateInConv←idle] = {
IF ~pd.finchOn OR info=NIL THEN {
Log.Report["Your Finch is not running", $Finch]; RETURN; };
on←TRUE;
cDesc ← GetCDesc[];
state←IF cDesc=NIL THEN idle ELSE cDesc.cState.state;
};
Request: INTERNAL PROC[
info: FinchInfo,
cDesc: ConvDesc,
state: StateInConv,
reason: Reason←wontSay,
comment: ROPENIL
] = {
cDesc.desiredState ← state;
cDesc.desiredReason ← reason;
cDesc.desiredComment ← comment;
Apprise[info];
};
Connect: INTERNAL PROC [
calledPartyID: PartyHandle, bluejayConnection: BOOL, proseConnection: BOOL, urgency: Thrush.CallUrgency, waitForActive: BOOLFALSE]
RETURNS [ cDesc: ConvDesc ] = {
state: StateInConv;
[,cDesc, state]𡤏inchOn[];
SELECT state FROM
idle => { cDesc ← GetConv[nullConvHandle, TRUE]; cDesc.cState.state𡤊ny; };
reserved => NULL;
ENDCASE => {
info.ReportConversationState[convStillActive, NIL, "Conversation already in progress"];
RETURN;
};
cDesc.bluejayConnection ← bluejayConnection;
cDesc.proseConnection ← proseConnection;
cDesc.desiredPartyID ← calledPartyID;
cDesc.pendingIntervals ← NIL;
ClearPendingProses[cDesc];
Request[info, cDesc, active];
IF ~waitForActive THEN RETURN;
FOR i: NAT IN [0..pd.waitsForConnect) DO
TRUSTED { Process.SetTimeout[@info.thAction, pd.noJayTicks]; };
WAIT info.thAction; -- <<The supervisor condition vbl; will this work?>>
TRUSTED { Process.SetTimeout[@info.thAction, pd.noActionTicks]; };
SELECT cDesc.cState.state FROM
active => RETURN;
idle => EXIT;
ENDCASE;
ENDLOOP;
Complain[info, cDesc, error, "Finch failed to connect to voice service."];
};
JayConnection: INTERNAL PROC[newConn: BOOLTRUE] RETURNS [
jayOpen: BOOL�LSE,
cDesc: ConvDesc←NIL,
state: StateInConv←idle
] = {
calledPartyID: PartyHandle;
IF NOT (([,cDesc, state]𡤏inchOn[]).on) THEN {
jayOpen←RepRet[FALSE, noSuchParty, "Your Finch is not running"]; RETURN; };
SELECT state FROM
idle, reserved => IF newConn THEN {
calledPartyID ← ThParty.GetParty[shh: info.shh, partyID: info.partyID, rName: "Recording", type: service];
IF calledPartyID # Thrush.nullHandle THEN
cDesc ← Connect[calledPartyID, TRUE, FALSE, normal, TRUE];
};
ENDCASE;
jayOpen←RepRet[IF cDesc=NIL THEN FALSE ELSE cDesc.bluejayConnection, noSuchConv, "Connection to voice server failed"];
};
ProseConnection: INTERNAL PROC[newConn: BOOLTRUE] RETURNS [
proseOpen: BOOL�LSE,
cDesc: ConvDesc←NIL,
state: StateInConv←idle
] = {
calledPartyID: PartyHandle;
IF NOT (([,cDesc, state]𡤏inchOn[]).on) THEN {
proseOpen←RepRet[FALSE, noSuchParty, "Your Finch is not running"]; RETURN; };
SELECT state FROM
idle, reserved => IF newConn THEN {
calledPartyID ← ThParty.GetParty[shh: info.shh, partyID: info.partyID, rName: "Text-to-Speech", type: service];
Would really like to be able to distinguish between service dead and service busy for client reports (return param from TextToSpeech). Also true for JayConnection above.
IF calledPartyID # Thrush.nullHandle THEN
cDesc ← Connect[calledPartyID, FALSE, TRUE, normal, TRUE];
};
ENDCASE;
proseOpen←RepRet[IF cDesc=NIL THEN FALSE ELSE cDesc.proseConnection, noSuchConv, "Connection to text-to-speech server failed"];
};
RepRet: PROC[bool: BOOL, nb: NB, remark: Rope.ROPE]
RETURNS[sameBool: BOOL] = {
IF bool=FALSE THEN
IF info#NIL THEN info.ReportConversationState[nb, NIL, remark]
ELSE Log.Report[remark, $Finch];
sameBool𡤋ool;
};
Problem: PROC[remark: ROPE←NIL] = { Log.Problem[remark, $Finch]; };
Feep: PUBLIC ENTRY PROC[feepString: ROPE] = {
cDesc: ConvDesc;
IF info=NIL OR info.conversations=NIL OR info.currentConvID=nullConvHandle
THEN RETURN;
cDesc ← GetConv[info.currentConvID, FALSE];
EnqueueProses[cDesc,
LIST[NEW[Thrush.ProseSpecBody←[prose: feepString, direction: play]]]];
};
State Transition Tables
Transition: TYPE = {
Just codes to dispatch on in Supervisor; explained there
noop, elim, idle, alrt, actv, spvs, invl, ntiy
};
transForStates: ARRAY StateInConv OF ARRAY StateInConv OF Transition ← [
idle resrv pars init pend mayb ring canAc activ inact any -- desired
[ elim, invl, invl, invl, invl, invl, invl, invl, elim, invl, elim ], -- idle  (current)
[ idle, noop, invl, invl, invl, invl, invl, invl, alrt, ntiy, noop ], -- reserved
[ idle, invl, invl, invl, invl, invl, invl, invl, invl, ntiy, noop ], -- parsing
[ idle, invl, invl, invl, invl, invl, invl, invl, noop, ntiy, noop ], -- initiating
[ idle, invl, invl, invl, invl, invl, invl, invl, actv, ntiy, noop ], -- pending
[ idle, invl, invl, invl, invl, invl, invl, invl, noop, ntiy, noop ], -- maybe
[ idle, invl, invl, invl, invl, invl, invl, invl, actv, ntiy, noop ], -- ringing
[ idle, invl, invl, invl, invl, invl, invl, invl, ntiy, ntiy, ntiy ], -- canActivate
[ idle, invl, invl, invl, invl, invl, invl, invl, spvs, ntiy, spvs ], -- active
[ idle, invl, invl, invl, invl, invl, invl, invl, ntiy, invl, ntiy ], -- inactive
[ elim, alrt, invl, invl, invl, invl, invl, invl, alrt, ntiy, invl ] -- any (nonex)
];
NB: examine relationship to validity table in PartyOpsImpl someday.
Debugging nonsense
ViewCmd: Commander.CommandProc = TRUSTED {
Nice.View[pd, "Finch PD"];
};
Commander.Register["VuFinch", ViewCmd, "Program Management variables Finch"];
Commander.Register["FlushNoiseCache", FlushNoiseCacheCmd, "Forget cached SysNoises"];
Registration for use by arms-length systems
FinchSmarts.Register[NEW[FinchSmarts.ProcsRecord ← [
playbackTune: PlaybackTune,
playNoise: PlayNoise,
recordTune: RecordTune,
stopTune: StopTune,
textToSpeech: TextToSpeech,
registerTranslateProc: RegisterTranslateProc,
stopSpeech: StopSpeech,
finchIsRunning: FinchIsRunning
]]];
}.
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