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.
9 #include "threads/SystemClock.h"
10 #include "CacheStrategy.h"
13 #include "PlatformDefs.h"
14 #include "platform/posix/ConvUtils.h"
17 #include "utils/log.h"
18 #include "SpecialProtocol.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
31 using namespace XFILE
;
33 using namespace std::chrono_literals
;
35 CCacheStrategy::~CCacheStrategy() = default;
37 void CCacheStrategy::EndOfInput() {
41 bool CCacheStrategy::IsEndOfInput()
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()
61 delete m_cacheFileRead
;
62 delete m_cacheFileWrite
;
65 int CSimpleFileCache::Open()
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__
);
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
);
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
);
95 return CACHE_RC_ERROR
;
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__
,
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
)
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__
,
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();
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;
163 const ssize_t lastRead
=
164 m_cacheFileRead
->Read(pBuffer
, std::min(toRead
, static_cast<size_t>(SSIZE_MAX
)));
170 CLog::Log(LOGERROR
, "CSimpleFileCache::{} - <{}> Failed to read from cache", __FUNCTION__
,
172 return CACHE_RC_ERROR
;
174 m_nReadPosition
+= lastRead
;
176 readBytes
+= lastRead
;
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
)
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
;
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
;
218 "CSimpleFileCache::{} - <{}> Requested position {} is beyond cached data ({})",
219 __FUNCTION__
, m_filename
, iFilePosition
, m_nWritePosition
);
220 return CACHE_RC_ERROR
;
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
;
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
);
252 m_nStartPosition
= iSourcePosition
;
253 m_nWritePosition
= m_cacheFileWrite
->Seek(0, SEEK_SET
);
254 m_nReadPosition
= m_cacheFileRead
->Seek(0, SEEK_SET
);
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
);
299 CDoubleCache::~CDoubleCache()
305 int CDoubleCache::Open()
307 return m_pCache
->Open();
310 void CDoubleCache::Close()
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
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
;
373 pCacheTmp
= m_pCache
->CreateNew();
374 if (pCacheTmp
->Open() != CACHE_RC_OK
)
377 return m_pCache
->Reset(iSourcePosition
);
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
432 int64_t ret
= m_pCache
->CachedDataEndPosIfSeekTo(iFilePosition
);
434 return std::max(ret
, m_pCacheOld
->CachedDataEndPosIfSeekTo(iFilePosition
));
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());