summaryrefslogtreecommitdiffstats
path: root/xbmc/filesystem/CircularCache.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--xbmc/filesystem/CircularCache.cpp281
1 files changed, 281 insertions, 0 deletions
diff --git a/xbmc/filesystem/CircularCache.cpp b/xbmc/filesystem/CircularCache.cpp
new file mode 100644
index 0000000..443736a
--- /dev/null
+++ b/xbmc/filesystem/CircularCache.cpp
@@ -0,0 +1,281 @@
+/*
+ * 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 "CircularCache.h"
+
+#include "threads/SystemClock.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <mutex>
+#include <string.h>
+
+using namespace XFILE;
+using namespace std::chrono_literals;
+
+CCircularCache::CCircularCache(size_t front, size_t back)
+ : CCacheStrategy()
+ , m_beg(0)
+ , m_end(0)
+ , m_cur(0)
+ , m_buf(NULL)
+ , m_size(front + back)
+ , m_size_back(back)
+#ifdef TARGET_WINDOWS
+ , m_handle(NULL)
+#endif
+{
+}
+
+CCircularCache::~CCircularCache()
+{
+ Close();
+}
+
+int CCircularCache::Open()
+{
+#ifdef TARGET_WINDOWS
+ m_handle = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, m_size, NULL);
+ if(m_handle == NULL)
+ return CACHE_RC_ERROR;
+ m_buf = (uint8_t*)MapViewOfFile(m_handle, FILE_MAP_ALL_ACCESS, 0, 0, 0);
+#else
+ m_buf = new uint8_t[m_size];
+#endif
+ if (m_buf == NULL)
+ return CACHE_RC_ERROR;
+ m_beg = 0;
+ m_end = 0;
+ m_cur = 0;
+ return CACHE_RC_OK;
+}
+
+void CCircularCache::Close()
+{
+#ifdef TARGET_WINDOWS
+ if (m_buf != NULL)
+ UnmapViewOfFile(m_buf);
+ if (m_handle != NULL)
+ CloseHandle(m_handle);
+ m_handle = NULL;
+#else
+ delete[] m_buf;
+#endif
+ m_buf = NULL;
+}
+
+size_t CCircularCache::GetMaxWriteSize(const size_t& iRequestSize)
+{
+ std::unique_lock<CCriticalSection> lock(m_sync);
+
+ size_t back = (size_t)(m_cur - m_beg); // Backbuffer size
+ size_t front = (size_t)(m_end - m_cur); // Frontbuffer size
+ size_t limit = m_size - std::min(back, m_size_back) - front;
+
+ // Never return more than limit and size requested by caller
+ return std::min(iRequestSize, limit);
+}
+
+/**
+ * Function will write to m_buf at m_end % m_size location
+ * it will write at maximum m_size, but it will only write
+ * as much it can without wrapping around in the buffer
+ *
+ * It will always leave m_size_back of the backbuffer intact
+ * but if the back buffer is less than that, that space is
+ * usable to write.
+ *
+ * If back buffer is filled to an larger extent than
+ * m_size_back, it will allow it to be overwritten
+ * until only m_size_back data remains.
+ *
+ * The following always apply:
+ * * m_end <= m_cur <= m_end
+ * * m_end - m_beg <= m_size
+ *
+ * Multiple calls may be needed to fill buffer completely.
+ */
+int CCircularCache::WriteToCache(const char *buf, size_t len)
+{
+ std::unique_lock<CCriticalSection> lock(m_sync);
+
+ // where are we in the buffer
+ size_t pos = m_end % m_size;
+ size_t back = (size_t)(m_cur - m_beg);
+ size_t front = (size_t)(m_end - m_cur);
+
+ size_t limit = m_size - std::min(back, m_size_back) - front;
+ size_t wrap = m_size - pos;
+
+ // limit by max forward size
+ if(len > limit)
+ len = limit;
+
+ // limit to wrap point
+ if(len > wrap)
+ len = wrap;
+
+ if(len == 0)
+ return 0;
+
+ if (m_buf == NULL)
+ return 0;
+
+ // write the data
+ memcpy(m_buf + pos, buf, len);
+ m_end += len;
+
+ // drop history that was overwritten
+ if(m_end - m_beg > (int64_t)m_size)
+ m_beg = m_end - m_size;
+
+ m_written.Set();
+
+ return len;
+}
+
+/**
+ * Reads data from cache. Will only read up till
+ * the buffer wrap point. So multiple calls
+ * may be needed to empty the whole cache
+ */
+int CCircularCache::ReadFromCache(char *buf, size_t len)
+{
+ std::unique_lock<CCriticalSection> lock(m_sync);
+
+ size_t pos = m_cur % m_size;
+ size_t front = (size_t)(m_end - m_cur);
+ size_t avail = std::min(m_size - pos, front);
+
+ if(avail == 0)
+ {
+ if(IsEndOfInput())
+ return 0;
+ else
+ return CACHE_RC_WOULD_BLOCK;
+ }
+
+ if(len > avail)
+ len = avail;
+
+ if(len == 0)
+ return 0;
+
+ if (m_buf == NULL)
+ return 0;
+
+ memcpy(buf, m_buf + pos, len);
+ m_cur += len;
+
+ m_space.Set();
+
+ return len;
+}
+
+/* Wait "millis" milliseconds for "minimum" amount of data to come in.
+ * Note that caller needs to make sure there's sufficient space in the forward
+ * buffer for "minimum" bytes else we may block the full timeout time
+ */
+int64_t CCircularCache::WaitForData(uint32_t minimum, std::chrono::milliseconds timeout)
+{
+ std::unique_lock<CCriticalSection> lock(m_sync);
+ int64_t avail = m_end - m_cur;
+
+ if (timeout == 0ms || IsEndOfInput())
+ return avail;
+
+ if(minimum > m_size - m_size_back)
+ minimum = m_size - m_size_back;
+
+ XbmcThreads::EndTime<> endtime{timeout};
+ while (!IsEndOfInput() && avail < minimum && !endtime.IsTimePast() )
+ {
+ lock.unlock();
+ m_written.Wait(50ms); // may miss the deadline. shouldn't be a problem.
+ lock.lock();
+ avail = m_end - m_cur;
+ }
+
+ return avail;
+}
+
+int64_t CCircularCache::Seek(int64_t pos)
+{
+ std::unique_lock<CCriticalSection> lock(m_sync);
+
+ // if seek is a bit over what we have, try to wait a few seconds for the data to be available.
+ // we try to avoid a (heavy) seek on the source
+ if (pos >= m_end && pos < m_end + 100000)
+ {
+ /* Make everything in the cache (back & forward) back-cache, to make sure
+ * there's sufficient forward space. Increasing it with only 100000 may not be
+ * sufficient due to variable filesystem chunksize
+ */
+ m_cur = m_end;
+
+ lock.unlock();
+ WaitForData((size_t)(pos - m_cur), 5s);
+ lock.lock();
+
+ if (pos < m_beg || pos > m_end)
+ CLog::Log(LOGDEBUG,
+ "CCircularCache::{} - ({}) Wait for data failed for pos {}, ended up at {}",
+ __FUNCTION__, fmt::ptr(this), pos, m_cur);
+ }
+
+ if (pos >= m_beg && pos <= m_end)
+ {
+ m_cur = pos;
+ return pos;
+ }
+
+ return CACHE_RC_ERROR;
+}
+
+bool CCircularCache::Reset(int64_t pos)
+{
+ std::unique_lock<CCriticalSection> lock(m_sync);
+ if (IsCachedPosition(pos))
+ {
+ m_cur = pos;
+ return false;
+ }
+ m_end = pos;
+ m_beg = pos;
+ m_cur = pos;
+
+ return true;
+}
+
+int64_t CCircularCache::CachedDataEndPosIfSeekTo(int64_t iFilePosition)
+{
+ if (IsCachedPosition(iFilePosition))
+ return m_end;
+ return iFilePosition;
+}
+
+int64_t CCircularCache::CachedDataStartPos()
+{
+ return m_beg;
+}
+
+int64_t CCircularCache::CachedDataEndPos()
+{
+ return m_end;
+}
+
+bool CCircularCache::IsCachedPosition(int64_t iFilePosition)
+{
+ return iFilePosition >= m_beg && iFilePosition <= m_end;
+}
+
+CCacheStrategy *CCircularCache::CreateNew()
+{
+ return new CCircularCache(m_size - m_size_back, m_size_back);
+}
+