void CFileCache::Process() { if (!m_pCache) { CLog::Log(LOGERROR,"CFileCache::Process - sanity failed. no cache strategy"); return; } // setup read chunks size int chunksize = CFile::GetChunkSize(m_source.GetChunkSize(), READ_CACHE_CHUNK_SIZE); // create our read buffer auto_aptr<char> buffer(new char[chunksize]); if (buffer.get() == NULL) { CLog::Log(LOGERROR, "%s - failed to allocate read buffer", __FUNCTION__); return; } CWriteRate limiter; CWriteRate average; while (!m_bStop) { // check for seek events if (m_seekEvent.WaitMSec(0)) { m_seekEvent.Reset(); CLog::Log(LOGDEBUG,"%s, request seek on source to %"PRId64, __FUNCTION__, m_seekPos); m_nSeekResult = m_source.Seek(m_seekPos, SEEK_SET); if (m_nSeekResult != m_seekPos) { CLog::Log(LOGERROR,"%s, error %d seeking. seek returned %"PRId64, __FUNCTION__, (int)GetLastError(), m_nSeekResult); m_seekPossible = m_source.IoControl(IOCTRL_SEEK_POSSIBLE, NULL); } else { m_pCache->Reset(m_seekPos); average.Reset(m_seekPos); limiter.Reset(m_seekPos); m_writePos = m_seekPos; m_readPos = m_seekPos; m_cacheFull = false; } m_seekEnded.Set(); } while (m_writeRate) { if (m_writePos - m_readPos < m_writeRate) { limiter.Reset(m_writePos); break; } if (limiter.Rate(m_writePos) < m_writeRate) break; if (m_seekEvent.WaitMSec(100)) { m_seekEvent.Set(); break; } } int iRead = m_source.Read(buffer.get(), chunksize); if (iRead == 0) { CLog::Log(LOGINFO, "CFileCache::Process - Hit eof."); m_pCache->EndOfInput(); // The thread event will now also cause the wait of an event to return a false. if (AbortableWait(m_seekEvent) == WAIT_SIGNALED) { m_pCache->ClearEndOfInput(); m_seekEvent.Set(); // hack so that later we realize seek is needed } else break; } else if (iRead < 0) m_bStop = true; int iTotalWrite=0; while (!m_bStop && (iTotalWrite < iRead)) { int iWrite = 0; iWrite = m_pCache->WriteToCache(buffer.get()+iTotalWrite, iRead - iTotalWrite); // write should always work. all handling of buffering and errors should be // done inside the cache strategy. only if unrecoverable error happened, WriteToCache would return error and we break. if (iWrite < 0) { CLog::Log(LOGERROR,"CFileCache::Process - error writing to cache"); m_bStop = true; break; } else if (iWrite == 0) { m_cacheFull = true; average.Pause(); m_pCache->m_space.WaitMSec(5); average.Resume(); } else m_cacheFull = false; iTotalWrite += iWrite; // check if seek was asked. otherwise if cache is full we'll freeze. if (m_seekEvent.WaitMSec(0)) { m_seekEvent.Set(); // make sure we get the seek event later. break; } } m_writePos += iTotalWrite; // under estimate write rate by a second, to // avoid uncertainty at start of caching m_writeRateActual = average.Rate(m_writePos, 1000); } }
void CFileCache::Process() { if (!m_pCache) { CLog::Log(LOGERROR,"CFileCache::Process - sanity failed. no cache strategy"); return; } // create our read buffer std::unique_ptr<char[]> buffer(new char[m_chunkSize]); if (buffer.get() == NULL) { CLog::Log(LOGERROR, "%s - failed to allocate read buffer", __FUNCTION__); return; } CWriteRate limiter; CWriteRate average; bool cacheReachEOF = false; while (!m_bStop) { // Update filesize m_fileSize = m_source.GetLength(); // check for seek events if (m_seekEvent.WaitMSec(0)) { m_seekEvent.Reset(); int64_t cacheMaxPos = m_pCache->CachedDataEndPosIfSeekTo(m_seekPos); cacheReachEOF = (cacheMaxPos == m_fileSize); bool sourceSeekFailed = false; if (!cacheReachEOF) { m_nSeekResult = m_source.Seek(cacheMaxPos, SEEK_SET); if (m_nSeekResult != cacheMaxPos) { CLog::Log(LOGERROR,"CFileCache::Process - Error %d seeking. Seek returned %" PRId64, (int)GetLastError(), m_nSeekResult); m_seekPossible = m_source.IoControl(IOCTRL_SEEK_POSSIBLE, NULL); sourceSeekFailed = true; } } if (!sourceSeekFailed) { const bool bCompleteReset = m_pCache->Reset(m_seekPos, false); m_readPos = m_seekPos; m_writePos = m_pCache->CachedDataEndPos(); assert(m_writePos == cacheMaxPos); average.Reset(m_writePos, bCompleteReset); // Can only recalculate new average from scratch after a full reset (empty cache) limiter.Reset(m_writePos); m_nSeekResult = m_seekPos; } m_seekEnded.Set(); } while (m_writeRate) { if (m_writePos - m_readPos < m_writeRate * g_advancedSettings.m_readBufferFactor) { limiter.Reset(m_writePos); break; } if (limiter.Rate(m_writePos) < m_writeRate * g_advancedSettings.m_readBufferFactor) break; if (m_seekEvent.WaitMSec(100)) { if (!m_bStop) m_seekEvent.Set(); break; } } size_t maxWrite = m_pCache->GetMaxWriteSize(m_chunkSize); /* Only read from source if there's enough write space in the cache * else we may keep disposing data and seeking back on (slow) source */ if (maxWrite == 0 && !cacheReachEOF) { m_pCache->m_space.WaitMSec(5); continue; } ssize_t iRead = 0; if (!cacheReachEOF) iRead = m_source.Read(buffer.get(), maxWrite); if (iRead == 0) { // Check for actual EOF and retry as long as we still have data in our cache if (m_writePos < m_fileSize && m_pCache->WaitForData(0, 0) > 0) { CLog::Log(LOGDEBUG, "CFileCache::Process - Source read didn't return any data! Will retry."); // Wait a bit: if (m_seekEvent.WaitMSec(5000)) { if (!m_bStop) m_seekEvent.Set(); // hack so that later we realize seek is needed } // and retry: continue; // while (!m_bStop) } else { CLog::Log(LOGINFO, "CFileCache::Process - Source read didn't return any data! Hit eof(?)"); m_pCache->EndOfInput(); // The thread event will now also cause the wait of an event to return a false. if (AbortableWait(m_seekEvent) == WAIT_SIGNALED) { m_pCache->ClearEndOfInput(); if (!m_bStop) m_seekEvent.Set(); // hack so that later we realize seek is needed } else break; // while (!m_bStop) } } else if (iRead < 0) // Fatal error { CLog::Log(LOGDEBUG, "CFileCache::Process - Source read returned a fatal error! Will wait for buffer to empty."); while (m_pCache->WaitForData(0, 0) > 0) { if (m_seekEvent.WaitMSec(100)) { break; } } break; // while (!m_bStop) } int iTotalWrite = 0; while (!m_bStop && (iTotalWrite < iRead)) { int iWrite = 0; iWrite = m_pCache->WriteToCache(buffer.get() + iTotalWrite, iRead - iTotalWrite); // write should always work. all handling of buffering and errors should be // done inside the cache strategy. only if unrecoverable error happened, WriteToCache would return error and we break. if (iWrite < 0) { CLog::Log(LOGERROR,"CFileCache::Process - error writing to cache"); m_bStop = true; break; } else if (iWrite == 0) { m_pCache->m_space.WaitMSec(5); } iTotalWrite += iWrite; // check if seek was asked. otherwise if cache is full we'll freeze. if (m_seekEvent.WaitMSec(0)) { if (!m_bStop) m_seekEvent.Set(); // make sure we get the seek event later. break; } } m_writePos += iTotalWrite; // under estimate write rate by a second, to // avoid uncertainty at start of caching m_writeRateActual = average.Rate(m_writePos, 1000); } }