/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef _MDB_ # include "mdb.h" #endif #ifndef _MORK_ # include "mork.h" #endif #ifndef _MORKNODE_ # include "morkNode.h" #endif #ifndef _MORKFILE_ # include "morkFile.h" #endif #ifndef _MORKENV_ # include "morkEnv.h" #endif #ifndef _MORKSTREAM_ # include "morkStream.h" #endif // 456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789 // ````` ````` ````` ````` ````` // { ===== begin morkNode interface ===== /*public virtual*/ void morkStream::CloseMorkNode( morkEnv* ev) // CloseStream() only if open { if (this->IsOpenNode()) { this->MarkClosing(); this->CloseStream(ev); this->MarkShut(); } } /*public virtual*/ morkStream::~morkStream() // assert CloseStream() executed earlier { MORK_ASSERT(mStream_ContentFile == 0); MORK_ASSERT(mStream_Buf == 0); } /*public non-poly*/ morkStream::morkStream(morkEnv* ev, const morkUsage& inUsage, nsIMdbHeap* ioHeap, nsIMdbFile* ioContentFile, mork_size inBufSize, mork_bool inFrozen) : morkFile(ev, inUsage, ioHeap, ioHeap), mStream_At(0), mStream_ReadEnd(0), mStream_WriteEnd(0) , mStream_ContentFile(0) , mStream_Buf(0), mStream_BufSize(inBufSize), mStream_BufPos(0), mStream_Dirty(morkBool_kFalse), mStream_HitEof(morkBool_kFalse) { if (ev->Good()) { if (inBufSize < morkStream_kMinBufSize) mStream_BufSize = inBufSize = morkStream_kMinBufSize; else if (inBufSize > morkStream_kMaxBufSize) mStream_BufSize = inBufSize = morkStream_kMaxBufSize; if (ioContentFile && ioHeap) { // if ( ioContentFile->FileFrozen() ) // forced to be readonly? // inFrozen = morkBool_kTrue; // override the input value nsIMdbFile_SlotStrongFile(ioContentFile, ev, &mStream_ContentFile); if (ev->Good()) { mork_u1* buf = 0; ioHeap->Alloc(ev->AsMdbEnv(), inBufSize, (void**)&buf); if (buf) { mStream_At = mStream_Buf = buf; if (!inFrozen) { // physical buffer end never moves: mStream_WriteEnd = buf + inBufSize; } else mStream_WriteEnd = 0; // no writing is allowed if (inFrozen) { // logical buffer end starts at Buf with no content: mStream_ReadEnd = buf; this->SetFileFrozen(inFrozen); } else mStream_ReadEnd = 0; // no reading is allowed this->SetFileActive(morkBool_kTrue); this->SetFileIoOpen(morkBool_kTrue); } if (ev->Good()) mNode_Derived = morkDerived_kStream; } } else ev->NilPointerError(); } } /*public non-poly*/ void morkStream::CloseStream( morkEnv* ev) // called by CloseMorkNode(); { if (this->IsNode()) { nsIMdbFile_SlotStrongFile((nsIMdbFile*)0, ev, &mStream_ContentFile); nsIMdbHeap* heap = mFile_SlotHeap; mork_u1* buf = mStream_Buf; mStream_Buf = 0; if (heap && buf) heap->Free(ev->AsMdbEnv(), buf); this->CloseFile(ev); this->MarkShut(); } else this->NonNodeError(ev); } // } ===== end morkNode methods ===== // ````` ````` ````` ````` ````` #define morkStream_kSpacesPerIndent 1 /* one space per indent */ #define morkStream_kMaxIndentDepth 70 /* max indent of 70 space bytes */ static const char morkStream_kSpaces[] // next line to ease length perception = " " " "; // 123456789_123456789_123456789_123456789_123456789_123456789_123456789_ // morkStream_kSpaces above must contain (at least) 70 spaces (ASCII 0x20) mork_size morkStream::PutIndent(morkEnv* ev, mork_count inDepth) // PutIndent() puts a linebreak, and then // "indents" by inDepth, and returns the line length after indentation. { mork_size outLength = 0; nsIMdbEnv* mev = ev->AsMdbEnv(); if (ev->Good()) { this->PutLineBreak(ev); if (ev->Good()) { outLength = inDepth; mdb_size bytesWritten; if (inDepth) this->Write(mev, morkStream_kSpaces, inDepth, &bytesWritten); } } return outLength; } mork_size morkStream::PutByteThenIndent(morkEnv* ev, int inByte, mork_count inDepth) // PutByteThenIndent() puts the byte, then a linebreak, and then // "indents" by inDepth, and returns the line length after indentation. { mork_size outLength = 0; nsIMdbEnv* mev = ev->AsMdbEnv(); if (inDepth > morkStream_kMaxIndentDepth) inDepth = morkStream_kMaxIndentDepth; this->Putc(ev, inByte); if (ev->Good()) { this->PutLineBreak(ev); if (ev->Good()) { outLength = inDepth; mdb_size bytesWritten; if (inDepth) this->Write(mev, morkStream_kSpaces, inDepth, &bytesWritten); } } return outLength; } mork_size morkStream::PutStringThenIndent(morkEnv* ev, const char* inString, mork_count inDepth) // PutStringThenIndent() puts the string, then a linebreak, and then // "indents" by inDepth, and returns the line length after indentation. { mork_size outLength = 0; mdb_size bytesWritten; nsIMdbEnv* mev = ev->AsMdbEnv(); if (inDepth > morkStream_kMaxIndentDepth) inDepth = morkStream_kMaxIndentDepth; if (inString) { mork_size length = strlen(inString); if (length && ev->Good()) // any bytes to write? this->Write(mev, inString, length, &bytesWritten); } if (ev->Good()) { this->PutLineBreak(ev); if (ev->Good()) { outLength = inDepth; if (inDepth) this->Write(mev, morkStream_kSpaces, inDepth, &bytesWritten); } } return outLength; } mork_size morkStream::PutString(morkEnv* ev, const char* inString) { nsIMdbEnv* mev = ev->AsMdbEnv(); mork_size outSize = 0; mdb_size bytesWritten; if (inString) { outSize = strlen(inString); if (outSize && ev->Good()) // any bytes to write? { this->Write(mev, inString, outSize, &bytesWritten); } } return outSize; } mork_size morkStream::PutStringThenNewline(morkEnv* ev, const char* inString) // PutStringThenNewline() returns total number of bytes written. { nsIMdbEnv* mev = ev->AsMdbEnv(); mork_size outSize = 0; mdb_size bytesWritten; if (inString) { outSize = strlen(inString); if (outSize && ev->Good()) // any bytes to write? { this->Write(mev, inString, outSize, &bytesWritten); if (ev->Good()) outSize += this->PutLineBreak(ev); } } return outSize; } mork_size morkStream::PutByteThenNewline(morkEnv* ev, int inByte) // PutByteThenNewline() returns total number of bytes written. { mork_size outSize = 1; // one for the following byte this->Putc(ev, inByte); if (ev->Good()) outSize += this->PutLineBreak(ev); return outSize; } mork_size morkStream::PutLineBreak(morkEnv* ev) { #if defined(MORK_MAC) this->Putc(ev, mork_kCR); return 1; #else # if defined(MORK_WIN) this->Putc(ev, mork_kCR); this->Putc(ev, mork_kLF); return 2; # else # ifdef MORK_UNIX this->Putc(ev, mork_kLF); return 1; # endif /* MORK_UNIX */ # endif /* MORK_WIN */ #endif /* MORK_MAC */ } // ````` ````` ````` ````` ````` ````` ````` ````` // public: // virtual morkFile methods NS_IMETHODIMP morkStream::Steal(nsIMdbEnv* mev, nsIMdbFile* ioThief) // Steal: tell this file to close any associated i/o stream in the file // system, because the file ioThief intends to reopen the file in order // to provide the MDB implementation with more exotic file access than is // offered by the nsIMdbFile alone. Presumably the thief knows enough // from Path() in order to know which file to reopen. If Steal() is // successful, this file should probably delegate all future calls to // the nsIMdbFile interface down to the thief files, so that even after // the file has been stolen, it can still be read, written, or forcibly // closed (by a call to CloseMdbObject()). { MORK_USED_1(ioThief); morkEnv* ev = morkEnv::FromMdbEnv(mev); ev->StubMethodOnlyError(); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP morkStream::BecomeTrunk(nsIMdbEnv* mev) // If this file is a file version branch created by calling AcquireBud(), // BecomeTrunk() causes this file's content to replace the original // file's content, typically by assuming the original file's identity. { morkEnv* ev = morkEnv::FromMdbEnv(mev); ev->StubMethodOnlyError(); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP morkStream::AcquireBud(nsIMdbEnv* mev, nsIMdbHeap* ioHeap, nsIMdbFile** acqBud) // AcquireBud() starts a new "branch" version of the file, empty of content, // so that a new version of the file can be written. This new file // can later be told to BecomeTrunk() the original file, so the branch // created by budding the file will replace the original file. Some // file subclasses might initially take the unsafe but expedient // approach of simply truncating this file down to zero length, and // then returning the same morkFile pointer as this, with an extra // reference count increment. Note that the caller of AcquireBud() is // expected to eventually call CutStrongRef() on the returned file // in order to release the strong reference. High quality versions // of morkFile subclasses will create entirely new files which later // are renamed to become the old file, so that better transactional // behavior is exhibited by the file, so crashes protect old files. // Note that AcquireBud() is an illegal operation on readonly files. { MORK_USED_1(ioHeap); morkFile* outFile = 0; nsIMdbFile* file = mStream_ContentFile; morkEnv* ev = morkEnv::FromMdbEnv(mev); if (this->IsOpenAndActiveFile() && file) { // figure out how this interacts with buffering and mStream_WriteEnd: ev->StubMethodOnlyError(); } else this->NewFileDownError(ev); *acqBud = outFile; return NS_ERROR_NOT_IMPLEMENTED; } mork_pos morkStream::Length(morkEnv* ev) const // eof { mork_pos outPos = 0; nsIMdbFile* file = mStream_ContentFile; if (this->IsOpenAndActiveFile() && file) { mork_pos contentEof = 0; file->Eof(ev->AsMdbEnv(), &contentEof); if (ev->Good()) { if (mStream_WriteEnd) // this stream supports writing? { // the local buffer might have buffered content past content eof if (ev->Good()) // no error happened during Length() above? { mork_u1* at = mStream_At; mork_u1* buf = mStream_Buf; if (at >= buf) // expected cursor order? { mork_pos localContent = mStream_BufPos + (at - buf); if (localContent > contentEof) // buffered past eof? contentEof = localContent; // return new logical eof outPos = contentEof; } else this->NewBadCursorOrderError(ev); } } else outPos = contentEof; // frozen files get length from content file } } else this->NewFileDownError(ev); return outPos; } void morkStream::NewBadCursorSlotsError(morkEnv* ev) const { ev->NewError("bad stream cursor slots"); } void morkStream::NewNullStreamBufferError(morkEnv* ev) const { ev->NewError("null stream buffer"); } void morkStream::NewCantReadSinkError(morkEnv* ev) const { ev->NewError("can't read stream sink"); } void morkStream::NewCantWriteSourceError(morkEnv* ev) const { ev->NewError("can't write stream source"); } void morkStream::NewPosBeyondEofError(morkEnv* ev) const { ev->NewError("stream pos beyond eof"); } void morkStream::NewBadCursorOrderError(morkEnv* ev) const { ev->NewError("bad stream cursor order"); } NS_IMETHODIMP morkStream::Tell(nsIMdbEnv* mdbev, mork_pos* aOutPos) const { nsresult rv = NS_OK; morkEnv* ev = morkEnv::FromMdbEnv(mdbev); NS_ENSURE_ARG_POINTER(aOutPos); nsIMdbFile* file = mStream_ContentFile; if (this->IsOpenAndActiveFile() && file) { mork_u1* buf = mStream_Buf; mork_u1* at = mStream_At; mork_u1* readEnd = mStream_ReadEnd; // nonzero only if readonly mork_u1* writeEnd = mStream_WriteEnd; // nonzero only if writeonly if (writeEnd) { if (buf && at >= buf && at <= writeEnd) { *aOutPos = mStream_BufPos + (at - buf); } else this->NewBadCursorOrderError(ev); } else if (readEnd) { if (buf && at >= buf && at <= readEnd) { *aOutPos = mStream_BufPos + (at - buf); } else this->NewBadCursorOrderError(ev); } } else this->NewFileDownError(ev); return rv; } NS_IMETHODIMP morkStream::Read(nsIMdbEnv* mdbev, void* outBuf, mork_size inSize, mork_size* aOutSize) { NS_ENSURE_ARG_POINTER(aOutSize); // First we satisfy the request from buffered bytes, if any. Then // if additional bytes are needed, we satisfy these by direct reads // from the content file without any local buffering (but we still need // to adjust the buffer position to reflect the current i/o point). morkEnv* ev = morkEnv::FromMdbEnv(mdbev); nsresult rv = NS_OK; nsIMdbFile* file = mStream_ContentFile; if (this->IsOpenAndActiveFile() && file) { mork_u1* end = mStream_ReadEnd; // byte after last buffered byte if (end) // file is open for read access? { if (inSize) // caller wants any output? { mork_u1* sink = (mork_u1*)outBuf; // where we plan to write bytes if (sink) // caller passed good buffer address? { mork_u1* at = mStream_At; mork_u1* buf = mStream_Buf; if (at >= buf && at <= end) // expected cursor order? { mork_num remaining = (mork_num)(end - at); // bytes left in buffer mork_num quantum = inSize; // number of bytes to copy if (quantum > remaining) // more than buffer content? quantum = remaining; // restrict to buffered bytes if (quantum) // any bytes left in the buffer? { MORK_MEMCPY(sink, at, quantum); // from buffer bytes at += quantum; // advance past read bytes mStream_At = at; *aOutSize += quantum; // this much copied so far sink += quantum; // in case we need to copy more inSize -= quantum; // filled this much of request mStream_HitEof = morkBool_kFalse; } if (inSize) // we still need to read more content? { // We need to read more bytes directly from the // content file, without local buffering. We have // exhausted the local buffer, so we need to show // it is now empty, and adjust the current buf pos. mork_num posDelta = (mork_num)(at - buf); // old buf content mStream_BufPos += posDelta; // past now empty buf mStream_At = mStream_ReadEnd = buf; // empty buffer // file->Seek(ev, mStream_BufPos); // set file pos // if ( ev->Good() ) // no seek error? // { // } mork_num actual = 0; nsIMdbEnv* menv = ev->AsMdbEnv(); file->Get(menv, sink, inSize, mStream_BufPos, &actual); if (ev->Good()) // no read error? { if (actual) { *aOutSize += actual; mStream_BufPos += actual; mStream_HitEof = morkBool_kFalse; } else if (!*aOutSize) mStream_HitEof = morkBool_kTrue; } } } else this->NewBadCursorOrderError(ev); } else this->NewNullStreamBufferError(ev); } } else this->NewCantReadSinkError(ev); } else this->NewFileDownError(ev); if (ev->Bad()) *aOutSize = 0; return rv; } NS_IMETHODIMP morkStream::Seek(nsIMdbEnv* mdbev, mork_pos inPos, mork_pos* aOutPos) { NS_ENSURE_ARG_POINTER(aOutPos); morkEnv* ev = morkEnv::FromMdbEnv(mdbev); *aOutPos = 0; nsresult rv = NS_OK; nsIMdbFile* file = mStream_ContentFile; if (this->IsOpenOrClosingNode() && this->FileActive() && file) { mork_u1* at = mStream_At; // current position in buffer mork_u1* buf = mStream_Buf; // beginning of buffer mork_u1* readEnd = mStream_ReadEnd; // nonzero only if readonly mork_u1* writeEnd = mStream_WriteEnd; // nonzero only if writeonly if (writeEnd) // file is mutable/writeonly? { if (mStream_Dirty) // need to commit buffer changes? this->Flush(mdbev); if (ev->Good()) // no errors during flush or earlier? { if (at == buf) // expected post flush cursor value? { if (mStream_BufPos != inPos) // need to change pos? { mork_pos eof = 0; nsIMdbEnv* menv = ev->AsMdbEnv(); file->Eof(menv, &eof); if (ev->Good()) // no errors getting length? { if (inPos <= eof) // acceptable new position? { mStream_BufPos = inPos; // new stream position *aOutPos = inPos; } else this->NewPosBeyondEofError(ev); } } } else this->NewBadCursorOrderError(ev); } } else if (readEnd) // file is frozen/readonly? { if (at >= buf && at <= readEnd) // expected cursor order? { mork_pos eof = 0; nsIMdbEnv* menv = ev->AsMdbEnv(); file->Eof(menv, &eof); if (ev->Good()) // no errors getting length? { if (inPos <= eof) // acceptable new position? { *aOutPos = inPos; mStream_BufPos = inPos; // new stream position mStream_At = mStream_ReadEnd = buf; // empty buffer if (inPos == eof) // notice eof reached? mStream_HitEof = morkBool_kTrue; } else this->NewPosBeyondEofError(ev); } } else this->NewBadCursorOrderError(ev); } } else this->NewFileDownError(ev); return rv; } NS_IMETHODIMP morkStream::Write(nsIMdbEnv* menv, const void* inBuf, mork_size inSize, mork_size* aOutSize) { mork_num outActual = 0; morkEnv* ev = morkEnv::FromMdbEnv(menv); nsIMdbFile* file = mStream_ContentFile; if (this->IsOpenActiveAndMutableFile() && file) { mork_u1* end = mStream_WriteEnd; // byte after last buffered byte if (end) // file is open for write access? { if (inSize) // caller provided any input? { const mork_u1* source = (const mork_u1*)inBuf; // from where if (source) // caller passed good buffer address? { mork_u1* at = mStream_At; mork_u1* buf = mStream_Buf; if (at >= buf && at <= end) // expected cursor order? { mork_num space = (mork_num)(end - at); // space left in buffer mork_num quantum = inSize; // number of bytes to write if (quantum > space) // more than buffer size? quantum = space; // restrict to avail space if (quantum) // any space left in the buffer? { mStream_Dirty = morkBool_kTrue; // to ensure later flush MORK_MEMCPY(at, source, quantum); // into buffer mStream_At += quantum; // advance past written bytes outActual += quantum; // this much written so far source += quantum; // in case we need to write more inSize -= quantum; // filled this much of request } if (inSize) // we still need to write more content? { // We need to write more bytes directly to the // content file, without local buffering. We have // exhausted the local buffer, so we need to flush // it and empty it, and adjust the current buf pos. // After flushing, if the rest of the write fits // inside the buffer, we will put bytes into the // buffer rather than write them to content file. if (mStream_Dirty) this->Flush(menv); // will update mStream_BufPos at = mStream_At; if (at < buf || at > end) // bad cursor? this->NewBadCursorOrderError(ev); if (ev->Good()) // no errors? { space = (mork_num)(end - at); // space left in buffer if (space > inSize) // write to buffer? { mStream_Dirty = morkBool_kTrue; // ensure flush MORK_MEMCPY(at, source, inSize); // copy mStream_At += inSize; // past written bytes outActual += inSize; // this much written } else // directly to content file instead { // file->Seek(ev, mStream_BufPos); // set pos // if ( ev->Good() ) // no seek error? // { // } mork_num actual = 0; file->Put(menv, source, inSize, mStream_BufPos, &actual); if (ev->Good()) // no write error? { outActual += actual; mStream_BufPos += actual; } } } } } else this->NewBadCursorOrderError(ev); } else this->NewNullStreamBufferError(ev); } } else this->NewCantWriteSourceError(ev); } else this->NewFileDownError(ev); if (ev->Bad()) outActual = 0; *aOutSize = outActual; return ev->AsErr(); } NS_IMETHODIMP morkStream::Flush(nsIMdbEnv* ev) { morkEnv* mev = morkEnv::FromMdbEnv(ev); nsresult rv = NS_ERROR_FAILURE; nsIMdbFile* file = mStream_ContentFile; if (this->IsOpenOrClosingNode() && this->FileActive() && file) { if (mStream_Dirty) this->spill_buf(mev); rv = file->Flush(ev); } else this->NewFileDownError(mev); return rv; } // ````` ````` ````` ````` ````` ````` ````` ````` // protected: // protected non-poly morkStream methods (for char io) int morkStream::fill_getc(morkEnv* ev) { int c = EOF; nsIMdbFile* file = mStream_ContentFile; if (this->IsOpenAndActiveFile() && file) { mork_u1* buf = mStream_Buf; mork_u1* end = mStream_ReadEnd; // beyond buf after earlier read if (end > buf) // any earlier read bytes buffered? { mStream_BufPos += (end - buf); // advance past old read } if (ev->Good()) // no errors yet? { // file->Seek(ev, mStream_BufPos); // set file pos // if ( ev->Good() ) // no seek error? // { // } nsIMdbEnv* menv = ev->AsMdbEnv(); mork_num actual = 0; file->Get(menv, buf, mStream_BufSize, mStream_BufPos, &actual); if (ev->Good()) // no read errors? { if (actual > mStream_BufSize) // more than asked for?? actual = mStream_BufSize; mStream_At = buf; mStream_ReadEnd = buf + actual; if (actual) // any bytes actually read? { c = *mStream_At++; // return first byte from buffer mStream_HitEof = morkBool_kFalse; } else mStream_HitEof = morkBool_kTrue; } } } else this->NewFileDownError(ev); return c; } void morkStream::spill_putc(morkEnv* ev, int c) { this->spill_buf(ev); if (ev->Good() && mStream_At < mStream_WriteEnd) this->Putc(ev, c); } void morkStream::spill_buf(morkEnv* ev) // spill/flush from buffer to file { nsIMdbFile* file = mStream_ContentFile; if (this->IsOpenOrClosingNode() && this->FileActive() && file) { mork_u1* buf = mStream_Buf; if (mStream_Dirty) { mork_u1* at = mStream_At; if (at >= buf && at <= mStream_WriteEnd) // order? { mork_num count = (mork_num)(at - buf); // bytes buffered if (count) // anything to write to the string? { if (count > mStream_BufSize) // no more than max? { count = mStream_BufSize; mStream_WriteEnd = buf + mStream_BufSize; this->NewBadCursorSlotsError(ev); } if (ev->Good()) { // file->Seek(ev, mStream_BufPos); // if ( ev->Good() ) // { // } nsIMdbEnv* menv = ev->AsMdbEnv(); mork_num actual = 0; file->Put(menv, buf, count, mStream_BufPos, &actual); if (ev->Good()) { mStream_BufPos += actual; // past bytes written mStream_At = buf; // reset buffer cursor mStream_Dirty = morkBool_kFalse; } } } } else this->NewBadCursorOrderError(ev); } else { #ifdef MORK_DEBUG ev->NewWarning("stream:spill:not:dirty"); #endif /*MORK_DEBUG*/ } } else this->NewFileDownError(ev); } // 456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789