-- Copyright (C) 1983, 1984 by Xerox Corporation. All rights reserved. -- BWSMStreamImpl.mesa Last edited by -- PXK , 27-Jan-84 13:54:43 -- LXR , 9-Jan-84 12:19:22 -- RXJ , 21-Dec-83 9:19:32 -- DWE , 28-Jul-83 14:12:43 -- BGY , 15-Aug-84 14:08:32 -- MEW 23-May-86 10:23:02 DIRECTORY ByteBlt USING [ByteBlt], Environment USING [Block, Byte, bytesPerPage, PageCount], File USING [File, GetSize, nullFile, PageCount, PageNumber], Heap USING [CreateUniform], Inline USING [HighHalf, LongCOPY, LongDiv, LongDivMod, LongMult, LowHalf], MFile USING [ Access, Acquire, ByteCount, dontCare, Error, GetAccess, GetLength, Handle, Object, PleaseReleaseProc, Release, ReleaseChoice, ReleaseData, SetAccess, SetReleaseData, SetLength, Type], MFileOps, MStream USING [ErrorCode, ReleaseData, Handle, PleaseReleaseProc], BWSMStreamOps USING [Handle, mStreamPassword, MyHandle, Object, StreamObject], MUsage USING [mFileLocalStream], Space USING [ Allocate, Deallocate, ForceOut, Interval, MakeReadOnly, MakeWritable, MapAt, UnmapAt], SpecialMFile USING [GetCapaWithAccess], Stream USING [ Block, Byte, CompletionCode, defaultInputOptions, EndOfStream, GetPositionProcedure, GetProcedure, Handle, InputOptions, Object, PutByteProcedure, PutProcedure, PutWordProcedure, SendAttentionProcedure, SendNowProcedure, SetPositionProcedure, SetSSTProcedure, WaitAttentionProcedure]; BWSMStreamImpl: MONITOR LOCKS streamHandle.lock USING streamHandle: Handle IMPORTS ByteBlt, File, Heap, Inline, MFile, MFileOps, Space, SpecialMFile, Stream EXPORTS MStream, BWSMStreamOps SHARES File = BEGIN -- Types: FHandle: TYPE = MFile.Handle; Handle: TYPE = BWSMStreamOps.Handle; MyHandle: TYPE = BWSMStreamOps.MyHandle; StreamObject: TYPE = BWSMStreamOps.StreamObject; Object: TYPE = BWSMStreamOps.Object; heap: UNCOUNTED ZONE = Heap.CreateUniform[ initial: 2, objectSize: SIZE[StreamObject]]; ReleaseData: TYPE = MStream.ReleaseData; leaderPages: CARDINAL = 1; Error: PUBLIC ERROR [ stream: Stream.Handle, code: MStream.ErrorCode] = CODE; -- Operations: bytesPerPage: CARDINAL = Environment.bytesPerPage; ConvertHandle: PUBLIC PROCEDURE [stream: Stream.Handle] RETURNS [MyHandle] = { RETURN[ConvertHandleInline[stream]]}; ConvertHandleInline: PROCEDURE [stream: Stream.Handle] RETURNS [MyHandle] = INLINE { RETURN[@LOOPHOLE[stream, Handle].object]}; ValidateHandle: PROCEDURE [stream: Stream.Handle] RETURNS [h: MyHandle] = { IF stream = NIL THEN ERROR Error[stream, invalidHandle]; h ¬ ConvertHandleInline[stream]; IF stream.getPosition # GetIndex OR h.password # BWSMStreamOps.mStreamPassword THEN ERROR Error[stream, invalidHandle]}; IsIt: PUBLIC PROCEDURE [stream: Stream.Handle] RETURNS [isit: BOOLEAN ¬ TRUE] = { [] ¬ ValidateHandle[stream ! Error => {isit ¬ FALSE; CONTINUE}]}; Create: PUBLIC PROCEDURE [ file: FHandle, release: ReleaseData, options: Stream.InputOptions ¬ Stream.defaultInputOptions, streamBase: File.PageNumber ¬ 0] RETURNS [stream: Stream.Handle] = BEGIN h: Handle; capability: File.File ¬ File.nullFile; read, write, shrink: BOOLEAN ¬ FALSE; filePages: File.PageCount; length: LONG CARDINAL; setLength: BOOLEAN ¬ FALSE; length ¬ MFile.GetLength[file! MFile.Error => Error[NIL, invalidFile]]; capability ¬ SpecialMFile.GetCapaWithAccess[file ! MFile.Error => Error[NIL, invalidFile]]; filePages ¬ File.GetSize[capability] - (leaderPages + streamBase); IF Inline.HighHalf[filePages] # 0 THEN ERROR Error[NIL, fileTooLong]; SELECT MFile.GetAccess[file] FROM writeOnly => write ¬ shrink ¬ TRUE; log => setLength ¬ write ¬ read ¬ TRUE; readWrite => write ¬ read ¬ shrink ¬ TRUE; readOnly => read ¬ TRUE; ENDCASE; h ¬ heap.NEW[StreamObject ¬ [ stream: [ getByte: GetByte, putByte: PutByte, getWord: GetWord, putWord: PutWord, get: GetBlock, put: PutBlock, delete: Delete, setSST: SetSSTNop, sendAttention: SendAttentionNop, waitAttention: WaitAttentionNop, getPosition: GetIndex, setPosition: SetIndex, sendNow: SendNow, options: options, clientData: NIL], object: [ read: read, write: write, shrink: shrink, filePages: CARDINAL[filePages], length: length, release: release, streamBase: streamBase, file: file, capa: capability, bufferSpacePages: localBufferPages]] ]; IF ~h.object.read THEN { h.stream.getByte ¬ h.stream.getWord ¬ GetError; h.stream.get ¬ GetBlockError}; IF h.object.write THEN { IF h.object.filePages = 0 THEN { MFileOps.SetMinimumFileDataPages[h.object.file, 1]; h.object.filePages ¬ 1}} ELSE { h.stream.putByte ¬ LOOPHOLE[PutError]; h.stream.putWord ¬ PutError; h.stream.put ¬ PutBlockError; IF h.object.filePages < h.object.bufferSpacePages THEN h.object.bufferSpacePages ¬ h.object.filePages}; IF setLength THEN {MFile.SetLength[h.object.file, 0]; h.object.length ¬ 0}; SetupBuffer[@h.object, 0]; MFile.SetReleaseData[file, [StreamRelease, h]]; RETURN[@h.stream]; END; StreamRelease: MFile.PleaseReleaseProc = { h: MyHandle = ConvertHandleInline[instanceData]; streamHandle: Handle; c: MFile.ReleaseChoice; [c, streamHandle] ¬ RealRelease[file, h, instanceData]; IF c = goAhead THEN InternalDelete[h, @streamHandle.stream, h.file]; RETURN[c]}; RealRelease: ENTRY PROCEDURE [ file: MFile.Handle, h: MyHandle, streamHandle: Handle] RETURNS [c: MFile.ReleaseChoice, s: Handle] = { ENABLE UNWIND => NULL; IF h.release.proc = NIL THEN RETURN[no, streamHandle]; IF h.file = NIL THEN RETURN[later, streamHandle]; c ¬ h.release.proc[@streamHandle.stream, h.release.clientInstanceData]; RETURN[c, streamHandle]}; DontRelease: PUBLIC MStream.PleaseReleaseProc = {RETURN[no]}; MakeFile: PROCEDURE [ name: LONG STRING, access: MFile.Access, type: MFile.Type ¬ unknown] RETURNS [file: MFile.Handle] = { file ¬ MFile.Acquire[name, access, [], IF access = readOnly THEN FALSE ELSE TRUE, MFile.dontCare, type ! MFile.Error => Error[NIL, fileNotAvailable]]}; NotImplemented: ERROR = CODE; Log: PUBLIC PROCEDURE [name: LONG STRING, release: ReleaseData] RETURNS [Stream.Handle] = {ERROR NotImplemented}; ReadOnly: PUBLIC PROCEDURE [name: LONG STRING, release: ReleaseData] RETURNS [Stream.Handle] = { RETURN[Create[MakeFile[name, readOnly], release]]}; ReadWrite: PUBLIC PROCEDURE [ name: LONG STRING, release: ReleaseData, type: MFile.Type ¬ unknown] RETURNS [Stream.Handle] = { RETURN[Create[MakeFile[name, readWrite, type], release]]}; WriteOnly: PUBLIC PROCEDURE [ name: LONG STRING, release: ReleaseData, type: MFile.Type ¬ unknown] RETURNS [Stream.Handle] = { RETURN[Create[MakeFile[name, writeOnly, type], release]]}; GetReleaseData: PUBLIC PROCEDURE [stream: MStream.Handle] RETURNS [ReleaseData] = {RETURN[RealGetReleaseData[ValidateHandle[stream], LOOPHOLE[stream]]]}; RealGetReleaseData: ENTRY PROCEDURE [h: MyHandle, streamHandle: Handle] RETURNS [ReleaseData] = {RETURN[h.release]}; SetReleaseData: PUBLIC PROCEDURE [ stream: MStream.Handle, release: ReleaseData] = { h: MyHandle = ValidateHandle[stream]; RealSetReleaseData[h, LOOPHOLE[stream], release]}; RealSetReleaseData: ENTRY PROCEDURE [h: MyHandle, streamHandle: Handle, release: ReleaseData] = {h.release ¬ release}; BackupLog: PUBLIC PROCEDURE [stream: MStream.Handle, count: MFile.ByteCount] RETURNS [backedUp: MFile.ByteCount] = {ERROR NotImplemented}; SetLogReadLength: PUBLIC PROCEDURE [ stream: MStream.Handle, position: LONG INTEGER] = { ERROR NotImplemented}; SetAccess: PUBLIC PROCEDURE [stream: MStream.Handle, access: MFile.Access] = BEGIN h: MyHandle = ValidateHandle[stream]; SELECT access FROM writeOnly, log, readWrite, readOnly => NULL; ENDCASE => Error[stream, invalidOperation]; Cleanup[h, h.file]; MFile.SetAccess[h.file, access ! MFile.Error => Error[stream, fileNotAvailable]]; -- catch errors? h.capa ¬ SpecialMFile.GetCapaWithAccess[h.file ! MFile.Error => Error[stream, invalidFile]]; h.read ¬ access # writeOnly AND access # log; h.write ¬ access # readOnly; IF h.read THEN { stream.getByte ¬ GetByte; stream.getWord ¬ GetWord; stream.get ¬ GetBlock} ELSE { stream.getByte ¬ GetError; stream.getWord ¬ GetError; stream.get ¬ GetBlockError}; IF h.write THEN { stream.putByte ¬ PutByte; stream.putWord ¬ PutWord; stream.put ¬ PutBlock} ELSE { stream.putByte ¬ LOOPHOLE[PutError]; stream.putWord ¬ PutError; stream.put ¬ PutBlockError}; SELECT access FROM readOnly => Space.MakeReadOnly[h.bufferSpace]; ENDCASE => { desiredPages: CARDINAL = localBufferPages; IF h.bufferSpacePages # desiredPages THEN { IF h.bufferSpacePages # 0 THEN { [] ¬ Space.UnmapAt[h.bufferSpace.pointer]; Space.Deallocate[h.bufferSpace]}; h.buffer ¬ NIL; h.bufferSpacePages ¬ desiredPages; SetupBuffer[h, h.firstFilePageInBuffer]} ELSE Space.MakeWritable[h.bufferSpace]}; END; -- Generic Stream procedures; assume handle ok GetByte: PROCEDURE [stream: Stream.Handle] RETURNS [byte: UNSPECIFIED[0..256)] = BEGIN h: MyHandle ¬ ConvertHandleInline[stream]; WHILE h.index = h.dataBytesInBuffer DO IF h.eofInBuffer THEN {SIGNAL Stream.EndOfStream[0]; RETURN[0]}; AdvanceBuffer[h]; ENDLOOP; byte ¬ h.buffer[h.index]; h.index ¬ h.index + 1; RETURN END; GetIndex: Stream.GetPositionProcedure = { h: MyHandle = ConvertHandleInline[sH]; position ¬ Inline.LongMult[h.firstFilePageInBuffer, bytesPerPage] + h.index}; GetWord: PROCEDURE [stream: Stream.Handle] RETURNS [UNSPECIFIED] = BEGIN t: UNSPECIFIED = CARDINAL[GetByte[stream]] * 256; RETURN[t + GetByte[stream]] END; GetError: PROCEDURE [stream: Stream.Handle] RETURNS [UNSPECIFIED] = { ERROR Error[stream, invalidOperation]}; PutByte: Stream.PutByteProcedure = { h: MyHandle = ConvertHandleInline[sH]; WHILE h.index = h.dataBytesInBuffer DO IF h.dataBytesInBuffer = h.bufferBytes THEN AdvanceBuffer[h] ELSE BEGIN h.dataBytesInBuffer ¬ h.dataBytesInBuffer + 1; h.length ¬ h.length + 1; h.lengthChanged ¬ TRUE; END; ENDLOOP; h.buffer[h.index] ¬ byte; h.index ¬ h.index + 1}; PutWord: Stream.PutWordProcedure = { PutByte[sH, CARDINAL[word] / 256]; PutByte[sH, CARDINAL[word] MOD 256]}; PutError: Stream.PutWordProcedure = {ERROR Error[sH, invalidOperation]}; SetIndex: Stream.SetPositionProcedure = { h: MyHandle = ConvertHandleInline[sH]; filePage, byte: CARDINAL; bufferPage: CARDINAL = h.firstFilePageInBuffer; oldFilePages: CARDINAL = h.filePages; bufferPages: CARDINAL = h.bufferBytes/bytesPerPage; setupOffset: CARDINAL = localOffset; IF (Inline.LongMult[bufferPage, bytesPerPage] + h.index) = position THEN RETURN; IF Inline.HighHalf[position] >= bytesPerPage THEN GOTO OutOfRange; [filePage, byte] ¬ Inline.LongDivMod[position, bytesPerPage]; IF h.buffer = NIL OR filePage ~IN[bufferPage..bufferPage+bufferPages) THEN BEGIN growFile: BOOLEAN; IF position > h.length THEN { IF ~h.write THEN GOTO OutOfRange; h.filePages ¬ filePage + growIncrement; growFile ¬ TRUE; h.length ¬ position; h.lengthChanged ¬ TRUE} ELSE growFile ¬ FALSE; SetupBuffer[h, MAX[filePage, setupOffset]-setupOffset, growFile ! UNWIND => h.filePages ¬ oldFilePages]; END ELSE IF position > h.length AND ~h.write THEN GOTO OutOfRange; h.index ¬ (filePage-h.firstFilePageInBuffer) * bytesPerPage + byte; IF h.index > h.dataBytesInBuffer THEN { h.dataBytesInBuffer ¬ h.index; h.length ¬ ComputeLength[h]; h.lengthChanged ¬ TRUE}; RETURN; EXITS OutOfRange => ERROR Error[sH, indexOutOfRange]}; GetBlock: Stream.GetProcedure = BEGIN h: MyHandle = ConvertHandleInline[sH]; bufferBlock: Environment.Block; countRemaining: CARDINAL ¬ block.stopIndexPlusOne-block.startIndex; countTransferred: CARDINAL; sst ¬ 0; bytesTransferred ¬ 0; why ¬ normal; WHILE countRemaining # 0 DO bufferBlock ¬ [ blockPointer: h.buffer, startIndex: h.index, stopIndexPlusOne: h.dataBytesInBuffer]; countTransferred ¬ ByteBlt.ByteBlt[from: bufferBlock, to: block]; h.index ¬ h.index + countTransferred; bytesTransferred ¬ bytesTransferred + countTransferred; IF (countRemaining ¬ countRemaining - countTransferred) = 0 THEN EXIT; IF h.eofInBuffer THEN BEGIN IF options.signalEndOfStream THEN SIGNAL Stream.EndOfStream[block.startIndex+countTransferred]; why ¬ endOfStream; EXIT END; block.startIndex ¬ block.startIndex + countTransferred; AdvanceBuffer[h]; ENDLOOP; RETURN END; GetBlockError: Stream.GetProcedure = {ERROR Error[sH, invalidOperation]}; PutBlock: Stream.PutProcedure = BEGIN h: MyHandle = ConvertHandleInline[sH]; bufferBlock: Environment.Block; countRemaining: CARDINAL ¬ block.stopIndexPlusOne-block.startIndex; countTransferred: CARDINAL; WHILE countRemaining # 0 DO bufferBlock ¬ [ blockPointer: h.buffer, startIndex: h.index, stopIndexPlusOne: h.bufferBytes]; countTransferred ¬ ByteBlt.ByteBlt[from: block, to: bufferBlock]; h.index ¬ h.index + countTransferred; IF h.eofInBuffer AND h.index > h.dataBytesInBuffer THEN { h.dataBytesInBuffer ¬ h.index; h.length ¬ ComputeLength[h]; h.lengthChanged ¬ TRUE}; IF (countRemaining ¬ countRemaining - countTransferred) = 0 THEN EXIT; IF h.eofInBuffer THEN { h.dataBytesInBuffer ¬ h.bufferBytes; h.length ¬ ComputeLength[h]; h.lengthChanged ¬ TRUE}; block.startIndex ¬ block.startIndex + countTransferred; AdvanceBuffer[h]; ENDLOOP; IF endRecord THEN Cleanup[h, h.file]; RETURN END; PutBlockError: Stream.PutProcedure = {ERROR Error[sH, invalidOperation]}; SendNow: Stream.SendNowProcedure = BEGIN h: MyHandle = ConvertHandleInline[sH]; IF endRecord THEN Cleanup[h, h.file]; RETURN END; Delete: PROCEDURE [stream: Stream.Handle] = { h: MyHandle = ConvertHandleInline[stream]; copy: MFile.Handle ¬ NIL; CheckDelete: ENTRY PROCEDURE [streamHandle: Handle] = { copy ¬ h.file; h.file ¬ NIL}; BEGIN ENABLE MFile.Error => IF code = courierError THEN GOTO remoteError; CheckDelete[LOOPHOLE[stream]]; IF copy # NIL THEN {InternalDelete[h, stream, copy]; MFile.Release[copy]}; EXITS remoteError => NULL; END; }; InternalDelete: PROCEDURE [ h: MyHandle, stream: Stream.Handle, file: MFile.Handle] = { index: LONG CARDINAL ¬ stream.getPosition[stream]; BEGIN ENABLE MFile.Error => IF code = courierError THEN GOTO remoteError; EmptyBuffer[h, TRUE, file]; IF ~h.read «AND index # 0» THEN MFile.SetLength[file, index]; EXITS remoteError => NULL; END; LOOPHOLE[stream, LONG POINTER TO WORD]­ ¬ 0; Inline.LongCOPY[from: stream, to: stream + 1, nwords: SIZE[StreamObject] - 1]; heap.FREE[@stream]}; SetSSTNop: Stream.SetSSTProcedure = {}; SendAttentionNop: Stream.SendAttentionProcedure = {}; WaitAttentionNop: Stream.WaitAttentionProcedure = {RETURN[0]}; -- FileStream specific procedures; must validate handle Copy: PUBLIC PROCEDURE [from, to: Stream.Handle, bytes: LONG CARDINAL] RETURNS [bytesCopied: LONG CARDINAL] = { bufferBlock: Environment.Block; bytesToTransfer: CARDINAL; fromH, toH: MyHandle ¬ NIL; fromH ¬ ValidateHandle[from ! Error => CONTINUE]; toH ¬ ValidateHandle[to ! Error => CONTINUE]; bytesCopied ¬ 0; SELECT TRUE FROM fromH # NIL => { WHILE bytes # 0 DO bytesToTransfer ¬ fromH.dataBytesInBuffer - fromH.index; IF bytesToTransfer > bytes THEN bytesToTransfer ¬ Inline.LowHalf[bytes]; bufferBlock ¬ [ blockPointer: fromH.buffer, startIndex: fromH.index, stopIndexPlusOne: fromH.index + bytesToTransfer]; IF bufferBlock.startIndex # bufferBlock.stopIndexPlusOne THEN to.put[to, bufferBlock, FALSE]; fromH.index ¬ fromH.index + bytesToTransfer; bytes ¬ bytes - bytesToTransfer; bytesCopied ¬ bytesCopied + bytesToTransfer; IF bytes = 0 OR fromH.eofInBuffer THEN RETURN; AdvanceBuffer[fromH]; ENDLOOP}; toH # NIL => { bytesTransferred: CARDINAL; why: Stream.CompletionCode; WHILE bytes # 0 DO bytesToTransfer ¬ toH.bufferBytes - toH.index; IF bytesToTransfer > bytes THEN bytesToTransfer ¬ Inline.LowHalf[bytes]; bufferBlock ¬ [ blockPointer: toH.buffer, startIndex: toH.index, stopIndexPlusOne: toH.index + bytesToTransfer]; [bytesTransferred, why, ] ¬ from.get[ from, bufferBlock, Stream.defaultInputOptions]; IF why = sstChange THEN ERROR Error[from, other]; toH.index ¬ toH.index + bytesTransferred; bytes ¬ bytes - bytesTransferred; bytesCopied ¬ bytesCopied + bytesTransferred; IF toH.eofInBuffer AND toH.index > toH.dataBytesInBuffer THEN { toH.dataBytesInBuffer ¬ toH.index; toH.length ¬ ComputeLength[toH]; toH.lengthChanged ¬ TRUE}; IF bytes = 0 OR why = endOfStream THEN RETURN; IF toH.bufferBytes = toH.index THEN { IF toH.eofInBuffer THEN { toH.dataBytesInBuffer ¬ toH.bufferBytes; toH.length ¬ ComputeLength[toH]; toH.lengthChanged ¬ TRUE}; AdvanceBuffer[toH]}; ENDLOOP}; ENDCASE => ERROR Error[from, invalidHandle]}; ShareBlock: PUBLIC PROCEDURE [ stream: Stream.Handle, start: LONG CARDINAL, length: CARDINAL] RETURNS [block: Environment.Block] = { h: MyHandle = ValidateHandle[stream]; SetIndex[stream, start]; IF ~h.eofInBuffer AND h.index = h.dataBytesInBuffer THEN AdvanceBuffer[h]; block ¬ [ blockPointer: h.buffer, startIndex: h.index, stopIndexPlusOne: -- check for overflow IF length > LAST[CARDINAL] - h.index THEN h.dataBytesInBuffer ELSE MIN[h.dataBytesInBuffer, h.index + length]]}; GetFile: PUBLIC PROCEDURE [stream: Stream.Handle] RETURNS [file: MFile.Handle] = { RETURN[ValidateHandle[stream].file]}; EndOf: PUBLIC PROCEDURE [stream: Stream.Handle] RETURNS [BOOLEAN] = { h: MyHandle = ValidateHandle[stream]; RETURN[h.eofInBuffer AND h.index = h.dataBytesInBuffer]}; GetLength: PUBLIC PROCEDURE [stream: Stream.Handle] RETURNS [fileLength: LONG CARDINAL] = { h: MyHandle = ValidateHandle[stream]; RETURN[h.length]}; SetLength: PUBLIC PROCEDURE [stream: Stream.Handle, fileLength: LONG CARDINAL] = BEGIN h: MyHandle = ValidateHandle[stream]; «IF MFile.GetAccess[h.file] = log THEN IF h.length < fileLength OR fileLength < MFileOps.GetReadLengthForLog[h.file] THEN ERROR Error[stream, invalidOperation] ELSE RETURN;» InternalSetLength[h, stream, fileLength]; END; InternalSetLength: PROCEDURE [ h: MyHandle, stream: Stream.Handle, fileLength: LONG CARDINAL] = BEGIN oldLength: LONG CARDINAL; newLengthInPages: File.PageCount = PagesForBytes[fileLength] - h.streamBase; bufferMin: LONG CARDINAL = Inline.LongMult[ h.firstFilePageInBuffer, bytesPerPage]; IF ~h.write THEN ERROR Error[stream, invalidOperation]; IF Inline.HighHalf[newLengthInPages] # 0 THEN ERROR Error[stream, indexOutOfRange]; oldLength ¬ h.length; MFile.SetLength[h.file, fileLength]; h.length ¬ fileLength; IF newLengthInPages > h.filePages THEN h.filePages ¬ Inline.LowHalf[newLengthInPages]; SELECT TRUE FROM fileLength < bufferMin => { page: CARDINAL ¬ IF fileLength <= h.bufferSpacePages*bytesPerPage/2 THEN 0 ELSE (Inline.LongDiv[fileLength, bytesPerPage] - h.bufferSpacePages/2); SetupBuffer[h, page]; h.index ¬ h.dataBytesInBuffer}; fileLength > bufferMin + h.bufferBytes => h.eofInBuffer ¬ FALSE; oldLength > fileLength => { h.dataBytesInBuffer ¬ Inline.LowHalf[fileLength - bufferMin]; h.index ¬ MIN[h.dataBytesInBuffer, h.index]; h.eofInBuffer ¬ TRUE}; ENDCASE => h.dataBytesInBuffer ¬ Inline.LowHalf[fileLength - bufferMin]; RETURN; END; -- Utilities maxBufferPages: CARDINAL = 40; swapUnitSize: CARDINAL = 5; growIncrement: CARDINAL = 10; localBufferPages: CARDINAL ¬ maxBufferPages; localOffset: CARDINAL ¬ swapUnitSize; ComputeLength: PROCEDURE [h: MyHandle] RETURNS [LONG CARDINAL] = INLINE { RETURN[Inline.LongMult[h.firstFilePageInBuffer, bytesPerPage] + h.dataBytesInBuffer]}; Cleanup: PROCEDURE [h: MyHandle, file: MFile.Handle] = BEGIN IF h.write THEN { Space.ForceOut[h.bufferSpace]}; IF h.lengthChanged THEN { MFile.SetLength[file, h.length]; h.lengthChanged ¬ FALSE}; END; SetupBuffer: PROCEDURE [ h: MyHandle, filePage: CARDINAL, setLength: BOOLEAN ¬ FALSE] = BEGIN filePagesFollowing: CARDINAL; indexOfStartOfBuffer: LONG CARDINAL; IF h.bufferSpacePages = 0 OR h.filePages <= filePage THEN { h.index ¬ h.bufferBytes ¬ h.dataBytesInBuffer ¬ 0; h.eofInBuffer ¬ TRUE; RETURN}; IF h.buffer = NIL THEN BEGIN h.bufferSpace ¬ Space.Allocate[count: h.bufferSpacePages]; h.buffer ¬ h.bufferSpace.pointer; END ELSE EmptyBuffer[h, FALSE]; IF setLength THEN MFileOps.SetMinimumFileDataPages[h.file, h.filePages + h.streamBase ! UNWIND => {Space.Deallocate[h.bufferSpace]; h.buffer ¬ NIL}]; [] ¬ Space.MapAt[ at: h.bufferSpace, window: [h.capa, filePage + (leaderPages + h.streamBase), h.bufferSpacePages], access: IF h.read AND ~h.write AND ~h.shrink THEN readOnly ELSE readWrite, usage: MUsage.mFileLocalStream, swapUnits: [uniform[swapUnitSize]]]; filePagesFollowing ¬ h.filePages - filePage; indexOfStartOfBuffer ¬ LONG[filePage]*bytesPerPage; -- bug with h.streamBase in here somewhere *************** IF h.eofInBuffer ¬ (h.length <= (indexOfStartOfBuffer + h.bufferSpacePages*bytesPerPage)) THEN h.dataBytesInBuffer ¬ Inline.LowHalf[h.length - indexOfStartOfBuffer] ELSE h.dataBytesInBuffer ¬ h.bufferSpacePages*bytesPerPage; h.bufferBytes ¬ MIN[filePagesFollowing, h.bufferSpacePages] * bytesPerPage; h.firstFilePageInBuffer ¬ filePage; RETURN END; EmptyBuffer: PROCEDURE [h: MyHandle, delete: BOOLEAN, file: MFile.Handle ¬ NIL] = BEGIN IF h.buffer = NIL THEN RETURN; IF file = NIL THEN file ¬ h.file; Cleanup[h, file! UNWIND => IF delete THEN { [] ¬ Space.UnmapAt[h.bufferSpace.pointer]; Space.Deallocate[h.bufferSpace]; h.buffer ¬ NIL}]; IF delete THEN { [] ¬ Space.UnmapAt[h.bufferSpace.pointer]; Space.Deallocate[h.bufferSpace]; h.buffer ¬ NIL} ELSE [] ¬ Space.UnmapAt[h.bufferSpace.pointer]; h.index ¬ h.bufferBytes ¬ h.dataBytesInBuffer ¬ 0; RETURN END; AdvanceBuffer: PROCEDURE [h: MyHandle] = BEGIN filePagesInBuffer: CARDINAL = h.bufferBytes/bytesPerPage; nextPage: CARDINAL = h.firstFilePageInBuffer + filePagesInBuffer; oldFilePages: CARDINAL = h.filePages; changeSize: BOOLEAN ¬ FALSE; setupOffset: CARDINAL ¬ 0; IF nextPage >= h.filePages THEN { h.filePages ¬ h.filePages + growIncrement; changeSize ¬ TRUE}; SetupBuffer[h, MAX[nextPage, setupOffset]-setupOffset, changeSize ! UNWIND => h.filePages ¬ oldFilePages]; h.index ¬ MIN[(nextPage-h.firstFilePageInBuffer) * bytesPerPage, h.dataBytesInBuffer]; RETURN END; PagesForBytes: PROCEDURE [w: LONG CARDINAL] RETURNS [p: LONG CARDINAL] = INLINE {RETURN[(w+bytesPerPage-1)/bytesPerPage]}; END. of MStreamImpl