File: NuthatchImpl.mesa
Last Edited by: Swinehart, October 17, 1985 12:17:06 pm PDT
DIRECTORY
Commander USING [ CommandProc, Handle, Register ],
CommandTool USING [ NextArgument ],
Convert USING [ AppendInt ],
GVBasics USING [RName],
IO,
Nice USING [ View ],
Nuthatch,
NuthatchDB USING [CloseTransaction, GetVoiceFileEntry, GetVoiceFileID, InitializeDB],
NuthatchLog USING [InitializeLog, WriteLogEntry],
NuthatchUpdater USING [DoLogEntries],
RefText USING [ New ],
Rope USING [Concat, ROPE ],
BasicTime USING [GMT, Now, nullGMT],
Thrush USING [nullKey],
UserProfile USING [ Token ]
;
NuthatchImpl: CEDAR PROGRAM
IMPORTS BasicTime, Commander, CommandTool, Convert, IO, Nice, NuthatchDB, NuthatchLog, NuthatchUpdater, RefText, Rope, UserProfile
EXPORTS Nuthatch = {
OPEN Nuthatch;
currentNUH: PUBLIC NuthatchUserHandle;
pd: PUBLIC REF Nuthatch.PDNEW[Nuthatch.PD];
updateProcess: PUBLIC PROCESS;
logStream: IO.STREAMNIL;
LogEntryType: TYPE = {increment, decrement, update};
RefIDType: TYPE = IDType;
RefID: TYPE = ID;
InitializeNuthatch: PUBLIC PROC[
userName: GVBasics.RName←NIL, 
logFileName: Rope.ROPENIL,
RefIDType: Rope.ROPENIL,
close: BOOLTRUE] RETURNS [success: BOOLTRUE, nuthatchUserHandle: NuthatchUserHandle] = {
initializes fileName as the current log stream, userName as the current user, and RefIDType as the current ID type for voice messages. If can't open the log file, or have some other kind of alpine failure, report back through some returned variable. On further calls to Nuthatch routines, if log can't be talked to then do what? For an abort, do a retry, for a failure to communicate, report back to walnut window that communication with log has failed and nuthatch functions may not be available. Make OpenLogFile into internal, non-entry proc, since the monitor is already locked. --
initializes fileName as the current log stream, userName as the current user, and RefIDType as the current ID type for voice messages.
ENABLE UNWIND => NULL;
logReadPoint: INT←-1;
Hack! Hack!--
updateProcess←NIL;
check out the database --
IF currentNUH#NIL THEN RETURN[TRUE, currentNUH];
[success, logReadPoint] ← NuthatchDB.InitializeDB[close: close];
IF ~success THEN RETURN;
nuthatchUserHandle←NEW[NuthatchUserRec←[]];
nuthatchUserHandle.userName←userName;
NuthatchLog.InitializeLog[nuthatchUserHandle];
nuthatchUserHandle.updateProcess←NIL;
nuthatchUserHandle.refIDType←RefIDType;
nuthatchUserHandle.logReadPoint←logReadPoint;
nuthatchUserHandle.defaultType ← "simple";
Hack! Hack!--
currentNUH←nuthatchUserHandle;
};
CatalogVoiceFile: PUBLIC PROC[
nuthatchUserHandle: NuthatchUserHandle,
creator: GVBasics.RName, get this from current user name --
voiceFileID: Rope.ROPE,
tuneNumber: INT,
recordedTime: BasicTime.GMT,
referenceCount: INT, 
samples: INT,    
startingSample: INT,  
encryptionKey: EncryptionKey←Thrush.nullKey,  
close: BOOL,
time: BasicTime.GMT,
type: Rope.ROPE
] = TRUSTED {
Writes a log entry to insert or update an entry for this voice file in the directory.
keyRope: Rope.ROPE;
cardKey: LONG POINTER TO ARRAY[0..2) OF LONG CARDINAL=LOOPHOLE [LONG[@encryptionKey]];
keyRope←IO.PutFR["%bB %bB", IO.card[cardKey[0]], IO.card[cardKey[1]]];
build log entry into a rope.--
pass it to NuthatchLog.WriteLogEntry[logRope, nuthatchUserHandle];--
NuthatchLog.WriteLogEntry[Rope.Concat[
IO.PutFR ["Catalog %g\n%g %d %g\n%d ", IO.rope[voiceFileID], IO.rope[nuthatchUserHandle.userName], IO.int[tuneNumber], IO.time[recordedTime], IO.int[referenceCount]],
IO.PutFR["%d %d %g %g %g\n", IO.int[samples], IO.int[startingSample], IO.rope[keyRope], IO.rope[type], IO.time[time]]], nuthatchUserHandle];
update DB from log --
NuthatchUpdater.DoLogEntries[nuthatchUserHandle, close]; 
};
GetDirectoryEntry: PUBLIC PROC[voiceFileID: VoiceFileID, close: BOOL] RETURNS[
tuneNumber: Tune,
recordTime: BasicTime.GMT,
creator: GVBasics.RName,
samples: INT,
startSample: INT,
expirationDate: BasicTime.GMT,
encryptionKey: EncryptionKey,
type: Rope.ROPE,
referenceCount: INT,
found: BOOL] = TRUSTED {
Should catch DB problem here?
Reads the directory entry for this voice file.
[tuneNumber, recordTime, creator, samples, startSample, expirationDate, encryptionKey, type, referenceCount, found] ←
NuthatchDB.GetVoiceFileEntry[voiceFileID, close];
};
GetFileID: PUBLIC PROC[
ID: Rope.ROPE, nuthatchUserHandle: NuthatchUserHandle, close: BOOL]
RETURNS [voiceFileID: VoiceFileID] = {
Given the user's ID for a voice message, return its voiceFileID.
Get IDTYpe and user's name from handle.
refIDType: Rope.ROPE ← nuthatchUserHandle.refIDType;
user: GVBasics.RName ← nuthatchUserHandle.userName;
Call nuthatchDB.lookup of some kind, giving user, IDType, and ID, to get voiceFileID
voiceFileID ← NuthatchDB.GetVoiceFileID[user, refIDType, ID, close];
};
MakeInterestEntry: PUBLIC PROC[
nuthatchUserHandle: NuthatchUserHandle,
voiceFileID: VoiceFileID←NIL,
refID: Rope.ROPENIL,
close: BOOL,
time: BasicTime.GMT
] = {
Writes a log entry to create an entry in the InterestList relation for this voice file, associating the user, RefID, and RefIDType with the voiceMessageID.
SHOULD TEST FOR NO INPUT PARMS = NIL
logEntryRope: Rope.ROPE;
refIDType: Rope.ROPE←nuthatchUserHandle.refIDType;
user: GVBasics.RName←nuthatchUserHandle.userName;
time ← NHTime[time];
Put user's refID at end of line to make parsing easier
logEntryRope ← IO.PutFR["Create %g\n%g %g\n%g %g\n", IO.rope[voiceFileID], IO.rope[user], IO.time[time], IO.rope[refIDType], IO.rope[refID]];
NuthatchLog.WriteLogEntry[logEntryRope, nuthatchUserHandle];
NuthatchUpdater.DoLogEntries[nuthatchUserHandle, close];
};
AddInterest: PUBLIC PROC[
nuthatchUserHandle: NuthatchUserHandle,
voiceFileID: Rope.ROPENIL, --optional
refID: Rope.ROPENIL,
close: BOOL,
time: BasicTime.GMT] = {
Writes a log entry to increment the reference count for this voice file
logEntryRope: Rope.ROPE;
From the nuthatchUserHandle, get the user and the refIDType.
If voice file id is given, write one kind of entry; otherwise write other kind.
refIDType: Rope.ROPE←nuthatchUserHandle.refIDType;
user: GVBasics.RName←nuthatchUserHandle.userName;
time ← NHTime[time];
Put user's refID at end of line to make parsing easier
IF voiceFileID#NIL THEN
logEntryRope←IO.PutFR["AddRef %g\n%g %g\n%g %g\n", IO.rope[voiceFileID],
IO.rope[user], IO.time[time], IO.rope[refIDType], IO.rope[refID]]
ELSE {
voiceFileID ← NuthatchDB.GetVoiceFileID[
user, refIDType, refID, FALSE];
IF voiceFileID=NIL THEN { IF close THEN NuthatchDB.CloseTransaction[]; RETURN; };
logEntryRope←IO.PutFR["AddUserRef %g %g\n%g %g\n", IO.rope[user],
IO.time[time], IO.rope[refIDType], IO.rope[refID]];
};
NuthatchLog.WriteLogEntry[logEntryRope, nuthatchUserHandle];
NuthatchUpdater.DoLogEntries[nuthatchUserHandle, close]; 
};
LoseInterest: PUBLIC PROC[
nuthatchUserHandle: NuthatchUserHandle,
voiceFileID: Rope.ROPE, --optional --
refID: Rope.ROPE,
close: BOOL,
time: BasicTime.GMT] = {
Writes a log entry to decrement the reference count for this voice file --
success: BOOLFALSE;
refIDType: Rope.ROPE←nuthatchUserHandle.refIDType;
user: GVBasics.RName←nuthatchUserHandle.userName;
logEntryRope: Rope.ROPE;
time ← NHTime[time];
put user's refID at end of line to make parsing easier --
IF voiceFileID#NIL THEN
logEntryRope←IO.PutFR["LoseRef %g\n%g %g\n%g %g\n", IO.rope[voiceFileID],
IO.rope[user], IO.time[time], IO.rope[refIDType], IO.rope[refID]]
ELSE {
voiceFileID ← NuthatchDB.GetVoiceFileID[
user, refIDType, refID, FALSE];
IF voiceFileID=NIL THEN { IF close THEN NuthatchDB.CloseTransaction[]; RETURN; };
logEntryRope←IO.PutFR["LoseUserRef %g %g\n%g %g\n",
IO.rope[user], IO.time[time], IO.rope[refIDType], IO.rope[refID]];
};
NuthatchLog.WriteLogEntry[logEntryRope, nuthatchUserHandle];
NuthatchUpdater.DoLogEntries[nuthatchUserHandle, close]; 
};
RemoveInterestEntry: PUBLIC PROC[
nuthatchUserHandle: NuthatchUserHandle,
voiceFileID: Rope.ROPE, --optional --
refID: Rope.ROPE,
close: BOOL,
time: BasicTime.GMT] = {
Writes a log entry to decrement the reference count for this voice file. If the database has no record of this message containing voice, that's OK; just ignore it. (Happens after doing an expunge in Walnut, for example.) --
success: BOOLFALSE;
refIDType: Rope.ROPE←nuthatchUserHandle.refIDType;
user: GVBasics.RName←nuthatchUserHandle.userName;
logEntryRope: Rope.ROPE;
time ← NHTime[time];
IF voiceFileID#NIL THEN
logEntryRope←IO.PutFR["RemoveRef %g\n%g %g\n%g %g\n", IO.rope[voiceFileID],
IO.rope[user], IO.time[time], IO.rope[refIDType], IO.rope[refID]]
ELSE {
voiceFileID ← NuthatchDB.GetVoiceFileID[
user, refIDType, refID, FALSE];
IF voiceFileID=NIL THEN { IF close THEN NuthatchDB.CloseTransaction[]; RETURN; };
logEntryRope←IO.PutFR["RemoveUserRef %g %g\n%g %g\n",
IO.rope[user], IO.time[time], IO.rope[refIDType], IO.rope[refID]];
};
NuthatchLog.WriteLogEntry[logEntryRope, nuthatchUserHandle];
NuthatchUpdater.DoLogEntries[nuthatchUserHandle, close]; 
};
FinishUpdates: PUBLIC PROC[nuthatchUserHandle: NuthatchUserHandle] = {
NuthatchUpdater.DoLogEntries[nuthatchUserHandle, TRUE]; };
GetTune: PUBLIC PROC[voiceFileID: VoiceFileID] RETURNS [tune: Tune] = {
Given the voiceFileID for a voice message, return its tune number.
tuneStream: IO.STREAMIO.RIS[voiceFileID];
tune ← IO.GetInt[tuneStream];
IO.Close[tuneStream];
}; 
NHTime: PUBLIC PROC[time: BasicTime.GMT] RETURNS [nhTime: BasicTime.GMT] = {
RETURN[IF time#BasicTime.nullGMT THEN time ELSE BasicTime.Now[]]; };
SetSysNoise: Commander.CommandProc = {
Enter the voice file ID (second argument) under current profile ThrushServerInstance, time is now, type is $SysNoises, value is first argument, interest is TRUE!!
nuh: NuthatchUserHandle ← NEW[Nuthatch.NuthatchUserRec ← currentNUH^];
noiseID: Rope.ROPE = CommandTool.NextArgument[cmd];
voiceFileID: Rope.ROPE = CommandTool.NextArgument[cmd];
IF noiseID=NIL OR voiceFileID = NIL THEN
RETURN[$Failure, "Args must be Noise-ID and VoiceFileID"];
nuh.userName ← UserProfile.Token[key: "ThrushServerInstance", default: "Morley.Lark"];
nuh.refIDType ← "SysNoises";
AddInterest[nuh, voiceFileID, noiseID, TRUE, BasicTime.nullGMT];
currentNUH.logReadPoint ← nuh.logReadPoint;
nuh←NIL;
};
ReplayLog: PROC[cmd: Commander.Handle, reset: BOOLFALSE] = {
nuh: Nuthatch.NuthatchUserHandle ← currentNUH;
userName: Rope.ROPE = CommandTool.NextArgument[cmd];
IF currentNUH=NIL THEN RETURN;
IF userName#NIL THEN {
nuh ← NEW[Nuthatch.NuthatchUserRec ← currentNUH^];
nuh.userName ← userName;
nuh.logStream ← NIL;
};
IF reset THEN nuh.logReadPoint ← 0;
NuthatchUpdater.DoLogEntries[nuh];
IF userName#NIL THEN nuh.logStream ← NIL;
};
DoLog: Commander.CommandProc = {
ReplayLog[cmd];
};
AllLog: Commander.CommandProc = {
ReplayLog[cmd, TRUE];
};
nonsenseID: INT ← 0;
nonsenseR: REF TEXTNIL;
Gen: PROC RETURNS [r: Rope.ROPE] = {
IF nonsenseR=NIL THEN nonsenseR ← RefText.New[10];
nonsenseR.length ← 0;
nonsenseR ← Convert.AppendInt[nonsenseR, (nonsenseID ← nonsenseID+1)];
};
Test1: Commander.CommandProc = {
voiceFileID: Rope.ROPE;
FOR i: NAT IN [0..100) DO
voiceFileID←NuthatchDB.GetVoiceFileID[user: "Swinehart.pa", refIDType: "GVID", refID: Gen[], close: i=99];
voiceFileID ← voiceFileID;
ENDLOOP;
};
Test2: Commander.CommandProc = {
};
Test3: Commander.CommandProc = {
tuneNumber: INT;
FOR i: NAT IN [0..100) DO
[tuneNumber]←NuthatchDB.GetVoiceFileEntry[msg: Gen[], close: i=99];
tuneNumber ← tuneNumber;
ENDLOOP;
};
ViewCmd: Commander.CommandProc = TRUSTED {
Nice.View[pd, "Nuthatch PD"];
};
Commander.Register["VuNuthatch", ViewCmd, "Program Management variables for Nuthatch"];
Commander.Register["DoLog", DoLog, "Make sure all Nuthatch log entries have been processed."];
Commander.Register["AllLog", AllLog, "Play Nuthatch log from the beginning."];
Commander.Register["SysNoise", SetSysNoise, "SysNoise Rollback \"12 3-Mar-83 4:52:07 PDT\" -- makes the entry in Nuthatch DB"];
Commander.Register["Test1", Test1, "Test effectiveness of irMsg index"];
Commander.Register["Test2", Test2, "Test effectiveness of irID index"];
Commander.Register["Test3", Test3, "Test effectiveness of miMsg index"];
}.