summaryrefslogtreecommitdiffstats
path: root/xbmc/filesystem/CacheStrategy.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
commitc04dcc2e7d834218ef2d4194331e383402495ae1 (patch)
tree7333e38d10d75386e60f336b80c2443c1166031d /xbmc/filesystem/CacheStrategy.cpp
parentInitial commit. (diff)
downloadkodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz
kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/filesystem/CacheStrategy.cpp')
-rw-r--r--xbmc/filesystem/CacheStrategy.cpp447
1 files changed, 447 insertions, 0 deletions
diff --git a/xbmc/filesystem/CacheStrategy.cpp b/xbmc/filesystem/CacheStrategy.cpp
new file mode 100644
index 0000000..7aaeec9
--- /dev/null
+++ b/xbmc/filesystem/CacheStrategy.cpp
@@ -0,0 +1,447 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "threads/SystemClock.h"
+#include "CacheStrategy.h"
+#include "IFile.h"
+#ifdef TARGET_POSIX
+#include "PlatformDefs.h"
+#include "platform/posix/ConvUtils.h"
+#endif
+#include "Util.h"
+#include "utils/log.h"
+#include "SpecialProtocol.h"
+#include "URL.h"
+#if defined(TARGET_POSIX)
+#include "platform/posix/filesystem/PosixFile.h"
+#define CacheLocalFile CPosixFile
+#elif defined(TARGET_WINDOWS)
+#include "platform/win32/filesystem/Win32File.h"
+#define CacheLocalFile CWin32File
+#endif // TARGET_WINDOWS
+
+#include <cassert>
+#include <algorithm>
+
+using namespace XFILE;
+
+using namespace std::chrono_literals;
+
+CCacheStrategy::~CCacheStrategy() = default;
+
+void CCacheStrategy::EndOfInput() {
+ m_bEndOfInput = true;
+}
+
+bool CCacheStrategy::IsEndOfInput()
+{
+ return m_bEndOfInput;
+}
+
+void CCacheStrategy::ClearEndOfInput()
+{
+ m_bEndOfInput = false;
+}
+
+CSimpleFileCache::CSimpleFileCache()
+ : m_cacheFileRead(new CacheLocalFile())
+ , m_cacheFileWrite(new CacheLocalFile())
+ , m_hDataAvailEvent(NULL)
+{
+}
+
+CSimpleFileCache::~CSimpleFileCache()
+{
+ Close();
+ delete m_cacheFileRead;
+ delete m_cacheFileWrite;
+}
+
+int CSimpleFileCache::Open()
+{
+ Close();
+
+ m_hDataAvailEvent = new CEvent;
+
+ m_filename = CSpecialProtocol::TranslatePath(
+ CUtil::GetNextFilename("special://temp/filecache{:03}.cache", 999));
+ if (m_filename.empty())
+ {
+ CLog::Log(LOGERROR, "CSimpleFileCache::{} - Unable to generate a new filename", __FUNCTION__);
+ Close();
+ return CACHE_RC_ERROR;
+ }
+
+ CURL fileURL(m_filename);
+
+ if (!m_cacheFileWrite->OpenForWrite(fileURL, false))
+ {
+ CLog::Log(LOGERROR, "CSimpleFileCache::{} - Failed to create file \"{}\" for writing",
+ __FUNCTION__, m_filename);
+ Close();
+ return CACHE_RC_ERROR;
+ }
+
+ if (!m_cacheFileRead->Open(fileURL))
+ {
+ CLog::Log(LOGERROR, "CSimpleFileCache::{} - Failed to open file \"{}\" for reading",
+ __FUNCTION__, m_filename);
+ Close();
+ return CACHE_RC_ERROR;
+ }
+
+ return CACHE_RC_OK;
+}
+
+void CSimpleFileCache::Close()
+{
+ if (m_hDataAvailEvent)
+ delete m_hDataAvailEvent;
+
+ m_hDataAvailEvent = NULL;
+
+ m_cacheFileWrite->Close();
+ m_cacheFileRead->Close();
+
+ if (!m_filename.empty() && !m_cacheFileRead->Delete(CURL(m_filename)))
+ CLog::Log(LOGWARNING, "SimpleFileCache::{} - Failed to delete cache file \"{}\"", __FUNCTION__,
+ m_filename);
+
+ m_filename.clear();
+}
+
+size_t CSimpleFileCache::GetMaxWriteSize(const size_t& iRequestSize)
+{
+ return iRequestSize; // Can always write since it's on disk
+}
+
+int CSimpleFileCache::WriteToCache(const char *pBuffer, size_t iSize)
+{
+ size_t written = 0;
+ while (iSize > 0)
+ {
+ const ssize_t lastWritten =
+ m_cacheFileWrite->Write(pBuffer, std::min(iSize, static_cast<size_t>(SSIZE_MAX)));
+ if (lastWritten <= 0)
+ {
+ CLog::Log(LOGERROR, "SimpleFileCache::{} - <{}> Failed to write to cache", __FUNCTION__,
+ m_filename);
+ return CACHE_RC_ERROR;
+ }
+ m_nWritePosition += lastWritten;
+ iSize -= lastWritten;
+ written += lastWritten;
+ }
+
+ // when reader waits for data it will wait on the event.
+ m_hDataAvailEvent->Set();
+
+ return written;
+}
+
+int64_t CSimpleFileCache::GetAvailableRead()
+{
+ return m_nWritePosition - m_nReadPosition;
+}
+
+int CSimpleFileCache::ReadFromCache(char *pBuffer, size_t iMaxSize)
+{
+ int64_t iAvailable = GetAvailableRead();
+ if ( iAvailable <= 0 )
+ return m_bEndOfInput ? 0 : CACHE_RC_WOULD_BLOCK;
+
+ size_t toRead = std::min(iMaxSize, static_cast<size_t>(iAvailable));
+
+ size_t readBytes = 0;
+ while (toRead > 0)
+ {
+ const ssize_t lastRead =
+ m_cacheFileRead->Read(pBuffer, std::min(toRead, static_cast<size_t>(SSIZE_MAX)));
+
+ if (lastRead == 0)
+ break;
+ if (lastRead < 0)
+ {
+ CLog::Log(LOGERROR, "CSimpleFileCache::{} - <{}> Failed to read from cache", __FUNCTION__,
+ m_filename);
+ return CACHE_RC_ERROR;
+ }
+ m_nReadPosition += lastRead;
+ toRead -= lastRead;
+ readBytes += lastRead;
+ }
+
+ if (readBytes > 0)
+ m_space.Set();
+
+ return readBytes;
+}
+
+int64_t CSimpleFileCache::WaitForData(uint32_t iMinAvail, std::chrono::milliseconds timeout)
+{
+ if (timeout == 0ms || IsEndOfInput())
+ return GetAvailableRead();
+
+ XbmcThreads::EndTime<> endTime{timeout};
+ while (!IsEndOfInput())
+ {
+ int64_t iAvail = GetAvailableRead();
+ if (iAvail >= iMinAvail)
+ return iAvail;
+
+ if (!m_hDataAvailEvent->Wait(endTime.GetTimeLeft()))
+ return CACHE_RC_TIMEOUT;
+ }
+ return GetAvailableRead();
+}
+
+int64_t CSimpleFileCache::Seek(int64_t iFilePosition)
+{
+ int64_t iTarget = iFilePosition - m_nStartPosition;
+
+ if (iTarget < 0)
+ {
+ CLog::Log(LOGDEBUG, "CSimpleFileCache::{} - <{}> Request seek to {} before start of cache",
+ __FUNCTION__, iFilePosition, m_filename);
+ return CACHE_RC_ERROR;
+ }
+
+ int64_t nDiff = iTarget - m_nWritePosition;
+ if (nDiff > 500000)
+ {
+ CLog::Log(LOGDEBUG,
+ "CSimpleFileCache::{} - <{}> Requested position {} is beyond cached data ({})",
+ __FUNCTION__, m_filename, iFilePosition, m_nWritePosition);
+ return CACHE_RC_ERROR;
+ }
+
+ if (nDiff > 0 &&
+ WaitForData(static_cast<uint32_t>(iTarget - m_nReadPosition), 5s) == CACHE_RC_TIMEOUT)
+ {
+ CLog::Log(LOGDEBUG, "CSimpleFileCache::{} - <{}> Wait for position {} failed. Ended up at {}",
+ __FUNCTION__, m_filename, iFilePosition, m_nWritePosition);
+ return CACHE_RC_ERROR;
+ }
+
+ m_nReadPosition = m_cacheFileRead->Seek(iTarget, SEEK_SET);
+ if (m_nReadPosition != iTarget)
+ {
+ CLog::Log(LOGERROR, "CSimpleFileCache::{} - <{}> Can't seek cache file for position {}",
+ __FUNCTION__, iFilePosition, m_filename);
+ return CACHE_RC_ERROR;
+ }
+
+ m_space.Set();
+
+ return iFilePosition;
+}
+
+bool CSimpleFileCache::Reset(int64_t iSourcePosition)
+{
+ if (IsCachedPosition(iSourcePosition))
+ {
+ m_nReadPosition = m_cacheFileRead->Seek(iSourcePosition - m_nStartPosition, SEEK_SET);
+ return false;
+ }
+
+ m_nStartPosition = iSourcePosition;
+ m_nWritePosition = m_cacheFileWrite->Seek(0, SEEK_SET);
+ m_nReadPosition = m_cacheFileRead->Seek(0, SEEK_SET);
+ return true;
+}
+
+void CSimpleFileCache::EndOfInput()
+{
+ CCacheStrategy::EndOfInput();
+ m_hDataAvailEvent->Set();
+}
+
+int64_t CSimpleFileCache::CachedDataEndPosIfSeekTo(int64_t iFilePosition)
+{
+ if (iFilePosition >= m_nStartPosition && iFilePosition <= m_nStartPosition + m_nWritePosition)
+ return m_nStartPosition + m_nWritePosition;
+ return iFilePosition;
+}
+
+int64_t CSimpleFileCache::CachedDataStartPos()
+{
+ return m_nStartPosition;
+}
+
+int64_t CSimpleFileCache::CachedDataEndPos()
+{
+ return m_nStartPosition + m_nWritePosition;
+}
+
+bool CSimpleFileCache::IsCachedPosition(int64_t iFilePosition)
+{
+ return iFilePosition >= m_nStartPosition && iFilePosition <= m_nStartPosition + m_nWritePosition;
+}
+
+CCacheStrategy *CSimpleFileCache::CreateNew()
+{
+ return new CSimpleFileCache();
+}
+
+
+CDoubleCache::CDoubleCache(CCacheStrategy *impl)
+{
+ assert(NULL != impl);
+ m_pCache = impl;
+ m_pCacheOld = NULL;
+}
+
+CDoubleCache::~CDoubleCache()
+{
+ delete m_pCache;
+ delete m_pCacheOld;
+}
+
+int CDoubleCache::Open()
+{
+ return m_pCache->Open();
+}
+
+void CDoubleCache::Close()
+{
+ m_pCache->Close();
+ if (m_pCacheOld)
+ {
+ delete m_pCacheOld;
+ m_pCacheOld = NULL;
+ }
+}
+
+size_t CDoubleCache::GetMaxWriteSize(const size_t& iRequestSize)
+{
+ return m_pCache->GetMaxWriteSize(iRequestSize); // NOTE: Check the active cache only
+}
+
+int CDoubleCache::WriteToCache(const char *pBuffer, size_t iSize)
+{
+ return m_pCache->WriteToCache(pBuffer, iSize);
+}
+
+int CDoubleCache::ReadFromCache(char *pBuffer, size_t iMaxSize)
+{
+ return m_pCache->ReadFromCache(pBuffer, iMaxSize);
+}
+
+int64_t CDoubleCache::WaitForData(uint32_t iMinAvail, std::chrono::milliseconds timeout)
+{
+ return m_pCache->WaitForData(iMinAvail, timeout);
+}
+
+int64_t CDoubleCache::Seek(int64_t iFilePosition)
+{
+ /* Check whether position is NOT in our current cache but IS in our old cache.
+ * This is faster/more efficient than having to possibly wait for data in the
+ * Seek() call below
+ */
+ if (!m_pCache->IsCachedPosition(iFilePosition) &&
+ m_pCacheOld && m_pCacheOld->IsCachedPosition(iFilePosition))
+ {
+ // Return error to trigger a seek event which will swap the caches:
+ return CACHE_RC_ERROR;
+ }
+
+ return m_pCache->Seek(iFilePosition); // Normal seek
+}
+
+bool CDoubleCache::Reset(int64_t iSourcePosition)
+{
+ /* Check if we should (not) swap the caches. Note that when both caches have the
+ * requested position, we prefer the cache that has the most forward data
+ */
+ if (m_pCache->IsCachedPosition(iSourcePosition) &&
+ (!m_pCacheOld || !m_pCacheOld->IsCachedPosition(iSourcePosition) ||
+ m_pCache->CachedDataEndPos() >= m_pCacheOld->CachedDataEndPos()))
+ {
+ // No swap: Just use current cache
+ return m_pCache->Reset(iSourcePosition);
+ }
+
+ // Need to swap caches
+ CCacheStrategy* pCacheTmp;
+ if (!m_pCacheOld)
+ {
+ pCacheTmp = m_pCache->CreateNew();
+ if (pCacheTmp->Open() != CACHE_RC_OK)
+ {
+ delete pCacheTmp;
+ return m_pCache->Reset(iSourcePosition);
+ }
+ }
+ else
+ {
+ pCacheTmp = m_pCacheOld;
+ }
+
+ // Perform actual swap:
+ m_pCacheOld = m_pCache;
+ m_pCache = pCacheTmp;
+
+ // If new active cache still doesn't have this position, log it
+ if (!m_pCache->IsCachedPosition(iSourcePosition))
+ {
+ CLog::Log(LOGDEBUG, "CDoubleCache::{} - ({}) Cache miss for {} with new={}-{} and old={}-{}",
+ __FUNCTION__, fmt::ptr(this), iSourcePosition, m_pCache->CachedDataStartPos(),
+ m_pCache->CachedDataEndPos(), m_pCacheOld->CachedDataStartPos(),
+ m_pCacheOld->CachedDataEndPos());
+ }
+
+ return m_pCache->Reset(iSourcePosition);
+}
+
+void CDoubleCache::EndOfInput()
+{
+ m_pCache->EndOfInput();
+}
+
+bool CDoubleCache::IsEndOfInput()
+{
+ return m_pCache->IsEndOfInput();
+}
+
+void CDoubleCache::ClearEndOfInput()
+{
+ m_pCache->ClearEndOfInput();
+}
+
+int64_t CDoubleCache::CachedDataStartPos()
+{
+ return m_pCache->CachedDataStartPos();
+}
+
+int64_t CDoubleCache::CachedDataEndPos()
+{
+ return m_pCache->CachedDataEndPos();
+}
+
+int64_t CDoubleCache::CachedDataEndPosIfSeekTo(int64_t iFilePosition)
+{
+ /* Return the position on source we would end up after a cache-seek(/reset)
+ * Note that we select the cache that has the most forward data already cached
+ * for this position
+ */
+ int64_t ret = m_pCache->CachedDataEndPosIfSeekTo(iFilePosition);
+ if (m_pCacheOld)
+ return std::max(ret, m_pCacheOld->CachedDataEndPosIfSeekTo(iFilePosition));
+ return ret;
+}
+
+bool CDoubleCache::IsCachedPosition(int64_t iFilePosition)
+{
+ return m_pCache->IsCachedPosition(iFilePosition) || (m_pCacheOld && m_pCacheOld->IsCachedPosition(iFilePosition));
+}
+
+CCacheStrategy *CDoubleCache::CreateNew()
+{
+ return new CDoubleCache(m_pCache->CreateNew());
+}
+