WalnutSendOpsImpl.mesa - More procs for WalnutSend
Copyright © 1984, 1985 by Xerox Corporation. All rights reserved.
Willie-Sue, March 20, 1985 8:30:48 am PST
Russ Atkinson (RRA) March 21, 1985 0:56:04 am PST
DIRECTORY
DefaultRemoteNames USING [Get],
GVAnswer USING [MakeHeader],
GVMailParse,
Menus,
IO,
Rope,
RuntimeError USING [BoundsFault],
TiogaOps USING [ Pattern, Ref, Location, ViewerDoc, CreateSimplePattern, GetSelection, InsertRope, SelectDocument, SelectPoint, SelectionRoot, SelectionSearch, SetSelection, ToPrimary],
ViewerOps,
ViewerClasses,
ViewerTools,
WalnutDocumentRope,
WalnutParseMsg USING [SendParseStatus, Parse],
WalnutSendInternal,
WalnutSendOps;
WalnutSendOpsImpl:
CEDAR
MONITOR
IMPORTS DefaultRemoteNames, IO, Rope, RuntimeError, GVAnswer, GVMailParse, Menus, TiogaOps, ViewerOps, WalnutDocumentRope, WalnutParseMsg, WalnutSendInternal, WalnutSendOps
EXPORTS WalnutSendInternal, WalnutSendOps
SHARES Menus = BEGIN OPEN WalnutSendOps, WalnutSendInternal;
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
TiogaContents: TYPE = ViewerTools.TiogaContents;
Viewer: TYPE = ViewerClasses.Viewer;
nullIndex: INT = LAST[INT];
defaultRegistry: PUBLIC ROPE ← DefaultRemoteNames.Get[].registry;
Answer:
PUBLIC
PROC[msgHeaders:
ROPE, who: Viewer←
NIL]
RETURNS [v: Viewer] = {
notOk: BOOL;
errorIndex: INT;
answer: ROPE;
answerForm: WalnutSendOps.Form;
AnswerGetChar: PROC[pos: INT] RETURNS[CHAR] = {RETURN[msgHeaders.Fetch[pos]]};
[notOk, answer, errorIndex]← GVAnswer.MakeHeader[AnswerGetChar, msgHeaders.Length[],
simpleUserName, defaultRegistry];
IF notOk
THEN {
start, end: INT← errorIndex;
BEGIN
ENABLE RuntimeError.BoundsFault => {start← 0; end← 1;
CONTINUE};
IF start = nullIndex
THEN start← 0
ELSE
{
UNTIL msgHeaders.Fetch[start] = '\n
DO start← start - 1;
ENDLOOP;
start← start + 1};
IF end = nullIndex
THEN end← start + 1
ELSE
{
UNTIL msgHeaders.Fetch[end] = '\n
DO end← end + 1;
ENDLOOP;
end← end - 1};
END;
IF who # NIL THEN ShowErrorFeedback[who, start, end];
SenderReport[
IO.PutFR[
"\nSyntax error in line starting at pos %g (in message being answered)",
IO.int[start]]];
RETURN
};
answerForm ←
NEW[ WalnutSendOps.FormRec ←
[formText: answerText, fields: ParseAnswerHeader[answer] ] ];
v← BuildSendViewer[TRUE, FALSE, answerForm, who];
ClearFileAssoc[v];
GrabFocus[v];
};
ParseAnswerHeader:
PROC[header:
ROPE]
RETURNS[fields:
LIST
OF
ROPE] = {
HeaderLines: ARRAY[0..3] OF Rope.ROPE;
startPos: INT ← Rope.Find[header, ": ", 0];
endOfLine: INT ← Rope.Find[header, "\n", startPos];
FOR i:
INT
IN [0..3]
DO
HeaderLines[i] ← Rope.Substr[header, startPos+2, endOfLine-startPos-2];
startPos ← Rope.Find[header, ": ", endOfLine];
IF startPos = -1 THEN EXIT ELSE endOfLine ← Rope.Find[header, "\n", startPos]
ENDLOOP;
RETURN[ LIST[ HeaderLines[0], HeaderLines[1], HeaderLines[2], HeaderLines[3] ] ]
Forward:
PUBLIC
PROC[msg: Viewer, who: Viewer←
NIL]
RETURNS[v: Viewer] =
TRUSTED {
forwardForm: WalnutSendOps.Form =
NEW[WalnutSendOps.FormRec ← [formText: forwardText, fields: NIL]];
messagePattern: TiogaOps.Pattern = TiogaOps.CreateSimplePattern["ForwardedMessage"];
headerPattern: TiogaOps.Pattern = TiogaOps.CreateSimplePattern["MessageHeader"];
pstart, pend: TiogaOps.Location;
root: TiogaOps.Ref;
found: BOOL;
v← BuildSendViewer[TRUE, FALSE, forwardForm, who];
ClearFileAssoc[v];
TiogaOps.SelectDocument[v];
root ← TiogaOps.SelectionRoot[];
pstart ← TiogaOps.Location[ root, 0 ];
TiogaOps.SelectPoint[ v, pstart ];
found ← TiogaOps.SelectionSearch[pattern: headerPattern];
IF found
THEN
{ [ ,pstart, pend, , , ] ← TiogaOps.GetSelection[];
TiogaOps.SetSelection[viewer: v, start: pstart, end: pend,
level: word, pendingDelete: TRUE];
TiogaOps.InsertRope[ msg.name ] };
found ← TiogaOps.SelectionSearch[pattern: messagePattern];
IF NOT found THEN RETURN;
[ ,pstart, pend, , , ] ← TiogaOps.GetSelection[];
TiogaOps.SetSelection[ viewer: v, start: pstart, end: pend, level: node, pendingDelete: TRUE ];
TiogaOps.SelectDocument[ viewer: msg, which: secondary ];
TiogaOps.ToPrimary[];
UnsetNewVersion[v];
GrabFocus[v]
};
ClearFileAssoc:
PUBLIC
PROC[v: Viewer] = {
IF v.file # NIL THEN v.file← NIL;
v.name← sendCaption;
ViewerOps.PaintViewer[v, caption];
};
SenderNewVersion:
PUBLIC
PROC[viewer: Viewer] = {
OPEN Menus;
menu: Menus.Menu = viewer.menu;
firstForm: Menus.MenuEntry = Menus.GetLine[formsMenu, 0];
getEntry: Menus.MenuEntry← Menus.FindEntry[menu, "Get"];
IF getEntry # NIL THEN Menus.SetGuarded[getEntry, TRUE];
getEntry← Menus.FindEntry[menu, "Default"];
IF getEntry # NIL THEN Menus.SetGuarded[getEntry, TRUE];
getEntry← Menus.FindEntry[menu, "PrevMsg"];
IF getEntry # NIL THEN Menus.SetGuarded[getEntry, TRUE];
FOR i: Menus.MenuLine
IN [1..5)
DO
thisLine: Menus.MenuEntry = Menus.GetLine[menu, i];
IF thisLine = NIL THEN EXIT;
IF Rope.Equal[thisLine.name, firstForm.name] THEN
FOR entry: MenuEntry ← thisLine, entry.link
UNTIL entry =
NIL
DO
SetGuarded[entry, TRUE]
ENDLOOP;
ENDLOOP;
ViewerOps.PaintViewer[viewer, menu]; -- show as guarded
viewer.newVersion← TRUE;
};
UnsetNewVersion:
PUBLIC
PROC[viewer: Viewer] = {
OPEN Menus;
menu: Menu = viewer.menu;
firstForm: Menus.MenuEntry = Menus.GetLine[formsMenu, 0];
getEntry: Menus.MenuEntry← Menus.FindEntry[menu, "Get"];
IF getEntry # NIL THEN Menus.SetGuarded[getEntry, FALSE];
getEntry← Menus.FindEntry[menu, "Default"];
IF getEntry # NIL THEN Menus.SetGuarded[getEntry, FALSE];
getEntry← Menus.FindEntry[menu, "PrevMsg"];
IF getEntry # NIL THEN Menus.SetGuarded[getEntry, FALSE];
FOR i: Menus.MenuLine
IN [1..5)
DO
thisLine: Menus.MenuEntry = Menus.GetLine[menu, i];
IF thisLine = NIL THEN EXIT;
IF Rope.Equal[thisLine.name, firstForm.name] THEN
FOR entry: MenuEntry ← thisLine, entry.link
UNTIL entry =
NIL
DO
SetGuarded[entry, FALSE]
ENDLOOP;
ENDLOOP;
ViewerOps.PaintViewer[viewer, menu]; -- show as unguarded
viewer.newVersion← FALSE;
ViewerOps.PaintViewer[viewer, caption];
};
TiogaTextFromStrm:
PUBLIC
PROC [strm:
IO.
STREAM, startPos:
INT← 0, len:
INT←
LAST[
INT]]
RETURNS [contents: TiogaContents] = {
returns NIL IF endOfStream encountered during read
ENABLE IO.EndOfStream => GOTO TooShort;
fulltext: ROPE ← RopeFromStream[strm, startPos, len];
formatPos: INT ← Rope.Index[fulltext, 0, "\000\000"];
contents← NEW[ViewerTools.TiogaContentsRec];
contents.contents← Rope.Substr[fulltext, 0, formatPos];
IF formatPos < Rope.Length[fulltext] THEN contents.formatting← Rope.Substr[fulltext, formatPos];
EXITS TooShort => RETURN[NIL];
};
RopeFromStream:
PUBLIC
PROC [strm:
STREAM, startPos, len:
INT]
RETURNS [contents:
ROPE] = {
Reads arbitrary length ROPE from a stream. If some caller catches the error, then the caller can diagnose it. Otherwise we do something more or less reasonable here.
rem: INT ← IO.GetLength[strm] - startPos;
IF rem < len THEN len ← rem;
IF len < 2048
THEN
TRUSTED {
Short enough to make in one piece
nat: NAT ← len;
text: Rope.Text ← Rope.NewText[nat];
IO.SetIndex[strm, startPos];
[] ← IO.GetBlock[strm, LOOPHOLE[text], 0, nat];
RETURN [text];
};
The rope is too large to make in one piece, so we divide the length in two and recursviely call ourselves. This gets at least 1024 characters in each piece in a balanced binary tree, so the recursion is quite acceptable even for large messages.
rem ← len/2;
len ← len - rem;
contents ← RopeFromStream[strm, startPos, len];
Do this first to ensure that the evaluation order is OK.
contents ← Rope.Concat[contents, RopeFromStream[strm, startPos+len, rem] ];
};
GetFieldBody:
PUBLIC PROC[text, fieldName:
ROPE]
RETURNS[fieldBody:
ROPE] = {
expects text to be the text of a message; parses the header fields looking for a field named fieldName and returns the value of the fieldBody, or NIL if no such fieldName is found
OPEN GVMailParse;
mPos: INT← 0;
lastCharPos: INT← text.Length[];
pH: GVMailParse.ParseHandle← GVMailParse.InitializeParse[];
NextChar: PROC[] RETURNS [ch: CHAR] =
{
IF mPos > lastCharPos
THEN ch← endOfInput
ELSE ch← text.Fetch[mPos];
mPos← mPos + 1;
};
BEGIN
field: ROPE← NIL;
DO
found, fieldNotRecognized: BOOL← TRUE;
[field, found]←
GVMailParse.GetFieldName[pH, NextChar ! ParseError => GOTO parseErrorExit];
IF ~found THEN EXIT;
IF Rope.Equal[fieldName, field,
FALSE]
THEN
-- ignore case
{ fieldBody← GVMailParse.GetFieldBody[pH, NextChar];
EXIT}
ELSE []← GVMailParse.GetFieldBody[pH, NextChar, TRUE];
ENDLOOP;
GVMailParse.FinalizeParse[pH];
EXITS
parseErrorExit => { GVMailParse.FinalizeParse[pH]; RETURN[NIL]};
END;
};
GetRecipients:
PUBLIC PROC[sender: Viewer]
RETURNS[rList:
LIST
OF
ROPE, parseError:
BOOL] = {
expects sender to contain a message to be sent; will parse the headers and return a list of all the recipients of the message; if an error occurs while parsing the viewer, parseError=TRUE is returned, and the viewer will have the offending field underlined.
text: ROPE;
status: WalnutParseMsg.SendParseStatus;
sPos, mPos: INT;
TRUSTED
{text← WalnutDocumentRope.Create[LOOPHOLE [TiogaOps.ViewerDoc[sender]]]};
[status, sPos, mPos, rList]← WalnutParseMsg.Parse[text];
IF (status # ok) AND (status # includesPublicDL) THEN
BEGIN
SELECT status
FROM
fieldNotAllowed =>
IF sPos # mPos THEN
{ ShowErrorFeedback[sender, sPos, mPos];
SenderReport[Rope.Substr[text, MAX[0, sPos-1], mPos-sPos]];
SenderReport[" field is not allowed\n"]}
ELSE SenderReport[IO.PutFR[" field at pos %g is not allowed\n", IO.int[sPos]]];
syntaxError =>
IF sPos # mPos THEN
{ ShowErrorFeedback[sender, sPos, mPos];
SenderReport["\nSyntax error on line beginning with "];
SenderReport[Rope.Substr[text, MAX[0, sPos-1], mPos-sPos]]}
ELSE SenderReport[IO.PutFR["..... Syntax error at position %g ", IO.int[sPos]]];
includesPrivateDL => SenderReport[" Private dl's are not yet implemented\n"];
ENDCASE => ERROR;
ViewerOps.BlinkIcon[sender, IF sender.iconic THEN 0 ELSE 1];
RETURN[NIL, TRUE]
END;
RETURN[rList, FALSE]
};
END.