persitent cache for image download

This commit is contained in:
2026-02-01 12:06:48 +01:00
parent dc325834c7
commit 17aef717b7
5 changed files with 166 additions and 4 deletions

View File

@@ -11,6 +11,7 @@ class TextureCacheService {
static TextureCacheService *getInstance();
Glib::RefPtr<Gdk::Texture> getTexture(const std::string &url);
void pruneCache();
private:
TextureCacheService() = default;

View File

@@ -160,4 +160,7 @@ button {
}
.notification-button-box {
}
margin-top: 8px;
border-top: 1px solid rgba(100, 100, 100, 0.3);
padding-top: 6px;
}

View File

@@ -4,6 +4,7 @@
#include <vector>
#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();
}

View File

@@ -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);

View File

@@ -1,11 +1,75 @@
#include "services/textureCache.hpp"
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <curl/curl.h>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <sstream>
#include <vector>
#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<std::chrono::system_clock::duration>(
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<std::vector<unsigned char> *>(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<Gdk::Texture> download_texture_from_url(const std::string &url) {
Glib::RefPtr<Gdk::Texture> 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<Gdk::Texture> download_texture_from_url(const std::string &url, const std::filesystem::path &path) {
if (url.empty()) {
return {};
}
@@ -38,6 +115,14 @@ Glib::RefPtr<Gdk::Texture> 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<const char *>(buffer.data()), static_cast<std::streamsize>(buffer.size()));
}
auto bytes = Glib::Bytes::create(buffer.data(), buffer.size());
return Gdk::Texture::create_from_bytes(bytes);
}
@@ -58,10 +143,81 @@ Glib::RefPtr<Gdk::Texture> 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<std::pair<std::filesystem::path, std::uintmax_t>> 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<std::uintmax_t>(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;
}
}
}
}