@Courier10b.Scn.Fnt ZParcElemsAlloc   +6Syntax10b.Scn.FntSyntax10.Scn.FntipVersionElemsAllocBeg#Syntax10.Scn.FntWindows31 Win95andMacWin95andMacWindows31$Syntax10b.Scn.Fnt(* Windows 3.1 *)Win95andMac pVersionElemsAllocEndCourier10i.Scn.Fnt`=StampElemsAlloc12 Oct 98FInfoElemsAlloc#Syntax10.Scn.FntAuthor: Martin Erdpresser Matr.Nr: 93 55 000 / 871 email: k3075e2@c210.edvz-uni-linz.ac.at www: http://linz.info.at/students/martin.erdpresserCourier10.Scn.FntCZ8FoldElemsNewCourier10.Scn.FntCourier10i.Scn.FntSSyntax10.Scn.Fnt "& " "   (*-------------------< User defined constants >--------------------------------*) profSection = "CACHE"; (* name of profile section *) minCacheSize = 1024; (* lower and *) maxCacheSize = MAX(LONGINT) DIV 2048; (* upper cache size bounds *) modes = "never once always"; (* cache request modes *) prefix = "t"; (* denotes cache data files *) fileExtData = ".dat"; (* ext. for data-files *) none = -1; (* non-valid index pos. *)88Courier10b.Scn.FntCourier10i.Scn.FntRSyntax10i.Scn.FntSyntax10.Scn.FntYCourier10.Scn.Fnt!## ## !# pVersionElemsAllocBeg#Syntax10.Scn.FntWindows31 Win95andMacWin95andMacWindows31DSyntax10.Scn.FntCourier10i.Scn.Fnt"8vList = POINTER TO ListDesc; (* single linked list *) ListDesc = RECORD name : FileName; next : List END;Win95andMac pVersionElemsAllocEnd (*-------------------< User defined types >------------------------------------*) UrlString = ARRAY 256 OF CHAR; FileName = ARRAY 64 OF CHAR; Tag = ARRAY 32 OF CHAR; Entry = RECORD (* entry in cache index *) url : UrlString; (* url of data *) checked : BOOLEAN; (* true if url is checked *) date, time, (* in Oberon-format *) fLen : LONGINT; (* length of cache-datafile *) fname : FileName; (* filename *) id : Web.ContentId; (* content type *) cod : Web.CodingId; (* content encoding *) accessTag : Tag; (* timestamp of last access *) END;  CONST recLen = SIZE(Entry);88!Courier10.Scn.FntCourier10i.Scn.FntSSyntax10.Scn.Fnt      !!!!Syntax10i.Scn.Fnt   "4pVersionElemsAllocBeg#Syntax10.Scn.FntWindows31 Win95andMacWin95andMacWindows31=Syntax10.Scn.Fnt Courier10i.Scn.Fnt -head : List; (* holds filenames to delete *)Win95andMac pVersionElemsAllocEnd (*-------------------< Global variables >--------------------------------------*) size, (* max. cache size in bytes *) mode, (* never | once | always *) dir : Web.ProfileEntry; (* cache data directory *) indexFile, (* main index for cache *) keyFile : Files.File; (* aux. index for url-keys *) indexName , keyName : FileName; (* index filenames *) usedDiskSize, (* current cache size *) lastPos, (* last checked index pos. *) oldTime : LONGINT; (* comparison timestamp *) 8U MarkElemsAlloc GV8Syntax10.Scn.Fnt8FoldElemsNew>Courier10.Scn.FntCourier10i.Scn.Fnt (*-- Reads urlstring from file, associated with rider "r". --------------------*) (* "start" is set to current rider position before read operation. *) (*-----------------------------------------------------------------------------*)8Courier10i.Scn.FntSyntax10b.Scn.Fnt. H  BEGIN start := Files.Pos(r); Files.ReadString(r, s) END ReadString;8 R258;Courier10.Scn.FntSyntax10.Scn.Fntq8FoldElemsNew>Courier10.Scn.FntCourier10i.Scn.FntQR (*-- Reads whole index entry from file into "e", starting at curr. rider-pos. -*)8Courier10i.Scn.FntSyntax10b.Scn.Fnt 7  BEGIN Files.ReadBytes(r, e, recLen) END ReadEntry;8 3P58;Courier10.Scn.FntSyntax10.Scn.Fntq8FoldElemsNew>Courier10.Scn.FntCourier10i.Scn.FntQR (*-- Writes entry "e" to file at current rider-pos. ---------------------------*)8Courier10i.Scn.FntSyntax10b.Scn.Fnt 9  BEGIN Files.WriteBytes(r, e, recLen) END WriteEntry;8 N\)_8Courier10.Scn.Fntq8FoldElemsNew>Courier10.Scn.FntCourier10i.Scn.FntQR (*-- Converts urlstring to numeric key ----------------------------------------*)Syntax10.Scn.Fnt8Courier10i.Scn.FntSyntax10b.Scn.Fnt   (  CONST P = 32707; M = 2; VAR i : INTEGER; h : LONGINT; BEGIN IF urlStr = "" THEN RETURN none END; i := 0; h := 0; WHILE urlStr[i] # 0X DO h := ASH(h, M) + ORD(urlStr[i]); INC(i) END; RETURN SHORT(h MOD P) END GetKey;8 bU8Courier10.Scn.FntSyntax10.Scn.Fnt)8FoldElemsNew>Courier10.Scn.FntCourier10i.Scn.Fnt (*-- Prepare index and key file (=main and aux. index): -----------------------*) (* All checkmarks are reset to FALSE, key file is build up. *) (* If switch "keepUrl" is FALSE, both indices are marked as deleted. *) (* (Only used for Win3.x version in procedure "CleanUp") *) (*-----------------------------------------------------------------------------*)8Courier10i.Scn.FntSyntax10b.Scn.Fnt0w  - X1   VAR reader, writer, keys : Files.Rider; e : Entry; BEGIN Files.Set(reader, indexFile, 0); Files.Set(writer, indexFile, 0); Files.Set(keys, keyFile, 0); ReadEntry(reader, e); WHILE ~reader.eof DO IF keepUrl THEN INC(usedDiskSize,e.fLen); e.checked := FALSE ELSE e.url := 0X END; WriteEntry(writer, e); Files.WriteInt(keys, GetKey(e.url)); ReadEntry(reader, e) END; Files.Close(indexFile); Files.Close(keyFile); END InitIndex;8 U0L86 Courier10.Scn.FntSyntax10.Scn.Fnt8FoldElemsNew>Courier10.Scn.FntCourier10i.Scn.Fnt (*-- Common initialisation ----------------------------------------------------*) (* Reads profile, prepares filenames and init main and aux. index, *) (* Set comparison timestamp for procedure "AddStamp", *) (* Init global variables "usedDiskSize" and "lastPos". *) (* When no profile section or illegal values are detected, following *) (* default values are assumed: *) (* size: 1024 kB *) (* mode: "once" *) (* dir: directory "Data" relative to Oberon-directory, or, if not *) (* accessible (e.g. readonly network drive), relative to current dir.*) (* If dir. does't exist, if will be created; if all failes, program halts *) (*-----------------------------------------------------------------------------*)8Courier10i.Scn.FntSyntax10b.Scn.Fnt, MarkElemsAlloc$ 8Syntax10.Scn.Fnt8FoldElemsNew>Courier10.Scn.FntCourier10i.Scn.Fnt (*-- Local procedure to open/create main- and aux. index using --------------*) (* global handles "indexFile" and "keyFile". *) (*---------------------------------------------------------------------------*)8Courier10.Scn.FntSyntax10b.Scn.Fnt%b%X ]  BEGIN indexFile := Files.Old(indexName); IF indexFile = NIL THEN indexFile := Files.New(indexName); Files.Register(indexFile); indexFile := Files.Old(indexName) END; keyFile := Files.Old(keyName); IF keyFile = NIL THEN keyFile := Files.New(keyName); Files.Register(keyFile); keyFile := Files.Old(keyName) END END CreateIndex;8+   >$$ .  p3, Syntax10i.Scn.Fnt  M/  4   2  Courier10b.Scn.Fnt! # ?  VAR d1, d2 : Dir.Directory; dummy : LONGINT; PROCEDURE CreateIndex;  BEGIN Web.GetProfile(profSection, "Size", size); (* read cache size *) IF size = NIL THEN NEW(size) END; IF (size.class # Texts.Int) THEN size.class := Texts.Int; size.int := minCacheSize * 1024; ELSIF size.int < minCacheSize THEN size.int := minCacheSize * 1024 ELSIF size.int > maxCacheSize THEN size.int := maxCacheSize * 1024 ELSE size.int := size.int * 1024 END; Web.GetProfile(profSection, "Mode", mode); (* read cache mode *) IF mode = NIL THEN NEW(mode) END; IF (mode.class # Texts.String) OR ((mode.class = Texts.String) & ((Strings.Pos(mode.str, modes, 0) MOD 6) # 0)) THEN mode.class := Texts.String; mode.str := "once" END; Web.GetProfile(profSection, "Dir", dir); (* read cache data directory *) IF dir = NIL THEN NEW(dir) END; IF (dir.class # Texts.String) OR ((dir.class = Texts.String) & (dir.str = "")) THEN dir.class := Texts.String; dir.str := "$Data" END; d1 := Dir.This(dir.str); (* verify directory *) IF d1 = NIL THEN Dir.Create(dir.str); d1 := Dir.This(dir.str); IF d1 = NIL THEN d2 := Dir.Current(); COPY(d2.path, dir.str); Strings.Append(Dir.delimiter, dir.str); Strings.Append("Data", dir.str); d2 := Dir.This(dir.str); IF d2 = NIL THEN Dir.Create(dir.str); d2 := Dir.This(dir.str); IF d2 = NIL THEN HALT(99) END (* severe disk access error *) END END END; Oberon.GetClock(oldTime, dummy); (* set comparison time *) usedDiskSize := 0; lastPos := none; indexName := "tCache.idx"; (* prepare main & aux. index *) Strings.Insert(Dir.delimiter, 0, indexName); Strings.Insert(dir.str, 0, indexName); keyName := "tCache.key"; Strings.Insert(Dir.delimiter, 0, keyName); Strings.Insert(dir.str, 0, keyName); CreateIndex; InitIndex(TRUE) END InitAll;8  08Courier10.Scn.FntSyntax10.Scn.Fnt8FoldElemsNew>Courier10.Scn.FntCourier10i.Scn.Fnt (*-- Creates timestamp and appends it to "name". ------------------------------*) (* To ensure unique timestamps, global var. "oldtime" is used for comparison *) (*-----------------------------------------------------------------------------*)8Syntax10b.Scn.FntCourier10i.Scn.Fnt# MarkElemsAllocQ=&8Syntax10.Scn.Fntd8FoldElemsNewLCourier10.Scn.FntCourier10i.Scn.Fnt5Q (*-- Converts integer to hex-str. and appends it to "dest" ------------------*)8Courier10.Scn.FntSyntax10b.Scn.Fnt?*  ") (   VAR i : INTEGER; d : ARRAY 2 OF INTEGER; h : ARRAY 3 OF CHAR; BEGIN d[0] := int DIV 16; d[1] := int MOD 16; FOR i := 0 TO 1 DO IF d[i] <= 9 THEN h[i] := CHR(ORD("0") + d[i]) ELSE h[i] := CHR(ORD("A") + d[i] - 10) END END; h[2] := 0X; Strings.Append(h, dest) END Int2HexStr;8"-#Syntax10i.Scn.Fnt*##".","$"  VAR d, t : LONGINT; (* date & time in Oberon-format *) PROCEDURE Int2HexStr(int : INTEGER; VAR(*inout*) dest : ARRAY OF CHAR);  BEGIN Oberon.GetClock(t, d); IF t <= oldTime THEN t := oldTime + 1 END; (* no timechange, do correction *) Int2HexStr(SHORT(d DIV 512 MOD 128), name); (* year, *) Int2HexStr(SHORT(d DIV 32 MOD 16), name); (* month, *) Int2HexStr(SHORT(d MOD 32), name); (* day, *) Int2HexStr(SHORT(t DIV 4096 MOD 32), name) ; (* hours, *) Int2HexStr(SHORT(t DIV 64 MOD 64), name) ; (* min, *) Int2HexStr(SHORT(t MOD 64), name); (* sec. in hex. format *) oldTime := t END AddTimeStamp;8 0RkR8Courier10.Scn.FntSyntax10.Scn.FntK8FoldElemsNewaCourier10.Scn.FntCourier10i.Scn.FntR)pVersionElemsAllocBeg#Syntax10.Scn.FntWindows31 Win95andMacWin95andMacWindows31%Courier10i.Scn.FntPP(*-- Stores all filenames for later deletion ---------------------------------*)Win95andMac pVersionElemsAllocEndU (*-- Enumeration procedure to delete whole cache data-files -------------------*) 8Courier10i.Scn.FntSyntax10b.Scn.Fnt7pVersionElemsAllocBeg#Syntax10.Scn.FntWin95andMac Windows31Win95andMacWin95andMac Windows31 pVersionElemsAllocEnd4p#Syntax10.Scn.FntWindows31 Win95andMacWin95andMacWindows31=Syntax10.Scn.Fnt Courier10i.Scn.Fnt!-l : List; (* head of list defined global *)Win95andMac p  r!Syntax10i.Scn.Fnt>p#Syntax10.Scn.FntWin95andMac Windows31Win95andMacWin95andMac Windows31 :pp#Syntax10.Scn.FntWindows31 Win95andMacWin95andMacWindows31Syntax10b.Scn.FntSyntax10.Scn.FntCourier10i.Scn.Fnt"Syntax10i.Scn.FntAIF (ext = fileExtData) THEN (* store filename to delete *) NEW(l); l.name := fn; l.next := head; head := l; Out.Char(".") END;Win95andMac pCourier10b.Scn.Fnt   VAR fn : FileName; ext : ARRAY 5 OF CHAR; pt : INTEGER; res : INTEGER;  BEGIN IF ~isDir & (name[0] = prefix) THEN COPY(d.path, fn); Strings.Append(Dir.delimiter, fn); Strings.Append(name, fn); pt := Strings.Pos(".", name, 0); (* get file-extention *) Strings.Extract(name, pt, Strings.Length(name) - pt , ext); IF ext = fileExtData THEN Files.Delete(fn, res); continue := res = 0; Out.Char(".") END  END END EnumDelete;8 P8:8;Courier10.Scn.FntSyntax10.Scn.Fnt8FoldElemsNew>Courier10.Scn.FntCourier10i.Scn.Fnt (*-- Modified url to string conversion: ---------------------------------------*) (* Web.UrlDesc.net and Web.NetLoc.port must be full exported in Web.Mod *) (* Modifications to compare strings directly: *) (* url.frag skipped, *) (* port := 80 when set to Web.anyPort *) (*-----------------------------------------------------------------------------*)8Syntax10b.Scn.FntSyntax10i.Scn.FntCourier10i.Scn.Fnt   VAR temp : Web.Url; BEGIN NEW(temp); temp^ := url^; (* let original url unchanged *) temp.frag := ""; IF temp.net.port = Web.anyPort THEN temp.net.port := 80 END; Web.Url2Str(temp, urlStr) END Url2Str;8 S 9Y8Courier10.Scn.Fnt8FoldElemsNew>Courier10.Scn.FntCourier10i.Scn.Fnt=> (*-- Returns main index position for specified url using keys from aux.-index -*) (* Search starts at specified "offset" from main index. *) (* IF found: fitting main index pos. is returned *) (* IF not found: "none" is returned *) (* If an entry in key-index is marked as deleted, then "emptyPos" is set *) (* according to this vaccant position, otherwise to "none". *) (*-----------------------------------------------------------------------------*)Syntax10.Scn.Fnt8Courier10i.Scn.FntSyntax10b.Scn.FntBx!!" /7 @  VAR keys : Files.Rider; thisKey, currKey : INTEGER; tmp : LONGINT; BEGIN tmp := none; emptyPos := none; Files.Set(keys, keyFile, offset DIV recLen * SIZE(INTEGER)); thisKey := GetKey(urlStr); Files.ReadInt(keys, currKey); WHILE ~keys.eof & (currKey # thisKey) DO IF (tmp = none) & (currKey = none) THEN tmp := Files.Pos(keys) END; Files.ReadInt(keys, currKey) END; IF tmp # none THEN emptyPos := (tmp DIV SIZE(INTEGER)-1) * recLen END; IF currKey = thisKey THEN RETURN (Files.Pos(keys) DIV SIZE(INTEGER)-1) * recLen ELSE RETURN none END END GetIndexPos;8 ]"@86Courier10.Scn.Fntq8FoldElemsNew>Courier10.Scn.FntCourier10i.Scn.FntQR (*-- Expand "base" to "full" filename including prefix, dir. and file-ext. ----*)Syntax10.Scn.Fnt8Syntax10b.Scn.Fnt  BEGIN COPY(dir.str, full); Strings.Append(Dir.delimiter, full); Strings.Append(prefix, full); Strings.Append(base, full); Strings.Append(fileExtData, full) END Expand;8 28aCourier10.Scn.FntSyntax10.Scn.Fnt)8FoldElemsNew>Courier10.Scn.FntCourier10i.Scn.Fnt (*-- Writes url to cache index, must be called before cache data is written ---*) (* Entry "e" is initialized with all important data to remain url in index. *) (* If url already exists, a new timestamp is applied and the outdated *) (* datafile is deleted. *) (*-----------------------------------------------------------------------------*)8Syntax10b.Scn.FntbCourier10i.Scn.Fnt!Syntax10i.Scn.Fnt ! ! !!!f Courier10b.Scn.Fnt5n8!=IE  + :H   VAR index, keys : Files.Rider; curr : Entry; currUrl : UrlString; oldData : FileName; res : INTEGER; (* result of delete-op. *) offset, (* search offset for keys *) currPos, (* curr. begin of filerec. *) accessPos, (* read access position *) emptyPos : LONGINT; (* pos. of vacant entry *) BEGIN Url2Str(url, e.url); (* init index entry *) AddTimeStamp(e.fname); AddTimeStamp(e.accessTag); e.checked := TRUE; emptyPos := none; offset := 0; REPEAT (* search for url *) accessPos := GetIndexPos(offset, e.url, emptyPos); IF accessPos # none THEN Files.Set(index, indexFile, accessPos); ReadString(index, currUrl, currPos); offset := currPos + recLen; END UNTIL index.eof OR (accessPos = none) OR (currUrl = e.url); IF currUrl = e.url THEN (* url found, delete old data *) Files.Set(index, indexFile, currPos); ReadEntry(index, curr); Expand(curr.fname, oldData); Files.Delete(oldData, res); ASSERT(res = 0); DEC(usedDiskSize, curr.fLen); Files.Set(index, indexFile, currPos); ELSE (* new url, update indices *) IF emptyPos # none THEN (* use deleted entry *) Files.Set(index, indexFile, emptyPos) ELSE (* append to files *) Files.Set(index, indexFile, Files.Length(indexFile)) END; Files.Set(keys, keyFile, Files.Pos(index) DIV recLen * SIZE(INTEGER)); Files.WriteInt(keys, GetKey(e.url)); Files.Close(keyFile); END; INC(usedDiskSize, e.fLen); WriteEntry(index, e); Files.Close(indexFile); END WriteIndex;8 )n78Courier10.Scn.FntSyntax10.Scn.Fntq8FoldElemsNew>Courier10.Scn.FntCourier10i.Scn.FntQR (*-- Writes data assigned to entry "e" to disk using specified rider ----------*)8Courier10i.Scn.FntSyntax10b.Scn.Fntw '` G  VAR fDest, fSrc : Files.File; rDest : Files.Rider; name : FileName; curr, remains : LONGINT; buf : ARRAY 8196 OF CHAR; BEGIN Expand(e.fname, name); fDest := Files.New(name); fSrc := Files.Base(rSrc); ASSERT((fDest # NIL) & (fSrc # NIL)); Files.Set(rSrc, fSrc, 0); Files.Set(rDest, fDest, 0); remains := Files.Length(fSrc); e.fLen := remains; WHILE remains > 0 DO IF remains > LEN(buf) THEN curr := LEN(buf) ELSE curr := remains END; Files.ReadBytes(rSrc, buf, curr); Files.WriteBytes(rDest, buf, curr); DEC(remains, curr) END; Files.Register(fDest); END WriteData;8   JQ"?8Courier10.Scn.FntSyntax10.Scn.Fntq8FoldElemsNew>Courier10.Scn.FntCourier10i.Scn.FntQR (*-- Checks, if request will fit in cache, otherwise delete least recent used -*)8Syntax10b.Scn.FntTCourier10i.Scn.Fnt" ! " Courier10b.Scn.Fnt8Syntax10i.Scn.Fnt. >  /R50 x     VAR oldData : FileName; index, keys : Files.Rider; curr, old : Entry; res : INTEGER; target : LONGINT; (* deletion target pos. *) now : Tag; (* comparison timestamp *) BEGIN IF (usedDiskSize+request) > size.int THEN (* deletion needed *) WHILE ((usedDiskSize+request) > (size.int - size.int DIV 5)) DO target := none; now := ""; AddTimeStamp(now); (* init comparison *) Files.Set(index, indexFile, 0); ReadEntry(index, curr); WHILE ~index.eof DO (* search for LRU *) IF ((curr.url[0] # 0X) & (curr.accessTag < now)) THEN now := curr.accessTag; target := Files.Pos(index) - recLen; old := curr END; ReadEntry(index, curr) END; IF (target >= 0) & (target <= Files.Length(indexFile)) THEN old.url := ""; Expand(old.fname, oldData); (* delete old data *) Files.Delete(oldData, res); ASSERT(res = 0); DEC(usedDiskSize, old.fLen); Files.Set(index, indexFile, target); (* update index and keys *) WriteEntry(index, old); Files.Close(indexFile); Files.Set(keys, keyFile, (target DIV recLen) * SIZE(INTEGER)); Files.WriteInt(keys, GetKey(old.url)); Files.Close(keyFile); ELSE (* no target found, restart *) CleanUp END END END END CheckCacheSize;8R }8Courier10.Scn.FntCourier10i.Scn.FntQSyntax10b.Scn.FntSyntax10.Scn.Fnt: (**- Only used to force loading of module ------------------------------------**) BEGIN END Install;8 lw8Courier10.Scn.FntSyntax10.Scn.Fntq8FoldElemsNew>Courier10.Scn.FntCourier10i.Scn.FntQR (**- User command to delete whole cache data. --------------------------------**)8ApVersionElemsAllocBeg#Syntax10.Scn.FntWindows31 Win95andMacWin95andMacWindows31<Syntax10b.Scn.FntSyntax10.Scn.Fnt!VAR curr : List; res : INTEGER;Win95andMac pVersionElemsAllocEndSyntax10b.Scn.Fntp#Syntax10.Scn.FntWin95andMac Windows31Win95andMacWin95andMac Windows31 np.p#Syntax10.Scn.FntWindows31 Win95andMacWin95andMacWindows31Syntax10.Scn.FntJSyntax10b.Scn.Fnt ACourier10.Scn.FntCourier10i.Scn.Fnt head := NIL; Dir.Enumerate(Dir.This(dir.str), EnumDelete); curr := head; WHILE curr # NIL DO Files.Delete(curr.name, res); ASSERT(res = 0); curr := curr.next END; InitIndex(FALSE); (* reset index file *)Win95andMac p$   BEGIN Dir.Enumerate(Dir.This(dir.str), EnumDelete); Files.Purge(indexFile); Files.Purge(keyFile); Out.String("..");  usedDiskSize := 0; lastPos := none; END CleanUp;8 E8Courier10.Scn.FntSyntax10.Scn.Fnt8FoldElemsNew>Courier10.Scn.FntCourier10i.Scn.Fnt=> (**- Puts url to cache -------------------------------------------------------**) (* Url is remained in indices, data associated with Fileinfo is stored. *) (* Time information is converted to Oberon date/time-format. *) (* Current cachesize is checked, if request will fit, otherwise least recent *) (* used url's are deleted. *) (* Huge requests, that exceed total cachesize, are ignored. *) (*-----------------------------------------------------------------------------*)8Courier10i.Scn.FntSyntax10b.Scn.Fnt  MarkElemsAlloc,tCX8&Courier10.Scn.FntSyntax10.Scn.FntW8FoldElemsNewCourier10.Scn.FntCourier10i.Scn.Fntf8FoldElemsNewECourier10i.Scn.FntCourier10.Scn.FntRV HTTP-date = rfc1123-date | rfc850-date | asctime-date SP = Space rfc1123-date = wkday "," SP date1 SP time SP "GMT" rfc850-date = weekday "," SP date2 SP time SP "GMT" asctime-date = wkday SP date3 SP time SP 4DIGIT date1 = 2DIGIT SP month SP 4DIGIT ; day month year (e.g., 02 Jun 1982) date2 = 2DIGIT "-" month "-" 2DIGIT ; day-month-year (e.g., 02-Jun-82) date3 = month SP ( 2DIGIT | ( SP 1DIGIT )) ; month day (e.g., Jun 2) time = 2DIGIT ":" 2DIGIT ":" 2DIGIT ; 00:00:00 - 23:59:59 wkday = "Mon" | "Tue" | "Wed" | "Thu" | "Fri" | "Sat" | "Sun" weekday = "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" | "Saturday" | "Sunday" month = "Jan" | "Feb" | "Mar" | "Apr" | "May" | "Jun" | "Jul" | "Aug" | "Sep" | "Oct" | "Nov" | "Dec" 8 (*-- Converts HTTP-date in "s" to Oberon date/time-format -------------------*) (* If conversion failed (unknown or invalid date-format), *) (* "d" (=date) and "t" (=time) are set to zero. *) (* Supplied date formats are: *) (*---------------------------------------------------------------------------*)8Syntax10b.Scn.Fnt\ MarkElemsAllocv';XCourier10i.Scn.Fnt"83Courier10i.Scn.FntQSyntax10i.Scn.FntSyntax10b.Scn.FntSyntax10.Scn.Fnt ' <(,C(,C&  (*-- Extracts hh:mm:ss from string "s" starting at "offset"----------------*) VAR pat : ARRAY 3 OF CHAR; BEGIN IF s[offset] = "0" THEN pat[0] := s[offset+1]; pat[1] := 0X ELSE pat[0] := s[offset]; pat[1] := s[offset+1]; pat[2] := 0X END; hrs := SHORT(Web.Str2Int(pat)); IF s[offset+3] = "0" THEN pat[0] := s[offset+4]; pat[1] := 0X ELSE pat[0] := s[offset+3]; pat[1] := s[offset+4]; pat[2] := 0X END; min := SHORT(Web.Str2Int(pat)); IF s[offset+6] = "0" THEN pat[0] := s[offset+7]; pat[1] := 0X ELSE pat[0] := s[offset+6]; pat[1] := s[offset+7]; pat[2] := 0X END; sec := SHORT(Web.Str2Int(pat)) END ExtractTime;82LlJ   -j  LSyntax10i.Scn.FntF  $ 8n LjH H  -L]  CONST Days = "Monday Tuesday WednesdayThursday Friday Saturday Sunday "; Month = "JanFebMarAprMayJunJulAugSepOctNovDec"; VAR sem, pos, day, month, year, hour, minute, second : INTEGER; pat : ARRAY 16 OF CHAR; PROCEDURE ExtractTime (s : ARRAY OF CHAR; offset : INTEGER; VAR(*out*) hrs, min, sec : INTEGER);  BEGIN sem := Strings.Pos(",", s, 0); d := 0; t := 0; (*-- rfc1123 format ------------------------------------------------------*) IF ((sem = 3) & (s[4] = " ") & (s[7] = " ") & (s[11] = " ") & (s[16] = " ") & (s[19] = ":") & (s[22] = ":")) THEN Strings.Extract(s, 0, sem, pat); pos := Strings.Pos(pat, Days, 0); IF pos MOD 9 # 0 THEN RETURN END; (* no weekday match *) IF s[5] = "0" THEN pat[0] := s[6]; pat[1] := 0X ELSE pat[0] := s[5]; pat[1] := s[6]; pat[2] := 0X END; day := SHORT(Web.Str2Int(pat)); Strings.Extract(s, 8, 3, pat); pos := Strings.Pos(pat, Month, 0); IF pos MOD 3 # 0 THEN RETURN END; (* month does't match *) month := pos DIV 3 + 1; Strings.Extract(s, 14, 2, pat); year := SHORT(Web.Str2Int(pat)); ExtractTime(s, 17, hour, minute, second) (*-- rfc 850 format ------------------------------------------------------*) ELSIF ((sem >= 6) & (sem <= 9) & (s[sem+1] = " ") & (s[sem+4] = "-") & (s[sem+8] = "-") & (s[sem+11] = " ") & (s[sem+14] = ":") & (s[sem+17] = ":")) THEN Strings.Extract(s, 0, sem, pat); pos := Strings.Pos(pat, Days, 0); IF pos MOD 9 # 0 THEN RETURN END; (* no weekday match *) IF s[sem+2] = "0" THEN pat[0] := s[sem+3]; pat[1] := 0X ELSE pat[0] := s[sem+2]; pat[1] := s[sem+3]; pat[2] := 0X END; day := SHORT(Web.Str2Int(pat)); Strings.Extract(s, sem+5, 3, pat); pos := Strings.Pos(pat, Month, 0); IF pos MOD 3 # 0 THEN RETURN END; (* month does't match *) month := pos DIV 3 + 1; Strings.Extract(s, sem+9, 2, pat); year := SHORT(Web.Str2Int(pat)); ExtractTime(s, sem+12, hour, minute, second) (*-- ANSII-C format ------------------------------------------------------*) ELSIF (sem = -1) & (s[3] = " ") & (s[7] = " ") & (s[10] = " ") & (s[13] = ":") & (s[16] = ":") & (s[19] = " ") THEN Strings.Extract(s, 0, 3, pat); pos := Strings.Pos(pat, Days, 0); IF pos MOD 9 # 0 THEN RETURN END; (* no weekday match *) Strings.Extract(s, 4, 3, pat); pos := Strings.Pos(pat, Month, 0); IF pos MOD 3 # 0 THEN RETURN END; (* month does't match *) month := pos DIV 3 + 1; IF s[8] = " " THEN pat[0] := s[9]; pat[1] := 0X ELSE pat[0] := s[8]; pat[1] := s[9]; pat[2] := 0X END; day := SHORT(Web.Str2Int(pat)); ExtractTime(s, 11, hour, minute, second); Strings.Extract(s, 22, 2, pat); year := SHORT(Web.Str2Int(pat)) (*-- unknown or invalid format -------------------------------------------*) ELSE RETURN END; d := day + month * 32 + LONG(year) * 512; t := second + minute * 64 + LONG(hour) * 4096 END HttpDateToOberonDate;8EH Z  VAR e : Entry; PROCEDURE HttpDateToOberonDate(s : ARRAY OF CHAR; VAR(*out*) d, t : LONGINT);  BEGIN ASSERT((url # NIL) & (fi # NIL)); e.fname := ""; e.accessTag := ""; IF (fi.len.act < size.int) THEN HttpDateToOberonDate(dateTime, e.date, e.time); e.fLen := fi.len.act; IF fi.cKey # NIL THEN e.id := fi.cKey.id ELSE e.id := "" END; e.cod := fi.coding; CheckCacheSize(e.fLen); WriteIndex(e, url); WriteData(e, fi.r) END END Put;8 tKb8< Courier10.Scn.FntSyntax10.Scn.Fnt8FoldElemsNewCourier10.Scn.FntCourier10i.Scn.Fnt38FoldElemsNewCourier10.Scn.FntCourier10b.Scn.FntS + _ +c/S "res" (= notFound | found | condFound ) is set to following values: +----------+-----------+-----------+ | Never | Once | Always | Cache operating modes +-------------+----------+-----------+-----------+ | in cache & | found | condFound | condFound | | not checked | | | | +-------------+----------+-----------+-----------+ | in cache & | found | found | condFound | | checked | | | | +-------------+----------+-----------+-----------+ | not in | notFound | notFound | notFound | | cache | | | | +-------------+----------+-----------+-----------+ Data state "checked" will be set TRUE after "Put" and "Get" operation  8  (**- Check url in cache, "res" is set according to Result matrix. ---------**) (* If url is found, "date" and "time" are set to stored timestamp of url *) (*----------------------------------------------------------------------------*)8Syntax10b.Scn.Fnt  MarkElemsAlloc:8,Courier10.Scn.FntSyntax10.Scn.Fnt38FoldElemsNew>Courier10.Scn.FntCourier10i.Scn.Fnt (*- Local find procedure for "Check": ---------------------------------------*) (* IF url found: "res" is set to "HTTP.found" *) (* whole entry is read from main index *) (* global variable "lastPos" holds main index pos. for a *) (* following call of procedure "Get" *) (* IF notfound: "res" is set to "HTTP.notFound" *) (* "lastPos" is set to "none" *) (*---------------------------------------------------------------------------*)8Courier10i.Scn.FntSyntax10i.Scn.FntSyntax10b.Scn.FntS67h[   VAR r : Files.Rider; currUrl : UrlString; currPos, accessPos, offset, dummy : LONGINT; BEGIN offset := 0; lastPos := none; res := HTTP.notFound; REPEAT accessPos := GetIndexPos(offset, e.url, dummy); IF (accessPos = none) THEN RETURN END; Files.Set(r, indexFile, accessPos); ReadString(r, currUrl, currPos); offset := currPos + recLen; UNTIL r.eof OR (currUrl = e.url); IF currUrl = e.url THEN Files.Set(r, indexFile, currPos); ReadEntry(r, e); lastPos := currPos; res := HTTP.found END END FindUrl;8:Syntax10i.Scn.Fnt"L c  VAR e : Entry; PROCEDURE FindUrl(VAR(*inout*) e : Entry; VAR(*out*) res : INTEGER);  BEGIN ASSERT(url # NIL); Url2Str(url, e.url); FindUrl(e, res); IF (res = HTTP.found) THEN date := e.date; time := e.time; IF (e.checked & (mode.str = "always")) OR (~e.checked & (mode.str # "never")) THEN res := HTTP.condFound END END END Check;8 8 Courier10.Scn.FntSyntax10.Scn.Fnt{8FoldElemsNew>Courier10.Scn.FntCourier10i.Scn.FntGH (**- Fetch requested url from cache ------------------------------------------**) (* Precond.: *) (* Procedure "Check" should be called before "Get" to prevent NIL result *) (*-----------------------------------------------------------------------------*)8Courier10i.Scn.FntSyntax10b.Scn.Fnt[ MarkElemsAlloc(;8Courier10.Scn.FntSyntax10.Scn.Fnt8FoldElemsNew>Courier10.Scn.FntCourier10i.Scn.Fnt57 (*- Local find procedure for "Get" ------------------------------------------*) (* To speedup search, main index is first accessed directly over global *) (* variable "lastPos", which was set by a preceeding call of "Check". *) (* IF url found: whole entry is read from main index, "checked" <- TRUE *) (* new timestamp is added, "res" <- HTTP.found *) (* IF notfound: "res" <- HTTP.notFound *) (*---------------------------------------------------------------------------*)8Courier10i.Scn.FntSyntax10i.Scn.FntSyntax10b.Scn.FntR $E%!3c! L  VAR r : Files.Rider; currUrl : UrlString; currPos, accessPos, offset, dummy : LONGINT; BEGIN IF lastPos # none THEN (* try direct access first *) Files.Set(r, indexFile, lastPos); ReadString(r, currUrl, currPos); END; offset := 0; res := HTTP.notFound; WHILE ~r.eof & (currUrl # e.url) DO (* search, if access failed *) accessPos := GetIndexPos(offset, e.url, dummy); IF (accessPos = none) THEN RETURN END; Files.Set(r, indexFile, accessPos); ReadString(r, currUrl, currPos); offset := currPos + recLen; END; IF currUrl = e.url THEN (* remain access in index *) Files.Set(r, indexFile, currPos); ReadEntry(r, e); e.accessTag := ""; AddTimeStamp(e.accessTag); e.checked := TRUE; Files.Set(r, indexFile, currPos); WriteEntry(r, e); Files.Close(indexFile); res := HTTP.found END END FindData;8;!Syntax10i.Scn.Fnt !  VAR dataName : FileName; dataFile : Files.File; fi : Web.FileInfo; e : Entry; res : INTEGER; PROCEDURE FindData(VAR(*inout*) e : Entry; VAR(*out*) res : INTEGER);  BEGIN ASSERT(url # NIL); Url2Str(url, e.url); FindData(e, res); IF res = HTTP.notFound THEN (* no data available *) RETURN NIL ELSE (* url found, init Fileinfo *) Expand(e.fname, dataName); dataFile := Files.Old(dataName); ASSERT(dataFile # NIL); NEW(fi); NEW(fi.len); NEW(fi.cKey); Files.Set(fi.r, dataFile, 0); fi.len.act := Files.Length(dataFile); fi.len.def := fi.len.act; fi.local := FALSE; fi.cKey.id := e.id; fi.coding := e.cod; RETURN fi END END Get;8R"< MODULE Cache; (* Windows 95 and Mac *) (*-----------------------------------------------------------------------------*) (* MODULE: Cache V2.9 ME  *) (* DESCRIPTION: Web-Cache, see also Cache.Text *) (* LANGUAGE: Oberon-2, V4.0-2.0 *) (* AUTHOR: Martin Erdpresser  *) (*-----------------------------------------------------------------------------*) (* REVISION HISTORY *) (* Date Author Changes *) (* -------- ------ ----------------------------------------------------------*) (* 28.01.97 ME Final release *) (*-----------------------------------------------------------------------------*) IMPORT Dir := Directories, Files, Out, Oberon, Strings, Texts, HTTP, Web; CONST  TYPE  VAR  (*-------------------< Auxiliary procedures >----------------------------------*) PROCEDURE ReadString(VAR r : Files.Rider; VAR(*out*) s : UrlString; VAR(*out*) start : LONGINT);  PROCEDURE ReadEntry(VAR r : Files.Rider; VAR(*out*) e : Entry);  PROCEDURE WriteEntry(VAR r : Files.Rider; VAR(*in*) e : Entry);  PROCEDURE GetKey(urlStr : ARRAY OF CHAR) : INTEGER;  PROCEDURE InitIndex(keepUrl : BOOLEAN);  PROCEDURE InitAll;  PROCEDURE AddTimeStamp(VAR(*inout*) name : ARRAY OF CHAR);  PROCEDURE EnumDelete(d : Dir.Directory; name : ARRAY OF CHAR; isDir : BOOLEAN; VAR(*inout*) continue : BOOLEAN);  PROCEDURE Url2Str(url : Web.Url; VAR(*out*) urlStr : ARRAY OF CHAR);  PROCEDURE GetIndexPos(offset: LONGINT; urlStr: UrlString; VAR(*out*) emptyPos: LONGINT) : LONGINT;  PROCEDURE Expand(base : ARRAY OF CHAR; VAR(*inout*) full : ARRAY OF CHAR);  PROCEDURE WriteIndex(VAR(*inout*) e : Entry; url : Web.Url);  PROCEDURE WriteData(VAR(*in*) e : Entry; VAR rSrc : Files.Rider);  PROCEDURE^ CleanUp*; PROCEDURE CheckCacheSize(request : LONGINT);  (*-------------------< Exported procedures >-----------------------------------*) PROCEDURE Install*;  PROCEDURE CleanUp*;  PROCEDURE Put*(url: Web.Url; fi: Web.FileInfo; VAR(*in*) dateTime : ARRAY OF CHAR);  PROCEDURE Check*(url : Web.Url; VAR(*out*) res : INTEGER; VAR(*out*) date, time : LONGINT);  PROCEDURE Get*(url : Web.Url) : Web.FileInfo;  (*-------------------< Module body >-------------------------------------------*) BEGIN HTTP.CheckCache := Check; (* procedure mapping *) HTTP.CachedFile := Get; HTTP.PutToCache := Put; InitAll END Cache.