DIRECTORY
BasicTime USING [GMT],
Commander USING [CommandProc, Register],
CommanderOps USING [ArgumentVector, Failed, Parse ],
Convert USING [RopeFromTime ],
FS USING [defaultStreamOptions, Error, FileInfo, StreamOpen, StreamOptions ],
FSBackdoor USING [RemoteEvent, NextRemoteEvent ],
IO USING [Close, PutFR1, PutRope, rope, STREAM ],
Process USING [Detach, priorityBackground, SetPriority ],
Rope USING [Cat, Fetch, Find, Length, MaxLen, ROPE, Substr ],
Tioga USING [ PropList ],
TiogaAccess USING [FromFile, Get, Reader, TiogaChar ],
TiogaFileIO USING [ GetParts ],
TiogaVoicePrivate USING [ thrushHandle ],
ViewerClasses USING [Viewer],
ViewerEvents USING [EventProc, EventRegistration, RegisterEventProc, UnRegisterEventProc ],
ViewerOps USING [AddProp, FetchProp],
VoiceRope USING [Handle, Retain, VoiceRopeInterval ]
;
VoiceInterestImpl:
CEDAR PROGRAM
IMPORTS Commander, CommanderOps, Convert, FS, -- FSBackdoor, Process, -- IO, Rope, TiogaAccess, TiogaFileIO, TiogaVoicePrivate, ViewerEvents, ViewerOps, VoiceRope
EXPORTS TiogaVoicePrivate
VoiceInterests
A mechanism to register interest in any voice contained in a Tioga file, plus a command to register interests in any voice found in a named file.
backgroundCommentary:
IO.
STREAM ¬
NIL;
Commentate:
PROC [remark: Rope.
ROPE, commentary:
IO.
STREAM] =
INLINE {
IF commentary # NIL THEN commentary.PutRope[remark];
};
LocalFile: ERROR;
DoFileInterests:
PROC [file: Rope.
ROPE, commentary:
IO.
STREAM] = {
Called by explicit RegisterVoiceInterest command (both PCedar and DCedar) or by file events generated when a file is copied to a global location (DCedar only).
globalName: Rope.ROPE;
voiceList: Rope.ROPE ¬ NIL;
createDate: BasicTime.GMT;
[globalName, createDate] ¬ GetFileNameAndDate[file, commentary];
IF
NOT IsTiogaFile[globalName]
THEN {
Commentate[" Not a Tioga file\n", commentary];
RETURN
};
voiceList ¬ GetVoiceList[globalName];
IF voiceList = NIL THEN Commentate[" No voice in file\n", commentary]
ELSE RegisterFileInterests[globalName, createDate, voiceList, commentary];
};
GetFileNameAndDate:
PROC [file: Rope.
ROPE, commentary:
IO.
STREAM]
RETURNS [globalName: Rope.
ROPE, createDate: BasicTime.
GMT] ~ {
fullFName, attachedTo: Rope.ROPE;
keep: CARDINAL;
[fullFName: fullFName, attachedTo: attachedTo, keep: keep, created: createDate] ¬ FS.FileInfo[file ! FS.Error => GOTO quit]; -- if there's something wrong with the file, do nothing. Presumably whoever called whatever triggered us will complain.
read FS.Mesa to understand the next few lines
Commentate[IO.PutFR1["File %g:\n", IO.rope[fullFName]], commentary];
IF attachedTo =
NIL THEN {
IF keep # 0 THEN ERROR LocalFile[] -- neither a global file or a local one with global attachment COMMENT THIS OUT FOR PCEDAR
ELSE {
globalName ¬ fullFName; Commentate["File is global:\n", commentary]
}
}
ELSE { globalName ¬ attachedTo; Commentate[IO.PutFR1["File is attached to global file %g:\n", IO.rope[globalName]], commentary] };
};
The following code is very inefficient in the way that it deals with files: it opens the file as a stream to see if it is a Tioga file and then gets a TiogaAccess.Reader on each Tioga file, which means making a complete copy of the file into TiogaAccess.Reader format, and then reads its root properties
Faster code could be produced by using the 'control' rope reader returned by TiogaFileIO.GetParts but that means writing fairly grubby code that knows a lot about the Tioga file format [see FileOps.mesa in Tioga.df if interested]
IsTiogaFile:
PROC [filename: Rope.
ROPE]
RETURNS [tiogaFile:
BOOL] ~ {
fileStream: IO.STREAM;
streamOptions:
FS.StreamOptions ¬
FS.defaultStreamOptions;
in order to determine if some file is a Tioga file, we need to open it as a plain file [hence following line] and use TiogaFileIO.GetParts to determine whether it is of the correct format. GetParts also returns the text, comments and control sections of the documents if we are interested [see comments at the head of this code]
streamOptions[tiogaRead] ¬ FALSE;
fileStream ¬ FS.StreamOpen[fileName: filename, streamOptions: streamOptions];
tiogaFile ¬ TiogaFileIO.GetParts[fileStream].isTioga;
fileStream.Close[];
};
GetVoiceList:
PROC [filename: Rope.
ROPE]
RETURNS [voiceList: Rope.
ROPE] ~ {
fileStream: TiogaAccess.Reader ¬ TiogaAccess.FromFile[filename];
rootChar: TiogaAccess.TiogaChar ¬ TiogaAccess.Get[fileStream]; -- first character produces the root properties
FOR rootProps: Tioga.PropList ¬ rootChar.propList, rootProps.rest
WHILE rootProps #
NIL
DO
IF rootProps.first.key = $voicelist
THEN {
IF voiceList = NIL THEN voiceList ¬ NARROW[rootProps.first.val, Rope.ROPE]
ELSE ERROR }
ENDLOOP
};
RegisterFileInterests:
PROC [filename: Rope.
ROPE, createDate: BasicTime.
GMT, voiceList: Rope.
ROPE, commentary:
IO.
STREAM] ~ {
nextVoice: Rope.ROPE;
startOfID: INT ¬ 1;
endOfID: INT;
Commentate[" Voice message IDs are\n", commentary];
IF NOT ( voiceList.Length > 0 AND voiceList.Fetch[0] = '& ) THEN ERROR;
DO
endOfID ¬ voiceList.Find["&", startOfID];
nextVoice ¬ voiceList.Substr[startOfID, IF endOfID = -1 THEN Rope.MaxLen ELSE endOfID - startOfID];
IF nextVoice.Length = 0 THEN ERROR;
the voiceRope specification in this procedure call is only used to extract the RopeID - the start and length are not used
VoiceRope.Retain[handle: TiogaVoicePrivate.thrushHandle, vr: NEW [VoiceRope.VoiceRopeInterval ¬ [nextVoice, 0, 0]], refID: filename.Cat[" ", Convert.RopeFromTime[createDate]], class: "TiogaVoice"];
Commentate[nextVoice, commentary]; Commentate["\n", commentary];
IF endOfID = -1 THEN RETURN;
startOfID ¬ endOfID + 1
ENDLOOP
};
RegisterVoiceInterestCmd: Commander.CommandProc = {
{
ENABLE {
FS.Error => IF error.group # user THEN REJECT ELSE {msg ¬ error.explanation; GOTO Quit};
LocalFile => {msg ¬ "File must be global or have a global attachment"; GOTO Quit};
};
argv: CommanderOps.ArgumentVector;
argv ¬ CommanderOps.Parse[cmd ! CommanderOps.Failed => {msg ¬ errorMsg; GOTO Quit}];
IF argv.argc # 2
THEN {
msg ¬ "Usage: RegisterVoiceInterest inFile";
GOTO Quit;
};
DoFileInterests[argv[1], cmd.out]
EXITS
Quit => RETURN [$Failure, msg]
BackgroundCommentaryInit: Commander.CommandProc ~ {
backgroundCommentary ¬ cmd.out
};
PCedar
RegisterViewerInterests:
PUBLIC PROC [viewer: ViewerClasses.Viewer, voiceList: Rope.
ROPE] ~ {
Register interests in all voice ropes contained in this viewer. A stopgap for PCedar, since there are currently no file events in PFS. 6 Dec 90 PTZ
Valid in PCedar world only
eventReg: ViewerEvents.EventRegistration;
IF voiceList=
NIL
THEN {
Commentate[" No voice in file\n", backgroundCommentary];
RETURN;
};
ViewerOps.AddProp[viewer, $VoiceList, voiceList];
eventReg ¬ ViewerEvents.RegisterEventProc[proc: DoViewerInterests, event: save, filter: viewer, before:
FALSE];
Need to wait until the save is complete to get the create date.
ViewerOps.AddProp[viewer, $VoiceEventReg, eventReg];
};
DoViewerInterests: ViewerEvents.EventProc ~ {
PROC [viewer: Viewer, event: ViewerEvent, before: BOOL] RETURNS [abort: BOOL ← FALSE]
globalName: Rope.ROPE;
voiceList: Rope.ROPE ¬ NIL;
createDate: BasicTime.GMT;
[globalName, createDate] ¬ GetFileNameAndDate[viewer.file, backgroundCommentary];
voiceList ¬ NARROW[ViewerOps.FetchProp[viewer, $VoiceList]];
IF voiceList#
NIL
THEN
RegisterFileInterests[globalName, createDate, voiceList, backgroundCommentary];
ViewerOps.AddProp[viewer, $VoiceList, NIL];
ViewerEvents.UnRegisterEventProc[proc: ViewerOps.FetchProp[viewer, $VoiceEventReg], event: save];
ViewerOps.AddProp[viewer, $VoiceEventReg, NIL];
};
DCedar
A mechanism to track all files copied to global file space and register interests in any voice contained in them. Uses FS file events, not yet implemented in PCedar.
Since the intent is to only register interests in global files (not on local machine), explicit use of the RegisterVoiceInterest command should never mention a local file. Check in GetFileNameAndDate controls this, SHOULD NOT BE COMMENTED OUT.
lastRemoteEvent: REF READONLY FSBackdoor.RemoteEvent ← NIL;
MonitorCopiesToGlobal: PROC = { -- invoked as a separate process
Process.SetPriority[Process.priorityBackground];
DO
lastRemoteEvent ← FSBackdoor.NextRemoteEvent[lastRemoteEvent];
IF lastRemoteEvent.op = endStoring THEN
DoFileInterests[lastRemoteEvent.fName, backgroundCommentary]
ENDLOOP
};
DCedar initialization
p: PROCESS ← FORK MonitorCopiesToGlobal;
TRUSTED {Process.Detach[p]};