FinchSmartsImpl.mesa
Copyright © 1984, 1986 by Xerox Corporation. All rights reserved.
Last Edited by: Swinehart, May 25, 1986 8:11:26 pm PDT
Polle Zellweger (PTZ) December 13, 1985 3:05:18 pm PST
DIRECTORY
BasicTime USING [ Now, nullGMT, OutOfRange, Update ],
Commander USING [ CommandProc, Register ],
FinchSmarts,
IO,
Lark USING [ KeyTable, SHHH ],
Log USING [Problem, Report ],
LupineRuntime USING [ BindingError ],
Names USING [ CurrentPasskey, CurrentRName, InstanceFromNetAddress, OwnNetAddress ],
NamesGV USING [ GVGetAttribute ],
NamesGVImpExp USING [ GVImport, UnGVImport ],
NamesRPC USING [ StartConversation ],
Nice,
PrincOpsUtils USING [ IsBound ],
Process USING [ Detach, SecondsToTicks, SetTimeout, Ticks ],
RefQ USING [ Dequeue, Enqueue, Map, MapType, Queue ],
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, 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, IO, FinchSmarts, Log, LupineRuntime, Names, NamesGV, NamesGVImpExp, NamesRPC, Nice, PrincOpsUtils, Process, RefQ, 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: BOOL←FALSE,
doNice: BOOL←FALSE,
timeoutNoAction: INTEGER ← 30,
noActionTicks: Process.Ticks ← Process.SecondsToTicks[30],
timeoutJayConnect: INTEGER ← 1,
noJayTicks: Process.Ticks ← Process.SecondsToTicks[1],
encryptionRequested: BOOLEAN←TRUE,
interfacesAreImported: BOOLEAN←FALSE,
smartsIsExported: BOOLEAN←FALSE,
waitsForConnect: NAT𡤆,
queueIt: BOOL←FALSE,
finchOn: BOOLEAN←FALSE,
maxProseLength: INT,
intvReq: CARDINAL ← 177777B
];
info: FinchInfo;
pd: REF PD ← NEW[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: ROPE←NIL;
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; };
};
nb: NB←success;
trans: Transition;
info.apprise ← FALSE;
FOR conversations: RefQ.Queue ← info.conversations, conversations.rest
WHILE conversations#
NIL
DO
cDesc: ConvDesc = NARROW[conversations.first];
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[StateInConvsc.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
SELECT stateNow
FROM
idle => IF ours THEN info.currentConvID ← nullConvHandle;
parsing, reserved, initiating, maybe, ringing, canActivate, active, inactive => {
ours←TRUE;
IF cDesc.ultimateState<active THEN cDesc.ultimateState ← stateNow;
info.currentConvID ← cDesc.cState.credentials.convID;
};
pending => cDesc.ultimateState ← 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 => {
cDesc.requestedIntervalssc.pendingIntervals←NIL;
cDesc.bluejayConnection ← cDesc.proseConnection ← FALSE;
cDesc.requestedProsessc.pendingProses←NIL;
};
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.
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. 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 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;
IF stateNow=idle
THEN {
info.conversations ← RefQ.Dequeue[info.conversations, conversations];
cDesc.clientData ← NIL;
};
ENDLOOP;
IF info.conversations = NIL 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: ROPE←NIL] = {
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: BOOL←FALSE] = {
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: BOOL←FALSE
]
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: BOOL←FALSE;
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: BOOL←FALSE] = {
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^]]];
};
PlaybackTune:
PUBLIC
ENTRY
PROC [
tune: Thrush.Tune,
interval: Thrush.VoiceInterval,
key: Thrush.EncryptionKey,
queueIt: BOOL←FALSE,
failOK: BOOL, -- playing is optional; leave connection open if tune doesn't exist.
wait: BOOL←FALSE
] RETURNS[ started: BOOL←FALSE ] = { -- 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;
<< ??? No word ??? >>
[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;
};
spec ← NEW[Thrush.IntervalSpecBody ← [ tune: tune, interval: interval, direction: play ] ];
[, exists, specs] ←
ThParty.DescribeInterval[
shhh: info.shh, credentials: cDesc.cState.credentials,
targetInterval: spec, 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=NIL THEN specs ← LIST[spec];
spec ← specs.first;
FOR sL: Thrush.IntervalSpecs ← specs, sL.rest
WHILE sL#
NIL
DO
Describe... may return a list in error; we only want the outer interval.
IF sL#specs
THEN spec.interval.length ←
(sL.first.interval.start+sL.first.interval.length) - spec.interval.start;
ENDLOOP;
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: BOOL←TRUE,
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: BOOL←FALSE;
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.ROPE←NIL,
ReportSystemState: PROC[ on: BOOL ],
ReportConversationState: PROC[ nb: NB, cDesc: ConvDesc, remark: Rope.ROPE ]
] = {
problem: ROPE←NIL; {
ENABLE RPC.CallFailed => { problem ← "Communication Failure"; GOTO InitFailed; };
partyID: Thrush.PartyHandle;
smartsID: Thrush.SmartsHandle;
thVR: RPC.VersionRange = ThVersions.GetThrushVR;
problem: Thrush.ROPE ← NIL;
namesGVInstance: Thrush.ROPE←NIL;
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 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 (Not any more!)
~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] ! --
interfaceName: [type: "ThParty.Lark", instance: info.serverInstanceName, version: thVR] ! -- Don't do "explicit import" any more.
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:
ROPE←
NIL] = {
<<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];
{
ClearConvs: RefQ.MapType = {
cDesc: ConvDesc=NARROW[subqueue.first];
cDesc.clientData ← NIL;
};
[]←RefQ.Map[info.conversations, ClearConvs];
info.conversations ← NIL;
};
};
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-- {
IsConv: RefQ.MapType = {
cDesc ← NARROW[subqueue.first];
IF cDesc.cState.credentials.convID = convID THEN RETURN[TRUE];
};
IF convID#nullConvHandle AND RefQ.Map[info.conversations, IsConv] THEN RETURN;
New conversation
cDesc ← NEW[FinchSmarts.ConvDescBody←[]];
cDesc.otherPartyDesc ← "unknown party";
cDesc.descValid ← validIfNew;
cDesc.startTime ← BasicTime.Now[];
cDesc.desiredPartyID ← nullHandle;
cDesc.cState.credentials ← [
convID: convID, smartsID: info.smartsID, partyID: info.partyID, stateID: 0];
info.conversations ← RefQ.Enqueue[info.conversations, cDesc];
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: BOOL←FALSE; -- 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.
IsConv: RefQ.MapType = {
cDesc ← NARROW[subqueue.first];
IF cDesc.cState.credentials.convID = convID THEN RETURN[TRUE];
};
IF convID=nullConvHandle THEN RETURN[NIL];
RETURN[
IF RefQ.Map[info.conversations, IsConv]
AND cDesc.descValid
THEN cDesc ELSE NIL];
};
GetCurrentConvID:
PUBLIC PROC
RETURNS [convID: ConversationHandle] = {
Not at present protected by monitor -- FinchToolImpl back-pointer problem.
RETURN[info.currentConvID];
};
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: CARDINAL = eS.intID.reqID;
FOR rSs: IntervalSpecs ← cDesc.requestedIntervals, rSs.rest
WHILE rSs#
NIL
DO
rS: IntervalSpec = rSs.first;
IF eSID # rS.intID.reqID THEN LOOP;
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: CARDINAL = eS.intID.reqID;
FOR rSs: ProseSpecs ← cDesc.requestedProses, rSs.rest
WHILE rSs#cDesc.pendingProses
AND rSs#
NIL
DO
rS: ProseSpec = rSs.first;
IF eSID # rS.intID.reqID THEN LOOP;
IF rS.type # eS.type
THEN rS^ ← eS^;
-- Update status.
Avoids duplicate request reports during debugging (caused by a process race)
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;
};
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.
FOR iSs: IntervalSpecs ← int, iSs.rest
WHILE iSs#
NIL
DO
iSs.first.intID ← [0, pd.intvReq ← pd.intvReq+1];
reqID unique within 2^16, forget about stateID
ENDLOOP;
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.
FOR pSs: ProseSpecs ← prose, pSs.rest
WHILE pSs#
NIL
DO
pSs.first.intID ← [0, pd.intvReq ← pd.intvReq+1];
reqID unique within 2^16, forget about stateID
ENDLOOP;
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:
ROPE←
NIL] = {
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: BOOL←FALSE, 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 ← GetConvDesc[info.currentConvID];
state←IF cDesc=NIL THEN idle ELSE cDesc.cState.state;
};
Request:
INTERNAL
PROC[
info: FinchInfo,
cDesc: ConvDesc,
state: StateInConv,
reason: Reason←wontSay,
comment: ROPE←NIL
] = {
cDesc.desiredState ← state;
cDesc.desiredReason ← reason;
cDesc.desiredComment ← comment;
Apprise[info];
};
Connect:
INTERNAL
PROC [
calledPartyID: PartyHandle, bluejayConnection: BOOL, proseConnection: BOOL, urgency: Thrush.CallUrgency, waitForActive: BOOL←FALSE]
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:
BOOL←
TRUE]
RETURNS [
jayOpen: BOOLLSE,
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:
BOOL←
TRUE]
RETURNS [
proseOpen: BOOLLSE,
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"];
Registration for use by arms-length systems
FinchSmarts.Register[
NEW[FinchSmarts.ProcsRecord ← [
playbackTune: PlaybackTune,
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
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)
Polle Zellweger (PTZ) December 13, 1985 2:53:06 pm PST
New function for FinchToolImpl to use for managing selections in conversation log.
changes to: GetCurrentConvID
Swinehart, February 2, 1986 11:01:59 pm PST
intID.stateID couldn't be maintained properly. Change intID.reqID to be a semi-unique value (cycles every 2^16 requests), and look for that only in incoming reports. They'll have (correct) stateID values in them, too, but we don't use them for anything. Fixes a problem where some requests were being played multiple times, others not at all, if things were queued.
changes to: Supervise, ReportProses, EnqueueProses, DIRECTORY
Swinehart, May 25, 1986 8:10:59 pm PDT
Accommodate 6.1 server, which no longer does "explicit export", so can't "explicit import". 6.1 Finch does it using hostHint.
changes to: InitFinchSmarts