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