Oberon/ETH Oberon/2.3.7/Mail.Mod

(* ETH Oberon, Copyright 1990-2003 Computer Systems Institute, ETH Zurich, CH-8092 Zurich. Refer to the license.txt file provided with this distribution. *) MODULE Mail; (** portable *)	(* ejz, 05.01.03 20.13.24 *) &#9;IMPORT Kernel, Files, Strings, Dates, Objects, Display, Fonts, Texts, Oberon, NetSystem, NetTools, MIME, Streams, &#9;&#9;TextStreams, Display3, Attributes, Links, Gadgets, ListRiders, ListGadgets, AsciiCoder, TextGadgets, TextDocs, Documents, &#9;&#9;Desktops, HyperDocs, MD5, Modules, FileDir, SYSTEM; &#9;CONST &#9;&#9;MsgFile &#61; "MailMessages"; &#9;&#9;IndexFile &#61; "MailMessages.idx"; IndexFileKey&#61;74EF5A0DH; &#9;&#9;DefPOPPort &#61; 110; &#9;&#9;OberonStart* &#61; "--- start of oberon mail ---"; &#9;&#9;BufLen &#61; 4096; &#9;&#9;Read&#61; 0; Deleted &#61; 1; &#9;&#9;SortByDateTime &#61; 1; SortByReplyTo &#61; 2; SortBySubject &#61; 3; &#9;&#9;Version &#61; 0; &#9;&#9;eq &#61; 1; leq &#61; 2; le &#61; 3; geq &#61; 4; ge &#61; 5; neq &#61; 6; or &#61; 7; and &#61; 8; &#9;&#9;from &#61; 20; subject &#61; 21; date &#61; 22; time &#61; 23; text &#61; 24; topic &#61; 25; notopic &#61; 26; readFlag &#61; 27; &#9;&#9;Menu &#61; "Desktops.Copy&#91;Copy&#93; TextDocs.Search&#91;Search&#93; TextDocs.Replace&#91;Rep&#93; Mail.Show&#91;Source&#93; Mail.Reply&#91;Reply&#93; Desktops.StoreDoc&#91;Store&#93;"; &#9;&#9;SysMenu &#61; "Desktops.Copy&#91;Copy&#93; Mail.Reply&#91;Reply&#93; Desktops.StoreDoc&#91;Store&#93;"; &#9;&#9;DefSMTPPort* &#61; 25; &#9;&#9;simpler &#61; TRUE; &#9;TYPE &#9;&#9;UIDL &#61; ARRAY 64 OF CHAR; &#9;&#9;ServerName* &#61; ARRAY HyperDocs.ServerStrLen OF CHAR; &#9;&#9;UserName &#61; ARRAY 64 OF CHAR; &#9;&#9;AdrString* &#61; ARRAY HyperDocs.PathStrLen OF CHAR; &#9;&#9;UIDLList &#61; POINTER TO ARRAY OF UIDL; &#9;&#9;UIDLSet &#61; POINTER TO UIDLSetDesc; &#9;&#9;UIDLSetDesc &#61; RECORD &#9;&#9;&#9;F&#58; Files.File; &#9;&#9;&#9;pop&#58; ServerName; &#9;&#9;&#9;user&#58; UserName; &#9;&#9;&#9;nouidls&#58; LONGINT; &#9;&#9;&#9;uidls&#58; UIDLList; &#9;&#9;&#9;next&#58; UIDLSet &#9;&#9;END; &#9;&#9;MsgHead &#61; RECORD &#9;&#9;&#9;pos, len (* - From head *), state, stamp&#58; LONGINT; &#9;&#9;&#9;flags, topics&#58; SET; &#9;&#9;&#9;date, time&#58; LONGINT; &#9;&#9;&#9;replyTo, subject&#58; LONGINT &#9;&#9;END; &#9;&#9;MsgHeadList &#61; POINTER TO ARRAY OF MsgHead; &#9;&#9;Topic &#61; POINTER TO TopicDesc; &#9;&#9;TopicDesc &#61; RECORD &#9;&#9;&#9;no, state, stamp&#58; LONGINT; &#9;&#9;&#9;topic&#58; ListRiders.String; &#9;&#9;&#9;next&#58; Topic &#9;&#9;END; &#9;&#9;SortList &#61; POINTER TO ARRAY OF LONGINT; &#9;&#9;Rider &#61; POINTER TO RiderDesc; &#9;&#9;RiderDesc &#61; RECORD (ListRiders.RiderDesc) &#9;&#9;&#9;noMsgs&#58; LONGINT; &#9;&#9;&#9;key, pos, sortPos&#58; LONGINT; &#9;&#9;&#9;ascending&#58; BOOLEAN; &#9;&#9;&#9;sort&#58; SortList &#9;&#9;END; &#9;&#9;QueryString &#61; ARRAY 128 OF CHAR; &#9;&#9;ValueString &#61; ARRAY 64 OF CHAR; &#9;&#9;ConnectMsg &#61; RECORD (ListRiders.ConnectMsg) &#9;&#9;&#9;query&#58; QueryString; &#9;&#9;&#9;sortBy&#58; INTEGER; (* SortByDateTime, SortByReplyTo, SortBySubject *) &#9;&#9;&#9;ascending&#58; BOOLEAN &#9;&#9;END; &#9;&#9;TopicRider &#61; POINTER TO TopicRiderDesc; &#9;&#9;TopicRiderDesc &#61; RECORD (ListRiders.RiderDesc) &#9;&#9;&#9;topic&#58; Topic &#9;&#9;END; &#9;&#9;Model &#61; POINTER TO ModelDesc; &#9;&#9;ModelDesc &#61; RECORD (Gadgets.ObjDesc) &#9;&#9;END; &#9;&#9;Frame &#61; POINTER TO FrameDesc; &#9;&#9;FrameDesc &#61; RECORD (ListGadgets.FrameDesc) &#9;&#9;&#9;query, sortBy, ascending&#58; Objects.Object &#9;&#9;END; &#9;&#9;Cond &#61; POINTER TO CondDesc; &#9;&#9;CondDesc &#61; RECORD &#9;&#9;&#9;val&#58; ValueString; &#9;&#9;&#9;date, time&#58; LONGINT; &#9;&#9;&#9;op, field&#58; LONGINT; &#9;&#9;&#9;value, eval&#58; BOOLEAN; &#9;&#9;&#9;next&#58; Cond &#9;&#9;END; &#9;&#9;Node &#61; POINTER TO NodeDesc; &#9;&#9;NodeDesc &#61; RECORD (CondDesc) &#9;&#9;&#9;left, right&#58; Cond &#9;&#9;END; &#9;&#9;Query &#61; RECORD &#9;&#9;&#9;query&#58; QueryString; &#9;&#9;&#9;conds, root&#58; Cond; &#9;&#9;&#9;error&#58; BOOLEAN &#9;&#9;END; &#9;&#9;SMTPSession* &#61; POINTER TO SMTPSessionDesc; &#9;&#9;SMTPSessionDesc* &#61; RECORD (NetTools.SessionDesc) &#9;&#9;&#9;from*&#58; AdrString &#9;&#9;END; &#9;&#9;Buffer &#61; POINTER TO ARRAY OF CHAR; &#9;&#9;Index &#61; POINTER TO ARRAY OF LONGINT; &#9;&#9;Heap &#61; RECORD &#9;&#9;&#9;&#9;buffer&#58; Buffer; bufLen&#58; LONGINT; &#9;&#9;&#9;&#9;index&#58; Index; idxLen&#58; LONGINT &#9;&#9;&#9;END; &#9;VAR &#9;&#9;msgs&#58; MsgHeadList; &#9;&#9;noMsgs, delMsgs&#58; LONGINT; &#9;&#9;msgsF&#58; Files.File; &#9;&#9;msgList&#58; Model; &#9;&#9;heap&#58; Heap; &#9;&#9;topicList&#58; Model; &#9;&#9;topics&#58; Topic; &#9;&#9;uidls&#58; UIDLSet; &#9;&#9;lastUIDL&#58; LONGINT; &#9;&#9;W&#58; Texts.Writer; &#9;&#9;mMethod, tmMethod&#58; ListRiders.Method; &#9;&#9;vMethod&#58; ListGadgets.Method; &#9;&#9;textFnt, headFnt, fieldFnt&#58; Fonts.Font; &#9;&#9;mailer&#58; ValueString; &#9;&#9;count&#58; LONGINT; &#9;&#9;trace&#58; BOOLEAN; (* String Heap *) &#9;PROCEDURE Open(VAR heap&#58; Heap); &#9;BEGIN &#9;&#9;NEW(heap.buffer, 512); heap.bufLen &#58;&#61; 0; &#9;&#9;NEW(heap.index, 64); heap.idxLen &#58;&#61; 0 &#9;END Open; &#9;PROCEDURE Append(VAR heap&#58; Heap; idx&#58; LONGINT; VAR str&#58; ARRAY OF CHAR); &#9;&#9;VAR buffer&#58; Buffer; index&#58; Index; len, i, j&#58; LONGINT; &#9;BEGIN &#9;&#9;len &#58;&#61; heap.idxLen; INC(heap.idxLen); &#9;&#9;IF heap.idxLen &#62;&#61; LEN(heap.index^) THEN &#9;&#9;&#9;NEW(index, 2*heap.idxLen); &#9;&#9;&#9;IF len &#62; 0 THEN &#9;&#9;&#9;&#9;SYSTEM.MOVE(SYSTEM.ADR(heap.index&#91;0&#93;), SYSTEM.ADR(index&#91;0&#93;), len*SIZE(LONGINT)) &#9;&#9;&#9;END; &#9;&#9;&#9;heap.index &#58;&#61; index &#9;&#9;END; &#9;&#9;WHILE len &#62; idx DO &#9;&#9;&#9;heap.index&#91;len&#93; &#58;&#61; heap.index&#91;len-1&#93;; DEC(len) &#9;&#9;END; &#9;&#9;heap.index&#91;idx&#93; &#58;&#61; heap.bufLen; &#9;&#9;IF (heap.bufLen+LEN(str)) &#62;&#61; LEN(heap.buffer^) THEN &#9;&#9;&#9;NEW(buffer, 2*(heap.bufLen+LEN(str))); &#9;&#9;&#9;SYSTEM.MOVE(SYSTEM.ADR(heap.buffer&#91;0&#93;), SYSTEM.ADR(buffer&#91;0&#93;), heap.bufLen*SIZE(CHAR)); &#9;&#9;&#9;heap.buffer &#58;&#61; buffer &#9;&#9;END; &#9;&#9;i &#58;&#61; 0; j &#58;&#61; heap.bufLen; &#9;&#9;WHILE str&#91;i&#93; # 0X DO &#9;&#9;&#9;heap.buffer&#91;j&#93; &#58;&#61; str&#91;i&#93;; &#9;&#9;&#9;INC(i); INC(j) &#9;&#9;END; &#9;&#9;heap.buffer&#91;j&#93; &#58;&#61; 0X; heap.bufLen &#58;&#61; j+1 &#9;END Append; &#9;PROCEDURE Compare(VAR heap&#58; Heap; ofs&#58; LONGINT; VAR str&#58; ARRAY OF CHAR)&#58; LONGINT; &#9;&#9;VAR i&#58; LONGINT; cb, cs&#58; CHAR; &#9;BEGIN &#9;&#9;cb &#58;&#61; heap.buffer&#91;ofs&#93;; &#9;&#9;i &#58;&#61; 0; cs &#58;&#61; str&#91;0&#93;; &#9;&#9;WHILE (cb # 0X) &#38; (cs # 0X) &#38; (cb &#61; cs) DO &#9;&#9;&#9;INC(ofs); cb &#58;&#61; heap.buffer&#91;ofs&#93;; &#9;&#9;&#9;INC(i); cs &#58;&#61; str&#91;i&#93; &#9;&#9;END; &#9;&#9;RETURN ORD(cb)-ORD(cs) &#9;END Compare; &#9;PROCEDURE Insert(VAR heap&#58; Heap; str&#58; ARRAY OF CHAR; VAR ofs&#58; LONGINT); &#9;&#9;VAR l, r, m, c, idx&#58; LONGINT; &#9;BEGIN &#9;&#9;l &#58;&#61; 0; r &#58;&#61; heap.idxLen-1; c &#58;&#61; 1; idx &#58;&#61; 0; &#9;&#9;WHILE (l &#60;&#61; r) &#38; (c # 0) DO &#9;&#9;&#9;m &#58;&#61; (l+r) DIV 2; idx &#58;&#61; m; &#9;&#9;&#9;c &#58;&#61; Compare(heap, heap.index&#91;m&#93;, str); &#9;&#9;&#9;IF c &#60; 0 THEN &#9;&#9;&#9;&#9;l &#58;&#61; m+1 &#9;&#9;&#9;ELSIF c &#62; 0 THEN &#9;&#9;&#9;&#9;r &#58;&#61; m-1 &#9;&#9;&#9;END &#9;&#9;END; &#9;&#9;IF c # 0 THEN &#9;&#9;&#9;IF c &#60; 0 THEN INC(idx) END; &#9;&#9;&#9;Append(heap, idx, str) &#9;&#9;END; &#9;&#9;ofs &#58;&#61; heap.index&#91;idx&#93; &#9;END Insert; &#9;PROCEDURE Copy(VAR heap&#58; Heap; ofs&#58; LONGINT; VAR str&#58; ARRAY OF CHAR); &#9;&#9;VAR i, l&#58; LONGINT; &#9;BEGIN &#9;&#9;i &#58;&#61; 0; l &#58;&#61; LEN(str)-1; &#9;&#9;WHILE (heap.buffer&#91;ofs&#93; # 0X) &#38; (i &#60; l) DO &#9;&#9;&#9;str&#91;i&#93; &#58;&#61; heap.buffer&#91;ofs&#93;; INC(i); INC(ofs) &#9;&#9;END; &#9;&#9;str&#91;i&#93; &#58;&#61; 0X &#9;END Copy; &#9;PROCEDURE Store(VAR R&#58; Files.Rider; VAR heap&#58; Heap); &#9;&#9;VAR i&#58; LONGINT; &#9;BEGIN &#9;&#9;Files.WriteLInt(R, heap.bufLen); &#9;&#9;Files.WriteBytes(R, heap.buffer^, heap.bufLen); &#9;&#9;Files.WriteLInt(R, heap.idxLen); &#9;&#9;i &#58;&#61; 0; &#9;&#9;WHILE i &#60; heap.idxLen DO &#9;&#9;&#9;Files.WriteLInt(R, heap.index&#91;i&#93;); INC(i) &#9;&#9;END &#9;END Store; &#9;PROCEDURE Load(VAR R&#58; Files.Rider; VAR heap&#58; Heap); &#9;&#9;VAR i&#58; LONGINT; &#9;BEGIN &#9;&#9;Files.ReadLInt(R, heap.bufLen); &#9;&#9;NEW(heap.buffer, heap.bufLen); &#9;&#9;Files.ReadBytes(R, heap.buffer^, heap.bufLen); &#9;&#9;Files.ReadLInt(R, heap.idxLen); &#9;&#9;NEW(heap.index, heap.idxLen); &#9;&#9;i &#58;&#61; 0; &#9;&#9;WHILE i &#60; heap.idxLen DO &#9;&#9;&#9;Files.ReadLInt(R, heap.index&#91;i&#93;); INC(i) &#9;&#9;END &#9;END Load; &#9;PROCEDURE NrToArg(nr&#58; LONGINT; VAR arg&#58; ARRAY OF CHAR); &#9;BEGIN &#9;&#9;IF nr &#62; 9 THEN &#9;&#9;&#9;Strings.IntToStr(nr, arg) &#9;&#9;ELSE &#9;&#9;&#9;arg&#91;0&#93; &#58;&#61; " "; arg&#91;1&#93; &#58;&#61; CHR(nr+ORD("0")); arg&#91;2&#93; &#58;&#61; 0X &#9;&#9;END &#9;END NrToArg; &#9;PROCEDURE SendCmd(S&#58; NetTools.Session; cmd, arg&#58; ARRAY OF CHAR); &#9;BEGIN &#9;&#9;IF trace THEN &#9;&#9;&#9;Texts.WriteString(W, "SND&#58; "); Texts.WriteString(W, cmd); &#9;&#9;&#9;IF arg # "" THEN &#9;&#9;&#9;&#9;Texts.Write(W, " "); &#9;&#9;&#9;&#9;IF cmd # "PASS" THEN Texts.WriteString(W, arg) ELSE Texts.WriteString(W, "****") END &#9;&#9;&#9;END; &#9;&#9;&#9;Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf) &#9;&#9;END; &#9;&#9;NetTools.SendString(S.C, cmd); &#9;&#9;IF arg # "" THEN &#9;&#9;&#9;NetSystem.Write(S.C, " ") &#9;&#9;END; &#9;&#9;NetSystem.WriteString(S.C, arg) &#9;END SendCmd; &#9;PROCEDURE ReadState(S&#58; NetTools.Session)&#58; BOOLEAN; &#9;BEGIN &#9;&#9;NetSystem.ReadString(S.C, S.reply); &#9;&#9;IF trace THEN &#9;&#9;&#9;Texts.WriteString(W, "RCV&#58; "); Texts.WriteString(W, S.reply); &#9;&#9;&#9;Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf) &#9;&#9;END; &#9;&#9;IF S.reply&#91;0&#93; &#61; "+" THEN &#9;&#9;&#9;S.status &#58;&#61; NetTools.Done; S.res &#58;&#61; NetTools.Done &#9;&#9;ELSE &#9;&#9;&#9;S.status &#58;&#61; NetTools.Failed; S.res &#58;&#61; NetTools.Failed &#9;&#9;END; &#9;&#9;RETURN S.status &#61; NetTools.Done &#9;END ReadState; &#9;PROCEDURE ClosePOP(S&#58; NetTools.Session); &#9;BEGIN &#9;&#9;IF S.C # NIL THEN &#9;&#9;&#9;SendCmd(S, "QUIT", ""); &#9;&#9;&#9;S.res &#58;&#61; NetTools.Done; &#9;&#9;&#9;NetTools.Disconnect(S.C); S.C &#58;&#61; NIL; S.S &#58;&#61; NIL &#9;&#9;ELSE &#9;&#9;&#9;S.res &#58;&#61; NetTools.Failed &#9;&#9;END &#9;END ClosePOP; &#9;PROCEDURE APOP(S&#58; NetTools.Session; user, passwd&#58; ARRAY OF CHAR); &#9;&#9;VAR &#9;&#9;&#9;cont&#58; MD5.Context; &#9;&#9;&#9;digest&#58; MD5.Digest; &#9;&#9;&#9;stamp, login&#58; ARRAY 128 OF CHAR; &#9;&#9;&#9;i, j&#58; LONGINT; &#9;BEGIN &#9;&#9;i &#58;&#61; 0; &#9;&#9;WHILE (S.reply&#91;i&#93; # 0X) &#38; (S.reply&#91;i&#93; # "&#60;") DO &#9;&#9;&#9;INC(i) &#9;&#9;END; &#9;&#9;j &#58;&#61; 0; &#9;&#9;WHILE (S.reply&#91;i&#93; # 0X) &#38; (S.reply&#91;i&#93; # "&#62;") DO &#9;&#9;&#9;stamp&#91;j&#93; &#58;&#61; S.reply&#91;i&#93;; INC(i); INC(j) &#9;&#9;END; &#9;&#9;stamp&#91;j&#93; &#58;&#61; "&#62;"; stamp&#91;j+1&#93; &#58;&#61; 0X; &#9;&#9;cont &#58;&#61; MD5.New; &#9;&#9;MD5.WriteBytes(cont, stamp, Strings.Length(stamp)); &#9;&#9;MD5.WriteBytes(cont, passwd, Strings.Length(passwd)); &#9;&#9;MD5.Close(cont, digest); &#9;&#9;MD5.ToString(digest, stamp); &#9;&#9;COPY(user, login); Strings.AppendCh(login, " "); Strings.Append(login, stamp); &#9;&#9;SendCmd(S, "APOP", login) &#9;END APOP; &#9;PROCEDURE OpenPOP(VAR S&#58; NetTools.Session; host, user, passwd&#58; ARRAY OF CHAR; port&#58; INTEGER; apop&#58; BOOLEAN); &#9;&#9;VAR hostIP&#58; NetSystem.IPAdr; login&#58; BOOLEAN; &#9;BEGIN &#9;&#9;IF trace THEN &#9;&#9;&#9;Texts.WriteString(W, "--- POP"); &#9;&#9;&#9;Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf) &#9;&#9;END; &#9;&#9;IF (port &#60;&#61; 0) OR (port &#62;&#61; 10000) THEN &#9;&#9;&#9;port &#58;&#61; DefPOPPort &#9;&#9;END; &#9;&#9;NEW(S); &#9;&#9;IF (host&#91;0&#93; # "&#60;") &#38; (host&#91;0&#93; # 0X) &#38; (user&#91;0&#93; # 0X) &#38; (passwd&#91;0&#93; # 0X) THEN &#9;&#9;&#9;NetSystem.GetIP(host, hostIP); &#9;&#9;&#9;IF NetTools.Connect(S.C, port, host, FALSE) THEN &#9;&#9;&#9;&#9;S.S &#58;&#61; NetTools.OpenStream(S.C); &#9;&#9;&#9;&#9;IF ReadState(S) THEN &#9;&#9;&#9;&#9;&#9;login &#58;&#61; TRUE; &#9;&#9;&#9;&#9;&#9;IF apop THEN &#9;&#9;&#9;&#9;&#9;&#9;APOP(S, user, passwd) &#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;SendCmd(S, "USER", user); &#9;&#9;&#9;&#9;&#9;&#9;IF ReadState(S) THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;SendCmd(S, "PASS", passwd) &#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;login &#58;&#61; FALSE &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;IF login THEN &#9;&#9;&#9;&#9;&#9;&#9;IF ReadState(S) THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;S.reply &#58;&#61; "connected"; S.res &#58;&#61; NetTools.Done; &#9;&#9;&#9;&#9;&#9;&#9;&#9;RETURN &#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;NetSystem.DelPassword("pop", host, user) &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;ELSIF S.reply&#91;0&#93; &#61; 0X THEN &#9;&#9;&#9;&#9;&#9;S.reply &#58;&#61; "timed out" &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;ClosePOP(S) &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;S.reply &#58;&#61; "no connection" &#9;&#9;&#9;END &#9;&#9;ELSE &#9;&#9;&#9;IF (host&#91;0&#93; &#61; "&#60;") OR (host&#91;0&#93; &#61; 0X) THEN &#9;&#9;&#9;&#9;S.reply &#58;&#61; "no pop-host specified" &#9;&#9;&#9;ELSIF user&#91;0&#93; &#61; 0X THEN &#9;&#9;&#9;&#9;S.reply &#58;&#61; "no pop user set" &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;S.reply &#58;&#61; "no pop password set" &#9;&#9;&#9;END &#9;&#9;END; &#9;&#9;S.res &#58;&#61; NetTools.Failed; S.C &#58;&#61; NIL; S.S &#58;&#61; NIL &#9;END OpenPOP; &#9;PROCEDURE ReadText(S&#58; NetTools.Session; VAR R&#58; Files.Rider); &#9;&#9;VAR &#9;&#9;&#9;buffer&#58; ARRAY BufLen OF CHAR; &#9;&#9;&#9;len, rlen, i, offs&#58; LONGINT; &#9;&#9;&#9;state&#58; INTEGER; &#9;&#9;&#9;ch, old&#58; CHAR; &#9;BEGIN &#9;&#9;old &#58;&#61; 0X; offs &#58;&#61; 1; &#9;&#9;len &#58;&#61; NetSystem.Available(S.C); &#9;&#9;state &#58;&#61; NetSystem.State(S.C); &#9;&#9;WHILE (len &#62; 0) OR (state &#61; NetSystem.inout) DO &#9;&#9;&#9;IF len &#62; (BufLen-2) THEN &#9;&#9;&#9;&#9;rlen &#58;&#61; BufLen-2 &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;rlen &#58;&#61; len &#9;&#9;&#9;END; &#9;&#9;&#9;NetSystem.ReadBytes(S.C, 0, rlen, buffer); &#9;&#9;&#9;i &#58;&#61; 0; &#9;&#9;&#9;WHILE i &#60; rlen DO &#9;&#9;&#9;&#9;ch &#58;&#61; buffer&#91;i&#93;; &#9;&#9;&#9;&#9;IF ch &#61; Strings.CR THEN &#9;&#9;&#9;&#9;&#9;Files.Write(R, ch); &#9;&#9;&#9;&#9;&#9;IF (offs &#61; 2) &#38; (old &#61; ".") THEN &#9;&#9;&#9;&#9;&#9;&#9;Files.Write(R, Strings.LF); &#9;&#9;&#9;&#9;&#9;&#9;RETURN &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;offs &#58;&#61; 0 &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;IF (offs &#62; 0) OR (ch # ".") THEN &#9;&#9;&#9;&#9;&#9;&#9;Files.Write(R, ch) &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;INC(offs) &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;old &#58;&#61; ch; INC(i) &#9;&#9;&#9;END; &#9;&#9;&#9;DEC(len, rlen); &#9;&#9;&#9;IF len &#60;&#61; 0 THEN &#9;&#9;&#9;&#9;len &#58;&#61; NetSystem.Available(S.C); &#9;&#9;&#9;&#9;state &#58;&#61; NetSystem.State(S.C) &#9;&#9;&#9;END &#9;&#9;END &#9;END ReadText; &#9;PROCEDURE DeleteMail(S&#58; NetTools.Session; no&#58; LONGINT); &#9;&#9;VAR arg&#58; ARRAY 12 OF CHAR; &#9;BEGIN &#9;&#9;NrToArg(no, arg); SendCmd(S, "DELE", arg); &#9;&#9;IF &#126;ReadState(S) THEN &#9;&#9;END &#9;END DeleteMail; &#9;PROCEDURE ReceiveMail(S&#58; NetTools.Session; no&#58; LONGINT; VAR R&#58; Files.Rider); &#9;&#9;VAR arg&#58; ARRAY 12 OF CHAR; &#9;BEGIN &#9;&#9;NrToArg(no, arg); SendCmd(S, "RETR", arg); &#9;&#9;IF ReadState(S) THEN &#9;&#9;&#9;ReadText(S, R) &#9;&#9;END &#9;END ReceiveMail; &#9;PROCEDURE MessageSize(S&#58; NetTools.Session; no&#58; LONGINT)&#58; LONGINT; &#9;&#9;VAR &#9;&#9;&#9;arg&#58; ARRAY 12 OF CHAR; &#9;&#9;&#9;size&#58; LONGINT; &#9;&#9;&#9;i&#58; INTEGER; &#9;BEGIN &#9;&#9;NrToArg(no, arg); SendCmd(S, "LIST", arg); &#9;&#9;IF ReadState(S) THEN &#9;&#9;&#9;i &#58;&#61; 4; Strings.StrToIntPos(S.reply, size, i); &#9;&#9;&#9;Strings.StrToIntPos(S.reply, size, i) &#9;&#9;ELSE &#9;&#9;&#9;size &#58;&#61; 0 &#9;&#9;END; &#9;&#9;RETURN size &#9;END MessageSize; &#9;PROCEDURE GetUIDLs(S&#58; NetTools.Session; VAR T&#58; Texts.Text); &#9;&#9;VAR &#9;&#9;&#9;F&#58; Files.File; &#9;&#9;&#9;R&#58; Files.Rider; &#9;BEGIN &#9;&#9;SendCmd(S, "UIDL", ""); &#9;&#9;IF ReadState(S) THEN &#9;&#9;&#9;F &#58;&#61; Files.New(""); Files.Set(R, F, 0); &#9;&#9;&#9;ReadText(S, R); &#9;&#9;&#9;NEW(T); Texts.LoadAscii(T, F) &#9;&#9;ELSE &#9;&#9;&#9;T &#58;&#61; NIL &#9;&#9;END &#9;END GetUIDLs; &#9;PROCEDURE ValidChar(ch&#58; CHAR)&#58; BOOLEAN; &#9;BEGIN &#9;&#9;RETURN ((CAP(ch) &#62;&#61; "A") &#38; (CAP(ch) &#60;&#61; "Z")) OR ((ch &#62;&#61; "0") &#38; (ch &#60;&#61; "9")) OR (ch &#61; ".") &#9;END ValidChar; &#9;PROCEDURE UIDLFile(VAR pop, user&#58; ARRAY OF CHAR; new&#58; BOOLEAN)&#58; Files.File; &#9;&#9;VAR &#9;&#9;&#9;F&#58; Files.File; &#9;&#9;&#9;name&#58; FileDir.FileName; &#9;&#9;&#9;num&#58; ARRAY 20 OF CHAR; &#9;&#9;&#9;ip&#58; NetSystem.IPAdr; &#9;&#9;&#9;i&#58; LONGINT; &#9;BEGIN &#9;&#9;NetSystem.GetIP(pop, ip);	(* assume server has a single IP address *) &#9;&#9;NetSystem.ToNum(ip, num); &#9;&#9;name &#58;&#61; "UIDL."; &#9;&#9;Strings.Append(name, num); &#9;&#9;Strings.AppendCh(name, "."); &#9;&#9;Strings.Append(name, user); &#9;&#9;i &#58;&#61; 0; &#9;&#9;WHILE name&#91;i&#93; # 0X DO &#9;&#9;&#9;IF &#126;ValidChar(name&#91;i&#93;) THEN name&#91;i&#93; &#58;&#61; "X" END; &#9;&#9;&#9;INC(i) &#9;&#9;END; &#9;&#9;F &#58;&#61; Files.Old(name); &#9;&#9;IF F # NIL THEN &#9;&#9;&#9;Files.GetName(F, name) &#9;&#9;END; &#9;&#9;IF new OR (F &#61; NIL) THEN &#9;&#9;&#9;F &#58;&#61; Files.New(name); Files.Register(F) &#9;&#9;END; &#9;&#9;RETURN F &#9;END UIDLFile; &#9;PROCEDURE GetUIDLSet(VAR pop, user&#58; ARRAY OF CHAR)&#58; UIDLSet; &#9;&#9;VAR &#9;&#9;&#9;set&#58; UIDLSet; &#9;&#9;&#9;uidll&#58; UIDLList; &#9;&#9;&#9;R&#58; Files.Rider; &#9;&#9;&#9;i, j, l&#58; LONGINT; &#9;BEGIN &#9;&#9;set &#58;&#61; uidls; &#9;&#9;WHILE (set # NIL) &#38; &#126;((set.pop &#61; pop) &#38; (set.user &#61; user)) DO &#9;&#9;&#9;set &#58;&#61; set.next &#9;&#9;END; &#9;&#9;IF set &#61; NIL THEN &#9;&#9;&#9;NEW(set); set.next &#58;&#61; uidls; uidls &#58;&#61; set; &#9;&#9;&#9;COPY(pop, set.pop); COPY(user, set.user); &#9;&#9;&#9;NEW(set.uidls, 128); l &#58;&#61; 128; &#9;&#9;&#9;set.F &#58;&#61; UIDLFile(pop, user, FALSE); &#9;&#9;&#9;IF Files.Length(set.F) &#60;&#61; 0 THEN &#9;&#9;&#9;&#9;set.nouidls &#58;&#61; 0 &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;Files.Set(R, set.F, 0); i &#58;&#61; 0; &#9;&#9;&#9;&#9;Files.ReadString(R, set.uidls&#91;i&#93;); &#9;&#9;&#9;&#9;WHILE &#126;R.eof DO &#9;&#9;&#9;&#9;&#9;INC(i); &#9;&#9;&#9;&#9;&#9;IF i &#62;&#61; l THEN &#9;&#9;&#9;&#9;&#9;&#9;NEW(uidll, l+128); &#9;&#9;&#9;&#9;&#9;&#9;FOR j &#58;&#61; 0 TO l-1 DO &#9;&#9;&#9;&#9;&#9;&#9;&#9;uidll&#91;j&#93; &#58;&#61; set.uidls&#91;j&#93; &#9;&#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;&#9;INC(l, 128); set.uidls &#58;&#61; uidll &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;Files.ReadString(R, set.uidls&#91;i&#93;) &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;set.nouidls &#58;&#61; i &#9;&#9;&#9;END &#9;&#9;ELSIF set.F &#61; NIL THEN &#9;&#9;&#9;set.F &#58;&#61; UIDLFile(pop, user, TRUE) &#9;&#9;END; &#9;&#9;lastUIDL &#58;&#61; 0; &#9;&#9;RETURN set &#9;END GetUIDLSet; &#9;PROCEDURE NewUIDLSet(VAR pop, user&#58; ARRAY OF CHAR)&#58; UIDLSet; &#9;&#9;VAR set&#58; UIDLSet; &#9;BEGIN &#9;&#9;NEW(set); set.next &#58;&#61; NIL; &#9;&#9;COPY(pop, set.pop); COPY(user, set.user); &#9;&#9;NEW(set.uidls, 128); set.nouidls &#58;&#61; 0; &#9;&#9;set.F &#58;&#61; UIDLFile(pop, user, TRUE); &#9;&#9;RETURN set &#9;END NewUIDLSet; &#9;PROCEDURE AddUIDL(set&#58; UIDLSet; VAR uidl&#58; UIDL); &#9;&#9;VAR &#9;&#9;&#9;R&#58; Files.Rider; &#9;&#9;&#9;uidll&#58; UIDLList; &#9;&#9;&#9;i, l&#58; LONGINT; &#9;BEGIN &#9;&#9;Files.Set(R, set.F, Files.Length(set.F)); &#9;&#9;Files.WriteString(R, uidl); &#9;&#9;l &#58;&#61; LEN(set.uidls^); &#9;&#9;IF l &#60;&#61; set.nouidls THEN &#9;&#9;&#9;NEW(uidll, 2*l); &#9;&#9;&#9;FOR i &#58;&#61; 0 TO l-1 DO &#9;&#9;&#9;&#9;uidll&#91;i&#93; &#58;&#61; set.uidls&#91;i&#93; &#9;&#9;&#9;END; &#9;&#9;&#9;set.uidls &#58;&#61; uidll &#9;&#9;END; &#9;&#9;set.uidls&#91;set.nouidls&#93; &#58;&#61; uidl; &#9;&#9;INC(set.nouidls) &#9;END AddUIDL; &#9;PROCEDURE ExistsUIDL(set&#58; UIDLSet; VAR uidl&#58; UIDL)&#58; LONGINT; &#9;&#9;VAR &#9;&#9;&#9;nouidls, i&#58; LONGINT; &#9;&#9;&#9;uidls&#58; UIDLList; &#9;BEGIN &#9;&#9;nouidls &#58;&#61; set.nouidls; uidls &#58;&#61; set.uidls; &#9;&#9;i &#58;&#61; lastUIDL; &#9;&#9;WHILE (i &#60; nouidls) &#38; (uidls&#91;i&#93; # uidl) DO &#9;&#9;&#9;INC(i) &#9;&#9;END; &#9;&#9;IF i &#62;&#61; nouidls THEN &#9;&#9;&#9;i &#58;&#61; 0; &#9;&#9;&#9;WHILE (i &#60; lastUIDL) &#38; (uidls&#91;i&#93; # uidl) DO &#9;&#9;&#9;&#9;INC(i) &#9;&#9;&#9;END; &#9;&#9;&#9;IF i &#60; lastUIDL THEN &#9;&#9;&#9;&#9;RETURN i &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;RETURN -1 &#9;&#9;&#9;END &#9;&#9;ELSE &#9;&#9;&#9;lastUIDL &#58;&#61; i+1; &#9;&#9;&#9;RETURN i &#9;&#9;END &#9;END ExistsUIDL; &#9;PROCEDURE FlushUIDL(set&#58; UIDLSet); &#9;BEGIN &#9;&#9;IF set.F # NIL THEN &#9;&#9;&#9;Files.Close(set.F); set.F &#58;&#61; NIL &#9;&#9;END &#9;END FlushUIDL; &#9;PROCEDURE ParseContent*(h&#58; MIME.Header; VAR cont&#58; MIME.Content); &#9;&#9;VAR val&#58; ValueString; pos&#58; LONGINT; &#9;BEGIN &#9;&#9;cont &#58;&#61; NIL; &#9;&#9;pos &#58;&#61; MIME.FindField(h, "X-Content-Type"); &#9;&#9;IF pos &#62; 0 THEN &#9;&#9;&#9;MIME.ExtractContentType(h, pos, cont); &#9;&#9;&#9;IF cont.typ.typ &#61; "application" THEN &#9;&#9;&#9;&#9;COPY(cont.typ.subTyp, val); &#9;&#9;&#9;&#9;IF Strings.CAPPrefix("oberon", val) THEN &#9;&#9;&#9;&#9;&#9;cont.encoding &#58;&#61; MIME.EncAsciiCoder &#9;&#9;&#9;&#9;ELSIF Strings.CAPPrefix("compressed/oberon", val) THEN &#9;&#9;&#9;&#9;&#9;cont.encoding &#58;&#61; MIME.EncAsciiCoderC &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;cont &#58;&#61; NIL &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;cont &#58;&#61; NIL &#9;&#9;&#9;END &#9;&#9;END; &#9;&#9;IF cont &#61; NIL THEN &#9;&#9;&#9;pos &#58;&#61; MIME.FindField(h, "Content-Type"); &#9;&#9;&#9;IF pos &#60; 0 THEN &#9;&#9;&#9;&#9;pos &#58;&#61; MIME.FindField(h, "X-Content-Type") &#9;&#9;&#9;END; &#9;&#9;&#9;IF pos &#62; 0 THEN &#9;&#9;&#9;&#9;MIME.ExtractContentType(h, pos, cont); &#9;&#9;&#9;&#9;IF cont.typ.typ &#61; "text" THEN &#9;&#9;&#9;&#9;&#9;pos &#58;&#61; MIME.FindField(h, "Content-Transfer-Encoding"); &#9;&#9;&#9;&#9;&#9;MIME.TextEncoding(h, pos, cont) &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;NEW(cont); cont.typ &#58;&#61; MIME.GetContentType("text/plain"); &#9;&#9;&#9;&#9;IF MIME.FindField(h, "X-Sun-Charset") &#62; 0 THEN &#9;&#9;&#9;&#9;&#9;cont.encoding &#58;&#61; MIME.Enc8Bit &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;cont.encoding &#58;&#61; MIME.EncBin &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END &#9;&#9;END; &#9;&#9;cont.len &#58;&#61; MAX(LONGINT) &#9;END ParseContent; &#9;PROCEDURE AddMsgHead(pos&#58; LONGINT); &#9;&#9;VAR &#9;&#9;&#9;S&#58; Streams.Stream; &#9;&#9;&#9;h&#58; MIME.Header; &#9;&#9;&#9;cont&#58; MIME.Content; &#9;&#9;&#9;nmsgs&#58; MsgHeadList; &#9;&#9;&#9;len, i, v&#58; LONGINT; &#9;&#9;&#9;str&#58; ARRAY BufLen OF CHAR; &#9;BEGIN &#9;&#9;S &#58;&#61; Streams.OpenFileReader(msgsF, pos); &#9;&#9;MIME.ReadHeader(S, NIL, h, len); &#9;&#9;ParseContent(h, cont); &#9;&#9;len &#58;&#61; LEN(msgs^); &#9;&#9;IF noMsgs &#62;&#61; len THEN &#9;&#9;&#9;NEW(nmsgs, 2*len); &#9;&#9;&#9;FOR i &#58;&#61; 0 TO noMsgs-1 DO &#9;&#9;&#9;&#9;nmsgs&#91;i&#93; &#58;&#61; msgs&#91;i&#93; &#9;&#9;&#9;END; &#9;&#9;&#9;msgs &#58;&#61; nmsgs &#9;&#9;END; &#9;&#9;msgs&#91;noMsgs&#93;.pos &#58;&#61; pos; &#9;&#9;msgs&#91;noMsgs&#93;.state&#58;&#61; 0; &#9;&#9;msgs&#91;noMsgs&#93;.stamp &#58;&#61; 0; &#9;&#9;msgs&#91;noMsgs&#93;.len &#58;&#61; -1; &#9;&#9;pos &#58;&#61; MIME.FindField(h, "Reply-To"); &#9;&#9;IF pos &#62;&#61; 0 THEN &#9;&#9;&#9;MIME.ExtractEMail(h, pos, str); &#9;&#9;&#9;IF str &#61; "" THEN pos &#58;&#61; -1 END &#9;&#9;END; &#9;&#9;IF pos &#60; 0 THEN &#9;&#9;&#9;pos &#58;&#61; MIME.FindField(h, "From") &#9;&#9;END; &#9;&#9;(* ASSERT(pos &#62; 0); *) &#9;&#9;MIME.ExtractEMail(h, pos, str); &#9;&#9;Insert(heap, str, msgs&#91;noMsgs&#93;.replyTo); &#9;&#9;pos &#58;&#61; MIME.FindField(h, "Date"); &#9;&#9;MIME.ExtractGMTDate(h, pos, msgs&#91;noMsgs&#93;.time, msgs&#91;noMsgs&#93;.date); &#9;&#9;pos &#58;&#61; MIME.FindField(h, "Subject"); &#9;&#9;MIME.ExtractValue(h, pos, str); &#9;&#9;Insert(heap, str, msgs&#91;noMsgs&#93;.subject); &#9;&#9;pos &#58;&#61; MIME.FindField(h, "X-Oberon-Status"); &#9;&#9;msgs&#91;noMsgs&#93;.flags &#58;&#61; &#123;&#125;; msgs&#91;noMsgs&#93;.topics &#58;&#61; &#123;&#125;; &#9;&#9;IF pos &#62; 0 THEN &#9;&#9;&#9;MIME.ExtractValue(h, pos, str); &#9;&#9;&#9;IF CAP(str&#91;0&#93;) &#61; "R" THEN &#9;&#9;&#9;&#9;INCL(msgs&#91;noMsgs&#93;.flags, Read) &#9;&#9;&#9;END; &#9;&#9;&#9;IF CAP(str&#91;1&#93;) &#61; "D" THEN &#9;&#9;&#9;&#9;INCL(msgs&#91;noMsgs&#93;.flags, Deleted); &#9;&#9;&#9;&#9;INC(delMsgs); DEC(noMsgs) &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;v &#58;&#61; 0; &#9;&#9;&#9;&#9;FOR i &#58;&#61; 7 TO 0 BY-1 DO &#9;&#9;&#9;&#9;&#9;IF str&#91;2+i&#93; &#60;&#61; "9" THEN &#9;&#9;&#9;&#9;&#9;&#9;v &#58;&#61; 16*v+ORD(str&#91;2+i&#93;)-ORD("0") &#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;v &#58;&#61; 16*v+ORD(str&#91;2+i&#93;)-ORD("A")+10 &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;FOR i &#58;&#61; MIN(SET) TO MAX(SET) DO &#9;&#9;&#9;&#9;&#9;IF (v MOD 2) &#62; 0 THEN &#9;&#9;&#9;&#9;&#9;&#9;INCL(msgs&#91;noMsgs&#93;.topics, i) &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;v &#58;&#61; v DIV 2 &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END &#9;&#9;END; &#9;&#9;INC(noMsgs) &#9;END AddMsgHead; &#9;PROCEDURE FindObj(name&#58; ARRAY OF CHAR)&#58; Objects.Object; &#9;&#9;VAR obj, context&#58; Objects.Object; &#9;BEGIN &#9;&#9;context &#58;&#61; Gadgets.context; &#9;&#9;obj &#58;&#61; Gadgets.FindObj(context, name); &#9;&#9;WHILE (obj &#61; NIL) &#38; (context # NIL) DO &#9;&#9;&#9;context &#58;&#61; context.dlink; &#9;&#9;&#9;obj &#58;&#61; Gadgets.FindObj(context, name) &#9;&#9;END; &#9;&#9;RETURN obj &#9;END FindObj; &#9;PROCEDURE GetSetting*(name&#58; ARRAY OF CHAR; VAR value&#58; ARRAY OF CHAR; local&#58; BOOLEAN); &#9;&#9;VAR obj&#58; Objects.Object; &#9;BEGIN &#9;&#9;obj &#58;&#61; FindObj(name); &#9;&#9;IF obj # NIL THEN &#9;&#9;&#9;Attributes.GetString(obj, "Value", value) &#9;&#9;ELSE &#9;&#9;&#9;COPY("", value) &#9;&#9;END; &#9;&#9;IF (value &#61; "") &#38; &#126;local THEN &#9;&#9;&#9;IF &#126;NetTools.QueryString(name, value) THEN &#9;&#9;&#9;&#9;COPY("", value) &#9;&#9;&#9;END &#9;&#9;END &#9;END GetSetting; &#9;PROCEDURE ShowStatus(msg&#58; ARRAY OF CHAR); &#9;&#9;VAR obj&#58; Objects.Object; &#9;BEGIN &#9;&#9;obj &#58;&#61; FindObj("StatusBar"); &#9;&#9;IF obj # NIL THEN &#9;&#9;&#9;Attributes.SetString(obj, "Value", msg); &#9;&#9;&#9;Gadgets.Update(obj) &#9;&#9;ELSE &#9;&#9;&#9;Texts.WriteString(W, msg); Texts.WriteLn(W); &#9;&#9;&#9;Texts.Append(Oberon.Log, W.buf) &#9;&#9;END &#9;END ShowStatus; &#9;PROCEDURE WriteString(VAR R&#58; Files.Rider; str&#58; ARRAY OF CHAR); &#9;&#9;VAR i&#58; LONGINT; &#9;BEGIN &#9;&#9;i &#58;&#61; 0; &#9;&#9;WHILE str&#91;i&#93; # 0X DO &#9;&#9;&#9;Files.Write(R, str&#91;i&#93;); INC(i) &#9;&#9;END &#9;END WriteString; &#9;PROCEDURE WriteLn(VAR R&#58; Files.Rider); &#9;BEGIN &#9;&#9;Files.WriteBytes(R, Strings.CRLF, 2) &#9;END WriteLn; &#9;PROCEDURE SetVPos(F&#58; Objects.Object); &#9;&#9;VAR obj&#58; Objects.Object; &#9;BEGIN &#9;&#9;Links.GetLink(F, "VPos", obj); &#9;&#9;Attributes.SetInt(obj, "Value", 0); &#9;&#9;Gadgets.Update(obj); &#9;END SetVPos; &#9;PROCEDURE Synchronize*; &#9;&#9;VAR &#9;&#9;&#9;S&#58; NetTools.Session; &#9;&#9;&#9;set, newSet&#58; UIDLSet; &#9;&#9;&#9;uidl&#58; UIDL; &#9;&#9;&#9;pop&#58; ServerName; &#9;&#9;&#9;user&#58; UserName; passwd&#58; ValueString; &#9;&#9;&#9;Ri&#58; Files.Rider; &#9;&#9;&#9;uT&#58; Texts.Text; &#9;&#9;&#9;Sc&#58; Texts.Scanner; &#9;&#9;&#9;R&#58; Texts.Reader; &#9;&#9;&#9;pos, i, k, new, maxSize&#58; LONGINT; &#9;&#9;&#9;onServer&#58; BOOLEAN; &#9;&#9;&#9;obj&#58; Objects.Object; &#9;&#9;&#9;ch&#58; CHAR; &#9;&#9;&#9;apop, add&#58; BOOLEAN; &#9;BEGIN &#9;&#9;trace &#58;&#61; NetTools.QueryBool("TraceMail"); &#9;&#9;GetSetting("POPMode", pop, FALSE); Strings.Upper(pop, pop); &#9;&#9;apop &#58;&#61; pop &#61; "APOP"; &#9;&#9;GetSetting("MaxMsgSize", user, FALSE); &#9;&#9;Strings.StrToInt(user, maxSize); &#9;&#9;GetSetting("LeaveOnServer", user, TRUE);	(* first check local in Mail.Panel *) &#9;&#9;IF user &#61; "No" THEN	(* not set, check config file for final setting *) &#9;&#9;&#9;IF &#126;NetTools.QueryString("LeaveOnServer", user) THEN user&#91;0&#93; &#58;&#61; 0X END &#9;&#9;END; &#9;&#9;IF user # "" THEN &#9;&#9;&#9;Strings.StrToBool(user, onServer) &#9;&#9;ELSE &#9;&#9;&#9;onServer &#58;&#61; TRUE &#9;&#9;END; &#9;&#9;GetSetting("User", user, FALSE); GetSetting("POP", pop, FALSE); &#9;&#9;NetSystem.GetPassword("pop", pop, user, passwd); &#9;&#9;ShowStatus("downloading..."); &#9;&#9;OpenPOP(S, pop, user, passwd, DefPOPPort, apop); &#9;&#9;IF S.res &#61; NetTools.Done THEN &#9;&#9;&#9;set &#58;&#61; GetUIDLSet(pop, user); newSet &#58;&#61; NewUIDLSet(pop, user); &#9;&#9;&#9;GetUIDLs(S, uT); &#9;&#9;&#9;IF (S.res &#61; NetTools.Done) &#38; (uT # NIL) &#38; (uT.len &#62; 0) THEN &#9;&#9;&#9;&#9;k &#58;&#61; 0; &#9;&#9;&#9;&#9;WHILE k &#60; set.nouidls DO &#9;&#9;&#9;&#9;&#9;set.uidls&#91;k&#93;&#91;63&#93; &#58;&#61; 0X; INC(k) &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;Texts.OpenScanner(Sc, uT, 0); Texts.Scan(Sc); &#9;&#9;&#9;&#9;new &#58;&#61; 0; i &#58;&#61; 1; &#9;&#9;&#9;&#9;WHILE (Sc.class &#61; Texts.Int) &#38; (Sc.i &#61; i) &#38; (S.res &#61; NetTools.Done) DO &#9;&#9;&#9;&#9;&#9;Texts.OpenReader(R, uT, Texts.Pos(Sc)); &#9;&#9;&#9;&#9;&#9;k &#58;&#61; 0; Texts.Read(R, ch); &#9;&#9;&#9;&#9;&#9;WHILE &#126;R.eot &#38; (ch &#62; " ") DO &#9;&#9;&#9;&#9;&#9;&#9;uidl&#91;k&#93; &#58;&#61; ch; INC(k); &#9;&#9;&#9;&#9;&#9;&#9;Texts.Read(R, ch) &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;uidl&#91;k&#93; &#58;&#61; 0X; k &#58;&#61; ExistsUIDL(set, uidl); add &#58;&#61; TRUE; &#9;&#9;&#9;&#9;&#9;IF k &#60; 0 THEN &#9;&#9;&#9;&#9;&#9;&#9;k &#58;&#61; MessageSize(S, i); &#9;&#9;&#9;&#9;&#9;&#9;IF k &#60;&#61; maxSize THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;INC(new); &#9;&#9;&#9;&#9;&#9;&#9;&#9;Files.Set(Ri, msgsF, Files.Length(msgsF)); &#9;&#9;&#9;&#9;&#9;&#9;&#9;WriteString(Ri, "From "); WriteLn(Ri); (* msg tag *) &#9;&#9;&#9;&#9;&#9;&#9;&#9;pos &#58;&#61; Files.Pos(Ri); &#9;&#9;&#9;&#9;&#9;&#9;&#9;WriteString(Ri, "X-Oberon-Status&#58; 0010000000"); WriteLn(Ri); &#9;&#9;&#9;&#9;&#9;&#9;&#9;WriteString(Ri, "X-UIDL&#58; "); WriteString(Ri, uidl); WriteLn(Ri); &#9;&#9;&#9;&#9;&#9;&#9;&#9;AddUIDL(set, uidl); set.uidls&#91;set.nouidls-1&#93;&#91;63&#93; &#58;&#61; 01X; &#9;&#9;&#9;&#9;&#9;&#9;&#9;ReceiveMail(S, i, Ri); add &#58;&#61; S.res &#61; NetTools.Done; &#9;&#9;&#9;&#9;&#9;&#9;&#9;AddMsgHead(pos); msgs&#91;noMsgs-1&#93;.len &#58;&#61; Files.Length(msgsF)-msgs&#91;noMsgs-1&#93;.pos; &#9;&#9;&#9;&#9;&#9;&#9;&#9;IF add &#38; &#126;onServer THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;Files.Close(msgsF); DeleteMail(S, i) &#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;Texts.WriteString(W, "message "); Texts.WriteInt(W, i, 0); &#9;&#9;&#9;&#9;&#9;&#9;&#9;Texts.WriteString(W, " too large ("); Texts.WriteInt(W, k, 0); &#9;&#9;&#9;&#9;&#9;&#9;&#9;Texts.WriteString(W, " bytes)"); Texts.WriteLn(W); &#9;&#9;&#9;&#9;&#9;&#9;&#9;Texts.Append(Oberon.Log, W.buf); add &#58;&#61; FALSE &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSIF set.uidls&#91;k&#93;&#91;63&#93; # 0X THEN &#9;&#9;&#9;&#9;&#9;&#9;Texts.WriteString(W, "message "); Texts.WriteInt(W, i, 0); &#9;&#9;&#9;&#9;&#9;&#9;Texts.WriteString(W, " ignored (UIDL not unique)"); Texts.WriteLn(W); &#9;&#9;&#9;&#9;&#9;&#9;Texts.Append(Oberon.Log, W.buf); add &#58;&#61; FALSE &#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;set.uidls&#91;k&#93;&#91;63&#93; &#58;&#61; 01X &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;IF add THEN &#9;&#9;&#9;&#9;&#9;&#9;AddUIDL(newSet, uidl) &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;Texts.OpenScanner(Sc, uT, Texts.Pos(R)); &#9;&#9;&#9;&#9;&#9;INC(i); Texts.Scan(Sc); &#9;&#9;&#9;&#9;&#9;WHILE (Sc.class &#61; Texts.Char) &#38; (Sc.c &#60;&#61; " ") DO &#9;&#9;&#9;&#9;&#9;&#9;Texts.Scan(Sc) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END; &#9;&#9;&#9;ClosePOP(S); &#9;&#9;&#9;FlushUIDL(newSet); set^ &#58;&#61; newSet^; &#9;&#9;&#9;IF new &#62; 0 THEN &#9;&#9;&#9;&#9;Files.Close(msgsF); &#9;&#9;&#9;&#9;Gadgets.Update(msgList); &#9;&#9;&#9;&#9;obj &#58;&#61; FindObj("MailList"); &#9;&#9;&#9;&#9;SetVPos(obj) &#9;&#9;&#9;END &#9;&#9;END; &#9;&#9;IF S.res # NetTools.Done THEN &#9;&#9;&#9;ShowStatus(S.reply) &#9;&#9;ELSIF new &#61; 0 THEN &#9;&#9;&#9;ShowStatus("no new mail") &#9;&#9;ELSE &#9;&#9;&#9;Strings.IntToStr(new, passwd); Strings.Append(passwd, " new messages"); &#9;&#9;&#9;ShowStatus(passwd) &#9;&#9;END &#9;END Synchronize; &#9;PROCEDURE POPCollect*; &#9;&#9;VAR &#9;&#9;&#9;S&#58; NetTools.Session; &#9;&#9;&#9;set, newSet&#58; UIDLSet; &#9;&#9;&#9;uidl&#58; UIDL; &#9;&#9;&#9;pop&#58; ServerName; &#9;&#9;&#9;user&#58; UserName; passwd&#58; ValueString; &#9;&#9;&#9;uT&#58; Texts.Text; &#9;&#9;&#9;Sc&#58; Texts.Scanner; &#9;&#9;&#9;R&#58; Texts.Reader; &#9;&#9;&#9;i, k&#58; LONGINT; &#9;&#9;&#9;apop&#58; BOOLEAN; &#9;&#9;&#9;ch&#58; CHAR; &#9;BEGIN &#9;&#9;GetSetting("POPMode", pop, FALSE); Strings.Upper(pop, pop); &#9;&#9;apop &#58;&#61; pop &#61; "APOP"; &#9;&#9;GetSetting("User", user, FALSE); GetSetting("POP", pop, FALSE); &#9;&#9;NetSystem.GetPassword("pop", pop, user, passwd); &#9;&#9;OpenPOP(S, pop, user, passwd, DefPOPPort, apop); &#9;&#9;IF S.res &#61; NetTools.Done THEN &#9;&#9;&#9;set &#58;&#61; GetUIDLSet(pop, user); &#9;&#9;&#9;GetUIDLs(S, uT); &#9;&#9;&#9;IF S.res &#61; NetTools.Done THEN &#9;&#9;&#9;&#9;Texts.OpenScanner(Sc, uT, 0); Texts.Scan(Sc); &#9;&#9;&#9;&#9;i &#58;&#61; 1; &#9;&#9;&#9;&#9;WHILE (Sc.class &#61; Texts.Int) &#38; (Sc.i &#61; i) &#38; (S.res &#61; NetTools.Done) DO &#9;&#9;&#9;&#9;&#9;Texts.OpenReader(R, uT, Texts.Pos(Sc)); &#9;&#9;&#9;&#9;&#9;k &#58;&#61; 0; Texts.Read(R, ch); &#9;&#9;&#9;&#9;&#9;WHILE &#126;R.eot &#38; (ch &#62; " ") DO &#9;&#9;&#9;&#9;&#9;&#9;uidl&#91;k&#93; &#58;&#61; ch; INC(k); &#9;&#9;&#9;&#9;&#9;&#9;Texts.Read(R, ch) &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;uidl&#91;k&#93; &#58;&#61; 0X; &#9;&#9;&#9;&#9;&#9;IF ExistsUIDL(set, uidl) &#62;&#61; 0 THEN &#9;&#9;&#9;&#9;&#9;&#9;Strings.IntToStr(i, passwd); ShowStatus(passwd); &#9;&#9;&#9;&#9;&#9;&#9;DeleteMail(S, i) &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;Texts.OpenScanner(Sc, uT, Texts.Pos(R)); &#9;&#9;&#9;&#9;&#9;INC(i); Texts.Scan(Sc); &#9;&#9;&#9;&#9;&#9;WHILE (Sc.class &#61; Texts.Char) &#38; (Sc.c &#60;&#61; " ") DO &#9;&#9;&#9;&#9;&#9;&#9;Texts.Scan(Sc) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END; &#9;&#9;&#9;ClosePOP(S) &#9;&#9;END; &#9;&#9;IF S.res # NetTools.Done THEN &#9;&#9;&#9;ShowStatus(S.reply) &#9;&#9;ELSE &#9;&#9;&#9;newSet &#58;&#61; NewUIDLSet(pop, user); &#9;&#9;&#9;FlushUIDL(newSet); set^ &#58;&#61; newSet^; &#9;&#9;&#9;ShowStatus("") &#9;&#9;END &#9;END POPCollect; &#9;PROCEDURE ReadString(VAR R&#58; Texts.Reader; VAR s&#58; ARRAY OF CHAR); &#9;&#9;VAR &#9;&#9;&#9;l, i&#58; LONGINT; &#9;&#9;&#9;ch&#58; CHAR; &#9;BEGIN &#9;&#9;l &#58;&#61; LEN(s)-1; i &#58;&#61; 0; &#9;&#9;Texts.Read(R, ch); &#9;&#9;WHILE &#126;R.eot &#38; (ch # Strings.CR) &#38; (i &#60; l) DO &#9;&#9;&#9;s&#91;i&#93; &#58;&#61; ch; INC(i); &#9;&#9;&#9;Texts.Read(R, ch) &#9;&#9;END; &#9;&#9;WHILE &#126;R.eot &#38; (ch # Strings.CR) DO &#9;&#9;&#9;Texts.Read(R, ch) &#9;&#9;END; &#9;&#9;s&#91;i&#93; &#58;&#61; 0X &#9;END ReadString; &#9;PROCEDURE ScanHeader(no&#58; LONGINT; VAR h&#58; MIME.Header); &#9;&#9;VAR &#9;&#9;&#9;S&#58; Streams.Stream; &#9;&#9;&#9;len&#58; LONGINT; &#9;BEGIN &#9;&#9;S &#58;&#61; Streams.OpenFileReader(msgsF, msgs&#91;no&#93;.pos); S.mode &#58;&#61; Streams.binary; &#9;&#9;MIME.ReadHeader(S, NIL, h, len) &#9;END ScanHeader; &#9;PROCEDURE WriteStatus(h&#58; MIME.Header; no&#58; LONGINT); &#9;&#9;VAR &#9;&#9;&#9;R&#58; Files.Rider; &#9;&#9;&#9;pos, i, v&#58; LONGINT; &#9;&#9;&#9;ch&#58; CHAR; &#9;BEGIN &#9;&#9;pos &#58;&#61; MIME.FindField(h, "X-Oberon-Status"); &#9;&#9;IF pos &#62; 0 THEN &#9;&#9;&#9;pos &#58;&#61; msgs&#91;no&#93;.pos+pos; &#9;&#9;&#9;Files.Set(R, msgsF, pos); &#9;&#9;&#9;IF Read IN msgs&#91;no&#93;.flags THEN &#9;&#9;&#9;&#9;Files.Write(R, "R") &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;Files.Write(R, "0") &#9;&#9;&#9;END; &#9;&#9;&#9;IF Deleted IN msgs&#91;no&#93;.flags THEN &#9;&#9;&#9;&#9;Files.Write(R, "D") &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;Files.Write(R, "0") &#9;&#9;&#9;END; &#9;&#9;&#9;v &#58;&#61; 0; &#9;&#9;&#9;FOR i &#58;&#61; MAX(SET) TO MIN(SET) BY -1 DO &#9;&#9;&#9;&#9;v &#58;&#61; 2*v; &#9;&#9;&#9;&#9;IF i IN msgs&#91;no&#93;.topics THEN &#9;&#9;&#9;&#9;&#9;INC(v) &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END; &#9;&#9;&#9;FOR i &#58;&#61; 0 TO 7 DO &#9;&#9;&#9;&#9;ch &#58;&#61; CHR(ORD("0")+(v MOD 16)); &#9;&#9;&#9;&#9;IF ch &#62; "9" THEN &#9;&#9;&#9;&#9;&#9;ch &#58;&#61; CHR(ORD("A")+(v MOD 16)-10) &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;Files.Write(R, ch); &#9;&#9;&#9;&#9;v &#58;&#61; v DIV 16 &#9;&#9;&#9;END &#9;&#9;END &#9;END WriteStatus; &#9;PROCEDURE WriteField(VAR h&#58; MIME.Header; field&#58; ARRAY OF CHAR; empty, long&#58; BOOLEAN); &#9;&#9;VAR &#9;&#9;&#9;caption&#58; ARRAY 64 OF CHAR; value&#58; ARRAY 128 OF CHAR; &#9;&#9;&#9;pos&#58; LONGINT; first&#58; BOOLEAN; &#9;BEGIN &#9;&#9;COPY(field, caption); &#9;&#9;pos &#58;&#61; MIME.FindField(h, field); &#9;&#9;first &#58;&#61; empty; &#9;&#9;WHILE (pos &#62; 0) OR first DO &#9;&#9;&#9;first &#58;&#61; FALSE; Texts.SetFont(W, headFnt); &#9;&#9;&#9;MIME.ExtractValue(h, pos, value); &#9;&#9;&#9;IF empty OR (value # "") THEN &#9;&#9;&#9;&#9;Texts.WriteString(W, caption); Texts.Write(W, "&#58;"); &#9;&#9;&#9;&#9;IF pos &#62; 0 THEN &#9;&#9;&#9;&#9;&#9;Texts.SetFont(W, fieldFnt); Texts.Write(W, Strings.Tab); &#9;&#9;&#9;&#9;&#9;IF long THEN &#9;&#9;&#9;&#9;&#9;&#9;WHILE h.fields&#91;pos&#93; # 0X DO &#9;&#9;&#9;&#9;&#9;&#9;&#9;Texts.Write(W, Strings.ISOToOberon&#91;ORD(h.fields&#91;pos&#93;)&#93;); INC(pos) &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;Texts.WriteString(W, value) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;Texts.WriteLn(W) &#9;&#9;&#9;END; &#9;&#9;&#9;IF (pos &#62; 0) &#38; (field # "") THEN &#9;&#9;&#9;&#9;MIME.FindFieldPos(h, field, pos) &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;pos &#58;&#61; -1 &#9;&#9;&#9;END &#9;&#9;END &#9;END WriteField; &#9;PROCEDURE DecodeMessage*(VAR T&#58; Texts.Text; h&#58; MIME.Header; cont&#58; MIME.Content; no&#58; LONGINT); &#9;&#9;VAR &#9;&#9;&#9;F, Fc&#58; Files.File; &#9;&#9;&#9;R&#58; Texts.Reader; &#9;&#9;&#9;str&#58; ValueString; &#9;&#9;&#9;style&#58; TextGadgets.Style; &#9;&#9;&#9;topic&#58; Topic; &#9;&#9;&#9;pos, len&#58; LONGINT; &#9;&#9;&#9;first, ok, oberon&#58; BOOLEAN; &#9;BEGIN &#9;&#9;oberon &#58;&#61; (cont.typ.typ &#61; "application") &#38; (cont.encoding IN &#123;MIME.EncAsciiCoder, MIME.EncAsciiCoderC, MIME.EncAsciiCoderCPlain&#125;); &#9;&#9;pos &#58;&#61; 0; len &#58;&#61; 0; &#9;&#9;Texts.OpenReader(R, T, pos); &#9;&#9;ok &#58;&#61; TRUE; ReadString(R, str); &#9;&#9;WHILE &#126;R.eot &#38; ((&#126;oberon &#38; (len &#62; 0)) OR (str # OberonStart)) DO &#9;&#9;&#9;len &#58;&#61; Texts.Pos(R); &#9;&#9;&#9;IF (str # "") &#38; ok THEN &#9;&#9;&#9;&#9;pos &#58;&#61; Texts.Pos(R) &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;ok &#58;&#61; FALSE &#9;&#9;&#9;END; &#9;&#9;&#9;ReadString(R, str) &#9;&#9;END; &#9;&#9;IF str &#61; OberonStart THEN &#9;&#9;&#9;F &#58;&#61; Files.New(""); len &#58;&#61; Texts.Pos(R); &#9;&#9;&#9;AsciiCoder.Decode(T, len, F, ok); &#9;&#9;&#9;IF ok THEN &#9;&#9;&#9;&#9;IF cont.encoding &#61; MIME.EncAsciiCoderC THEN &#9;&#9;&#9;&#9;&#9;Fc &#58;&#61; Files.New(""); AsciiCoder.Expand(F, Fc) &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;Fc &#58;&#61; F &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;Texts.Save(T, 0, pos+1, W.buf); &#9;&#9;&#9;&#9;NEW(T); Texts.Load(T, Fc, 1, len); &#9;&#9;&#9;&#9;Texts.Insert(T, 0, W.buf) &#9;&#9;&#9;END &#9;&#9;END; &#9;&#9;IF no &#62;&#61; 0 THEN &#9;&#9;&#9;style &#58;&#61; TextGadgets.newStyle; &#9;&#9;&#9;Attributes.SetInt(style, "Message", no); &#9;&#9;&#9;style.mode &#58;&#61; &#123;TextGadgets.left&#125;; &#9;&#9;&#9;style.noTabs &#58;&#61; 1; &#9;&#9;&#9;style.tab&#91;0&#93; &#58;&#61; 6*(headFnt.maxX-headFnt.minX); &#9;&#9;&#9;Texts.WriteObj(W, style); &#9;&#9;&#9;WriteField(h, "Reply-To", FALSE, FALSE); &#9;&#9;&#9;WriteField(h, "From", TRUE, FALSE); &#9;&#9;&#9;WriteField(h, "Subject", TRUE, FALSE); &#9;&#9;&#9;IF msgs&#91;no&#93;.topics # &#123;&#125; THEN &#9;&#9;&#9;&#9;Texts.SetFont(W, headFnt); &#9;&#9;&#9;&#9;Texts.WriteString(W, "Topics&#58;"); Texts.Write(W, Strings.Tab); &#9;&#9;&#9;&#9;Texts.SetFont(W, fieldFnt); &#9;&#9;&#9;&#9;first &#58;&#61; TRUE; &#9;&#9;&#9;&#9;FOR pos &#58;&#61; MIN(SET) TO MAX(SET) DO &#9;&#9;&#9;&#9;&#9;IF pos IN msgs&#91;no&#93;.topics THEN &#9;&#9;&#9;&#9;&#9;&#9;topic &#58;&#61; topics; &#9;&#9;&#9;&#9;&#9;&#9;WHILE (topic # NIL) &#38; (topic.no # pos) DO &#9;&#9;&#9;&#9;&#9;&#9;&#9;topic&#58;&#61; topic.next &#9;&#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;&#9;IF &#126;first THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;Texts.WriteString(W, ", ") &#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;first &#58;&#61; FALSE &#9;&#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;&#9;IF topic # NIL THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;Texts.WriteString(W, topic.topic.s) &#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;Texts.WriteString(W, "Topic"); Texts.WriteInt(W, pos, 1) &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;Texts.WriteLn(W); &#9;&#9;&#9;END; &#9;&#9;&#9;WriteField(h, "Date", TRUE, FALSE); &#9;&#9;&#9;WriteField(h, "To", TRUE, TRUE); &#9;&#9;&#9;WriteField(h, "Cc", FALSE, TRUE); &#9;&#9;&#9;WriteField(h, "Bcc", FALSE, TRUE); &#9;&#9;&#9;style &#58;&#61; TextGadgets.newStyle; &#9;&#9;&#9;style.mode &#58;&#61; &#123;TextGadgets.left&#125;; &#9;&#9;&#9;style.noTabs &#58;&#61; 0; &#9;&#9;&#9;Texts.WriteObj(W, style); &#9;&#9;&#9;Texts.Insert(T, 0, W.buf) &#9;&#9;END; &#9;&#9;Texts.SetFont(W, Fonts.Default) &#9;END DecodeMessage; &#9;PROCEDURE decodeMessage(no&#58; LONGINT; VAR T&#58; Texts.Text; plain&#58; BOOLEAN); &#9;&#9;VAR &#9;&#9;&#9;S&#58; Streams.Stream; &#9;&#9;&#9;mT&#58; Texts.Text; &#9;&#9;&#9;h&#58; MIME.Header; &#9;&#9;&#9;cont&#58; MIME.Content; &#9;&#9;&#9;len&#58; LONGINT; &#9;BEGIN &#9;&#9;S &#58;&#61; Streams.OpenFileReader(msgsF, msgs&#91;no&#93;.pos); S.mode &#58;&#61; Streams.binary; &#9;&#9;IF plain THEN &#9;&#9;&#9;NEW(cont); cont.typ &#58;&#61; MIME.GetContentType("text/plain"); &#9;&#9;&#9;S &#58;&#61; Streams.OpenFileReader(msgsF, msgs&#91;no&#93;.pos) &#9;&#9;ELSE &#9;&#9;&#9;MIME.ReadHeader(S, NIL, h, len); ParseContent(h, cont); &#9;&#9;&#9;S &#58;&#61; Streams.OpenFileReader(msgsF, msgs&#91;no&#93;.pos+len) &#9;&#9;END; &#9;&#9;S.mode &#58;&#61; Streams.binary; &#9;&#9;cont.len &#58;&#61; msgs&#91;no&#93;.len; &#9;&#9;IF plain THEN &#9;&#9;&#9;Texts.SetFont(W, Fonts.Default); &#9;&#9;&#9;Texts.WriteString(W, "&#91;Message "); Texts.WriteInt(W, no, 1); &#9;&#9;&#9;Texts.WriteString(W, ", pos "); Texts.WriteInt(W, msgs&#91;no&#93;.pos-7, 1); &#9;&#9;&#9;Texts.WriteString(W, "&#93;"); Texts.WriteLn(W) &#9;&#9;ELSE &#9;&#9;&#9;Texts.SetFont(W, textFnt) &#9;&#9;END; &#9;&#9;IF cont.typ.typ # "multipart" THEN &#9;&#9;&#9;MIME.ReadText(S, W, cont, TRUE) &#9;&#9;ELSE &#9;&#9;&#9;MIME.ReadMultipartText(S, mT, cont, TRUE); Texts.Save(mT, 0, mT.len, W.buf) &#9;&#9;END; &#9;&#9;NEW(T); Texts.Open(T, ""); &#9;&#9;Texts.Append(T, W.buf); &#9;&#9;IF &#126;plain THEN &#9;&#9;&#9;DecodeMessage(T, h, cont, no) &#9;&#9;END; &#9;&#9;IF &#126;(Read IN msgs&#91;no&#93;.flags) THEN &#9;&#9;&#9;INCL(msgs&#91;no&#93;.flags, Read); &#9;&#9;&#9;WriteStatus(h, no); Files.Close(msgsF); &#9;&#9;&#9;Gadgets.Update(msgList) &#9;&#9;END &#9;END decodeMessage; &#9;PROCEDURE *DocHandler(D&#58; Objects.Object; VAR M&#58; Objects.ObjMsg); &#9;BEGIN &#9;&#9;WITH D&#58; Documents.Document DO &#9;&#9;&#9;IF M IS Objects.AttrMsg THEN &#9;&#9;&#9;&#9;WITH M&#58; Objects.AttrMsg DO &#9;&#9;&#9;&#9;&#9;IF (M.id &#61; Objects.get) &#38; (M.name &#61; "Gen") THEN &#9;&#9;&#9;&#9;&#9;&#9;M.class &#58;&#61; Objects.String; M.s &#58;&#61; "Mail.NewMsgDoc"; M.res &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;TextDocs.DocHandler(D, M) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSIF M IS Objects.LinkMsg THEN &#9;&#9;&#9;&#9;WITH M&#58; Objects.LinkMsg DO &#9;&#9;&#9;&#9;&#9;IF M.id &#61; Objects.get THEN &#9;&#9;&#9;&#9;&#9;&#9;IF M.name &#61; "DeskMenu" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;M.obj &#58;&#61; Gadgets.CopyPublicObject("NetDocs.MailDeskMenu", TRUE); &#9;&#9;&#9;&#9;&#9;&#9;&#9;IF M.obj &#61; NIL THEN M.obj &#58;&#61; Desktops.NewMenu(Menu) END; &#9;&#9;&#9;&#9;&#9;&#9;&#9;M.res &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;&#9;ELSIF M.name &#61; "SystemMenu" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;M.obj &#58;&#61; Gadgets.CopyPublicObject("NetDocs.MailSystemMenu", TRUE); &#9;&#9;&#9;&#9;&#9;&#9;&#9;IF M.obj &#61; NIL THEN M.obj &#58;&#61; Desktops.NewMenu(SysMenu) END; &#9;&#9;&#9;&#9;&#9;&#9;&#9;M.res &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;&#9;ELSIF M.name &#61; "UserMenu" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;M.obj &#58;&#61; Gadgets.CopyPublicObject("NetDocs.MailUserMenu", TRUE); &#9;&#9;&#9;&#9;&#9;&#9;&#9;IF M.obj &#61; NIL THEN M.obj &#58;&#61; Desktops.NewMenu(Menu) END; &#9;&#9;&#9;&#9;&#9;&#9;&#9;M.res &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;TextDocs.DocHandler(D, M) &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;TextDocs.DocHandler(D, M) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;TextDocs.DocHandler(D, M) &#9;&#9;&#9;END &#9;&#9;END &#9;END DocHandler; &#9;PROCEDURE ShowText(title&#58; ARRAY OF CHAR; T&#58; Texts.Text; reply&#58; BOOLEAN); &#9;&#9;VAR &#9;&#9;&#9;D&#58; Documents.Document; &#9;&#9;&#9;F&#58; TextGadgets.Frame; &#9;BEGIN &#9;&#9;NEW(D); TextDocs.InitDoc(D); &#9;&#9;NEW(F); TextGadgets.Init(F, T, FALSE); &#9;&#9;Documents.Init(D, F); COPY(title, D.name); &#9;&#9;IF reply THEN D.handle &#58;&#61; DocHandler END; &#9;&#9;Desktops.ShowDoc(D) &#9;END ShowText; &#9;PROCEDURE Show*; &#9;&#9;VAR &#9;&#9;&#9;S&#58; Attributes.Scanner; &#9;&#9;&#9;T&#58; Texts.Text; &#9;&#9;&#9;D&#58; Documents.Document; &#9;&#9;&#9;obj&#58; Objects.Object; &#9;&#9;&#9;F&#58; Texts.Finder; &#9;&#9;&#9;no&#58; LONGINT; &#9;&#9;&#9;plain&#58; BOOLEAN; &#9;&#9;&#9;line&#58; ListGadgets.Line; &#9;BEGIN &#9;&#9;Attributes.OpenScanner(S, Oberon.Par.text,Oberon.Par.pos); &#9;&#9;Attributes.Scan(S); &#9;&#9;IF S.class &#61; Attributes.Int THEN &#9;&#9;&#9;IF (S.i &#62;&#61; 0) &#38; (S.i &#60; noMsgs) THEN &#9;&#9;&#9;&#9;obj &#58;&#61; FindObj("MailList"); &#9;&#9;&#9;&#9;IF obj # NIL THEN &#9;&#9;&#9;&#9;&#9;WITH obj&#58; ListGadgets.Frame DO &#9;&#9;&#9;&#9;&#9;&#9;line &#58;&#61; obj.lines; &#9;&#9;&#9;&#9;&#9;&#9;REPEAT &#9;&#9;&#9;&#9;&#9;&#9;&#9;line.sel &#58;&#61; msgs&#91;S.i&#93;.pos &#61; line.key; &#9;&#9;&#9;&#9;&#9;&#9;&#9;line &#58;&#61; line.next &#9;&#9;&#9;&#9;&#9;&#9;UNTIL line &#61; obj.lines; &#9;&#9;&#9;&#9;&#9;&#9;obj.sel &#58;&#61; TRUE; obj.time &#58;&#61; Oberon.Time; &#9;&#9;&#9;&#9;&#9;&#9;Gadgets.Update(obj) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;decodeMessage(S.i, T, FALSE); &#9;&#9;&#9;&#9;ShowText("Mail.Text", T, TRUE) &#9;&#9;&#9;END &#9;&#9;ELSIF Desktops.IsInMenu(Gadgets.context) THEN &#9;&#9;&#9;D &#58;&#61; Desktops.CurDoc(Gadgets.context); &#9;&#9;&#9;Links.GetLink(D.dsc, "Model", obj); &#9;&#9;&#9;IF (obj # NIL) &#38; (obj IS Texts.Text) THEN &#9;&#9;&#9;&#9;Texts.OpenFinder(F, obj(Texts.Text), 0); &#9;&#9;&#9;&#9;Texts.FindObj(F, obj); &#9;&#9;&#9;&#9;IF (obj # NIL) &#38; (obj IS TextGadgets.Style) THEN &#9;&#9;&#9;&#9;&#9;Attributes.SetString(Gadgets.executorObj, "Caption", "Text"); &#9;&#9;&#9;&#9;&#9;Attributes.GetInt(obj, "Message", no); plain &#58;&#61; TRUE &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;Attributes.SetString(Gadgets.executorObj, "Caption", "Source"); &#9;&#9;&#9;&#9;&#9;Attributes.GetInt(Gadgets.executorObj, "Message", no); plain &#58;&#61; FALSE &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;IF (no &#62;&#61; 0) &#38; (no &#60; noMsgs) THEN &#9;&#9;&#9;&#9;&#9;decodeMessage(no, T, plain); &#9;&#9;&#9;&#9;&#9;Attributes.SetInt(Gadgets.executorObj, "Message", no); &#9;&#9;&#9;&#9;&#9;Links.SetLink(D.dsc, "Model", T) &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;Gadgets.Update(Gadgets.executorObj) &#9;&#9;&#9;END &#9;&#9;END &#9;END Show; &#9;PROCEDURE Shrink; &#9;&#9;VAR &#9;&#9;&#9;F&#58; Files.File; &#9;&#9;&#9;R&#58; Files.Rider; &#9;&#9;&#9;msg, bak&#58; FileDir.FileName; &#9;&#9;&#9;beg, end, offs, i&#58; LONGINT; &#9;&#9;&#9;res&#58; INTEGER; &#9;&#9;&#9;ch, old&#58; CHAR; &#9;&#9;PROCEDURE Copy; &#9;&#9;&#9;VAR &#9;&#9;&#9;&#9;R, r&#58; Files.Rider; &#9;&#9;&#9;&#9;ch&#58; CHAR; &#9;&#9;BEGIN &#9;&#9;&#9;IF (msgs&#91;i&#93;.pos &#62; beg) &#38; (msgs&#91;i&#93;.pos &#60; end) THEN &#9;&#9;&#9;&#9;Files.Set(R, msgsF, beg); &#9;&#9;&#9;&#9;Files.Set(r, F, Files.Length(F)); &#9;&#9;&#9;&#9;msgs&#91;i&#93;.pos &#58;&#61; Files.Pos(r)+offs; &#9;&#9;&#9;&#9;WHILE beg &#60; end DO &#9;&#9;&#9;&#9;&#9;Files.Read(R, ch); Files.Write(r, ch); &#9;&#9;&#9;&#9;&#9;INC(beg) &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;INC(i) &#9;&#9;&#9;END &#9;&#9;END Copy; &#9;BEGIN &#9;&#9;ShowStatus("shrinking message file"); &#9;&#9;Files.GetName(msgsF, msg); &#9;&#9;COPY(msg, bak); Strings.Append(bak, ".Bak"); &#9;&#9;Files.Rename(msg, bak, res); ASSERT(res &#61; 0); &#9;&#9;F &#58;&#61; Files.New(msg); i &#58;&#61; 0; &#9;&#9;msgsF &#58;&#61; Files.Old(bak); Files.Set(R, msgsF, 0); &#9;&#9;old &#58;&#61; Strings.LF; beg &#58;&#61; MAX(LONGINT); &#9;&#9;Files.Read(R, ch); &#9;&#9;WHILE &#126;R.eof DO &#9;&#9;&#9;end &#58;&#61; Files.Pos(R)-1; &#9;&#9;&#9;IF (ch &#61; "F") &#38; (old &#61; Strings.LF) THEN &#9;&#9;&#9;&#9;Files.Read(R, ch); &#9;&#9;&#9;&#9;IF ch &#61; "r" THEN &#9;&#9;&#9;&#9;&#9;Files.Read(R, ch); &#9;&#9;&#9;&#9;&#9;IF ch &#61; "o" THEN &#9;&#9;&#9;&#9;&#9;&#9;Files.Read(R, ch); &#9;&#9;&#9;&#9;&#9;&#9;IF ch &#61; "m" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;Files.Read(R, ch); &#9;&#9;&#9;&#9;&#9;&#9;&#9;IF ch &#61; " " THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;WHILE &#126;R.eof &#38; (ch &#62;&#61; " ") DO &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;Files.Read(R, ch) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;WHILE &#126;R.eof &#38; (ch &#60; " ") DO &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;Files.Read(R, ch) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;IF end &#62; beg THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;Copy &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;offs &#58;&#61; Files.Pos(R)-1-end; beg &#58;&#61; end &#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END; &#9;&#9;&#9;old &#58;&#61; ch; Files.Read(R, ch) &#9;&#9;END; &#9;&#9;end &#58;&#61; Files.Length(msgsF); &#9;&#9;Copy; &#9;&#9;ASSERT(i &#61; noMsgs); delMsgs &#58;&#61; 0; &#9;&#9;Files.Register(F); msgsF &#58;&#61; F; &#9;&#9;ShowStatus("") &#9;END Shrink; &#9;PROCEDURE collect; &#9;&#9;VAR i, j, no&#58; LONGINT; &#9;BEGIN &#9;&#9;i &#58;&#61; 0; j &#58;&#61; 0; no &#58;&#61; noMsgs; &#9;&#9;WHILE i &#60; no DO &#9;&#9;&#9;IF msgs&#91;i&#93;.pos &#62;&#61; 0 THEN &#9;&#9;&#9;&#9;msgs&#91;j&#93; &#58;&#61; msgs&#91;i&#93;; INC(j) &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;INC(delMsgs); DEC(noMsgs) &#9;&#9;&#9;END; &#9;&#9;&#9;INC(i) &#9;&#9;END; &#9;&#9;IF delMsgs &#62; 100 THEN &#9;&#9;&#9;Shrink &#9;&#9;END &#9;END collect; &#9;PROCEDURE Collect*; &#9;BEGIN &#9;&#9;delMsgs &#58;&#61; 200; collect; &#9;&#9;Gadgets.Update(msgList) &#9;END Collect; &#9;PROCEDURE DeleteMessage(no&#58; LONGINT); &#9;&#9;VAR h&#58; MIME.Header; &#9;BEGIN &#9;&#9;INCL(msgs&#91;no&#93;.flags, Deleted); &#9;&#9;ScanHeader(no, h); &#9;&#9;WriteStatus(h, no); &#9;&#9;msgs&#91;no&#93;.pos &#58;&#61; -1 &#9;END DeleteMessage; &#9;PROCEDURE Re*(VAR W&#58; Texts.Writer; VAR t&#58; ARRAY OF CHAR); &#9;&#9;VAR &#9;&#9;&#9;i, j, re, oldre&#58; LONGINT; &#9;&#9;&#9;p&#58; INTEGER; &#9;&#9;&#9;end&#58; BOOLEAN; &#9;&#9;PROCEDURE Blanks; &#9;&#9;BEGIN &#9;&#9;&#9;WHILE (t&#91;i&#93; # 0X) &#38; (t&#91;i&#93; &#60;&#61; " ") DO &#9;&#9;&#9;&#9;INC(i) &#9;&#9;&#9;END &#9;&#9;END Blanks; &#9;BEGIN &#9;&#9;re &#58;&#61; 1; i &#58;&#61; 0; &#9;&#9;REPEAT &#9;&#9;&#9;end &#58;&#61; TRUE; Blanks; j &#58;&#61; i; &#9;&#9;&#9;IF CAP(t&#91;i&#93;) &#61; "R" THEN &#9;&#9;&#9;&#9;IF CAP(t&#91;i+1&#93;) &#61; "E" THEN &#9;&#9;&#9;&#9;&#9;INC(i, 2); Blanks; &#9;&#9;&#9;&#9;&#9;IF t&#91;i&#93; &#61; "&#58;" THEN &#9;&#9;&#9;&#9;&#9;&#9;INC(i); INC(re); end &#58;&#61; FALSE &#9;&#9;&#9;&#9;&#9;ELSIF t&#91;i&#93; &#61; "(" THEN &#9;&#9;&#9;&#9;&#9;&#9;INC(i); p &#58;&#61; SHORT(i); oldre &#58;&#61; re; &#9;&#9;&#9;&#9;&#9;&#9;Strings.StrToIntPos(t, re, p); &#9;&#9;&#9;&#9;&#9;&#9;IF re &#62; 0 THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;i &#58;&#61; p; Blanks; &#9;&#9;&#9;&#9;&#9;&#9;&#9;IF t&#91;i&#93; &#61; ")" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;INC(i); Blanks; &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;IF t&#91;i&#93; &#61; "&#58;" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;INC(i) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;INC(re); end &#58;&#61; FALSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;re &#58;&#61; oldre &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END &#9;&#9;UNTIL end; &#9;&#9;IF t&#91;j&#93; &#61; 0X THEN &#9;&#9;&#9;RETURN &#9;&#9;ELSIF re &#62; 1 THEN &#9;&#9;&#9;Texts.WriteString(W, "Re ("); Texts.WriteInt(W, re, 0); Texts.WriteString(W, ")&#58; ") &#9;&#9;ELSE &#9;&#9;&#9;Texts.WriteString(W, "Re&#58; ") &#9;&#9;END; &#9;&#9;WHILE t&#91;j&#93; # 0X DO &#9;&#9;&#9;Texts.Write(W, t&#91;j&#93;); INC(j) &#9;&#9;END &#9;END Re; &#9;PROCEDURE ReplyText(T&#58; Texts.Text); &#9;&#9;VAR &#9;&#9;&#9;S&#58; Streams.Stream; &#9;&#9;&#9;R&#58; Texts.Reader; &#9;&#9;&#9;h&#58; MIME.Header; &#9;&#9;&#9;t&#58; ARRAY BufLen OF CHAR; &#9;&#9;&#9;pos, len&#58; LONGINT; &#9;&#9;&#9;ch&#58; CHAR; &#9;BEGIN &#9;&#9;pos &#58;&#61; 0; Texts.OpenReader(R, T, pos); &#9;&#9;Texts.Read(R, ch); &#9;&#9;WHILE &#126;R.eot &#38; (ch &#60;&#61; " ") &#38; &#126;(R.lib IS Fonts.Font) DO &#9;&#9;&#9;Texts.Read(R, ch); INC(pos) &#9;&#9;END; &#9;&#9;Texts.WriteString(W, "To&#58; "); &#9;&#9;S &#58;&#61; TextStreams.OpenReader(T, pos); &#9;&#9;MIME.ReadHeader(S, NIL, h, len); &#9;&#9;pos &#58;&#61; MIME.FindField(h, "Reply-To"); &#9;&#9;IF pos &#60; 0 THEN &#9;&#9;&#9;pos &#58;&#61; MIME.FindField(h, "From") &#9;&#9;END; &#9;&#9;MIME.ExtractEMail(h, pos, t); &#9;&#9;Texts.WriteString(W, t); Texts.WriteLn(W); &#9;&#9;pos &#58;&#61; MIME.FindField(h, "Subject"); &#9;&#9;MIME.ExtractValue(h, pos, t); &#9;&#9;Texts.WriteString(W, "Subject&#58; "); Re(W, t); &#9;&#9;Texts.WriteLn(W) &#9;END ReplyText; &#9;PROCEDURE CiteText*(VAR W&#58; Texts.Writer; T&#58; Texts.Text; beg, end&#58; LONGINT); &#9;&#9;VAR &#9;&#9;&#9;R&#58; Texts.Reader; &#9;&#9;&#9;lib&#58; Objects.Library; &#9;&#9;&#9;col, voff&#58; SHORTINT; &#9;&#9;&#9;ch&#58; CHAR; &#9;BEGIN &#9;&#9;lib &#58;&#61; W.lib; col &#58;&#61; W.col; voff &#58;&#61; W.voff; &#9;&#9;Texts.OpenReader(R, T, beg); Texts.Read(R, ch); &#9;&#9;Texts.WriteString(W, "&#62; "); &#9;&#9;WHILE &#126;R.eot &#38; (Texts.Pos(R) &#60;&#61; end) DO &#9;&#9;&#9;Texts.SetFont(W, R.lib); Texts.SetColor(W, R.col); Texts.SetOffset(W, R.voff); &#9;&#9;&#9;Texts.Write(W, ch); &#9;&#9;&#9;IF (R.lib IS Fonts.Font) &#38; (ch &#61; Strings.CR) &#38; (Texts.Pos(R) &#60; end) THEN &#9;&#9;&#9;&#9;Texts.SetFont(W, lib); Texts.SetColor(W, col); Texts.SetOffset(W, voff); &#9;&#9;&#9;&#9;Texts.WriteString(W, "&#62; ") &#9;&#9;&#9;END; &#9;&#9;&#9;Texts.Read(R, ch) &#9;&#9;END; &#9;&#9;Texts.SetFont(W, lib); Texts.SetColor(W, col); Texts.SetOffset(W, voff) &#9;END CiteText; &#9;PROCEDURE Reply*; &#9;&#9;VAR &#9;&#9;&#9;S&#58; Attributes.Scanner; &#9;&#9;&#9;T, text&#58; Texts.Text; &#9;&#9;&#9;D&#58; Documents.Document; &#9;&#9;&#9;obj&#58; Objects.Object; &#9;&#9;&#9;beg, end, time&#58; LONGINT; &#9;&#9;&#9;fnt&#58; Objects.Library; &#9;&#9;&#9;str&#58; AdrString; &#9;BEGIN &#9;&#9;fnt &#58;&#61; W.lib; Texts.SetFont(W, textFnt); &#9;&#9;NEW(T); Texts.Open(T, ""); &#9;&#9;Attributes.OpenScanner(S, Oberon.Par.text,Oberon.Par.pos); &#9;&#9;Attributes.Scan(S); &#9;&#9;IF S.class &#61; Attributes.Int THEN &#9;&#9;&#9;IF (S.i &#62;&#61; 0) &#38; (S.i &#60; noMsgs) THEN &#9;&#9;&#9;&#9;Copy(heap, msgs&#91;S.i&#93;.replyTo, str); &#9;&#9;&#9;&#9;Texts.WriteString(W, "To&#58; "); Texts.WriteString(W, str); Texts.WriteLn(W); &#9;&#9;&#9;&#9;Copy(heap, msgs&#91;S.i&#93;.subject, str); &#9;&#9;&#9;&#9;Texts.WriteString(W, "Subject&#58; "); Re(W, str); Texts.WriteLn(W) &#9;&#9;&#9;END &#9;&#9;ELSIF Desktops.IsInMenu(Gadgets.context) THEN &#9;&#9;&#9;D &#58;&#61; Desktops.CurDoc(Gadgets.context); &#9;&#9;&#9;Links.GetLink(D.dsc, "Model", obj); &#9;&#9;&#9;IF (obj # NIL) &#38; (obj IS Texts.Text) THEN &#9;&#9;&#9;&#9;ReplyText(obj(Texts.Text)); &#9;&#9;&#9;&#9;text &#58;&#61; NIL; time &#58;&#61; -1; &#9;&#9;&#9;&#9;Oberon.GetSelection(text, beg, end, time); &#9;&#9;&#9;&#9;IF text &#61; obj THEN &#9;&#9;&#9;&#9;&#9;Texts.WriteLn(W); &#9;&#9;&#9;&#9;&#9;CiteText(W, text, beg, end) &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END &#9;&#9;ELSE &#9;&#9;&#9;Texts.WriteString(W, "To&#58; "); Texts.WriteLn(W); &#9;&#9;&#9;Texts.WriteString(W, "Subject&#58; "); Texts.WriteLn(W) &#9;&#9;END; &#9;&#9;Texts.WriteLn(W); Texts.Append(T, W.buf); &#9;&#9;ShowText("Mail.Out.Text", T, FALSE); &#9;&#9;Texts.SetFont(W, fnt) &#9;END Reply; &#9;PROCEDURE DoTopic(set&#58; BOOLEAN); &#9;&#9;VAR &#9;&#9;&#9;S&#58; Attributes.Scanner; &#9;&#9;&#9;mailL&#58; Objects.Object; &#9;&#9;&#9;topic&#58; Topic; &#9;&#9;&#9;C&#58; ListRiders.ConnectMsg; &#9;&#9;&#9;R&#58; ListRiders.Rider; &#9;&#9;&#9;mLine&#58; ListGadgets.Line; &#9;&#9;&#9;h&#58; MIME.Header; &#9;&#9;&#9;no&#58; LONGINT; &#9;BEGIN &#9;&#9;Attributes.OpenScanner(S, Oberon.Par.text,Oberon.Par.pos); &#9;&#9;Attributes.Scan(S); &#9;&#9;IF S.class IN &#123;Attributes.Name, Attributes.String&#125; THEN &#9;&#9;&#9;mailL &#58;&#61; FindObj(S.s); &#9;&#9;&#9;Attributes.Scan(S); &#9;&#9;&#9;IF S.class IN &#123;Attributes.Name, Attributes.String&#125; THEN &#9;&#9;&#9;&#9;topic &#58;&#61; topics; &#9;&#9;&#9;&#9;WHILE (topic # NIL) &#38; (topic.topic.s # S.s) DO &#9;&#9;&#9;&#9;&#9;topic &#58;&#61; topic.next &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;IF topic # NIL THEN &#9;&#9;&#9;&#9;&#9;WITH mailL&#58; ListGadgets.Frame DO &#9;&#9;&#9;&#9;&#9;&#9;C.R &#58;&#61; NIL; Objects.Stamp(C); mailL.obj.handle(mailL.obj, C); R &#58;&#61; C.R; &#9;&#9;&#9;&#9;&#9;&#9;mLine &#58;&#61; mailL.lines; &#9;&#9;&#9;&#9;&#9;&#9;REPEAT &#9;&#9;&#9;&#9;&#9;&#9;&#9;IF mLine.sel THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;R.do.Seek(R, mLine.key); &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;no &#58;&#61; R.d(ListRiders.Int).i; &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;IF set THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;INCL(msgs&#91;no&#93;.topics, topic.no) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;EXCL(msgs&#91;no&#93;.topics, topic.no) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ScanHeader(no, h); &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;WriteStatus(h, no) &#9;&#9;&#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;&#9;&#9;mLine &#58;&#61; mLine.next &#9;&#9;&#9;&#9;&#9;&#9;UNTIL mLine &#61; mailL.lines; &#9;&#9;&#9;&#9;&#9;&#9;Files.Close(msgsF); Gadgets.Update(msgList) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END &#9;&#9;END &#9;END DoTopic; &#9;PROCEDURE SetTopic*; &#9;BEGIN &#9;&#9;DoTopic(TRUE) &#9;END SetTopic; &#9;PROCEDURE ClearTopic*; &#9;BEGIN &#9;&#9;DoTopic(FALSE) &#9;END ClearTopic; &#9;(* Move mail(s) from current topic to another topic. It&#39;s only allowed if your current query is a topic. (es, 22.10.2000 *) &#9; &#9;PROCEDURE MoveTopic*; &#9;&#9;VAR &#9;&#9;&#9;S&#58; Attributes.Scanner; &#9;&#9;&#9;mailL&#58; Objects.Object; &#9;&#9;&#9;topic&#58; Topic; &#9;&#9;&#9;C&#58; ListRiders.ConnectMsg; &#9;&#9;&#9;R&#58; ListRiders.Rider; &#9;&#9;&#9;mLine&#58; ListGadgets.Line; &#9;&#9;&#9;h&#58; MIME.Header; &#9;&#9;&#9;currentNo, no&#58; LONGINT; &#9;&#9;&#9;queryObj&#58; Objects.Object; &#9;&#9;&#9;queryStr&#58; ARRAY 128 OF CHAR; &#9;&#9;&#9; &#9;&#9;PROCEDURE GetTopicNo(queryStr&#58; ARRAY OF CHAR)&#58; LONGINT; &#9;&#9;VAR topic&#58; Topic; name&#58; ARRAY 128 OF CHAR; i, j&#58; LONGINT; ch&#58; CHAR; &#9;&#9;BEGIN &#9;&#9;&#9;(* drop &#39;topic&#61;"&#39; and &#39;"&#39; from query string *) &#9;&#9;&#9;IF queryStr&#91;6&#93; &#61; 22X THEN ch &#58;&#61; 22X; j &#58;&#61; 7 ELSE ch &#58;&#61; " "; j &#58;&#61; 6 END; &#9;&#9;&#9;i &#58;&#61; 0; &#9;&#9;&#9;WHILE (queryStr&#91;j&#93; # ch) &#38; (queryStr&#91;j&#93; # 0X) DO &#9;&#9;&#9;&#9;name&#91;i&#93; &#58;&#61; queryStr&#91;j&#93;; INC(i); INC(j) &#9;&#9;&#9;END; &#9;&#9;&#9;name&#91;i&#93; &#58;&#61; 0X; &#9;&#9;&#9;topic &#58;&#61; topics; &#9;&#9;&#9;WHILE (topic # NIL) &#38; (topic.topic.s # name) DO &#9;&#9;&#9;&#9;topic &#58;&#61; topic.next &#9;&#9;&#9;END; &#9;&#9;&#9;IF topic # NIL THEN &#9;&#9;&#9;&#9;RETURN topic.no &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;COPY("Topic not found&#58; ", queryStr); Strings.Append(queryStr, name); ShowStatus(queryStr); &#9;&#9;&#9;&#9;RETURN -1 &#9;&#9;&#9;END &#9;&#9;END GetTopicNo; &#9;BEGIN &#9;&#9;queryObj &#58;&#61; FindObj("Query"); &#9;&#9;IF queryObj # NIL THEN &#9;&#9;&#9;Attributes.GetString(queryObj, "Value", queryStr); &#9;&#9;&#9;IF &#126;Strings.Prefix("topic&#61;", queryStr) THEN &#9;&#9;&#9;&#9;ShowStatus("must show single topic first"); RETURN &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;currentNo &#58;&#61; GetTopicNo(queryStr) &#9;&#9;&#9;END &#9;&#9;ELSE &#9;&#9;&#9;ShowStatus("no query value found"); RETURN; &#9;&#9;END; &#9;&#9;Attributes.OpenScanner(S, Oberon.Par.text,Oberon.Par.pos); &#9;&#9;Attributes.Scan(S); &#9;&#9;IF S.class IN &#123;Attributes.Name, Attributes.String&#125; THEN &#9;&#9;&#9;mailL &#58;&#61; FindObj(S.s); &#9;&#9;&#9;Attributes.Scan(S); &#9;&#9;&#9;IF S.class IN &#123;Attributes.Name, Attributes.String&#125; THEN &#9;&#9;&#9;&#9;topic &#58;&#61; topics; &#9;&#9;&#9;&#9;WHILE (topic # NIL) &#38; (topic.topic.s # S.s) DO &#9;&#9;&#9;&#9;&#9;topic &#58;&#61; topic.next &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;IF topic # NIL THEN &#9;&#9;&#9;&#9;&#9;WITH mailL&#58; ListGadgets.Frame DO &#9;&#9;&#9;&#9;&#9;&#9;C.R &#58;&#61; NIL; Objects.Stamp(C); mailL.obj.handle(mailL.obj, C); R &#58;&#61; C.R; &#9;&#9;&#9;&#9;&#9;&#9;mLine &#58;&#61; mailL.lines; &#9;&#9;&#9;&#9;&#9;&#9;REPEAT &#9;&#9;&#9;&#9;&#9;&#9;&#9;IF mLine.sel THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;R.do.Seek(R, mLine.key); &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;no &#58;&#61; R.d(ListRiders.Int).i; &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;IF currentNo &#62; -1 THEN EXCL(msgs&#91;no&#93;.topics, currentNo) END; &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;INCL(msgs&#91;no&#93;.topics, topic.no); &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ScanHeader(no, h); &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;WriteStatus(h, no) &#9;&#9;&#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;&#9;&#9;mLine &#58;&#61; mLine.next &#9;&#9;&#9;&#9;&#9;&#9;UNTIL mLine &#61; mailL.lines; &#9;&#9;&#9;&#9;&#9;&#9;Files.Close(msgsF); Gadgets.Update(msgList) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END &#9;&#9;END &#9;END MoveTopic; &#9; &#9;PROCEDURE QueryTopic*; &#9;&#9;VAR &#9;&#9;&#9;S&#58; Attributes.Scanner; &#9;&#9;&#9;obj&#58; Objects.Object; &#9;&#9;&#9;topic&#58; Topic; &#9;&#9;&#9;query&#58; QueryString; &#9;BEGIN &#9;&#9;Attributes.OpenScanner(S, Oberon.Par.text,Oberon.Par.pos); &#9;&#9;Attributes.Scan(S); &#9;&#9;IF S.class IN &#123;Attributes.Name, Attributes.String&#125; THEN &#9;&#9;&#9;obj &#58;&#61; FindObj(S.s); &#9;&#9;&#9;Attributes.Scan(S); &#9;&#9;&#9;IF S.class IN &#123;Attributes.Name, Attributes.String&#125; THEN &#9;&#9;&#9;&#9;topic &#58;&#61; topics; &#9;&#9;&#9;&#9;WHILE (topic # NIL) &#38; (topic.topic.s # S.s) DO &#9;&#9;&#9;&#9;&#9;topic &#58;&#61; topic.next &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;IF (obj # NIL) &#38; (topic # NIL) THEN &#9;&#9;&#9;&#9;&#9;query &#58;&#61; &#39;topic&#61;"&#39;; &#9;&#9;&#9;&#9;&#9;Strings.Append(query, topic.topic.s); &#9;&#9;&#9;&#9;&#9;Strings.AppendCh(query, &#39;"&#39;); &#9;&#9;&#9;&#9;&#9;Attributes.SetString(obj, "Value", query); &#9;&#9;&#9;&#9;&#9;Gadgets.Update(obj) &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END &#9;&#9;END &#9;END QueryTopic; &#9;PROCEDURE SaveIndexFile; &#9;&#9;VAR f&#58; Files.File; r&#58; Files.Rider; i, t, d, len&#58; LONGINT; new&#58; BOOLEAN; &#9;BEGIN &#9;&#9;ASSERT(msgsF # NIL); &#9;&#9;f &#58;&#61; Files.Old(IndexFile); new &#58;&#61; FALSE; &#9;&#9;IF f &#61; NIL THEN f &#58;&#61; Files.New(IndexFile); new &#58;&#61; TRUE END; &#9;&#9;IF f # NIL THEN &#9;&#9;&#9;Files.GetDate(msgsF, t, d); len &#58;&#61; Files.Length(msgsF); &#9;&#9;&#9;Files.Set(r, f, 0); &#9;&#9;&#9;Files.WriteLInt(r, IndexFileKey); &#9;&#9;&#9;Files.WriteNum(r, t); Files.WriteNum(r, d); Files.WriteNum(r, len); &#9;&#9;&#9;Files.WriteNum(r, noMsgs); Files.WriteNum(r, delMsgs); &#9;&#9;&#9;Files.WriteNum(r, LEN(msgs^));	(* size of msgs array *) &#9;&#9;&#9;Files.WriteNum(r, noMsgs); &#9;&#9;&#9;FOR i &#58;&#61; 0 TO noMsgs - 1 DO &#9;&#9;&#9;&#9;Files.WriteNum(r, i); &#9;&#9;&#9;&#9;Files.WriteNum(r, msgs&#91;i&#93;.pos); &#9;&#9;&#9;&#9;Files.WriteNum(r, msgs&#91;i&#93;.len); &#9;&#9;&#9;&#9;Files.WriteNum(r, msgs&#91;i&#93;.state); &#9;&#9;&#9;&#9;Files.WriteNum(r, msgs&#91;i&#93;.stamp); &#9;&#9;&#9;&#9;Files.WriteSet(r, msgs&#91;i&#93;.flags); &#9;&#9;&#9;&#9;Files.WriteSet(r, msgs&#91;i&#93;.topics); &#9;&#9;&#9;&#9;Files.WriteNum(r, msgs&#91;i&#93;.date); &#9;&#9;&#9;&#9;Files.WriteNum(r, msgs&#91;i&#93;.time); &#9;&#9;&#9;&#9;Files.WriteLInt(r, msgs&#91;i&#93;.replyTo); &#9;&#9;&#9;&#9;Files.WriteLInt(r, msgs&#91;i&#93;.subject); &#9;&#9;&#9;&#9;Files.WriteLInt(r, 0FFFFFFFFH) &#9;&#9;&#9;END; &#9;&#9;&#9;Store(r, heap); &#9;&#9;&#9;IF new THEN Files.Register(f) ELSE Files.Close(f) END &#9;&#9;END &#9;END SaveIndexFile; &#9; &#9;PROCEDURE TryLoadIndexFile&#58; BOOLEAN; &#9;&#9;VAR &#9;&#9;&#9;f&#58; Files.File; r&#58; Files.Rider; &#9;&#9;&#9;t0, d0, len0, key, i, t, d, len&#58; LONGINT; &#9;&#9; &#9;&#9;PROCEDURE err(n&#58; INTEGER); &#9;&#9;BEGIN &#9;&#9;&#9;Texts.WriteString(W, "Reparsing Mail&#58; "); &#9;&#9;&#9;CASE n OF &#9;&#9;&#9;&#32;&#9;1&#58; Texts.WriteString(W, "(1) MailMessages.idx not found."); &#9;&#9;&#9;&#124;	2&#58; Texts.WriteString(W, "(2) MailMessages not open."); &#9;&#9;&#9;&#124;	3&#58; Texts.WriteString(W, "(3) MailMessages.idx lacks proper key."); &#9;&#9;&#9;&#9;&#9;Texts.WriteHex(W, key); Texts.WriteString(W, " # "); Texts.WriteHex(W, IndexFileKey); &#9;&#9;&#9;&#124;	4&#58; Texts.WriteString(W, "(4) MailMessages has changed since index was saved."); &#9;&#9;&#9;&#124;	5&#58; Texts.WriteString(W, "(5) MailMessages.idx internally corrupted."); &#9;&#9;&#9;&#124;	6&#58; Texts.WriteString(W, "(6) MailMessages.idx sequence number corrupted."); &#9;&#9;&#9;&#124;	7&#58; Texts.WriteString(W, "(7) MailMessages.idx internally corrupted.") &#9;&#9;&#9;&#124;	8&#58; Texts.WriteString(W, "(8) readcount is too large."); &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;Texts.WriteString(W, "Unknown problem."); &#9;&#9;&#9;END; &#9;&#9;&#9;Texts.WriteLn(W); &#9;&#9;&#9;Texts.Append(Oberon.Log, W.buf); &#9;&#9;END err; &#9;&#9;&#9; &#9;BEGIN &#9;&#9;f &#58;&#61; Files.Old(IndexFile); &#9;&#9;IF f &#61; NIL THEN err(1); RETURN FALSE END; &#9;&#9;IF msgsF &#61; NIL THEN msgsF &#58;&#61; Files.Old(MsgFile) END; &#9;&#9;IF msgsF &#61; NIL THEN err(2); RETURN FALSE END; &#9;&#9;Files.Set(r, f, 0); &#9;&#9;Files.ReadLInt(r, key); &#9;&#9;IF key # IndexFileKey THEN err(3); RETURN FALSE END; &#9;&#9;Files.GetDate(msgsF, t, d); len &#58;&#61; Files.Length(msgsF); &#9;&#9;Files.ReadNum(r, t0); Files.ReadNum(r, d0); Files.ReadNum(r, len0); &#9;&#9;IF (* (t0 # t) OR (d0 # d) OR *) (len0 # len) THEN err(4); RETURN FALSE END; &#9;&#9;Files.ReadNum(r, noMsgs); Files.ReadNum(r, delMsgs); &#9;&#9;Files.ReadNum(r, len); 	(* size of msgs array *) &#9;&#9;IF (msgs &#61; NIL) OR (LEN(msgs^) &#60; len) THEN NEW(msgs, len) END; &#9;&#9;Files.ReadNum(r, len);	(* number of elements to be read *) &#9;&#9;IF (len &#62; LEN(msgs^)) THEN err(8); RETURN FALSE END; &#9;&#9;FOR i &#58;&#61; 0 TO len - 1 DO &#9;&#9;&#9;Files.ReadNum(r, t); IF (t # i) THEN err(6); RETURN FALSE END; &#9;&#9;&#9;Files.ReadNum(r, msgs&#91;i&#93;.pos); &#9;&#9;&#9;Files.ReadNum(r, msgs&#91;i&#93;.len); &#9;&#9;&#9;Files.ReadNum(r, msgs&#91;i&#93;.state); &#9;&#9;&#9;Files.ReadNum(r, msgs&#91;i&#93;.stamp); &#9;&#9;&#9;Files.ReadSet(r, msgs&#91;i&#93;.flags); &#9;&#9;&#9;Files.ReadSet(r, msgs&#91;i&#93;.topics); &#9;&#9;&#9;Files.ReadNum(r, msgs&#91;i&#93;.date); &#9;&#9;&#9;Files.ReadNum(r, msgs&#91;i&#93;.time); &#9;&#9;&#9;Files.ReadLInt(r, msgs&#91;i&#93;.replyTo); &#9;&#9;&#9;Files.ReadLInt(r, msgs&#91;i&#93;.subject); &#9;&#9;&#9;Files.ReadLInt(r, d); IF (d # 0FFFFFFFFH) THEN err(7); RETURN FALSE END &#9;&#9;END; &#9;&#9;Load(r, heap); &#9;&#9;RETURN TRUE &#9;END TryLoadIndexFile; &#9;PROCEDURE LoadMsgs; &#9;&#9;VAR &#9;&#9;&#9;R&#58; Files.Rider; &#9;&#9;&#9;buf&#58; ARRAY BufLen+4 OF CHAR; &#9;&#9;&#9;pat&#58; ARRAY 16 OF CHAR; &#9;&#9;&#9;div&#58; ARRAY 16 OF LONGINT; &#9;&#9;&#9;pos, patlen&#58; LONGINT; &#9;&#9;&#9; &#9;&#9;PROCEDURE Search(VAR pos&#58; LONGINT); &#9;&#9;&#9;VAR &#9;&#9;&#9;&#9;i&#58; LONGINT; &#9;&#9;&#9;&#9;ch&#58; CHAR; &#9;&#9;BEGIN &#9;&#9;&#9;ch &#58;&#61; buf&#91;pos&#93;; i &#58;&#61; 0; &#9;&#9;&#9;WHILE (i # patlen) &#38; (ch # 0X) DO &#9;&#9;&#9;&#9;IF ch &#61; pat&#91;i&#93; THEN &#9;&#9;&#9;&#9;&#9;INC(i); &#9;&#9;&#9;&#9;&#9;IF i &#60; patlen THEN &#9;&#9;&#9;&#9;&#9;&#9;INC(pos); ch &#58;&#61; buf&#91;pos&#93; &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;ELSIF i &#61; 0 THEN &#9;&#9;&#9;&#9;&#9;INC(pos); ch &#58;&#61; buf&#91;pos&#93; &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;i &#58;&#61; i - div&#91;i&#93; &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END; &#9;&#9;&#9;IF i # patlen THEN &#9;&#9;&#9;&#9;pos &#58;&#61; -1 &#9;&#9;&#9;END &#9;&#9;END Search; &#9;&#9;PROCEDURE AddMsgs; &#9;&#9;&#9;VAR i, j&#58; LONGINT; &#9;&#9;BEGIN &#9;&#9;&#9;i &#58;&#61; 0; Search(i); &#9;&#9;&#9;WHILE i &#62;&#61; 0 DO &#9;&#9;&#9;&#9;j &#58;&#61; i; &#9;&#9;&#9;&#9;WHILE buf&#91;i&#93; &#62;&#61; " " DO &#9;&#9;&#9;&#9;&#9;INC(i) &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;WHILE (buf&#91;i&#93; # 0X) &#38; (buf&#91;i&#93; &#60; " ") DO &#9;&#9;&#9;&#9;&#9;INC(i) &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;IF buf&#91;i&#93; # 0X THEN &#9;&#9;&#9;&#9;&#9;IF (noMsgs &#62; 0) &#38; (msgs&#91;noMsgs-1&#93;.len &#60;&#61; 0) THEN &#9;&#9;&#9;&#9;&#9;&#9;msgs&#91;noMsgs-1&#93;.len &#58;&#61; pos+j-4-msgs&#91;noMsgs-1&#93;.pos &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;AddMsgHead(pos+i) &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;pos &#58;&#61; pos+j-8; &#9;&#9;&#9;&#9;&#9;Files.Set(R, msgsF, pos); &#9;&#9;&#9;&#9;&#9;RETURN &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;Search(i) &#9;&#9;&#9;END; &#9;&#9;&#9;IF &#126;R.eof THEN &#9;&#9;&#9;&#9;i &#58;&#61; BufLen-5; &#9;&#9;&#9;&#9;WHILE (i &#60; BufLen) &#38; (buf&#91;i&#93; # Strings.LF) DO &#9;&#9;&#9;&#9;&#9;INC(i) &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;IF i &#60; BufLen THEN &#9;&#9;&#9;&#9;&#9;pos &#58;&#61; pos+i; &#9;&#9;&#9;&#9;&#9;Files.Set(R, msgsF, pos); &#9;&#9;&#9;&#9;&#9;RETURN &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END; &#9;&#9;&#9;INC(pos, BufLen); &#9;&#9;&#9;Files.Set(R, msgsF, pos) &#9;&#9;END AddMsgs; &#9;&#9;PROCEDURE CalcDispVec; &#9;&#9;&#9;VAR i, j, d&#58; LONGINT; &#9;&#9;BEGIN &#9;&#9;&#9;i &#58;&#61; 1; d &#58;&#61; 1; &#9;&#9;&#9;WHILE i &#60;&#61; patlen DO &#9;&#9;&#9;&#9;j &#58;&#61; 0; &#9;&#9;&#9;&#9;WHILE ((j + d) &#60; patlen) &#38; (pat&#91;j&#93; &#61; pat&#91;j+d&#93;) DO &#9;&#9;&#9;&#9;&#9;INC(j) &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;WHILE i &#60;&#61; j + d DO &#9;&#9;&#9;&#9;&#9;div&#91;i&#93; &#58;&#61; d; INC(i) &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;INC(d) &#9;&#9;&#9;END &#9;&#9;END CalcDispVec; &#9;BEGIN &#9;&#9;uidls &#58;&#61; NIL; Open(heap); &#9;&#9;IF &#126;TryLoadIndexFile THEN &#9;&#9;&#9;Texts.WriteString(W, "Generating mail index..."); Texts.WriteLn(W); &#9;&#9;&#9;Texts.Append(Oberon.Log, W.buf); &#9;&#9;&#9;pat &#58;&#61; "xx.xxFrom "; &#9;&#9;&#9;pat&#91;0&#93; &#58;&#61; Strings.CR; pat&#91;1&#93; &#58;&#61; Strings.LF; &#9;&#9;&#9;pat&#91;3&#93; &#58;&#61; Strings.CR; pat&#91;4&#93; &#58;&#61; Strings.LF; &#9;&#9;&#9;patlen &#58;&#61; 0; WHILE pat&#91;patlen&#93; # 0X DO INC(patlen) END; &#9;&#9;&#9;NEW(msgs, 128); noMsgs &#58;&#61; 0; delMsgs &#58;&#61; 0; &#9;&#9;&#9;msgsF &#58;&#61; Files.Old(MsgFile); &#9;&#9;&#9;IF msgsF &#61; NIL THEN &#9;&#9;&#9;&#9;msgsF &#58;&#61; Files.New(MsgFile); Files.Register(msgsF) &#9;&#9;&#9;END; &#9;&#9;&#9;CalcDispVec; &#9;&#9;&#9;Files.Set(R, msgsF, 0); buf&#91;BufLen&#93; &#58;&#61; 0X; &#9;&#9;&#9;IF Files.Length(msgsF) &#62; 7 THEN &#9;&#9;&#9;&#9;AddMsgHead(7) &#9;&#9;&#9;END; &#9;&#9;&#9;pos &#58;&#61; 0; Files.ReadBytes(R, buf, BufLen); &#9;&#9;&#9;WHILE &#126;R.eof DO &#9;&#9;&#9;&#9;AddMsgs; &#9;&#9;&#9;&#9;Files.ReadBytes(R, buf, BufLen) &#9;&#9;&#9;END; &#9;&#9;&#9;buf&#91;BufLen-R.res&#93; &#58;&#61; 0X; &#9;&#9;&#9;AddMsgs; &#9;&#9;&#9;IF (noMsgs &#62; 0) &#38; (msgs&#91;noMsgs-1&#93;.len &#60;&#61; 0) THEN &#9;&#9;&#9;&#9;msgs&#91;noMsgs-1&#93;.len &#58;&#61; Files.Length(msgsF)-1-msgs&#91;noMsgs-1&#93;.pos &#9;&#9;&#9;END; &#9;&#9;&#9;SaveIndexFile &#9;&#9;END &#9;END LoadMsgs; &#9;PROCEDURE LoadTopics; &#9;&#9;VAR &#9;&#9;&#9;key, value&#58; ValueString; &#9;&#9;&#9;topic&#58; Topic; &#9;&#9;&#9;i&#58; LONGINT; &#9;BEGIN &#9;&#9;topics &#58;&#61; NIL; i &#58;&#61; 0; &#9;&#9;LOOP &#9;&#9;&#9;key &#58;&#61; "Topic"; Strings.IntToStr(i, value); &#9;&#9;&#9;Strings.Append(key, value); &#9;&#9;&#9;IF NetTools.QueryString(key, value) THEN &#9;&#9;&#9;&#9;NEW(topic); topic.next &#58;&#61; topics; topics &#58;&#61; topic; topic.no &#58;&#61; i; INC(i); &#9;&#9;&#9;&#9;NEW(topic.topic); COPY(value, topic.topic.s) &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;EXIT &#9;&#9;&#9;END &#9;&#9;END; &#9;&#9;IF topicList # NIL THEN &#9;&#9;&#9;Gadgets.Update(topicList) &#9;&#9;END &#9;END LoadTopics; &#9;PROCEDURE *Key(R&#58; ListRiders.Rider)&#58; LONGINT; &#9;BEGIN &#9;&#9;RETURN R(Rider).key &#9;END Key; &#9;PROCEDURE *Seek(R&#58; ListRiders.Rider; key&#58; LONGINT); &#9;BEGIN &#9;&#9;WITH R&#58; Rider DO &#9;&#9;&#9;R.key &#58;&#61; key; R.pos &#58;&#61; 0; R.sortPos &#58;&#61; 0; &#9;&#9;&#9;WHILE (R.pos &#60; noMsgs) &#38; (msgs&#91;R.pos&#93;.pos # key) DO &#9;&#9;&#9;&#9;INC(R.pos) &#9;&#9;&#9;END; &#9;&#9;&#9;IF R.pos &#62;&#61; noMsgs THEN &#9;&#9;&#9;&#9;R.key &#58;&#61; -1; R.pos &#58;&#61; -1; R.sortPos &#58;&#61; -1; R.eol &#58;&#61; TRUE; &#9;&#9;&#9;&#9;RETURN &#9;&#9;&#9;END; &#9;&#9;&#9;IF R.sort # NIL THEN &#9;&#9;&#9;&#9;WHILE msgs&#91;R.sort&#91;R.sortPos&#93;&#93;.pos # key DO &#9;&#9;&#9;&#9;&#9;INC(R.sortPos) &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END; &#9;&#9;&#9;R.d(ListRiders.Int).i &#58;&#61; R.pos &#9;&#9;END &#9;END Seek; &#9;PROCEDURE *Pos(R&#58; ListRiders.Rider)&#58; LONGINT; &#9;&#9;VAR pos&#58; LONGINT; &#9;BEGIN &#9;&#9;WITH R&#58; Rider DO &#9;&#9;&#9;IF R.sort # NIL THEN &#9;&#9;&#9;&#9;pos &#58;&#61; R.sortPos &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;pos &#58;&#61; R.pos &#9;&#9;&#9;END; &#9;&#9;&#9;IF &#126;R.ascending THEN &#9;&#9;&#9;&#9;pos &#58;&#61; R.noMsgs-pos-1 &#9;&#9;&#9;END; &#9;&#9;&#9;RETURN pos &#9;&#9;END &#9;END Pos; &#9;PROCEDURE *Set(R&#58; ListRiders.Rider; pos&#58; LONGINT); &#9;BEGIN &#9;&#9;WITH R&#58; Rider DO &#9;&#9;&#9;IF (pos &#62;&#61; 0) &#38; (pos &#60; R.noMsgs) THEN &#9;&#9;&#9;&#9;IF &#126;R.ascending THEN &#9;&#9;&#9;&#9;&#9;pos &#58;&#61; R.noMsgs-pos-1 &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;IF R.sort # NIL THEN &#9;&#9;&#9;&#9;&#9;R.pos &#58;&#61; R.sort&#91;pos&#93;; R.sortPos &#58;&#61; pos &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;R.pos &#58;&#61; pos; R.sortPos &#58;&#61; 0 &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;R.key &#58;&#61; msgs&#91;R.pos&#93;.pos &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;R.key &#58;&#61; -1; R.pos &#58;&#61; -1; R.sortPos &#58;&#61; -1; R.eol &#58;&#61; TRUE &#9;&#9;&#9;END; &#9;&#9;&#9;R.d(ListRiders.Int).i &#58;&#61; R.pos &#9;&#9;END &#9;END Set; &#9;PROCEDURE *GetState(R&#58; ListRiders.Rider)&#58; LONGINT; &#9;BEGIN &#9;&#9;RETURN msgs&#91;R(Rider).pos&#93;.state &#9;END GetState; &#9;PROCEDURE *SetState(R&#58; ListRiders.Rider; state&#58; LONGINT); &#9;BEGIN &#9;&#9;msgs&#91;R(Rider).pos&#93;.state &#58;&#61; state &#9;END SetState; &#9;PROCEDURE *GetStamp(R&#58; ListRiders.Rider)&#58; LONGINT; &#9;BEGIN &#9;&#9;RETURN msgs&#91;R(Rider).pos&#93;.stamp &#9;END GetStamp; &#9;PROCEDURE *SetStamp(R&#58; ListRiders.Rider; stamp&#58; LONGINT); &#9;BEGIN &#9;&#9;msgs&#91;R(Rider).pos&#93;.stamp &#58;&#61; stamp &#9;END SetStamp; &#9;PROCEDURE *Write(R&#58; ListRiders.Rider; d&#58; ListRiders.Data); &#9;END Write; &#9;PROCEDURE *WriteLink(R, linkR&#58; ListRiders.Rider); &#9;END WriteLink; &#9;PROCEDURE *DeleteLink(R, linkR&#58; ListRiders.Rider); &#9;&#9;VAR no&#58; LONGINT; &#9;BEGIN &#9;&#9;R &#58;&#61; linkR; &#9;&#9;WITH R&#58; Rider DO &#9;&#9;&#9;no &#58;&#61; R.pos; &#9;&#9;&#9;IF &#126;(Deleted IN msgs&#91;no&#93;.flags) THEN &#9;&#9;&#9;&#9;DeleteMessage(no); &#9;&#9;&#9;&#9;Files.Close(msgsF); collect; &#9;&#9;&#9;&#9;(*R.do.Set(R, no)*) &#9;&#9;&#9;END &#9;&#9;END &#9;END DeleteLink; &#9;PROCEDURE *Desc(R, old&#58; ListRiders.Rider)&#58; ListRiders.Rider; &#9;END Desc; &#9;PROCEDURE Less(VAR i, j&#58; MsgHead; sortBy&#58; INTEGER)&#58; BOOLEAN; &#9;BEGIN &#9;&#9;CASE sortBy OF &#9;&#9;&#9;SortByDateTime&#58; IF i.date &#60; j.date THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;RETURN TRUE &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSIF i.date &#61; j.date THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;IF i.time &#60; j.time THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;RETURN TRUE &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSIF i.time &#62; j.time THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;RETURN FALSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSIF i.date &#62; j.date THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;RETURN FALSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#124;SortByReplyTo&#58; IF i.replyTo &#60; j.replyTo THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;RETURN TRUE &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSIF i.replyTo &#62; j.replyTo THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;RETURN FALSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#124;SortBySubject&#58; IF i.subject &#60; j.subject THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;RETURN TRUE &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSIF i.subject &#62; j.subject THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;RETURN FALSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;END; &#9;&#9;RETURN i.pos &#60; j.pos &#9;END Less; &#9;PROCEDURE QuickSort(sort&#58; SortList; noMsgs&#58; LONGINT; sortBy&#58; INTEGER); &#9;&#9;PROCEDURE Sort(lo, hi&#58; LONGINT); &#9;&#9;&#9;VAR &#9;&#9;&#9;&#9;i, j&#58; LONGINT; &#9;&#9;&#9;&#9;m, t&#58; LONGINT; &#9;&#9;BEGIN &#9;&#9;&#9;IF lo &#60; hi THEN &#9;&#9;&#9;&#9;i &#58;&#61; lo; j &#58;&#61; hi; &#9;&#9;&#9;&#9;m &#58;&#61; sort&#91;(lo + hi) DIV 2&#93;; &#9;&#9;&#9;&#9;REPEAT &#9;&#9;&#9;&#9;&#9;WHILE Less(msgs&#91;sort&#91;i&#93;&#93;, msgs&#91;m&#93;, sortBy) DO INC(i) END; &#9;&#9;&#9;&#9;&#9;WHILE Less(msgs&#91;m&#93;, msgs&#91;sort&#91;j&#93;&#93;, sortBy) DO DEC(j) END; &#9;&#9;&#9;&#9;&#9;IF i &#60;&#61; j THEN &#9;&#9;&#9;&#9;&#9;&#9;t &#58;&#61; sort&#91;i&#93;; sort&#91;i&#93; &#58;&#61; sort&#91;j&#93;; sort&#91;j&#93; &#58;&#61; t; &#9;&#9;&#9;&#9;&#9;&#9;INC(i); DEC(j) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;UNTIL i &#62; j; &#9;&#9;&#9;&#9;Sort(lo, j); Sort(i, hi) &#9;&#9;&#9;END &#9;&#9;END Sort; &#9;BEGIN &#9;&#9;Sort(0, noMsgs - 1) &#9;END QuickSort; &#9;PROCEDURE ToISO(VAR value&#58; ARRAY OF CHAR); &#9;&#9;VAR i&#58; LONGINT; &#9;BEGIN &#9;&#9;i &#58;&#61; 0; &#9;&#9;WHILE value&#91;i&#93; # 0X DO &#9;&#9;&#9;value&#91;i&#93; &#58;&#61; Strings.OberonToISO&#91;ORD(value&#91;i&#93;)&#93;; &#9;&#9;&#9;INC(i) &#9;&#9;END &#9;END ToISO; &#9; &#9;PROCEDURE CompileQuery(VAR Q&#58; Query); &#9;&#9;CONST &#9;&#9;&#9;eof &#61; 0; colon &#61; 9; name &#61; 10; string &#61; 11; number &#61; 12; dot &#61; 13; today &#61; 14; now &#61; 15; &#9;&#9;&#9;read &#61; 16; unread &#61; 17; &#9;&#9;VAR &#9;&#9;&#9;str, keyw&#58; ValueString; &#9;&#9;&#9;pos, num, d, m, y, h, s, sym&#58; LONGINT; &#9;&#9;&#9;ch&#58; CHAR; &#9;&#9;PROCEDURE GetName; &#9;&#9;&#9;VAR j&#58; LONGINT; &#9;&#9;BEGIN &#9;&#9;&#9;j &#58;&#61; 0; &#9;&#9;&#9;WHILE (ch # 0X) &#38; (Strings.IsAlpha(ch) OR (ch &#61; ".") OR (ch &#61; "@") OR Strings.IsDigit(ch)) DO &#9;&#9;&#9;&#9;str&#91;j&#93; &#58;&#61; ch; INC(j); ch &#58;&#61; Q.query&#91;pos&#93;; INC(pos) &#9;&#9;&#9;END; &#9;&#9;&#9;str&#91;j&#93; &#58;&#61; 0X &#9;&#9;END GetName; &#9;&#9;PROCEDURE GetString; &#9;&#9;&#9;VAR j&#58; LONGINT; &#9;&#9;BEGIN &#9;&#9;&#9;j &#58;&#61; 0; &#9;&#9;&#9;WHILE (ch # 0X) &#38; (ch # 022X) DO &#9;&#9;&#9;&#9;str&#91;j&#93; &#58;&#61; ch; INC(j); ch &#58;&#61; Q.query&#91;pos&#93;; INC(pos) &#9;&#9;&#9;END; &#9;&#9;&#9;IF ch &#61; 022X THEN &#9;&#9;&#9;&#9;ch &#58;&#61; Q.query&#91;pos&#93;; INC(pos) &#9;&#9;&#9;END; &#9;&#9;&#9;str&#91;j&#93; &#58;&#61; 0X &#9;&#9;END GetString; &#9;&#9;PROCEDURE GetNumber; &#9;&#9;BEGIN &#9;&#9;&#9;num &#58;&#61; 0; &#9;&#9;&#9;WHILE (ch # 0X) &#38; Strings.IsDigit(ch) DO &#9;&#9;&#9;&#9;num &#58;&#61; 10*num+ORD(ch)-ORD("0"); ch &#58;&#61; Q.query&#91;pos&#93;; INC(pos) &#9;&#9;&#9;END &#9;&#9;END GetNumber; &#9;&#9;PROCEDURE Next; &#9;&#9;BEGIN &#9;&#9;&#9;WHILE (ch # 0X) &#38; (ch &#60;&#61; " ") DO &#9;&#9;&#9;&#9;ch &#58;&#61; Q.query&#91;pos&#93;; INC(pos) &#9;&#9;&#9;END; &#9;&#9;&#9;CASE ch OF &#9;&#9;&#9;&#9;"&#61;"&#58; sym &#58;&#61; eq; ch &#58;&#61; Q.query&#91;pos&#93;; INC(pos) &#9;&#9;&#9;&#9;&#124;"&#58;"&#58; sym &#58;&#61; colon; ch &#58;&#61; Q.query&#91;pos&#93;; INC(pos) &#9;&#9;&#9;&#9;&#124;"&#60;"&#58; ch &#58;&#61; Q.query&#91;pos&#93;; INC(pos); &#9;&#9;&#9;&#9;&#9;&#9;IF ch &#61; "&#61;" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;ch &#58;&#61; Q.query&#91;pos&#93;; INC(pos); sym &#58;&#61; leq &#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;sym &#58;&#61; le &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#124;"&#62;"&#58; ch &#58;&#61; Q.query&#91;pos&#93;; INC(pos); &#9;&#9;&#9;&#9;&#9;&#9;IF ch &#61; "&#61;" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;ch &#58;&#61; Q.query&#91;pos&#93;; INC(pos); sym &#58;&#61; geq &#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;sym &#58;&#61; ge &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#124;"&#38;"&#58; sym &#58;&#61; and; ch &#58;&#61; Q.query&#91;pos&#93;; INC(pos) &#9;&#9;&#9;&#9;&#124;"."&#58; sym &#58;&#61; dot; ch &#58;&#61; Q.query&#91;pos&#93;; INC(pos) &#9;&#9;&#9;&#9;&#124;"#"&#58; sym &#58;&#61; neq; ch &#58;&#61; Q.query&#91;pos&#93;; INC(pos) &#9;&#9;&#9;&#9;&#124;"A" .. "Z", "a" .. "z"&#58; GetName; Strings.Upper(str, keyw); &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;IF (keyw &#61; "FROM") OR (keyw &#61; "REPLYTO") THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;sym &#58;&#61; from &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSIF keyw &#61; "SUBJECT" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;sym &#58;&#61; subject &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSIF keyw &#61; "DATE" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;sym &#58;&#61; date &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSIF keyw &#61; "NOW" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;sym &#58;&#61; now &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSIF keyw &#61; "TEXT" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;sym &#58;&#61; text &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSIF keyw &#61; "TIME" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;sym &#58;&#61; time &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSIF keyw &#61; "TOPIC" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;sym &#58;&#61; topic &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSIF keyw &#61; "TODAY" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;sym &#58;&#61; today &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSIF keyw &#61; "OR" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;sym &#58;&#61; or &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSIF keyw &#61; "READ" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;sym &#58;&#61; read &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSIF keyw &#61; "UNREAD" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;sym &#58;&#61; unread &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;sym &#58;&#61; name &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#124;"0" .. "9"&#58; sym &#58;&#61; number; GetNumber &#9;&#9;&#9;&#9;&#124;022X&#58; sym &#58;&#61; string; ch &#58;&#61; Q.query&#91;pos&#93;; INC(pos); GetString &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;sym &#58;&#61; eof &#9;&#9;&#9;END &#9;&#9;END Next; &#9;&#9;PROCEDURE Check(sy&#58; LONGINT); &#9;&#9;BEGIN &#9;&#9;&#9;IF sy &#61; sym THEN &#9;&#9;&#9;&#9;Next &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;Q.error &#58;&#61; TRUE &#9;&#9;&#9;END &#9;&#9;END Check; &#9;&#9;PROCEDURE Factor&#58; Cond; &#9;&#9;&#9;VAR &#9;&#9;&#9;&#9;cond&#58; Cond; &#9;&#9;&#9;&#9;topicp&#58; Topic; &#9;&#9;BEGIN &#9;&#9;&#9;NEW(cond); cond.field &#58;&#61; sym; &#9;&#9;&#9;IF sym IN &#123;from, subject, topic, text&#125; THEN &#9;&#9;&#9;&#9;Next; &#9;&#9;&#9;&#9;IF sym IN &#123;eq, neq&#125; THEN &#9;&#9;&#9;&#9;&#9;cond.op &#58;&#61; sym &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;Q.error &#58;&#61; TRUE &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;Next; &#9;&#9;&#9;&#9;IF sym IN &#123;name, string&#125; THEN &#9;&#9;&#9;&#9;&#9;COPY(str, cond.val); Next; &#9;&#9;&#9;&#9;&#9;IF cond.field &#61; topic THEN &#9;&#9;&#9;&#9;&#9;&#9;topicp &#58;&#61; topics; &#9;&#9;&#9;&#9;&#9;&#9;WHILE (topicp # NIL) &#38; &#126;Strings.CAPCompare(cond.val, topicp.topic.s) DO &#9;&#9;&#9;&#9;&#9;&#9;&#9;topicp &#58;&#61; topicp.next &#9;&#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;&#9;IF topicp # NIL THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;cond.time &#58;&#61; topicp.no &#9;&#9;&#9;&#9;&#9;&#9;ELSIF cond.val &#61; "" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;cond.field &#58;&#61; notopic &#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;Q.error &#58;&#61; TRUE &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSIF cond.field &#61; text THEN &#9;&#9;&#9;&#9;&#9;&#9;ToISO(cond.val) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;Q.error &#58;&#61; TRUE &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSIF sym &#61; date THEN &#9;&#9;&#9;&#9;Next; &#9;&#9;&#9;&#9;IF sym IN &#123;eq, leq, le, geq, ge, neq&#125; THEN &#9;&#9;&#9;&#9;&#9;cond.op &#58;&#61; sym &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;Q.error &#58;&#61; TRUE &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;Next; &#9;&#9;&#9;&#9;IF sym &#61; today THEN &#9;&#9;&#9;&#9;&#9;MIME.GetClock(cond.time, cond.date); Next &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;Check(number); &#9;&#9;&#9;&#9;&#9;d &#58;&#61; num; &#9;&#9;&#9;&#9;&#9;Check(dot); &#9;&#9;&#9;&#9;&#9;Check(number); &#9;&#9;&#9;&#9;&#9;m &#58;&#61; num; &#9;&#9;&#9;&#9;&#9;Check(dot); &#9;&#9;&#9;&#9;&#9;Check(number); &#9;&#9;&#9;&#9;&#9;y &#58;&#61; num; &#9;&#9;&#9;&#9;&#9;IF y &#62;&#61; 1900 THEN DEC(y, 1900) END;	(* assume user typed 4-digit year *) &#9;&#9;&#9;&#9;&#9;cond.date &#58;&#61; (y*16+m)*32+d; &#9;&#9;&#9;&#9;&#9;cond.time &#58;&#61; Dates.ToTime(SHORT(Dates.TimeDiff DIV 60), SHORT(Dates.TimeDiff MOD 60), 0); &#9;&#9;&#9;&#9;&#9;Dates.AddTime(cond.time, cond.date, -Dates.TimeDiff * 60) &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSIF sym &#61; time THEN &#9;&#9;&#9;&#9;Next; &#9;&#9;&#9;&#9;IF sym IN &#123;eq, leq, le, geq, ge, neq&#125; THEN &#9;&#9;&#9;&#9;&#9;cond.op &#58;&#61; sym &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;Q.error &#58;&#61; TRUE &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;Next; &#9;&#9;&#9;&#9;IF sym &#61; now THEN &#9;&#9;&#9;&#9;&#9;MIME.GetClock(cond.time, cond.date); Next &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;Check(number); &#9;&#9;&#9;&#9;&#9;h &#58;&#61; num; &#9;&#9;&#9;&#9;&#9;Check(colon); &#9;&#9;&#9;&#9;&#9;Check(number); &#9;&#9;&#9;&#9;&#9;m &#58;&#61; num; &#9;&#9;&#9;&#9;&#9;IF sym &#61; colon THEN &#9;&#9;&#9;&#9;&#9;&#9;Check(colon); &#9;&#9;&#9;&#9;&#9;&#9;Check(number); &#9;&#9;&#9;&#9;&#9;&#9;s &#58;&#61; num &#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;s &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;cond.time &#58;&#61; h*1000H + m*40H + s; &#9;&#9;&#9;&#9;&#9;cond.time &#58;&#61; Dates.AddMinute(cond.time, -SHORT(Dates.TimeDiff)) &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSIF sym IN &#123;read, unread&#125; THEN &#9;&#9;&#9;&#9;cond.field &#58;&#61; readFlag; cond.op &#58;&#61; eq; &#9;&#9;&#9;&#9;COPY(keyw, cond.val); Next &#9;&#9;&#9;ELSIF sym IN &#123;name, string&#125; THEN &#9;&#9;&#9;&#9;cond.field &#58;&#61; text; cond.op &#58;&#61; eq; &#9;&#9;&#9;&#9;COPY(str, cond.val); ToISO(cond.val); &#9;&#9;&#9;&#9;Next &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;Q.error &#58;&#61; TRUE &#9;&#9;&#9;END; &#9;&#9;&#9;IF &#126;Q.error THEN &#9;&#9;&#9;&#9;cond.next &#58;&#61; Q.conds; Q.conds &#58;&#61; cond &#9;&#9;&#9;END; &#9;&#9;&#9;RETURN cond &#9;&#9;END Factor; &#9;&#9;PROCEDURE Term&#58; Cond; &#9;&#9;&#9;VAR &#9;&#9;&#9;&#9;factor&#58; Cond; &#9;&#9;&#9;&#9;term&#58; Node; &#9;&#9;BEGIN &#9;&#9;&#9;factor &#58;&#61; Factor; &#9;&#9;&#9;WHILE (sym &#61; and) &#38; &#126;Q.error DO &#9;&#9;&#9;&#9;NEW(term); term.field &#58;&#61; MAX(INTEGER); term.op &#58;&#61; and; &#9;&#9;&#9;&#9;term.next &#58;&#61; Q.conds; Q.conds &#58;&#61; term; term.left &#58;&#61; factor; &#9;&#9;&#9;&#9;Next; term.right &#58;&#61; Factor; factor &#58;&#61; term &#9;&#9;&#9;END; &#9;&#9;&#9;RETURN factor &#9;&#9;END Term; &#9;&#9;PROCEDURE Expr&#58; Cond; &#9;&#9;&#9;VAR &#9;&#9;&#9;&#9;term&#58; Cond; &#9;&#9;&#9;&#9;expr&#58; Node; &#9;&#9;BEGIN &#9;&#9;&#9;term &#58;&#61; Term; &#9;&#9;&#9;WHILE (sym &#61; or) &#38; &#126;Q.error DO &#9;&#9;&#9;&#9;NEW(expr); expr.field &#58;&#61; MAX(INTEGER); expr.op &#58;&#61; or; &#9;&#9;&#9;&#9;expr.next &#58;&#61; Q.conds; Q.conds &#58;&#61; expr; expr.left &#58;&#61; term; &#9;&#9;&#9;&#9;Next; expr.right &#58;&#61; Expr; term &#58;&#61; expr &#9;&#9;&#9;END; &#9;&#9;&#9;RETURN term &#9;&#9;END Expr; &#9;BEGIN &#9;&#9;Q.conds &#58;&#61; NIL; Q.root &#58;&#61; NIL; Q.error &#58;&#61; FALSE; &#9;&#9;ch &#58;&#61; Q.query&#91;0&#93;; pos &#58;&#61; 1; &#9;&#9;Next; Q.root &#58;&#61; Expr; &#9;&#9;IF (sym # eof) OR Q.error THEN &#9;&#9;&#9;Q.conds &#58;&#61; NIL; Q.root &#58;&#61; NIL; &#9;&#9;&#9;Q.error &#58;&#61; TRUE &#9;&#9;END &#9;END CompileQuery; &#9;PROCEDURE TextSearch(cond&#58; Cond; no&#58; LONGINT)&#58; BOOLEAN; &#9;&#9;CONST &#9;&#9;&#9;MaxPatLen &#61; 128; &#9;&#9;VAR &#9;&#9;&#9;i, pos, end, sPatLen&#58; LONGINT; &#9;&#9;&#9;R&#58; Files.Rider; &#9;&#9;&#9;sPat&#58; ARRAY MaxPatLen OF CHAR; &#9;&#9;&#9;sDv&#58; ARRAY MaxPatLen + 1 OF LONGINT; &#9;&#9;&#9;ch&#58; CHAR; &#9;&#9;PROCEDURE CalcDispVec; &#9;&#9;&#9;VAR i, j, d&#58; LONGINT; &#9;&#9;BEGIN &#9;&#9;&#9;i &#58;&#61; 1; d &#58;&#61; 1; &#9;&#9;&#9;WHILE i &#60;&#61; sPatLen DO &#9;&#9;&#9;&#9;j &#58;&#61; 0; &#9;&#9;&#9;&#9;WHILE ((j + d) &#60; sPatLen) &#38; (sPat&#91;j&#93; &#61; sPat&#91;j+d&#93;) DO &#9;&#9;&#9;&#9;&#9;INC(j) &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;WHILE i &#60;&#61; j + d DO &#9;&#9;&#9;&#9;&#9;sDv&#91;i&#93; &#58;&#61; d; INC(i) &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;INC(d) &#9;&#9;&#9;END &#9;&#9;END CalcDispVec; &#9;BEGIN &#9;&#9;COPY(cond.val, sPat); &#9;&#9;sPatLen &#58;&#61; Strings.Length(sPat); &#9;&#9;CalcDispVec; &#9;&#9;IF sPatLen &#62; 0 THEN &#9;&#9;&#9;pos &#58;&#61; msgs&#91;no&#93;.pos; Files.Set(R, msgsF, pos); &#9;&#9;&#9;Files.Read(R, ch); INC(pos); &#9;&#9;&#9;end &#58;&#61; msgs&#91;no&#93;.pos+msgs&#91;no&#93;.len; &#9;&#9;&#9;i &#58;&#61; 0; &#9;&#9;&#9;WHILE (i # sPatLen) &#38; (pos &#60;&#61; end) DO &#9;&#9;&#9;&#9;IF ch &#61; sPat&#91;i&#93; THEN &#9;&#9;&#9;&#9;&#9;INC(i); &#9;&#9;&#9;&#9;&#9;IF i &#60; sPatLen THEN &#9;&#9;&#9;&#9;&#9;&#9;Files.Read(R, ch); INC(pos) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;ELSIF i &#61; 0 THEN &#9;&#9;&#9;&#9;&#9;Files.Read(R, ch); INC(pos) &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;i &#58;&#61; i - sDv&#91;i&#93; &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END &#9;&#9;ELSE &#9;&#9;&#9;i &#58;&#61; -1 &#9;&#9;END; &#9;&#9;RETURN i &#61; sPatLen &#9;END TextSearch; &#9;PROCEDURE MatchQuery(VAR Q&#58; Query; no&#58; LONGINT; VAR msg&#58; MsgHead)&#58; BOOLEAN; &#9;&#9;VAR &#9;&#9;&#9;cond&#58; Cond; &#9;&#9;&#9;pos, i&#58; LONGINT; &#9;&#9;&#9;str&#58; ValueString; &#9;&#9;&#9;txt&#58; BOOLEAN; &#9;BEGIN &#9;&#9;cond &#58;&#61; Q.conds; txt &#58;&#61; FALSE; &#9;&#9;WHILE cond # NIL DO (* evaluate simple conditions *) &#9;&#9;&#9;cond.eval &#58;&#61; TRUE; &#9;&#9;&#9;CASE cond.field OF &#9;&#9;&#9;&#9;from&#58; pos &#58;&#61; 0; Copy(heap, msg.replyTo, str); &#9;&#9;&#9;&#9;&#9;&#9;Strings.Search(cond.val, str, pos); &#9;&#9;&#9;&#9;&#9;&#9;cond.value &#58;&#61; ((cond.op &#61; eq) &#38; (pos &#62;&#61; 0)) OR ((cond.op &#61; neq) &#38; (pos &#60; 0)) &#9;&#9;&#9;&#9;&#124;subject&#58; pos &#58;&#61; 0; Copy(heap, msg.subject, str); &#9;&#9;&#9;&#9;&#9;&#9;&#9;Strings.Search(cond.val, str, pos); &#9;&#9;&#9;&#9;&#9;&#9;&#9;cond.value &#58;&#61; ((cond.op &#61; eq) &#38; (pos &#62;&#61; 0)) OR ((cond.op &#61; neq) &#38; (pos &#60; 0)) &#9;&#9;&#9;&#9;&#124;topic&#58; cond.value &#58;&#61; ((cond.op &#61; eq) &#38; (cond.time IN msg.topics)) OR ((cond.op &#61; neq) &#38; &#126;(cond.time IN msg.topics)) &#9;&#9;&#9;&#9;&#124;notopic&#58; cond.value &#58;&#61; msg.topics &#61; &#123;&#125; &#9;&#9;&#9;&#9;&#124;date&#58; CASE cond.op OF &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;eq&#58; cond.value &#58;&#61; msg.date &#61; cond.date &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#124;leq&#58; cond.value &#58;&#61; msg.date &#60;&#61; cond.date &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#124;le&#58; cond.value &#58;&#61; msg.date &#60; cond.date &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#124;geq&#58; cond.value &#58;&#61; msg.date &#62;&#61; cond.date &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#124;ge&#58; cond.value &#58;&#61; msg.date &#62; cond.date &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#124;neq&#58; cond.value &#58;&#61; msg.date # cond.date &#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#124;time&#58; CASE cond.op OF &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;eq&#58; cond.value &#58;&#61; msg.time &#61; cond.time &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#124;leq&#58; cond.value &#58;&#61; msg.time &#60;&#61; cond.time &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#124;le&#58; cond.value &#58;&#61; msg.time &#60; cond.time &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#124;geq&#58; cond.value &#58;&#61; msg.time &#62;&#61; cond.time &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#124;ge&#58; cond.value &#58;&#61; msg.time &#62; cond.time &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#124;neq&#58; cond.value &#58;&#61; msg.time # cond.time &#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#124;readFlag&#58; cond.value &#58;&#61; (Read IN msg.flags) &#61; (cond.val&#91;0&#93; &#61; "R") &#9;&#9;&#9;&#9;&#124;text&#58; txt &#58;&#61; TRUE; cond.value &#58;&#61; FALSE; cond.eval &#58;&#61; FALSE &#9;&#9;&#9;ELSE (* or, and *) &#9;&#9;&#9;&#9;cond.value &#58;&#61; FALSE; cond.eval &#58;&#61; FALSE &#9;&#9;&#9;END; &#9;&#9;&#9;cond &#58;&#61; cond.next &#9;&#9;END; &#9;&#9;LOOP &#9;&#9;&#9;REPEAT &#9;&#9;&#9;&#9;i &#58;&#61; 0; cond &#58;&#61; Q.conds; (* evaluate logical ops *) &#9;&#9;&#9;&#9;WHILE cond # NIL DO &#9;&#9;&#9;&#9;&#9;IF cond IS Node THEN &#9;&#9;&#9;&#9;&#9;&#9;WITH cond&#58; Node DO &#9;&#9;&#9;&#9;&#9;&#9;&#9;IF &#126;cond.eval THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;IF cond.left.eval &#38; cond.right.eval THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;IF cond.op &#61; or THEN (* OR *) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;cond.value &#58;&#61; cond.left.value OR cond.right.value &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSIF cond.op &#61; and THEN (* AND *) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;cond.value &#58;&#61; cond.left.value &#38; cond.right.value &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;HALT(99) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;cond.eval &#58;&#61; TRUE; INC(i) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSIF (cond.op &#61; or) &#38; ((cond.left.eval &#38; cond.left.value) OR (cond.right.eval &#38; cond.right.value)) THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;cond.value &#58;&#61; TRUE; cond.eval &#58;&#61; TRUE; cond.left.eval &#58;&#61; TRUE; cond.right.eval &#58;&#61; TRUE; INC(i) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSIF (cond.op &#61; and) &#38; ((cond.left.eval &#38; &#126;cond.left.value) OR (cond.right.eval &#38; &#126;cond.right.value)) THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;cond.value &#58;&#61; FALSE; cond.eval &#58;&#61; TRUE; cond.left.eval &#58;&#61; TRUE; cond.right.eval &#58;&#61; TRUE; INC(i) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;cond &#58;&#61; cond.next &#9;&#9;&#9;&#9;END &#9;&#9;&#9;UNTIL Q.root.eval OR (i &#60;&#61; 0); &#9;&#9;&#9;IF Q.root.eval THEN &#9;&#9;&#9;&#9;RETURN Q.root.value &#9;&#9;&#9;ELSIF txt THEN &#9;&#9;&#9;&#9;cond &#58;&#61; Q.conds; &#9;&#9;&#9;&#9;WHILE cond # NIL DO &#9;&#9;&#9;&#9;&#9;IF (cond.field &#61; text) &#38; &#126;cond.eval THEN &#9;&#9;&#9;&#9;&#9;&#9;cond.value &#58;&#61; TextSearch(cond, no); &#9;&#9;&#9;&#9;&#9;&#9;cond.eval &#58;&#61; TRUE &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;cond &#58;&#61; cond.next &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;HALT(99) &#9;&#9;&#9;END &#9;&#9;END &#9;END MatchQuery; &#9;PROCEDURE ConnectRider(VAR M&#58; ListRiders.ConnectMsg; base&#58; Model); &#9;&#9;VAR &#9;&#9;&#9;R&#58; Rider; &#9;&#9;&#9;int&#58; ListRiders.Int; &#9;&#9;&#9;i&#58; LONGINT; &#9;&#9;&#9;Q&#58; Query; &#9;BEGIN &#9;&#9;NEW(R); R.do &#58;&#61; mMethod; R.sort &#58;&#61; NIL; &#9;&#9;R.noMsgs &#58;&#61; noMsgs; Q.error &#58;&#61; FALSE; &#9;&#9;IF M IS ConnectMsg THEN &#9;&#9;&#9;WITH M&#58; ConnectMsg DO &#9;&#9;&#9;&#9;R.ascending &#58;&#61; M.ascending; &#9;&#9;&#9;&#9;IF ((M.sortBy &#62; 0) OR (M.query # "")) &#38; (noMsgs &#62; 0) THEN &#9;&#9;&#9;&#9;&#9;NEW(R.sort, noMsgs); &#9;&#9;&#9;&#9;&#9;FOR i &#58;&#61; 0 TO noMsgs-1 DO &#9;&#9;&#9;&#9;&#9;&#9;R.sort&#91;i&#93; &#58;&#61; i &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;IF M.query # "" THEN &#9;&#9;&#9;&#9;&#9;&#9;COPY(M.query, Q.query); &#9;&#9;&#9;&#9;&#9;&#9;CompileQuery(Q); &#9;&#9;&#9;&#9;&#9;&#9;IF &#126;Q.error THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;R.noMsgs &#58;&#61; 0; &#9;&#9;&#9;&#9;&#9;&#9;&#9;FOR i &#58;&#61; 0 TO noMsgs-1 DO &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;IF MatchQuery(Q, i, msgs&#91;i&#93;) THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;R.sort&#91;R.noMsgs&#93; &#58;&#61; i; INC(R.noMsgs) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;ShowStatus("error in query") &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;IF M.sortBy &#62; 0 THEN &#9;&#9;&#9;&#9;&#9;&#9;QuickSort(R.sort, R.noMsgs, M.sortBy) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END &#9;&#9;ELSE &#9;&#9;&#9;R.ascending &#58;&#61; FALSE &#9;&#9;END; &#9;&#9;R.base &#58;&#61; base; R.dsc &#58;&#61; FALSE; R.eol &#58;&#61; FALSE; &#9;&#9;NEW(int); R.d &#58;&#61; int; &#9;&#9;R.do.Set(R, 0); M.R &#58;&#61; R &#9;END ConnectRider; &#9;PROCEDURE *ModelHandler(obj&#58; Objects.Object; VAR M&#58; Objects.ObjMsg); &#9;BEGIN &#9;&#9;WITH obj&#58; Model DO &#9;&#9;&#9;IF M IS Objects.AttrMsg THEN &#9;&#9;&#9;&#9;WITH M&#58; Objects.AttrMsg DO &#9;&#9;&#9;&#9;&#9;IF (M.id &#61; Objects.get) &#38; (M.name &#61; "Gen") THEN &#9;&#9;&#9;&#9;&#9;&#9;M.class &#58;&#61; Objects.String; M.s &#58;&#61; "Mail.NewModel"; M.res &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;Gadgets.objecthandle(obj, M) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSIF M IS Objects.CopyMsg THEN &#9;&#9;&#9;&#9;M(Objects.CopyMsg).obj &#58;&#61; obj &#9;&#9;&#9;ELSIF M IS ListRiders.ConnectMsg THEN &#9;&#9;&#9;&#9;ConnectRider(M(ListRiders.ConnectMsg), obj) &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;Gadgets.objecthandle(obj, M) &#9;&#9;&#9;END &#9;&#9;END &#9;END ModelHandler; &#9;PROCEDURE NewModel*; &#9;BEGIN &#9;&#9;&#32;Objects.NewObj &#58;&#61; msgList &#9;END NewModel; &#9;PROCEDURE *GetRider(F&#58; ListGadgets.Frame; new&#58; BOOLEAN)&#58; ListRiders.Rider; &#9;&#9;VAR &#9;&#9;&#9;M&#58; ConnectMsg; &#9;&#9;&#9;i&#58; LONGINT; &#9;BEGIN &#9;&#9;IF ((F.R &#61; NIL) OR new) &#38; (F.obj # NIL) THEN &#9;&#9;&#9;IF F IS Frame THEN &#9;&#9;&#9;&#9;WITH F&#58; Frame DO &#9;&#9;&#9;&#9;&#9;Attributes.GetString(F.sortBy, "Value", M.query); &#9;&#9;&#9;&#9;&#9;Strings.Upper(M.query, M.query); &#9;&#9;&#9;&#9;&#9;IF (M.query &#61; "DATE") OR (M.query &#61; "TIME") THEN &#9;&#9;&#9;&#9;&#9;&#9;M.sortBy &#58;&#61; SortByDateTime &#9;&#9;&#9;&#9;&#9;ELSIF M.query &#61; "REPLYTO" THEN &#9;&#9;&#9;&#9;&#9;&#9;M.sortBy &#58;&#61; SortByReplyTo &#9;&#9;&#9;&#9;&#9;ELSIF M.query &#61; "SUBJECT" THEN &#9;&#9;&#9;&#9;&#9;&#9;M.sortBy &#58;&#61; SortBySubject &#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;Attributes.GetInt(F.sortBy, "Value", i); &#9;&#9;&#9;&#9;&#9;&#9;M.sortBy &#58;&#61; SHORT(i) &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;Attributes.GetBool(F.ascending, "Value", M.ascending); &#9;&#9;&#9;&#9;&#9;Attributes.GetString(F.query, "Value", M.query) &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;M.sortBy &#58;&#61; 0; M.ascending &#58;&#61; FALSE; M.query &#58;&#61; "" &#9;&#9;&#9;END; &#9;&#9;&#9;M.R &#58;&#61; NIL; Objects.Stamp(M); &#9;&#9;&#9;F.obj.handle(F.obj, M); F.R &#58;&#61; M.R &#9;&#9;END; &#9;&#9;RETURN F.R &#9;END GetRider; &#9;PROCEDURE *FormatLine(F&#58; ListGadgets.Frame; R&#58; ListRiders.Rider; L&#58; ListGadgets.Line); &#9;BEGIN &#9;&#9;L.w &#58;&#61; F.W; L.h &#58;&#61; F.fnt.height; L.dsr &#58;&#61; -F.fnt.minY; L.dx &#58;&#61; 0 &#9;END FormatLine; &#9;PROCEDURE *DisplayLine(F&#58; ListGadgets.Frame; Q&#58; Display3.Mask; x, y, w, h&#58; INTEGER; R&#58; ListRiders.Rider; L&#58; ListGadgets.Line); &#9;&#9;VAR &#9;&#9;&#9;Q2&#58; Display3.Mask; &#9;&#9;&#9;str&#58; ValueString; &#9;&#9;&#9;textC, sw, sh, dsr&#58; INTEGER; &#9;BEGIN &#9;&#9;Display3.ReplConst(Q, F.backC, x, y, w-50, h, Display.replace); &#9;&#9;WITH R&#58; Rider DO &#9;&#9;&#9;IF Read IN msgs&#91;R.pos&#93;.flags THEN &#9;&#9;&#9;&#9;textC &#58;&#61; F.textC &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;textC &#58;&#61; Display3.red &#9;&#9;&#9;END; &#9;&#9;&#9;Copy(heap, msgs&#91;R.pos&#93;.subject, str); &#9;&#9;&#9;Display3.String(Q, textC, x + (w DIV 3) + 8, y + L.dsr, F.fnt, str, Display.paint); &#9;&#9;&#9;Display3.Copy(Q, Q2); Display3.AdjustMask(Q2, x, y, w DIV 3, h); &#9;&#9;&#9;Copy(heap, msgs&#91;R.pos&#93;.replyTo, str); &#9;&#9;&#9;Display3.String(Q2, textC, x, y + L.dsr, F.fnt, str, Display.paint); &#9;&#9;&#9;Strings.DateToStr(msgs&#91;R.pos&#93;.date, str); &#9;&#9;&#9;Display3.StringSize(str, F.fnt, sw, sh, dsr); &#9;&#9;&#9;Display3.ReplConst(Q, F.backC, x+w-sw-8, y, sw+8, h, Display.replace); &#9;&#9;&#9;Display3.String(Q, textC, x+w-sw, y + L.dsr, F.fnt, str, Display.paint) &#9;&#9;END &#9;END DisplayLine; &#9;PROCEDURE CopyFrame(VAR M&#58; Objects.CopyMsg; from, to&#58; Frame); &#9;BEGIN &#9;&#9;ListGadgets.CopyFrame(M, from, to); &#9;&#9;to.query &#58;&#61; Gadgets.CopyPtr(M, from.query); &#9;&#9;to.sortBy &#58;&#61; Gadgets.CopyPtr(M, from.sortBy); &#9;&#9;to.ascending &#58;&#61; Gadgets.CopyPtr(M, from.ascending) &#9;END CopyFrame; &#9;PROCEDURE Update(F&#58; Frame); &#9;&#9;VAR M&#58; Gadgets.UpdateMsg; &#9;BEGIN &#9;&#9;M.F &#58;&#61; F; M.obj &#58;&#61; F.obj; &#9;&#9;Display.Broadcast(M); &#9;&#9;SetVPos(F) &#9;END Update; &#9;PROCEDURE *FrameHandler(F&#58; Objects.Object; VAR M&#58; Objects.ObjMsg); &#9;&#9;VAR &#9;&#9;&#9;F1&#58; Frame; &#9;&#9;&#9;obj&#58; Objects.Object; &#9;&#9;&#9;ver&#58; INTEGER; &#9;BEGIN &#9;&#9;WITH F&#58; Frame DO &#9;&#9;&#9;IF M IS Display.FrameMsg THEN &#9;&#9;&#9;&#9;WITH M&#58; Display.FrameMsg DO &#9;&#9;&#9;&#9;&#9;IF (M.F &#61; NIL) OR (M.F &#61; F) THEN &#9;&#9;&#9;&#9;&#9;&#9;IF M IS Gadgets.UpdateMsg THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;WITH M&#58; Gadgets.UpdateMsg DO &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;IF M.obj # NIL THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;IF M.obj &#61; F.query THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;Update(F) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSIF M.obj &#61; F.sortBy THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;Update(F) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSIF M.obj &#61; F.ascending THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;Update(F) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ListGadgets.FrameHandler(F, M) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ListGadgets.FrameHandler(F, M) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;ListGadgets.FrameHandler(F, M) &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;ListGadgets.FrameHandler(F, M) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSIF M IS Objects.AttrMsg THEN &#9;&#9;&#9;&#9;WITH M&#58; Objects.AttrMsg DO &#9;&#9;&#9;&#9;&#9;IF (M.id &#61; Objects.get) &#38; (M.name &#61; "Gen") THEN &#9;&#9;&#9;&#9;&#9;&#9;M.class &#58;&#61; Objects.String; M.s &#58;&#61; "Mail.NewFrame"; M.res &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;ListGadgets.FrameHandler(F, M) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSIF M IS Objects.LinkMsg THEN &#9;&#9;&#9;&#9;WITH M&#58; Objects.LinkMsg DO &#9;&#9;&#9;&#9;&#9;IF M.id &#61; Objects.get THEN &#9;&#9;&#9;&#9;&#9;&#9;IF M.name &#61; "SortBy" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;M.obj &#58;&#61; F.sortBy; M.res &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;&#9;ELSIF M.name &#61; "Ascending" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;M.obj &#58;&#61; F.ascending; M.res &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;&#9;ELSIF M.name &#61; "Query" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;M.obj &#58;&#61; F.query; M.res &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;ListGadgets.FrameHandler(F, M) &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSIF M.id &#61; Objects.set THEN &#9;&#9;&#9;&#9;&#9;&#9;IF M.name &#61; "SortBy" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;F.sortBy &#58;&#61; M.obj; M.res &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;&#9;ELSIF M.name &#61; "Ascending" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;F.ascending &#58;&#61; M.obj; M.res &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;&#9;ELSIF M.name &#61; "Query" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;F.query &#58;&#61; M.obj; M.res &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;ListGadgets.FrameHandler(F, M) &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSIF M.id &#61; Objects.enum THEN &#9;&#9;&#9;&#9;&#9;&#9;ListGadgets.FrameHandler(F, M); &#9;&#9;&#9;&#9;&#9;&#9;M.Enum("SortBy"); M.Enum("Ascending"); M.Enum("Query") &#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;ListGadgets.FrameHandler(F, M) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSIF M IS Objects.CopyMsg THEN &#9;&#9;&#9;&#9;WITH M&#58; Objects.CopyMsg DO &#9;&#9;&#9;&#9;&#9;IF M.stamp &#61; F.stamp THEN &#9;&#9;&#9;&#9;&#9;&#9;M.obj &#58;&#61; F.dlink &#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;NEW(F1); F.stamp &#58;&#61; M.stamp; F.dlink &#58;&#61; F1; &#9;&#9;&#9;&#9;&#9;&#9;CopyFrame(M, F, F1); M.obj &#58;&#61; F1 &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSIF M IS Objects.FileMsg THEN &#9;&#9;&#9;&#9;WITH M&#58; Objects.FileMsg DO &#9;&#9;&#9;&#9;&#9;IF M.id &#61; Objects.load THEN &#9;&#9;&#9;&#9;&#9;&#9;Files.ReadInt(M.R, ver); ASSERT(ver &#61; Version); &#9;&#9;&#9;&#9;&#9;&#9;Gadgets.ReadRef(M.R, F.lib, F.sortBy); &#9;&#9;&#9;&#9;&#9;&#9;Gadgets.ReadRef(M.R, F.lib, F.ascending); &#9;&#9;&#9;&#9;&#9;&#9;Gadgets.ReadRef(M.R, F.lib, F.query) &#9;&#9;&#9;&#9;&#9;ELSIF M.id &#61; Objects.store THEN &#9;&#9;&#9;&#9;&#9;&#9;Files.WriteInt(M.R, Version); &#9;&#9;&#9;&#9;&#9;&#9;Gadgets.WriteRef(M.R, F.lib, F.sortBy); &#9;&#9;&#9;&#9;&#9;&#9;Gadgets.WriteRef(M.R, F.lib, F.ascending); &#9;&#9;&#9;&#9;&#9;&#9;Gadgets.WriteRef(M.R, F.lib, F.query) &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;ListGadgets.FrameHandler(F, M); &#9;&#9;&#9;&#9;&#9;IF M.id &#61; Objects.load THEN &#9;&#9;&#9;&#9;&#9;&#9;Links.GetLink(F, "VRange", obj); &#9;&#9;&#9;&#9;&#9;&#9;Attributes.SetInt(obj, "Value", noMsgs) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;ListGadgets.FrameHandler(F, M) &#9;&#9;&#9;END &#9;&#9;END &#9;END FrameHandler; &#9;PROCEDURE InitFrame(F&#58; Frame); &#9;BEGIN &#9;&#9;ListGadgets.InitFrame(F); &#9;&#9;F.handle &#58;&#61; FrameHandler; F.do &#58;&#61; vMethod; F.tab &#58;&#61; 8; &#9;&#9;F.ascending &#58;&#61; NIL; F.sortBy &#58;&#61; NIL; F.query &#58;&#61; NIL; &#9;&#9;Attributes.SetString(F, "Cmd", "Mail.Show #Point") &#9;END InitFrame; &#9;PROCEDURE NewFrame*; &#9;&#9;VAR F&#58; Frame; &#9;BEGIN &#9;&#9;NEW(F); InitFrame(F); &#9;&#9;Objects.NewObj &#58;&#61; F &#9;END NewFrame; &#9;PROCEDURE *TopicKey(R&#58; ListRiders.Rider)&#58; LONGINT; &#9;BEGIN &#9;&#9;WITH R&#58; TopicRider DO &#9;&#9;&#9;IF R.topic # NIL THEN &#9;&#9;&#9;&#9;RETURN R.topic.no &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;RETURN 0 &#9;&#9;&#9;END &#9;&#9;END &#9;END TopicKey; &#9;PROCEDURE *TopicSeek(R&#58; ListRiders.Rider; key&#58; LONGINT); &#9;BEGIN &#9;&#9;WITH R&#58; TopicRider DO &#9;&#9;&#9;R.topic &#58;&#61; topics; &#9;&#9;&#9;WHILE (R.topic # NIL) &#38; (R.topic.no # key) DO &#9;&#9;&#9;&#9;R.topic &#58;&#61; R.topic.next &#9;&#9;&#9;END; &#9;&#9;&#9;IF R.topic # NIL THEN &#9;&#9;&#9;&#9;R.d &#58;&#61; R.topic.topic &#9;&#9;&#9;END; &#9;&#9;&#9;R.eol &#58;&#61; R.topic &#61; NIL &#9;&#9;END &#9;END TopicSeek; &#9;PROCEDURE *TopicPos(R&#58; ListRiders.Rider)&#58; LONGINT; &#9;BEGIN &#9;&#9;RETURN R.do.Key(R) &#9;END TopicPos; &#9;PROCEDURE *TopicSet(R&#58; ListRiders.Rider; pos&#58; LONGINT); &#9;BEGIN &#9;&#9;R.do.Seek(R, pos) &#9;END TopicSet; &#9;PROCEDURE *TopicGetState(R&#58; ListRiders.Rider)&#58; LONGINT; &#9;BEGIN &#9;&#9;RETURN R(TopicRider).topic.state &#9;END TopicGetState; &#9;PROCEDURE *TopicSetState(R&#58; ListRiders.Rider; state&#58; LONGINT); &#9;BEGIN &#9;&#9;R(TopicRider).topic.state &#58;&#61; state &#9;END TopicSetState; &#9;PROCEDURE *TopicGetStamp(R&#58; ListRiders.Rider)&#58; LONGINT; &#9;BEGIN &#9;&#9;RETURN R(TopicRider).topic.stamp &#9;END TopicGetStamp; &#9;PROCEDURE *TopicSetStamp(R&#58; ListRiders.Rider; stamp&#58; LONGINT); &#9;BEGIN &#9;&#9;R(TopicRider).topic.stamp &#58;&#61; stamp &#9;END TopicSetStamp; &#9;PROCEDURE *TopicDeleteLink(R, linkR&#58; ListRiders.Rider); &#9;BEGIN &#9;END TopicDeleteLink; &#9;PROCEDURE ConnectTopicRider(VAR M&#58; ListRiders.ConnectMsg; base&#58; Model); &#9;&#9;VAR &#9;&#9;&#9;R&#58; TopicRider; &#9;BEGIN &#9;&#9;NEW(R); R.do &#58;&#61; tmMethod; &#9;&#9;R.base &#58;&#61; base; R.dsc &#58;&#61; FALSE; R.eol &#58;&#61; FALSE; &#9;&#9;R.do.Set(R, 0); M.R &#58;&#61; R &#9;END ConnectTopicRider; &#9;PROCEDURE *TopicModelHandler(obj&#58; Objects.Object; VAR M&#58; Objects.ObjMsg); &#9;BEGIN &#9;&#9;WITH obj&#58; Model DO &#9;&#9;&#9;IF M IS Objects.AttrMsg THEN &#9;&#9;&#9;&#9;WITH M&#58; Objects.AttrMsg DO &#9;&#9;&#9;&#9;&#9;IF (M.id &#61; Objects.get) &#38; (M.name &#61; "Gen") THEN &#9;&#9;&#9;&#9;&#9;&#9;M.class &#58;&#61; Objects.String; M.s &#58;&#61; "Mail.NewTopicModel"; M.res &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;Gadgets.objecthandle(obj, M) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSIF M IS Objects.CopyMsg THEN &#9;&#9;&#9;&#9;M(Objects.CopyMsg).obj &#58;&#61; obj &#9;&#9;&#9;ELSIF M IS ListRiders.ConnectMsg THEN &#9;&#9;&#9;&#9;ConnectTopicRider(M(ListRiders.ConnectMsg), obj) &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;Gadgets.objecthandle(obj, M) &#9;&#9;&#9;END &#9;&#9;END &#9;END TopicModelHandler; &#9;PROCEDURE NewTopicModel*; &#9;BEGIN &#9;&#9;Objects.NewObj &#58;&#61; topicList &#9;END NewTopicModel; &#9;PROCEDURE Recipient(VAR i&#58; LONGINT; VAR s, rcpt&#58; ARRAY OF CHAR); &#9;&#9;VAR &#9;&#9;&#9;j, k, end, dom&#58; LONGINT; &#9;&#9;&#9;candidate&#58; AdrString; &#9;&#9;&#9;special&#58; BOOLEAN; &#9;&#9;&#9;ch, old, close&#58; CHAR; &#9;BEGIN &#9;&#9;IF simpler THEN &#9;&#9;&#9;WHILE (s&#91;i&#93; # 0X) &#38; (s&#91;i&#93; &#60;&#61; " ") DO &#9;&#9;&#9;&#9;INC(i) &#9;&#9;&#9;END; &#9;&#9;&#9;IF s&#91;i&#93; &#61; "," THEN &#9;&#9;&#9;&#9;INC(i); &#9;&#9;&#9;&#9;WHILE (s&#91;i&#93; # 0X) &#38; (s&#91;i&#93; &#60;&#61; " ") DO &#9;&#9;&#9;&#9;&#9;INC(i) &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END; &#9;&#9;&#9;j &#58;&#61; 0; &#9;&#9;&#9;WHILE (s&#91;i&#93; &#62; " ") &#38; (s&#91;i&#93; # ",") DO &#9;&#9;&#9;&#9;rcpt&#91;j&#93; &#58;&#61; s&#91;i&#93;; &#9;&#9;&#9;&#9;INC(j); INC(i) &#9;&#9;&#9;END; &#9;&#9;&#9;rcpt&#91;j&#93; &#58;&#61; 0X &#9;&#9;ELSE &#9;&#9;&#9;j &#58;&#61; i; ch &#58;&#61; s&#91;j&#93;; old &#58;&#61; 01X; close &#58;&#61; 02X; &#9;&#9;&#9;WHILE (ch # 0X) &#38; &#126;( ((ch &#61; ",") &#38; (close &#61; 02X)) OR (old &#61; close) ) DO &#9;&#9;&#9;&#9;IF ch &#61; "(" THEN &#9;&#9;&#9;&#9;&#9;close &#58;&#61; ")" &#9;&#9;&#9;&#9;ELSIF ch &#61; "&#60;" THEN &#9;&#9;&#9;&#9;&#9;close &#58;&#61; "&#62;" &#9;&#9;&#9;&#9;ELSIF ch &#61; "&#123;" THEN &#9;&#9;&#9;&#9;&#9;close &#58;&#61; "&#125;" &#9;&#9;&#9;&#9;ELSIF ch &#61; "&#91;" THEN &#9;&#9;&#9;&#9;&#9;close &#58;&#61; "&#93;" &#9;&#9;&#9;&#9;ELSIF ch &#61; 22X THEN &#9;&#9;&#9;&#9;&#9;close &#58;&#61; 22X &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;INC(j); old &#58;&#61; ch; ch &#58;&#61; s&#91;j&#93; &#9;&#9;&#9;END; &#9;&#9;&#9;IF old # close THEN &#9;&#9;&#9;&#9;end &#58;&#61; j &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;end &#58;&#61; j-1 &#9;&#9;&#9;END; &#9;&#9;&#9;WHILE (j &#62;&#61; i) &#38; (s&#91;j&#93; &#60;&#61; " ") DO &#9;&#9;&#9;&#9;DEC(j) &#9;&#9;&#9;END; &#9;&#9;&#9;WHILE (j &#62;&#61; i) &#38; (s&#91;j&#93; &#62; " ") DO &#9;&#9;&#9;&#9;DEC(j) &#9;&#9;&#9;END; &#9;&#9;&#9;INC(j); &#9;&#9;&#9;k &#58;&#61; 0; dom &#58;&#61; -1; special &#58;&#61; FALSE; ch &#58;&#61; s&#91;j&#93;; &#9;&#9;&#9;IF ch &#61; "(" THEN &#9;&#9;&#9;&#9;close &#58;&#61; ")"; INC(j) &#9;&#9;&#9;ELSIF ch &#61; "&#60;" THEN &#9;&#9;&#9;&#9;close &#58;&#61; "&#62;"; INC(j) &#9;&#9;&#9;ELSIF ch &#61; "&#123;" THEN &#9;&#9;&#9;&#9;close &#58;&#61; "&#125;"; INC(j) &#9;&#9;&#9;ELSIF ch &#61; "&#91;" THEN &#9;&#9;&#9;&#9;close &#58;&#61; "&#93;"; INC(j) &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;close &#58;&#61; 02X &#9;&#9;&#9;END; &#9;&#9;&#9;ch &#58;&#61; s&#91;j&#93;; &#9;&#9;&#9;WHILE (ch &#62; " ") &#38; (j &#60; end) &#38; (ch # close) DO &#9;&#9;&#9;&#9;IF ch &#61; "@" THEN &#9;&#9;&#9;&#9;&#9;dom &#58;&#61; j &#9;&#9;&#9;&#9;ELSIF (dom &#60; 0) &#38; ((ch &#61; "(") OR (ch &#61; ")") OR (ch &#61; "&#60;") OR (ch &#61; "&#62;") OR (ch &#61; ",") OR (ch &#61; ";") OR (ch &#61; "&#58;") OR &#9;&#9;&#9;&#9;&#9;(ch &#61; "\") OR (ch &#61; 22X) OR (*(ch &#61; ".") OR*) (ch &#61; "&#91;") OR (ch &#61; "&#93;") OR (ch &#61; "/")) THEN &#9;&#9;&#9;&#9;&#9;special &#58;&#61; TRUE &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;candidate&#91;k&#93; &#58;&#61; ch; INC(k); INC(j); ch &#58;&#61; s&#91;j&#93; &#9;&#9;&#9;END; &#9;&#9;&#9;candidate&#91;k&#93; &#58;&#61; 0X; &#9;&#9;&#9;IF special THEN &#9;&#9;&#9;&#9;IF candidate&#91;0&#93; # 22X THEN &#9;&#9;&#9;&#9;&#9;rcpt&#91;0&#93; &#58;&#61; 22X; k &#58;&#61; 1 &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;k &#58;&#61; 0 &#9;&#9;&#9;&#9;END; j &#58;&#61; 0; &#9;&#9;&#9;&#9;WHILE (candidate&#91;j&#93; # 0X) &#38; (candidate&#91;j&#93; # "@") DO &#9;&#9;&#9;&#9;&#9;rcpt&#91;k&#93; &#58;&#61; candidate&#91;j&#93;; INC(k); INC(j) &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;rcpt&#91;k&#93; &#58;&#61; 22X; INC(k); &#9;&#9;&#9;&#9;WHILE candidate&#91;j&#93; # 0X DO &#9;&#9;&#9;&#9;&#9;rcpt&#91;k&#93; &#58;&#61; candidate&#91;j&#93;; INC(k); INC(j) &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;IF candidate&#91;j-1&#93; &#61; 22X THEN &#9;&#9;&#9;&#9;&#9;DEC(k) &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;rcpt&#91;k&#93; &#58;&#61; 0X &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;COPY(candidate, rcpt) &#9;&#9;&#9;END; &#9;&#9;&#9;WHILE (s&#91;end&#93; # 0X) &#38; (s&#91;end&#93; # ",") DO &#9;&#9;&#9;&#9;INC(end) &#9;&#9;&#9;END; &#9;&#9;&#9;IF s&#91;end&#93; &#61; "," THEN &#9;&#9;&#9;&#9;i &#58;&#61; end+1 &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;i &#58;&#61; end &#9;&#9;&#9;END &#9;&#9;END &#9;END Recipient; &#9;PROCEDURE QueryContType*(T&#58; Texts.Text; beg&#58; LONGINT; cont&#58; MIME.Content); &#9;&#9;VAR R&#58; Texts.Reader; ch&#58; CHAR; &#9;BEGIN &#9;&#9;cont.typ &#58;&#61; MIME.GetContentType("text/plain"); cont.encoding &#58;&#61; MIME.EncBin; &#9;&#9;Texts.OpenReader(R, T, beg); Texts.Read(R, ch); &#9;&#9;WHILE &#126;R.eot &#38; ((ch &#60;&#61; " ") OR &#126;(R.lib IS Fonts.Font)) DO &#9;&#9;&#9;Texts.Read(R, ch) &#9;&#9;END; &#9;&#9;WHILE &#126;R.eot DO &#9;&#9;&#9;IF &#126;(R.lib IS Fonts.Font) THEN &#9;&#9;&#9;&#9;cont.typ &#58;&#61; MIME.GetContentType(MIME.OberonMime); &#9;&#9;&#9;&#9;cont.encoding &#58;&#61; MIME.EncAsciiCoderC; RETURN &#9;&#9;&#9;ELSIF ch &#62; CHR(127) THEN &#9;&#9;&#9;&#9;cont.encoding &#58;&#61; MIME.Enc8Bit &#9;&#9;&#9;END; &#9;&#9;&#9;Texts.Read(R, ch) &#9;&#9;END &#9;END QueryContType; &#9;PROCEDURE ReadResponse(S&#58; SMTPSession); &#9;&#9;VAR &#9;&#9;&#9;reply&#58; ARRAY BufLen OF CHAR; &#9;&#9;&#9;l&#58; LONGINT; &#9;BEGIN &#9;&#9;NetSystem.ReadString(S.C, S.reply); &#9;&#9;IF trace THEN &#9;&#9;&#9;Texts.WriteString(W, "RCV&#58; "); Texts.WriteString(W, S.reply); &#9;&#9;&#9;Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf) &#9;&#9;END; &#9;&#9;Strings.StrToInt(S.reply, l); S.status &#58;&#61; SHORT(l); &#9;&#9;COPY(S.reply, reply); &#9;&#9;WHILE reply&#91;3&#93; &#61; "-" DO &#9;&#9;&#9;NetSystem.ReadString(S.C, reply); &#9;&#9;&#9;IF trace THEN &#9;&#9;&#9;&#9;Texts.WriteString(W, "RCV&#58; "); Texts.WriteString(W, S.reply); &#9;&#9;&#9;&#9;Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf) &#9;&#9;&#9;END &#9;&#9;END &#9;END ReadResponse; &#9;PROCEDURE CloseSMTP*(S&#58; SMTPSession); &#9;BEGIN &#9;&#9;IF S.C # NIL THEN &#9;&#9;&#9;SendCmd(S, "QUIT", ""); &#9;&#9;&#9;(*NetSystem.ReadString(S.C, S.reply);*) &#9;&#9;&#9;S.res &#58;&#61; NetTools.Done; &#9;&#9;&#9;NetTools.Disconnect(S.C); S.C &#58;&#61; NIL; S.S &#58;&#61; NIL &#9;&#9;ELSE &#9;&#9;&#9;S.res &#58;&#61; NetTools.Failed &#9;&#9;END &#9;END CloseSMTP; &#9;PROCEDURE OpenSMTP*(VAR S&#58; SMTPSession; host, from&#58; ARRAY OF CHAR; port&#58; INTEGER); &#9;BEGIN &#9;&#9;IF trace THEN &#9;&#9;&#9;Texts.WriteString(W, "--- SMTP"); &#9;&#9;&#9;Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf) &#9;&#9;END; &#9;&#9;IF (port &#60;&#61; 0) OR (port &#62;&#61; 10000) THEN &#9;&#9;&#9;port &#58;&#61; DefSMTPPort &#9;&#9;END; &#9;&#9;NEW(S); &#9;&#9;IF (host&#91;0&#93; # "&#60;") &#38; (host&#91;0&#93; # 0X) THEN &#9;&#9;&#9;IF NetTools.Connect(S.C, port, host, FALSE) THEN &#9;&#9;&#9;&#9;S.S &#58;&#61; NetTools.OpenStream(S.C); &#9;&#9;&#9;&#9;ReadResponse(S); &#9;&#9;&#9;&#9;IF S.reply&#91;0&#93; &#61; "2" THEN &#9;&#9;&#9;&#9;&#9;SendCmd(S, "HELO", NetSystem.hostName); &#9;&#9;&#9;&#9;&#9;ReadResponse(S); &#9;&#9;&#9;&#9;&#9;IF S.reply&#91;0&#93; &#61; "2" THEN &#9;&#9;&#9;&#9;&#9;&#9;COPY(from, S.from); S.res &#58;&#61; NetTools.Done; &#9;&#9;&#9;&#9;&#9;&#9;RETURN &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;CloseSMTP(S) &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;S.reply &#58;&#61; "no connection" &#9;&#9;&#9;END &#9;&#9;ELSE &#9;&#9;&#9;S.reply &#58;&#61; "no smtp-host specified" &#9;&#9;END; &#9;&#9;S.res &#58;&#61; NetTools.Failed; S.C &#58;&#61; NIL; S.S &#58;&#61; NIL &#9;END OpenSMTP; &#9;PROCEDURE SendReplyLine*(S&#58; NetTools.Session; cont&#58; MIME.Content); &#9;BEGIN &#9;&#9;S.reply &#58;&#61; "Done "; &#9;&#9;CASE cont.encoding OF &#9;&#9;&#9;MIME.EncBin&#58; Strings.Append(S.reply, "ASCII") &#9;&#9;&#9;&#124;MIME.Enc8Bit&#58; Strings.Append(S.reply, "ASCII (ISO 8bit)") &#9;&#9;&#9;&#124;MIME.Enc7Bit&#58; Strings.Append(S.reply, "ASCII (ISO 7bit)") &#9;&#9;&#9;&#124;MIME.EncQuoted&#58; Strings.Append(S.reply, "ASCII (ISO quoted)") &#9;&#9;&#9;&#124;MIME.EncAsciiCoder, MIME.EncAsciiCoderC&#58; Strings.Append(S.reply, "Oberon + Text") &#9;&#9;&#9;&#124;MIME.EncAsciiCoderCPlain&#58; Strings.Append(S.reply, "Oberon") &#9;&#9;ELSE &#9;&#9;&#9;Strings.Append(S.reply, "???") &#9;&#9;END &#9;END SendReplyLine; &#9;PROCEDURE MakeAscii*(body&#58; Texts.Text; beg, end&#58; LONGINT; compress&#58; BOOLEAN; VAR ascii&#58; Texts.Text); &#9;&#9;VAR &#9;&#9;&#9;F, Fc&#58; Files.File; &#9;&#9;&#9;buf&#58; Texts.Buffer; &#9;&#9;&#9;len&#58; LONGINT; &#9;BEGIN &#9;&#9;NEW(buf); Texts.OpenBuf(buf); &#9;&#9;Texts.Save(body, beg, end, buf); &#9;&#9;NEW(ascii); Texts.Open(ascii, ""); &#9;&#9;Texts.Append(ascii, buf); &#9;&#9;F &#58;&#61; Files.New(""); &#9;&#9;Texts.Store(ascii, F, 0, len); &#9;&#9;IF compress THEN &#9;&#9;&#9;Fc &#58;&#61; Files.New(""); &#9;&#9;&#9;AsciiCoder.Compress(F, Fc); &#9;&#9;&#9;F &#58;&#61; Fc &#9;&#9;END; &#9;&#9;NEW(ascii); Texts.Open(ascii, ""); &#9;&#9;AsciiCoder.Code(F, ascii) &#9;END MakeAscii; &#9;PROCEDURE SendMsgId(S&#58; SMTPSession); &#9;VAR t, d&#58; LONGINT; s&#58; ARRAY 20 OF CHAR; id&#58; ARRAY 80 OF CHAR; &#9;BEGIN &#9;&#9;id &#58;&#61; "&#60;"; &#9;&#9;t &#58;&#61; 0; d &#58;&#61; 0; WHILE S.from&#91;t&#93; # 0X DO d &#58;&#61; (d + ORD(S.from&#91;t&#93;)) MOD 65536; INC(t) END; &#9;&#9;Strings.IntToStr(d, s); Strings.Append(id, s); &#9;&#9;Oberon.GetClock(t, d); &#9;&#9;Strings.IntToStr(d, s); Strings.Append(id, s); Strings.AppendCh(id, "."); &#9;&#9;Strings.IntToStr(t, s); Strings.Append(id, s); Strings.AppendCh(id, "."); &#9;&#9;Strings.IntToStr(count, s); Strings.Append(id, s); Strings.AppendCh(id, "."); INC(count); &#9;&#9;Strings.AppendCh(id, "@"); &#9;&#9;IF NetSystem.hostName # "" THEN &#9;&#9;&#9;Strings.Append(id, NetSystem.hostName) &#9;&#9;ELSE &#9;&#9;&#9;NetSystem.ToNum(NetSystem.hostIP, s); Strings.Append(id, s) &#9;&#9;END; &#9;&#9;Strings.AppendCh(id, "&#62;"); &#9;&#9;SendCmd(S, "Message-ID&#58;", id) &#9;END SendMsgId; &#9; &#9;PROCEDURE Append2(VAR to&#58; ARRAY OF CHAR; x&#58; LONGINT); &#9;BEGIN &#9;&#9;Strings.AppendCh(to, CHR(ORD("0")+x DIV 10 MOD 10)); &#9;&#9;Strings.AppendCh(to, CHR(ORD("0")+x MOD 10)) &#9;END Append2; &#9; &#9;PROCEDURE FormatTime(time, date&#58; LONGINT; VAR s&#58; ARRAY OF CHAR);	(* RFC 2822 format *) &#9;VAR t&#58; ARRAY 20 OF CHAR; year, mon, day, hour, min, sec&#58; INTEGER; diff&#58; LONGINT; &#9;BEGIN &#9;&#9;s&#91;0&#93; &#58;&#61; 0X; &#9;&#9;Dates.ToHMS(time, hour, min, sec); &#9;&#9;Dates.ToYMD(date, year, mon, day); &#9;&#9;Strings.DayToStr(Dates.DayOfWeek(date), t, TRUE); Strings.Append(s, t); Strings.Append(s, ", "); &#9;&#9;Strings.IntToStr(day, t); Strings.Append(s, t); Strings.AppendCh(s, " "); &#9;&#9;Strings.MonthToStr(mon, t, TRUE); Strings.Append(s, t); Strings.AppendCh(s, " "); &#9;&#9;Strings.IntToStr(year, t); Strings.Append(s, t); Strings.AppendCh(s, " "); &#9;&#9;Append2(s, hour); Strings.AppendCh(s, "&#58;"); &#9;&#9;Append2(s, min); Strings.AppendCh(s, "&#58;"); &#9;&#9;Append2(s, sec); Strings.AppendCh(s, " "); &#9;&#9;diff &#58;&#61; Dates.TimeDiff; &#9;&#9;IF diff &#60; 0 THEN Strings.AppendCh(s, "-"); diff &#58;&#61; -diff ELSE Strings.AppendCh(s, "+") END; &#9;&#9;Append2(s, diff DIV 60); &#9;&#9;Append2(s, diff MOD 60) &#9;END FormatTime; &#9; &#9;PROCEDURE SendDate(S&#58; SMTPSession); &#9;VAR t, d&#58; LONGINT; s&#58; ARRAY 64 OF CHAR; &#9;BEGIN &#9;&#9;Oberon.GetClock(t, d); &#9;&#9;FormatTime(t, d, s); &#9;&#9;SendCmd(S, "Date&#58;", s) &#9;END SendDate; &#9; &#9;PROCEDURE SendText*(S&#58; SMTPSession; head, body&#58; Texts.Text; beg, end&#58; LONGINT; cont&#58; MIME.Content); &#9;&#9;VAR &#9;&#9;&#9;enc&#58; LONGINT; &#9;&#9;&#9;ascii&#58; Texts.Text; &#9;BEGIN &#9;&#9;enc &#58;&#61; cont.encoding; cont.len &#58;&#61; MAX(LONGINT); &#9;&#9;SendCmd(S,"From&#58;", S.from);	(* no space after "&#58;" *) &#9;&#9;SendDate(S); &#9;&#9;SendMsgId(S); &#9;&#9;SendCmd(S, "X-Mailer&#58;", mailer); &#9;&#9;IF enc IN &#123;MIME.EncAsciiCoder, MIME.EncAsciiCoderC, MIME.EncAsciiCoderCPlain&#125; THEN &#9;&#9;&#9;SendCmd(S, "X-Content-Type&#58;", MIME.OberonMime); &#9;&#9;&#9;cont.encoding &#58;&#61; MIME.Enc8Bit &#9;&#9;END; &#9;&#9;IF cont.encoding # MIME.EncBin THEN &#9;&#9;&#9;MIME.WriteISOMime(S.S, cont) &#9;&#9;END; &#9;&#9;cont.encoding &#58;&#61; MIME.Enc8Bit; &#9;&#9;MIME.WriteText(head, 0, head.len, S.S, cont, TRUE, FALSE); &#9;&#9;NetSystem.WriteString(S.C, ""); &#9;&#9;IF enc IN &#123;MIME.EncAsciiCoder, MIME.EncAsciiCoderC, MIME.EncAsciiCoderCPlain&#125; THEN &#9;&#9;&#9;IF enc IN &#123;MIME.EncAsciiCoder, MIME.EncAsciiCoderC&#125; THEN &#9;&#9;&#9;&#9;MIME.WriteText(body, beg, end, S.S, cont, TRUE, FALSE) &#9;&#9;&#9;END; &#9;&#9;&#9;NetSystem.WriteString(S.C, ""); &#9;&#9;&#9;NetSystem.WriteString(S.C, OberonStart); &#9;&#9;&#9;MakeAscii(body, beg, end, enc # MIME.EncAsciiCoder, ascii); &#9;&#9;&#9;MIME.WriteText(ascii, 0, ascii.len, S.S, cont, TRUE, TRUE) &#9;&#9;ELSE &#9;&#9;&#9;cont.encoding &#58;&#61; enc; &#9;&#9;&#9;MIME.WriteText(body, beg, end, S.S, cont, TRUE, TRUE) &#9;&#9;END; &#9;&#9;cont.encoding &#58;&#61; enc; &#9;&#9;NetSystem.WriteString(S.C, ".") &#9;END SendText; &#9;PROCEDURE SendMail*(S&#58; SMTPSession; T&#58; Texts.Text; cont&#58; MIME.Content; autoCc&#58; BOOLEAN); &#9;&#9;VAR &#9;&#9;&#9;R&#58; Texts.Reader; &#9;&#9;&#9;t&#58; ARRAY BufLen OF CHAR; &#9;&#9;&#9;pos&#58; LONGINT; &#9;&#9;&#9;head&#58; Texts.Text; &#9;&#9;&#9;ch, old&#58; CHAR; &#9;&#9;PROCEDURE Recipients(VAR pos&#58; LONGINT)&#58; BOOLEAN; &#9;&#9;&#9;VAR &#9;&#9;&#9;&#9;R&#58; Texts.Reader; &#9;&#9;&#9;&#9;t&#58; ARRAY BufLen OF CHAR; &#9;&#9;&#9;&#9;i&#58; LONGINT; &#9;&#9;&#9;&#9;rcpt&#58; AdrString; &#9;&#9;&#9;&#9;first&#58; BOOLEAN; &#9;&#9;BEGIN &#9;&#9;&#9;Texts.OpenReader(R, T, pos); ReadString(R, t); first &#58;&#61; TRUE; &#9;&#9;&#9;WHILE (Strings.CAPPrefix("TO&#58;", t) OR Strings.CAPPrefix("CC&#58;", t) OR Strings.CAPPrefix("BCC&#58;", t)) OR &#9;&#9;&#9;&#9;(&#126;first &#38; (t&#91;0&#93; &#61; " ") OR (t&#91;0&#93; &#61; 09X)) DO &#9;&#9;&#9;&#9;Texts.WriteString(W, t); Texts.WriteLn(W); &#9;&#9;&#9;&#9;IF (t&#91;0&#93; &#61; " ") OR (t&#91;0&#93; &#61; 09X) THEN &#9;&#9;&#9;&#9;&#9;i &#58;&#61; 1 &#9;&#9;&#9;&#9;ELSIF Strings.CAPPrefix("BCC&#58;", t) THEN &#9;&#9;&#9;&#9;&#9;i &#58;&#61; 4 &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;i &#58;&#61; 3 &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;Recipient(i, t, rcpt); &#9;&#9;&#9;&#9;WHILE rcpt # "" DO &#9;&#9;&#9;&#9;&#9;Texts.Append(head, W.buf); &#9;&#9;&#9;&#9;&#9;Texts.WriteString(W, "To&#58; "); Texts.WriteString(W, rcpt); &#9;&#9;&#9;&#9;&#9;Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf); &#9;&#9;&#9;&#9;&#9;SendCmd(S, "RCPT TO&#58;", rcpt); ReadResponse(S); &#9;&#9;&#9;&#9;&#9;IF S.reply&#91;0&#93; # "2" THEN &#9;&#9;&#9;&#9;&#9;&#9;S.res &#58;&#61; NetTools.Failed; RETURN FALSE &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;Recipient(i, t, rcpt); first &#58;&#61; FALSE &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;pos &#58;&#61; Texts.Pos(R); ReadString(R, t) &#9;&#9;&#9;END; &#9;&#9;&#9;IF autoCc THEN &#9;&#9;&#9;&#9;Texts.WriteString(W, "Cc&#58; "); Texts.WriteString(W, S.from); &#9;&#9;&#9;&#9;Texts.WriteLn(W); Texts.Append(head, W.buf); &#9;&#9;&#9;&#9;SendCmd(S, "RCPT TO&#58;", S.from); ReadResponse(S); &#9;&#9;&#9;&#9;IF S.reply&#91;0&#93; # "2" THEN &#9;&#9;&#9;&#9;&#9;S.res &#58;&#61; NetTools.Failed; RETURN FALSE &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END; &#9;&#9;&#9;Texts.Append(head, W.buf); &#9;&#9;&#9;RETURN TRUE &#9;&#9;END Recipients; &#9;BEGIN &#9;&#9;Texts.OpenReader(R, T, 0); Texts.Read(R, ch); pos &#58;&#61; 1; &#9;&#9;WHILE &#126;R.eot &#38; ((ch &#60;&#61; " ") OR &#126;(R.lib IS Fonts.Font)) DO &#9;&#9;&#9;Texts.Read(R, ch); INC(pos) &#9;&#9;END; &#9;&#9;DEC(pos); Texts.OpenReader(R, T, pos); &#9;&#9;REPEAT &#9;&#9;&#9;pos &#58;&#61; Texts.Pos(R); ReadString(R, t) &#9;&#9;UNTIL R.eot OR Strings.CAPPrefix("TO&#58;", t) OR Strings.CAPPrefix("CC&#58;", t) OR Strings.CAPPrefix("BCC&#58;", t); &#9;&#9;IF &#126;R.eot THEN &#9;&#9;&#9;SendCmd(S, "MAIL FROM&#58;", S.from); ReadResponse(S); &#9;&#9;&#9;IF S.reply&#91;0&#93; &#61; "2" THEN &#9;&#9;&#9;&#9;S.res &#58;&#61; NetTools.Done; &#9;&#9;&#9;&#9;NEW(head); Texts.Open(head, ""); &#9;&#9;&#9;&#9;IF Recipients(pos) THEN &#9;&#9;&#9;&#9;&#9;Texts.OpenReader(R, T, pos); &#9;&#9;&#9;&#9;&#9;old &#58;&#61; 0X; Texts.Read(R, ch); &#9;&#9;&#9;&#9;&#9;WHILE &#126;R.eot &#38; &#126;( ((old &#61; Strings.CR) OR (old &#61; Strings.LF)) &#38; ((ch &#61; Strings.CR) OR (ch &#61; Strings.LF)) ) DO &#9;&#9;&#9;&#9;&#9;&#9;old &#58;&#61; ch; Texts.Read(R, ch) &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;Texts.Save(T, pos, Texts.Pos(R)-1, W.buf); &#9;&#9;&#9;&#9;&#9;Texts.Append(head, W.buf); &#9;&#9;&#9;&#9;&#9;SendCmd(S, "DATA", ""); ReadResponse(S); &#9;&#9;&#9;&#9;&#9;IF S.reply&#91;0&#93; &#61; "3" THEN &#9;&#9;&#9;&#9;&#9;&#9;SendText(S, head, T, Texts.Pos(R), T.len, cont); ReadResponse(S); &#9;&#9;&#9;&#9;&#9;&#9;IF S.reply&#91;0&#93; &#61; "2" THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;SendReplyLine(S, cont); RETURN &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END &#9;&#9;ELSE &#9;&#9;&#9;S.reply &#58;&#61; "no recipient" &#9;&#9;END; &#9;&#9;S.res &#58;&#61; NetTools.Failed &#9;END SendMail; &#9;(** (es), Mail.Send ( @ &#124; ^ &#124; &#123;mailfile&#125; &#126; ) *) &#9;PROCEDURE Send*; &#9;&#9;VAR &#9;&#9;&#9;email&#58; AdrString; &#9;&#9;&#9;server&#58; ServerName; &#9;&#9;&#9;val&#58; ValueString; &#9;&#9;&#9;S&#58; SMTPSession; &#9;&#9;&#9;cont&#58; MIME.Content; &#9;&#9;&#9;Sc&#58; Texts.Scanner; &#9;&#9;&#9;T, sig&#58; Texts.Text; &#9;&#9;&#9;F&#58; Texts.Finder; &#9;&#9;&#9;obj&#58; Objects.Object; &#9;&#9;&#9;beg, end, time, i&#58; LONGINT; &#9;&#9;&#9;autoCc&#58; BOOLEAN; &#9;&#9;PROCEDURE SendIt; &#9;&#9;BEGIN &#9;&#9;&#9;IF T # NIL THEN &#9;&#9;&#9;&#9;IF cont.encoding &#61; MIME.EncAuto THEN &#9;&#9;&#9;&#9;&#9;QueryContType(T, beg, cont) &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;GetSetting("MailSignature", val, FALSE); &#9;&#9;&#9;&#9;IF val # "" THEN &#9;&#9;&#9;&#9;&#9;NEW(sig); Texts.Open(sig, val); &#9;&#9;&#9;&#9;&#9;IF sig.len &#62; 0 THEN &#9;&#9;&#9;&#9;&#9;&#9;Texts.Save(T, 0, T.len, W.buf); &#9;&#9;&#9;&#9;&#9;&#9;NEW(T); Texts.Open(T, ""); &#9;&#9;&#9;&#9;&#9;&#9;Texts.WriteLn(W); Texts.Append(T, W.buf); &#9;&#9;&#9;&#9;&#9;&#9;Texts.Save(sig, 0, sig.len, W.buf); &#9;&#9;&#9;&#9;&#9;&#9;Texts.Append(T, W.buf) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;OpenSMTP(S, server, email, DefSMTPPort); &#9;&#9;&#9;&#9;IF S.res &#61; NetTools.Done THEN &#9;&#9;&#9;&#9;&#9;ShowStatus("mailing "); &#9;&#9;&#9;&#9;&#9;SendMail(S, T, cont, autoCc); &#9;&#9;&#9;&#9;&#9;CloseSMTP(S) &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;ShowStatus(S.reply) &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;ShowStatus("no text") &#9;&#9;&#9;END &#9;&#9;END SendIt; &#9;BEGIN &#9;&#9;trace &#58;&#61; NetTools.QueryBool("TraceMail"); &#9;&#9;GetSetting("EMail", email, FALSE); &#9;&#9;GetSetting("SMTP", server, FALSE); &#9;&#9;GetSetting("AutoCc", val, TRUE); &#9;&#9;Strings.StrToBool(val, autoCc); &#9;&#9;IF email &#61; "" THEN &#9;&#9;&#9;ShowStatus("no return address set"); RETURN &#9;&#9;ELSE &#9;&#9;&#9;i &#58;&#61; 0; Recipient(i, email, val); &#9;&#9;&#9;IF val # email THEN &#9;&#9;&#9;&#9;ShowStatus("invalid return address"); RETURN &#9;&#9;&#9;END &#9;&#9;END; &#9;&#9;GetSetting("ContType", val, TRUE); &#9;&#9;NEW(cont); cont.typ &#58;&#61; MIME.GetContentType("text/plain"); &#9;&#9;IF val&#91;0&#93; &#61; "0" THEN &#9;&#9;&#9;cont.encoding &#58;&#61; MIME.EncBin &#9;&#9;ELSIF val&#91;0&#93; &#61; "1" THEN &#9;&#9;&#9;cont.encoding &#58;&#61; MIME.Enc8Bit &#9;&#9;ELSIF val&#91;0&#93; &#61; "2" THEN &#9;&#9;&#9;cont.typ &#58;&#61; MIME.GetContentType(MIME.OberonMime); cont.encoding &#58;&#61; MIME.EncAsciiCoderC &#9;&#9;ELSE &#9;&#9;&#9;cont.encoding &#58;&#61; MIME.EncAuto &#9;&#9;END; &#9;&#9;beg &#58;&#61; 0; T &#58;&#61; NIL; &#9;&#9;Texts.OpenScanner(Sc, Oberon.Par.text, Oberon.Par.pos); Texts.Scan(Sc); &#9;&#9;IF (Sc.class &#61; Texts.Char) &#38; (Sc.c &#61; "*") THEN (* send marked text *) &#9;&#9;&#9;T &#58;&#61; Oberon.MarkedText; SendIt &#9;&#9;ELSIF (Sc.class &#61; Texts.Char) &#38; (Sc.c &#61; "^") THEN (* send selected text *) &#9;&#9;&#9;Oberon.GetSelection(T, beg, end, time); &#9;&#9;&#9;IF time &#62;&#61; 0 THEN &#9;&#9;&#9;&#9;Texts.OpenScanner(Sc, T, beg); Texts.Scan(Sc); &#9;&#9;&#9;&#9;IF Sc.class IN &#123;Texts.Name, Texts.String&#125; THEN NEW(T); Texts.Open(T, Sc.s); SendIt END &#9;&#9;&#9;END &#9;&#9;ELSIF (Sc.class &#61; Texts.Char) &#38; (Sc.c &#61; "@") THEN (* Send button (mailto url) *) &#9;&#9;&#9;IF Gadgets.executorObj # NIL THEN &#9;&#9;&#9;&#9;Gadgets.GetObjName(Gadgets.executorObj, val); &#9;&#9;&#9;&#9;IF val &#61; "mailto" THEN &#9;&#9;&#9;&#9;&#9;Links.GetLink(Gadgets.context, "Model", obj); &#9;&#9;&#9;&#9;&#9;IF (obj # NIL) &#38; (obj IS Texts.Text) THEN &#9;&#9;&#9;&#9;&#9;&#9;T &#58;&#61; obj(Texts.Text); &#9;&#9;&#9;&#9;&#9;&#9;Texts.OpenFinder(F, T, beg); &#9;&#9;&#9;&#9;&#9;&#9;beg &#58;&#61; F.pos; Texts.FindObj(F, obj); &#9;&#9;&#9;&#9;&#9;&#9;WHILE &#126;F.eot &#38; (obj # Gadgets.executorObj) DO &#9;&#9;&#9;&#9;&#9;&#9;&#9;beg &#58;&#61; F.pos; Texts.FindObj(F, obj) &#9;&#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;&#9;INC(beg); &#9;&#9;&#9;&#9;&#9;&#9;SendIt &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END &#9;&#9;ELSIF Sc.class IN &#123;Texts.Name, Texts.String&#125; THEN (* &#123;filename&#125; &#126; *) &#9;&#9;&#9;WHILE Sc.class IN &#123;Texts.Name, Texts.String&#125; DO &#9;&#9;&#9;&#9;NEW(T); Texts.Open(T, Sc.s); SendIt; &#9;&#9;&#9;&#9;Texts.Scan(Sc) &#9;&#9;&#9;END &#9;&#9;END &#9;END Send; (** Mail.Cite (selection &#38; caret) &#9;&#9;Copy the selection to the caret with an left indent "&#62; ". *) &#9;PROCEDURE Cite*; &#9;&#9;VAR &#9;&#9;&#9;text&#58; Texts.Text; &#9;&#9;&#9;beg, end, time&#58; LONGINT; &#9;&#9;&#9;C&#58; Oberon.CaretMsg; &#9;BEGIN &#9;&#9;text &#58;&#61; NIL; time &#58;&#61; -1; &#9;&#9;Oberon.GetSelection(text, beg, end, time); &#9;&#9;IF (text # NIL) &#38; (time &#62; 0) THEN &#9;&#9;&#9;C.id &#58;&#61; Oberon.get; C.car &#58;&#61; NIL; C.text &#58;&#61; NIL; C.pos &#58;&#61; -1; C.F &#58;&#61; NIL; &#9;&#9;&#9;Objects.Stamp(C); Display.Broadcast(C); &#9;&#9;&#9;IF C.text # NIL THEN &#9;&#9;&#9;&#9;CiteText(W, text, beg, end); &#9;&#9;&#9;&#9;Texts.Insert(C.text, C.pos, W.buf) &#9;&#9;&#9;END &#9;&#9;END &#9;END Cite; (** Mail.Mono (marked text) &#9;&#9;Change the font of the marked viewer into Courier10. *) &#9;PROCEDURE Mono*; &#9;&#9;VAR T&#58; Texts.Text; &#9;BEGIN &#9;&#9;T &#58;&#61; Oberon.MarkedText; &#9;&#9;IF T # NIL THEN &#9;&#9;&#9;Texts.ChangeLooks(T, 0, T.len, &#123;0, 1&#125;, textFnt, Display.FG, 0) &#9;&#9;END &#9;END Mono; (** Mail.CutLines &#91;width&#93; (marked text) &#9;&#9;Break all lines in the marked viewer after a maximum of width characters. &#9;&#9;The default width is 80. *) &#9;PROCEDURE CutLines*; &#9;&#9;VAR &#9;&#9;&#9;S&#58; Attributes.Scanner; &#9;&#9;&#9;T&#58; Texts.Text; &#9;&#9;&#9;R&#58; Texts.Reader; &#9;&#9;&#9;pos, crpos, n, l&#58; LONGINT; &#9;&#9;&#9;ch&#58; CHAR; &#9;BEGIN &#9;&#9;T &#58;&#61; Oberon.MarkedText; &#9;&#9;IF T &#61; NIL THEN &#9;&#9;&#9;RETURN &#9;&#9;END; &#9;&#9;Attributes.OpenScanner(S, Oberon.Par.text, Oberon.Par.pos); Attributes.Scan(S); &#9;&#9;IF S.class &#61; Attributes.Int THEN &#9;&#9;&#9;IF S.i &#60; 40 THEN &#9;&#9;&#9;&#9;n &#58;&#61; 40 &#9;&#9;&#9;ELSIF S.i &#62; 132 THEN &#9;&#9;&#9;&#9;n &#58;&#61; 132 &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;n &#58;&#61; S.i &#9;&#9;&#9;END &#9;&#9;ELSE &#9;&#9;&#9;n &#58;&#61; 80 &#9;&#9;END; &#9;&#9;Texts.OpenReader(R, T, 0); Texts.Read(R, ch); &#9;&#9;pos &#58;&#61; 0; crpos &#58;&#61; 0; l &#58;&#61; 1; &#9;&#9;WHILE &#126;R.eot DO &#9;&#9;&#9;IF R.lib IS Fonts.Font THEN &#9;&#9;&#9;&#9;IF ch &#61; Strings.CR THEN &#9;&#9;&#9;&#9;&#9;l &#58;&#61; 0; pos &#58;&#61; Texts.Pos(R); crpos &#58;&#61; pos &#9;&#9;&#9;&#9;ELSIF (l &#62;&#61; n) &#38; (pos # crpos) THEN &#9;&#9;&#9;&#9;&#9;Texts.WriteLn(W); Texts.Insert(T, pos, W.buf); &#9;&#9;&#9;&#9;&#9;Texts.OpenReader(R, T, Texts.Pos(R)+1); &#9;&#9;&#9;&#9;&#9;l &#58;&#61; Texts.Pos(R)-pos &#9;&#9;&#9;&#9;ELSIF ch &#60;&#61; " " THEN &#9;&#9;&#9;&#9;&#9;pos &#58;&#61; Texts.Pos(R) &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;pos &#58;&#61; Texts.Pos(R) &#9;&#9;&#9;END; &#9;&#9;&#9;Texts.Read(R, ch); INC(l) &#9;&#9;END &#9;END CutLines; (** Parsing of a mailto url. *) &#9;PROCEDURE SplitMailTo*(VAR url, mailadr&#58; ARRAY OF CHAR)&#58; LONGINT; &#9;&#9;VAR &#9;&#9;&#9;key, i, j, l&#58; LONGINT; &#9;&#9;&#9;buffer&#58; ARRAY BufLen OF CHAR; &#9;&#9;&#9;iskey&#58; BOOLEAN; &#9;&#9;PROCEDURE Blanks; &#9;&#9;BEGIN &#9;&#9;&#9;WHILE (url&#91;i&#93; # 0X) &#38; (url&#91;i&#93; &#60;&#61; " ") DO &#9;&#9;&#9;&#9;INC(i) &#9;&#9;&#9;END &#9;&#9;END Blanks; &#9;BEGIN &#9;&#9;HyperDocs.UnESC(url); &#9;&#9;i &#58;&#61; 0; Blanks; &#9;&#9;(* skip mailto *) &#9;&#9;WHILE (url&#91;i&#93; # 0X) &#38; (url&#91;i&#93; # "&#58;") DO &#9;&#9;&#9;INC(i) &#9;&#9;END; &#9;&#9;(* skip &#58; *) &#9;&#9;WHILE (url&#91;i&#93; # 0X) &#38; ((url&#91;i&#93; &#61; "&#58;") OR (url&#91;i&#93; &#61; "/")) DO &#9;&#9;&#9;INC(i) &#9;&#9;END; &#9;&#9;Blanks; &#9;&#9;(* get mailadr *) &#9;&#9;iskey &#58;&#61; TRUE; &#9;&#9;l &#58;&#61; LEN(mailadr); j &#58;&#61; 0; &#9;&#9;WHILE url&#91;i&#93; # 0X DO &#9;&#9;&#9;IF (url&#91;i&#93; &#62; " ") &#38; &#126;Strings.IsDigit(url&#91;i&#93;) THEN &#9;&#9;&#9;&#9;iskey &#58;&#61; FALSE &#9;&#9;&#9;END; &#9;&#9;&#9;IF j &#60; l THEN &#9;&#9;&#9;&#9;mailadr&#91;j&#93; &#58;&#61; url&#91;i&#93;; INC(j) &#9;&#9;&#9;END; &#9;&#9;&#9;INC(i) &#9;&#9;END; &#9;&#9;mailadr&#91;j&#93; &#58;&#61; 0X; DEC(j); &#9;&#9;WHILE (j &#62;&#61; 0) &#38; (mailadr&#91;j&#93; &#60;&#61; " ") DO &#9;&#9;&#9;mailadr&#91;j&#93; &#58;&#61; 0X; DEC(j) &#9;&#9;END; &#9;&#9;IF (url&#91;i&#93; &#61; 0X) &#38; iskey THEN &#9;&#9;&#9;IF mailadr # "" THEN &#9;&#9;&#9;&#9;Strings.StrToInt(mailadr, key); &#9;&#9;&#9;&#9;HyperDocs.RetrieveLink(key, buffer); &#9;&#9;&#9;&#9;key &#58;&#61; SplitMailTo(buffer, mailadr) &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;key &#58;&#61; HyperDocs.UndefKey &#9;&#9;&#9;END &#9;&#9;ELSE &#9;&#9;&#9;COPY("mailto&#58;", url); &#9;&#9;&#9;Strings.Append(url, mailadr); &#9;&#9;&#9;key &#58;&#61; HyperDocs.RegisterLink(url) &#9;&#9;END; &#9;&#9;RETURN key &#9;END SplitMailTo; &#9;PROCEDURE *MailToSchemeHandler(L&#58; Objects.Object; VAR M&#58; Objects.ObjMsg); &#9;&#9;VAR mailadr&#58; ARRAY NetTools.PathStrLen OF CHAR; &#9;BEGIN &#9;&#9;WITH L&#58; HyperDocs.LinkScheme DO &#9;&#9;&#9;IF M IS HyperDocs.RegisterLinkMsg THEN &#9;&#9;&#9;&#9;WITH M&#58; HyperDocs.RegisterLinkMsg DO &#9;&#9;&#9;&#9;&#9;M.key &#58;&#61; SplitMailTo(M.link, mailadr); &#9;&#9;&#9;&#9;&#9;IF M.key # HyperDocs.UndefKey THEN &#9;&#9;&#9;&#9;&#9;&#9;M.res &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSIF M IS Objects.AttrMsg THEN &#9;&#9;&#9;&#9;WITH M&#58; Objects.AttrMsg DO &#9;&#9;&#9;&#9;&#9;IF (M.id &#61; Objects.get) &#38; (M.name &#61; "Gen") THEN &#9;&#9;&#9;&#9;&#9;&#9;M.class &#58;&#61; Objects.String; &#9;&#9;&#9;&#9;&#9;&#9;M.s &#58;&#61; "Mail.NewMailToLinkScheme"; &#9;&#9;&#9;&#9;&#9;&#9;M.res &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;HyperDocs.LinkSchemeHandler(L, M) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;HyperDocs.LinkSchemeHandler(L, M) &#9;&#9;&#9;END &#9;&#9;END &#9;END MailToSchemeHandler; &#9;PROCEDURE NewMailToLinkScheme*; &#9;&#9;VAR L&#58; HyperDocs.LinkScheme; &#9;BEGIN &#9;&#9;NEW(L); L.usePath &#58;&#61; FALSE; &#9;&#9;L.handle &#58;&#61; MailToSchemeHandler; Objects.NewObj &#58;&#61; L &#9;END NewMailToLinkScheme; (** Parsing of a mailserver url. *) &#9;PROCEDURE SplitMailServer*(VAR url, mailadr, subject, body&#58; ARRAY OF CHAR)&#58; LONGINT; &#9;&#9;VAR &#9;&#9;&#9;key, i, j, l&#58; LONGINT; &#9;&#9;&#9;buffer&#58; ARRAY BufLen OF CHAR; &#9;&#9;&#9;iskey&#58; BOOLEAN; &#9;&#9;PROCEDURE Blanks; &#9;&#9;BEGIN &#9;&#9;&#9;WHILE (url&#91;i&#93; # 0X) &#38; (url&#91;i&#93; &#60;&#61; " ") DO &#9;&#9;&#9;&#9;INC(i) &#9;&#9;&#9;END &#9;&#9;END Blanks; &#9;BEGIN &#9;&#9;HyperDocs.UnESC(url); &#9;&#9;i &#58;&#61; 0; Blanks; &#9;&#9;(* skip mailserver *) &#9;&#9;WHILE (url&#91;i&#93; # 0X) &#38; (url&#91;i&#93; # "&#58;") DO &#9;&#9;&#9;INC(i) &#9;&#9;END; &#9;&#9;(* skip &#58; *) &#9;&#9;WHILE (url&#91;i&#93; # 0X) &#38; ((url&#91;i&#93; &#61; "&#58;") OR (url&#91;i&#93; &#61; "/")) DO &#9;&#9;&#9;INC(i) &#9;&#9;END; &#9;&#9;Blanks; &#9;&#9;(* get mailadr *) &#9;&#9;iskey &#58;&#61; TRUE; &#9;&#9;l &#58;&#61; LEN(mailadr); j &#58;&#61; 0; &#9;&#9;WHILE (url&#91;i&#93; # 0X) &#38; (url&#91;i&#93; # "/") DO &#9;&#9;&#9;IF (url&#91;i&#93; &#62; " ") &#38; &#126;Strings.IsDigit(url&#91;i&#93;) THEN &#9;&#9;&#9;&#9;iskey &#58;&#61; FALSE &#9;&#9;&#9;END; &#9;&#9;&#9;IF j &#60; l THEN &#9;&#9;&#9;&#9;mailadr&#91;j&#93; &#58;&#61; url&#91;i&#93;; INC(j) &#9;&#9;&#9;END; &#9;&#9;&#9;INC(i) &#9;&#9;END; &#9;&#9;mailadr&#91;j&#93; &#58;&#61; 0X; DEC(j); &#9;&#9;WHILE (j &#62;&#61; 0) &#38; (mailadr&#91;j&#93; &#60;&#61; " ") DO &#9;&#9;&#9;mailadr&#91;j&#93; &#58;&#61; 0X; DEC(j) &#9;&#9;END; &#9;&#9;IF (url&#91;i&#93; &#61; 0X) &#38; iskey THEN &#9;&#9;&#9;IF mailadr # "" THEN &#9;&#9;&#9;&#9;Strings.StrToInt(mailadr, key); &#9;&#9;&#9;&#9;HyperDocs.RetrieveLink(key, buffer); &#9;&#9;&#9;&#9;key &#58;&#61; SplitMailServer(buffer, mailadr, subject, body) &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;key &#58;&#61; HyperDocs.UndefKey &#9;&#9;&#9;END; &#9;&#9;&#9;RETURN key &#9;&#9;END; &#9;&#9;IF url&#91;i&#93; &#61; "/" THEN &#9;&#9;&#9;INC(i) &#9;&#9;END; &#9;&#9;l &#58;&#61; LEN(subject); j &#58;&#61; 0; &#9;&#9;WHILE (url&#91;i&#93; # 0X) &#38; (url&#91;i&#93; # "/") DO &#9;&#9;&#9;IF j &#60; l THEN &#9;&#9;&#9;&#9;subject&#91;j&#93; &#58;&#61; url&#91;i&#93;; INC(j) &#9;&#9;&#9;END; &#9;&#9;&#9;INC(i) &#9;&#9;END; &#9;&#9;subject&#91;j&#93; &#58;&#61; 0X; DEC(j); &#9;&#9;WHILE (j &#62;&#61; 0) &#38; (subject&#91;j&#93; &#60;&#61; " ") DO &#9;&#9;&#9;subject&#91;j&#93; &#58;&#61; 0X; DEC(j) &#9;&#9;END; &#9;&#9;IF url&#91;i&#93; &#61; "/" THEN &#9;&#9;&#9;INC(i) &#9;&#9;END; &#9;&#9;l &#58;&#61; LEN(body); j &#58;&#61; 0; &#9;&#9;WHILE url&#91;i&#93; # 0X DO &#9;&#9;&#9;IF j &#60; l THEN &#9;&#9;&#9;&#9;body&#91;j&#93; &#58;&#61; url&#91;i&#93;; INC(j) &#9;&#9;&#9;END; &#9;&#9;&#9;INC(i) &#9;&#9;END; &#9;&#9;body&#91;j&#93; &#58;&#61; 0X; &#9;&#9;COPY("mailserver&#58;", url); &#9;&#9;Strings.Append(url, mailadr); &#9;&#9;Strings.AppendCh(url, "/"); &#9;&#9;Strings.Append(url, subject); &#9;&#9;Strings.AppendCh(url, "/"); &#9;&#9;Strings.Append(url, body); &#9;&#9;key &#58;&#61; HyperDocs.RegisterLink(url); &#9;&#9;RETURN key &#9;END SplitMailServer; &#9;PROCEDURE *MailServerSchemeHandler(L&#58; Objects.Object; VAR M&#58; Objects.ObjMsg); &#9;&#9;VAR mailadr, subject, body&#58; ARRAY NetTools.PathStrLen OF CHAR; &#9;BEGIN &#9;&#9;WITH L&#58; HyperDocs.LinkScheme DO &#9;&#9;&#9;IF M IS HyperDocs.RegisterLinkMsg THEN &#9;&#9;&#9;&#9;WITH M&#58; HyperDocs.RegisterLinkMsg DO &#9;&#9;&#9;&#9;&#9;M.key &#58;&#61; SplitMailServer(M.link, mailadr, subject, body); &#9;&#9;&#9;&#9;&#9;IF M.key # HyperDocs.UndefKey THEN &#9;&#9;&#9;&#9;&#9;&#9;M.res &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSIF M IS Objects.AttrMsg THEN &#9;&#9;&#9;&#9;WITH M&#58; Objects.AttrMsg DO &#9;&#9;&#9;&#9;&#9;IF (M.id &#61; Objects.get) &#38; (M.name &#61; "Gen") THEN &#9;&#9;&#9;&#9;&#9;&#9;M.class &#58;&#61; Objects.String; &#9;&#9;&#9;&#9;&#9;&#9;M.s &#58;&#61; "Mail.NewMailServerLinkScheme"; &#9;&#9;&#9;&#9;&#9;&#9;M.res &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;HyperDocs.LinkSchemeHandler(L, M) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;HyperDocs.LinkSchemeHandler(L, M) &#9;&#9;&#9;END &#9;&#9;END &#9;END MailServerSchemeHandler; &#9;PROCEDURE NewMailServerLinkScheme*; &#9;&#9;VAR L&#58; HyperDocs.LinkScheme; &#9;BEGIN &#9;&#9;NEW(L); L.usePath &#58;&#61; FALSE; &#9;&#9;L.handle &#58;&#61; MailServerSchemeHandler; Objects.NewObj &#58;&#61; L &#9;END NewMailServerLinkScheme; &#9;PROCEDURE *LoadDoc(D&#58; Documents.Document); &#9;&#9;VAR &#9;&#9;&#9;T, text&#58; Texts.Text; &#9;&#9;&#9;objb&#58; Objects.Object; &#9;&#9;&#9;mailadr, subject, body&#58; ARRAY NetTools.PathStrLen OF CHAR; &#9;&#9;&#9;buffer&#58; ARRAY BufLen OF CHAR; &#9;&#9;&#9;key, beg, end, time, i&#58; LONGINT; &#9;&#9;&#9;node&#58; HyperDocs.Node; &#9;BEGIN &#9;&#9;IF Strings.CAPPrefix("mailto", D.name) THEN &#9;&#9;&#9;key &#58;&#61; SplitMailTo(D.name, mailadr); subject &#58;&#61; ""; body &#58;&#61; "" &#9;&#9;ELSIF Strings.CAPPrefix("mailserver", D.name) THEN &#9;&#9;&#9;key &#58;&#61; SplitMailServer(D.name, mailadr, subject, body) &#9;&#9;ELSE &#9;&#9;&#9;key &#58;&#61; HyperDocs.UndefKey &#9;&#9;END; &#9;&#9;IF key &#61; HyperDocs.UndefKey THEN &#9;&#9;&#9;D.dsc &#58;&#61; NIL; RETURN &#9;&#9;END; &#9;&#9;NEW(T); Texts.Open(T, ""); &#9;&#9;objb &#58;&#61; Gadgets.CreateObject("BasicGadgets.NewButton"); &#9;&#9;Attributes.SetString(objb, "Caption", "Send"); Attributes.SetString(objb, "Cmd", "Mail.Send @ &#126;"); &#9;&#9;Gadgets.NameObj(objb, "mailto"); &#9;&#9;Texts.WriteObj(W, objb); Texts.WriteLn(W); Texts.WriteLn(W); &#9;&#9;Texts.WriteString(W, "To&#58; "); Texts.WriteString(W, mailadr); Texts.WriteLn(W); &#9;&#9;Texts.WriteString(W, "Subject&#58; "); Texts.WriteString(W, subject); Texts.WriteLn(W); &#9;&#9;IF (HyperDocs.context # NIL) &#38; (HyperDocs.context.old # NIL) THEN &#9;&#9;&#9;node &#58;&#61; HyperDocs.context.old &#9;&#9;ELSE &#9;&#9;&#9;node &#58;&#61; HyperDocs.NodeByDoc(Desktops.CurDoc(Gadgets.context)) &#9;&#9;END; &#9;&#9;IF node # NIL THEN &#9;&#9;&#9;Texts.WriteString(W, "X-URL&#58; "); HyperDocs.RetrieveLink(node.key, buffer); Texts.WriteString(W, buffer); Texts.WriteLn(W) &#9;&#9;END; &#9;&#9;IF body # "" THEN &#9;&#9;&#9;Texts.WriteLn(W); i &#58;&#61; 0; &#9;&#9;&#9;WHILE body&#91;i&#93; # 0X DO &#9;&#9;&#9;&#9;IF body&#91;i&#93; &#61; "/" THEN &#9;&#9;&#9;&#9;&#9;Texts.WriteLn(W) &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;Texts.Write(W, body&#91;i&#93;) &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;INC(i) &#9;&#9;&#9;END; &#9;&#9;&#9;Texts.WriteLn(W) &#9;&#9;ELSE &#9;&#9;&#9;text &#58;&#61; NIL; time &#58;&#61; -1; &#9;&#9;&#9;Oberon.GetSelection(text, beg, end, time); &#9;&#9;&#9;IF (text # NIL) &#38; (time &#62; 0) THEN &#9;&#9;&#9;&#9;Texts.WriteLn(W); Texts.Append(T, W.buf); CiteText(W, text, beg, end) &#9;&#9;&#9;END &#9;&#9;END; &#9;&#9;Texts.Append(T, W.buf); &#9;&#9;COPY(mailadr, D.name); Links.SetLink(D.dsc, "Model", T); &#9;&#9;IF HyperDocs.context # NIL THEN &#9;&#9;&#9;HyperDocs.context.replace &#58;&#61; FALSE; HyperDocs.context.history &#58;&#61; FALSE &#9;&#9;END &#9;END LoadDoc; (** Mail.NewDoc &#9;&#9;Document new-procedure for "mailto&#58;" &#38; "mailserver&#58;" documents. &#9;&#9;E.g. Use Desktops.OpenDoc "mailto&#58;zeller@inf.ethz.ch" to send me a mail. *) &#9;PROCEDURE NewDoc*; &#9;&#9;VAR D&#58; Objects.Object; &#9;BEGIN &#9;&#9;D &#58;&#61; Gadgets.CreateObject("TextDocs.NewDoc"); &#9;&#9;D(Documents.Document).Load &#58;&#61; LoadDoc &#9;END NewDoc; BEGIN &#9;Oberon.GetClock(count, lastUIDL);	(* count &#58;&#61; time, throw away date *) &#9;Modules.InstallTermHandler(SaveIndexFile); &#9;trace &#58;&#61; NetTools.QueryBool("TraceMail"); &#9;mailer &#58;&#61; "Oberon Mail (ejz) on "; Strings.Append(mailer, Kernel.version); &#9;headFnt &#58;&#61; Fonts.This("Default12b.Scn.Fnt"); &#9;fieldFnt &#58;&#61; Fonts.This("Default12.Scn.Fnt"); &#9;textFnt &#58;&#61; Fonts.This("Courier10.Scn.Fnt"); &#9;Texts.OpenWriter(W); LoadMsgs; LoadTopics; &#9;NEW(mMethod); &#9;mMethod.Key &#58;&#61; Key; mMethod.Seek &#58;&#61; Seek; &#9;mMethod.Pos &#58;&#61; Pos; mMethod.Set &#58;&#61; Set; &#9;mMethod.State &#58;&#61; GetState; mMethod.SetState &#58;&#61; SetState; &#9;mMethod.GetStamp &#58;&#61; GetStamp; mMethod.SetStamp &#58;&#61; SetStamp; &#9;mMethod.Write &#58;&#61; Write; mMethod.WriteLink &#58;&#61; WriteLink; &#9;mMethod.DeleteLink &#58;&#61; DeleteLink; mMethod.Desc &#58;&#61; Desc; &#9;NEW(msgList); msgList.handle &#58;&#61; ModelHandler; &#9;NEW(vMethod); vMethod^ &#58;&#61; ListGadgets.methods^; &#9;vMethod.GetRider &#58;&#61; GetRider; &#9;vMethod.Display &#58;&#61; DisplayLine; vMethod.Format &#58;&#61; FormatLine; &#9;NEW(tmMethod); tmMethod^ &#58;&#61; mMethod^; &#9;tmMethod.Key &#58;&#61; TopicKey; tmMethod.Seek &#58;&#61; TopicSeek; &#9;tmMethod.Pos &#58;&#61; TopicPos; tmMethod.Set &#58;&#61; TopicSet; &#9;tmMethod.State &#58;&#61; TopicGetState; tmMethod.SetState &#58;&#61; TopicSetState; &#9;tmMethod.GetStamp &#58;&#61; TopicGetStamp; tmMethod.SetStamp &#58;&#61; TopicSetStamp; &#9;tmMethod.DeleteLink &#58;&#61; TopicDeleteLink; &#9;NEW(topicList); topicList.handle &#58;&#61; TopicModelHandler END Mail. !System.CopyFiles MailMessages &#61;&#62; ejz.MailMessages &#126; !System.CopyFiles ejz.MailMessages &#61;&#62; MailMessages &#126; !System.DeleteFiles MailMessages MailMessages.Bak lillian.inf.ethz.ch.zeller.UIDLs &#126; System.Set NetSystem Topic0 &#58;&#61; Miscellaneous &#126; System.Set NetSystem Topic1 &#58;&#61; "Bug Report" &#126; System.Set NetSystem Topic2 &#58;&#61; "To Do" &#126; ListGadgets.InsertVScrollList Mail.NewFrame Mail.NewModel &#126; Gadgets.Insert ListGadgets.NewFrame Mail.NewTopicModel &#126; Mail.Mod Mail.Panel Mail.Collect - snooper? - signature? - simplify GetUIDLs -&#62; Texts.LoadAscii - ReSync&#58; delete messages on server - import/export - use faster text search (t-search) (- query, optimize with stamp) LayLa.OpenAsDoc 	( CONFIG &#123; Mail.Panel &#125; &#9; &#123; Patch&#58; &#9; &#9;1. Mark the pin of the Settings iconizer and open a Columbus inspector. &#9; &#9;2. Click on pin of Settings iconizer to open settings panel. &#9; &#9;3. Click on Coords button in Columbus, set X&#61;4 Y&#61;-195 and Apply. &#9;&#125; &#9;(DEF CW 32) (DEF BW 42) (DEF BH 23) (DEF IW 42) (DEF IW2 87)  &#9;(DEF LW 80) (DEF LH 100) (DEF SW 376) (DEF SH 192) &#9;(DEF mailmodel (NEW Mail.NewModel)) &#9;(DEF topicmodel (NEW Mail.NewTopicModel)) &#9;(DEF query (NEW String (ATTR Name&#61;"Query" Value&#61;"topic&#61;ToDo"))) &#9;(DEF vpos (NEW Integer)) &#9;(DEF vrange (NEW Integer)) &#9;(DEF sortby (NEW Integer (ATTR Value&#61;1))) &#9;(DEF ascend (NEW Boolean (ATTR Value&#61;FALSE))) &#9;(DEF cont (NEW Integer (ATTR Name&#61;"ContType" Value&#61;3))) &#9; &#9;&#9; &#123; Iconizer front panels &#125;  &#9;( DEF set0 (HLIST Panel (w&#61;IW h&#61;BH vjustify&#61;CENTER hjustify&#61;CENTER) (ATTR Locked&#61;TRUE) &#9;&#9;(NEW Caption (ATTR Value&#61;"Set"))) &#9;) &#9;( DEF move0 (HLIST Panel (w&#61;IW h&#61;BH vjustify&#61;CENTER hjustify&#61;CENTER) (ATTR Locked&#61;TRUE) &#9;&#9;(NEW Caption (ATTR Value&#61;"Move"))) &#9;) &#9;( DEF clear0 (HLIST Panel (w&#61;IW h&#61;BH vjustify&#61;CENTER hjustify&#61;CENTER) (ATTR Locked&#61;TRUE) &#9;&#9;(NEW Caption (ATTR Value&#61;"Clear"))) &#9;) &#9;( DEF query0 (HLIST Panel (w&#61;IW h&#61;BH vjustify&#61;CENTER hjustify&#61;CENTER) (ATTR Locked&#61;TRUE) &#9;&#9;(NEW Caption (ATTR Value&#61;"Topic"))) &#9;) &#9;( DEF conf0 (HLIST Panel (w&#61;IW2 h&#61;BH vjustify&#61;CENTER hjustify&#61;CENTER) (ATTR Locked&#61;TRUE) &#9;&#9;(NEW Caption (ATTR Value&#61;"Settings"))) &#9;) &#9; &#9;&#9; &#123; Iconizer insides &#125; &#9;( DEF set1 &#9;&#9;(NEW ListGadget (w&#61;LW h&#61;LH) (LINKS Model&#61;topicmodel) (ATTR Cmd&#61;"Mail.SetTopic MailList &#39;#Point &#39;" Locked&#61;TRUE)) &#9;) &#9;( DEF move1 &#9;&#9;(NEW ListGadget (w&#61;LW h&#61;LH) (LINKS Model&#61;topicmodel) (ATTR Cmd&#61;"Mail.MoveTopic MailList &#39;#Point &#39;" Locked&#61;TRUE)) &#9;) &#9;( DEF clear1 &#9;&#9;(NEW ListGadget (w&#61;LW h&#61;LH) (LINKS Model&#61;topicmodel) (ATTR Cmd&#61;"Mail.ClearTopic MailList &#39;#Point &#39;" Locked&#61;TRUE)) &#9;) &#9;( DEF query1 &#9;&#9;(NEW ListGadget (w&#61;LW h&#61;LH) (LINKS Model&#61;topicmodel) (ATTR Cmd&#61;"Mail.QueryTopic Query &#39;#Point &#39;" Locked&#61;TRUE)) &#9;) &#9;( DEF conf1	 &#123; Settings panel &#125; &#9;&#9;( HLIST Panel (border&#61;5 w&#61;SW h&#61;SH dist&#61;14 vjustify&#61;CENTER) (ATTR Locked&#61;TRUE) &#9;&#9;&#9;( VLIST VIRTUAL (w&#61;&#91;2&#93; dist&#61;8) &#9;&#9;&#9;&#9;( HLIST VIRTUAL (w&#61;&#91;&#93; hjustify&#61;CENTER) &#9;&#9;&#9;&#9;&#9;(NEW Caption (ATTR Value&#61;"Local Settings (override Oberon.Text)")) &#9;&#9;&#9;&#9;) &#9;&#9;&#9;&#9;( TABLE VIRTUAL (w&#61;&#91;&#93; cols&#61;2) &#9;&#9;&#9;&#9;&#9;(NEW Caption (ATTR Value&#61;"EMail Address")) &#9;&#9;&#9;&#9;&#9;(NEW TextField (w&#61;&#91;10&#93;) (ATTR Name&#61;"EMail")) &#9;&#9;&#9;&#9;&#9;(NEW Caption (ATTR Value&#61;"SMTP Server")) &#9;&#9;&#9;&#9;&#9;(NEW TextField (w&#61;&#91;10&#93;) (ATTR Name&#61;"SMTP")) &#9;&#9;&#9;&#9;&#9;(NEW Caption (ATTR Value&#61;"POP Server")) &#9;&#9;&#9;&#9;&#9;( HLIST VIRTUAL (w&#61;&#91;10&#93;) &#9;&#9;&#9;&#9;&#9;&#9;(NEW TextField (w&#61;&#91;7&#93;) (ATTR Name&#61;"POP")) &#9;&#9;&#9;&#9;&#9;&#9;(NEW TextField (w&#61;&#91;3&#93;) (ATTR Name&#61;"POPMode") (ATTR Value&#61;"POP3")) &#9;&#9;&#9;&#9;&#9;) &#9;&#9;&#9;&#9;&#9;(NEW Caption (ATTR Value&#61;"POP User")) &#9;&#9;&#9;&#9;&#9;(NEW TextField (w&#61;&#91;10&#93;) (ATTR Name&#61;"User")) &#9;&#9;&#9;&#9;&#9;(NEW Caption (ATTR Value&#61;"Max message size")) &#9;&#9;&#9;&#9;&#9;(NEW TextField (w&#61;&#91;10&#93;) (ATTR Name&#61;"MaxMsgSize" Value&#61;"100000")) &#9;&#9;&#9;&#9;) &#9;&#9;&#9;&#9;( HLIST VIRTUAL (w&#61;&#91;&#93;) &#9;&#9;&#9;&#9;&#9;(NEW Caption (ATTR Value&#61;"Leave messages on server")) &#9;&#9;&#9;&#9;&#9;(NEW CheckBox (ATTR Name&#61;"LeaveOnServer")) &#9;&#9;&#9;&#9;&#9;(NEW VIRTUAL (w&#61;&#91;&#93;)) &#9;&#9;&#9;&#9;&#9;(NEW Caption (ATTR Value&#61;"Auto Cc")) &#9;&#9;&#9;&#9;&#9;(NEW CheckBox (ATTR Name&#61;"AutoCc" Value&#61;TRUE)) &#9;&#9;&#9;&#9;) &#9;&#9;&#9;) &#9;&#9;&#9;( TABLE VIRTUAL (w&#61;&#91;&#93; orientation&#61;VERT rows&#61;6) &#9;&#9;&#9;&#9;(HLIST VIRTUAL (w&#61;45 h&#61;BH hjustify&#61;CENTER vjustify&#61;CENTER) (NEW Caption (ATTR Value&#61;"Sorting"))) &#9;&#9;&#9;&#9;(NEW Button (w&#61;&#91;&#93; h&#61;BH) (ATTR Caption&#61;"Date" SetVal&#61;1) (LINKS Model&#61;sortby)) &#9;&#9;&#9;&#9;(NEW Button (w&#61;&#91;&#93; h&#61;BH) (ATTR Caption&#61;"From" SetVal&#61;2) (LINKS Model&#61;sortby)) &#9;&#9;&#9;&#9;(NEW Button (w&#61;&#91;&#93; h&#61;BH) (ATTR Caption&#61;"Subject" SetVal&#61;3) (LINKS Model&#61;sortby)) &#9;&#9;&#9;&#9;(NEW Button (w&#61;&#91;&#93; h&#61;BH) (ATTR Caption&#61;"None" SetVal&#61;0) (LINKS Model&#61;sortby)) &#9;&#9;&#9;&#9;( SPAN 1 2 &#9;&#9;&#9;&#9;&#9;( HLIST VIRTUAL (w&#61;&#91;&#93;) &#9;&#9;&#9;&#9;&#9;&#9;(NEW Caption (ATTR Value&#61;"Ascending")) &#9;&#9;&#9;&#9;&#9;&#9;(NEW CheckBox (LINKS Model&#61;ascend)) &#9;&#9;&#9;&#9;&#9;&#9;(NEW VIRTUAL (w&#61;&#91;&#93;)) &#9;&#9;&#9;&#9;&#9;) &#9;&#9;&#9;&#9;) &#9;&#9;&#9;&#9;(HLIST VIRTUAL (w&#61;&#91;&#93; h&#61;BH hjustify&#61;CENTER vjustify&#61;CENTER) (NEW Caption (ATTR Value&#61;"Content"))) &#9;&#9;&#9;&#9;(NEW Button (w&#61;&#91;&#93; h&#61;BH) (ATTR Caption&#61;"Auto" SetVal&#61;3) (LINKS Model&#61;cont)) &#9;&#9;&#9;&#9;(NEW Button (w&#61;&#91;&#93; h&#61;BH) (ATTR Caption&#61;"Oberon" SetVal&#61;2) (LINKS Model&#61;cont)) &#9;&#9;&#9;&#9;(NEW Button (w&#61;&#91;&#93; h&#61;BH) (ATTR Caption&#61;"ISO-8859-1" SetVal&#61;1) (LINKS Model&#61;cont)) &#9;&#9;&#9;&#9;(NEW Button (w&#61;&#91;&#93; h&#61;BH) (ATTR Caption&#61;"ASCII" SetVal&#61;0) (LINKS Model&#61;cont)) &#9;&#9;&#9;) &#9;&#9;) &#9;) &#9;&#9; &#123; Main panel &#125; &#9;( VLIST Panel (border&#61;5 w&#61;384 h&#61;200 dist&#61;3 vjustify&#61;CENTER) (ATTR Locked&#61;TRUE) &#9;&#9;( HLIST VIRTUAL (w&#61;&#91;&#93; h&#61;&#91;&#93; dist&#61;0)	 &#123; Mail list &#38; scrollbar &#125; &#9;&#9;&#9;( NEW Mail.NewFrame (w&#61;&#91;&#93; h&#61;&#91;&#93;) (ATTR Name&#61;"MailList") &#9;&#9;&#9;&#9;(LINKS Model&#61;mailmodel SortBy&#61;sortby Ascending&#61;ascend Query&#61;query VPos&#61;vpos VRange&#61;vrange) &#9;&#9;&#9;) &#9;&#9;&#9;(NEW Scrollbar (h&#61;&#91;&#93;) (ATTR Max&#61;0 HeavyDrag&#61;TRUE) (LINKS Min&#61;vrange Model&#61;vpos)) &#9;&#9;) &#9;&#9;( HLIST VIRTUAL (w&#61;&#91;&#93; vdist&#61;5 hdist&#61;3 vjustify&#61;CENTER)	 &#123; Top row &#125; &#9;&#9;&#9;(HLIST VIRTUAL (w&#61;CW hjustify&#61;CENTER) (NEW Caption (ATTR Value&#61;"Show"))) &#9;&#9;&#9;(NEW Button (w&#61;BW h&#61;BH) (ATTR Caption&#61;"ToDo" Cmd&#61;"Gadgets.Set Query.Value &#39;topic&#61;ToDo&#39;")) &#9;&#9;&#9;(NEW Button (w&#61;BW h&#61;BH) (ATTR Caption&#61;"All" Cmd&#61;"Gadgets.Set Query.Value &#39;&#39;")) &#9;&#9;&#9;(NEW TextField (w&#61;&#91;&#93;) (LINKS Model&#61;query)) &#9;&#9;&#9;(NEW Iconizer (w&#61;IW h&#61;&#91;&#93;) (ATTR Popup&#61;TRUE Pin&#61;FALSE Locked&#61;TRUE) (LINKS Closed&#61;query0 Open&#61;query1)) &#9;&#9;) &#9;&#9;( HLIST VIRTUAL (w&#61;&#91;&#93; vdist&#61;5 hdist&#61;3 vjustify&#61;CENTER)	 &#123; Middle row &#125; &#9;&#9;&#9;(HLIST VIRTUAL (w&#61;CW hjustify&#61;CENTER) (NEW Caption (ATTR Value&#61;"Text"))) &#9;&#9;&#9;(NEW Button (w&#61;BW h&#61;BH) (ATTR Caption&#61;"Reply" Cmd&#61;"Mail.Reply ^")) &#9;&#9;&#9;(NEW Button (w&#61;BW h&#61;BH) (ATTR Caption&#61;"Cite ^" Cmd&#61;"Mail.Cite")) &#9;&#9;&#9;(NEW Button (w&#61;66 h&#61;BH) (ATTR Caption&#61;"AsciiCode ^" Cmd&#61;"AsciiCoder.CodeFiles % ^")) &#9;&#9;&#9;(NEW VIRTUAL (w&#61;&#91;&#93;)) &#9;&#9;&#9;(HLIST VIRTUAL (w&#61;BW hjustify&#61;CENTER) (NEW Caption (ATTR Value&#61;"Topic"))) &#9;&#9;&#9;(NEW Iconizer (w&#61;IW h&#61;&#91;&#93;) (ATTR Popup&#61;TRUE Pin&#61;FALSE Locked&#61;TRUE) (LINKS Closed&#61;set0 Open&#61;set1)) &#9;&#9;&#9;(NEW Iconizer (w&#61;IW h&#61;&#91;&#93;) (ATTR Popup&#61;TRUE Pin&#61;FALSE Locked&#61;TRUE) (LINKS Closed&#61;clear0 Open&#61;clear1)) &#9;&#9;&#9;(NEW Iconizer (w&#61;IW h&#61;&#91;&#93;) (ATTR Popup&#61;TRUE Pin&#61;FALSE Locked&#61;TRUE) (LINKS Closed&#61;move0 Open&#61;move1)) &#9;&#9;) &#9;&#9;( HLIST VIRTUAL (w&#61;&#91;&#93; vdist&#61;5 hdist&#61;3 vjustify&#61;CENTER)	 &#123; Bottom row &#125; &#9;&#9;&#9;(HLIST VIRTUAL (w&#61;CW hjustify&#61;CENTER) (NEW Caption (ATTR Value&#61;"Server"))) &#9;&#9;&#9;(NEW Button (w&#61;BW h&#61;BH) (ATTR Caption&#61;"Get" Cmd&#61;"Mail.Synchronize")) &#9;&#9;&#9;(NEW Button (w&#61;BW h&#61;BH) (ATTR Caption&#61;"Send *" Cmd&#61;"Mail.Send *")) &#9;&#9;&#9;(NEW TextField (w&#61;&#91;&#93;) (ATTR Name&#61;"StatusBar" Value&#61;"")) &#9;&#9;&#9;(NEW Iconizer (w&#61;IW2 h&#61;&#91;&#93;) (ATTR FixedViews&#61;FALSE Locked&#61;TRUE) (LINKS Closed&#61;conf0 Open&#61;conf1)) &#9;&#9;) &#9;) ) UIDL handling POPCollect -&#62; remove all UIDLs -&#62; new UIDL file Synchronize -&#62; store only UIDLs current (from UIDL command) UIDL System.Directory UIDL.* System.Free News Mail NetTools HyperDocs MIME &#126;