| /* ------------------------------------------------------------------ |
| * Copyright (C) 2008 PacketVideo |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either |
| * express or implied. |
| * See the License for the specific language governing permissions |
| * and limitations under the License. |
| * ------------------------------------------------------------------- |
| */ |
| |
| #include "oscl_base.h" |
| #include "oscl_file_cache.h" |
| #include "oscl_mem.h" |
| #include "oscl_file_io.h" |
| #include "oscl_file_native.h" |
| #include "pvlogger.h" |
| |
| OsclFileCache::OsclFileCache(Oscl_File& aContainer): |
| iContainer(aContainer) |
| , _cacheSize(0) |
| , _pCacheBufferStart(NULL) |
| , _cacheFilePosition(0) |
| , _currentCachePos(0) |
| , _endCachePos(0) |
| , _cacheUpdateStart(0) |
| , _cacheUpdateEnd(0) |
| , _fileSize(0) |
| , _nativePosition(0) |
| , iLogger(NULL) |
| { |
| } |
| |
| OsclFileCache::~OsclFileCache() |
| { |
| Close(); |
| } |
| |
| int32 OsclFileCache::Open(uint32 mode, uint32 size) |
| //Called to open the cache for a newly opened file. |
| //The NativeOpen was just called prior to this and was successful. |
| { |
| //should not be called with zero-size cache. |
| OSCL_ASSERT(size > 0); |
| |
| //Save the open parameters |
| _cacheSize = size; |
| _mode = mode; |
| |
| //open logger object only if logging is enabled on this |
| //file |
| if (iContainer.iLogger) |
| iLogger = PVLogger::GetLoggerObject("OsclFileCache"); |
| else |
| iLogger = NULL; |
| |
| PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_DEBUG, |
| (0, "OsclFileCache(0x%x)::Open mode %d size %d", this, mode, size)); |
| |
| // allocate memory for cache |
| // free any old buffer since its size may be different |
| if (_pCacheBufferStart) |
| { |
| OSCL_FREE(_pCacheBufferStart); |
| _pCacheBufferStart = NULL; |
| } |
| _pCacheBufferStart = (uint8*)OSCL_MALLOC(_cacheSize); |
| if (!_pCacheBufferStart) |
| { |
| PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_DEBUG, |
| (0, "OsclFileCache(0x%x)::Open ERROR no memory %d", this)); |
| return (-1);//error |
| } |
| |
| //initialise the cache variables |
| SetCachePosition(0); |
| |
| //get initial file size & native position |
| _fileSize = iContainer.CallNativeSize(); |
| _nativePosition = iContainer.CallNativeTell(); |
| |
| return 0; |
| } |
| |
| |
| void OsclFileCache::Close() |
| { |
| //flush any cache updates |
| SetCachePosition(0); |
| |
| //free the memory for cache buffer |
| if (_pCacheBufferStart) |
| { |
| OSCL_FREE(_pCacheBufferStart); |
| _pCacheBufferStart = NULL; |
| } |
| } |
| |
| /** |
| * Read |
| * Reads data from the file cache buffer and copies into |
| * the buffer supplied (outputBuffer) |
| * |
| * @param outputBuffer pointer to buffer of type void |
| * @param size element size in bytes |
| * @param numelements |
| * max number of elements to read |
| * |
| * @return returns the number of full elements actually read, which |
| * may be less than count if an error occurs or if the end |
| * of the file is encountered before reaching count. |
| */ |
| uint32 OsclFileCache::Read(void* outputBuffer, uint32 size, uint32 numelements) |
| { |
| if (!outputBuffer) |
| { |
| PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_DEBUG, |
| (0, "OsclFileCache(0x%x)::Read ERROR invalid arg ", this)); |
| return 0; |
| } |
| |
| //check for a valid read mode |
| if ((_mode & Oscl_File::MODE_READWRITE) |
| || (_mode & Oscl_File::MODE_READ) |
| || (_mode & Oscl_File::MODE_APPEND) |
| || (_mode & Oscl_File::MODE_READ_PLUS)) |
| { |
| ;//ok to write |
| } |
| else |
| { |
| PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_DEBUG, |
| (0, "OsclFileCache(0x%x)::Read ERROR invalid mode for reading ", this)); |
| return 0;//invalid mode. |
| } |
| |
| uint8* destBuf = (uint8*)(outputBuffer); |
| |
| uint32 bytesToRead = numelements * size; |
| |
| //pull data out of the cache until we run out, then re-fill the cache |
| //as needed until we get the desired amount. |
| |
| while (bytesToRead > 0) |
| { |
| //Break out of the loop if there isn't enough data left |
| //in the file to read a whole element. We don't want to read |
| //a partial element. |
| if ((uint32)(FileSize() - Tell()) < size) |
| break; |
| |
| uint32 bytesInCache = (_endCachePos - _currentCachePos); |
| |
| if (bytesInCache > 0) |
| { |
| //pull out either all data in cache, or number of bytes required, |
| //whichever is less |
| uint32 thisRead = (bytesInCache > bytesToRead) ? bytesToRead : bytesInCache; |
| |
| oscl_memcpy(destBuf, _pCacheBufferStart + _currentCachePos, thisRead); |
| bytesToRead -= thisRead; |
| destBuf += thisRead; |
| |
| //update virtual position |
| _currentCachePos += thisRead; |
| } |
| else |
| { |
| //Re-fill cache from current virtual position |
| int32 retval = FillCacheFromFile(); |
| if (retval != 0) |
| { |
| PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_DEBUG, |
| (0, "OsclFileCache(0x%x)::Read ERROR FillCacheFromFile failed ", this)); |
| break;//error! |
| } |
| |
| //When at EOF, cache will be empty after fillcache, |
| //so break out of loop. |
| if (_endCachePos - _currentCachePos == 0) |
| break; |
| } |
| } |
| |
| //return number of whole elements read. |
| return (size) ? ((size*numelements - bytesToRead) / size) : 0; |
| |
| } |
| |
| |
| /** |
| * Write |
| * Writes data from the input buffer into |
| * the buffer supplied (inputBuffer) |
| * |
| * @param inputBuffer pointer to buffer of type void |
| * @param size element size in bytes |
| * @param numelements |
| * number of elements to write |
| * |
| * @return returns the number of elements written |
| */ |
| int32 OsclFileCache::Write(const void* inputBuffer, uint32 size, uint32 numelements) |
| { |
| if (inputBuffer == NULL) |
| { |
| PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_DEBUG, |
| (0, "OsclFileCache(0x%x)::Write ERROR invalid arg ", this)); |
| return 0; |
| } |
| |
| //write the data only in the mode we are permitted to write |
| if ((_mode & Oscl_File::MODE_READWRITE) |
| || (_mode & Oscl_File::MODE_APPEND) |
| || (_mode & Oscl_File::MODE_READ_PLUS)) |
| { |
| ;//ok to write |
| } |
| else |
| { |
| PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_DEBUG, |
| (0, "OsclFileCache(0x%x)::Write ERROR invalid mode for writing ", this)); |
| return 0;//invalid mode. |
| } |
| |
| //In Append mode, write always happens at the end of file, |
| //so relocate the cache now if needed. |
| if ((_mode & Oscl_File::MODE_APPEND) |
| && (Tell() != FileSize())) |
| { |
| int32 result = SetCachePosition(FileSize()); |
| if (result != 0) |
| { |
| PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_DEBUG, |
| (0, "OsclFileCache(0x%x)::Write ERROR SetCachePosition failed. ", this)); |
| return 0; |
| } |
| } |
| |
| uint8* srcBuf = (uint8*)(inputBuffer); |
| uint32 bytesToWrite = size * numelements; |
| |
| //write into cache, flushing as needed when it fills up. |
| |
| while (bytesToWrite > 0) |
| { |
| uint32 spaceInCache = (_cacheSize - _currentCachePos); |
| |
| if (spaceInCache > 0) |
| { |
| //write to cache |
| uint32 thisWrite = (spaceInCache > bytesToWrite) ? bytesToWrite : spaceInCache; |
| |
| oscl_memcpy((_pCacheBufferStart + _currentCachePos), srcBuf, thisWrite); |
| bytesToWrite -= thisWrite; |
| srcBuf += thisWrite; |
| |
| //keep track of the range of data in the cache that has been updated. |
| if (_cacheUpdateEnd == _cacheUpdateStart) |
| { |
| //first update in this cache |
| _cacheUpdateStart = _currentCachePos; |
| _cacheUpdateEnd = _currentCachePos + thisWrite; |
| } |
| else |
| { |
| //cache has already been updated. Expand the updated range |
| //to include this update. |
| |
| if (_currentCachePos < _cacheUpdateStart) |
| _cacheUpdateStart = _currentCachePos; |
| |
| if ((_currentCachePos + thisWrite) > _cacheUpdateEnd) |
| _cacheUpdateEnd = _currentCachePos + thisWrite; |
| } |
| |
| //update the virtual position. |
| _currentCachePos += thisWrite; |
| |
| //extend the end of cache data if needed |
| if (_endCachePos < _currentCachePos) |
| _endCachePos = _currentCachePos; |
| |
| //extend the virtual file size if needed. |
| if (_fileSize < _cacheFilePosition + _endCachePos) |
| _fileSize = _cacheFilePosition + _endCachePos; |
| |
| //consistency checks. if these asserts fire, there is |
| //a logic error. |
| OSCL_ASSERT(_cacheUpdateEnd >= _cacheUpdateStart); |
| OSCL_ASSERT(_endCachePos >= _currentCachePos); |
| } |
| else |
| { |
| //entire cache is full-- flush updates to disk |
| //and start a new (empty) cache at the current virtual |
| //position. |
| int32 retval = SetCachePosition(Tell()); |
| if (retval != 0) |
| { |
| PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_DEBUG, |
| (0, "OsclFileCache(0x%x)::Write ERROR SetCachePosition failed ", this)); |
| break;//error! |
| } |
| } |
| } |
| |
| //return number of whole elements written. |
| // PNSC-106: Fix division of 0 problem if size is 0 |
| return (size) ? ((size*numelements - bytesToWrite) / size) : 0; |
| |
| } |
| |
| |
| /** |
| * Seek |
| * |
| * @param [in] offset from origin |
| * @param [in] origin: either SEEKSET, SEEKCUR, or SEEKEND |
| * |
| * @return 0 for success. |
| */ |
| int32 OsclFileCache::Seek(int32 offset, Oscl_File::seek_type origin) |
| { |
| //figure out the file position we're trying to seek to |
| int32 pos; |
| switch (origin) |
| { |
| case Oscl_File::SEEKCUR: |
| pos = Tell() + offset; |
| break; |
| case Oscl_File::SEEKSET: |
| pos = 0 + offset; |
| break; |
| case Oscl_File::SEEKEND: |
| pos = FileSize() + offset; |
| break; |
| default: |
| PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_DEBUG, |
| (0, "OsclFileCache(0x%x)::Seek ERROR invalid origin %d", this, origin)); |
| return (-1);//error-- invalid origin! |
| } |
| |
| //don't allow seeking outside valid file size |
| if (pos < 0 || pos > FileSize()) |
| { |
| PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_DEBUG, |
| (0, "OsclFileCache(0x%x)::Seek ERROR invalid seek position %d", this, pos)); |
| return -1;//error |
| } |
| |
| //when seek is in current cache range, just update the |
| //virtual position. |
| if (_cacheFilePosition <= (uint32)pos && (uint32)pos <= (_cacheFilePosition + _endCachePos)) |
| { |
| _currentCachePos = pos - _cacheFilePosition; |
| return 0;//success |
| } |
| |
| //else seeking outside cache |
| |
| //Seek to the real target location. |
| //Always use SEEKSET because the actual file end or current |
| //position may not be accurate at this point. |
| int32 retval; |
| retval = iContainer.CallNativeSeek(pos, Oscl_File::SEEKSET); |
| if (retval != 0) |
| { |
| PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_DEBUG, |
| (0, "OsclFileCache(0x%x)::Seek ERROR CallNativeSeek failed", this)); |
| return retval;//error |
| } |
| |
| //update native position |
| _nativePosition = pos; |
| |
| //Relocate the cache & virtual position |
| retval = SetCachePosition(pos); |
| |
| if (retval != 0) |
| { |
| PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_DEBUG, |
| (0, "OsclFileCache(0x%x)::Seek ERROR SetCachePosition failed", this)); |
| } |
| |
| return retval; |
| } |
| |
| |
| /** |
| * Flush |
| * Flush file to disk. |
| * After this call, previously cached data |
| * is still available. |
| * |
| * @param void |
| * |
| * @return 0 for success. |
| */ |
| int32 OsclFileCache::Flush() |
| { |
| if (_cacheUpdateEnd > _cacheUpdateStart) |
| { |
| //flush updated data to file |
| int32 retval = WriteCacheToFile(); |
| if (retval != 0) |
| { |
| PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_DEBUG, |
| (0, "OsclFileCache(0x%x)::Flush ERROR WriteCacheToFile failed", this)); |
| return retval; |
| } |
| |
| //Note: don't reset cache here, since |
| //we may still want to use the data |
| //in read operations. |
| |
| //flush file to disk. |
| retval = iContainer.CallNativeFlush(); |
| |
| if (retval != 0) |
| { |
| PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_DEBUG, |
| (0, "OsclFileCache(0x%x)::Flush ERROR CallNativeFlush failed", this)); |
| } |
| |
| //not sure if native file position is affected |
| //by a native flush, so query now. |
| _nativePosition = iContainer.CallNativeTell(); |
| |
| return retval; |
| } |
| else |
| { |
| //no updates in cache since last flush |
| //so nothing is needed. |
| |
| return 0; //success! |
| } |
| } |
| |
| |
| /** |
| * SetCachePosition |
| * |
| * Flush any updated data in current cache and reset |
| * the cache location and current virtual position to the |
| * input file position. |
| * |
| * On return, cache is empty. |
| * |
| * @param [in] aNewPos: file position. |
| * |
| * return 0 on success. |
| * |
| */ |
| int32 OsclFileCache::SetCachePosition(uint32 aNewPos) |
| { |
| PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_DEBUG, |
| (0, "OsclFileCache(0x%x)::SetCachePosition curpos %d newpos %d", this, _cacheFilePosition, aNewPos)); |
| |
| //flush any updated data currently in cache. |
| if (_cacheUpdateEnd > _cacheUpdateStart) |
| { |
| int32 result = WriteCacheToFile(); |
| if (result != 0) |
| { |
| PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_DEBUG, |
| (0, "OsclFileCache(0x%x)::SetCachePosition ERROR WriteCacheToFile failed", this)); |
| return result;//error! |
| } |
| } |
| |
| //reset the virtual position and the cache location. |
| _cacheFilePosition = aNewPos; |
| _currentCachePos = 0; |
| |
| //cache is empty |
| _endCachePos = 0; |
| _cacheUpdateStart = _cacheUpdateEnd = 0; |
| |
| return 0;//success |
| } |
| |
| |
| /** |
| * FillCacheFromFile |
| * |
| * Fill cache from current virtual position. |
| * Flush any updated data first. |
| * This will return a full cache, or |
| * if at EOF, a partial or empty cache. |
| * |
| * @param void |
| * |
| * @return 0 on success. |
| */ |
| int32 OsclFileCache::FillCacheFromFile() |
| { |
| PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_DEBUG, |
| (0, "OsclFileCache(0x%x)::FillCacheFromFile vpos %d", this, Tell())); |
| |
| //flush and relocate cache to current virtual |
| //position if needed |
| uint32 newpos = Tell(); |
| if (_cacheFilePosition != newpos |
| || (_cacheUpdateEnd - _cacheUpdateStart) > 0) |
| { |
| int32 retval = SetCachePosition(newpos); |
| if (retval != 0) |
| { |
| PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_DEBUG, |
| (0, "OsclFileCache(0x%x)::FillCacheFromFile ERROR SetCachePosition failed", this)); |
| return retval;//error! |
| } |
| } |
| |
| //Now seek to the read position if needed. |
| if (_nativePosition != newpos) |
| { |
| int32 result = iContainer.CallNativeSeek(newpos, Oscl_File::SEEKSET); |
| if (result != 0) |
| { |
| PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_DEBUG, |
| (0, "OsclFileCache(0x%x)::FillCacheFromFile ERROR CallNativeSeek failed", this)); |
| return result;//error! |
| } |
| |
| //keep track of the native file position |
| _nativePosition = newpos; |
| } |
| |
| //try to fill the cache. If we hit EOF we won't get a full cache. |
| _endCachePos = iContainer.CallNativeRead((void*)_pCacheBufferStart, 1, _cacheSize); |
| |
| PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_DEBUG, |
| (0, "OsclFileCache(0x%x)::FillCacheFromFile got %d bytes", this, _endCachePos)); |
| |
| //update native position |
| _nativePosition += _endCachePos; |
| |
| return 0;//success |
| } |
| |
| |
| /** |
| * WriteCacheToFile |
| * |
| * Writes any updated data in the cache to disk, but does not |
| * otherwise alter cache position or contents. Existing cache |
| * data is still available for read. |
| * |
| * Has a side effect of adjusting the native file position. |
| * |
| * @param void |
| * |
| * @return 0 if successful and a non-zero value otherwise |
| */ |
| int32 OsclFileCache::WriteCacheToFile() |
| { |
| PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_DEBUG, |
| (0, "OsclFileCache(0x%x)::WriteCacheToFile vpos %d", this, Tell())); |
| |
| if (_cacheUpdateEnd > _cacheUpdateStart) |
| { |
| uint32 bytesToWrite = (_cacheUpdateEnd - _cacheUpdateStart); |
| |
| PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_DEBUG, |
| (0, "OsclFileCache(0x%x)::WriteCacheToFile nbytes %d filepos %d start %d end %d " |
| , this, bytesToWrite, _cacheFilePosition, _cacheUpdateStart, _cacheUpdateEnd)); |
| |
| //Seek to the correct write location in the file if needed |
| |
| uint32 pos = _cacheFilePosition + _cacheUpdateStart; |
| |
| if (_nativePosition != pos) |
| { |
| if (_mode & Oscl_File::MODE_APPEND) |
| { |
| //In Append mode, writes automatically happen at the end of file so there is no |
| //need to seek. |
| ; |
| } |
| else |
| { |
| //seek to the write location |
| int32 retval = iContainer.CallNativeSeek(pos, Oscl_File::SEEKSET); |
| if (retval != 0) |
| { |
| PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_DEBUG, |
| (0, "OsclFileCache(0x%x)::WriteCacheToFile ERROR CallNativeSeek failed", this)); |
| return retval;//error! |
| } |
| } |
| |
| _nativePosition = pos; |
| } |
| |
| //write the updated data range. |
| int32 retval = iContainer.CallNativeWrite(_pCacheBufferStart + _cacheUpdateStart, 1, bytesToWrite); |
| |
| //keep track of the native file position. |
| _nativePosition += retval; |
| |
| //clear the updated data range. |
| _cacheUpdateStart = _cacheUpdateEnd = 0; |
| |
| if ((uint32)retval == bytesToWrite) |
| return 0;//success! |
| |
| PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_DEBUG, |
| (0, "OsclFileCache(0x%x)::WriteCacheToFile ERROR CallNativeWrite only wrote %d of %d", this, retval, bytesToWrite)); |
| |
| //At this point, the cache is corrupt, since we lost data. |
| //Recover by checking actual native file values, emptying the cache, |
| //and locating it as close as possible to the desired position. |
| _fileSize = iContainer.CallNativeSize(); |
| _nativePosition = iContainer.CallNativeTell(); |
| uint32 newpos = Tell(); |
| if (newpos > _fileSize) |
| newpos = _fileSize; |
| SetCachePosition(newpos); |
| |
| return (-1); //error |
| } |
| return 0;//success-- nothing to do |
| } |
| |
| |