-- FileWriterImpl.Mesa
-- written by Paxton. March 1981
-- last written by McGregor. September 10, 1982 1:15 pm
Last Edited by: Maxwell, January 5, 1983 12:50 pm
DIRECTORY
FileWriter,
IO,
FileIO,
FileOps,
File,
MonitoredQueue,
Rope,
RopeReader,
TextNode;
FileWriterImpl: CEDAR MONITOR
IMPORTS IO, FileIO, FileWriter, MonitoredQueue, Rope, TextNode
EXPORTS FileWriter
SHARES FileWriter =
BEGIN OPEN FileWriter;
ROPE: TYPE = Rope.ROPE;
OpenC: PUBLIC PROC [capability: File.Capability,
start: Offset ← 0, makeControl: BOOLEANTRUE]
RETURNS [control,comment,data: Ref] = {
stream: IO.Handle = FileIO.StreamFromCapability[capability,overwrite];
IO.SetLength[stream,start];
IO.SetIndex[stream,start];
[control,comment,data] ← FinishOpen[stream,FALSE,makeControl,TRUE] };
ToRope: PUBLIC PROC [makeControl: BOOLEANTRUE]
RETURNS [control,comment,data: Ref] = {
[control,comment,data] ← FinishOpen[NIL,TRUE,makeControl,FALSE] };
ToStream: PUBLIC PROC [stream: IO.Handle, makeControl: BOOLEANTRUE]
RETURNS [control,comment,data: Ref] = {
[control,comment,data] ← FinishOpen[stream,FALSE,makeControl,FALSE] };
FinishOpen: PROC
[stream: IO.Handle, toRope: BOOLEANFALSE, makeControl, closeStream: BOOLEANTRUE]
RETURNS [control,comment,data: Ref] = {
MakeOne: PROC [kind: OfWriter] RETURNS [writer: Ref] = {
writer ← GetFileWriter[];
writer.kind ← kind;
writer.block ← GetBLOCK[];
writer.stream ← stream;
writer.toRope ← toRope;
writer.closeStream ← closeStream };
IF makeControl THEN { -- make comment and control writers
control ← MakeOne[control];
FOR i:NAT IN [0..FileOps.fileIdSize) DO
WriteChar[FileOps.controlHeaderId[i],control]; ENDLOOP;
THROUGH [0..3] DO WriteChar[0C,control]; ENDLOOP; -- for length
comment ← MakeOne[comment];
FOR i:NAT IN [0..FileOps.fileIdSize) DO
WriteChar[FileOps.commentHeaderId[i],comment]; ENDLOOP;
THROUGH [0..3] DO WriteChar[0C,comment]; ENDLOOP; -- for length
};
data ← MakeOne[data];
IF ~toRope THEN { -- fork process to write data as it is produced
data.blockQueue ← GetMQ[];
data.consumer ← FORK Consumer[data] }};
q1, q2: MonitoredQueue.MQ;
GetMQ: ENTRY PROC RETURNS [q: MonitoredQueue.MQ] = {
ENABLE UNWIND => NULL;
IF q1 # NIL THEN { q ← q1; q1 ← NIL }
ELSE IF q2 # NIL THEN { q ← q2; q2 ← NIL }
ELSE q ← MonitoredQueue.Create[TextNode.pZone] };
FreeMQ: ENTRY PROC [q: MonitoredQueue.MQ] = {
ENABLE UNWIND => NULL;
IF q=NIL THEN RETURN;
MonitoredQueue.Reset[q];
IF q1 = NIL THEN q1 ← q
ELSE IF q2 = NIL THEN q2 ← q };
fw1, fw2: Ref;
GetFileWriter: ENTRY PROC RETURNS [fw: Ref] = {
ENABLE UNWIND => NULL;
IF fw1 # NIL THEN { fw ← fw1; fw1 ← NIL }
ELSE IF fw2 # NIL THEN { fw ← fw2; fw2 ← NIL }
ELSE fw ← TextNode.pZone.NEW[FileWriterBody];
IF fw.kind # unused THEN ERROR };
FreeFileWriter: ENTRY PROC [fw: Ref] = {
ENABLE UNWIND => NULL;
fw.block ← NIL;
fw.blockList ← NIL;
fw.blockQueue ← NIL;
fw.blockCount ← 0;
fw.stream ← NIL;
fw.kind ← unused;
IF fw1 = NIL THEN fw1 ← fw
ELSE IF fw2 = NIL THEN fw2 ← fw };
blks: NAT = 8;
BlockArray: TYPE = ARRAY [0..blks) OF REF TEXT;
blocks: REF BlockArray ← NEW[BlockArray];
GetBLOCK: ENTRY PROC RETURNS [b: REF TEXT] = {
ENABLE UNWIND => NULL;
FOR i:NAT IN [0..blks) DO
IF blocks[i] # NIL THEN { b ← blocks[i]; blocks[i] ← NIL; EXIT };
ENDLOOP;
IF b=NIL THEN b ← TextNode.qZone.NEW[TEXT[blockSize]];
b.length ← 0 };
FreeBLOCK: ENTRY PROC [b: REF TEXT] = {
ENABLE UNWIND => NULL;
FOR i:NAT IN [0..blks) DO
IF blocks[i] = NIL THEN { blocks[i] ← b; RETURN };
ENDLOOP };
BumpWriter: PUBLIC PROC [writer: Ref] RETURNS [block: REF TEXT] = {
writer.blockCount ← writer.blockCount+1; -- number added to queue/list
IF writer.kind=data AND ~writer.toRope THEN MonitoredQueue.Add[writer.block, writer.blockQueue]
ELSE writer.blockList ← TextNode.pZone.NEW[BlockListBody ← [writer.block, writer.blockList]];
writer.block ← block ← GetBLOCK[] };
Consumer: PROC [data: Ref] = { OPEN MonitoredQueue;
queue: MQ ← data.blockQueue;
stream: IO.Handle ← data.stream;
DO x: REF ANY ← Remove[queue ! EndOfQueue => EXIT];
WITH x SELECT FROM
xx: REF TEXT => { IO.PutBlock[stream,xx]; FreeBLOCK[xx] };
ENDCASE => ERROR;
ENDLOOP };
Close: PUBLIC PROC [control,comment,data: Ref, textOnly: BOOLEAN]
RETURNS [dataLen, count: Offset, output: ROPE] = {
deltaBytes: PACKED ARRAY [0..3] OF CHARACTER;
commentStart, commentLen, controlLen: Offset ← 0;
stream: IO.Handle ← data.stream;
len, controlBlocks, commentBlocks, dataBlocks: NAT ← 0;
toRope: BOOLEAN ← data.toRope;
controlList,commentList,dataList: BlockList;
controlBlock,commentBlock,dataBlock: REF TEXT;
Reverse: PROC [lst: BlockList] RETURNS [prev: BlockList] = {
next: BlockList;
UNTIL lst=NIL DO
next ← lst.next; lst.next ← prev; prev ← lst; lst ← next; ENDLOOP };
LastBlock: PROC [lst: BlockList] RETURNS [block: REF TEXT] = {
FOR l: BlockList ← lst, l.next DO
IF l.next=NIL THEN RETURN [l.block];
ENDLOOP };
MakeRope: PROC [first, num: NAT] RETURNS [ROPE] = {
half: NAT;
SELECT num FROM
0 => RETURN [NIL];
1 => RETURN [Rope.FromRefText[GetChars[first]]];
ENDCASE;
half ← num/2;
RETURN [Rope.Concat[MakeRope[first,half],MakeRope[first+half,num-half]]] };
FinishWriter: PROC [writer: Ref] RETURNS [list: BlockList, block: REF TEXT] = {
list ← Reverse[writer.blockList]; block ← writer.block; FreeFileWriter[writer] };
GetChars: PROC [num: NAT] RETURNS [chars: REF TEXT] = {
GetIt: PROC [blocks: NAT, list: BlockList, block: REF TEXT]
RETURNS [ok: BOOLEAN] = {
IF num NOT IN [0..blocks) THEN RETURN [FALSE];
FOR lst: BlockList ← list, lst.next UNTIL lst=NIL DO
IF num = 0 THEN { chars ← lst.block; RETURN [TRUE] };
num ← num-1; ENDLOOP;
IF num = 0 THEN { chars ← block; RETURN [TRUE] };
ERROR };
IF GetIt[dataBlocks,dataList,dataBlock] THEN RETURN [chars];
num ← num-dataBlocks;
IF GetIt[commentBlocks,commentList,commentBlock] THEN RETURN [chars];
num ← num-commentBlocks;
IF GetIt[controlBlocks,controlList,controlBlock] THEN RETURN [chars];
ERROR };
dataBlocks ← data.blockCount;
commentStart ← dataBlocks*LONG[blockSize];
IF (len�ta.block.length) MOD 2 = 1 THEN { WriteChar[0C,data]; len ← len+1 };
commentStart ← commentStart+len;
dataBlocks ← dataBlocks+1;
IF control # NIL THEN {
IF ~textOnly THEN {
chars: REF TEXT;
WriteControlInt: PROC [int: INT] = {
deltaBytes ← LOOPHOLE[int];
FOR i:NAT IN [0..3] DO
WriteChar[deltaBytes[i],control];
ENDLOOP };
Check: PROC [writer: Ref, kind: OfWriter] = {
IF writer.kind # kind THEN ERROR;
IF writer.toRope # toRope THEN ERROR;
IF writer.closeStream # data.closeStream THEN ERROR;
IF writer.stream # data.stream THEN ERROR };
Count: PROC [list: BlockList, writer: Ref, extra: Offset ← 0]
RETURNS [blocks: NAT, len: Offset] = {
blocks ← writer.blockCount;
len ← LONG[blocks]*blockSize+writer.block.length+extra;
blocks ← blocks+1;
deltaBytes ← LOOPHOLE[len];
chars ← IF list=NIL THEN writer.block ELSE LastBlock[list];
FOR i:NAT IN [0..3] DO chars[FileOps.fileIdSize+i] ← deltaBytes[i]; ENDLOOP };
Check[comment,comment];
Check[control,control];
-- this is the place to write out the file props, if any
FOR i:NAT IN [0..FileOps.fileIdSize) DO -- write end trailer
WriteChar[FileOps.controlTrailerId[i],control]; ENDLOOP;
WriteControlInt[0]; -- <file-props-length>
WriteControlInt[commentStart];
IF comment.block.length MOD 2 = 1 THEN WriteChar[0C,comment]; -- fill word
[commentBlocks,commentLen] ← Count[comment.blockList,comment];
[controlBlocks,controlLen] ← Count[control.blockList,control,4]; -- add 4 for final file length
WriteControlInt[commentStart+commentLen+controlLen];
};
[controlList,controlBlock] ← FinishWriter[control];
[commentList,commentBlock] ← FinishWriter[comment] };
IF data.kind # data THEN ERROR;
dataBlock ← data.block;
IF toRope THEN {
dataList ← Reverse[data.blockList];
output ← MakeRope[0,dataBlocks+commentBlocks+controlBlocks] } -- return a balanced rope
ELSE {
MonitoredQueue.Close[data.blockQueue];
TRUSTED {JOIN data.consumer};
FreeMQ[data.blockQueue];
IO.PutBlock[stream,dataBlock];
FreeBLOCK[dataBlock];
IF control # NIL THEN {
DoBlocks: PROC [list: BlockList, block: REF TEXT] = {
FOR lst:BlockList ← list, lst.next UNTIL lst=NIL DO
IF ~textOnly THEN IO.PutBlock[stream,lst.block];
FreeBLOCK[lst.block];
ENDLOOP;
IF ~textOnly THEN IO.PutBlock[stream,block];
FreeBLOCK[block] };
DoBlocks[commentList,commentBlock];
DoBlocks[controlList,controlBlock] }};
count ← commentStart+commentLen+controlLen;
dataLen ← commentStart;
IF data.closeStream THEN IO.Close[stream];
FreeFileWriter[data] };
Start: PUBLIC PROC = {
};
END.