<> <> <> DIRECTORY BcdDefs USING [BCD], Convert USING [MapValue, Parse, Value, ValueToRope], ConvertUnsafe USING [AppendRope, ToRope], DFUser USING [CleanupEnumerate, EnumerateEntries, EnumerateProcType, TooManyEntries], Environment USING [bytesPerWord], FileIO USING [Open], Heap USING [systemZone], IFSFile USING [Close, Completer, FileHandle, Finalize, FSInstance, Initialize, Login, Logout, Open, StartRead], IO USING [Close, GetChar, GetIndex, GetLength, GetSequence, PutChar, SetIndex, SetLength, STREAM], MessageWindow USING [Append], PriorityQueue USING [Insert, Predict, Ref, Remove, SortPred], Process USING [Detach, SecondsToTicks, SetTimeout], Rope USING [Cat, Concat, Equal, Fetch, FetchType, Flatten, FromProc, Index, MakeRope, Match, Replace, ROPE, Run, Size, SkipTo, Substr], SafeStorage USING [NewZone], SymTab USING [Create, Fetch, Insert, Ref], System USING [GetClockPulses, Microseconds, PulsesToMicroseconds], Time USING [Current, defaultTime], TimeStamp USING [Stamp], UnsafeSTP USING [Close, Create, DesiredProperties, Enumerate, FileInfo, GetFileInfo, Handle, Login, NoteFileProcType, Open, SetDesiredProperties], UserCredentials USING [GetUserCredentials], UserExec USING [AskUser, ExecHandle, GetExecHandle, GetStreams], VersionMap USING [Fetch, FetchName, GetPrefix, Length, Map, MapRep, MapEntry, NameMapIndex, MyStamp], VersionMapBuilder USING [EachProc, FilterProc]; VersionMapBuilderImpl: CEDAR MONITOR IMPORTS Convert, ConvertUnsafe, DFUser, FileIO, Heap, IFSFile, IO, MessageWindow, PriorityQueue, Process, Rope, SafeStorage, UnsafeSTP, SymTab, System, Time, UserCredentials, UserExec, VersionMap EXPORTS VersionMapBuilder SHARES VersionMap = BEGIN OPEN Rope, VersionMap, VersionMapBuilder; <> XMyStamp: TYPE = MACHINE DEPENDENT RECORD [lo,num,hi: CARDINAL]; <> <> <> NullStamp: MyStamp _ [0, 0, 0]; MyStampAsHex: TYPE = PACKED ARRAY [0..12) OF [0..16); XMap: TYPE = REF MapRep; XMapList: TYPE = LIST OF Map; XMapRep: PUBLIC TYPE = RECORD [ names: ROPE _ NIL, prefix: NameMapIndex _ 0, -- place to find factored prefix entries: SEQUENCE len: CARDINAL OF MapEntry]; <> <> XMapEntry: TYPE = RECORD [stamp: MyStamp, index: NameMapIndex]; XNameMapIndex: TYPE = INT; -- byte index of name in map NullNameIndex: NameMapIndex = LAST[NameMapIndex]; EntryIndex: TYPE = CARDINAL; NullEntryIndex: EntryIndex = LAST[CARDINAL]; EntryRef: TYPE = REF EntryRep; EntryRep: TYPE = RECORD [next: EntryRef, name: ROPE, version: MyStamp]; GenerateData: TYPE = REF GenerateDataRep; GenerateDataRep: TYPE = RECORD [ host,directory: ROPE _ NIL, pq: PriorityQueue.Ref _ NIL, list: EntryRef _ NIL, countGoing: NAT _ 0, errors: NAT _ 0, done: CONDITION]; pz: ZONE _ SafeStorage.NewZone[prefixed]; qz: ZONE _ SafeStorage.NewZone[quantized]; alarmClock: CONDITION; reportTime: CONDITION; reportInterval: NAT _ 0; reportProcess: PROCESS _ NIL; report: ROPE _ NIL; fileCount: INT _ 0; outstandingRequests: INT _ 2; -- # of processes to have looking at version pages lastMap: Map _ NIL; -- last map produced (useful for debugging) maxPiece: INT _ 8000; <<**** BEGIN EXPORTED PROCEDURES ****>> VersionNotAvailable: PUBLIC ERROR = CODE; NameError: PUBLIC ERROR = CODE; DuplicateVersionSignal: PUBLIC SIGNAL = CODE; WriteMapToFile: PUBLIC PROC [map: Map, name: ROPE, checkForDuplicateVersions: BOOL _ FALSE] = { lagStamp: MyStamp _ NullStamp; prefix: ROPE _ NIL; st: IO.STREAM _ FileIO.Open[name, write]; count: NAT _ map.len; names: ROPE _ map.names; firstCR: INT _ names.Index[0, "\n"]; prefix _ names.Flatten[0, firstCR]; MyPut[st, prefix]; MyPut[st, "\n"]; FOR i: NAT IN [0..map.len) DO <> entry: MapEntry _ map[i]; index: INT _ entry.index; stamp: MyStamp _ entry.stamp; IF stamp = lagStamp AND checkForDuplicateVersions THEN { <> r1: ROPE _ IndexToShortName[map, map[i-1].index]; r2: ROPE _ IndexToShortName[map, index]; IF r1.Equal[r2, FALSE] THEN LOOP ELSE SIGNAL DuplicateVersionSignal}; lagStamp _ stamp; PutStampAsHex[st, stamp]; MyPut[st, " "]; DO c: CHAR _ names.Fetch[index]; index _ index + 1; IO.PutChar[st, c]; IF c = '\n THEN EXIT; ENDLOOP; ENDLOOP; MyPut[st, "\n"]; IO.SetLength[st, IO.GetIndex[st]]; -- force goddamm truncation already!!! IO.Close[st]; }; ParseMapFromFile: PUBLIC PROC [name: ROPE] RETURNS [map: Map _ NIL] = { <> <> <<(full name sans prefix)>> st: IO.STREAM _ FileIO.Open[name, read]; bytes: INT _ IO.GetLength[st]; getNext: PROC RETURNS [CHAR] = { RETURN [st.GetChar[]]; }; rope: ROPE _ Rope.FromProc[bytes, getNext, maxPiece]; pos: INT _ 0; head,list: EntryRef _ NIL; prefix: ROPE _ IO.GetSequence[st]; count: NAT _ 0; IO.Close[st]; prefix _ rope.Flatten[0, pos _ rope.Index[0, "\n"]]; pos _ pos + 1; WHILE pos < bytes DO c: CHAR _ rope.Fetch[pos]; start: INT _ 0; name: ROPE _ NIL; hex: MyStampAsHex _ ALL[0]; pos _ pos + 1; IF c <= 40C THEN LOOP; <> FOR i: NAT IN [0..12) WHILE pos <= bytes DO x: INTEGER _ 0; SELECT c FROM IN ['0..'9] => x _ c - '0; IN ['A..'F] => x _ (c - 'A) + 10; IN ['a..'f] => x _ (c - 'a) + 10; ENDCASE => EXIT; hex[i] _ x; c _ rope.Fetch[pos]; pos _ pos + 1; ENDLOOP; <> WHILE pos <= bytes DO IF c > 40C THEN {start _ pos - 1; EXIT}; c _ rope.Fetch[pos]; pos _ pos + 1; ENDLOOP; <> WHILE pos <= bytes DO IF c <= 40C THEN { name _ rope.Substr[start, pos-start-1]; EXIT}; c _ rope.Fetch[pos]; pos _ pos + 1; ENDLOOP; IF head = NIL THEN head _ list _ NewEntryRef[name] ELSE {list.next _ NewEntryRef[name]; list _ list.next}; list.version _ LOOPHOLE[hex]; count _ count + 1; ENDLOOP; lastMap _ map _ GenerateMapFromEntryList[head, prefix, count]; }; SetReportInterval: PUBLIC PROC [seconds: NAT _ 0] = TRUSTED { <> temp: NAT _ IF seconds = 0 THEN 10 ELSE seconds; forkIt: ENTRY PROC = TRUSTED { ENABLE UNWIND => NULL; reportInterval _ seconds; IF reportProcess = NIL AND seconds # 0 THEN Process.Detach[reportProcess _ FORK ClarkKent[]]; BROADCAST reportTime; }; Process.SetTimeout[@reportTime, Process.SecondsToTicks[temp]]; forkIt[]; }; GenerateMapFromRemote: PUBLIC PROC [host: ROPE _ NIL, directory: ROPE _ NIL, oldMap: Map _ NIL, filter: FilterProc _ NIL] RETURNS [map: Map] = TRUSTED { head: EntryRef _ NIL; prefix: ROPE _ NIL; count: NAT _ 0; IF host = NIL THEN host _ "Ivy"; IF directory = NIL THEN { user: ROPE _ UserCredentials.GetUserCredentials[].name; user _ user.Flatten[0, user.Index[0, "."]]; -- remove registry directory _ Rope.Cat["<", user, ">*.bcd"]}; IF directory.Index[0, "*"] = directory.Size[] THEN { directory _ directory.Concat["*.bcd"]}; [head, prefix, count] _ GenerateEntryListFromRemote[host, directory, oldMap, filter]; lastMap _ map _ GenerateMapFromEntryList[head, prefix, count]; }; GenerateMapFromDFFile: PUBLIC PROC [name: ROPE _ NIL, pattern: ROPE _ NIL, oldMap: Map _ NIL, filter: FilterProc _ NIL] RETURNS [map: Map] = TRUSTED { <> <> flat: ROPE _ name.Flatten[]; string: LONG STRING _ LOOPHOLE[flat]; head: EntryRef _ NIL; prefix: ROPE _ NIL; count: NAT _ 0; guess: NAT _ 8000; tab: SymTab.Ref _ NIL; verbose: BOOL _ FALSE; eachProc: DFUser.EnumerateProcType = TRUSTED { <> localName: STRING _ [120]; eachName: ROPE _ NIL; new: EntryRef _ NIL; Add1: PROC [c: CHAR] = TRUSTED { k: NAT _ localName.length; localName[k] _ c; localName.length _ k+1}; Add: PROC [s1,s2,s3,s4: LONG STRING _ NIL] = TRUSTED { as: ARRAY [0..4) OF LONG STRING _ [s1, s2, s3, s4]; k: NAT _ localName.length; FOR i: NAT IN [0..4) DO s: LONG STRING _ as[i]; IF s # NIL THEN FOR j: NAT IN [0..s.length) DO localName[k] _ s[j]; k _ k + 1; ENDLOOP; ENDLOOP; localName.length _ k; }; eachName _ ConvertUnsafe.ToRope[shortName]; IF NOT Rope.Match[pattern, eachName, FALSE] THEN RETURN; -- no match <> IF host # NIL THEN Add["[", host, "]"]; IF directory # NIL THEN Add["<", directory, ">", shortName]; IF version # 0 THEN {Add1['!]; Convert.MapValue[Add1, [signed[version]]]}; eachName _ ConvertUnsafe.ToRope[localName]; IF LookupName[tab, eachName] THEN RETURN; -- name already here IF filter # NIL AND NOT filter[eachName] THEN RETURN; -- user does not want this one new _ NewEntryRef[eachName]; IF eachName.Index[0, ".bcd", FALSE] = eachName.Size[] THEN { <> new.version _ LOOPHOLE[TimeStamp.Stamp[0, 0, createTime]]}; count _ count + 1; new.next _ head; head _ new; StoreName[tab, eachName]; }; IF pattern = NIL THEN pattern _ "*"; [head, prefix, count] _ GenerateEntryListFromMap[oldMap]; tab _ GenSymTabFromVersionMap[oldMap]; FOR try: NAT IN [0..4) DO err: BOOL _ FALSE; {ENABLE UNWIND => DFUser.CleanupEnumerate[]; exec: UserExec.ExecHandle _ UserExec.GetExecHandle[]; in,out: IO.STREAM _ NIL; [in: in, out: out] _ UserExec.GetStreams[exec]; DFUser.EnumerateEntries [ dfFileName: string, procToCall: eachProc, nEntriesInDFFiles: guess, confirmBeforeOverwriting: FALSE, printProgressMessages: verbose, in: in, out: out, confirmData: NIL, Confirm: Confirm ! DFUser.TooManyEntries => {err _ TRUE; CONTINUE}]; DFUser.CleanupEnumerate[]}; guess _ guess + guess; IF err OR count = 0 THEN LOOP; lastMap _ map _ GenerateMapFromEntryList[head, NIL, count]; RETURN ENDLOOP; ERROR DFUser.TooManyEntries; }; KeyList: LIST OF ATOM = LIST[$Y, $N, $A, $Q]; Confirm: PROC [in, out: IO.STREAM, data: REF, msg: ROPE, dch: CHAR] RETURNS [CHAR] = TRUSTED { exec: UserExec.ExecHandle _ UserExec.GetExecHandle[]; answer: ATOM _ UserExec.AskUser [msg: " OK? ", defaultKey: $Y, exec: exec, keyList: KeyList]; SELECT answer FROM $Y => RETURN ['Y]; $N => RETURN ['N]; $A => RETURN ['A]; $Q => RETURN ['Q]; ENDCASE => RETURN ['?]; }; GenerateMapFromProc: PUBLIC PROC [each: EachProc, data: REF _ NIL, prefix: ROPE _ NIL] RETURNS [map: Map] = { <> head: EntryRef _ NIL; stamp: TimeStamp.Stamp; name: ROPE _ NIL; new: EntryRef _ NIL; count: NAT _ 0; DO <> [stamp, name] _ each[data]; IF name.Size[] = 0 THEN EXIT; new _ NewEntryRef[name]; new.version _ LOOPHOLE[stamp]; count _ count + 1; new.next _ head; head _ new; ENDLOOP; lastMap _ map _ GenerateMapFromEntryList[head, prefix, count]; }; <> <> <> bcdList: LIST OF ROPE _ LIST["bcd", "symbols"]; NonBCDFilter: PUBLIC FilterProc = { <> RETURN[NOT TestExtension[name, bcdList]] }; OnlyBCDFilter: PUBLIC FilterProc = { <> RETURN[TestExtension[name, bcdList]] }; MergeMaps: PUBLIC PROC [map1,map2: VersionMap.Map] RETURNS [map: VersionMap.Map] = { implicit1: INT _ map1.len - CountExplicitPrefixes[map1]; prefix1: ROPE _ map1.GetPrefix[]; implicit2: INT _ map2.len - CountExplicitPrefixes[map2]; prefix2: ROPE _ map2.GetPrefix[]; prefix: ROPE _ prefix1; IF map1.Length[] = 0 THEN RETURN [map2]; IF map2.Length[] = 0 THEN RETURN [map1]; IF implicit1*prefix1.Size[] < implicit2*prefix2.Size[] THEN { <> tempMap: VersionMap.Map _ map1; map1 _ map2; map2 _ tempMap; }; IF NOT Rope.Equal[prefix1, prefix2, FALSE] THEN <> map2 _ DistributePrefix[map2]; <> prefix1 _ map1.GetPrefix[]; prefix2 _ map2.GetPrefix[]; map _ pz.NEW[VersionMap.MapRep[map1.len + map2.len]]; map.names _ Rope.Replace[map2.names, 0, prefix2.Size[]+1, map1.names]; map.prefix _ map1.prefix; {-- now merge the index set index1: NAT _ 0; index2: NAT _ 0; valid1, valid2: BOOL _ TRUE; offset: INT _ map1.names.Size[] - prefix2.Size[] - 1; -- offset of start of map2 names entry1: VersionMap.MapEntry _ map1[index1]; entry2: VersionMap.MapEntry _ map2[index2]; FOR i: NAT IN [0..map.len) DO SELECT TRUE FROM NOT valid2 OR (valid1 AND VersionPred[entry1.stamp, entry2.stamp]) => { <> map[i] _ entry1; index1 _ index1 + 1; valid1 _ index1 < map1.len; IF valid1 THEN entry1 _ map1[index1]; }; valid2 => { <> entry2.index _ entry2.index + offset; map[i] _ entry2; index2 _ index2 + 1; valid2 _ index2 < map2.len; IF valid2 THEN entry2 _ map2[index2]; }; ENDCASE => ERROR; -- we blew it somewhere ENDLOOP; }; }; DistributePrefix: PUBLIC PROC [map: VersionMap.Map] RETURNS [newMap: VersionMap.Map _ NIL] = { <> IF map # NIL THEN { names: ROPE _ map.names; explicit: INT _ CountExplicitPrefixes[map]; implicit: INT _ map.len - explicit; prefix: ROPE _ map.GetPrefix[]; prefixSize: INT _ prefix.Size[]; newNamesLen: INT _ names.Size[] + implicit * prefixSize; delta: INT _ 0; prefixPos: INT _ 0; inName: BOOL _ TRUE; namePos: INT _ prefixSize; nextIndex: NAT _ 0; getChar: PROC RETURNS [c: CHAR] = { DO IF prefixPos < prefixSize THEN { <> c _ prefix.Fetch[prefixPos]; prefixPos _ prefixPos + 1; RETURN}; IF inName THEN { <> c _ names.Fetch[namePos]; namePos _ namePos + 1; inName _ c # '\n; RETURN}; <> namePos _ map[nextIndex].index; nextIndex _ nextIndex + 1; inName _ TRUE; c _ names.Fetch[namePos]; SELECT c FROM '/, '[ => prefixPos _ prefixSize; ENDCASE => prefixPos _ 0; ENDLOOP; }; IF implicit <= 0 OR names.Fetch[map.prefix] = '\n THEN RETURN [map]; -- no prefix to distribute newMap _ pz.NEW[VersionMap.MapRep[map.len]]; <> FOR i: CARDINAL IN [0..map.len) DO entry: VersionMap.MapEntry _ map[i]; char: CHAR _ names.Fetch[entry.index]; entry.index _ entry.index + delta; newMap[i] _ entry; SELECT char FROM '/, '[ => {}; ENDCASE => delta _ delta + prefixSize; ENDLOOP; newMap.prefix _ 0; -- we retain the prefix, even though it is distributed newMap.names _ Rope.FromProc[newNamesLen, getChar, maxPiece]; }; }; SplitMap: PUBLIC PROC [map: VersionMap.Map] RETURNS [symbols,source: VersionMap.Map _ NIL] = { <> IF map # NIL THEN { count1,count2: INT _ 0; chars1,chars2: INT _ 0; index1,index2: INT _ 0; names: ROPE _ map.names; <> FOR i: NAT IN [0..map.len) DO entry: VersionMap.MapEntry _ map[i]; pos: INT _ Rope.SkipTo[names, entry.index, ".\n"]+1; len: INT _ Rope.SkipTo[names, pos, "!\n"] - pos; IF len > 0 THEN { chars: INT _ Rope.SkipTo[names, pos, "\n"] - entry.index; SELECT len FROM Rope.Run[names, pos, "bcd", 0, FALSE] => {count1 _ count1 + 1; chars1 _ chars1 + chars}; Rope.Run[names, pos, "symbols", 0, FALSE] => {count1 _ count1 + 1; chars1 _ chars1 + chars}; ENDCASE => {count2 _ count2 + 1; chars2 _ chars2 + chars}; }; ENDLOOP; <> IF count1 > 0 THEN { symbols _ pz.NEW[VersionMap.MapRep[count1]]; symbols.prefix _ map.prefix; symbols.names _ map.names}; <> IF count2 > 0 THEN { source _ pz.NEW[VersionMap.MapRep[count2]]; source.prefix _ map.prefix; source.names _ map.names}; <> FOR i: NAT IN [0..map.len) DO entry: VersionMap.MapEntry _ map[i]; pos: INT _ Rope.SkipTo[names, entry.index, ".\n"]+1; len: INT _ Rope.SkipTo[names, pos, "!\n"] - pos; IF len > 0 THEN { SELECT len FROM Rope.Run[names, pos, "bcd", 0, FALSE] => {symbols[index1] _ entry; index1 _ index1 + 1}; Rope.Run[names, pos, "symbols", 0, FALSE] => {symbols[index1] _ entry; index1 _ index1 + 1}; ENDCASE => {source[index2] _ entry; index2 _ index2 + 1}; }; ENDLOOP; }; }; CompressMap: PUBLIC PROC [map: VersionMap.Map] RETURNS [newMap: VersionMap.Map _ NIL] = { <> names: ROPE _ map.names; newLen: INT _ 0; inName: BOOL _ FALSE; inPrefix: BOOL _ TRUE; nextIndex: NAT _ 0; ropePos: INT _ 0; namePos: INT _ 0; prefix: ROPE _ names.Flatten[namePos, names.SkipTo[namePos, "\n"]]; prefixLen: INT _ prefix.Size[]; getChar: PROC RETURNS [c: CHAR] = { DO IF inPrefix THEN { <> IF namePos < prefixLen THEN {c _ prefix.Fetch[namePos]; namePos _ namePos + 1} ELSE {c _ '\n; inPrefix _ FALSE}; ropePos _ ropePos + 1; RETURN}; IF inName THEN { <> c _ names.Fetch[namePos]; namePos _ namePos + 1; inName _ c # '\n; ropePos _ ropePos + 1; RETURN}; <> namePos _ newMap[nextIndex].index; newMap[nextIndex].index _ ropePos; nextIndex _ nextIndex + 1; inName _ TRUE; ENDLOOP; }; newMap _ pz.NEW[MapRep[map.len]]; newMap.prefix _ 0; IF prefixLen = 0 THEN <> FOR i: CARDINAL IN [0..map.len) DO entry: VersionMap.MapEntry _ map[i]; char: CHAR _ names.Fetch[entry.index]; SELECT char FROM '[ => { end: INT _ Rope.SkipTo[names, entry.index, ">\n"]; IF names.Fetch[end] = '> THEN { prefixLen _ end - entry.index + 1; prefix _ names.Flatten[entry.index, prefixLen]; EXIT}}; ENDCASE; ENDLOOP; <> newLen _ prefixLen+1; FOR i: CARDINAL IN [0..map.len) DO entry: VersionMap.MapEntry _ map[i]; pos: INT _ entry.index; char: CHAR _ names.Fetch[pos]; len: INT _ Rope.SkipTo[names, pos, "\n"] - pos + 1; SELECT char FROM '[ => IF Rope.Run[names, pos, prefix, 0, FALSE] = prefixLen THEN { <> newLen _ newLen + len - prefixLen; entry.index _ pos + prefixLen} ELSE newLen _ newLen + len; ENDCASE => newLen _ newLen + len; newMap[i] _ entry; ENDLOOP; <> newMap.names _ Rope.FromProc[newLen, getChar, maxPiece]; }; <<**** END EXPORTED PROCEDURES ****>> TestExtension: PROC [name: ROPE, extList: LIST OF ROPE] RETURNS [BOOL] = { exclPos: INT _ name.Size[]; pos: INT _ exclPos; WHILE pos > 0 DO c: CHAR _ name.Fetch[pos _ pos - 1]; SELECT c FROM '. => { len: INT _ exclPos-(pos _ pos + 1); FOR xl: LIST OF ROPE _ extList, xl.rest WHILE xl # NIL DO ext: ROPE _ xl.first; IF Rope.Run[name, pos, ext, 0, FALSE] = len THEN RETURN [TRUE]; ENDLOOP; RETURN [FALSE]}; '! => exclPos _ pos; ENDCASE; ENDLOOP; RETURN [FALSE]; }; CurrentTime: PROC RETURNS [ROPE] = TRUSTED { RETURN [Convert.ValueToRope[[time[Time.Current[]]]]]; }; MyStampToHex: PROC [stamp: MyStamp] RETURNS [ROPE] = { index: NAT _ 0; each: PROC RETURNS [c: CHAR] = { x: CARDINAL [0..15] _ hex[index]; IF x IN [0..9] THEN c _ '0 + x ELSE c _ 'A + (x-10); index _ index + 1; }; hex: MyStampAsHex _ LOOPHOLE[stamp]; RETURN [Rope.FromProc[12, each]]; }; PutStampAsHex: PROC [st: IO.STREAM, stamp: MyStamp] = { hex: MyStampAsHex _ LOOPHOLE[stamp]; FOR index: CARDINAL IN [0..12) DO x: CARDINAL [0..15] _ hex[index]; c: CHAR _ IF x IN [0..9] THEN '0 + x ELSE 'A + (x-10); st.PutChar[c]; ENDLOOP; }; IndexToShortName: PROC [map: Map, index: NameMapIndex] RETURNS [ROPE] = { <> names: ROPE _ map.names; end: INT _ names.SkipTo[index, "!\n"]; pos: INT _ end; WHILE pos > index DO SELECT names.Fetch[pos] FROM '/, '], '> => EXIT; ENDCASE; pos _ pos - 1; ENDLOOP; RETURN [names.Substr[pos, end-pos]]; }; Snooze: ENTRY PROC [lp: LONG POINTER TO BOOL] = TRUSTED { ENABLE UNWIND => NULL; UNTIL lp^ DO WAIT alarmClock; ENDLOOP; }; Awake: ENTRY PROC [lp: LONG POINTER TO BOOL] = TRUSTED { ENABLE UNWIND => NULL; lp^ _ TRUE; BROADCAST alarmClock; }; ClarkKent: PROC = TRUSTED { Superman: ENTRY PROC RETURNS [ok: BOOL _ TRUE] = TRUSTED { ENABLE UNWIND => NULL; IF reportInterval = 0 THEN {ok _ FALSE; reportProcess _ NIL} ELSE WAIT reportTime; }; last: ROPE _ NIL; lastTime: System.Microseconds _ System.PulsesToMicroseconds[System.GetClockPulses[]]; lastCount: INT _ fileCount _ 0; WHILE Superman[] DO IF last # report THEN { thisTime: System.Microseconds _ System.PulsesToMicroseconds[System.GetClockPulses[]]; currentFiles: INT _ fileCount; deltaFiles: INT _ currentFiles - lastCount; deltaMicros: System.Microseconds _ thisTime - lastTime; MessageWindow.Append[last _ report, TRUE]; IF deltaMicros > 1000 THEN { invSecs: REAL _ 1E6/deltaMicros; filesPerSec: REAL _ deltaFiles * invSecs; MessageWindow.Append[" (files: ", FALSE]; MessageWindow.Append[Convert.ValueToRope[[signed[currentFiles]]], FALSE]; MessageWindow.Append[", rate: ", FALSE]; MessageWindow.Append[Convert.ValueToRope[[real[filesPerSec, 2]]], FALSE]; MessageWindow.Append[")", FALSE]; lastTime _ thisTime; lastCount _ currentFiles; }; }; ENDLOOP; MessageWindow.Append[report _ NIL, TRUE]; }; BytesForEntries: PROC [n: INT] RETURNS [INT] = { words: INT _ SIZE[MapEntry]*n + SIZE[MapRep[0]] - SIZE[ROPE]; RETURN [words+words]}; EntrySortPred: PriorityQueue.SortPred = { <<[x: Item, y: Item, data: REF _ NIL] RETURNS [BOOL]>> xx: EntryRef _ NARROW[x]; yy: EntryRef _ NARROW[y]; IF xx.version.num # yy.version.num THEN RETURN [xx.version.num < yy.version.num]; IF xx.version.hi # yy.version.hi THEN RETURN [xx.version.hi < yy.version.hi]; RETURN [xx.version.lo < yy.version.lo]; }; VersionPred: PROC [version1,version2: VersionMap.MyStamp] RETURNS [BOOL] = { IF version1.num # version2.num THEN RETURN [version1.num < version2.num]; IF version1.hi # version2.hi THEN RETURN [version1.hi < version2.hi]; RETURN [version1.lo < version2.lo]; }; NewEntryRef: PROC [name: ROPE] RETURNS [EntryRef] = { report _ name; fileCount _ fileCount + 1; RETURN [qz.NEW[EntryRep _ [next: NIL, name: name, version: NullStamp]]]}; GenerateEntryListFromMap: PROC [map: Map] RETURNS [head: EntryRef, prefix: ROPE, count: NAT] = { <> count _ VersionMap.Length[map]; prefix _ NIL; head _ NIL; IF count > 0 THEN { stamp: TimeStamp.Stamp; name: ROPE; ent, tail: EntryRef; [stamp, name] _ map.Fetch[0]; head _ tail _ NewEntryRef[name]; tail.version _ LOOPHOLE[stamp]; FOR i: NAT IN [1..count) DO [stamp, name] _ map.Fetch[i]; ent _ NewEntryRef[name]; ent.version _ LOOPHOLE[stamp]; tail.next _ ent; tail _ ent; ENDLOOP; }; }; EntryListSize: PROC [elist: EntryRef] RETURNS [count: INT _ 0] = { FOR list: EntryRef _ elist, list.next UNTIL list = NIL DO count _ count + 1; ENDLOOP; }; CopyEntryList: PROC [elist: EntryRef] RETURNS [copy: EntryRef _ NIL] = { tail: EntryRef _ NIL; FOR list: EntryRef _ elist, list.next UNTIL list = NIL DO new: EntryRef _ NewEntryRef[list.name]; new.version _ list.version; IF tail = NIL THEN copy _ new ELSE tail.next _ new; tail _ new; ENDLOOP; }; MergeEntryLists: PROC [elist1,elist2: EntryRef, prefix1,prefix2: ROPE _ NIL] RETURNS [head: EntryRef _ NIL, prefix: ROPE _ NIL, count: NAT] = { <> count1: INT _ EntryListSize[elist1]; count2: INT _ EntryListSize[elist2]; tail, rem: EntryRef _ NIL; count _ count1 + count2; WHILE elist1 # NIL AND elist2 # NIL DO better: EntryRef _ NIL; IF VersionPred[elist1.version, elist2.version] THEN { <> better _ elist1; elist1 _ elist1.next; } ELSE { <> better _ elist2; elist2 _ elist2.next; }; IF tail = NIL THEN head _ better ELSE tail.next _ better; tail _ better; ENDLOOP; <> IF prefix1 # NIL AND (count1 > count2 OR prefix2 = NIL) THEN prefix _ prefix1 ELSE prefix _ prefix2; IF elist1 = NIL THEN rem _ elist1 ELSE rem _ elist2; IF tail = NIL THEN head _ rem ELSE tail.next _ rem; }; CountExplicitPrefixes: PROC [map: VersionMap.Map] RETURNS [count: INT _ 0] = { IF map # NIL THEN { names: ROPE _ map.names; FOR i: NAT IN [0..map.len) DO entry: VersionMap.MapEntry _ map[i]; c: CHAR _ names.Fetch[entry.index]; SELECT c FROM '/, '[ => count _ count + 1; ENDCASE; ENDLOOP; }; }; GenerateEntryListFromRemote: PROC [host,match: ROPE _ NIL, oldMap: Map _ NIL, filter: FilterProc _ NIL] RETURNS [head: EntryRef _ NIL, prefix: ROPE _ NIL, count: NAT _ 0] = TRUSTED { <> stringMatch: STRING _ [80]; stp: UnsafeSTP.Handle _ NIL; tab: SymTab.Ref _ NIL; prefixLen: INT _ 0; -- length of common prefix, including server skipLen: INT _ 0; -- length of common prefix, excluding server bracketedHost: ROPE _ Rope.Cat["[", host, "]"]; tail: EntryRef _ NIL; EachFile: UnsafeSTP.NoteFileProcType = TRUSTED { <> rname: ROPE _ ConvertUnsafe.ToRope[file]; new: EntryRef _ NIL; usePrefix: BOOL _ Rope.Run[rname, 0, match, 0, FALSE] = skipLen; continue _ yes; IF filter # NIL AND NOT filter[rname] THEN RETURN; -- user does not want this one IF usePrefix AND prefix # NIL THEN rname _ rname.Substr[skipLen] ELSE rname _ bracketedHost.Concat[rname]; IF LookupName[tab, rname] THEN RETURN; -- already seen new _ NewEntryRef[rname]; IF tail = NIL THEN head _ new ELSE tail.next _ new; tail _ new; count _ count + 1; SELECT TRUE FROM Rope.Match["*.bcd!*", rname, FALSE], Rope.Match["*.symbols!*", rname, FALSE] => { -- we will get the date later by accessing the first page of the bcd }; ENDCASE => { <> info: UnsafeSTP.FileInfo _ UnsafeSTP.GetFileInfo[stp]; IF info # NIL AND info.create # NIL THEN { s: LONG STRING _ info.create; len: NAT _ s.length; get: PROC [i: INT] RETURNS [CHAR] = TRUSTED { IF i IN [0..len) THEN RETURN [s[i]]; RETURN [0C]}; value: Convert.Value _ Convert.Parse [[get[get]], [time[Time.defaultTime]], 0, len].value; WITH v: value SELECT FROM time => new.version _ LOOPHOLE[TimeStamp.Stamp[0, 0, v.time], MyStamp] ENDCASE; }; }; }; [head, prefix, count] _ GenerateEntryListFromMap[oldMap]; tab _ GenSymTabFromVersionMap[oldMap]; skipLen _ match.Index[0, "*"]; IF oldMap = NIL THEN { prefix _ Rope.Cat[bracketedHost, match.Substr[0, skipLen]]; prefixLen _ prefix.Size[]; }; ConvertUnsafe.AppendRope[stringMatch, match]; <> stp _ STPInit[host]; <> UnsafeSTP.Enumerate[stp, stringMatch, EachFile]; <> UnsafeSTP.Close[stp ! ANY => CONTINUE]; }; GenSymTabFromVersionMap: PROC [map: Map] RETURNS [tab: SymTab.Ref _ NIL] = { count: NAT _ VersionMap.Length[map]; tab _ SymTab.Create[MAX[53, (count+5)/2], FALSE]; IF count > 0 THEN { FOR i: NAT IN [0..count) DO StoreName[tab, VersionMap.FetchName[map, i]]; ENDLOOP; }; }; StoreName: PROC [tab: SymTab.Ref, name: ROPE] = { [] _ SymTab.Insert[tab, name, name]; }; LookupName: PROC [tab: SymTab.Ref, name: ROPE] RETURNS [BOOL] = { x: REF _ SymTab.Fetch[tab, name].val; RETURN [x # NIL]; }; GetNextFromList: ENTRY PROC [gen: GenerateData] RETURNS [ent: EntryRef] = { IF gen.list # NIL AND gen.errors = 0 THEN { ent _ gen.list; gen.list _ ent.next; RETURN}; gen.countGoing _ gen.countGoing - 1; BROADCAST gen.done; }; WaitForGenDone: ENTRY PROC [gen: GenerateData] = { UNTIL gen.countGoing = 0 DO WAIT gen.done; ENDLOOP; }; GenerateMapFromEntryList: PROC [head: EntryRef, prefix: ROPE, count: NAT] RETURNS [map: Map] = TRUSTED { prefixLen: INT _ prefix.Size[]; skipPos: INT _ prefix.Index[0, "]"] + 1; skipLen: INT _ prefixLen - skipPos; host: ROPE _ IF prefixLen = 0 THEN NIL ELSE prefix.Flatten[1, skipPos-2]; directory: ROPE _ IF skipPos >= prefixLen THEN NIL ELSE prefix.Substr[skipPos]; list: EntryRef _ head; offset: INT _ 0; pq: PriorityQueue.Ref _ PriorityQueue.Predict[count, EntrySortPred]; gen: GenerateData _ pz.NEW[GenerateDataRep]; gen.host _ host; gen.directory _ directory; gen.list _ head; gen.pq _ pq; gen.countGoing _ outstandingRequests; <> IFSFile.Initialize[]; FOR i: NAT IN [0..outstandingRequests) DO Process.Detach[FORK VersionFromName[prefix, gen]]; ENDLOOP; map _ pz.NEW[MapRep[count]]; <> WaitForGenDone[gen]; <> FlushServerCache[]; IFSFile.Finalize[! ANY => CONTINUE]; IF gen.errors # 0 THEN RETURN [NIL]; <> map.prefix _ offset _ 0; -- offset of prefix start offset _ prefixLen + 1; -- byte offset of first name head _ list _ NIL; FOR i: NAT IN [0..count) DO each: EntryRef _ NARROW[pq.Remove[]]; each.next _ NIL; map[i] _ [each.version, offset]; IF head = NIL THEN head _ each ELSE list.next _ each; list _ each; offset _ offset + each.name.Size[] + 1; ENDLOOP; {-- make up the names string EachChar: PROC RETURNS [c: CHAR] = TRUSTED { IF thisIndex = thisLen THEN { <> c _ '\n; IF list = NIL THEN RETURN; thisRope _ list.name; thisIndex _ 0; thisLen _ thisRope.Size[]; list _ list.next; RETURN }; c _ thisRope.Fetch[thisIndex]; thisIndex _ thisIndex + 1; }; thisRope: ROPE _ prefix; thisIndex: INT _ 0; thisLen: INT _ prefixLen; list _ head; map.names _ Rope.FromProc[offset, EachChar, maxPiece]}; }; STPInit: PROC [server: ROPE] RETURNS [stp: UnsafeSTP.Handle] = TRUSTED { nameRope,passRope: ROPE _ NIL; nameString: STRING _ [80]; passString: STRING _ [80]; hostString: STRING _ [40]; herald: LONG STRING _ NIL; props: UnsafeSTP.DesiredProperties _ ALL[FALSE]; stp _ UnsafeSTP.Create[]; [nameRope,passRope] _ UserCredentials.GetUserCredentials[]; ConvertUnsafe.AppendRope[nameString, nameRope]; ConvertUnsafe.AppendRope[passString, passRope]; ConvertUnsafe.AppendRope[hostString, server]; UnsafeSTP.Login[stp, nameString, passString]; herald _ UnsafeSTP.Open[stp, hostString]; Heap.systemZone.FREE[@herald]; props[directory] _ TRUE; props[nameBody] _ TRUE; props[version] _ TRUE; props[createDate] _ TRUE; UnsafeSTP.SetDesiredProperties[stp, props]; }; IFSInit: ENTRY PROC [server: ROPE] RETURNS [ifs: IFSFile.FSInstance] = TRUSTED { ENABLE UNWIND => NULL; nameRope,passRope: ROPE _ NIL; nameString: STRING _ [80]; passString: STRING _ [80]; serverString: STRING _ [80]; FOR list: OpenServerList _ serverCache, list.rest UNTIL list = NIL DO IF Rope.Equal[list.first.name, server, FALSE] THEN { list.first.count _ list.first.count + 1; RETURN [list.first.ifs]}; ENDLOOP; [nameRope,passRope] _ UserCredentials.GetUserCredentials[]; ConvertUnsafe.AppendRope[serverString, server]; ConvertUnsafe.AppendRope[nameString, nameRope]; ConvertUnsafe.AppendRope[passString, passRope]; ifs _ IFSFile.Login [serverString, nameString, passString, nameString, passString]; serverCache _ CONS[[server, ifs, 1], serverCache]; }; IFSFinis: ENTRY PROC [ifs: IFSFile.FSInstance] = TRUSTED { ENABLE UNWIND => NULL; FOR list: OpenServerList _ serverCache, list.rest UNTIL list = NIL DO IF ifs = list.first.ifs THEN list.first.count _ list.first.count - 1; ENDLOOP; }; FlushServerCache: PROC = TRUSTED { lag: OpenServerList _ NIL; FOR list: OpenServerList _ serverCache, list.rest UNTIL list = NIL DO IF list.first.count <= 0 THEN { IFSFile.Logout[list.first.ifs ! ANY => CONTINUE]; IF lag = NIL THEN serverCache _ list.rest ELSE lag.rest _ list.rest}; ENDLOOP; }; OpenServerList: TYPE = LIST OF OpenServerRecord; OpenServerRecord: TYPE = RECORD [name: ROPE, ifs: IFSFile.FSInstance, count: INTEGER]; serverCache: OpenServerList _ NIL; WhenCompleted: IFSFile.Completer = TRUSTED { <<[arg: CompleterArg, outcome: Problem]>> <> IF outcome # ok THEN ERROR VersionNotAvailable; Awake[LOOPHOLE[arg, LONG POINTER TO BOOL]]; }; VersionFromName: PROC [prefix: ROPE, gen: GenerateData] = TRUSTED { <> <> ent: EntryRef _ NIL; WHILE (ent _ GetNextFromList[gen]) # NIL DO ENABLE ABORTED => {gen.errors _ gen.errors + 1; EXIT}; len: INT _ prefix.Size[]; IF len > 0 AND Rope.Run[prefix, 0, ent.name, 0, FALSE] = len THEN { -- now we can strip off the prefix! ent.name _ Rope.Substr[ent.name, len]; }; IF ent.version = NullStamp THEN { -- must fill in the version FillInBcdVersion[gen, ent]}; PQInsert[gen.pq, ent]; ENDLOOP; }; FillInBcdVersion: PROC [gen: GenerateData, ent: EntryRef] = TRUSTED { bcd: BcdDefs.BCD; ifs: IFSFile.FSInstance _ NIL; fh: IFSFile.FileHandle _ NIL; string: STRING _ [132]; ready: BOOL _ FALSE; lp: LONG POINTER TO BOOL _ @ready; fname: ROPE _ ent.name; fnSize: INT _ fname.Size[]; rbPos: INT _ fname.Index[0, "]"]; host: ROPE _ gen.host; {ENABLE UNWIND => { IF fh # NIL THEN IFSFile.Close[fh]; IF ifs # NIL THEN IFSFinis[ifs]}; IF rbPos < fnSize THEN { host _ fname.Substr[1, rbPos-1]; fname _ fname.Substr[rbPos+1]}; ifs _ IFSInit[host]; ConvertUnsafe.AppendRope[string, gen.directory]; ConvertUnsafe.AppendRope[string, fname]; fh _ IFSFile.Open[ifs, string ! ABORTED => GO TO abort; ANY => GO TO none]; IFSFile.StartRead [ fh, 0, Environment.bytesPerWord*SIZE[BcdDefs.BCD], @bcd, WhenCompleted, LOOPHOLE[lp, LONG UNSPECIFIED]]; report _ fname; fileCount _ fileCount + 1; Snooze[lp]; IFSFile.Close[fh ! ABORTED => GO TO abort; ANY => CONTINUE]; IFSFinis[ifs]}; ent.version _ LOOPHOLE[bcd.version, MyStamp]; EXITS none => {}; abort => ERROR ABORTED; }; RopeFromFileData: TYPE = RECORD [st: IO.STREAM, size: INT, offset: INT _ 0]; RopeFromFile: PROC [st: IO.STREAM, size: INT, offset: INT _ 0] RETURNS [ROPE] = { data: REF _ qz.NEW[RopeFromFileData _ [st, size, offset]]; RETURN [Rope.MakeRope[data, size, FetchFromFile]]; }; FetchFromFile: ENTRY Rope.FetchType = { <> ENABLE UNWIND => NULL; mine: REF RopeFromFileData _ NARROW[data]; IO.SetIndex[mine.st, index+mine.offset]; RETURN [IO.GetChar[mine.st]]; }; PQInsert: ENTRY PROC [pq: PriorityQueue.Ref, entry: EntryRef] = { <> pq.Insert[entry]; }; MyPut: PROC [st: IO.STREAM, r: ROPE] = { FOR i: INT IN [0..r.Size[]) DO IO.PutChar[st, r.Fetch[i]]; ENDLOOP; }; END.