From 58df9284d42b0ad9e7aad1629f6888ccbe8638c1 Mon Sep 17 00:00:00 2001 From: emeric Date: Fri, 29 Mar 2024 21:02:36 +0100 Subject: [PATCH] Extracted image cache --- src/libs/services/cover/impl/CoverService.cpp | 63 ++++------------ src/libs/services/cover/impl/CoverService.hpp | 55 +------------- src/libs/services/cover/impl/ImageCache.cpp | 74 +++++++++++++++++++ src/libs/services/cover/impl/ImageCache.hpp | 72 ++++++++++++++++++ 4 files changed, 162 insertions(+), 102 deletions(-) create mode 100644 src/libs/services/cover/impl/ImageCache.cpp create mode 100644 src/libs/services/cover/impl/ImageCache.hpp diff --git a/src/libs/services/cover/impl/CoverService.cpp b/src/libs/services/cover/impl/CoverService.cpp index 605e5a92..49859f4e 100644 --- a/src/libs/services/cover/impl/CoverService.cpp +++ b/src/libs/services/cover/impl/CoverService.cpp @@ -117,7 +117,7 @@ namespace lms::cover CoverService::CoverService(db::Db& db, const std::filesystem::path& defaultSvgCoverPath) : _db{ db } - , _maxCacheSize{ core::Service::get()->getULong("cover-max-cache-size", 30) * 1000 * 1000 } + , _cache{ core::Service::get()->getULong("cover-max-cache-size", 30) * 1000 * 1000 } , _maxFileSize{ core::Service::get()->getULong("cover-max-file-size", 10) * 1000 * 1000 } , _preferredFileNames{ constructPreferredFileNames() } , _artistFileNames{ constructArtistFileNames() } @@ -125,7 +125,7 @@ namespace lms::cover setJpegQuality(core::Service::get()->getULong("cover-jpeg-quality", 75)); LMS_LOG(COVER, INFO, "Default cover path = '" << defaultSvgCoverPath.string() << "'"); - LMS_LOG(COVER, INFO, "Max cache size = " << _maxCacheSize); + LMS_LOG(COVER, INFO, "Max cache size = " << _cache.getMaxCacheSize()); LMS_LOG(COVER, INFO, "Max file size = " << _maxFileSize); LMS_LOG(COVER, INFO, "Preferred file names: " << core::stringUtils::joinStrings(_preferredFileNames, ",")); @@ -199,7 +199,7 @@ namespace lms::cover std::unique_ptr image; - for (std::string_view filename : preferredFileNames) + for (const std::string_view filename : preferredFileNames) { image = tryLoadImageFromFilename(filename); if (image) @@ -267,7 +267,7 @@ namespace lms::cover std::error_code ec; std::filesystem::directory_iterator itPath(directoryPath, ec); - std::filesystem::directory_iterator itEnd; + const std::filesystem::directory_iterator itEnd; while (!ec && itPath != itEnd) { const std::filesystem::path& path{ *itPath }; @@ -306,9 +306,9 @@ namespace lms::cover { using namespace db; - const CacheEntryDesc cacheEntryDesc{ trackId, width }; + const ImageCache::EntryDesc cacheEntryDesc{ trackId, width }; - std::shared_ptr cover{ loadFromCache(cacheEntryDesc) }; + std::shared_ptr cover{ _cache.getImage(cacheEntryDesc) }; if (cover) return cover; @@ -331,7 +331,7 @@ namespace lms::cover } if (cover) - saveToCache(cacheEntryDesc, cover); + _cache.addImage(cacheEntryDesc, cover); return cover; } @@ -339,9 +339,9 @@ namespace lms::cover std::shared_ptr CoverService::getFromRelease(db::ReleaseId releaseId, ImageSize width) { using namespace db; - const CacheEntryDesc cacheEntryDesc{ releaseId, width }; + const ImageCache::EntryDesc cacheEntryDesc{ releaseId, width }; - std::shared_ptr cover{ loadFromCache(cacheEntryDesc) }; + std::shared_ptr cover{ _cache.getImage(cacheEntryDesc) }; if (cover) return cover; @@ -380,7 +380,7 @@ namespace lms::cover } if (cover) - saveToCache(cacheEntryDesc, cover); + _cache.addImage(cacheEntryDesc, cover); return cover; } @@ -388,9 +388,9 @@ namespace lms::cover std::shared_ptr CoverService::getFromArtist(db::ArtistId artistId, ImageSize width) { using namespace db; - const CacheEntryDesc cacheEntryDesc{ artistId, width }; + const ImageCache::EntryDesc cacheEntryDesc{ artistId, width }; - std::shared_ptr artistImage{ loadFromCache(cacheEntryDesc) }; + std::shared_ptr artistImage{ _cache.getImage(cacheEntryDesc) }; if (artistImage) return artistImage; @@ -480,20 +480,13 @@ namespace lms::cover } if (artistImage) - saveToCache(cacheEntryDesc, artistImage); + _cache.addImage(cacheEntryDesc, artistImage); return artistImage; } void CoverService::flushCache() { - std::unique_lock lock{ _cacheMutex }; - - LMS_LOG(COVER, DEBUG, "Cache stats: hits = " << _cacheHits << ", misses = " << _cacheMisses << ", nb entries = " << _cache.size() << ", size = " << _cacheSize); - _cacheHits = 0; - _cacheMisses = 0; - _cacheSize = 0; - _cache.clear(); } void CoverService::setJpegQuality(unsigned quality) @@ -503,35 +496,5 @@ namespace lms::cover LMS_LOG(COVER, INFO, "JPEG export quality = " << _jpegQuality); } - void CoverService::saveToCache(const CacheEntryDesc& entryDesc, std::shared_ptr image) - { - std::unique_lock lock{ _cacheMutex }; - - while (_cacheSize + image->getDataSize() > _maxCacheSize && !_cache.empty()) - { - auto itRandom{ core::random::pickRandom(_cache) }; - _cacheSize -= itRandom->second->getDataSize(); - _cache.erase(itRandom); - } - - _cacheSize += image->getDataSize(); - _cache[entryDesc] = image; - } - - std::shared_ptr CoverService::loadFromCache(const CacheEntryDesc& entryDesc) - { - std::shared_lock lock{ _cacheMutex }; - - auto it{ _cache.find(entryDesc) }; - if (it == std::cend(_cache)) - { - ++_cacheMisses; - return nullptr; - } - - ++_cacheHits; - return it->second; - } - } // namespace lms::cover diff --git a/src/libs/services/cover/impl/CoverService.hpp b/src/libs/services/cover/impl/CoverService.hpp index 40fffe4c..946d3845 100644 --- a/src/libs/services/cover/impl/CoverService.hpp +++ b/src/libs/services/cover/impl/CoverService.hpp @@ -19,19 +19,14 @@ #pragma once -#include #include #include -#include -#include -#include -#include -#include #include #include "services/cover/ICoverService.hpp" #include "image/IEncodedImage.hpp" #include "database/Types.hpp" +#include "ImageCache.hpp" namespace lms::db { @@ -43,42 +38,6 @@ namespace lms::av class IAudioFile; } -namespace lms::cover -{ - struct CacheEntryDesc - { - std::variant id; - std::size_t size; - - bool operator==(const CacheEntryDesc& other) const - { - return id == other.id - && size == other.size; - } - }; -} // ns Cover - -namespace std -{ - template<> - class hash - { - public: - size_t operator()(const lms::cover::CacheEntryDesc& e) const - { - size_t h{}; - std::visit([&](auto id) - { - using IdType = std::decay_t; - h ^= std::hash{}(id); - }, e.id); - h ^= std::hash{}(e.size) << 1; - return h; - } - }; - -} // ns std - namespace lms::cover { class CoverService : public ICoverService @@ -106,21 +65,13 @@ namespace lms::cover std::unique_ptr getFromDirectory(const std::filesystem::path& directory, image::ImageSize width, const std::vector& preferredFileNames, bool allowPickRandom) const; std::unique_ptr getFromSameNamedFile(const std::filesystem::path& filePath, image::ImageSize width) const; - bool checkCoverFile(const std::filesystem::path& directoryPath) const; + bool checkCoverFile(const std::filesystem::path& filePath) const; db::Db& _db; - std::shared_mutex _cacheMutex; - std::unordered_map> _cache; + ImageCache _cache; std::shared_ptr _defaultCover; - std::atomic _cacheMisses{}; - std::atomic _cacheHits{}; - std::size_t _cacheSize{}; - void saveToCache(const CacheEntryDesc& entryDesc, std::shared_ptr image); - std::shared_ptr loadFromCache(const CacheEntryDesc& entryDesc); - - const std::size_t _maxCacheSize; static inline const std::vector _fileExtensions{ ".jpg", ".jpeg", ".png", ".bmp" }; // TODO parametrize const std::size_t _maxFileSize; const std::vector _preferredFileNames; diff --git a/src/libs/services/cover/impl/ImageCache.cpp b/src/libs/services/cover/impl/ImageCache.cpp new file mode 100644 index 00000000..1f9b6550 --- /dev/null +++ b/src/libs/services/cover/impl/ImageCache.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2015 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "ImageCache.hpp" + +#include + +#include "core/Random.hpp" +#include "core/ILogger.hpp" + +namespace lms::cover +{ + ImageCache::ImageCache(std::size_t maxCacheSize) + : _maxCacheSize{ maxCacheSize } + {} + + void ImageCache::addImage(const EntryDesc& entryDesc, std::shared_ptr image) + { + const std::unique_lock lock{ _mutex }; + + while (_cacheSize + image->getDataSize() > _maxCacheSize && !_cache.empty()) + { + auto itRandom{ core::random::pickRandom(_cache) }; + _cacheSize -= itRandom->second->getDataSize(); + _cache.erase(itRandom); + } + + _cacheSize += image->getDataSize(); + _cache[entryDesc] = image; + } + + std::shared_ptr ImageCache::getImage(const EntryDesc& entryDesc) const + { + const std::shared_lock lock{ _mutex }; + + const auto it{ _cache.find(entryDesc) }; + if (it == std::cend(_cache)) + { + ++_cacheMisses; + return nullptr; + } + + ++_cacheHits; + return it->second; + + } + + void ImageCache::flush() + { + const std::unique_lock lock{ _mutex }; + + LMS_LOG(COVER, DEBUG, "Cache stats: hits = " << _cacheHits.load() << ", misses = " << _cacheMisses.load() << ", nb entries = " << _cache.size() << ", size = " << _cacheSize); + _cacheHits = 0; + _cacheMisses = 0; + _cacheSize = 0; + _cache.clear(); + } +} \ No newline at end of file diff --git a/src/libs/services/cover/impl/ImageCache.hpp b/src/libs/services/cover/impl/ImageCache.hpp new file mode 100644 index 00000000..9ee44e14 --- /dev/null +++ b/src/libs/services/cover/impl/ImageCache.hpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2015 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +#include +#include +#include +#include + +#include "database/ArtistId.hpp" +#include "database/ReleaseId.hpp" +#include "database/TrackId.hpp" +#include "image/IEncodedImage.hpp" + +namespace lms::cover +{ + class ImageCache + { + public: + ImageCache(std::size_t maxCacheSize); + + struct EntryDesc + { + using VariantType = std::variant; + VariantType id; + std::size_t size; + + bool operator==(const EntryDesc& other) const = default; + }; + + std::size_t getMaxCacheSize() const { return _maxCacheSize; } + + void addImage(const EntryDesc& entryDesc, std::shared_ptr image); + std::shared_ptr getImage(const EntryDesc& entryDesc) const; + void flush(); + + private: + const std::size_t _maxCacheSize; + + mutable std::shared_mutex _mutex; + + struct EntryHasher + { + constexpr std::size_t operator()(const EntryDesc& entry) const + { + return std::hash{}(entry.id) ^ std::hash{}(entry.size); + } + }; + + std::unordered_map, EntryHasher> _cache; + std::size_t _cacheSize{}; + mutable std::atomic _cacheMisses{}; + mutable std::atomic _cacheHits{}; + }; +} \ No newline at end of file