/* -*- 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 //3456789_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 = MORK_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 = MORK_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 = MORK_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("cant read stream sink"); } void morkStream::NewCantWriteSourceError(morkEnv* ev) const { ev->NewError("cant 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); } //3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789