123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008 |
- /*++
- Copyright (c) 2016 Minoca Corp.
- This file is licensed under the terms of the GNU General Public License
- version 3. Alternative licensing terms are available. Contact
- info@minocacorp.com for details. See the LICENSE file at the root of this
- project for complete licensing information.
- Module Name:
- bufferedio.ck
- Abstract:
- This module implements support for performing buffered file I/O. It's
- inspired by Python's buffered I/O classes.
- Author:
- Evan Green 10-Jan-2016
- Environment:
- Chalk
- --*/
- //
- // ------------------------------------------------------------------- Includes
- //
- from iobase import BufferedIoBase, IoError, DEFAULT_BUFFER_SIZE, IO_SEEK_CUR;
- //
- // ---------------------------------------------------------------- Definitions
- //
- //
- // ----------------------------------------------- Internal Function Prototypes
- //
- //
- // -------------------------------------------------------------------- Globals
- //
- //
- // ------------------------------------------------------------------ Functions
- //
- //
- // BufferedRwSeekable is the fully functional read/write implementation of the
- // Buffered I/O base for a seekable file.
- //
- class BufferedIo is BufferedIoBase {
- var _raw;
- var _readable;
- var _writable;
- var _position;
- var _bufferSize;
- var _readBuffer;
- var _readOffset;
- var _writeBuffers;
- var _writeSize;
- function
- __init (
- raw,
- bufferSize
- )
- /*++
- Routine Description:
- This routine instantiates a new BufferdIo object.
- Arguments:
- raw - Supplies the raw I/O object, an instance of a RawIoBase subclass.
- bufferSize - Supplies the buffer size to use. If -1,
- DEFAULT_BUFFER_SIZE will be used.
- Return Value:
- Returns the object.
- --*/
- {
- _raw = raw;
- this.name = _raw.name;
- this.mode = _raw.mode;
- this.closed = false;
- this.raw = _raw;
- if (bufferSize <= 0) {
- bufferSize = DEFAULT_BUFFER_SIZE;
- }
- _bufferSize = bufferSize;
- _readable = _raw.isReadable();
- _writable = _raw.isWritable();
- _position = 0;
- _readBuffer = "";
- _readOffset = 0;
- _writeBuffers = [];
- _writeSize = 0;
- return this;
- }
- function
- flush (
- )
- /*++
- Routine Description:
- This routine flushes the stream data. This does nothing for read-only
- or non-blocking streams.
- Arguments:
- None.
- Return Value:
- None.
- --*/
- {
- if (_raw.isClosed()) {
- Core.raise(ValueError("I/O operation on closed file"));
- }
- if (_writable) {
- this._flushWrite();
- }
- this._flushRead();
- return;
- }
- function
- peek (
- size
- )
- /*++
- Routine Description:
- This routine returns data from the stream without advancing the
- position. At most one single read on the raw stream is done to
- satisfy the call. The number of bytes returned may be more or less
- than requested.
- Arguments:
- size - Supplies the size to peek at.
- Return Value:
- Returns the peeked data.
- --*/
- {
- var data;
- var existingLength;
- var readData;
- var readSize;
- var result;
- if (_writable) {
- this._flushWrite();
- }
- existingLength = _readBuffer.length() - _readOffset;
- if (size <= 0) {
- if (existingLength > 0) {
- size = existingLength;
- } else {
- size = _bufferSize;
- }
- }
- //
- // See if the request can be satisfied completely from the buffer.
- //
- if (existingLength >= size) {
- data = _readBuffer[_readOffset..(_readOffset + size)];
- return data;
- }
- //
- // Do one read to try and get the rest of the data requested. Round up
- // to try and read the whole buffer.
- //
- readSize = size - existingLength;
- if (readSize < _bufferSize) {
- readSize = _bufferSize;
- }
- readData = _raw.read(readSize);
- _position += readData.length();
- _readBuffer = _readBuffer[_readOffset...-1] + readData;
- _readOffset = 0;
- return _readBuffer[0..size];
- }
- function
- read (
- size
- )
- /*++
- Routine Description:
- This routine performs a read of the requested size.
- Arguments:
- size - Supplies the amount to read. If -1 is supplied, then readall is
- called.
- Return Value:
- Returns the bytes read. If the empty string is returned and more than
- zero bytes were requested, then this is end-of-file.
- --*/
- {
- var data;
- var existingLength;
- var length = 0;
- var result;
- if (size < -1) {
- Core.raise(ValueError("Size must be positive or -1"));
- }
- if (size == -1) {
- return this.readall();
- }
- if (_writable) {
- this._flushWrite();
- }
- //
- // Grab as much as possible from the existing buffer. Maybe it's all
- // that's needed.
- //
- existingLength = _readBuffer.length() - _readOffset;
- if (existingLength > size) {
- result = _readBuffer[_readOffset..(_readOffset + size)];
- _readOffset += size;
- return result;
- }
- //
- // Wipe out the remaining buffer, and start a list of buffers that
- // represents the result.
- //
- data = _readBuffer[_readOffset...-1];
- length = data.length();
- if (length) {
- size -= length;
- result = [data];
- } else {
- result = [];
- }
- _readBuffer = "";
- _readOffset = 0;
- //
- // Read directly into the result if huge amounts are requested.
- //
- length = 1;
- while (size >= _bufferSize) {
- data = _raw.read(size);
- length = data.length();
- if (!length) {
- break;
- }
- _position += length;
- result.append(data);
- size -= length;
- }
- //
- // Now refill the buffer and do the partial read out of it.
- //
- if (length != 0) {
- while (size != 0) {
- _readBuffer = _raw.read(_bufferSize);
- length = _readBuffer.length();
- if (!length) {
- break;
- }
- _position += length;
- if (size <= length) {
- result.append(_readBuffer[0..size]);
- _readOffset = size;
- break;
- } else {
- result.append(_readBuffer);
- size -= length;
- }
- }
- }
- return "".joinList(result);
- }
- function
- read1 (
- size
- )
- /*++
- Routine Description:
- This routine performs a single read.
- Arguments:
- size - Supplies the amount to read. If -1 is supplied, then data is
- read until the end of file is encountered.
- Return Value:
- Returns the bytes read. If the empty string is returned and more than
- zero bytes were requested, then this is end-of-file.
- --*/
- {
- var data;
- var existingLength;
- var length = 0;
- var result;
- if (size < 0) {
- Core.raise(ValueError("Size must be positive"));
- }
- if (_writable) {
- this._flushWrite();
- }
- //
- // Grab as much as possible from the existing buffer. Maybe it's all
- // that's needed.
- //
- existingLength = _readBuffer.length() - _readOffset;
- if (existingLength > size) {
- result = _readBuffer[_readOffset..(_readOffset + size)];
- _readOffset += size;
- return result;
- }
- //
- // Wipe out the remaining buffer, and start a list of buffers that
- // represents the result.
- //
- data = _readBuffer[_readOffset...-1];
- if (length) {
- size -= existingLength;
- result = [data];
- } else {
- result = [];
- }
- _readBuffer = "";
- _readOffset = 0;
- //
- // If the caller just wants a little, refill the buffer. Otherwise,
- // do all of what the caller wants.
- //
- if (size < _bufferSize) {
- _readBuffer = _raw.read(_bufferSize);
- length = _readBuffer.length();
- if (length) {
- _position += length;
- if (size > length) {
- size = length;
- }
- _readOffset = size;
- result.append(_readBuffer[0..size]);
- }
- } else {
- data = _raw.read(size);
- result.append(data);
- _position += data.length();
- }
- return "".joinList(result);
- }
- function
- readline (
- limit
- )
- /*++
- Routine Description:
- This routine reads a single line from the stream.
- Arguments:
- limit - Supplies the maximum number of bytes to read. Supply -1 for no
- limit.
- Return Value:
- Returns the line read from the file.
- --*/
- {
- var data;
- var index;
- var length;
- var result;
- if (_writable) {
- this._flushWrite();
- }
- //
- // Clip the buffer so that indexOf doesn't find something already found.
- //
- if (_readOffset) {
- _readBuffer = _readBuffer[_readOffset...-1];
- _readOffset = 0;
- }
- //
- // See if the buffer already contains a complete line.
- //
- index = _readBuffer.indexOf("\n");
- if (index != -1) {
- result = _readBuffer[_readOffset...index];
- _readOffset = index + 1;
- return result;
- }
- //
- // Pull the whole buffer into the result.
- //
- if (_readBuffer.length()) {
- data = _readBuffer[_readOffset...-1];
- result = [data];
- _readBuffer = "";
- _readOffset = 0;
- } else {
- result = [];
- }
- //
- // Loop reading blocks until a newline or EOF is found.
- //
- while (true) {
- _readBuffer = _raw.read(_bufferSize);
- length = _readBuffer.length();
- if (!length) {
- break;
- }
- _position += length;
- index = _readBuffer.indexOf("\n");
- if (index != -1) {
- result.append(_readBuffer[0...index]);
- _readOffset = index + 1;
- break;
- }
- result.append(_readBuffer);
- }
- return "".join(result);
- }
- function
- tell (
- )
- /*++
- Routine Description:
- This routine returns the current file position.
- Arguments:
- None.
- Return Value:
- Returns the current file position.
- --*/
- {
- return _position + _writeSize - (_readBuffer.length() - _readOffset);
- }
- function
- seek (
- offset,
- whence
- )
- /*++
- Routine Description:
- This routine seeks into the file.
- Arguments:
- offset - Supplies the desired offset to seek from or to.
- whence - Supplies the anchor point for the seek. Supply IO_SEEK_SET to
- seek from the beginning of the file, IO_SEEK_CUR to seek from the
- current position, or IO_SEEK_END to seek from the end of the file.
- Return Value:
- Returns the new absolute position within the file.
- --*/
- {
- this.flush();
- _position = _raw.seek(offset, whence);
- return _position;
- }
- function
- truncate (
- size
- )
- /*++
- Routine Description:
- This routine truncates the file to the given size. The stream position
- is not changed.
- Arguments:
- size - Supplies the new size of the file.
- Return Value:
- Returns the new file size.
- --*/
- {
- var result;
- this.flush();
- result = _raw.truncate(size);
- _position = _raw.tell();
- return result;
- }
- function
- readall (
- )
- /*++
- Routine Description:
- This routine reads the entire file.
- Arguments:
- None.
- Return Value:
- Returns the contents of the entire file.
- --*/
- {
- var data;
- var existingLength;
- var result;
- if (_writable) {
- this._flushWrite();
- }
- existingLength = _readBuffer.length() - _readOffset;
- if (!existingLength) {
- result = _raw.readall();
- _position += result.length();
- return result;
- }
- result = [_readBuffer[_readOffset...-1]];
- data = _raw.readall();
- _position += data.length();
- result.append(data);
- return "".join(result);
- }
- function
- write (
- data
- )
- /*++
- Routine Description:
- This routine performs a single write.
- Arguments:
- size - Supplies the string of data to write.
- Return Value:
- Returns the number of bytes written, which may be less than the size of
- the data.
- --*/
- {
- var length = data.length();
- var result = 0;
- if (_raw.isClosed()) {
- Core.raise(ValueError("I/O operation on closed file"));
- }
- if (!_writable) {
- Core.raise(ValueError("File not open for writing"));
- }
- if (_readable) {
- this._flushRead();
- }
- if (length > _bufferSize) {
- this._flushWrite();
- result = _raw.write(data);
- _position += result;
- return result;
- }
- if (!(data is String)) {
- Core.raise(TypeError("Expected a string"));
- }
- _writeBuffers.append(data);
- _writeSize += length;
- if (_writeSize > _bufferSize) {
- this._flushWrite();
- }
- return length;
- }
- //
- // IoBase functions.
- //
- function
- close (
- )
- /*++
- Routine Description:
- This routine closes and flushes a stream. If the stream is already
- closed, this does nothing.
- Arguments:
- None.
- Return Value:
- None.
- --*/
- {
- if (!this.closed) {
- this.flush();
- _raw.close();
- this.closed = true;
- }
- return 0;
- }
- function
- detach (
- )
- /*++
- Routine Description:
- This routine returns the raw file I/O object this object is buffering.
- Afterwards, this object is left in an unusable state.
- Arguments:
- None.
- Return Value:
- Returns the raw object.
- --*/
- {
- var raw = _raw;
- this.flush();
- _raw = null;
- return raw;
- }
- function
- fileno (
- )
- /*++
- Routine Description:
- This routine returns the OS file descriptor number associated with the
- stream.
- Arguments:
- None.
- Return Value:
- Returns the file descriptor number associated with the stream.
- -1 if no file descriptor is associated with the stream or the file has
- been closed.
- --*/
- {
- return _raw.fileno();
- }
- function
- isatty (
- )
- /*++
- Routine Description:
- This routine determines if the file descriptor backing this stream is
- a terminal device.
- Arguments:
- None.
- Return Value:
- true if the file descriptor is a terminal device.
- false if the file descriptor is not a terminal.
- --*/
- {
- return _raw.isatty();
- }
- function
- isReadable (
- )
- /*++
- Routine Description:
- This routine determines if the given stream can be read from.
- Arguments:
- None.
- Return Value:
- true if the stream was opened with read permissions.
- false if the stream cannot be read from.
- --*/
- {
- return _raw.isReadable();
- }
- function
- isSeekable (
- )
- /*++
- Routine Description:
- This routine determines if the file backing the stream can be seeked on.
- Arguments:
- None.
- Return Value:
- true if the underlying file is seekable.
- false if the underlying file is not seekable.
- --*/
- {
- return _raw.isSeekable();
- }
- function
- isWritable (
- )
- /*++
- Routine Description:
- This routine determines if the given file can be written to.
- Arguments:
- None.
- Return Value:
- true if the file can be written to.
- false if the file cannot be written to.
- --*/
- {
- return _raw.isWritable();
- }
- //
- // Internal functions
- //
- function
- _flushRead (
- )
- /*++
- Routine Description:
- This routine closes and flushes a stream and seeks back over the
- buffered contents.
- Arguments:
- None.
- Return Value:
- None.
- --*/
- {
- var readLength;
- if (_readable) {
- readLength = _readBuffer.length() - _readOffset;
- if (readLength) {
- _position = _raw.seek(-readLength, IO_SEEK_CUR);
- _readBuffer = "";
- _readOffset = 0;
- }
- }
- return;
- }
- function
- _flushWrite (
- )
- /*++
- Routine Description:
- This routine flushes the dirty write buffer.
- Arguments:
- None.
- Return Value:
- None.
- --*/
- {
- var data = "".join(_writeBuffers);
- var length = data.length();
- var written;
- _writeBuffers = [];
- _writeSize = 0;
- while (length) {
- written = _raw.write(data);
- if (written <= 0) {
- Core.raise(IoError("Unable to write"));
- }
- _position += written;
- data = data[written...-1];
- length = data.length();
- }
- return;
- }
- }
- //
- // --------------------------------------------------------- Internal Functions
- //
|