persitent cache for image download
This commit is contained in:
@@ -11,6 +11,7 @@ class TextureCacheService {
|
|||||||
static TextureCacheService *getInstance();
|
static TextureCacheService *getInstance();
|
||||||
|
|
||||||
Glib::RefPtr<Gdk::Texture> getTexture(const std::string &url);
|
Glib::RefPtr<Gdk::Texture> getTexture(const std::string &url);
|
||||||
|
void pruneCache();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TextureCacheService() = default;
|
TextureCacheService() = default;
|
||||||
|
|||||||
@@ -160,4 +160,7 @@ button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.notification-button-box {
|
.notification-button-box {
|
||||||
}
|
margin-top: 8px;
|
||||||
|
border-top: 1px solid rgba(100, 100, 100, 0.3);
|
||||||
|
padding-top: 6px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include "services/dbus/notification.hpp"
|
#include "services/dbus/notification.hpp"
|
||||||
#include "services/dbus/mpris.hpp"
|
#include "services/dbus/mpris.hpp"
|
||||||
|
#include "services/textureCache.hpp"
|
||||||
|
|
||||||
App::App() {
|
App::App() {
|
||||||
this->app = Gtk::Application::create("org.example.mybar");
|
this->app = Gtk::Application::create("org.example.mybar");
|
||||||
@@ -43,6 +44,7 @@ App::App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void App::setupServices() {
|
void App::setupServices() {
|
||||||
|
TextureCacheService::getInstance()->pruneCache();
|
||||||
|
|
||||||
this->trayService->start();
|
this->trayService->start();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ void NotificationController::showSpotifyNotification(MprisController::MprisPlaye
|
|||||||
buttonBox->set_center_widget(*playPauseButton);
|
buttonBox->set_center_widget(*playPauseButton);
|
||||||
buttonBox->set_end_widget(*nextButton);
|
buttonBox->set_end_widget(*nextButton);
|
||||||
|
|
||||||
// rightArea->append(*buttonBox);
|
rightArea->append(*buttonBox);
|
||||||
|
|
||||||
container->append(*rightArea);
|
container->append(*rightArea);
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,75 @@
|
|||||||
#include "services/textureCache.hpp"
|
#include "services/textureCache.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdlib>
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "giomm/file.h"
|
||||||
#include "glibmm/bytes.h"
|
#include "glibmm/bytes.h"
|
||||||
|
|
||||||
namespace {
|
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) {
|
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 *buffer = static_cast<std::vector<unsigned char> *>(userp);
|
||||||
auto total = size * nmemb;
|
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;
|
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()) {
|
if (url.empty()) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@@ -38,6 +115,14 @@ Glib::RefPtr<Gdk::Texture> download_texture_from_url(const std::string &url) {
|
|||||||
return {};
|
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());
|
auto bytes = Glib::Bytes::create(buffer.data(), buffer.size());
|
||||||
return Gdk::Texture::create_from_bytes(bytes);
|
return Gdk::Texture::create_from_bytes(bytes);
|
||||||
}
|
}
|
||||||
@@ -58,10 +143,81 @@ Glib::RefPtr<Gdk::Texture> TextureCacheService::getTexture(const std::string &ur
|
|||||||
return it->second;
|
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) {
|
if (texture) {
|
||||||
cache.emplace(url, 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;
|
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