persitent cache for image download
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user