[PVR][Estuary] Timer settings dialog: Show client name in timer type selection dialog...
[xbmc.git] / xbmc / filesystem / CacheStrategy.cpp
blob7aaeec9fde1c9d3c134ea6754b5e568c77616839
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 "threads/SystemClock.h"
10 #include "CacheStrategy.h"
11 #include "IFile.h"
12 #ifdef TARGET_POSIX
13 #include "PlatformDefs.h"
14 #include "platform/posix/ConvUtils.h"
15 #endif
16 #include "Util.h"
17 #include "utils/log.h"
18 #include "SpecialProtocol.h"
19 #include "URL.h"
20 #if defined(TARGET_POSIX)
21 #include "platform/posix/filesystem/PosixFile.h"
22 #define CacheLocalFile CPosixFile
23 #elif defined(TARGET_WINDOWS)
24 #include "platform/win32/filesystem/Win32File.h"
25 #define CacheLocalFile CWin32File
26 #endif // TARGET_WINDOWS
28 #include <cassert>
29 #include <algorithm>
31 using namespace XFILE;
33 using namespace std::chrono_literals;
35 CCacheStrategy::~CCacheStrategy() = default;
37 void CCacheStrategy::EndOfInput() {
38 m_bEndOfInput = true;
41 bool CCacheStrategy::IsEndOfInput()
43 return m_bEndOfInput;
46 void CCacheStrategy::ClearEndOfInput()
48 m_bEndOfInput = false;
51 CSimpleFileCache::CSimpleFileCache()
52 : m_cacheFileRead(new CacheLocalFile())
53 , m_cacheFileWrite(new CacheLocalFile())
54 , m_hDataAvailEvent(NULL)
58 CSimpleFileCache::~CSimpleFileCache()
60 Close();
61 delete m_cacheFileRead;
62 delete m_cacheFileWrite;
65 int CSimpleFileCache::Open()
67 Close();
69 m_hDataAvailEvent = new CEvent;
71 m_filename = CSpecialProtocol::TranslatePath(
72 CUtil::GetNextFilename("special://temp/filecache{:03}.cache", 999));
73 if (m_filename.empty())
75 CLog::Log(LOGERROR, "CSimpleFileCache::{} - Unable to generate a new filename", __FUNCTION__);
76 Close();
77 return CACHE_RC_ERROR;
80 CURL fileURL(m_filename);
82 if (!m_cacheFileWrite->OpenForWrite(fileURL, false))
84 CLog::Log(LOGERROR, "CSimpleFileCache::{} - Failed to create file \"{}\" for writing",
85 __FUNCTION__, m_filename);
86 Close();
87 return CACHE_RC_ERROR;
90 if (!m_cacheFileRead->Open(fileURL))
92 CLog::Log(LOGERROR, "CSimpleFileCache::{} - Failed to open file \"{}\" for reading",
93 __FUNCTION__, m_filename);
94 Close();
95 return CACHE_RC_ERROR;
98 return CACHE_RC_OK;
101 void CSimpleFileCache::Close()
103 if (m_hDataAvailEvent)
104 delete m_hDataAvailEvent;
106 m_hDataAvailEvent = NULL;
108 m_cacheFileWrite->Close();
109 m_cacheFileRead->Close();
111 if (!m_filename.empty() && !m_cacheFileRead->Delete(CURL(m_filename)))
112 CLog::Log(LOGWARNING, "SimpleFileCache::{} - Failed to delete cache file \"{}\"", __FUNCTION__,
113 m_filename);
115 m_filename.clear();
118 size_t CSimpleFileCache::GetMaxWriteSize(const size_t& iRequestSize)
120 return iRequestSize; // Can always write since it's on disk
123 int CSimpleFileCache::WriteToCache(const char *pBuffer, size_t iSize)
125 size_t written = 0;
126 while (iSize > 0)
128 const ssize_t lastWritten =
129 m_cacheFileWrite->Write(pBuffer, std::min(iSize, static_cast<size_t>(SSIZE_MAX)));
130 if (lastWritten <= 0)
132 CLog::Log(LOGERROR, "SimpleFileCache::{} - <{}> Failed to write to cache", __FUNCTION__,
133 m_filename);
134 return CACHE_RC_ERROR;
136 m_nWritePosition += lastWritten;
137 iSize -= lastWritten;
138 written += lastWritten;
141 // when reader waits for data it will wait on the event.
142 m_hDataAvailEvent->Set();
144 return written;
147 int64_t CSimpleFileCache::GetAvailableRead()
149 return m_nWritePosition - m_nReadPosition;
152 int CSimpleFileCache::ReadFromCache(char *pBuffer, size_t iMaxSize)
154 int64_t iAvailable = GetAvailableRead();
155 if ( iAvailable <= 0 )
156 return m_bEndOfInput ? 0 : CACHE_RC_WOULD_BLOCK;
158 size_t toRead = std::min(iMaxSize, static_cast<size_t>(iAvailable));
160 size_t readBytes = 0;
161 while (toRead > 0)
163 const ssize_t lastRead =
164 m_cacheFileRead->Read(pBuffer, std::min(toRead, static_cast<size_t>(SSIZE_MAX)));
166 if (lastRead == 0)
167 break;
168 if (lastRead < 0)
170 CLog::Log(LOGERROR, "CSimpleFileCache::{} - <{}> Failed to read from cache", __FUNCTION__,
171 m_filename);
172 return CACHE_RC_ERROR;
174 m_nReadPosition += lastRead;
175 toRead -= lastRead;
176 readBytes += lastRead;
179 if (readBytes > 0)
180 m_space.Set();
182 return readBytes;
185 int64_t CSimpleFileCache::WaitForData(uint32_t iMinAvail, std::chrono::milliseconds timeout)
187 if (timeout == 0ms || IsEndOfInput())
188 return GetAvailableRead();
190 XbmcThreads::EndTime<> endTime{timeout};
191 while (!IsEndOfInput())
193 int64_t iAvail = GetAvailableRead();
194 if (iAvail >= iMinAvail)
195 return iAvail;
197 if (!m_hDataAvailEvent->Wait(endTime.GetTimeLeft()))
198 return CACHE_RC_TIMEOUT;
200 return GetAvailableRead();
203 int64_t CSimpleFileCache::Seek(int64_t iFilePosition)
205 int64_t iTarget = iFilePosition - m_nStartPosition;
207 if (iTarget < 0)
209 CLog::Log(LOGDEBUG, "CSimpleFileCache::{} - <{}> Request seek to {} before start of cache",
210 __FUNCTION__, iFilePosition, m_filename);
211 return CACHE_RC_ERROR;
214 int64_t nDiff = iTarget - m_nWritePosition;
215 if (nDiff > 500000)
217 CLog::Log(LOGDEBUG,
218 "CSimpleFileCache::{} - <{}> Requested position {} is beyond cached data ({})",
219 __FUNCTION__, m_filename, iFilePosition, m_nWritePosition);
220 return CACHE_RC_ERROR;
223 if (nDiff > 0 &&
224 WaitForData(static_cast<uint32_t>(iTarget - m_nReadPosition), 5s) == CACHE_RC_TIMEOUT)
226 CLog::Log(LOGDEBUG, "CSimpleFileCache::{} - <{}> Wait for position {} failed. Ended up at {}",
227 __FUNCTION__, m_filename, iFilePosition, m_nWritePosition);
228 return CACHE_RC_ERROR;
231 m_nReadPosition = m_cacheFileRead->Seek(iTarget, SEEK_SET);
232 if (m_nReadPosition != iTarget)
234 CLog::Log(LOGERROR, "CSimpleFileCache::{} - <{}> Can't seek cache file for position {}",
235 __FUNCTION__, iFilePosition, m_filename);
236 return CACHE_RC_ERROR;
239 m_space.Set();
241 return iFilePosition;
244 bool CSimpleFileCache::Reset(int64_t iSourcePosition)
246 if (IsCachedPosition(iSourcePosition))
248 m_nReadPosition = m_cacheFileRead->Seek(iSourcePosition - m_nStartPosition, SEEK_SET);
249 return false;
252 m_nStartPosition = iSourcePosition;
253 m_nWritePosition = m_cacheFileWrite->Seek(0, SEEK_SET);
254 m_nReadPosition = m_cacheFileRead->Seek(0, SEEK_SET);
255 return true;
258 void CSimpleFileCache::EndOfInput()
260 CCacheStrategy::EndOfInput();
261 m_hDataAvailEvent->Set();
264 int64_t CSimpleFileCache::CachedDataEndPosIfSeekTo(int64_t iFilePosition)
266 if (iFilePosition >= m_nStartPosition && iFilePosition <= m_nStartPosition + m_nWritePosition)
267 return m_nStartPosition + m_nWritePosition;
268 return iFilePosition;
271 int64_t CSimpleFileCache::CachedDataStartPos()
273 return m_nStartPosition;
276 int64_t CSimpleFileCache::CachedDataEndPos()
278 return m_nStartPosition + m_nWritePosition;
281 bool CSimpleFileCache::IsCachedPosition(int64_t iFilePosition)
283 return iFilePosition >= m_nStartPosition && iFilePosition <= m_nStartPosition + m_nWritePosition;
286 CCacheStrategy *CSimpleFileCache::CreateNew()
288 return new CSimpleFileCache();
292 CDoubleCache::CDoubleCache(CCacheStrategy *impl)
294 assert(NULL != impl);
295 m_pCache = impl;
296 m_pCacheOld = NULL;
299 CDoubleCache::~CDoubleCache()
301 delete m_pCache;
302 delete m_pCacheOld;
305 int CDoubleCache::Open()
307 return m_pCache->Open();
310 void CDoubleCache::Close()
312 m_pCache->Close();
313 if (m_pCacheOld)
315 delete m_pCacheOld;
316 m_pCacheOld = NULL;
320 size_t CDoubleCache::GetMaxWriteSize(const size_t& iRequestSize)
322 return m_pCache->GetMaxWriteSize(iRequestSize); // NOTE: Check the active cache only
325 int CDoubleCache::WriteToCache(const char *pBuffer, size_t iSize)
327 return m_pCache->WriteToCache(pBuffer, iSize);
330 int CDoubleCache::ReadFromCache(char *pBuffer, size_t iMaxSize)
332 return m_pCache->ReadFromCache(pBuffer, iMaxSize);
335 int64_t CDoubleCache::WaitForData(uint32_t iMinAvail, std::chrono::milliseconds timeout)
337 return m_pCache->WaitForData(iMinAvail, timeout);
340 int64_t CDoubleCache::Seek(int64_t iFilePosition)
342 /* Check whether position is NOT in our current cache but IS in our old cache.
343 * This is faster/more efficient than having to possibly wait for data in the
344 * Seek() call below
346 if (!m_pCache->IsCachedPosition(iFilePosition) &&
347 m_pCacheOld && m_pCacheOld->IsCachedPosition(iFilePosition))
349 // Return error to trigger a seek event which will swap the caches:
350 return CACHE_RC_ERROR;
353 return m_pCache->Seek(iFilePosition); // Normal seek
356 bool CDoubleCache::Reset(int64_t iSourcePosition)
358 /* Check if we should (not) swap the caches. Note that when both caches have the
359 * requested position, we prefer the cache that has the most forward data
361 if (m_pCache->IsCachedPosition(iSourcePosition) &&
362 (!m_pCacheOld || !m_pCacheOld->IsCachedPosition(iSourcePosition) ||
363 m_pCache->CachedDataEndPos() >= m_pCacheOld->CachedDataEndPos()))
365 // No swap: Just use current cache
366 return m_pCache->Reset(iSourcePosition);
369 // Need to swap caches
370 CCacheStrategy* pCacheTmp;
371 if (!m_pCacheOld)
373 pCacheTmp = m_pCache->CreateNew();
374 if (pCacheTmp->Open() != CACHE_RC_OK)
376 delete pCacheTmp;
377 return m_pCache->Reset(iSourcePosition);
380 else
382 pCacheTmp = m_pCacheOld;
385 // Perform actual swap:
386 m_pCacheOld = m_pCache;
387 m_pCache = pCacheTmp;
389 // If new active cache still doesn't have this position, log it
390 if (!m_pCache->IsCachedPosition(iSourcePosition))
392 CLog::Log(LOGDEBUG, "CDoubleCache::{} - ({}) Cache miss for {} with new={}-{} and old={}-{}",
393 __FUNCTION__, fmt::ptr(this), iSourcePosition, m_pCache->CachedDataStartPos(),
394 m_pCache->CachedDataEndPos(), m_pCacheOld->CachedDataStartPos(),
395 m_pCacheOld->CachedDataEndPos());
398 return m_pCache->Reset(iSourcePosition);
401 void CDoubleCache::EndOfInput()
403 m_pCache->EndOfInput();
406 bool CDoubleCache::IsEndOfInput()
408 return m_pCache->IsEndOfInput();
411 void CDoubleCache::ClearEndOfInput()
413 m_pCache->ClearEndOfInput();
416 int64_t CDoubleCache::CachedDataStartPos()
418 return m_pCache->CachedDataStartPos();
421 int64_t CDoubleCache::CachedDataEndPos()
423 return m_pCache->CachedDataEndPos();
426 int64_t CDoubleCache::CachedDataEndPosIfSeekTo(int64_t iFilePosition)
428 /* Return the position on source we would end up after a cache-seek(/reset)
429 * Note that we select the cache that has the most forward data already cached
430 * for this position
432 int64_t ret = m_pCache->CachedDataEndPosIfSeekTo(iFilePosition);
433 if (m_pCacheOld)
434 return std::max(ret, m_pCacheOld->CachedDataEndPosIfSeekTo(iFilePosition));
435 return ret;
438 bool CDoubleCache::IsCachedPosition(int64_t iFilePosition)
440 return m_pCache->IsCachedPosition(iFilePosition) || (m_pCacheOld && m_pCacheOld->IsCachedPosition(iFilePosition));
443 CCacheStrategy *CDoubleCache::CreateNew()
445 return new CDoubleCache(m_pCache->CreateNew());