/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ /* file: abfile.cpp ** Most portions closely derive from public domain IronDoc code and interfaces. ** ** Changes: ** <1> 06Feb1998 first implementation ** <0> 23Dec1997 first interface draft */ #ifndef _ABTABLE_ #include "abtable.h" #endif #ifndef _ABMODEL_ #include "abmodel.h" #endif #include "xp_error.h" /*3456789_123456789_123456789_123456789_123456789_123456789_123456789_12345678*/ /* ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- */ #if AB_CONFIG_TRACE_orDEBUG_orPRINT static const char* ab_File_kClassName /*i*/ = "ab_File"; #endif /* end AB_CONFIG_TRACE_orDEBUG_orPRINT*/ #if AB_CONFIG_TRACE_orDEBUG_orPRINT static const char* AB_File_kClassName /*i*/ = "AB_File"; #endif /* end AB_CONFIG_TRACE_orDEBUG_orPRINT*/ #if AB_CONFIG_TRACE_orDEBUG_orPRINT static const char* ab_StdioFile_kClassName /*i*/ = "ab_StdioFile"; #endif /* end AB_CONFIG_TRACE_orDEBUG_orPRINT*/ #if AB_CONFIG_TRACE_orDEBUG_orPRINT static const char* ab_Stream_kClassName /*i*/ = "ab_Stream"; #endif /* end AB_CONFIG_TRACE_orDEBUG_orPRINT*/ #if AB_CONFIG_TRACE_orDEBUG_orPRINT static const char* ab_StringFile_kClassName /*i*/ = "ab_StringFile"; #endif /* end AB_CONFIG_TRACE_orDEBUG_orPRINT*/ /*============================================================================= * ab_File: abstract file interface */ // ````` ````` ````` ````` ````` ````` ````` ````` // virtual ab_Object methods static const char* ab_File_k_empty_string = ""; // for null or empty filename ab_File::~ab_File() /*i*/ { AB_ASSERT(mFile_Name==ab_File_k_empty_string); } char* ab_File::ObjectAsString(ab_Env* ev, char* outXmlBuf) const /*i*/ { AB_USED_PARAMS_1(ev); #if AB_CONFIG_TRACE_orDEBUG_orPRINT char fileFlags[ 5 ]; fileFlags[ 0 ] = (FileFrozen())? 'F' : 'f'; fileFlags[ 1 ] = (FileDoTrace())? 'T' : 't'; fileFlags[ 2 ] = (FileIoOpen())? 'O' : 'o'; fileFlags[ 3 ] = (FileActive())? 'A' : 'a'; fileFlags[ 4 ] = '\0'; const char* name = (mFile_Name)? mFile_Name : ""; XP_SPRINTF(outXmlBuf, "", (long) this, // me=\"^%lX\" fileFlags, // flags=\"%.4s\" name, // fn=\"%.96s\" (unsigned long) mObject_RefCount, // rc=\"%lu\" this->GetObjectAccessAsString(), // ac=\"%.9s\" this->GetObjectUsageAsString() // us=\"%.9s\" ); #else *outXmlBuf = 0; /* empty string */ #endif /*AB_CONFIG_TRACE_orDEBUG_orPRINT*/ return outXmlBuf; } // ````` ````` ````` ````` ````` ````` ````` ````` // non-poly ab_File methods ab_File::ab_File(const ab_Usage& inUsage) /*i*/ : ab_Object(inUsage), mFile_Frozen( 0 ), mFile_DoTrace( 0 ), mFile_IoOpen( 0 ), mFile_Active( 0 ), mFile_Name( ab_File_k_empty_string ), mFile_FormatHint( ab_File_kUnknownFormat ) { } void ab_File::CloseFile(ab_Env* ev) /*i*/ { ab_File_BeginMethod(this, ev, ab_File_kClassName, "CloseFile") this->ChangeFileName(ev, ab_File_k_empty_string); ab_File_EndMethod(this, ev) } ab_File_eFormat ab_File::GuessFileFormat(ab_Env* ev) /*i*/ { ab_File_eFormat outFormat = ab_File_kUnknownFormat; ab_File_BeginMethod(this, ev, ab_File_kClassName, "GuessFileFormat") ev->NewAbookFault(AB_Env_kFaultMethodStubOnly); ab_File_EndMethod(this, ev) return outFormat; } void ab_File::ChangeFileName(ab_Env* ev, const char* inFileName) /*i*/ // inFileName can be nil (which has the same effect as passing "") { ab_File_BeginMethod(this, ev, ab_File_kClassName, "ChangeFileName") if ( !inFileName ) inFileName = ab_File_k_empty_string; #ifdef AB_CONFIG_TRACE if ( this->FileDoTrace() && ev->DoTrace() ) ev->Trace("", mFile_Name, inFileName); #endif /*AB_CONFIG_TRACE*/ char* name = (char*) mFile_Name; // cast away const in case free occurs if ( name && name != ab_File_k_empty_string ) ev->FreeString(name); if ( *inFileName ) mFile_Name = ev->CopyString(inFileName); else mFile_Name = ab_File_k_empty_string; ab_File_EndMethod(this, ev) } void ab_File::NewFileDownFault(ab_Env* ev) const /*i*/ // call NewFileDownFault() when either IsOpenAndActiveFile() // is false, or when IsOpenActiveAndMutableFile() is false. { ab_File_BeginMethod(this, ev, ab_File_kClassName, "NewFileDownFault") #ifdef AB_CONFIG_TRACE if ( this->FileDoTrace() && ev->DoTrace() ) this->TraceObject(ev); #endif /*AB_CONFIG_TRACE*/ if ( this->IsOpenObject() ) { if ( this->FileActive() ) { if ( this->FileFrozen() ) { ev->NewAbookFault(ab_File_kFaultFrozen); } else ev->NewAbookFault(ab_File_kFaultDownUnknown); } else ev->NewAbookFault(ab_File_kFaultNotActive); } else ev->NewAbookFault(ab_File_kFaultNotOpen); ab_File_EndMethod(this, ev) } void ab_File::NewFileErrnoFault(ab_Env* ev) const /*i*/ // call NewFileErrnoFault() to convert std C errno into AB fault { ab_error_uid faultCode = (ab_error_uid) errno; // capture first thing ab_File_BeginMethod(this, ev, ab_File_kClassName, "NewFileErrnoFault") #ifdef AB_CONFIG_TRACE if ( this->FileDoTrace() && ev->DoTrace() ) this->TraceObject(ev); #endif /*AB_CONFIG_TRACE*/ if ( !faultCode ) // errno is unexpectedly equal to zero? faultCode = ab_File_kFaultZeroErrno; ev->NewFault(faultCode, /*space*/ AB_Fault_kErrnoSpace); ab_File_EndMethod(this, ev) } // ````` ````` ````` ````` newlines ````` ````` ````` ````` #ifdef XP_MAC static const char* ab_File_kNewlines = "\015\015\015\015\015\015\015\015\015\015\015\015\015\015\015\015"; # define ab_File_kNewlinesCount 16 #else # if defined(XP_WIN) || defined(XP_OS2) static const char* ab_File_kNewlines = "\015\012\015\012\015\012\015\012\015\012\015\012\015\012\015\012"; # define ab_File_kNewlinesCount 8 # else # ifdef XP_UNIX static const char* ab_File_kNewlines = "\012\012\012\012\012\012\012\012\012\012\012\012\012\012\012\012"; # define ab_File_kNewlinesCount 16 # endif /* XP_UNIX */ # endif /* XP_WIN */ #endif /* XP_MAC */ void // (close copy of IronDoc's FeStdioFile_WriteNewlines() ) ab_File::WriteNewlines(ab_Env* ev, ab_num inNewlines) /*i*/ { while ( inNewlines && ev->Good() ) // more newlines to write? { ab_u4 quantum = inNewlines; if ( quantum > ab_File_kNewlinesCount ) quantum = ab_File_kNewlinesCount; this->Write(ev, ab_File_kNewlines, quantum * ab_kNewlineSize); inNewlines -= quantum; } } /*============================================================================= * ab_StdioFile: concrete file using standard C file io */ // ````` ````` ````` ````` ````` ````` ````` ````` // virtual ab_File methods ab_pos ab_StdioFile::Length(ab_Env* ev) const /*i*/ { ab_pos outPos = 0; ab_File_BeginMethod(this, ev, ab_StdioFile_kClassName, "Length") if ( this->IsOpenAndActiveFile() ) { FILE* file = (FILE*) mStdioFile_File; if ( file ) { long start = XP_FileTell(file); if ( start >= 0 ) { long fore = XP_FileSeek(file, 0, SEEK_END); if ( fore >= 0 ) { long eof = XP_FileTell(file); if ( eof >= 0 ) { long back = XP_FileSeek(file, start, SEEK_SET); if ( back >= 0 ) outPos = eof; else this->new_stdio_file_fault(ev); } else this->new_stdio_file_fault(ev); } else this->new_stdio_file_fault(ev); } else this->new_stdio_file_fault(ev); } else ev->NewAbookFault(ab_File_kFaultMissingIo); } else this->NewFileDownFault(ev); ab_File_EndMethod(this, ev) return outPos; } ab_pos ab_StdioFile::Tell(ab_Env* ev) const /*i*/ { ab_pos outPos = 0; ab_File_BeginMethod(this, ev, ab_StdioFile_kClassName, "Tell") if ( this->IsOpenAndActiveFile() ) { FILE* file = (FILE*) mStdioFile_File; if ( file ) { long where = XP_FileTell(file); if ( where >= 0 ) outPos = where; else this->new_stdio_file_fault(ev); } else ev->NewAbookFault(ab_File_kFaultMissingIo); } else this->NewFileDownFault(ev); ab_File_EndMethod(this, ev) return outPos; } ab_num ab_StdioFile::Read(ab_Env* ev, void* outBuf, ab_num inSize) /*i*/ { ab_num outCount = 0; ab_File_BeginMethod(this, ev, ab_StdioFile_kClassName, "Read") if ( this->IsOpenAndActiveFile() ) { FILE* file = (FILE*) mStdioFile_File; if ( file ) { long count = XP_FileRead(outBuf, inSize, file); if ( count >= 0 ) { outCount = (ab_num) count; } else this->new_stdio_file_fault(ev); } else ev->NewAbookFault(ab_File_kFaultMissingIo); } else this->NewFileDownFault(ev); ab_File_EndMethod(this, ev) return outCount; } ab_pos ab_StdioFile::Seek(ab_Env* ev, ab_pos inPos) /*i*/ { ab_pos outPos = 0; ab_File_BeginMethod(this, ev, ab_StdioFile_kClassName, "Seek") if ( this->IsOpenOrClosingObject() && this->FileActive() ) { FILE* file = (FILE*) mStdioFile_File; if ( file ) { long where = XP_FileSeek(file, inPos, SEEK_SET); if ( where >= 0 ) outPos = inPos; else this->new_stdio_file_fault(ev); } else ev->NewAbookFault(ab_File_kFaultMissingIo); } else this->NewFileDownFault(ev); ab_File_EndMethod(this, ev) return outPos; } ab_num ab_StdioFile::Write(ab_Env* ev, const void* inBuf, ab_num inSize) /*i*/ { ab_num outCount = 0; ab_File_BeginMethod(this, ev, ab_StdioFile_kClassName, "Write") if ( this->IsOpenActiveAndMutableFile() ) { FILE* file = (FILE*) mStdioFile_File; if ( file ) { if ( fwrite(inBuf, 1, inSize, file) >= 0 ) outCount = inSize; else this->new_stdio_file_fault(ev); } else ev->NewAbookFault(ab_File_kFaultMissingIo); } else this->NewFileDownFault(ev); ab_File_EndMethod(this, ev) return outCount; } void ab_StdioFile::Flush(ab_Env* ev) /*i*/ { ab_File_BeginMethod(this, ev, ab_StdioFile_kClassName, "Flush") if ( this->IsOpenOrClosingObject() && this->FileActive() ) { FILE* file = (FILE*) mStdioFile_File; if ( file ) { XP_FileFlush(file); } else ev->NewAbookFault(ab_File_kFaultMissingIo); } else this->NewFileDownFault(ev); ab_File_EndMethod(this, ev) } // ````` ````` ````` ````` ````` ````` ````` ````` // virtual ab_Object methods char* ab_StdioFile::ObjectAsString(ab_Env* ev, char* outXmlBuf) const /*i*/ { AB_USED_PARAMS_1(ev); #if AB_CONFIG_TRACE_orDEBUG_orPRINT const char* name = (mFile_Name)? mFile_Name : ""; XP_SPRINTF(outXmlBuf, "", (long) this, // me=\"^%lX\" (long) mStdioFile_File, // sf=\"^%lX\" name, // fn=\"%.96s\" (unsigned long) mObject_RefCount, // rc=\"%lu\" this->GetObjectAccessAsString(), // ac=\"%.9s\" this->GetObjectUsageAsString() // us=\"%.9s\" ); #else *outXmlBuf = 0; /* empty string */ #endif /*AB_CONFIG_TRACE_orDEBUG_orPRINT*/ return outXmlBuf; } void ab_StdioFile::CloseObject(ab_Env* ev) /*i*/ { if ( this->IsOpenObject() ) { this->MarkClosing(); this->CloseStdioFile(ev); this->MarkShut(); } } void ab_StdioFile::PrintObject(ab_Env* ev, ab_Printer* ioPrinter) const /*i*/ { #ifdef AB_CONFIG_PRINT ioPrinter->PutString(ev, ""); char xmlBuf[ ab_Object_kXmlBufSize + 2 ]; if ( this->IsOpenObject() ) { ioPrinter->PushDepth(ev); // indent all objects in the list ioPrinter->NewlineIndent(ev, /*count*/ 1); ioPrinter->PutString(ev, this->ab_File::ObjectAsString(ev, xmlBuf)); ioPrinter->NewlineIndent(ev, /*count*/ 1); ioPrinter->PutString(ev, this->ObjectAsString(ev, xmlBuf)); ioPrinter->PopDepth(ev); // stop indentation } else // use ab_Object::ObjectAsString() for non-objects: { ioPrinter->PutString(ev, this->ab_Object::ObjectAsString(ev, xmlBuf)); } ioPrinter->NewlineIndent(ev, /*count*/ 1); ioPrinter->PutString(ev, ""); #endif /*AB_CONFIG_PRINT*/ } ab_StdioFile::~ab_StdioFile() /*i*/ { AB_ASSERT(mStdioFile_File==0); } // ````` ````` ````` ````` ````` ````` ````` ````` // protected non-poly ab_StdioFile methods void ab_StdioFile::new_stdio_file_fault(ab_Env* ev) const { ab_File_BeginMethod(this, ev, ab_StdioFile_kClassName, "new_stdio_file_fault") FILE* file = (FILE*) mStdioFile_File; #if defined(AB_CONFIG_DEBUG) || defined(AB_CONFIG_TRACE) if ( file && ferror(file) != errno ) { long fe = ferror(file); long e = errno; #if defined(AB_CONFIG_TRACE) && !defined(AB_CONFIG_DEBUG) if ( this->DoTrace() && ev->DoTrace() ) ev->Trace(ev, "ferror(f)=%ld errno=%ld", fe, e); #endif /* debug but not trace */ ev->Break("ferror(f)=%ld errno=%ld", fe, e); } #endif /*AB_CONFIG_DEBUG or AB_CONFIG_TRACE*/ if ( !errno && file ) errno = ferror(file); this->NewFileErrnoFault(ev); ab_File_EndMethod(this, ev) } // ````` ````` ````` ````` ````` ````` ````` ````` // public non-poly ab_StdioFile methods ab_StdioFile::ab_StdioFile(const ab_Usage& inUsage) /*i*/ : ab_File(inUsage), mStdioFile_File( 0 ) { } ab_StdioFile::ab_StdioFile(ab_Env* ev, const ab_Usage& inUsage, /*i*/ const char* inName, const char* inMode) // calls OpenStdio() after construction : ab_File(inUsage), mStdioFile_File( 0 ) { ab_File_BeginMethod(this, ev, ab_StdioFile_kClassName, ab_StdioFile_kClassName) if ( ev->Good() ) { this->OpenStdio(ev, inName, inMode); } ab_File_EndMethod(this, ev) } ab_StdioFile::ab_StdioFile(ab_Env* ev, const ab_Usage& inUsage, /*i*/ void* ioFile, const char* inName, ab_bool inFrozen) // calls UseStdio() after construction : ab_File(inUsage), mStdioFile_File( 0 ) { ab_File_BeginMethod(this, ev, ab_StdioFile_kClassName, ab_StdioFile_kClassName) if ( ev->Good() ) { this->UseStdio(ev, ioFile, inName, inFrozen); } ab_File_EndMethod(this, ev) } // ````` ````` globals for GetLogStdioFile() ````` ````` char ab_StdioFile_g_log_file_name[ 32 ]; FILE* ab_StdioFile_g_log_file = 0; ab_StdioFile* ab_StdioFile_g_log_stdio_file = 0; /*static*/ ab_StdioFile* ab_StdioFile::GetLogStdioFile(ab_Env* ev) /*i*/ // return the standard log file used by ab_StdioFile for each session. { // (static file methods must the Env method macros:) ab_Env_BeginMethod(ev, ab_StdioFile_kClassName, "GetLogStdioFile") if ( !ab_StdioFile_g_log_stdio_file ) { FILE* file = ab_StdioFile_g_log_file; if ( !file ) { time_t now; time(&now); char* fileName = ab_StdioFile_g_log_file_name; XP_SPRINTF(fileName, "ab.%lX.log", (long) now); file = fopen(fileName, "a+"); if ( file ) ab_StdioFile_g_log_file = file; else { ab_error_uid faultCode = (ab_error_uid) errno; if ( !faultCode ) // errno is unexpectedly equal to zero? faultCode = ab_File_kFaultZeroErrno; ev->NewFault(faultCode, /*space*/ AB_Fault_kErrnoSpace); } } if ( file ) { char* fileName = ab_StdioFile_g_log_file_name; ab_bool frozen = AB_kFalse; ab_StdioFile* stdioFile = new(*ev) ab_StdioFile(ev, ab_Usage::kHeap, file, fileName, frozen); if ( stdioFile ) { ab_StdioFile_g_log_stdio_file = stdioFile; stdioFile->AcquireObject(ev); } } else { ab_error_uid faultCode = (ab_error_uid) errno; if ( !faultCode ) // errno is unexpectedly equal to zero? faultCode = ab_File_kFaultZeroErrno; ev->NewFault(faultCode, /*space*/ AB_Fault_kErrnoSpace); } } ab_Env_EndMethod(ev) return ab_StdioFile_g_log_stdio_file; } void ab_StdioFile::CloseStdioFile(ab_Env* ev) /*i*/ { ab_File_BeginMethod(this, ev, ab_StdioFile_kClassName, "CloseStdioFile") if ( mStdioFile_File && this->FileActive() && this->FileIoOpen() ) { this->CloseStdio(ev); } mStdioFile_File = 0; this->CloseFile(ev); ab_File_EndMethod(this, ev) } void ab_StdioFile::OpenStdio(ab_Env* ev, const char* inName, /*i*/ const char* inMode) // Open a new FILE with name inName, using mode flags from inMode. { ab_File_BeginMethod(this, ev, ab_StdioFile_kClassName, "OpenStdio") if ( ev->Good() ) { if ( !inMode ) inMode = ""; ab_bool frozen = (*inMode == 'r'); // cursory attempt to note readonly if ( this->IsOpenObject() ) { if ( !this->FileActive() ) { this->SetFileIoOpen(AB_kFalse); if ( inName && *inName ) { this->ChangeFileName(ev, inName); if ( ev->Good() ) { FILE* file = fopen(inName, inMode); if ( file ) { mStdioFile_File = file; this->SetFileActive(AB_kTrue); this->SetFileIoOpen(AB_kTrue); this->SetFileFrozen(frozen); } else this->new_stdio_file_fault(ev); } } else ev->NewAbookFault(ab_File_kFaultNoFileName); } else ev->NewAbookFault(ab_File_kFaultAlreadyActive); } else this->NewFileDownFault(ev); } ab_File_EndMethod(this, ev) } void ab_StdioFile::UseStdio(ab_Env* ev, void* ioFile, /*i*/ const char* inName, ab_bool inFrozen) // Use an existing file, like stdin/stdout/stderr, which should not // have the io stream closed when the file is closed. The ioFile // parameter must actually be of type FILE (but we don't want to make // this header file include the stdio.h header file). { ab_File_BeginMethod(this, ev, ab_StdioFile_kClassName, "UseStdio") if ( ev->Good() ) { if ( this->IsOpenObject() ) { if ( !this->FileActive() ) { if ( ioFile ) { this->SetFileIoOpen(AB_kFalse); this->ChangeFileName(ev, inName); if ( ev->Good() ) { mStdioFile_File = ioFile; this->SetFileActive(AB_kTrue); this->SetFileFrozen(inFrozen); } } else ev->NewAbookFault(ab_File_kFaultNullOpaqueParameter); } else ev->NewAbookFault(ab_File_kFaultAlreadyActive); } else this->NewFileDownFault(ev); } ab_File_EndMethod(this, ev) } void ab_StdioFile::CloseStdio(ab_Env* ev) /*i*/ // Close the stream io if both and FileActive() and FileIoOpen(), but // this does not close this instances (like CloseStdioFile() does). // If stream io was made active by means of calling UseStdio(), // then this method does little beyond marking the stream inactive // because FileIoOpen() is false. { ab_File_BeginMethod(this, ev, ab_StdioFile_kClassName, "CloseStdio") if ( mStdioFile_File && this->FileActive() && this->FileIoOpen() ) { FILE* file = (FILE*) mStdioFile_File; if ( XP_FileClose(file) < 0 ) this->new_stdio_file_fault(ev); mStdioFile_File = 0; this->SetFileActive(AB_kFalse); this->SetFileIoOpen(AB_kFalse); } ab_File_EndMethod(this, ev) } /*============================================================================= * ab_Stream: buffered file i/o */ // ````` ````` ````` ````` ````` ````` ````` ````` // virtual ab_File methods ab_pos ab_Stream::Length(ab_Env* ev) const /*i*/ { ab_pos outPos = 0; ab_File_BeginMethod(this, ev, ab_Stream_kClassName, "Length") ab_File* file = mStream_ContentFile; if ( this->IsOpenAndActiveFile() && file ) { ab_pos contentEof = file->Length(ev); 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? { ab_u1* at = mStream_At; ab_u1* buf = mStream_Buf; if ( at >= buf ) // expected cursor order? { ab_pos localContent = mStream_BufPos + (at - buf); if ( localContent > contentEof ) // buffered past eof? contentEof = localContent; // return new logical eof outPos = contentEof; } else ev->NewAbookFault(ab_File_kFaultBadCursorOrder); } } else outPos = contentEof; // frozen files get length from content file } else this->NewFileDownFault(ev); ab_File_EndMethod(this, ev) return outPos; } ab_pos ab_Stream::Tell(ab_Env* ev) const /*i*/ { ab_pos outPos = 0; ab_File_BeginMethod(this, ev, ab_Stream_kClassName, "Tell") ab_File* file = mStream_ContentFile; if ( this->IsOpenAndActiveFile() && file ) { ab_u1* buf = mStream_Buf; ab_u1* at = mStream_At; ab_u1* readEnd = mStream_ReadEnd; // nonzero only if readonly ab_u1* writeEnd = mStream_WriteEnd; // nonzero only if writeonly if ( writeEnd ) { if ( buf && at >= buf && at <= writeEnd ) { outPos = mStream_BufPos + (at - buf); } else ev->NewAbookFault(ab_File_kFaultBadCursorOrder); } else if ( readEnd ) { if ( buf && at >= buf && at <= readEnd ) { outPos = mStream_BufPos + (at - buf); } else ev->NewAbookFault(ab_File_kFaultBadCursorOrder); } } else this->NewFileDownFault(ev); ab_File_EndMethod(this, ev) return outPos; } ab_num ab_Stream::Read(ab_Env* ev, void* outBuf, ab_num inSize) { // 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). ab_pos outActual = 0; ab_File_BeginMethod(this, ev, ab_Stream_kClassName, "Read") ab_File* file = mStream_ContentFile; if ( this->IsOpenAndActiveFile() && file ) { ab_u1* end = mStream_ReadEnd; // byte after last buffered byte if ( end ) // file is open for read access? { if ( inSize ) // caller wants any output? { ab_u1* sink = (ab_u1*) outBuf; // where we plan to write bytes if ( sink ) // caller passed good buffer address? { ab_u1* at = mStream_At; ab_u1* buf = mStream_Buf; if ( at >= buf && at <= end ) // expected cursor order? { ab_num remaining = end - at; // bytes left in buffer ab_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? { AB_MEMCPY(sink, at, quantum); // from buffer bytes at += quantum; // advance past read bytes mStream_At = at; outActual += quantum; // this much copied so far sink += quantum; // in case we need to copy more inSize -= quantum; // filled this much of request mStream_HitEof = AB_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. ab_num posDelta = (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? { ab_num actual = file->Read(ev, sink, inSize); if ( ev->Good() ) // no read error? { if ( actual ) { outActual += actual; mStream_BufPos += actual; mStream_HitEof = AB_kFalse; } else if ( !outActual ) mStream_HitEof = AB_kTrue; } } } } else ev->NewAbookFault(ab_File_kFaultBadCursorOrder); } else ev->NewAbookFault(ab_File_kFaultNullBuffer); } } else ev->NewAbookFault(ab_File_kFaultCantReadSink); } else this->NewFileDownFault(ev); if ( ev->Bad() ) outActual = 0; ab_File_EndMethod(this, ev) return outActual; } ab_pos ab_Stream::Seek(ab_Env* ev, ab_pos inPos) { ab_pos outPos = 0; ab_File_BeginMethod(this, ev, ab_Stream_kClassName, "Seek") ab_File* file = mStream_ContentFile; if ( this->IsOpenOrClosingObject() && this->FileActive() && file ) { ab_u1* at = mStream_At; // current position in buffer ab_u1* buf = mStream_Buf; // beginning of buffer ab_u1* readEnd = mStream_ReadEnd; // nonzero only if readonly ab_u1* writeEnd = mStream_WriteEnd; // nonzero only if writeonly if ( writeEnd ) // file is mutable/writeonly? { if ( mStream_Dirty ) // need to commit buffer changes? this->Flush(ev); 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? { ab_pos eof = file->Length(ev); if ( ev->Good() ) // no errors getting length? { if ( inPos <= eof ) // acceptable new position? { mStream_BufPos = inPos; // new stream position outPos = inPos; } else ev->NewAbookFault(ab_File_kFaultPosBeyondEof); } } } else ev->NewAbookFault(ab_File_kFaultBadCursorOrder); } } else if ( readEnd ) // file is frozen/readonly? { if ( at >= buf && at <= readEnd ) // expected cursor order? { ab_pos eof = file->Length(ev); if ( ev->Good() ) // no errors getting length? { if ( inPos <= eof ) // acceptable new position? { outPos = inPos; mStream_BufPos = inPos; // new stream position mStream_At = mStream_ReadEnd = buf; // empty buffer if ( inPos == eof ) // notice eof reached? mStream_HitEof = AB_kTrue; } else ev->NewAbookFault(ab_File_kFaultPosBeyondEof); } } else ev->NewAbookFault(ab_File_kFaultBadCursorOrder); } } else this->NewFileDownFault(ev); ab_File_EndMethod(this, ev) return outPos; } ab_num ab_Stream::Write(ab_Env* ev, const void* inBuf, ab_num inSize) { ab_num outActual = 0; ab_File_BeginMethod(this, ev, ab_Stream_kClassName, "Write") ab_File* file = mStream_ContentFile; if ( this->IsOpenActiveAndMutableFile() && file ) { ab_u1* end = mStream_WriteEnd; // byte after last buffered byte if ( end ) // file is open for write access? { if ( inSize ) // caller provided any input? { const ab_u1* source = (const ab_u1*) inBuf; // from where if ( source ) // caller passed good buffer address? { ab_u1* at = mStream_At; ab_u1* buf = mStream_Buf; if ( at >= buf && at <= end ) // expected cursor order? { ab_num space = end - at; // space left in buffer ab_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 = AB_kTrue; // to ensure later flush AB_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(ev); // will update mStream_BufPos at = mStream_At; if ( at < buf || at > end ) // bad cursor? ev->NewAbookFault(ab_File_kFaultBadCursorOrder); if ( ev->Good() ) // no errors? { space = end - at; // space left in buffer if ( space > inSize ) // write to buffer? { mStream_Dirty = AB_kTrue; // ensure flush AB_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? { ab_num actual = file->Write(ev, source, inSize); if ( ev->Good() ) // no write error? { outActual += actual; mStream_BufPos += actual; } } } } } } else ev->NewAbookFault(ab_File_kFaultBadCursorOrder); } else ev->NewAbookFault(ab_File_kFaultNullBuffer); } } else ev->NewAbookFault(ab_File_kFaultCantWriteSource); } else this->NewFileDownFault(ev); if ( ev->Bad() ) outActual = 0; ab_File_EndMethod(this, ev) return outActual; } void ab_Stream::Flush(ab_Env* ev) { ab_File_BeginMethod(this, ev, ab_Stream_kClassName, "Flush") ab_File* file = mStream_ContentFile; if ( this->IsOpenOrClosingObject() && this->FileActive() && file ) { if ( mStream_Dirty ) this->spill_buf(ev); file->Flush(ev); } else this->NewFileDownFault(ev); ab_File_EndMethod(this, ev) } void ab_Stream::spill_buf(ab_Env* ev) { ab_File_BeginMethod(this, ev, ab_Stream_kClassName, "spill_buf") ab_File* file = mStream_ContentFile; if ( this->IsOpenOrClosingObject() && this->FileActive() && file ) { ab_u1* buf = mStream_Buf; if ( mStream_Dirty ) { ab_u1* at = mStream_At; if ( at >= buf && at <= mStream_WriteEnd ) // order? { ab_num count = at - buf; // the number of 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; ev->NewAbookFault(ab_String_kFaultBadCursorFields); } if ( ev->Good() ) { file->Seek(ev, mStream_BufPos); if ( ev->Good() ) { file->Write(ev, buf, count); if ( ev->Good() ) { mStream_BufPos += count; // past bytes written mStream_At = buf; // reset buffer cursor mStream_Dirty = AB_kFalse; } } } } } else ev->NewAbookFault(ab_File_kFaultBadCursorOrder); } else { #ifdef AB_CONFIG_TRACE this->TraceObject(ev); #endif /*AB_CONFIG_TRACE*/ #ifdef AB_CONFIG_DEBUG ev->Break("", (long) this); #endif /*AB_CONFIG_DEBUG*/ } } else this->NewFileDownFault(ev); ab_File_EndMethod(this, ev) } // ````` ````` ````` ````` ````` ````` ````` ````` // virtual ab_Object methods char* ab_Stream::ObjectAsString(ab_Env* ev, char* outXmlBuf) const { AB_USED_PARAMS_1(ev); #if AB_CONFIG_TRACE_orDEBUG_orPRINT XP_SPRINTF(outXmlBuf, "", (long) this, // me=\"^%lX\" (long) mStream_ContentFile, // cf=\"^%lX\" (long) mStream_Buf, // buf=\"^%lX\" (long) mStream_BufSize, // bs=\"%lu\" (unsigned long) mObject_RefCount, // rc=\"%lu\" this->GetObjectAccessAsString(), // ac=\"%.9s\" this->GetObjectUsageAsString() // us=\"%.9s\" ); #else *outXmlBuf = 0; /* empty string */ #endif /*AB_CONFIG_TRACE_orDEBUG_orPRINT*/ return outXmlBuf; } void ab_Stream::CloseObject(ab_Env* ev) { if ( this->IsOpenObject() ) { this->MarkClosing(); this->CloseStream(ev); this->MarkShut(); } } void ab_Stream::PrintObject(ab_Env* ev, ab_Printer* ioPrinter) const { #ifdef AB_CONFIG_PRINT ioPrinter->PutString(ev, ""); char xmlBuf[ ab_Object_kXmlBufSize + 2 ]; if ( this->IsOpenObject() ) { ioPrinter->PushDepth(ev); // indent all objects in the list ioPrinter->NewlineIndent(ev, /*count*/ 1); ioPrinter->PutString(ev, this->ObjectAsString(ev, xmlBuf)); if ( mStream_ContentFile ) { ioPrinter->NewlineIndent(ev, /*count*/ 1); mStream_ContentFile->PrintObject(ev, ioPrinter); } if ( mStream_Buf && ev->mEnv_BeVerbose && !ev->mEnv_BeConcise ) { ioPrinter->NewlineIndent(ev, /*count*/ 1); ioPrinter->Hex(ev, mStream_Buf, mStream_BufSize, /*pos*/ 0); } ioPrinter->PopDepth(ev); // stop indentation } else // use ab_Object::ObjectAsString() for non-objects: { ioPrinter->PutString(ev, this->ab_Object::ObjectAsString(ev, xmlBuf)); } ioPrinter->NewlineIndent(ev, /*count*/ 1); ioPrinter->PutString(ev, ""); #else /*AB_CONFIG_PRINT*/ AB_USED_PARAMS_2(ev,ioPrinter); #endif /*AB_CONFIG_PRINT*/ } ab_Stream::~ab_Stream() { AB_ASSERT(mStream_Buf==0); AB_ASSERT(mStream_ContentFile==0); #if AB_CONFIG_TRACE_orDEBUG_orPRINT ab_Object* obj = mStream_ContentFile; if ( obj ) obj->ObjectNotReleasedPanic(ab_Stream_kClassName); #endif /*AB_CONFIG_TRACE_orDEBUG_orPRINT*/ } void ab_Stream::PutString(ab_Env* ev, const char* inString) /*i*/ { ab_File_BeginMethod(this, ev, ab_Stream_kClassName, "PutString") if ( inString ) { ab_num length = AB_STRLEN(inString); if ( length && ev->Good() ) // any bytes to write? { this->Write(ev, inString, length); } } ab_File_EndMethod(this, ev) } void ab_Stream::PutStringThenNewline(ab_Env* ev, const char* inString) /*i*/ { ab_File_BeginMethod(this, ev, ab_Stream_kClassName, "PutStringThenNewline") if ( inString ) { ab_num length = AB_STRLEN(inString); if ( length && ev->Good() ) // any bytes to write? { this->Write(ev, inString, length); if ( ev->Good() ) this->WriteNewlines(ev, /*count*/ 1); } } ab_File_EndMethod(this, ev) } // ````` ````` ````` ````` ````` ````` ````` ````` // protected non-poly ab_Stream methods (for char io) int ab_Stream::fill_getc(ab_Env* ev) /*i*/ { int c = EOF; ab_File_BeginMethod(this, ev, ab_Stream_kClassName, "fill_getc") ab_File* file = mStream_ContentFile; if ( this->IsOpenAndActiveFile() && file ) { ab_u1* buf = mStream_Buf; ab_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? { ab_num actual = file->Read(ev, buf, mStream_BufSize); 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 = AB_kFalse; } else mStream_HitEof = AB_kTrue; } } } } else this->NewFileDownFault(ev); ab_File_EndMethod(this, ev) return c; } void ab_Stream::spill_putc(ab_Env* ev, int c) /*i*/ { ab_File_BeginMethod(this, ev, ab_Stream_kClassName, "spill_putc") this->spill_buf(ev); if ( ev->Good() && mStream_At < mStream_WriteEnd ) this->Putc(ev, c); ab_File_EndMethod(this, ev) } // ````` ````` ````` ````` ````` ````` ````` ````` // non-poly ab_Stream methods ab_Stream::ab_Stream(ab_Env* ev, const ab_Usage& inUsage, /*i*/ ab_File* ioContentFile, ab_num inBufSize, ab_bool inFrozen) : ab_File(inUsage), mStream_At( 0 ), mStream_ReadEnd( 0 ), // logical end of read buffered content mStream_WriteEnd( 0 ), // physical end of buffer mStream_ContentFile( 0 ), mStream_Buf( 0 ), mStream_BufSize( inBufSize ), mStream_BufPos( 0 ), mStream_Dirty( AB_kFalse ), mStream_HitEof( AB_kFalse ) { if ( ev->Good() ) { if ( inBufSize < ab_Stream_kMinBufSize ) mStream_BufSize = inBufSize = ab_Stream_kMinBufSize; else if ( inBufSize > ab_Stream_kMaxBufSize ) mStream_BufSize = inBufSize = ab_Stream_kMaxBufSize; if ( ioContentFile ) { if ( ioContentFile->FileFrozen() ) // forced to be readonly? inFrozen = AB_kTrue; // override the input value if ( ioContentFile->AcquireObject(ev) ) { mStream_ContentFile = ioContentFile; ab_u1* buf = (ab_u1*) ev->HeapAlloc(inBufSize); 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(AB_kTrue); this->SetFileIoOpen(AB_kTrue); } } } else ev->NewAbookFault(ab_Object_kFaultNullObjectParameter); } } void ab_Stream::CloseStream(ab_Env* ev) /*i*/ { ab_File_BeginMethod(this, ev, ab_Stream_kClassName, "CloseStream") if ( this->FileActive() && mStream_ContentFile && mStream_WriteEnd ) { if ( mStream_Dirty ) // need to commit buffer changes? this->Flush(ev); // also flushes the content file } ab_Object* obj = mStream_ContentFile; if ( obj ) { mStream_ContentFile = 0; obj->ReleaseObject(ev); } void* buf = mStream_Buf; if ( buf ) { mStream_Buf = 0; ev->HeapFree(buf); } this->CloseFile(ev); ab_File_EndMethod(this, ev) } // ````` ````` ````` ````` ````` ````` ````` ````` // ````` ````` stdio type methods ````` ````` void ab_Stream::Printf(ab_Env* ev, const char* inFormat, ...) /*i*/ { ab_File_BeginMethod(this, ev, ab_Stream_kClassName, "Printf") char buf[ ab_Stream_kPrintBufSize + 2 ]; va_list args; va_start(args,inFormat); PR_vsnprintf(buf, ab_Stream_kPrintBufSize, inFormat, args); va_end(args); this->PutString(ev, buf); ab_File_EndMethod(this, ev) } // ````` ````` high level i/o operations ````` ````` void ab_Stream::GetDoubleNewlineDelimitedRecord(ab_Env* ev, /*i*/ ab_String* outString) // GetDoubleNewlineDelimitedRecord() reads a "record" from the stream // that is intended to correspond to an ldif record in a file being // imported into an address book. Such records are delimited by two // (or more) empty lines, where all lines separated by only a single // empty line are considered part of the record. The record is written // to outString which grows as necessary to hold the record. (This // string is first emptied with ab_String::TruncateStringLength(), so // the string should not be a nonempty static readonly string. The // same string can easily be used in successive calls to this method // to avoid any more memory management than it needed.) // // Let LF, CR, and BL denote bytes 0xA, 0xD, and 0x20 respectively. // This method collapses sequences of LF and CR to a single LF because // this is what ldif parsing code expects to separate lines in each // ldif record. // // UNLIKE the original spec, BLANKS ARE NOT REMOVED, because leading // blanks are significant to continued line parsing in ldif format. // // Counting the number of empty lines // encountered is made more complex by the need to make the process // cross platform, where a line-end on various platforms might be a // single LF or CR, or a combined CRLF. Additionally, ldif files were // once erroneously written with line-ends composed of CRCRLF, so this // should count as a single line as well. Otherwise, this method will // consider two empty lines encountered (thus ending the record) when // enough LF's and CR's are seen to account for two lines on some // platform or another. Once either LF or CR is seen, all instances // of LF, CR, and BL will be consumed from the input stream, but only a // single LF will be written to the output record string. While this // white space is being parsed and discarded, the loop decides whether // more than one line is being seen, which ends the record when true. // // This method is intended to be a more efficient way to parse groups // of lines from an input file than by parsing individual lines which // then get concatenated together. The performance difference might // matter for large ldif files, especially when more than one pass must // be performed on the same input file. // // When this method returns, the current file position should point to // either eof (end of file) or the first byte of the next record. { ab_File_BeginMethod(this, ev, ab_Stream_kClassName, "GetDoubleNewlineDelimitedRecord") if ( outString->TruncateStringLength(ev, /*len*/ 0) ) // string trimmed? { ab_StringSink sink(ev, outString); if ( ev->Good() ) { register int c; while ( (c = this->Getc(ev)) != EOF && ev->Good() ) { if ( c == 0xA || c == 0xD ) { sink.Putc(ev, 0xA); if ( this->consuming_white_space_ends_record(ev, c) ) break; // end while loop at end of record } else sink.Putc(ev, c); } if ( ev->Good() ) sink.FlushStringSink(ev); } } ab_File_EndMethod(this, ev) } // ````` ````` ````` ````` ````` ````` ````` ````` // protected non-poly high-level support utilities ab_bool ab_Stream::consuming_white_space_ends_record(ab_Env* ev, /*i*/ register int c) // Consume the rest of the line ending after a leading 0xA or 0xD // which is passed in as the value of input argument c. Analyze the // consumed white space as per the description of public method // GetDoubleNewlineDelimitedRecord(), and return true if more than // one line-ending is detected which signals end of record. // // UNLIKE the original spec, BLANKS ARE NOT REMOVED, because leading // blanks are significant to continued line parsing in ldif format. { ab_bool outMultipleLineEnds = AB_kFalse; ab_File_BeginMethod(this, ev, ab_Stream_kClassName, "consuming_white_space_ends_record") ab_num countLF = (ab_num) (c == 0xA); // total LF's seen ab_num countCR = (ab_num) (c == 0xD); // total CR's seen ab_num countWS = 0; // total other white space seen while ( (c = this->Getc(ev)) != EOF ) { if ( ev->Good() ) // no read errors? { if ( c == 0xA ) // LF ? { ++countLF; } else if ( c == 0xD ) // CR ? { ++countCR; } // KEEP THE BLANKS because they affect continued line parsing: // else if ( XP_IS_SPACE(c) ) // other white space? // { // ++countWS; // the value of this is currently unused // } else // non-white space { this->Ungetc(c); // put back non-(LF or CR) characters break; // end while loop after non-space is seen } } else { if ( c != 0xA && c != 0xD && !XP_IS_SPACE(c) ) this->Ungetc(c); // put back non-white space characters break; // end while loop after any error occurs } } // the following calculation must show CRCRLF as a single line end: outMultipleLineEnds = ( ( countLF > 1 ) || // more than one LF ( countCR > 2 && countLF ) || // more than just CRCRLF ( !countLF && countCR > 1 ) // no LF, but multiple CR ); ab_File_EndMethod(this, ev) return outMultipleLineEnds; } /* ===== ===== ===== ===== ab_StringFile ===== ===== ===== ===== */ // ````` ````` ````` ````` ````` ````` ````` ````` // virtual ab_File methods ab_pos ab_StringFile::Length(ab_Env* ev) const /*i*/ { ab_pos outLength = 0; ab_File_BeginMethod(this, ev, ab_StringFile_kClassName, "Length") if ( this->IsOpenAndActiveFile() && mStringFile_String ) { outLength = mStringFile_String->GetStringLength(); } else this->NewFileDownFault(ev); ab_File_EndMethod(this, ev) return outLength; } ab_pos ab_StringFile::Tell(ab_Env* ev) const /*i*/ { ab_pos outTell = 0; ab_File_BeginMethod(this, ev, ab_StringFile_kClassName, "Tell") if ( this->IsOpenAndActiveFile() ) { outTell = mStringFile_Pos; } else this->NewFileDownFault(ev); ab_File_EndMethod(this, ev) return outTell; } ab_num ab_StringFile::Read(ab_Env* ev, void* outBuf, ab_num inSize) /*i*/ { ab_num outRead = 0; ab_File_BeginMethod(this, ev, ab_StringFile_kClassName, "Read") ab_String* string = mStringFile_String; if ( this->IsOpenAndActiveFile() && string ) { ab_pos pos = mStringFile_Pos; ab_num length = string->GetStringLength(); if ( pos < length ) // any bytes left to read? { ab_num remaining = length - pos; if ( inSize > remaining ) // more bytes requested than remain? inSize = remaining; // read no more than are left in string if ( inSize ) // anything to copy? { if ( outBuf ) // buffer to receive output? { const char* source = string->GetStringContent() + pos; AB_MEMCPY(outBuf, source, inSize); mStringFile_Pos += inSize; // advance beyond bytes read outRead = inSize; } else ev->NewAbookFault(ab_File_kFaultNullBuffer); } } } else this->NewFileDownFault(ev); ab_File_EndMethod(this, ev) return outRead; } ab_pos ab_StringFile::Seek(ab_Env* ev, ab_pos inPos) /*i*/ { ab_pos outSeek = 0; ab_File_BeginMethod(this, ev, ab_StringFile_kClassName, "Seek") if ( this->IsOpenOrClosingObject() && this->FileActive() && mStringFile_String ) { if ( inPos <= mStringFile_String->GetStringLength() ) { mStringFile_Pos = inPos; outSeek = inPos; } else ev->NewAbookFault(ab_File_kFaultPosBeyondEof); } else this->NewFileDownFault(ev); ab_File_EndMethod(this, ev) return outSeek; } ab_num ab_StringFile::Write(ab_Env* ev, const void* inBuf, ab_num inSize) /*i*/ { ab_num outWrite = 0; ab_File_BeginMethod(this, ev, ab_StringFile_kClassName, "Write") ab_String* string = mStringFile_String; if ( this->IsOpenActiveAndMutableFile() && string ) { if ( !this->FileFrozen() ) // file modification allowed? { ab_pos pos = mStringFile_Pos; if ( inSize ) // anything to write? { if ( inBuf ) // buffer to provide input? { const char* buf = (const char*) inBuf; // void* to char* if ( string->PutBlockContent(ev, buf, inSize, pos) ) { mStringFile_Pos += inSize; // go past bytes written outWrite = inSize; } } else ev->NewAbookFault(ab_File_kFaultNullBuffer); } } else ev->NewAbookFault(ab_File_kFaultFrozen); } else this->NewFileDownFault(ev); ab_File_EndMethod(this, ev) return outWrite; } void ab_StringFile::Flush(ab_Env* ev) /*i*/ { ab_File_BeginMethod(this, ev, ab_StringFile_kClassName, "Flush") AB_USED_PARAMS_1(ev); // do nothing since the string does not need flushing ab_File_EndMethod(this, ev) } // ````` ````` ````` ````` ````` ````` ````` ````` // virtual ab_Object methods char* ab_StringFile::ObjectAsString(ab_Env* ev, /*i*/ char* outXmlBuf) const { AB_USED_PARAMS_1(ev); #if AB_CONFIG_TRACE_orDEBUG_orPRINT XP_SPRINTF(outXmlBuf, "", (long) this, // me=\"^%lX\" (long) mStringFile_String, // sf=\"^%lX\" (long) mStringFile_Pos, // pos=\"^%lX\" (unsigned long) mObject_RefCount, // rc=\"%lu\" this->GetObjectAccessAsString(), // ac=\"%.9s\" this->GetObjectUsageAsString() // us=\"%.9s\" ); #else *outXmlBuf = 0; /* empty string */ #endif /*AB_CONFIG_TRACE_orDEBUG_orPRINT*/ return outXmlBuf; } void ab_StringFile::CloseObject(ab_Env* ev) /*i*/ { if ( this->IsOpenObject() ) { this->MarkClosing(); this->CloseStringFile(ev); this->MarkShut(); } } void ab_StringFile::PrintObject(ab_Env* ev, ab_Printer* ioPrinter) const /*i*/ { #ifdef AB_CONFIG_PRINT ioPrinter->PutString(ev, ""); char xmlBuf[ ab_Object_kXmlBufSize + 2 ]; if ( this->IsOpenObject() ) { ioPrinter->PushDepth(ev); // indent all objects in the list ioPrinter->NewlineIndent(ev, /*count*/ 1); ioPrinter->PutString(ev, this->ObjectAsString(ev, xmlBuf)); if ( mStringFile_String ) { ioPrinter->NewlineIndent(ev, /*count*/ 1); ioPrinter->PutString(ev, mStringFile_String->ObjectAsString(ev, xmlBuf)); } ioPrinter->PopDepth(ev); // stop indentation } else // use ab_Object::ObjectAsString() for non-objects: { ioPrinter->PutString(ev, this->ab_Object::ObjectAsString(ev, xmlBuf)); } ioPrinter->NewlineIndent(ev, /*count*/ 1); ioPrinter->PutString(ev, ""); #else /*AB_CONFIG_PRINT*/ AB_USED_PARAMS_2(ev,ioPrinter); #endif /*AB_CONFIG_PRINT*/ } ab_StringFile::~ab_StringFile() /*i*/ { AB_ASSERT(mStringFile_String==0); #if AB_CONFIG_TRACE_orDEBUG_orPRINT ab_Object* obj = mStringFile_String; if ( obj ) obj->ObjectNotReleasedPanic(ab_StringFile_kClassName); #endif /*AB_CONFIG_TRACE_orDEBUG_orPRINT*/ } // ````` ````` ````` ````` ````` ````` ````` ````` // non-poly ab_StringFile methods ab_StringFile::ab_StringFile(ab_Env* ev, const ab_Usage& inUsage, /*i*/ ab_String* ioString) : ab_File(inUsage), mStringFile_String( 0 ), mStringFile_Pos( 0 ) { if ( ev->Good() ) { if ( ioString ) { if ( ioString->AcquireObject(ev) ) { if ( ioString->IsStringReadOnly() ) // string is readonly? this->SetFileFrozen(AB_kTrue); // file is readonly too mStringFile_String = ioString; this->SetFileActive(AB_kTrue); this->SetFileIoOpen(AB_kTrue); } } else ev->NewAbookFault(ab_Object_kFaultNullObjectParameter); } } ab_StringFile::ab_StringFile(ab_Env* ev, const ab_Usage& inUsage, /*i*/ const char* inBytes) : ab_File(inUsage), mStringFile_String( 0 ), mStringFile_Pos( 0 ) { if ( ev->Good() ) { ab_String* newString = new(*ev) ab_String(ev, ab_Usage::kHeap, inBytes); if ( newString ) { if ( ev->Good() ) { this->SetFileFrozen(AB_kTrue); // string and file are readonly mStringFile_String = newString; this->SetFileActive(AB_kTrue); this->SetFileIoOpen(AB_kTrue); } else newString->ReleaseObject(ev); } } } void ab_StringFile::CloseStringFile(ab_Env* ev) /*i*/ { ab_File_BeginMethod(this, ev, ab_StringFile_kClassName, "CloseStringFile") ab_Object* obj = mStringFile_String; if ( obj ) { mStringFile_String = 0; obj->ReleaseObject(ev); } this->SetFileActive(AB_kFalse); this->SetFileIoOpen(AB_kFalse); ab_File_EndMethod(this, ev) } /* ````` ````` ````` C APIs ````` ````` ````` */ /* ````` file refcounting ````` */ AB_API_IMPL(ab_ref_count) /* abstore.cpp */ AB_File_Acquire(AB_File* self, AB_Env* cev) /*i*/ { ab_ref_count outCount = 0; ab_Env* ev = ab_Env::AsThis(cev); ab_Env_BeginMethod(ev, AB_File_kClassName, "Acquire") outCount = ((ab_File*) self)->AcquireObject(ev); ab_Env_EndMethod(ev) return outCount; } AB_API_IMPL(ab_ref_count) /* abstore.cpp */ AB_File_Release(AB_File* self, AB_Env* cev) /*i*/ { ab_ref_count outCount = 0; ab_Env* ev = ab_Env::AsThis(cev); ab_Env_BeginMethod(ev, AB_File_kClassName, "Release") outCount = ((ab_File*) self)->ReleaseObject(ev); ab_Env_EndMethod(ev) return outCount; }