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.
11 #include "CircularCache.h"
12 #include "ServiceBroker.h"
14 #include "settings/Settings.h"
15 #include "settings/SettingsComponent.h"
16 #include "threads/Thread.h"
17 #include "utils/log.h"
21 #if !defined(TARGET_WINDOWS)
22 #include "platform/posix/ConvUtils.h"
31 #include "platform/posix/ConvUtils.h"
34 using namespace XFILE
;
39 CWriteRate() : m_stamp(std::chrono::steady_clock::now()), m_time(std::chrono::milliseconds(0))
45 void Reset(int64_t pos
, bool bResetAll
= true)
47 m_stamp
= std::chrono::steady_clock::now();
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
);
66 if (m_time
== std::chrono::milliseconds(0))
69 return static_cast<uint32_t>(1000 * (m_size
/ (m_time
.count() + time_bias
)));
73 std::chrono::time_point
<std::chrono::steady_clock
> m_stamp
;
75 std::chrono::milliseconds m_time
;
79 CFileCache::CFileCache(const unsigned int flags
)
80 : CThread("FileCache"), m_fileSize(0), m_flags(flags
)
84 CFileCache::~CFileCache()
89 IFile
*CFileCache::GetFileImp()
91 return m_source
.GetImplementation();
94 bool CFileCache::Open(const CURL
& url
)
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 if (!m_source
.Open(url
.Get(), READ_NO_CACHE
| READ_TRUNCATED
| READ_CHUNKED
))
107 CLog::Log(LOGERROR
, "CFileCache::{} - <{}> failed to open", __FUNCTION__
, m_sourcePath
);
112 const auto settings
= CServiceBroker::GetSettingsComponent()->GetSettings();
116 const unsigned int cacheMemSize
=
117 settings
->GetInt(CSettings::SETTING_FILECACHE_MEMORYSIZE
) * 1024 * 1024;
119 m_source
.IoControl(IOCTRL_SET_CACHE
, this);
122 m_source
.IoControl(IOCTRL_SET_RETRY
, &retry
); // We already handle retrying ourselves
124 // check if source can seek
125 m_seekPossible
= m_source
.IoControl(IOCTRL_SEEK_POSSIBLE
, NULL
);
127 // Determine the best chunk size we can use
128 m_chunkSize
= CFile::DetermineChunkSize(m_source
.GetChunkSize(),
129 settings
->GetInt(CSettings::SETTING_FILECACHE_CHUNKSIZE
));
131 "CFileCache::{} - <{}> source chunk size is {}, setting cache chunk size to {}",
132 __FUNCTION__
, m_sourcePath
, m_source
.GetChunkSize(), m_chunkSize
);
134 m_fileSize
= m_source
.GetLength();
138 if (cacheMemSize
== 0)
141 m_pCache
= std::make_unique
<CSimpleFileCache
>();
142 m_forwardCacheSize
= 0;
143 m_maxForward
= m_fileSize
;
148 if (m_fileSize
> 0 && m_fileSize
< cacheMemSize
&& !(m_flags
& READ_AUDIO_VIDEO
))
150 // Cap cache size by filesize, but not for audio/video files as those may grow.
151 // We don't need to take into account READ_MULTI_STREAM here as that's only used for audio/video
152 cacheSize
= m_fileSize
;
154 // Cap chunk size by cache size
155 if (m_chunkSize
> cacheSize
)
156 m_chunkSize
= cacheSize
;
160 cacheSize
= cacheMemSize
;
162 // NOTE: READ_MULTI_STREAM is only used with READ_AUDIO_VIDEO
163 if (m_flags
& READ_MULTI_STREAM
)
165 // READ_MULTI_STREAM requires double buffering, so use half the amount of memory for each buffer
169 // Make sure cache can at least hold 2 chunks
170 if (cacheSize
< m_chunkSize
* 2)
171 cacheSize
= m_chunkSize
* 2;
174 if (m_flags
& READ_MULTI_STREAM
)
175 CLog::Log(LOGDEBUG
, "CFileCache::{} - <{}> using double memory cache each sized {} bytes",
176 __FUNCTION__
, m_sourcePath
, cacheSize
);
178 CLog::Log(LOGDEBUG
, "CFileCache::{} - <{}> using single memory cache sized {} bytes",
179 __FUNCTION__
, m_sourcePath
, cacheSize
);
181 const size_t back
= cacheSize
/ 4;
182 const size_t front
= cacheSize
- back
;
184 m_pCache
= std::make_unique
<CCircularCache
>(front
, back
);
185 m_forwardCacheSize
= front
;
186 m_maxForward
= m_forwardCacheSize
;
189 if (m_flags
& READ_MULTI_STREAM
)
191 // If READ_MULTI_STREAM flag is set: Double buffering is required
192 m_pCache
= std::make_unique
<CDoubleCache
>(m_pCache
.release());
196 // open cache strategy
197 if (!m_pCache
|| m_pCache
->Open() != CACHE_RC_OK
)
199 CLog::Log(LOGERROR
, "CFileCache::{} - <{}> failed to open cache", __FUNCTION__
, m_sourcePath
);
206 m_writeRate
= 1024 * 1024;
207 m_writeRateActual
= 0;
208 m_writeRateLowSpeed
= 0;
213 CThread::Create(false);
218 void CFileCache::Process()
222 CLog::Log(LOGERROR
, "CFileCache::{} - <{}> sanity failed. no cache strategy", __FUNCTION__
,
227 // create our read buffer
228 std::unique_ptr
<char[]> buffer(new char[m_chunkSize
]);
229 if (buffer
== nullptr)
231 CLog::Log(LOGERROR
, "CFileCache::{} - <{}> failed to allocate read buffer", __FUNCTION__
,
236 const auto settings
= CServiceBroker::GetSettingsComponent()->GetSettings();
240 float readFactor
= settings
->GetInt(CSettings::SETTING_FILECACHE_READFACTOR
) / 100.0f
;
242 const bool useAdaptativeReadFactor
= (readFactor
< 1.0f
);
250 m_fileSize
= m_source
.GetLength();
252 // check for seek events
253 if (m_seekEvent
.Wait(0ms
))
256 const int64_t cacheMaxPos
= m_pCache
->CachedDataEndPosIfSeekTo(m_seekPos
);
257 const bool cacheReachEOF
= (cacheMaxPos
== m_fileSize
);
259 bool sourceSeekFailed
= false;
262 m_nSeekResult
= m_source
.Seek(cacheMaxPos
, SEEK_SET
);
263 if (m_nSeekResult
!= cacheMaxPos
)
265 CLog::Log(LOGERROR
, "CFileCache::{} - <{}> error {} seeking. Seek returned {}",
266 __FUNCTION__
, m_sourcePath
, GetLastError(), m_nSeekResult
);
267 m_seekPossible
= m_source
.IoControl(IOCTRL_SEEK_POSSIBLE
, NULL
);
268 sourceSeekFailed
= true;
272 if (!sourceSeekFailed
)
274 const bool bCompleteReset
= m_pCache
->Reset(m_seekPos
);
275 m_readPos
= m_seekPos
;
276 m_writePos
= m_pCache
->CachedDataEndPos();
277 assert(m_writePos
== cacheMaxPos
);
278 average
.Reset(m_writePos
, bCompleteReset
); // Can only recalculate new average from scratch after a full reset (empty cache)
279 limiter
.Reset(m_writePos
);
280 m_nSeekResult
= m_seekPos
;
284 "CFileCache::{} - <{}> cache completely reset for seek to position {}",
285 __FUNCTION__
, m_sourcePath
, m_seekPos
);
287 m_writeRateLowSpeed
= 0;
294 // variable read factor based on cache level
295 if (useAdaptativeReadFactor
)
297 // cache level [0.0 - 1.0]
298 const double level
= static_cast<double>(m_writePos
- m_readPos
) / m_maxForward
;
299 readFactor
= static_cast<float>(level
* -2.5 + 4.0); // read factor [4.0x - 1.5x]
304 if (m_writePos
- m_readPos
< m_writeRate
* readFactor
)
306 limiter
.Reset(m_writePos
);
310 if (limiter
.Rate(m_writePos
) < m_writeRate
* readFactor
)
313 if (m_seekEvent
.Wait(m_processWait
))
321 const int64_t maxWrite
= m_pCache
->GetMaxWriteSize(m_chunkSize
);
322 int64_t maxSourceRead
= m_chunkSize
;
323 // Cap source read size by space available between current write position and EOF
325 maxSourceRead
= std::min(maxSourceRead
, m_fileSize
- m_writePos
);
327 /* Only read from source if there's enough write space in the cache
328 * else we may keep disposing data and seeking back on (slow) source
330 if (maxWrite
< maxSourceRead
)
332 // Wait until sufficient cache write space is available
333 m_pCache
->m_space
.Wait(5ms
);
338 if (maxSourceRead
> 0)
339 iRead
= m_source
.Read(buffer
.get(), maxSourceRead
);
342 // Check for actual EOF and retry as long as we still have data in our cache
343 if (m_writePos
< m_fileSize
&& m_pCache
->WaitForData(0, 0ms
) > 0)
345 CLog::Log(LOGWARNING
, "CFileCache::{} - <{}> source read returned {}! Will retry",
346 __FUNCTION__
, m_sourcePath
, iRead
);
349 if (m_seekEvent
.Wait(2000ms
))
352 m_seekEvent
.Set(); // hack so that later we realize seek is needed
356 continue; // while (!m_bStop)
362 "{} - <{}> source read failed with {}!", __FUNCTION__
, m_sourcePath
, iRead
);
363 else if (m_fileSize
== 0)
365 "CFileCache::{} - <{}> source read didn't return any data! Hit eof(?)",
366 __FUNCTION__
, m_sourcePath
);
367 else if (m_writePos
< m_fileSize
)
369 "CFileCache::{} - <{}> source read didn't return any data before eof!",
370 __FUNCTION__
, m_sourcePath
);
372 CLog::Log(LOGDEBUG
, "CFileCache::{} - <{}> source read hit eof", __FUNCTION__
,
375 m_pCache
->EndOfInput();
377 // The thread event will now also cause the wait of an event to return a false.
378 if (AbortableWait(m_seekEvent
) == WAIT_SIGNALED
)
380 m_pCache
->ClearEndOfInput();
382 m_seekEvent
.Set(); // hack so that later we realize seek is needed
385 break; // while (!m_bStop)
390 while (!m_bStop
&& (iTotalWrite
< iRead
))
393 iWrite
= m_pCache
->WriteToCache(buffer
.get() + iTotalWrite
, iRead
- iTotalWrite
);
395 // write should always work. all handling of buffering and errors should be
396 // done inside the cache strategy. only if unrecoverable error happened, WriteToCache would return error and we break.
399 CLog::Log(LOGERROR
, "CFileCache::{} - <{}> error writing to cache", __FUNCTION__
,
404 else if (iWrite
== 0)
406 m_pCache
->m_space
.Wait(5ms
);
409 iTotalWrite
+= iWrite
;
411 // check if seek was asked. otherwise if cache is full we'll freeze.
412 if (m_seekEvent
.Wait(0ms
))
415 m_seekEvent
.Set(); // make sure we get the seek event later.
420 m_writePos
+= iTotalWrite
;
422 // under estimate write rate by a second, to
423 // avoid uncertainty at start of caching
424 m_writeRateActual
= average
.Rate(m_writePos
, 1000);
426 /* NOTE: We can only reliably test for low speed condition, when the cache is *really*
427 * filling. This is because as soon as it's full the average-
428 * rate will become approximately the current-rate which can flag false
429 * low read-rate conditions.
431 if (m_bFilling
&& m_forwardCacheSize
!= 0)
433 const int64_t forward
= m_pCache
->WaitForData(0, 0ms
);
434 if (forward
+ m_chunkSize
>= m_forwardCacheSize
)
436 if (m_writeRateActual
< m_writeRate
)
437 m_writeRateLowSpeed
= m_writeRateActual
;
445 void CFileCache::OnExit()
449 // make sure cache is set to mark end of file (read may be waiting).
451 m_pCache
->EndOfInput();
453 // just in case someone's waiting...
457 bool CFileCache::Exists(const CURL
& url
)
459 return CFile::Exists(url
.Get());
462 int CFileCache::Stat(const CURL
& url
, struct __stat64
* buffer
)
464 return CFile::Stat(url
.Get(), buffer
);
467 ssize_t
CFileCache::Read(void* lpBuf
, size_t uiBufSize
)
469 std::unique_lock
<CCriticalSection
> lock(m_sync
);
472 CLog::Log(LOGERROR
, "CFileCache::{} - <{}> sanity failed. no cache strategy!", __FUNCTION__
,
478 if (uiBufSize
> SSIZE_MAX
)
479 uiBufSize
= SSIZE_MAX
;
483 iRc
= m_pCache
->ReadFromCache((char *)lpBuf
, uiBufSize
);
490 if (iRc
== CACHE_RC_WOULD_BLOCK
)
492 // just wait for some data to show up
493 iRc
= m_pCache
->WaitForData(1, 10s
);
498 if (iRc
== CACHE_RC_TIMEOUT
)
500 CLog::Log(LOGWARNING
, "CFileCache::{} - <{}> timeout waiting for data", __FUNCTION__
,
508 // unknown error code
509 CLog::Log(LOGERROR
, "CFileCache::{} - <{}> cache strategy returned unknown error code {}",
510 __FUNCTION__
, m_sourcePath
, (int)iRc
);
514 int64_t CFileCache::Seek(int64_t iFilePosition
, int iWhence
)
516 std::unique_lock
<CCriticalSection
> lock(m_sync
);
520 CLog::Log(LOGERROR
, "CFileCache::{} - <{}> sanity failed. no cache strategy!", __FUNCTION__
,
525 int64_t iCurPos
= m_readPos
;
526 int64_t iTarget
= iFilePosition
;
527 if (iWhence
== SEEK_END
)
528 iTarget
= m_fileSize
+ iTarget
;
529 else if (iWhence
== SEEK_CUR
)
530 iTarget
= iCurPos
+ iTarget
;
531 else if (iWhence
!= SEEK_SET
)
534 if (iTarget
== m_readPos
)
537 if ((m_nSeekResult
= m_pCache
->Seek(iTarget
)) != iTarget
)
539 if (m_seekPossible
== 0)
540 return m_nSeekResult
;
542 // Never request closer to end than one chunk. Speeds up tag reading
543 m_seekPos
= std::min(iTarget
, std::max((int64_t)0, m_fileSize
- m_chunkSize
));
546 while (!m_seekEnded
.Wait(100ms
))
548 // SeekEnded will never be set if FileCache thread is not running
549 if (!CThread::IsRunning())
553 /* wait for any remaining data */
554 if(m_seekPos
< iTarget
)
556 CLog::Log(LOGDEBUG
, "CFileCache::{} - <{}> waiting for position {}", __FUNCTION__
,
557 m_sourcePath
, iTarget
);
558 if (m_pCache
->WaitForData(static_cast<uint32_t>(iTarget
- m_seekPos
), 10s
) <
561 CLog::Log(LOGWARNING
, "CFileCache::{} - <{}> failed to get remaining data", __FUNCTION__
,
565 m_pCache
->Seek(iTarget
);
576 void CFileCache::Close()
580 std::unique_lock
<CCriticalSection
> lock(m_sync
);
587 int64_t CFileCache::GetPosition()
592 int64_t CFileCache::GetLength()
597 void CFileCache::StopThread(bool bWait
/*= true*/)
600 //Process could be waiting for seekEvent
602 CThread::StopThread(bWait
);
605 const std::string
CFileCache::GetProperty(XFILE::FileProperty type
, const std::string
&name
) const
607 if (!m_source
.GetImplementation())
608 return IFile::GetProperty(type
, name
);
610 return m_source
.GetImplementation()->GetProperty(type
, name
);
613 int CFileCache::IoControl(EIoControl request
, void* param
)
615 if (request
== IOCTRL_CACHE_STATUS
)
617 SCacheStatus
* status
= (SCacheStatus
*)param
;
618 status
->maxforward
= m_maxForward
;
619 status
->forward
= m_pCache
->WaitForData(0, 0ms
);
620 status
->maxrate
= m_writeRate
;
621 status
->currate
= m_writeRateActual
;
622 status
->lowrate
= m_writeRateLowSpeed
;
623 m_writeRateLowSpeed
= 0; // Reset low speed condition
627 if (request
== IOCTRL_CACHE_SETRATE
)
629 m_writeRate
= *static_cast<uint32_t*>(param
);
631 const double mBits
= m_writeRate
/ 1024.0 / 1024.0 * 8.0; // Mbit/s
633 // calculates wait time inversely proportional to the bitrate
634 // and limited between 30 - 100 ms
635 const int wait
= std::clamp(static_cast<int>(110.0 - mBits
), 30, 100);
637 m_processWait
= std::chrono::milliseconds(wait
);
640 "CFileCache::IoControl - setting maxRate to {:.2f} Mbit/s with processWait of {} ms",
645 if (request
== IOCTRL_SEEK_POSSIBLE
)
646 return m_seekPossible
;