diff options
-rw-r--r-- | xbmc/TextureCache.cpp | 44 | ||||
-rw-r--r-- | xbmc/TextureCache.h | 7 | ||||
-rw-r--r-- | xbmc/imagefiles/CMakeLists.txt | 6 | ||||
-rw-r--r-- | xbmc/imagefiles/ImageCacheCleaner.cpp | 112 | ||||
-rw-r--r-- | xbmc/imagefiles/ImageCacheCleaner.h | 58 |
5 files changed, 224 insertions, 3 deletions
diff --git a/xbmc/TextureCache.cpp b/xbmc/TextureCache.cpp index c2e1c37af2..4d8fb2c047 100644 --- a/xbmc/TextureCache.cpp +++ b/xbmc/TextureCache.cpp @@ -15,11 +15,13 @@ #include "filesystem/File.h" #include "filesystem/IFileTypes.h" #include "guilib/Texture.h" +#include "imagefiles/ImageCacheCleaner.h" #include "imagefiles/ImageFileURL.h" #include "profiles/ProfileManager.h" #include "settings/SettingsComponent.h" #include "utils/Crc32.h" #include "utils/Job.h" +#include "utils/JobManager.h" #include "utils/StringUtils.h" #include "utils/URIUtils.h" #include "utils/log.h" @@ -27,12 +29,14 @@ #include <chrono> #include <exception> #include <mutex> +#include <optional> #include <string.h> using namespace XFILE; using namespace std::chrono_literals; -CTextureCache::CTextureCache() : CJobQueue(false, 1, CJob::PRIORITY_LOW_PAUSABLE) +CTextureCache::CTextureCache() + : CJobQueue(false, 1, CJob::PRIORITY_LOW_PAUSABLE), m_cleanTimer{[this]() { CleanTimer(); }} { } @@ -40,6 +44,7 @@ CTextureCache::~CTextureCache() = default; void CTextureCache::Initialize() { + m_cleanTimer.Start(60s); std::unique_lock<CCriticalSection> lock(m_databaseSection); if (!m_database.IsOpen()) m_database.Open(); @@ -344,3 +349,40 @@ bool CTextureCache::Export(const std::string &image, const std::string &destinat } return false; } + +void CTextureCache::CleanTimer() +{ + CServiceBroker::GetJobManager()->Submit( + [this]() + { + auto next = ScanOldestCache(); + m_cleanTimer.Start(next); + }, + CJob::PRIORITY_LOW_PAUSABLE); +} + +std::chrono::milliseconds CTextureCache::ScanOldestCache() +{ + auto cleaner = IMAGE_FILES::CImageCacheCleaner::Create(); + if (!cleaner) + return std::chrono::hours(1); + + const unsigned int cleanAmount = 1000; + const auto result = cleaner->ScanOldestCache(cleanAmount); + for (const auto& image : result.imagesToClean) + { + ClearCachedImage(image); + } + + // update in the next 6 - 48 hours depending on number of items processed + const auto minTime = 6; + const auto maxTime = 24; + const auto zeroItemTime = 48; + const unsigned int next = + result.processedCount == 0 + ? zeroItemTime + : minTime + (maxTime - minTime) * + (1 - (static_cast<float>(result.processedCount) / cleanAmount)); + CLog::LogF(LOGDEBUG, "scheduling the next image cache cleaning in {} hours", next); + return std::chrono::hours(next); +} diff --git a/xbmc/TextureCache.h b/xbmc/TextureCache.h index 3c56db1f5f..6b0bf44d21 100644 --- a/xbmc/TextureCache.h +++ b/xbmc/TextureCache.h @@ -12,8 +12,10 @@ #include "TextureDatabase.h" #include "threads/CriticalSection.h" #include "threads/Event.h" +#include "threads/Timer.h" #include "utils/JobManager.h" +#include <chrono> #include <memory> #include <set> #include <string> @@ -152,6 +154,7 @@ public: */ bool Export(const std::string &image, const std::string &destination, bool overwrite); bool Export(const std::string &image, const std::string &destination); //! @todo BACKWARD COMPATIBILITY FOR MUSIC THUMBS + private: // private construction, and no assignments; use the provided singleton methods CTextureCache(const CTextureCache&) = delete; @@ -213,6 +216,10 @@ private: */ void OnCachingComplete(bool success, CTextureCacheJob *job); + void CleanTimer(); + std::chrono::milliseconds ScanOldestCache(); + + CTimer m_cleanTimer; CCriticalSection m_databaseSection; CTextureDatabase m_database; std::set<std::string> m_processinglist; ///< currently processing list to avoid 2 jobs being processed at once diff --git a/xbmc/imagefiles/CMakeLists.txt b/xbmc/imagefiles/CMakeLists.txt index 66d1b45aa8..69b4551944 100644 --- a/xbmc/imagefiles/CMakeLists.txt +++ b/xbmc/imagefiles/CMakeLists.txt @@ -1,7 +1,9 @@ -set(SOURCES ImageFileURL.cpp +set(SOURCES ImageCacheCleaner.cpp + ImageFileURL.cpp SpecialImageLoaderFactory.cpp) -set(HEADERS ImageFileURL.h +set(HEADERS ImageCacheCleaner.h + ImageFileURL.h SpecialImageFileLoader.h SpecialImageLoaderFactory.h) diff --git a/xbmc/imagefiles/ImageCacheCleaner.cpp b/xbmc/imagefiles/ImageCacheCleaner.cpp new file mode 100644 index 0000000000..6821b8dc43 --- /dev/null +++ b/xbmc/imagefiles/ImageCacheCleaner.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2024 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 "ImageCacheCleaner.h" + +#include "ServiceBroker.h" +#include "TextureCache.h" +#include "TextureDatabase.h" +#include "addons/AddonDatabase.h" +#include "music/MusicDatabase.h" +#include "utils/log.h" +#include "video/VideoDatabase.h" + +namespace IMAGE_FILES +{ +std::optional<IMAGE_FILES::CImageCacheCleaner> CImageCacheCleaner::Create() +{ + auto result = CImageCacheCleaner(); + if (result.m_valid) + return std::move(result); + return {}; +} + +CImageCacheCleaner::CImageCacheCleaner() +{ + bool valid = true; + m_textureDB = std::make_unique<CTextureDatabase>(); + if (!m_textureDB->Open()) + { + valid = false; + CLog::LogF(LOGWARNING, "failed to initialize image cache cleaner: failed to open texture DB"); + } + + m_videoDB = std::make_unique<CVideoDatabase>(); + if (!m_videoDB->Open()) + { + valid = false; + CLog::LogF(LOGWARNING, "failed to initialize image cache cleaner: failed to open video DB"); + } + + m_musicDB = std::make_unique<CMusicDatabase>(); + if (!m_musicDB->Open()) + { + valid = false; + CLog::LogF(LOGWARNING, "failed to initialize image cache cleaner: failed to open music DB"); + } + + m_addonDB = std::make_unique<ADDON::CAddonDatabase>(); + if (!m_addonDB->Open()) + { + valid = false; + CLog::LogF(LOGWARNING, "failed to initialize image cache cleaner: failed to open add-on DB"); + } + m_valid = valid; +} + +CImageCacheCleaner::~CImageCacheCleaner() +{ + if (m_addonDB) + m_addonDB->Close(); + if (m_musicDB) + m_musicDB->Close(); + if (m_videoDB) + m_videoDB->Close(); + if (m_textureDB) + m_textureDB->Close(); +} + +CleanerResult CImageCacheCleaner::ScanOldestCache(unsigned int imageLimit) +{ + CLog::LogF(LOGDEBUG, "begin process to clean image cache"); + + auto images = m_textureDB->GetOldestCachedImages(imageLimit); + if (images.empty()) + { + CLog::LogF(LOGDEBUG, "found no old cached images to process"); + return CleanerResult{0, {}, {}}; + } + unsigned int processedCount = images.size(); + CLog::LogF(LOGDEBUG, "found {} old cached images to process", processedCount); + + auto usedImages = m_videoDB->GetUsedImages(images); + + auto nextUsedImages = m_musicDB->GetUsedImages(images); + usedImages.insert(usedImages.end(), std::make_move_iterator(nextUsedImages.begin()), + std::make_move_iterator(nextUsedImages.end())); + + nextUsedImages = m_addonDB->GetUsedImages(images); + usedImages.insert(usedImages.end(), std::make_move_iterator(nextUsedImages.begin()), + std::make_move_iterator(nextUsedImages.end())); + + images.erase(std::remove_if(images.begin(), images.end(), + [&usedImages](const std::string& image) { + return std::find(usedImages.cbegin(), usedImages.cend(), image) != + usedImages.cend(); + }), + images.end()); + + m_textureDB->SetKeepCachedImages(usedImages); + + unsigned int keptCount = usedImages.size(); + CLog::LogF(LOGDEBUG, "cleaning {} unused images from cache, keeping {}", images.size(), + keptCount); + + return CleanerResult{processedCount, keptCount, std::move(images)}; +} +} // namespace IMAGE_FILES diff --git a/xbmc/imagefiles/ImageCacheCleaner.h b/xbmc/imagefiles/ImageCacheCleaner.h new file mode 100644 index 0000000000..d975ab9972 --- /dev/null +++ b/xbmc/imagefiles/ImageCacheCleaner.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 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. + */ + +#pragma once + +#include <map> +#include <memory> +#include <optional> +#include <string> +#include <vector> + +class CTextureDatabase; +class CVideoDatabase; +class CMusicDatabase; +namespace ADDON +{ +class CAddonDatabase; +} + +namespace IMAGE_FILES +{ +struct CleanerResult +{ + unsigned int processedCount; + unsigned int keptCount; + std::vector<std::string> imagesToClean; +}; + +/*! + * @brief Clean old unused images from the image cache. + */ +class CImageCacheCleaner +{ +public: + static std::optional<IMAGE_FILES::CImageCacheCleaner> Create(); + ~CImageCacheCleaner(); + CImageCacheCleaner(const CImageCacheCleaner&) = delete; + CImageCacheCleaner& operator=(const CImageCacheCleaner&) = delete; + CImageCacheCleaner(CImageCacheCleaner&&) = default; + CImageCacheCleaner& operator=(CImageCacheCleaner&&) = default; + + CleanerResult ScanOldestCache(unsigned int imageLimit); + +private: + CImageCacheCleaner(); + bool m_valid; + + std::unique_ptr<CTextureDatabase> m_textureDB; + std::unique_ptr<CVideoDatabase> m_videoDB; + std::unique_ptr<CMusicDatabase> m_musicDB; + std::unique_ptr<ADDON::CAddonDatabase> m_addonDB; +}; +} // namespace IMAGE_FILES |