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 // 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
);
114 const auto settings
= CServiceBroker::GetSettingsComponent()->GetSettings();
118 const unsigned int cacheMemSize
=
119 settings
->GetInt(CSettings::SETTING_FILECACHE_MEMORYSIZE
) * 1024 * 1024;
121 m_source
.IoControl(IOCTRL_SET_CACHE
, this);
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
));
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();
140 if (cacheMemSize
== 0)
143 m_pCache
= std::make_unique
<CSimpleFileCache
>();
144 m_forwardCacheSize
= 0;
145 m_maxForward
= m_fileSize
;
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
;
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
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
);
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
);
208 m_writeRate
= 1024 * 1024;
209 m_writeRateActual
= 0;
210 m_writeRateLowSpeed
= 0;
215 CThread::Create(false);
220 void CFileCache::Process()
224 CLog::Log(LOGERROR
, "CFileCache::{} - <{}> sanity failed. no cache strategy", __FUNCTION__
,
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__
,
238 const auto settings
= CServiceBroker::GetSettingsComponent()->GetSettings();
242 float readFactor
= settings
->GetInt(CSettings::SETTING_FILECACHE_READFACTOR
) / 100.0f
;
244 const bool useAdaptativeReadFactor
= (readFactor
< 1.0f
);
252 m_fileSize
= m_source
.GetLength();
254 // check for seek events
255 if (m_seekEvent
.Wait(0ms
))
258 const int64_t cacheMaxPos
= m_pCache
->CachedDataEndPosIfSeekTo(m_seekPos
);
259 const bool cacheReachEOF
= (cacheMaxPos
== m_fileSize
);
261 bool sourceSeekFailed
= false;
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
;
286 "CFileCache::{} - <{}> cache completely reset for seek to position {}",
287 __FUNCTION__
, m_sourcePath
, m_seekPos
);
289 m_writeRateLowSpeed
= 0;
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]
306 if (m_writePos
- m_readPos
< m_writeRate
* readFactor
)
308 limiter
.Reset(m_writePos
);
312 if (limiter
.Rate(m_writePos
) < m_writeRate
* readFactor
)
315 if (m_seekEvent
.Wait(m_processWait
))
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
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
);
340 if (maxSourceRead
> 0)
341 iRead
= m_source
.Read(buffer
.get(), maxSourceRead
);
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
);
351 if (m_seekEvent
.Wait(2000ms
))
354 m_seekEvent
.Set(); // hack so that later we realize seek is needed
358 continue; // while (!m_bStop)
364 "{} - <{}> source read failed with {}!", __FUNCTION__
, m_sourcePath
, iRead
);
365 else if (m_fileSize
== 0)
367 "CFileCache::{} - <{}> source read didn't return any data! Hit eof(?)",
368 __FUNCTION__
, m_sourcePath
);
369 else if (m_writePos
< m_fileSize
)
371 "CFileCache::{} - <{}> source read didn't return any data before eof!",
372 __FUNCTION__
, m_sourcePath
);
374 CLog::Log(LOGDEBUG
, "CFileCache::{} - <{}> source read hit eof", __FUNCTION__
,
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();
384 m_seekEvent
.Set(); // hack so that later we realize seek is needed
387 break; // while (!m_bStop)
392 while (!m_bStop
&& (iTotalWrite
< iRead
))
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.
401 CLog::Log(LOGERROR
, "CFileCache::{} - <{}> error writing to cache", __FUNCTION__
,
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
))
417 m_seekEvent
.Set(); // make sure we get the seek event later.
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
;
447 void CFileCache::OnExit()
451 // make sure cache is set to mark end of file (read may be waiting).
453 m_pCache
->EndOfInput();
455 // just in case someone's waiting...
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
);
474 CLog::Log(LOGERROR
, "CFileCache::{} - <{}> sanity failed. no cache strategy!", __FUNCTION__
,
480 if (uiBufSize
> SSIZE_MAX
)
481 uiBufSize
= SSIZE_MAX
;
485 iRc
= m_pCache
->ReadFromCache((char *)lpBuf
, uiBufSize
);
492 if (iRc
== CACHE_RC_WOULD_BLOCK
)
494 // just wait for some data to show up
495 iRc
= m_pCache
->WaitForData(1, 10s
);
500 if (iRc
== CACHE_RC_TIMEOUT
)
502 CLog::Log(LOGWARNING
, "CFileCache::{} - <{}> timeout waiting for data", __FUNCTION__
,
510 // unknown error code
511 CLog::Log(LOGERROR
, "CFileCache::{} - <{}> cache strategy returned unknown error code {}",
512 __FUNCTION__
, m_sourcePath
, (int)iRc
);
516 int64_t CFileCache::Seek(int64_t iFilePosition
, int iWhence
)
518 std::unique_lock
<CCriticalSection
> lock(m_sync
);
522 CLog::Log(LOGERROR
, "CFileCache::{} - <{}> sanity failed. no cache strategy!", __FUNCTION__
,
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
)
536 if (iTarget
== 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
));
548 while (!m_seekEnded
.Wait(100ms
))
550 // SeekEnded will never be set if FileCache thread is not running
551 if (!CThread::IsRunning())
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
) <
563 CLog::Log(LOGWARNING
, "CFileCache::{} - <{}> failed to get remaining data", __FUNCTION__
,
567 m_pCache
->Seek(iTarget
);
578 void CFileCache::Close()
582 std::unique_lock
<CCriticalSection
> lock(m_sync
);
589 int64_t CFileCache::GetPosition()
594 int64_t CFileCache::GetLength()
599 void CFileCache::StopThread(bool bWait
/*= true*/)
602 //Process could be waiting for seekEvent
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
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
);
642 "CFileCache::IoControl - setting maxRate to {:.2f} Mbit/s with processWait of {} ms",
647 if (request
== IOCTRL_SEEK_POSSIBLE
)
648 return m_seekPossible
;