Merge pull request #25808 from CastagnaIT/fix_url_parse
[xbmc.git] / xbmc / filesystem / FileCache.cpp
blob94f7bd966f0e57f6bf58cec5995a526d0d5bd8a0
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "FileCache.h"
11 #include "CircularCache.h"
12 #include "ServiceBroker.h"
13 #include "URL.h"
14 #include "settings/Settings.h"
15 #include "settings/SettingsComponent.h"
16 #include "threads/Thread.h"
17 #include "utils/log.h"
19 #include <mutex>
21 #if !defined(TARGET_WINDOWS)
22 #include "platform/posix/ConvUtils.h"
23 #endif
25 #include <algorithm>
26 #include <cassert>
27 #include <inttypes.h>
28 #include <memory>
30 #ifdef TARGET_POSIX
31 #include "platform/posix/ConvUtils.h"
32 #endif
34 using namespace XFILE;
36 class CWriteRate
38 public:
39 CWriteRate() : m_stamp(std::chrono::steady_clock::now()), m_time(std::chrono::milliseconds(0))
41 m_pos = 0;
42 m_size = 0;
45 void Reset(int64_t pos, bool bResetAll = true)
47 m_stamp = std::chrono::steady_clock::now();
48 m_pos = pos;
50 if (bResetAll)
52 m_size = 0;
53 m_time = std::chrono::milliseconds(0);
57 uint32_t Rate(int64_t pos, uint32_t time_bias = 0)
59 auto ts = std::chrono::steady_clock::now();
61 m_size += (pos - m_pos);
62 m_time += std::chrono::duration_cast<std::chrono::milliseconds>(ts - m_stamp);
63 m_pos = pos;
64 m_stamp = ts;
66 if (m_time == std::chrono::milliseconds(0))
67 return 0;
69 return static_cast<uint32_t>(1000 * (m_size / (m_time.count() + time_bias)));
72 private:
73 std::chrono::time_point<std::chrono::steady_clock> m_stamp;
74 int64_t m_pos;
75 std::chrono::milliseconds m_time;
76 int64_t m_size;
79 CFileCache::CFileCache(const unsigned int flags)
80 : CThread("FileCache"), m_fileSize(0), m_flags(flags)
84 CFileCache::~CFileCache()
86 Close();
89 IFile *CFileCache::GetFileImp()
91 return m_source.GetImplementation();
94 bool CFileCache::Open(const CURL& url)
96 Close();
98 std::unique_lock<CCriticalSection> lock(m_sync);
100 m_sourcePath = url.GetRedacted();
102 CLog::Log(LOGDEBUG, "CFileCache::{} - <{}> opening", __FUNCTION__, m_sourcePath);
104 // Opening the source file.
105 // The READ_NO_CACHE and READ_NO_BUFFER flags are required to avoid create other intances of
106 // FileCache or StreamBuffer since CFile::Open is called again in loop
107 if (!m_source.Open(url.Get(), READ_NO_CACHE | READ_TRUNCATED | READ_NO_BUFFER))
109 CLog::Log(LOGERROR, "CFileCache::{} - <{}> failed to open", __FUNCTION__, m_sourcePath);
110 Close();
111 return false;
114 const auto settings = CServiceBroker::GetSettingsComponent()->GetSettings();
115 if (!settings)
116 return false;
118 const unsigned int cacheMemSize =
119 settings->GetInt(CSettings::SETTING_FILECACHE_MEMORYSIZE) * 1024 * 1024;
121 m_source.IoControl(IOCTRL_SET_CACHE, this);
123 bool retry = false;
124 m_source.IoControl(IOCTRL_SET_RETRY, &retry); // We already handle retrying ourselves
126 // check if source can seek
127 m_seekPossible = m_source.IoControl(IOCTRL_SEEK_POSSIBLE, NULL);
129 // Determine the best chunk size we can use
130 m_chunkSize = CFile::DetermineChunkSize(m_source.GetChunkSize(),
131 settings->GetInt(CSettings::SETTING_FILECACHE_CHUNKSIZE));
132 CLog::Log(LOGDEBUG,
133 "CFileCache::{} - <{}> source chunk size is {}, setting cache chunk size to {}",
134 __FUNCTION__, m_sourcePath, m_source.GetChunkSize(), m_chunkSize);
136 m_fileSize = m_source.GetLength();
138 if (!m_pCache)
140 if (cacheMemSize == 0)
142 // Use cache on disk
143 m_pCache = std::make_unique<CSimpleFileCache>();
144 m_forwardCacheSize = 0;
145 m_maxForward = m_fileSize;
147 else
149 size_t cacheSize;
150 if (m_fileSize > 0 && m_fileSize < cacheMemSize && !(m_flags & READ_AUDIO_VIDEO))
152 // Cap cache size by filesize, but not for audio/video files as those may grow.
153 // We don't need to take into account READ_MULTI_STREAM here as that's only used for audio/video
154 cacheSize = m_fileSize;
156 // Cap chunk size by cache size
157 if (m_chunkSize > cacheSize)
158 m_chunkSize = cacheSize;
160 else
162 cacheSize = cacheMemSize;
164 // NOTE: READ_MULTI_STREAM is only used with READ_AUDIO_VIDEO
165 if (m_flags & READ_MULTI_STREAM)
167 // READ_MULTI_STREAM requires double buffering, so use half the amount of memory for each buffer
168 cacheSize /= 2;
171 // Make sure cache can at least hold 2 chunks
172 if (cacheSize < m_chunkSize * 2)
173 cacheSize = m_chunkSize * 2;
176 if (m_flags & READ_MULTI_STREAM)
177 CLog::Log(LOGDEBUG, "CFileCache::{} - <{}> using double memory cache each sized {} bytes",
178 __FUNCTION__, m_sourcePath, cacheSize);
179 else
180 CLog::Log(LOGDEBUG, "CFileCache::{} - <{}> using single memory cache sized {} bytes",
181 __FUNCTION__, m_sourcePath, cacheSize);
183 const size_t back = cacheSize / 4;
184 const size_t front = cacheSize - back;
186 m_pCache = std::make_unique<CCircularCache>(front, back);
187 m_forwardCacheSize = front;
188 m_maxForward = m_forwardCacheSize;
191 if (m_flags & READ_MULTI_STREAM)
193 // If READ_MULTI_STREAM flag is set: Double buffering is required
194 m_pCache = std::make_unique<CDoubleCache>(m_pCache.release());
198 // open cache strategy
199 if (!m_pCache || m_pCache->Open() != CACHE_RC_OK)
201 CLog::Log(LOGERROR, "CFileCache::{} - <{}> failed to open cache", __FUNCTION__, m_sourcePath);
202 Close();
203 return false;
206 m_readPos = 0;
207 m_writePos = 0;
208 m_writeRate = 1024 * 1024;
209 m_writeRateActual = 0;
210 m_writeRateLowSpeed = 0;
211 m_bFilling = true;
212 m_seekEvent.Reset();
213 m_seekEnded.Reset();
215 CThread::Create(false);
217 return true;
220 void CFileCache::Process()
222 if (!m_pCache)
224 CLog::Log(LOGERROR, "CFileCache::{} - <{}> sanity failed. no cache strategy", __FUNCTION__,
225 m_sourcePath);
226 return;
229 // create our read buffer
230 std::unique_ptr<char[]> buffer(new char[m_chunkSize]);
231 if (buffer == nullptr)
233 CLog::Log(LOGERROR, "CFileCache::{} - <{}> failed to allocate read buffer", __FUNCTION__,
234 m_sourcePath);
235 return;
238 const auto settings = CServiceBroker::GetSettingsComponent()->GetSettings();
239 if (!settings)
240 return;
242 float readFactor = settings->GetInt(CSettings::SETTING_FILECACHE_READFACTOR) / 100.0f;
244 const bool useAdaptativeReadFactor = (readFactor < 1.0f);
246 CWriteRate limiter;
247 CWriteRate average;
249 while (!m_bStop)
251 // Update filesize
252 m_fileSize = m_source.GetLength();
254 // check for seek events
255 if (m_seekEvent.Wait(0ms))
257 m_seekEvent.Reset();
258 const int64_t cacheMaxPos = m_pCache->CachedDataEndPosIfSeekTo(m_seekPos);
259 const bool cacheReachEOF = (cacheMaxPos == m_fileSize);
261 bool sourceSeekFailed = false;
262 if (!cacheReachEOF)
264 m_nSeekResult = m_source.Seek(cacheMaxPos, SEEK_SET);
265 if (m_nSeekResult != cacheMaxPos)
267 CLog::Log(LOGERROR, "CFileCache::{} - <{}> error {} seeking. Seek returned {}",
268 __FUNCTION__, m_sourcePath, GetLastError(), m_nSeekResult);
269 m_seekPossible = m_source.IoControl(IOCTRL_SEEK_POSSIBLE, NULL);
270 sourceSeekFailed = true;
274 if (!sourceSeekFailed)
276 const bool bCompleteReset = m_pCache->Reset(m_seekPos);
277 m_readPos = m_seekPos;
278 m_writePos = m_pCache->CachedDataEndPos();
279 assert(m_writePos == cacheMaxPos);
280 average.Reset(m_writePos, bCompleteReset); // Can only recalculate new average from scratch after a full reset (empty cache)
281 limiter.Reset(m_writePos);
282 m_nSeekResult = m_seekPos;
283 if (bCompleteReset)
285 CLog::Log(LOGDEBUG,
286 "CFileCache::{} - <{}> cache completely reset for seek to position {}",
287 __FUNCTION__, m_sourcePath, m_seekPos);
288 m_bFilling = true;
289 m_writeRateLowSpeed = 0;
293 m_seekEnded.Set();
296 // variable read factor based on cache level
297 if (useAdaptativeReadFactor)
299 // cache level [0.0 - 1.0]
300 const double level = static_cast<double>(m_writePos - m_readPos) / m_maxForward;
301 readFactor = static_cast<float>(level * -2.5 + 4.0); // read factor [4.0x - 1.5x]
304 while (m_writeRate)
306 if (m_writePos - m_readPos < m_writeRate * readFactor)
308 limiter.Reset(m_writePos);
309 break;
312 if (limiter.Rate(m_writePos) < m_writeRate * readFactor)
313 break;
315 if (m_seekEvent.Wait(m_processWait))
317 if (!m_bStop)
318 m_seekEvent.Set();
319 break;
323 const int64_t maxWrite = m_pCache->GetMaxWriteSize(m_chunkSize);
324 int64_t maxSourceRead = m_chunkSize;
325 // Cap source read size by space available between current write position and EOF
326 if (m_fileSize != 0)
327 maxSourceRead = std::min(maxSourceRead, m_fileSize - m_writePos);
329 /* Only read from source if there's enough write space in the cache
330 * else we may keep disposing data and seeking back on (slow) source
332 if (maxWrite < maxSourceRead)
334 // Wait until sufficient cache write space is available
335 m_pCache->m_space.Wait(5ms);
336 continue;
339 ssize_t iRead = 0;
340 if (maxSourceRead > 0)
341 iRead = m_source.Read(buffer.get(), maxSourceRead);
342 if (iRead <= 0)
344 // Check for actual EOF and retry as long as we still have data in our cache
345 if (m_writePos < m_fileSize && m_pCache->WaitForData(0, 0ms) > 0)
347 CLog::Log(LOGWARNING, "CFileCache::{} - <{}> source read returned {}! Will retry",
348 __FUNCTION__, m_sourcePath, iRead);
350 // Wait a bit:
351 if (m_seekEvent.Wait(2000ms))
353 if (!m_bStop)
354 m_seekEvent.Set(); // hack so that later we realize seek is needed
357 // and retry:
358 continue; // while (!m_bStop)
360 else
362 if (iRead < 0)
363 CLog::Log(LOGERROR,
364 "{} - <{}> source read failed with {}!", __FUNCTION__, m_sourcePath, iRead);
365 else if (m_fileSize == 0)
366 CLog::Log(LOGDEBUG,
367 "CFileCache::{} - <{}> source read didn't return any data! Hit eof(?)",
368 __FUNCTION__, m_sourcePath);
369 else if (m_writePos < m_fileSize)
370 CLog::Log(LOGERROR,
371 "CFileCache::{} - <{}> source read didn't return any data before eof!",
372 __FUNCTION__, m_sourcePath);
373 else
374 CLog::Log(LOGDEBUG, "CFileCache::{} - <{}> source read hit eof", __FUNCTION__,
375 m_sourcePath);
377 m_pCache->EndOfInput();
379 // The thread event will now also cause the wait of an event to return a false.
380 if (AbortableWait(m_seekEvent) == WAIT_SIGNALED)
382 m_pCache->ClearEndOfInput();
383 if (!m_bStop)
384 m_seekEvent.Set(); // hack so that later we realize seek is needed
386 else
387 break; // while (!m_bStop)
391 int iTotalWrite = 0;
392 while (!m_bStop && (iTotalWrite < iRead))
394 int iWrite = 0;
395 iWrite = m_pCache->WriteToCache(buffer.get() + iTotalWrite, iRead - iTotalWrite);
397 // write should always work. all handling of buffering and errors should be
398 // done inside the cache strategy. only if unrecoverable error happened, WriteToCache would return error and we break.
399 if (iWrite < 0)
401 CLog::Log(LOGERROR, "CFileCache::{} - <{}> error writing to cache", __FUNCTION__,
402 m_sourcePath);
403 m_bStop = true;
404 break;
406 else if (iWrite == 0)
408 m_pCache->m_space.Wait(5ms);
411 iTotalWrite += iWrite;
413 // check if seek was asked. otherwise if cache is full we'll freeze.
414 if (m_seekEvent.Wait(0ms))
416 if (!m_bStop)
417 m_seekEvent.Set(); // make sure we get the seek event later.
418 break;
422 m_writePos += iTotalWrite;
424 // under estimate write rate by a second, to
425 // avoid uncertainty at start of caching
426 m_writeRateActual = average.Rate(m_writePos, 1000);
428 /* NOTE: We can only reliably test for low speed condition, when the cache is *really*
429 * filling. This is because as soon as it's full the average-
430 * rate will become approximately the current-rate which can flag false
431 * low read-rate conditions.
433 if (m_bFilling && m_forwardCacheSize != 0)
435 const int64_t forward = m_pCache->WaitForData(0, 0ms);
436 if (forward + m_chunkSize >= m_forwardCacheSize)
438 if (m_writeRateActual < m_writeRate)
439 m_writeRateLowSpeed = m_writeRateActual;
441 m_bFilling = false;
447 void CFileCache::OnExit()
449 m_bStop = true;
451 // make sure cache is set to mark end of file (read may be waiting).
452 if (m_pCache)
453 m_pCache->EndOfInput();
455 // just in case someone's waiting...
456 m_seekEnded.Set();
459 bool CFileCache::Exists(const CURL& url)
461 return CFile::Exists(url.Get());
464 int CFileCache::Stat(const CURL& url, struct __stat64* buffer)
466 return CFile::Stat(url.Get(), buffer);
469 ssize_t CFileCache::Read(void* lpBuf, size_t uiBufSize)
471 std::unique_lock<CCriticalSection> lock(m_sync);
472 if (!m_pCache)
474 CLog::Log(LOGERROR, "CFileCache::{} - <{}> sanity failed. no cache strategy!", __FUNCTION__,
475 m_sourcePath);
476 return -1;
478 int64_t iRc;
480 if (uiBufSize > SSIZE_MAX)
481 uiBufSize = SSIZE_MAX;
483 retry:
484 // attempt to read
485 iRc = m_pCache->ReadFromCache((char *)lpBuf, uiBufSize);
486 if (iRc > 0)
488 m_readPos += iRc;
489 return (int)iRc;
492 if (iRc == CACHE_RC_WOULD_BLOCK)
494 // just wait for some data to show up
495 iRc = m_pCache->WaitForData(1, 10s);
496 if (iRc > 0)
497 goto retry;
500 if (iRc == CACHE_RC_TIMEOUT)
502 CLog::Log(LOGWARNING, "CFileCache::{} - <{}> timeout waiting for data", __FUNCTION__,
503 m_sourcePath);
504 return -1;
507 if (iRc == 0)
508 return 0;
510 // unknown error code
511 CLog::Log(LOGERROR, "CFileCache::{} - <{}> cache strategy returned unknown error code {}",
512 __FUNCTION__, m_sourcePath, (int)iRc);
513 return -1;
516 int64_t CFileCache::Seek(int64_t iFilePosition, int iWhence)
518 std::unique_lock<CCriticalSection> lock(m_sync);
520 if (!m_pCache)
522 CLog::Log(LOGERROR, "CFileCache::{} - <{}> sanity failed. no cache strategy!", __FUNCTION__,
523 m_sourcePath);
524 return -1;
527 int64_t iCurPos = m_readPos;
528 int64_t iTarget = iFilePosition;
529 if (iWhence == SEEK_END)
530 iTarget = m_fileSize + iTarget;
531 else if (iWhence == SEEK_CUR)
532 iTarget = iCurPos + iTarget;
533 else if (iWhence != SEEK_SET)
534 return -1;
536 if (iTarget == m_readPos)
537 return m_readPos;
539 if ((m_nSeekResult = m_pCache->Seek(iTarget)) != iTarget)
541 if (m_seekPossible == 0)
542 return m_nSeekResult;
544 // Never request closer to end than one chunk. Speeds up tag reading
545 m_seekPos = std::min(iTarget, std::max((int64_t)0, m_fileSize - m_chunkSize));
547 m_seekEvent.Set();
548 while (!m_seekEnded.Wait(100ms))
550 // SeekEnded will never be set if FileCache thread is not running
551 if (!CThread::IsRunning())
552 return -1;
555 /* wait for any remaining data */
556 if(m_seekPos < iTarget)
558 CLog::Log(LOGDEBUG, "CFileCache::{} - <{}> waiting for position {}", __FUNCTION__,
559 m_sourcePath, iTarget);
560 if (m_pCache->WaitForData(static_cast<uint32_t>(iTarget - m_seekPos), 10s) <
561 iTarget - m_seekPos)
563 CLog::Log(LOGWARNING, "CFileCache::{} - <{}> failed to get remaining data", __FUNCTION__,
564 m_sourcePath);
565 return -1;
567 m_pCache->Seek(iTarget);
569 m_readPos = iTarget;
570 m_seekEvent.Reset();
572 else
573 m_readPos = iTarget;
575 return iTarget;
578 void CFileCache::Close()
580 StopThread();
582 std::unique_lock<CCriticalSection> lock(m_sync);
583 if (m_pCache)
584 m_pCache->Close();
586 m_source.Close();
589 int64_t CFileCache::GetPosition()
591 return m_readPos;
594 int64_t CFileCache::GetLength()
596 return m_fileSize;
599 void CFileCache::StopThread(bool bWait /*= true*/)
601 m_bStop = true;
602 //Process could be waiting for seekEvent
603 m_seekEvent.Set();
604 CThread::StopThread(bWait);
607 const std::string CFileCache::GetProperty(XFILE::FileProperty type, const std::string &name) const
609 if (!m_source.GetImplementation())
610 return IFile::GetProperty(type, name);
612 return m_source.GetImplementation()->GetProperty(type, name);
615 int CFileCache::IoControl(EIoControl request, void* param)
617 if (request == IOCTRL_CACHE_STATUS)
619 SCacheStatus* status = (SCacheStatus*)param;
620 status->maxforward = m_maxForward;
621 status->forward = m_pCache->WaitForData(0, 0ms);
622 status->maxrate = m_writeRate;
623 status->currate = m_writeRateActual;
624 status->lowrate = m_writeRateLowSpeed;
625 m_writeRateLowSpeed = 0; // Reset low speed condition
626 return 0;
629 if (request == IOCTRL_CACHE_SETRATE)
631 m_writeRate = *static_cast<uint32_t*>(param);
633 const double mBits = m_writeRate / 1024.0 / 1024.0 * 8.0; // Mbit/s
635 // calculates wait time inversely proportional to the bitrate
636 // and limited between 30 - 100 ms
637 const int wait = std::clamp(static_cast<int>(110.0 - mBits), 30, 100);
639 m_processWait = std::chrono::milliseconds(wait);
641 CLog::Log(LOGDEBUG,
642 "CFileCache::IoControl - setting maxRate to {:.2f} Mbit/s with processWait of {} ms",
643 mBits, wait);
644 return 0;
647 if (request == IOCTRL_SEEK_POSSIBLE)
648 return m_seekPossible;
650 return -1;