Oberon/A2/Oberon.News.Mod

(* ETH Oberon, Copyright 2001 ETH Zuerich Institut fuer Computersysteme, ETH Zentrum, CH-8092 Zuerich. Refer to the "General ETH Oberon System Source License" contract available at: http:&#47;/www.oberon.ethz.ch/ *) MODULE News IN Oberon; (** portable *)	(* ejz, *) IMPORT BTrees, Strings, HyperDocs, Files, Objects, Texts, Display, Fonts, Display3, Oberon, NetSystem, NetTools, Gadgets, Attributes, TextGadgets, TextDocs, Documents, Desktops, Links, Modules, MIME, Streams, TextStreams, Mail, Dates, FileDir; (** This module implements a newsreader (RFC 977, 1036) for oberon. The News-module supports news and nntp urls. 		The nntp host is specified in the NetSystem section of the Registry (e.g. NNTP=news.inf.ethz.ch). 		The following lines should be added to the LinkSchemes section of the Registry: 			nntp = News.NewNNTPLinkScheme 			news = News.NewNewsLinkScheme 		And the following lines to the Documents section: 			nntp News.NewDoc 			news News.NewDoc. 		For sending or posting new articles your e-mail address should be defined in the NetSystem section of the Registry. 		e.g.: EMail = "me@home" *) CONST DefPort = 119; InitText = "News.Read.Text"; Done* = NetTools.Done; ErrGroup* = 1; ErrXOver* = 2; ErrArticle* = 3; ErrStat* = 4; ErrList* = 5; ErrPost* = 6; Failed* = NetTools.Failed; MaxMessages = 16*1024; TYPE ArtNrs = POINTER TO ArtNrsDesc; ArtNrsDesc = RECORD beg, end: SIGNED32; next: ArtNrs END; Group = POINTER TO GroupDesc; GroupDesc = RECORD name: ARRAY 128 OF CHAR; subscribed: BOOLEAN; readArtNrs: ArtNrs; next: Group END; (** The connection to an nntp. *) Session* = POINTER TO SessionDesc; SessionDesc* = RECORD (Mail.SMTPSessionDesc) END; VAR W, Wr: Texts.Writer; groups, curGrp: Group; curGrpNewOnly: BOOLEAN; newgDate, newgTime: SIGNED32; EMail: ARRAY NetTools.ServerStrLen OF CHAR; refs: Files.File; indexM, indexA: BTrees.Tree; availBeg, beg, end, level, thread: SIGNED32; marked: POINTER TO ARRAY OF BOOLEAN; line: ARRAY 2*NetTools.MaxLine OF CHAR; newsFont: Fonts.Font; trace: BOOLEAN; PROCEDURE ScanInt(VAR S: Texts.Scanner; VAR i: SIGNED16); BEGIN IF S.class = Texts.Int THEN i := SHORT(S.i); Texts.Scan(S) ELSE i := 0 END END ScanInt; PROCEDURE ScanDate(VAR S: Texts.Scanner; VAR date: SIGNED32); VAR day, month, year: SIGNED16; BEGIN ScanInt(S, day); ScanInt(S, month); ScanInt(S, year); IF year &#60; 100 THEN IF (month = 0) OR (day = 0) THEN year := 1980; month := 1; day := 1 ELSE year := year + 1900 END END; date := Dates.ToDate(year, month, day) END ScanDate; PROCEDURE ScanTime(VAR S: Texts.Scanner; VAR time: SIGNED32); VAR hour, min, sec: SIGNED16; BEGIN ScanInt(S, hour); ScanInt(S, min); ScanInt(S, sec); time := Dates.ToTime(hour, min, sec) END ScanTime; PROCEDURE TwoDigit(i: SIGNED16; VAR str: ARRAY OF CHAR); BEGIN str&#91;0] := CHR((i DIV 10)+ORD("0")); str&#91;1] := CHR((i MOD 10)+ORD("0")); str&#91;2] := 0X END TwoDigit; PROCEDURE ConcatDate(VAR line: ARRAY OF CHAR; VAR date: SIGNED32); VAR str: ARRAY 8 OF CHAR; day, month, year: SIGNED16; BEGIN Dates.ToYMD(date, year, month, day); IF year &#60; 2000 THEN TwoDigit(year MOD 100, str) ELSE Strings.IntToStr(year, str) END; Strings.Append(line, str); TwoDigit(month, str); Strings.Append(line, str); TwoDigit(day, str); Strings.Append(line, str) END ConcatDate; PROCEDURE ConcatTime(VAR line: ARRAY OF CHAR; VAR time: SIGNED32); VAR str: ARRAY 8 OF CHAR; hour, min, sec: SIGNED16; BEGIN Dates.ToHMS(time, hour, min, sec); TwoDigit(hour, str); Strings.Append(line, str); TwoDigit(min, str); Strings.Append(line, str); TwoDigit(sec, str); Strings.Append(line, str) END ConcatTime; PROCEDURE LoadInitText; VAR text: Texts.Text; S: Texts.Scanner; group, lastg: Group; art, last: ArtNrs; i: SIGNED32; val: ARRAY 64 OF CHAR; BEGIN IF NetTools.QueryString("NewsFont", val) & (Fonts.This(val) # NIL) THEN newsFont := Fonts.This(val) ELSE newsFont := Fonts.Default END; groups := NIL; lastg := NIL; NEW(text); Texts.Open(text, InitText); Texts.OpenScanner(S, text, 0); Texts.Scan(S); ScanDate(S, newgDate); ScanTime(S, newgTime); WHILE ~S.eot DO 			NEW(group); IF (S.class = Texts.Char) & (S.c = "!") THEN group.subscribed := FALSE; Texts.Scan(S) ELSE group.subscribed := TRUE END; IF S.class IN {Texts.Name, Texts.String} THEN COPY(S.s, group.name); i := 0; WHILE group.name&#91;i] # 0X DO 					INC(i) END; IF group.name&#91;i-1] = ":" THEN group.name&#91;i-1] := 0X END; group.next := NIL; IF lastg # NIL THEN lastg.next := group ELSE groups := group END; lastg := group; group.readArtNrs := NIL; last := NIL; Texts.Scan(S); IF ((S.class = Texts.Char) & (S.c = ":")) OR ((S.class IN {Texts.Name, Texts.String}) & (S.s = ":")) THEN Texts.Scan(S) END; WHILE ~S.eot & (S.class = Texts.Int) DO 					NEW(art); art.next := NIL; IF last = NIL THEN group.readArtNrs := art ELSE last.next := art END; last := art; art.beg := S.i; Texts.Scan(S); IF ((S.class = Texts.Char) & (S.c = "-")) OR ((S.class IN {Texts.Name, Texts.String}) & (S.s = "-")) THEN Texts.Scan(S); IF S.class = Texts.Int THEN art.end := S.i; 							Texts.Scan(S) ELSE art.end := art.beg END ELSIF (S.class = Texts.Int) & (S.i &#60; 0) THEN art.end := -S.i; 						Texts.Scan(S) ELSE art.end := art.beg END; IF (S.class = Texts.Char) & (S.c = ",") THEN Texts.Scan(S) END END ELSE Texts.Scan(S) END END END LoadInitText; PROCEDURE WriteDate(VAR W: Texts.Writer; VAR date: SIGNED32); VAR day, month, year: SIGNED16; BEGIN Dates.ToYMD(date, year, month, day); Texts.WriteInt(W, day, 0); Texts.Write(W, " "); Texts.WriteInt(W, month, 0); Texts.Write(W, " "); Texts.WriteInt(W, year, 0) END WriteDate; PROCEDURE WriteTime(VAR W: Texts.Writer; VAR time: SIGNED32); VAR hour, min, sec: SIGNED16; BEGIN Dates.ToHMS(time, hour, min, sec); Texts.WriteInt(W, hour, 0); Texts.Write(W, " "); Texts.WriteInt(W, min, 0); Texts.Write(W, " "); Texts.WriteInt(W, sec, 0) END WriteTime; PROCEDURE storeInitText; VAR text: Texts.Text; group: Group; art: ArtNrs; F: Files.File; len: SIGNED32; name: FileDir.FileName; BEGIN NEW(text); Texts.Open(text, ""); WriteDate(W, newgDate); Texts.Write(W, " "); WriteTime(W, newgTime); Texts.WriteLn(W); group := groups; WHILE group # NIL DO 			IF ~group.subscribed THEN Texts.Write(W, "!") END; len := 0; WHILE (group.name&#91;len] # 0X) & (Strings.IsAlpha(group.name&#91;len]) OR (group.name&#91;len] = ".")) DO 				INC(len) END; IF group.name&#91;len] # 0X THEN Texts.Write(W, 22X); Texts.WriteString(W, group.name); Texts.Write(W, 22X) ELSE Texts.WriteString(W, group.name) END; Texts.WriteString(W, ": "); art := group.readArtNrs; WHILE art # NIL DO 				Texts.WriteInt(W, art.beg, 0); IF art.end > art.beg THEN Texts.WriteString(W, " - "); Texts.WriteInt(W, art.end, 0) END; art := art.next; IF art # NIL THEN Texts.WriteString(W, ", ") END END; Texts.WriteLn(W); group := group.next END; Texts.Append(text, W.buf); F := Files.Old(InitText); IF F # NIL THEN Files.GetName(F, name) ELSE COPY(InitText, name) END; F := Files.New(name); Texts.Store(text, F, 0, len); Files.Register(F) END storeInitText; (** News.StoreInitText 		Store information on read articles and subscribed groups. *) PROCEDURE StoreInitText*; BEGIN Texts.WriteString(W, "Store "); Texts.WriteString(W, InitText); Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf); storeInitText END StoreInitText; PROCEDURE NewArtNr(nr: SIGNED32): ArtNrs; VAR art: ArtNrs; BEGIN NEW(art); art.next := NIL; art.beg := nr; art.end := nr; RETURN art END NewArtNr; PROCEDURE AddArtNr(group: Group; nr: SIGNED32); VAR prev, cur, art: ArtNrs; BEGIN prev := NIL; cur := group.readArtNrs; WHILE (cur # NIL) & (cur.beg &#60;= nr) DO 			prev := cur; cur := cur.next END; IF cur # NIL THEN IF cur.beg = (nr+1) THEN cur.beg := nr 			ELSIF prev # NIL THEN IF prev.end = (nr-1) THEN prev.end := nr 				ELSIF prev.end &#60; nr THEN art := NewArtNr(nr); prev.next := art; art.next := cur END ELSE art := NewArtNr(nr); prev := art; group.readArtNrs := art; art.next := cur END; IF (prev # NIL) & ((prev.end+1) = cur.beg) THEN prev.end := cur.end; prev.next := cur.next END ELSIF prev # NIL THEN IF prev.end = (nr-1) THEN prev.end := nr 			ELSIF prev.end &#60; nr THEN art := NewArtNr(nr); prev.next := art END ELSE art := NewArtNr(nr); group.readArtNrs := art END END AddArtNr; PROCEDURE GetGroup(name: ARRAY OF CHAR; new: BOOLEAN): Group; VAR group: Group; BEGIN group := groups; WHILE (group # NIL) & (group.name # name) DO 			group := group.next END; IF (group = NIL) & new THEN NEW(group); COPY(name, group.name); group.subscribed := FALSE; group.readArtNrs := NIL; group.next := groups; groups := group END; RETURN group END GetGroup; PROCEDURE ReadArt(group: Group; nr: SIGNED32): BOOLEAN; VAR article: ArtNrs; BEGIN article := group.readArtNrs; WHILE (article # NIL) & (article.beg &#60;= nr) DO 			IF (nr >= article.beg) & (nr &#60;= article.end) THEN RETURN TRUE END; article := article.next END; RETURN FALSE END ReadArt; PROCEDURE ReadResponse(S: Session); VAR i: SIGNED32; BEGIN NetSystem.ReadString(S.C, S.reply); IF trace THEN Texts.WriteString(W, "RCV: "); Texts.WriteString(W, S.reply); Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf) END; Strings.StrToInt(S.reply, i); S.status := SHORT(i) END ReadResponse; (** Open a new session to nntp-host host on ort port. *) PROCEDURE Open*(VAR S: Session; host: ARRAY OF CHAR; port: SIGNED16); BEGIN NEW(S); IF NetTools.Connect(S.C, port, host, FALSE) THEN ReadResponse(S); IF S.status # 200 THEN NetTools.Disconnect(S.C); S.C := NIL; S.S := NIL ELSE S.S := NetTools.OpenStream(S.C); S.res := Done; RETURN END ELSE S.reply := "no connection" END; S.res := Failed END Open; PROCEDURE Open1*(VAR S: Session; host, user, passwd: ARRAY OF CHAR; port: SIGNED16); BEGIN IF trace THEN Texts.WriteString(W, "--- NNTP"); Texts.WriteLn(W); Texts.WriteString(W, "host = "); Texts.WriteString(W, host); Texts.WriteLn(W); Texts.WriteString(W, "user = "); Texts.WriteString(W, user); Texts.WriteLn(W); (* Texts.WriteString(W, "passwd = "); Texts.WriteString(W, passwd); Texts.WriteLn(W); *) Texts.Append(Oberon.Log, W.buf) END; NEW(S); S.res := NetTools.Failed; S.C := NIL; S.S := NIL; IF host&#91;0] = 0X THEN S.reply := "no news-host specified" ELSE (* nntp-host name available *) IF ~NetTools.Connect(S.C, port, host, FALSE) THEN S.reply := "no connection"; S.res := Failed ELSE (* Connection established. *) S.S := NetTools.OpenStream(S.C); ReadResponse(S); IF S.status # 200 THEN (* Server declined to open stream. *) NetTools.Disconnect(S.C); S.C := NIL; S.S := NIL; S.res := Failed ELSE (* Stream opened. *) IF (user&#91;0] = 0X) OR (passwd&#91;0] = 0X) THEN IF trace THEN Texts.WriteString(W, "(user&#91;0] = 0X) OR (passwd&#91;0] = 0X)). Authentication not possible "); 							Texts.WriteString(W, "but server may proceed without authentication."); Texts.WriteLn(W); 							Texts.Append(Oberon.Log, W.buf) 						END 					ELSE(* Try to authenticate. *) 						Mail.SendCmd(S, "CAPABILITIES", ""); 						REPEAT ReadResponse(S) UNTIL S.reply&#91;0] = "."; 						Mail.SendCmd(S, "AUTHINFO USER", user); 						ReadResponse(S); 						IF S.reply&#91;0] = "3" THEN (* user received; passwd now required. *) 							Mail.SendCmd(S, "AUTHINFO PASS", passwd); 							ReadResponse(S); 							IF S.reply&#91;0] = "2" THEN 								IF trace THEN 									Texts.WriteString(W, "Authentication accepted."); Texts.WriteLn(W); 									Texts.Append(Oberon.Log, W.buf); 								END 							ELSIF (S.reply&#91;0] = "4") & (S.reply&#91;2] = "2") THEN 								IF trace THEN 									Texts.WriteString(W, "Authentication failed/rejected."); Texts.WriteLn(W); 									Texts.Append(Oberon.Log, W.buf) 								END 							END 						END 					END; 					S.res := Done 				END 			END 		END 	END Open1; (** Close the connection for session S. *) 	PROCEDURE Close*(S: Session); 	BEGIN 		IF S.C # NIL THEN 			NetTools.Disconnect(S.C); S.C := NIL; S.S := NIL 		END 	END Close; 	PROCEDURE Connect(VAR S: Session): BOOLEAN; 		VAR 			NNTPHost, user, passwd: ARRAY 64 OF CHAR; 			NNTPPort: SIGNED16; 	BEGIN 		NetTools.GetHostPort("NNTP", NNTPHost, NNTPPort, DefPort); IF NNTPHost # "" THEN NetSystem.GetPassword("nntp", NNTPHost, user, passwd); Open1(S, NNTPHost, user, passwd, NNTPPort) ELSE NEW(S); S.C := NIL; S.reply := "NNTP-Host not set"; S.res := Failed END; RETURN S.res = Done END Connect; PROCEDURE RegisterNewsAdr(host, group: ARRAY OF CHAR): SIGNED32; VAR key: SIGNED32; BEGIN COPY("news:", line); Strings.Append(line, group); IF host # "" THEN Strings.AppendCh(line, "@"); Strings.Append(line, host) END; key := HyperDocs.RegisterLink(line); RETURN key END RegisterNewsAdr; PROCEDURE WriteGroup(VAR group: ARRAY OF CHAR); VAR i, key: SIGNED32; link: Objects.Object; BEGIN Texts.SetColor(W, SHORT(HyperDocs.linkC)); i := 0; WHILE (group&#91;i] # 0X) & (group&#91;i] > " ") DO 			Texts.Write(W, group&#91;i]); INC(i) END; group&#91;i] := 0X; key := RegisterNewsAdr("", group); link := HyperDocs.LinkControl(key); Texts.WriteObj(W, link); Texts.SetColor(W, SHORT(Display3.textC)); Texts.WriteLn(W) END WriteGroup; PROCEDURE SubGroups(T: Texts.Text); VAR group: Group; BEGIN group := groups; WHILE group # NIL DO 			IF group.subscribed THEN WriteGroup(group.name) END; group := group.next END; Texts.Append(T, W.buf) END SubGroups; (** Write al list of all available groups to T. *) PROCEDURE AllGroups*(S: Session; VAR T: Texts.Text); BEGIN NetSystem.WriteString(S.C, "LIST"); ReadResponse(S); IF S.status = 215 THEN NEW(T); Texts.Open(T, ""); NetSystem.ReadString(S.C, line); WHILE (line&#91;0] # ".") OR (line&#91;1] # 0X) DO 				Texts.WriteString(W, "news:"); Texts.WriteString(W, line); Texts.WriteLn(W); NetSystem.ReadString(S.C, line) END; Texts.Append(T, W.buf); S.res := Done ELSE T := NIL; S.res := ErrList END END AllGroups; (** List all new groups since the last access. *) PROCEDURE NewGroups*(S: Session; date, time: SIGNED32; VAR T: Texts.Text); BEGIN line := "NEWGROUPS "; ConcatDate(line, date); Strings.AppendCh(line, " "); ConcatTime(line, time); NetSystem.WriteString(S.C, line); ReadResponse(S); IF S.status = 231 THEN NEW(T); Texts.Open(T, ""); NetSystem.ReadString(S.C, line); WHILE (line&#91;0] # ".") OR (line&#91;1] # 0X) DO 				Texts.WriteString(W, "news:"); Texts.WriteString(W, line); Texts.WriteLn(W); NetSystem.ReadString(S.C, line) END; Texts.Append(T, W.buf); S.res := Done ELSE T := NIL; S.res := ErrList END END NewGroups; PROCEDURE NewGrp(S: Session; VAR T: Texts.Text); VAR time, date: SIGNED32; BEGIN Oberon.GetClock(time, date); NewGroups(S, newgDate, newgTime, T); IF S.res = Done THEN newgDate := date; newgTime := time END END NewGrp; PROCEDURE HorzRule: Objects.Object; VAR obj: Objects.Object; BEGIN obj := Gadgets.CreateObject("BasicFigures.NewRect3D"); Attributes.SetBool(obj, "Filled", TRUE); Attributes.SetInt(obj, "Color", Display3.textbackC); Gadgets.ModifySize(obj(Display.Frame), Display.Width, 4); RETURN obj END HorzRule; PROCEDURE WriteGrpHead(group: ARRAY OF CHAR); BEGIN Texts.Write(Wr, 22X); Texts.WriteString(Wr, "news:"); Texts.WriteString(Wr, group); Texts.Write(Wr, 22X); Texts.WriteLn(Wr); Texts.WriteObj(Wr, HorzRule); Texts.WriteLn(Wr) END WriteGrpHead; PROCEDURE RegisterNNTPAdr(group: ARRAY OF CHAR; artnr: SIGNED32): SIGNED32; VAR line: ARRAY NetTools.MaxLine OF CHAR; str: ARRAY 12 OF CHAR; key: SIGNED32; BEGIN COPY("nntp:", line); Strings.Append(line, group); Strings.AppendCh(line, "/"); Strings.IntToStr(artnr, str); Strings.Append(line, str); key := HyperDocs.RegisterLink(line); RETURN key END RegisterNNTPAdr; PROCEDURE WriteArticle(nr: SIGNED32; VAR line: ARRAY OF CHAR); VAR link: Objects.Object; i, key: SIGNED32; BEGIN IF nr >= availBeg THEN IF ~ReadArt(curGrp, nr) THEN Texts.SetColor(Wr, SHORT(Display3.red)) ELSE Texts.SetColor(Wr, SHORT(HyperDocs.linkC)) END ELSE Texts.SetColor(Wr, SHORT(Display3.textC)) END; i := 0; WHILE line&#91;i] # 0X DO 			Texts.Write(Wr, line&#91;i]); IF (line&#91;i] = Strings.Tab) & (Wr.col # Display3.textC) THEN key := RegisterNNTPAdr(curGrp.name, nr); link := HyperDocs.LinkControl(key); Texts.WriteObj(Wr, link); Texts.SetColor(Wr, SHORT(Display3.textC)) END; INC(i) END; Texts.WriteLn(Wr); Texts.SetColor(Wr, SHORT(Display3.textC)) END WriteArticle; PROCEDURE ListArts(T: Texts.Text); VAR R: Files.Rider; key, org: SIGNED32; BEGIN Files.Set(R, refs, 0); Files.ReadLInt(R, org); WHILE ~R.eof DO 			Files.ReadLInt(R, key); Files.Set(R, refs, org); Files.ReadString(R, line); WriteArticle(key, line); Texts.Insert(T, 0, Wr.buf); Files.ReadLInt(R, org) END END ListArts; PROCEDURE enumThread(key, org: SIGNED32; VAR cont: BOOLEAN); VAR R: Files.Rider; sorg, app, i, oldThread: SIGNED32; inthread: BOOLEAN; BEGIN inthread := FALSE; Files.Set(R, refs, org); Files.ReadLInt(R, sorg); Files.ReadLInt(R, app); Files.ReadLInt(R, app); WHILE (app >= 0) & ~inthread DO	(* for all references *) inthread := thread = app; Files.ReadLInt(R, app) END; IF inthread & ~marked&#91;key-beg] THEN marked&#91;key-beg] := TRUE; Files.Set(R, refs, sorg); Files.ReadString(R, line); FOR i := 1 TO level DO 				Texts.Write(Wr, Strings.Tab) END; WriteArticle(key, line); oldThread := thread; thread := org; INC(level); BTrees.EnumLInt(indexA, key+1, end, enumThread); thread := oldThread; DEC(level) END END enumThread; PROCEDURE Thread(T: Texts.Text); VAR R: Files.Rider; a, org, sorg, porg: SIGNED32; re: SIGNED16; BEGIN NEW(marked, MaxMessages); FOR a := 0 TO MaxMessages-1 DO 			marked&#91;a] := FALSE END; IF (end - beg) >= MaxMessages THEN beg := end - MaxMessages  + 1 END; NetTools.curLen := end-beg; FOR a := beg TO end DO	(* from oldest to newest *) BTrees.SearchLInt(indexA, a, org, re); IF re = BTrees.Done THEN Files.Set(R, refs, org); Files.ReadLInt(R, sorg); Files.ReadLInt(R, porg); Files.ReadLInt(R, porg); IF porg &#60; 0 THEN	(* article has no references *) Files.Set(R, refs, sorg); Files.ReadString(R, line); WriteArticle(a, line); marked&#91;a-beg] := TRUE; thread := org; level := 1; BTrees.EnumLInt(indexA, a+1, end, enumThread)	(* enum all newer articles *) ELSIF ~marked&#91;a-beg] THEN (* article not yet marked *) Files.Set(R, refs, sorg); Files.ReadString(R, line); WriteArticle(a, line); marked&#91;a-beg] := TRUE END; Texts.Insert(T, 0, Wr.buf) END END; marked := NIL END Thread; (** List all available articles in group in a certain range. 0-0 = all *) PROCEDURE ArticleRange(S: Session; group: ARRAY OF CHAR; VAR T: Texts.Text; thread: BOOLEAN; from, to: SIGNED32); VAR nr: SIGNED32; org, org2, org3, fixup: SIGNED32; str: ARRAY 16 OF CHAR; msgid: ARRAY 128 OF CHAR; dummy: ARRAY 256 OF CHAR; R: Files.Rider; i, j, iRef, bres: SIGNED16; BEGIN line := "GROUP "; Strings.Append(line, group); NetSystem.WriteString(S.C, line); ReadResponse(S); IF S.status = 211 THEN NEW(T); Texts.Open(T, ""); i := 0; Strings.StrToIntPos(S.reply, beg, i); Strings.StrToIntPos(S.reply, beg, i); Strings.StrToIntPos(S.reply, beg, i); Strings.StrToIntPos(S.reply, end, i); Texts.WriteString(W, group); Texts.WriteString(W, " available: "); Texts.WriteInt(W, beg, 1); Texts.Write(W, "-");  Texts.WriteInt(W, end, 1); IF (from # 0) & (to = 0) THEN (* get 'from' newest articles *) beg := end - from + 1 ELSIF (from # 0) & (to # 0) THEN (* from-to articles *) beg := from; end := to 			END; (* be careful that your number range is smaller than MaxMessages *) IF (end - beg) >= MaxMessages THEN beg := end - MaxMessages  + 1 END; Texts.WriteString(W, ", get "); Texts.WriteInt(W, beg, 1); Texts.Write(W, "-"); Texts.WriteInt(W, end, 1); IF curGrpNewOnly THEN Texts.WriteString(W, " unread") END; Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf); indexM := BTrees.NewStr(Files.New(""), 0, SHORT(2*(end-beg))); BTrees.Flush(indexM); indexA := BTrees.NewLInt(BTrees.Base(indexM), Files.Length(BTrees.Base(indexM)), SHORT(2*(end-beg))); NetTools.curLen := end-beg; availBeg := beg; refs := Files.New(""); Files.Set(R, refs, 0); line := "XOVER "; Strings.IntToStr(beg, str); Strings.Append(line, str); Strings.AppendCh(line, "-"); Strings.IntToStr(end, str); Strings.Append(line, str); NetSystem.WriteString(S.C, line); ReadResponse(S); IF S.status = 224 THEN curGrp := GetGroup(group, TRUE); NetSystem.ReadString(S.C, line); WHILE (line&#91;0] # ".") OR (line&#91;1] # 0X) DO	(* parse "message" line *) Strings.StrToInt(line, nr); IF ~curGrpNewOnly OR ~ReadArt(curGrp, nr) THEN i := 0; j := 0; WHILE (line&#91;i] # 0X) & (j &#60; 4) DO 							IF line&#91;i] = Strings.Tab THEN INC(j) END; INC(i) END; line&#91;i-1] := 0X; j := 0; WHILE (line&#91;i] > " ") & (line&#91;i] # ">") DO 							msgid&#91;j] := line&#91;i]; INC(j); INC(i) END; IF line&#91;i] = ">" THEN msgid&#91;j] := line&#91;i]; INC(j); INC(i) END; msgid&#91;j] := 0X; Strings.Upper(msgid, msgid); WHILE (line&#91;i] # 0X) & (line&#91;i] # "&#60;") DO 							INC(i) END; org := Files.Pos(R); BTrees.InsertStr(indexM, msgid, org, bres);	(* add msgid to msgs index *) BTrees.InsertLInt(indexA, nr, org, bres); Files.WriteLInt(R, -1);	(* offset of title line *) Files.WriteLInt(R, nr);	(* the article nr. for article msgid *) iRef := i; fixup := 0; WHILE line&#91;i] = "&#60;" DO 							j := 0; WHILE (line&#91;i] > " ") & (line&#91;i] # ">") DO 								msgid&#91;j] := line&#91;i]; INC(j); INC(i) END; IF line&#91;i] = ">" THEN msgid&#91;j] := line&#91;i]; INC(j); INC(i) END; msgid&#91;j] := 0X; Strings.Upper(msgid, msgid); BTrees.SearchStr(indexM, msgid, org2, bres);	(* lookup the msgid referenced *) IF bres = BTrees.Done THEN Files.WriteLInt(R, org2)	(* add it to the references list *) ELSE	(* referenced article no longer available *) Files.WriteLInt(R, -2); INC(fixup) END; WHILE (line&#91;i] # 0X) & (line&#91;i] # "&#60;") DO 								INC(i) END END; Files.WriteLInt(R, -1);	(* end of reference list *) org2 := Files.Pos(R); Files.Set(R, refs, org);	(* fixup for title line *) Files.WriteLInt(R, org2); Files.Set(R, refs, org2);	(* write the title line *) Files.WriteString(R, line); IF thread & (fixup > 0) THEN Files.Set(R, refs, org); Files.ReadLInt(R, org); Files.ReadLInt(R, org); i := iRef; WHILE line&#91;i] = "&#60;" DO 								j := 0; WHILE (line&#91;i] > " ") & (line&#91;i] # ">") DO 									msgid&#91;j] := line&#91;i]; INC(j); INC(i) END; IF line&#91;i] = ">" THEN msgid&#91;j] := line&#91;i]; INC(j); INC(i) END; msgid&#91;j] := 0X; Strings.Upper(msgid, msgid); Files.ReadLInt(R, org); IF org = -2 THEN org2 := Files.Length(refs); DEC(beg); BTrees.InsertStr(indexM, msgid, org2, bres); BTrees.InsertLInt(indexA, beg, org2, bres); org := Files.Pos(R)-4; Files.Set(R, refs, org2); Files.WriteLInt(R, -1);	(* offset of title line *) Files.WriteLInt(R, beg);	(* the article nr. for article msgid *) Files.WriteLInt(R, -1);	(* end of reference list *) org3 := Files.Pos(R); Strings.IntToStr(beg, dummy); Strings.AppendCh(dummy, Strings.Tab); Strings.Append(dummy, "Was: "); Strings.Append(dummy, msgid); Files.WriteString(R, dummy);	(* write the title line *) Files.Set(R, refs, org2); Files.WriteLInt(R, org3);	(* fixup for title line *) Files.Set(R, refs, org); Files.WriteLInt(R, org2); DEC(fixup) END; WHILE (line&#91;i] # 0X) & (line&#91;i] # "&#60;") DO 									INC(i) END END; org := Files.Length(refs); Files.Set(R, refs, org) END END; NetSystem.ReadString(S.C, line) END; S.res := Done ELSE S.res := ErrXOver END ELSE S.res := ErrGroup END; IF S.res = Done THEN IF thread THEN Thread(T) ELSE ListArts(T) END; Texts.Append(T, Wr.buf); WriteGrpHead(group); Texts.Insert(T, 0, Wr.buf) END; curGrp := NIL; refs := NIL; indexM := NIL; indexA := NIL END ArticleRange; (** List all available articles in group. *) PROCEDURE Articles*(S: Session; group: ARRAY OF CHAR; VAR T: Texts.Text; thread: BOOLEAN); BEGIN ArticleRange(S, group, T, thread, 0, 0) END Articles; PROCEDURE ReadString(VAR R: Texts.Reader; VAR s: ARRAY OF CHAR); VAR l, i: SIZE; ch: CHAR; BEGIN l := LEN(s)-1; i := 0; Texts.Read(R, ch); WHILE ~R.eot & (ch # Strings.CR) & (i &#60; l) DO 			IF R.lib IS Fonts.Font THEN s&#91;i] := ch; INC(i) END; Texts.Read(R, ch) END; s&#91;i] := 0X END ReadString; PROCEDURE ReadArticle(S: Session; VAR T: Texts.Text); VAR h: MIME.Header; cont: MIME.Content; out: Streams.Stream; val: ARRAY 256 OF CHAR; pos: SIGNED32; mT: Texts.Text; i: SIGNED16; BEGIN out := TextStreams.OpenWriter(T); MIME.ReadHeader(S.S, out, h, pos); out.Flush(out); Mail.ParseContent(h, cont); pos := MIME.FindField(h, "Xref"); IF pos > 0 THEN MIME.ExtractValue(h, pos, line); i := 0; pos := 0; WHILE line&#91;pos] # 0X DO 				IF line&#91;pos] &#60;= " " THEN i := 0; INC(pos) ELSIF line&#91;pos] = ":" THEN val&#91;i] := 0X; INC(pos); i := SHORT(pos); Strings.StrToIntPos(line, pos, i); AddArtNr(GetGroup(val, TRUE), pos); pos := i 				ELSE val&#91;i] := line&#91;pos]; INC(i); INC(pos) END END END; Texts.Append(T, W.buf); (*Texts.WriteLn(W);*) cont.len := MAX(SIGNED32); IF cont.typ.typ # "multipart" THEN MIME.ReadText(S.S, W, cont, TRUE) ELSE Texts.Append(T, W.buf); MIME.ReadMultipartText(S.S, mT, cont, TRUE); Texts.Save(mT, 0, mT.len, W.buf) END; Texts.Append(T, W.buf); IF (cont.typ.typ = "application") & (cont.encoding IN {MIME.EncAsciiCoder, MIME.EncAsciiCoderC, MIME.EncAsciiCoderCPlain}) THEN Mail.DecodeMessage(T, h, cont, -1); ELSIF NetTools.QueryString("NewsFont", val) & (Fonts.This(val) # NIL) THEN newsFont := Fonts.This(val); Texts.ChangeLooks(T, 0, T.len, {0}, newsFont, 0, 0) END END ReadArticle; (** Retrieve article with number artnr in group. *) PROCEDURE ArticleByNr*(S: Session; group: ARRAY OF CHAR; artnr: SIGNED32; VAR T: Texts.Text); VAR str: ARRAY 12 OF CHAR; BEGIN line := "GROUP "; Strings.Append(line, group); NetSystem.WriteString(S.C, line); ReadResponse(S); IF S.status = 211 THEN NEW(T); Texts.Open(T, ""); line := "STAT "; Strings.IntToStr(artnr, str); Strings.Append(line, str); NetSystem.WriteString(S.C, line); ReadResponse(S); IF S.status = 223 THEN line := "ARTICLE"; NetSystem.WriteString(S.C, line); ReadResponse(S); IF S.status = 220 THEN AddArtNr(GetGroup(group, TRUE), artnr); ReadArticle(S, T); S.res := Done ELSE S.res := ErrArticle END ELSE S.res := ErrStat END ELSE S.res := ErrGroup END END ArticleByNr; (** Retrieve the article with the message-id msgid. *) PROCEDURE ArticleByMsgId*(S: Session; msgid: ARRAY OF CHAR; VAR T: Texts.Text); BEGIN line := "ARTICLE &#60;"; Strings.Append(line, msgid); Strings.AppendCh(line, ">"); NetSystem.WriteString(S.C, line); ReadResponse(S); IF S.status = 220 THEN NEW(T); Texts.Open(T, ""); ReadArticle(S, T); S.res := Done ELSE S.res := ErrArticle END END ArticleByMsgId; PROCEDURE ReadGroupName(VAR name: ARRAY OF CHAR); VAR R: Texts.Reader; beg, end, time: SIGNED32; text: Texts.Text; ch: CHAR; BEGIN COPY("", name); Texts.OpenReader(R, Oberon.Par.text, Oberon.Par.pos); Texts.Read(R, ch); WHILE ~R.eot & (ch &#60;= " ") DO 			Texts.Read(R, ch) END; IF ~R.eot & (ch = "^") THEN time := -1; text := NIL; Oberon.GetSelection(text, beg, end, time); IF (text # NIL) & (time >= 0) THEN Texts.OpenReader(R, text, beg); Texts.Read(R, ch); WHILE ~R.eot & (ch &#60;= " ") DO 					Texts.Read(R, ch) END ELSE RETURN END END; IF ch = 22X THEN Texts.Read(R, ch) END; beg := 0; WHILE ~R.eot & (ch > " ") & (ch # 22X) DO 			name&#91;beg] := ch; INC(beg); IF ch = ":" THEN beg := 0 END; Texts.Read(R, ch) END; name&#91;beg] := 0X END ReadGroupName; (** News.SubGroup ^ 		Subscribe a group (selection). *) PROCEDURE SubGroup*; VAR name: ARRAY 128 OF CHAR; group: Group; BEGIN ReadGroupName(name); IF name # "" THEN group := GetGroup(name, TRUE); group.subscribed := TRUE; Texts.WriteString(W, name); Texts.WriteString(W, " subcribed"); Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf) END END SubGroup; (** News.UnsubGroup ^ 		Unsubscribe a group (selection). *) PROCEDURE UnsubGroup*; VAR name: ARRAY 128 OF CHAR; group: Group; BEGIN ReadGroupName(name); IF name # "" THEN Texts.WriteString(W, name); group := GetGroup(name, FALSE); IF group # NIL THEN group.subscribed := FALSE; Texts.WriteString(W, " unsubcribed") ELSE Texts.WriteString(W, " not found") END; Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf) END END UnsubGroup; PROCEDURE catchUp(S: Session; group: Group); VAR end: SIGNED32; art: ArtNrs; i: SIGNED16; BEGIN line := "GROUP "; Strings.Append(line, group.name); NetSystem.WriteString(S.C, line); ReadResponse(S); IF S.status = 211 THEN i := 0; Strings.StrToIntPos(S.reply, end, i); Strings.StrToIntPos(S.reply, end, i); Strings.StrToIntPos(S.reply, end, i); Strings.StrToIntPos(S.reply, end, i) 		ELSE Texts.WriteString(W, S.reply);Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf); art := group.readArtNrs; WHILE art # NIL DO 				IF art.end > end THEN end := art.end END; art := art.next END END; NEW(art); art.beg := 0; art.end := end; art.next := NIL; group.readArtNrs := art END catchUp; (** News.CatchUp ^ 		Mark all articles in a group (selection) as read. *) PROCEDURE CatchUp*; VAR name: ARRAY 128 OF CHAR; group: Group; S: Session; BEGIN ReadGroupName(name); IF name # "" THEN group := GetGroup(name, TRUE); IF Connect(S) THEN catchUp(S, group) END; IF (S # NIL) & (S.res # Done) THEN Texts.WriteString(W, S.reply); Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf) END; Close(S) END END CatchUp; (** News.CatchUpAll 		Mark all articles in all subscribed groups. *) PROCEDURE CatchUpAll*; VAR group: Group; S: Session; BEGIN IF Connect(S) THEN group := groups; WHILE group # NIL DO 				IF group.subscribed THEN catchUp(S, group) END; group := group.next END END; IF (S # NIL) & (S.res # Done) THEN Texts.WriteString(W, S.reply); Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf) END; Close(S) END CatchUpAll; PROCEDURE SplitNewsAdr(VAR url, host, groupart: ARRAY OF CHAR): SIGNED32; VAR key: SIGNED32; i, j, l: SIZE; iskey: BOOLEAN; PROCEDURE Blanks; BEGIN WHILE (url&#91;i] # 0X) & (url&#91;i] &#60;= " ") DO 				INC(i) END END Blanks; BEGIN HyperDocs.UnESC(url); i := 0; Blanks; (* skip news *) WHILE (url&#91;i] # 0X) & (url&#91;i] # ":") DO 			INC(i) END; (* skip : *) WHILE (url&#91;i] # 0X) & (url&#91;i] = ":") DO 			INC(i) END; Blanks; (* get groupart *) iskey := TRUE; l := LEN(groupart); j := 0; WHILE (url&#91;i] # 0X) & (url&#91;i] # "@") DO 			IF (url&#91;i] > " ") & ~Strings.IsDigit(url&#91;i]) THEN iskey := FALSE END; IF j &#60; l THEN groupart&#91;j] := url&#91;i]; INC(j) END; INC(i) END; groupart&#91;j] := 0X; DEC(j); WHILE (j >= 0) & (groupart&#91;j] &#60;= " ") DO 			groupart&#91;j] := 0X; DEC(j) END; IF (url&#91;i] = 0X) & iskey THEN IF groupart # "" THEN Strings.StrToInt(groupart, key); HyperDocs.RetrieveLink(key, line); key := SplitNewsAdr(line, host, groupart); RETURN key ELSE RETURN HyperDocs.UndefKey END ELSIF url&#91;i] = "@" THEN INC(i); l := LEN(host); j := 0; WHILE url&#91;i] # 0X DO 				IF j &#60; l THEN host&#91;j] := url&#91;i]; INC(j) END; INC(i) END; host&#91;j] := 0X; DEC(j); WHILE (j >= 0) & (host&#91;j] &#60;= " ") DO 				host&#91;j] := 0X; DEC(j) END ELSE COPY("", host) END; key := RegisterNewsAdr(host, groupart); RETURN key END SplitNewsAdr; PROCEDURE SplitNNTPAdr(VAR url, group: ARRAY OF CHAR; VAR artnr: SIGNED32): SIGNED32; VAR i, j, l: SIZE; key: SIGNED32; iskey: BOOLEAN; str: ARRAY 12 OF CHAR; PROCEDURE Blanks; BEGIN WHILE (url&#91;i] # 0X) & (url&#91;i] &#60;= " ") DO 				INC(i) END END Blanks; BEGIN HyperDocs.UnESC(url); i := 0; Blanks; (* skip nntp *) WHILE (url&#91;i] # 0X) & (url&#91;i] # ":") DO 			INC(i) END; (* skip : *) WHILE (url&#91;i] # 0X) & (url&#91;i] = ":") DO 			INC(i) END; Blanks; (* get group *) iskey := TRUE; l := LEN(group); j := 0; WHILE (url&#91;i] # 0X) & (url&#91;i] # "/") DO 			IF (url&#91;i] > " ") & ~Strings.IsDigit(url&#91;i]) THEN iskey := FALSE END; IF j &#60; l THEN group&#91;j] := url&#91;i]; INC(j) END; INC(i) END; group&#91;j] := 0X; DEC(j); WHILE (j >= 0) & (group&#91;j] &#60;= " ") DO 			group&#91;j] := 0X; DEC(j) END; IF (url&#91;i] = 0X) & iskey THEN IF group # "" THEN Strings.StrToInt(group, key); HyperDocs.RetrieveLink(key, line); key := SplitNNTPAdr(line, group, artnr); RETURN key ELSE RETURN -1 END ELSIF url&#91;i] = "/" THEN INC(i); l := 12; j := 0; WHILE url&#91;i] # 0X DO 				IF j &#60; l THEN str&#91;j] := url&#91;i]; INC(j) END; INC(i) END; str&#91;j] := 0X; Strings.StrToInt(str, artnr) ELSE artnr := 0 END; key := RegisterNNTPAdr(group, artnr); RETURN key END SplitNNTPAdr; PROCEDURE DocHandler(D: Objects.Object; VAR M: Objects.ObjMsg); BEGIN WITH D: Documents.Document DO 			TextDocs.DocHandler(D, M) 		END END DocHandler; (* Find the line containing pos. *) PROCEDURE FindBeg(T: Texts.Text; VAR pos: SIGNED32); VAR R: Texts.Reader; ch: CHAR; BEGIN Texts.OpenReader(R, T, pos); Texts.Read(R, ch); WHILE (pos > 0) & ((ch # Strings.CR) OR ~(R.lib IS Fonts.Font)) DO 			DEC(pos); Texts.OpenReader(R, T, pos); Texts.Read(R, ch) END; IF ch = Strings.CR THEN INC(pos) END END FindBeg; PROCEDURE LoadDoc(D: Documents.Document); VAR host: ARRAY NetTools.ServerStrLen OF CHAR; group, msgid: ARRAY NetTools.PathStrLen OF CHAR; T: Texts.Text; pos, artnr, artnr1: SIGNED32; date, time: SIGNED32; S: Session; obj, t: Objects.Object; F: Texts.Finder; article: BOOLEAN; sPos: SIGNED16; BEGIN S := NIL; article := FALSE; D.dsc := NIL; IF (D.name = "") OR (D.name = "subgroups") THEN TextDocs.InitDoc(D); NEW(T); Texts.Open(T, ""); SubGroups(T); IF T.len = 0 THEN Texts.WriteString(W, "No subscribed groups"); Texts.WriteLn(W); Texts.Append(T, W.buf) END; COPY("Subscribed Groups", D.name) ELSIF D.name = "newgroups" THEN IF Connect(S) THEN date := newgDate; time := newgTime; TextDocs.InitDoc(D); NEW(T); Texts.Open(T, ""); NewGrp(S, T); IF T # NIL THEN IF T.len = 0 THEN Texts.WriteString(W, "No new groups since ") ELSE Texts.WriteString(W, "New groups since ") END; Texts.WriteDate(W, time, date); Texts.WriteLn(W); Texts.WriteLn(W); Texts.Insert(T, 0, W.buf) END; COPY("New Groups", D.name); Close(S) END ELSIF D.name = "news:" THEN IF Connect(S) THEN TextDocs.InitDoc(D); COPY("All Groups", D.name); AllGroups(S, T); Close(S) END ELSIF Strings.CAPPrefix("news:", D.name) THEN IF SplitNewsAdr(D.name, host, group) # HyperDocs.UndefKey THEN IF Connect(S) THEN TextDocs.InitDoc(D); curGrpNewOnly := FALSE; IF group = "" THEN COPY("All Groups", D.name); AllGroups(S, T) 					ELSIF host = "" THEN COPY(group, D.name); Articles(S, group, T, NetTools.QueryBool("NewsThreading")) ELSE (* news:&#60;group>@&#60;number&#91;-&#60;number>]&#91;\n] *) sPos := 0; Strings.StrToIntPos(host, artnr, sPos); IF artnr # 0 THEN (* a number was found *) IF host&#91;sPos] = "-" THEN (* a range *) INC(sPos); Strings.StrToIntPos(host, artnr1, sPos) (* get articles #artnr to #artnr1 *) ELSE artnr1 := 0 (* get the artnr newest articles *) END; curGrpNewOnly := (host&#91;sPos] = "\" ) & (host&#91;sPos+1] = "n" );  (* look for option \n *) ArticleRange(S, group, T, NetTools.QueryBool("NewsThreading"), artnr, artnr1) ELSE (* a real host *) COPY(group, msgid); Strings.AppendCh(msgid, "@"); Strings.Append(msgid, host); COPY(msgid, D.name); ArticleByMsgId(S, msgid, T); article := TRUE END END; Close(S) END END ELSIF Strings.CAPPrefix("nntp:", D.name) THEN IF SplitNNTPAdr(D.name, group, artnr) # HyperDocs.UndefKey THEN IF Connect(S) THEN TextDocs.InitDoc(D); ArticleByNr(S, group, artnr, T); COPY(group, D.name); Strings.AppendCh(D.name, "."); Strings.IntToStr(artnr, msgid); Strings.Append(D.name, msgid); article := TRUE; IF (S.res = Done) & (Gadgets.executorObj # NIL) & (Gadgets.executorObj IS TextGadgets.Control) THEN Links.GetLink(Gadgets.context, "Model", t); IF (t # NIL) & (t IS Texts.Text) THEN Texts.OpenFinder(F, t(Texts.Text), 0); pos := F.pos; Texts.FindObj(F, obj); WHILE ~F.eot DO 								IF obj = Gadgets.executorObj THEN artnr := pos; FindBeg(t(Texts.Text), artnr); Texts.ChangeLooks(t(Texts.Text), artnr, pos-1, {1}, NIL, SHORT(HyperDocs.linkC), 0) END; pos := F.pos; Texts.FindObj(F, obj) END END END; Close(S) END END ELSE HALT(99) END; IF (S # NIL) & (S.res # Done) THEN D.dsc := NIL; Texts.WriteString(W, S.reply); Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf) ELSE Links.SetLink(D.dsc, "Model", T); D.W := HyperDocs.docW; D.H := HyperDocs.docH; IF ~article THEN D.handle := DocHandler END END; IF HyperDocs.context # NIL THEN HyperDocs.context.replace := FALSE; HyperDocs.context.history := FALSE END END LoadDoc; PROCEDURE NewDoc*; VAR D: Documents.Document; BEGIN NEW(D); D.Load := LoadDoc; D.Store := NIL; D.handle := NIL; Objects.NewObj := D 	END NewDoc; (** News.ShowAllGroups 		Show all newsgroups. *) PROCEDURE ShowAllGroups*; VAR doc: Documents.Document; BEGIN NewDoc; doc := Objects.NewObj(Documents.Document); doc.name := "news:"; doc.Load(doc); IF (doc # NIL) & (doc.dsc # NIL) THEN Desktops.ShowDoc(doc) END END ShowAllGroups; (** News.ShowNewGroups 		Show new groups since last access. *) PROCEDURE ShowNewGroups*; VAR doc: Documents.Document; BEGIN NewDoc; doc := Objects.NewObj(Documents.Document); doc.name := "newgroups"; doc.Load(doc); IF (doc # NIL) & (doc.dsc # NIL) THEN Desktops.ShowDoc(doc) END END ShowNewGroups; (** News.SubscribedGroups 		List subscribed groups. *) PROCEDURE SubscribedGroups*; VAR doc: Documents.Document; BEGIN NewDoc; doc := Objects.NewObj(Documents.Document); doc.name := "subgroups"; doc.Load(doc); IF (doc # NIL) & (doc.dsc # NIL) THEN Desktops.ShowDoc(doc) END END SubscribedGroups; PROCEDURE NewsSchemeHandler(L: Objects.Object; VAR M: Objects.ObjMsg); VAR host: ARRAY NetTools.ServerStrLen OF CHAR; group: ARRAY NetTools.PathStrLen OF CHAR; BEGIN WITH L: HyperDocs.LinkScheme DO 			IF M IS HyperDocs.RegisterLinkMsg THEN WITH M: HyperDocs.RegisterLinkMsg DO 					M.key := SplitNewsAdr(M.link, host, group); IF M.key # HyperDocs.UndefKey THEN M.res := 0 END END ELSIF M IS Objects.AttrMsg THEN WITH M: Objects.AttrMsg DO 					IF (M.id = Objects.get) & (M.name = "Gen") THEN M.class := Objects.String; M.s := "News.NewNewsLinkScheme"; M.res := 0 ELSE HyperDocs.LinkSchemeHandler(L, M) 					END END ELSE HyperDocs.LinkSchemeHandler(L, M) 			END END END NewsSchemeHandler; PROCEDURE NewNewsLinkScheme*; VAR L: HyperDocs.LinkScheme; BEGIN NEW(L); L.handle := NewsSchemeHandler; L.usePath := FALSE; Objects.NewObj := L 	END NewNewsLinkScheme; PROCEDURE NNTPSchemeHandler(L: Objects.Object; VAR M: Objects.ObjMsg); VAR group: ARRAY NetTools.PathStrLen OF CHAR; artnr: SIGNED32; BEGIN WITH L: HyperDocs.LinkScheme DO 			IF M IS HyperDocs.RegisterLinkMsg THEN WITH M: HyperDocs.RegisterLinkMsg DO 					M.key := SplitNNTPAdr(M.link, group, artnr); IF M.key # HyperDocs.UndefKey THEN M.res := 0 END END ELSIF M IS Objects.AttrMsg THEN WITH M: Objects.AttrMsg DO 					IF (M.id = Objects.get) & (M.name = "Gen") THEN M.class := Objects.String; M.s := "News.NewNNTPLinkScheme"; M.res := 0 ELSE HyperDocs.LinkSchemeHandler(L, M) 					END END ELSE HyperDocs.LinkSchemeHandler(L, M) 			END END END NNTPSchemeHandler; PROCEDURE NewNNTPLinkScheme*; VAR L: HyperDocs.LinkScheme; BEGIN NEW(L); L.handle := NNTPSchemeHandler; L.usePath := FALSE; Objects.NewObj := L 	END NewNNTPLinkScheme; PROCEDURE SendArticle*(S: Session; T: Texts.Text; cont: MIME.Content); VAR s: Streams.Stream; h: MIME.Header; head: Texts.Text; R: Texts.Reader; end: SIGNED32; ch: CHAR; BEGIN NetSystem.WriteString(S.C, "POST"); ReadResponse(S); IF S.status = 340 THEN s := TextStreams.OpenReader(T, 0); MIME.ReadHeader(s, NIL, h, end); NEW(head); Texts.Open(head, ""); Texts.OpenReader(R, T, end); Texts.Read(R, ch); IF (ch = Strings.CR) OR (ch = Strings.LF) THEN WHILE (end > 0) & ((ch = Strings.CR) OR (ch = Strings.LF)) DO 					DEC(end); Texts.OpenReader(R, T, end); Texts.Read(R, ch) END; INC(end); IF end > T.len THEN end := T.len END END; Texts.Save(T, 0, end, W.buf); Texts.Append(head, W.buf); Texts.OpenReader(R, T, end); Texts.Read(R, ch); WHILE (ch = Strings.CR) OR (ch = Strings.LF) DO 				Texts.Read(R, ch); INC(end) END; Mail.GetSetting("EMail", S.from, FALSE); Mail.SendText(S, head, T, end-1, T.len, cont); ReadResponse(S); IF S.status = 240 THEN S.res := Done; Mail.SendReplyLine(S, cont) ELSE S.res := ErrPost END ELSE S.res := ErrPost END END SendArticle; (** News.Send &#91;mime] * 		Send article (the marked text), mime: 			ascii : text/plain, us-ascii 			iso : text/plain, iso 8bit 			oberon : text/plain with application/compressed/oberondoc attachment 			&#60;no mime> : 				- Simple Text without different colors or fonts 					no Umlaut -> ascii 					Umlaut -> iso 				- Text without objects, but with different colors or fonts -> oberon 				- Text with objects -> ooberon *) PROCEDURE Send*; VAR T, sig: Texts.Text; cont: MIME.Content; Sc: Attributes.Scanner; S: Session; val: ARRAY 64 OF CHAR; BEGIN T := Oberon.MarkedText; IF T # NIL THEN NEW(cont); cont.typ := MIME.GetContentType("text/plain"); Attributes.OpenScanner(Sc, Oberon.Par.text, Oberon.Par.pos); Sc.s := ""; Attributes.Scan(Sc); IF CAP(Sc.s&#91;0]) = "O" THEN cont.typ := MIME.GetContentType(MIME.OberonMime); cont.encoding := MIME.EncAsciiCoderC ELSIF CAP(Sc.s&#91;0]) = "A" THEN cont.encoding := MIME.EncBin ELSIF CAP(Sc.s&#91;0]) = "I" THEN cont.encoding := MIME.Enc8Bit ELSE Mail.QueryContType(T, 0, cont) END; Mail.GetSetting("NewsSignature", val, FALSE); IF val # "" THEN NEW(sig); Texts.Open(sig, val); IF sig.len > 0 THEN Texts.Save(T, 0, T.len, W.buf); NEW(T); Texts.Open(T, ""); Texts.WriteLn(W); Texts.Append(T, W.buf); Texts.Save(sig, 0, sig.len, W.buf); Texts.Append(T, W.buf) END END; Texts.WriteString(W, "sending "); Texts.Append(Oberon.Log, W.buf); IF Connect(S) THEN SendArticle(S, T, cont) END; Texts.WriteString(W, S.reply); Close(S); Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf) END END Send; (** News.Reply (selection) 	Compose a minimal followup article for the selected article. *) PROCEDURE Reply*; VAR T, text: Texts.Text; S: Attributes.Scanner; time, beg, end: SIGNED32; par, msgid, from: ARRAY 256 OF CHAR; R: Texts.Reader; lib: Objects.Library; grp, sub, frm: BOOLEAN; BEGIN lib := W.lib; Texts.SetFont(W, newsFont); grp := FALSE; sub := FALSE; frm := FALSE; Mail.GetSetting("EMail", EMail, FALSE); Attributes.OpenScanner(S, Oberon.Par.text, Oberon.Par.pos); IF (S.class = Texts.Char) & (S.c = "^") THEN text := NIL; time := -1; Oberon.GetSelection(text, beg, end, time); IF time &#60; 0 THEN text := NIL END ELSE text := Oberon.MarkedText; beg := 0 END; from := "nobody"; msgid := "&#60;>"; IF text # NIL THEN Texts.OpenReader(R, text, beg); ReadString(R, line); WHILE ~R.eot & (line # "") DO 				IF Strings.CAPPrefix("Message-ID:", line) THEN Strings.GetPar(line, msgid); Texts.WriteString(W, "References: "); Texts.WriteString(W, msgid); Texts.WriteLn(W) ELSIF Strings.CAPPrefix("Subject:", line) THEN sub := TRUE; Strings.GetPar(line, par); Texts.WriteString(W, "Subject: "); Mail.Re(W, par); Texts.WriteLn(W) ELSIF Strings.CAPPrefix("Newsgroups:", line) THEN grp := TRUE; frm := TRUE; Texts.WriteString(W, line); Texts.WriteLn(W); Strings.GetPar(line, par); Texts.WriteString(W, "Followup-To: "); Texts.WriteString(W, par); Texts.WriteLn(W) ELSIF Strings.CAPPrefix("From:", line) THEN Strings.GetPar(line, from) END; ReadString(R, line) END END; NEW(T); Texts.Open(T, ""); Texts.Append(T, W.buf); IF ~grp THEN Texts.WriteString(W, "Newsgroups: "); Texts.WriteLn(W); Texts.Insert(T, 0, W.buf) END; IF ~frm THEN Texts.WriteString(W, "From: "); Texts.WriteString(W, EMail); Texts.WriteLn(W) END; IF ~sub THEN Texts.WriteString(W, "Subject: "); Texts.WriteLn(W) END; Texts.WriteLn(W); Texts.WriteString(W, "In article "); Texts.WriteString(W, msgid); Texts.WriteString(W, ", "); Texts.WriteString(W, from); Texts.WriteString(W, " wrote: "); Texts.WriteLn(W); IF text # NIL THEN Texts.WriteLn(W); Mail.CiteText(W, text, Texts.Pos(R), text.len) END; Texts.WriteLn(W); Texts.Append(T, W.buf); Texts.SetFont(W, lib); TextDocs.ShowText("Article.Text", T, HyperDocs.docW, HyperDocs.docH) END Reply; BEGIN trace := NetTools.QueryBool("TraceNews"); Texts.OpenWriter(W); Texts.OpenWriter(Wr); LoadInitText; Modules.InstallTermHandler(storeInitText) END News. News.Read.Text News.StoreInitText System.Free News ~