nuclear@1: /************************************************************************** nuclear@1: nuclear@1: Filename : OVR_File.cpp nuclear@1: Content : File wrapper class implementation (Win32) nuclear@1: nuclear@1: Created : April 5, 1999 nuclear@1: Authors : Michael Antonov nuclear@1: nuclear@1: Copyright : Copyright 2012 Oculus VR, Inc. All Rights reserved. nuclear@1: nuclear@1: Use of this software is subject to the terms of the Oculus license nuclear@1: agreement provided at the time of installation or download, or which nuclear@1: otherwise accompanies this software in either electronic or hard copy form. nuclear@1: nuclear@1: **************************************************************************/ nuclear@1: nuclear@1: #define GFILE_CXX nuclear@1: nuclear@1: // Standard C library (Captain Obvious guarantees!) nuclear@1: #include nuclear@1: nuclear@1: #include "OVR_File.h" nuclear@1: nuclear@1: namespace OVR { nuclear@1: nuclear@1: // Buffered file adds buffering to an existing file nuclear@1: // FILEBUFFER_SIZE defines the size of internal buffer, while nuclear@1: // FILEBUFFER_TOLERANCE controls the amount of data we'll effectively try to buffer nuclear@1: #define FILEBUFFER_SIZE (8192-8) nuclear@1: #define FILEBUFFER_TOLERANCE 4096 nuclear@1: nuclear@1: // ** Constructor/Destructor nuclear@1: nuclear@1: // Hidden constructor nuclear@1: // Not supposed to be used nuclear@1: BufferedFile::BufferedFile() : DelegatedFile(0) nuclear@1: { nuclear@1: pBuffer = (UByte*)OVR_ALLOC(FILEBUFFER_SIZE); nuclear@1: BufferMode = NoBuffer; nuclear@1: FilePos = 0; nuclear@1: Pos = 0; nuclear@1: DataSize = 0; nuclear@1: } nuclear@1: nuclear@1: // Takes another file as source nuclear@1: BufferedFile::BufferedFile(File *pfile) : DelegatedFile(pfile) nuclear@1: { nuclear@1: pBuffer = (UByte*)OVR_ALLOC(FILEBUFFER_SIZE); nuclear@1: BufferMode = NoBuffer; nuclear@1: FilePos = pfile->LTell(); nuclear@1: Pos = 0; nuclear@1: DataSize = 0; nuclear@1: } nuclear@1: nuclear@1: nuclear@1: // Destructor nuclear@1: BufferedFile::~BufferedFile() nuclear@1: { nuclear@1: // Flush in case there's data nuclear@1: if (pFile) nuclear@1: FlushBuffer(); nuclear@1: // Get rid of buffer nuclear@1: if (pBuffer) nuclear@1: OVR_FREE(pBuffer); nuclear@1: } nuclear@1: nuclear@1: /* nuclear@1: bool BufferedFile::VCopy(const Object &source) nuclear@1: { nuclear@1: if (!DelegatedFile::VCopy(source)) nuclear@1: return 0; nuclear@1: nuclear@1: // Data members nuclear@1: BufferedFile *psource = (BufferedFile*)&source; nuclear@1: nuclear@1: // Buffer & the mode it's in nuclear@1: pBuffer = psource->pBuffer; nuclear@1: BufferMode = psource->BufferMode; nuclear@1: Pos = psource->Pos; nuclear@1: DataSize = psource->DataSize; nuclear@1: return 1; nuclear@1: } nuclear@1: */ nuclear@1: nuclear@1: // Initializes buffering to a certain mode nuclear@1: bool BufferedFile::SetBufferMode(BufferModeType mode) nuclear@1: { nuclear@1: if (!pBuffer) nuclear@1: return false; nuclear@1: if (mode == BufferMode) nuclear@1: return true; nuclear@1: nuclear@1: FlushBuffer(); nuclear@1: nuclear@1: // Can't set write mode if we can't write nuclear@1: if ((mode==WriteBuffer) && (!pFile || !pFile->IsWritable()) ) nuclear@1: return 0; nuclear@1: nuclear@1: // And SetMode nuclear@1: BufferMode = mode; nuclear@1: Pos = 0; nuclear@1: DataSize = 0; nuclear@1: return 1; nuclear@1: } nuclear@1: nuclear@1: // Flushes buffer nuclear@1: void BufferedFile::FlushBuffer() nuclear@1: { nuclear@1: switch(BufferMode) nuclear@1: { nuclear@1: case WriteBuffer: nuclear@1: // Write data in buffer nuclear@1: FilePos += pFile->Write(pBuffer,Pos); nuclear@1: Pos = 0; nuclear@1: break; nuclear@1: nuclear@1: case ReadBuffer: nuclear@1: // Seek back & reset buffer data nuclear@1: if ((DataSize-Pos)>0) nuclear@1: FilePos = pFile->LSeek(-(int)(DataSize-Pos), Seek_Cur); nuclear@1: DataSize = 0; nuclear@1: Pos = 0; nuclear@1: break; nuclear@1: default: nuclear@1: // not handled! nuclear@1: break; nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: // Reloads data for ReadBuffer nuclear@1: void BufferedFile::LoadBuffer() nuclear@1: { nuclear@1: if (BufferMode == ReadBuffer) nuclear@1: { nuclear@1: // We should only reload once all of pre-loaded buffer is consumed. nuclear@1: OVR_ASSERT(Pos == DataSize); nuclear@1: nuclear@1: // WARNING: Right now LoadBuffer() assumes the buffer's empty nuclear@1: int sz = pFile->Read(pBuffer,FILEBUFFER_SIZE); nuclear@1: DataSize = sz<0 ? 0 : (unsigned)sz; nuclear@1: Pos = 0; nuclear@1: FilePos += DataSize; nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: nuclear@1: // ** Overridden functions nuclear@1: nuclear@1: // We override all the functions that can possibly nuclear@1: // require buffer mode switch, flush, or extra calculations nuclear@1: nuclear@1: // Tell() requires buffer adjustment nuclear@1: int BufferedFile::Tell() nuclear@1: { nuclear@1: if (BufferMode == ReadBuffer) nuclear@1: return int (FilePos - DataSize + Pos); nuclear@1: nuclear@1: int pos = pFile->Tell(); nuclear@1: // Adjust position based on buffer mode & data nuclear@1: if (pos!=-1) nuclear@1: { nuclear@1: OVR_ASSERT(BufferMode != ReadBuffer); nuclear@1: if (BufferMode == WriteBuffer) nuclear@1: pos += Pos; nuclear@1: } nuclear@1: return pos; nuclear@1: } nuclear@1: nuclear@1: SInt64 BufferedFile::LTell() nuclear@1: { nuclear@1: if (BufferMode == ReadBuffer) nuclear@1: return FilePos - DataSize + Pos; nuclear@1: nuclear@1: SInt64 pos = pFile->LTell(); nuclear@1: if (pos!=-1) nuclear@1: { nuclear@1: OVR_ASSERT(BufferMode != ReadBuffer); nuclear@1: if (BufferMode == WriteBuffer) nuclear@1: pos += Pos; nuclear@1: } nuclear@1: return pos; nuclear@1: } nuclear@1: nuclear@1: int BufferedFile::GetLength() nuclear@1: { nuclear@1: int len = pFile->GetLength(); nuclear@1: // If writing through buffer, file length may actually be bigger nuclear@1: if ((len!=-1) && (BufferMode==WriteBuffer)) nuclear@1: { nuclear@1: int currPos = pFile->Tell() + Pos; nuclear@1: if (currPos>len) nuclear@1: len = currPos; nuclear@1: } nuclear@1: return len; nuclear@1: } nuclear@1: SInt64 BufferedFile::LGetLength() nuclear@1: { nuclear@1: SInt64 len = pFile->LGetLength(); nuclear@1: // If writing through buffer, file length may actually be bigger nuclear@1: if ((len!=-1) && (BufferMode==WriteBuffer)) nuclear@1: { nuclear@1: SInt64 currPos = pFile->LTell() + Pos; nuclear@1: if (currPos>len) nuclear@1: len = currPos; nuclear@1: } nuclear@1: return len; nuclear@1: } nuclear@1: nuclear@1: /* nuclear@1: bool BufferedFile::Stat(FileStats *pfs) nuclear@1: { nuclear@1: // Have to fix up length is stat nuclear@1: if (pFile->Stat(pfs)) nuclear@1: { nuclear@1: if (BufferMode==WriteBuffer) nuclear@1: { nuclear@1: SInt64 currPos = pFile->LTell() + Pos; nuclear@1: if (currPos > pfs->Size) nuclear@1: { nuclear@1: pfs->Size = currPos; nuclear@1: // ?? nuclear@1: pfs->Blocks = (pfs->Size+511) >> 9; nuclear@1: } nuclear@1: } nuclear@1: return 1; nuclear@1: } nuclear@1: return 0; nuclear@1: } nuclear@1: */ nuclear@1: nuclear@1: int BufferedFile::Write(const UByte *psourceBuffer, int numBytes) nuclear@1: { nuclear@1: if ( (BufferMode==WriteBuffer) || SetBufferMode(WriteBuffer)) nuclear@1: { nuclear@1: // If not data space in buffer, flush nuclear@1: if ((FILEBUFFER_SIZE-(int)Pos)FILEBUFFER_TOLERANCE) nuclear@1: { nuclear@1: int sz = pFile->Write(psourceBuffer,numBytes); nuclear@1: if (sz > 0) nuclear@1: FilePos += sz; nuclear@1: return sz; nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: // Enough space in buffer.. so copy to it nuclear@1: memcpy(pBuffer+Pos, psourceBuffer, numBytes); nuclear@1: Pos += numBytes; nuclear@1: return numBytes; nuclear@1: } nuclear@1: int sz = pFile->Write(psourceBuffer,numBytes); nuclear@1: if (sz > 0) nuclear@1: FilePos += sz; nuclear@1: return sz; nuclear@1: } nuclear@1: nuclear@1: int BufferedFile::Read(UByte *pdestBuffer, int numBytes) nuclear@1: { nuclear@1: if ( (BufferMode==ReadBuffer) || SetBufferMode(ReadBuffer)) nuclear@1: { nuclear@1: // Data in buffer... copy it nuclear@1: if ((int)(DataSize-Pos) >= numBytes) nuclear@1: { nuclear@1: memcpy(pdestBuffer, pBuffer+Pos, numBytes); nuclear@1: Pos += numBytes; nuclear@1: return numBytes; nuclear@1: } nuclear@1: nuclear@1: // Not enough data in buffer, copy buffer nuclear@1: int readBytes = DataSize-Pos; nuclear@1: memcpy(pdestBuffer, pBuffer+Pos, readBytes); nuclear@1: numBytes -= readBytes; nuclear@1: pdestBuffer += readBytes; nuclear@1: Pos = DataSize; nuclear@1: nuclear@1: // Don't reload buffer if more then tolerance nuclear@1: // (No major advantage, and we don't want to write a loop) nuclear@1: if (numBytes>FILEBUFFER_TOLERANCE) nuclear@1: { nuclear@1: numBytes = pFile->Read(pdestBuffer,numBytes); nuclear@1: if (numBytes > 0) nuclear@1: { nuclear@1: FilePos += numBytes; nuclear@1: Pos = DataSize = 0; nuclear@1: } nuclear@1: return readBytes + ((numBytes==-1) ? 0 : numBytes); nuclear@1: } nuclear@1: nuclear@1: // Reload the buffer nuclear@1: // WARNING: Right now LoadBuffer() assumes the buffer's empty nuclear@1: LoadBuffer(); nuclear@1: if ((int)(DataSize-Pos) < numBytes) nuclear@1: numBytes = (int)DataSize-Pos; nuclear@1: nuclear@1: memcpy(pdestBuffer, pBuffer+Pos, numBytes); nuclear@1: Pos += numBytes; nuclear@1: return numBytes + readBytes; nuclear@1: nuclear@1: /* nuclear@1: // Alternative Read implementation. The one above is probably better nuclear@1: // due to FILEBUFFER_TOLERANCE. nuclear@1: int total = 0; nuclear@1: nuclear@1: do { nuclear@1: int bufferBytes = (int)(DataSize-Pos); nuclear@1: int copyBytes = (bufferBytes > numBytes) ? numBytes : bufferBytes; nuclear@1: nuclear@1: memcpy(pdestBuffer, pBuffer+Pos, copyBytes); nuclear@1: numBytes -= copyBytes; nuclear@1: pdestBuffer += copyBytes; nuclear@1: Pos += copyBytes; nuclear@1: total += copyBytes; nuclear@1: nuclear@1: if (numBytes == 0) nuclear@1: break; nuclear@1: LoadBuffer(); nuclear@1: nuclear@1: } while (DataSize > 0); nuclear@1: nuclear@1: return total; nuclear@1: */ nuclear@1: } nuclear@1: int sz = pFile->Read(pdestBuffer,numBytes); nuclear@1: if (sz > 0) nuclear@1: FilePos += sz; nuclear@1: return sz; nuclear@1: } nuclear@1: nuclear@1: nuclear@1: int BufferedFile::SkipBytes(int numBytes) nuclear@1: { nuclear@1: int skippedBytes = 0; nuclear@1: nuclear@1: // Special case for skipping a little data in read buffer nuclear@1: if (BufferMode==ReadBuffer) nuclear@1: { nuclear@1: skippedBytes = (((int)DataSize-(int)Pos) >= numBytes) ? numBytes : (DataSize-Pos); nuclear@1: Pos += skippedBytes; nuclear@1: numBytes -= skippedBytes; nuclear@1: } nuclear@1: nuclear@1: if (numBytes) nuclear@1: { nuclear@1: numBytes = pFile->SkipBytes(numBytes); nuclear@1: // Make sure we return the actual number skipped, or error nuclear@1: if (numBytes!=-1) nuclear@1: { nuclear@1: skippedBytes += numBytes; nuclear@1: FilePos += numBytes; nuclear@1: Pos = DataSize = 0; nuclear@1: } nuclear@1: else if (skippedBytes <= 0) nuclear@1: skippedBytes = -1; nuclear@1: } nuclear@1: return skippedBytes; nuclear@1: } nuclear@1: nuclear@1: int BufferedFile::BytesAvailable() nuclear@1: { nuclear@1: int available = pFile->BytesAvailable(); nuclear@1: // Adjust available size based on buffers nuclear@1: switch(BufferMode) nuclear@1: { nuclear@1: case ReadBuffer: nuclear@1: available += DataSize-Pos; nuclear@1: break; nuclear@1: case WriteBuffer: nuclear@1: available -= Pos; nuclear@1: if (available<0) nuclear@1: available= 0; nuclear@1: break; nuclear@1: default: nuclear@1: break; nuclear@1: } nuclear@1: return available; nuclear@1: } nuclear@1: nuclear@1: bool BufferedFile::Flush() nuclear@1: { nuclear@1: FlushBuffer(); nuclear@1: return pFile->Flush(); nuclear@1: } nuclear@1: nuclear@1: // Seeking could be optimized better.. nuclear@1: int BufferedFile::Seek(int offset, int origin) nuclear@1: { nuclear@1: if (BufferMode == ReadBuffer) nuclear@1: { nuclear@1: if (origin == Seek_Cur) nuclear@1: { nuclear@1: // Seek can fall either before or after Pos in the buffer, nuclear@1: // but it must be within bounds. nuclear@1: if (((unsigned(offset) + Pos)) <= DataSize) nuclear@1: { nuclear@1: Pos += offset; nuclear@1: return int (FilePos - DataSize + Pos); nuclear@1: } nuclear@1: nuclear@1: // Lightweight buffer "Flush". We do this to avoid an extra seek nuclear@1: // back operation which would take place if we called FlushBuffer directly. nuclear@1: origin = Seek_Set; nuclear@1: OVR_ASSERT(((FilePos - DataSize + Pos) + (UInt64)offset) < ~(UInt64)0); nuclear@1: offset = (int)(FilePos - DataSize + Pos) + offset; nuclear@1: Pos = DataSize = 0; nuclear@1: } nuclear@1: else if (origin == Seek_Set) nuclear@1: { nuclear@1: if (((unsigned)offset - (FilePos-DataSize)) <= DataSize) nuclear@1: { nuclear@1: OVR_ASSERT((FilePos-DataSize) < ~(UInt64)0); nuclear@1: Pos = (unsigned)offset - (unsigned)(FilePos-DataSize); nuclear@1: return offset; nuclear@1: } nuclear@1: Pos = DataSize = 0; nuclear@1: } nuclear@1: else nuclear@1: { nuclear@1: FlushBuffer(); nuclear@1: } nuclear@1: } nuclear@1: else nuclear@1: { nuclear@1: FlushBuffer(); nuclear@1: } nuclear@1: nuclear@1: /* nuclear@1: // Old Seek Logic nuclear@1: if (origin == Seek_Cur && offset + Pos < DataSize) nuclear@1: { nuclear@1: //OVR_ASSERT((FilePos - DataSize) >= (FilePos - DataSize + Pos + offset)); nuclear@1: Pos += offset; nuclear@1: OVR_ASSERT(int (Pos) >= 0); nuclear@1: return int (FilePos - DataSize + Pos); nuclear@1: } nuclear@1: else if (origin == Seek_Set && unsigned(offset) >= FilePos - DataSize && unsigned(offset) < FilePos) nuclear@1: { nuclear@1: Pos = unsigned(offset - FilePos + DataSize); nuclear@1: OVR_ASSERT(int (Pos) >= 0); nuclear@1: return int (FilePos - DataSize + Pos); nuclear@1: } nuclear@1: nuclear@1: FlushBuffer(); nuclear@1: */ nuclear@1: nuclear@1: nuclear@1: FilePos = pFile->Seek(offset,origin); nuclear@1: return int (FilePos); nuclear@1: } nuclear@1: nuclear@1: SInt64 BufferedFile::LSeek(SInt64 offset, int origin) nuclear@1: { nuclear@1: if (BufferMode == ReadBuffer) nuclear@1: { nuclear@1: if (origin == Seek_Cur) nuclear@1: { nuclear@1: // Seek can fall either before or after Pos in the buffer, nuclear@1: // but it must be within bounds. nuclear@1: if (((unsigned(offset) + Pos)) <= DataSize) nuclear@1: { nuclear@1: Pos += (unsigned)offset; nuclear@1: return SInt64(FilePos - DataSize + Pos); nuclear@1: } nuclear@1: nuclear@1: // Lightweight buffer "Flush". We do this to avoid an extra seek nuclear@1: // back operation which would take place if we called FlushBuffer directly. nuclear@1: origin = Seek_Set; nuclear@1: offset = (SInt64)(FilePos - DataSize + Pos) + offset; nuclear@1: Pos = DataSize = 0; nuclear@1: } nuclear@1: else if (origin == Seek_Set) nuclear@1: { nuclear@1: if (((UInt64)offset - (FilePos-DataSize)) <= DataSize) nuclear@1: { nuclear@1: Pos = (unsigned)((UInt64)offset - (FilePos-DataSize)); nuclear@1: return offset; nuclear@1: } nuclear@1: Pos = DataSize = 0; nuclear@1: } nuclear@1: else nuclear@1: { nuclear@1: FlushBuffer(); nuclear@1: } nuclear@1: } nuclear@1: else nuclear@1: { nuclear@1: FlushBuffer(); nuclear@1: } nuclear@1: nuclear@1: /* nuclear@1: OVR_ASSERT(BufferMode != NoBuffer); nuclear@1: nuclear@1: if (origin == Seek_Cur && offset + Pos < DataSize) nuclear@1: { nuclear@1: Pos += int (offset); nuclear@1: return FilePos - DataSize + Pos; nuclear@1: } nuclear@1: else if (origin == Seek_Set && offset >= SInt64(FilePos - DataSize) && offset < SInt64(FilePos)) nuclear@1: { nuclear@1: Pos = unsigned(offset - FilePos + DataSize); nuclear@1: return FilePos - DataSize + Pos; nuclear@1: } nuclear@1: nuclear@1: FlushBuffer(); nuclear@1: */ nuclear@1: nuclear@1: FilePos = pFile->LSeek(offset,origin); nuclear@1: return FilePos; nuclear@1: } nuclear@1: nuclear@1: int BufferedFile::CopyFromStream(File *pstream, int byteSize) nuclear@1: { nuclear@1: // We can't rely on overridden Write() nuclear@1: // because delegation doesn't override virtual pointers nuclear@1: // So, just re-implement nuclear@1: UByte buff[0x4000]; nuclear@1: int count = 0; nuclear@1: int szRequest, szRead, szWritten; nuclear@1: nuclear@1: while(byteSize) nuclear@1: { nuclear@1: szRequest = (byteSize > int(sizeof(buff))) ? int(sizeof(buff)) : byteSize; nuclear@1: nuclear@1: szRead = pstream->Read(buff,szRequest); nuclear@1: szWritten = 0; nuclear@1: if (szRead > 0) nuclear@1: szWritten = Write(buff,szRead); nuclear@1: nuclear@1: count +=szWritten; nuclear@1: byteSize-=szWritten; nuclear@1: if (szWritten < szRequest) nuclear@1: break; nuclear@1: } nuclear@1: return count; nuclear@1: } nuclear@1: nuclear@1: // Closing files nuclear@1: bool BufferedFile::Close() nuclear@1: { nuclear@1: switch(BufferMode) nuclear@1: { nuclear@1: case WriteBuffer: nuclear@1: FlushBuffer(); nuclear@1: break; nuclear@1: case ReadBuffer: nuclear@1: // No need to seek back on close nuclear@1: BufferMode = NoBuffer; nuclear@1: break; nuclear@1: default: nuclear@1: break; nuclear@1: } nuclear@1: return pFile->Close(); nuclear@1: } nuclear@1: nuclear@1: nuclear@1: // ***** Global path helpers nuclear@1: nuclear@1: // Find trailing short filename in a path. nuclear@1: const char* OVR_CDECL GetShortFilename(const char* purl) nuclear@1: { nuclear@1: UPInt len = OVR_strlen(purl); nuclear@1: for (UPInt i=len; i>0; i--) nuclear@1: if (purl[i]=='\\' || purl[i]=='/') nuclear@1: return purl+i+1; nuclear@1: return purl; nuclear@1: } nuclear@1: nuclear@1: } // OVR nuclear@1: