RopeFileImpl.mesa
Russ Atkinson, August 26, 1983 4:02 pm
DIRECTORY
Commander USING [CommandProc, Register],
FileIO USING [CapabilityFromStream, Open, OpenFailed],
IO USING [Close, GetBlock, GetLength, PutRope, SetIndex, STREAM],
Process USING [Detach],
PutGet USING [FromRope],
RopeFile USING [],
Rope
USING
[Concat, FetchType, Flatten, Index, MakeRope, MapType, Match, ROPE, Size, SkipOver, SkipTo, Text],
RopeInline USING [NewText],
RTFilesExtra USING [DecFileUse, IncFileUse],
RTTypesBasic
USING
[CantEstablishFinalization, EstablishFinalization, FinalizationQueue, FQNext, NewFQ, ReEstablishFinalization],
TEditImpl USING [InitTEditDocument],
TiogaMenuOps USING [DefaultMenus],
TiogaOps USING [FindDef],
ViewerClasses USING [Viewer],
ViewerOps USING [OpenIcon],
ViewerTools USING [MakeNewTextViewer];
RopeFileImpl:
CEDAR MONITOR
LOCKS handle USING handle: Handle
IMPORTS
Commander, FileIO, IO, Process, PutGet, Rope, RopeInline, RTFilesExtra, RTTypesBasic, TEditImpl, TiogaMenuOps, TiogaOps, ViewerOps, ViewerTools
EXPORTS RopeFile
= BEGIN
T Y P E S
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
Handle: TYPE = REF RopeFileRep;
RopeFileRep:
TYPE =
MONITORED
RECORD [
lockChange: CONDITION,
next: Handle ← NIL,
file: STREAM ← NIL,
locked: BOOL ← FALSE,
bufSize: NAT ← 0,
offset: INT ← 0,
data: SEQUENCE buffers: NAT OF Buf];
Buf: TYPE = REF BufRec;
BufRec: TYPE = RECORD [start: INT, len: NAT, text: REF TEXT];
AcquireLock:
ENTRY
PROC [handle: Handle] = {
WHILE handle.locked DO WAIT handle.lockChange; ENDLOOP;
handle.locked ← TRUE;
};
ReleaseLock:
ENTRY
PROC [handle: Handle] = {
IF NOT handle.locked THEN ERROR;
handle.locked ← FALSE;
BROADCAST handle.lockChange;
};
Create:
PUBLIC
PROC
[name:
ROPE, start:
INT ← 0, len:
INT ←
LAST[
INT],
bufSize: INT ← 512, buffers: NAT ← 4,
useMap: BOOL ← TRUE, raw: BOOL ← TRUE]
RETURNS [rope: ROPE] = {
handle: Handle;
fileStream:
STREAM ← FileIO.Open[
fileName: name,
raw: raw,
streamBufferParms: [bufferSize: 2, bufferSwapUnitSize: 1]];
filesize: INT ← IO.GetLength[fileStream];
bestBuffers: INT;
IF start < 0 THEN start ← 0;
IF len <= 0 THEN RETURN [NIL];
IF len = LAST[INT] OR start+len > filesize THEN len ← filesize - start;
IO.SetIndex[fileStream, start];
IF buffers = 0 THEN buffers ← 1;
IF bufSize < 64 THEN bufSize ← 64;
IF bufSize > 16*1024 THEN bufSize ← 16*1024;
bestBuffers ← (len + bufSize - 1) / bufSize;
IF bestBuffers < buffers THEN buffers ← bestBuffers;
IF len < 16*1024
AND len <= (buffers * bufSize)
THEN
TRUSTED {
text: Rope.Text ← RopeInline.NewText[len];
[] ← IO.GetBlock[fileStream, LOOPHOLE[text], 0, len];
IO.Close[fileStream];
RETURN [text];
};
IF
NOT RTFilesExtra.IncFileUse[FileIO.CapabilityFromStream[fileStream],
FALSE]
THEN ERROR;
handle ← NEW[RopeFileRep[buffers]];
handle.bufSize ← bufSize;
handle.file ← fileStream;
handle.offset ← start;
FOR i:
NAT
IN [0..buffers)
DO
handle[i] ← NEW[BufRec ← [start: 0, len: 0, text: NEW[TEXT[bufSize]]]];
ENDLOOP;
rope ← Rope.MakeRope[handle, len, MyFetch, IF useMap THEN MyMap ELSE NIL];
splice this ropeFile onto the global list
AcquireLock[dummyRopeFile];
handle.next ← dummyRopeFile.next;
dummyRopeFile.next ← handle;
ReleaseLock[dummyRopeFile];
};
MyFetch: Rope.FetchType = {
[data: REF, index: INT] RETURNS [CHAR]
handle: Handle = NARROW[data];
last: NAT = handle.buffers-1;
c: CHAR;
IF handle.file = NIL THEN ERROR;
AcquireLock[handle];
index ← index + handle.offset;
FOR i:
NAT
IN [0..last]
DO
buf: Buf ← handle[i];
delta: INT ← index - buf.start;
SELECT
TRUE
FROM
delta >= 0
AND delta < buf.len => {
this buffer contains the character range
};
i < last =>
we have not come to the last buffer
LOOP;
ENDCASE => {
need to swap in new buffer
length: NAT ← handle.bufSize;
start: INT = index - (delta ← index MOD length);
IO.SetIndex[handle.file, start];
length ← IO.GetBlock[handle.file, buf.text, 0, length];
IF length = 0 THEN {ReleaseLock[handle]; ERROR};
buf.start ← start;
buf.len ← length;
};
IF i # 0
THEN {
swap to speed searches
handle[i] ← handle[i-1];
handle[i-1] ← buf;
};
c ← buf.text[delta];
EXIT;
ENDLOOP;
ReleaseLock[handle];
RETURN [c];
};
MyMap: Rope.MapType =
TRUSTED {
[base: REF, start, len: INT, action: ActionType] RETURNS [quit: BOOL ← FALSE]
handle: Handle ← NARROW[base];
last: NAT = handle.buffers-1;
IF handle.file = NIL THEN ERROR;
start ← start + handle.offset;
WHILE len > 0
DO
buf: Buf ← NIL;
delta: INT ← 0;
subLen: NAT = 32;
string: STRING ← [subLen];
pos: NAT ← delta;
spos: NAT ← 0;
chars: NAT ← 0;
AcquireLock[handle];
FOR i:
NAT
IN [0..last]
DO
buf ← handle[i];
delta ← start - buf.start;
SELECT
TRUE
FROM
delta >= 0
AND delta < buf.len => {
this buffer contains the character range
};
i < last =>
we have not come to the last buffer
LOOP;
ENDCASE => {
need to swap in new buffer
length: NAT ← handle.bufSize;
bpos: INT = start - (delta ← start MOD length);
IO.SetIndex[handle.file, bpos];
length ← IO.GetBlock[handle.file, buf.text, 0, length];
IF length = 0 THEN {ReleaseLock[handle]; ERROR};
buf.start ← bpos;
buf.len ← length;
};
IF i # 0
THEN {
swap to speed searches
handle[i] ← handle[i-1];
handle[i-1] ← buf;
};
EXIT;
ENDLOOP;
At this point we have found a good buffer, so we snatch a sub-buffer from it
pos ← delta;
chars ← MIN[buf.len-pos, subLen];
IF len < chars THEN chars ← len;
FOR j:
NAT
IN [0..chars)
DO
string[j] ← buf.text[pos+j];
ENDLOOP;
we will not touch the buffer until the next time around (if any)
ReleaseLock[handle];
Now yield all of the chars in the sub-buffer
FOR j:
NAT
IN [0..chars)
DO
IF action[string[j]] THEN RETURN [TRUE];
ENDLOOP;
len ← len - chars;
start ← start + chars;
ENDLOOP;
RETURN [FALSE];
};
SubstrCreate:
PUBLIC
PROC
[name: ROPE, start: INT ← 0, len: INT ← LAST[INT]] RETURNS [rope: ROPE] = {
RETURN [Create[name, start, len]];
};
SimpleCreate:
PUBLIC
PROC [name:
ROPE]
RETURNS [rope:
ROPE] = {
RETURN [Create[name]];
};
extensionList: LIST OF ROPE ← LIST["", ".mesa", ".tioga", ".df"];
OpenHuge: Commander.CommandProc = {
line: ROPE ← cmd.commandLine;
last: INT ← 0;
out: STREAM ← cmd.out;
DO
start: INT ← line.SkipOver[last, " ,;\t\n"];
next: INT ← line.SkipTo[start, " ,;\t\n"];
name: ROPE ← line.Flatten[start, next-start];
fullName: ROPE ← NIL;
rope: ROPE ← NIL;
ext: ROPE ← NIL;
IF name.Size[] = 0 THEN RETURN;
out.PutRope["Opening "];
out.PutRope[name];
out.PutRope["..."];
last ← next;
IF Rope.Match["*.*", name]
THEN {
rope ← Create[
name: fullName ← name
! FileIO.OpenFailed => CONTINUE];
IF rope =
NIL
THEN {
pos: INT = Rope.Index[name, 0, "."];
prefix: ROPE = Rope.Flatten[name, 0, pos];
ext ← Rope.Flatten[name, pos+1];
IF ext.Size[] # 0
THEN
rope ← Create[
name: fullName ← prefix.Concat[".mesa"]
! FileIO.OpenFailed => CONTINUE];
};
}
ELSE {
FOR list:
LIST
OF
ROPE ← extensionList, list.rest
WHILE list #
NIL
DO
rope ← Create[
name: fullName ← name.Concat[list.first]
! FileIO.OpenFailed => LOOP];
IF rope # NIL THEN EXIT;
ENDLOOP;
};
IF rope #
NIL
THEN {
v: ViewerClasses.Viewer ← ViewerTools.MakeNewTextViewer[
info: [name: fullName, column: left, iconic: TRUE],
paint: FALSE];
v.data ← PutGet.FromRope[rope];
v.file ← name;
TEditImpl.InitTEditDocument[v];
TiogaMenuOps.DefaultMenus[v];
IF ext.Size[] # 0 THEN [] ← TiogaOps.FindDef[v, ext];
ViewerOps.OpenIcon[v];
out.PutRope["done.\n"];
}
ELSE {
out.PutRope["failed!\n"];
};
ENDLOOP;
};
RopeFileFinalizer:
PROC = {
This procedure closes the files associated with rope files as part of finalizing the ropes in question. We have to be quite careful to lock things properly, which is why there is a dummyRopeFile. The dummyRopeFile is used to hold on to the ropes created by Create, and to synchronize insertinon of new rope files and deletion of old rope files. Remember: paranoia pays!
DO
ref: REF ← RTTypesBasic.FQNext[fq];
WITH ref
SELECT
FROM
rf: Handle => {
note: always use the following order in acquiring/releasing locks
lag: Handle ← dummyRopeFile;
found: BOOL ← FALSE;
IF rf = dummyRopeFile THEN LOOP;
AcquireLock[dummyRopeFile];
AcquireLock[rf];
FOR each: Handle ← dummyRopeFile.next, each.next
WHILE each #
NIL
DO
IF each = rf
THEN {
splice out this file
lag.next ← each.next;
found ← TRUE;
EXIT;
};
lag ← each;
ENDLOOP;
IF found
AND rf.file #
NIL
THEN {
Only close the file if it is on the list AND has been opened. This makes us insensitive to multiple occurences of rope files on the finalization queue.
ENABLE UNWIND => {ReleaseLock[rf]; ReleaseLock[dummyRopeFile]};
RTFilesExtra.DecFileUse[FileIO.CapabilityFromStream[rf.file]];
IO.Close[rf.file];
rf.file ← NIL;
};
ReleaseLock[rf];
ReleaseLock[dummyRopeFile];
};
ENDCASE;
ENDLOOP;
};
dummyRopeFile: Handle ← NIL; -- for synchronization
fq: RTTypesBasic.FinalizationQueue = RTTypesBasic.NewFQ[32]; -- for finalization
RTTypesBasic.EstablishFinalization[
CODE[RopeFileRep], 1, fq
! RTTypesBasic.CantEstablishFinalization =>
RTTypesBasic.ReEstablishFinalization[CODE[RopeFileRep], 1, fq]];
dummyRopeFile ← NEW[RopeFileRep];
TRUSTED {
Process.Detach[FORK RopeFileFinalizer[]];
};
Commander.Register["openHuge", OpenHuge, "Opens huge files in a somewhat unsafe fashion."];