From 17aef717b73baadb4fe6e2be3155cbe31a2acb1f Mon Sep 17 00:00:00 2001 From: Arif Hasanic Date: Sun, 1 Feb 2026 12:06:48 +0100 Subject: [PATCH] persitent cache for image download --- include/services/textureCache.hpp | 1 + resources/bar.css | 5 +- src/app.cpp | 2 + src/services/notificationController.cpp | 2 +- src/services/textureCache.cpp | 160 +++++++++++++++++++++++- 5 files changed, 166 insertions(+), 4 deletions(-) diff --git a/include/services/textureCache.hpp b/include/services/textureCache.hpp index 80aedaa..705ac9f 100644 --- a/include/services/textureCache.hpp +++ b/include/services/textureCache.hpp @@ -11,6 +11,7 @@ class TextureCacheService { static TextureCacheService *getInstance(); Glib::RefPtr getTexture(const std::string &url); + void pruneCache(); private: TextureCacheService() = default; diff --git a/resources/bar.css b/resources/bar.css index cd17442..f25404c 100644 --- a/resources/bar.css +++ b/resources/bar.css @@ -160,4 +160,7 @@ button { } .notification-button-box { -} + margin-top: 8px; + border-top: 1px solid rgba(100, 100, 100, 0.3); + padding-top: 6px; + } diff --git a/src/app.cpp b/src/app.cpp index 160391a..834c1b9 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -4,6 +4,7 @@ #include #include "services/dbus/notification.hpp" #include "services/dbus/mpris.hpp" +#include "services/textureCache.hpp" App::App() { this->app = Gtk::Application::create("org.example.mybar"); @@ -43,6 +44,7 @@ App::App() { } void App::setupServices() { + TextureCacheService::getInstance()->pruneCache(); this->trayService->start(); } diff --git a/src/services/notificationController.cpp b/src/services/notificationController.cpp index 57a4ba6..59f8363 100644 --- a/src/services/notificationController.cpp +++ b/src/services/notificationController.cpp @@ -104,7 +104,7 @@ void NotificationController::showSpotifyNotification(MprisController::MprisPlaye buttonBox->set_center_widget(*playPauseButton); buttonBox->set_end_widget(*nextButton); - // rightArea->append(*buttonBox); + rightArea->append(*buttonBox); container->append(*rightArea); diff --git a/src/services/textureCache.cpp b/src/services/textureCache.cpp index 9aca9dc..6cec102 100644 --- a/src/services/textureCache.cpp +++ b/src/services/textureCache.cpp @@ -1,11 +1,75 @@ #include "services/textureCache.hpp" +#include +#include +#include #include +#include +#include +#include +#include #include +#include "giomm/file.h" #include "glibmm/bytes.h" namespace { +#define CACHE_SIZE 128 +#define CACHE_AGE 168 + +constexpr std::uint64_t kFnvOffsetBasis = 14695981039346656037ull; +constexpr std::uint64_t kFnvPrime = 1099511628211ull; + +std::string to_hex(std::uint64_t value) { + std::ostringstream stream; + stream << std::hex << std::setw(16) << std::setfill('0') << value; + return stream.str(); +} + +std::string hash_url(const std::string &url) { + std::uint64_t hash = kFnvOffsetBasis; + for (unsigned char c : url) { + hash ^= c; + hash *= kFnvPrime; + } + return to_hex(hash); +} + +std::filesystem::path get_cache_dir() { + const char *xdg_cache = std::getenv("XDG_CACHE_HOME"); + if (xdg_cache && *xdg_cache) { + return std::filesystem::path(xdg_cache) / "bar"; + } + + const char *home = std::getenv("HOME"); + if (home && *home) { + return std::filesystem::path(home) / ".cache" / "bar"; + } + + return std::filesystem::temp_directory_path() / "bar"; +} + +std::filesystem::path get_cache_path_for_url(const std::string &url) { + auto filename = hash_url(url); + + auto last_slash = url.find_last_of('/'); + auto last_dot = url.find_last_of('.'); + if (last_dot != std::string::npos && (last_slash == std::string::npos || last_dot > last_slash)) { + auto ext = url.substr(last_dot); + if (ext.size() <= 10) { + filename += ext; + } + } + + return get_cache_dir() / filename; +} + +std::chrono::system_clock::time_point to_system_clock(std::filesystem::file_time_type time) { + return std::chrono::time_point_cast( + time - std::filesystem::file_time_type::clock::now() + std::chrono::system_clock::now() + ); +} + size_t write_to_buffer(void *contents, size_t size, size_t nmemb, void *userp) { auto *buffer = static_cast *>(userp); auto total = size * nmemb; @@ -14,7 +78,20 @@ size_t write_to_buffer(void *contents, size_t size, size_t nmemb, void *userp) { return total; } -Glib::RefPtr download_texture_from_url(const std::string &url) { +Glib::RefPtr load_texture_from_file(const std::filesystem::path &path) { + if (!std::filesystem::exists(path)) { + return {}; + } + + auto file = Gio::File::create_for_path(path.string()); + try { + return Gdk::Texture::create_from_file(file); + } catch (...) { + return {}; + } +} + +Glib::RefPtr download_texture_from_url(const std::string &url, const std::filesystem::path &path) { if (url.empty()) { return {}; } @@ -38,6 +115,14 @@ Glib::RefPtr download_texture_from_url(const std::string &url) { return {}; } + std::error_code error; + std::filesystem::create_directories(path.parent_path(), error); + + std::ofstream output(path, std::ios::binary | std::ios::trunc); + if (output) { + output.write(reinterpret_cast(buffer.data()), static_cast(buffer.size())); + } + auto bytes = Glib::Bytes::create(buffer.data(), buffer.size()); return Gdk::Texture::create_from_bytes(bytes); } @@ -58,10 +143,81 @@ Glib::RefPtr TextureCacheService::getTexture(const std::string &ur return it->second; } - auto texture = download_texture_from_url(url); + auto cache_path = get_cache_path_for_url(url); + auto texture = load_texture_from_file(cache_path); + if (!texture) { + texture = download_texture_from_url(url, cache_path); + } + if (texture) { cache.emplace(url, texture); + std::error_code error; + std::filesystem::last_write_time(cache_path, std::filesystem::file_time_type::clock::now(), error); } return texture; } + +void TextureCacheService::pruneCache() { + std::error_code error; + auto cache_dir = get_cache_dir(); + std::filesystem::create_directories(cache_dir, error); + + std::vector> files; + std::uintmax_t total_size = 0; + + auto now = std::chrono::system_clock::now(); + auto max_age = std::chrono::hours(CACHE_AGE); + + for (const auto &entry : std::filesystem::directory_iterator(cache_dir, error)) { + if (error || !entry.is_regular_file()) { + continue; + } + + auto path = entry.path(); + auto last_write = entry.last_write_time(error); + if (!error) { + auto age = now - to_system_clock(last_write); + if (age > max_age) { + std::filesystem::remove(path, error); + continue; + } + } + + auto size = entry.file_size(error); + if (!error) { + total_size += size; + files.emplace_back(path, size); + } + } + + auto max_size = static_cast(CACHE_SIZE) * 1024ull * 1024ull; + if (total_size <= max_size) { + return; + } + + std::sort(files.begin(), files.end(), [&](const auto &left, const auto &right) { + std::error_code left_error; + std::error_code right_error; + auto left_time = std::filesystem::last_write_time(left.first, left_error); + auto right_time = std::filesystem::last_write_time(right.first, right_error); + if (left_error || right_error) { + return left.first.string() < right.first.string(); + } + return left_time < right_time; + }); + + for (const auto &item : files) { + if (total_size <= max_size) { + break; + } + std::filesystem::remove(item.first, error); + if (!error) { + if (total_size >= item.second) { + total_size -= item.second; + } else { + total_size = 0; + } + } + } +}