Browse Source

Protect pending avatar uploads while bounding disk cache retention

master
evgeny 6 days ago
parent
commit
7582509792
  1. 4
      tools/chatgui/src/avatars/avatarcache.cpp
  2. 3
      tools/chatgui/src/avatars/avatarcache.h
  3. 10
      tools/chatgui/src/avatars/avatarmanager.cpp

4
tools/chatgui/src/avatars/avatarcache.cpp

@ -11,14 +11,14 @@ extern "C" {
#include "debug_config.h" #include "debug_config.h"
} }
bool AvatarCache::initialize(const QString& directory) { bool AvatarCache::initialize(const QString& directory, const QStringList& pinned) {
if (!QDir().mkpath(directory)) { if (!QDir().mkpath(directory)) {
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "avatar: cannot create cache path=%s", qPrintable(directory)); return false; DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "avatar: cannot create cache path=%s", qPrintable(directory)); return false;
} }
const auto entries = QDir(directory).entryInfoList({QStringLiteral("*.part*"), QStringLiteral("*.download*")}, QDir::Files); const auto entries = QDir(directory).entryInfoList({QStringLiteral("*.part*"), QStringLiteral("*.download*")}, QDir::Files);
for (const auto& entry : entries) if (!QFile::remove(entry.absoluteFilePath())) for (const auto& entry : entries) if (!QFile::remove(entry.absoluteFilePath()))
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "avatar: cannot remove interrupted cache file=%s", qPrintable(entry.fileName())); DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "avatar: cannot remove interrupted cache file=%s", qPrintable(entry.fileName()));
prune(directory, {}); prune(directory, pinned);
return true; return true;
} }

3
tools/chatgui/src/avatars/avatarcache.h

@ -2,12 +2,13 @@
#pragma once #pragma once
#include <QImage> #include <QImage>
#include <QString> #include <QString>
#include <QStringList>
namespace AvatarCache { namespace AvatarCache {
constexpr int MaxBytes = 1024 * 1024; constexpr int MaxBytes = 1024 * 1024;
constexpr qint64 DiskLimit = 100 * 1024 * 1024; constexpr qint64 DiskLimit = 100 * 1024 * 1024;
struct Result { QImage image; QString hash, error; }; struct Result { QImage image; QString hash, error; };
bool initialize(const QString& directory); bool initialize(const QString& directory, const QStringList& pinned = {});
Result load(const QString& path, const QString& hash); Result load(const QString& path, const QString& hash);
Result prepare(const QString& source, const QString& directory); Result prepare(const QString& source, const QString& directory);
Result publish(const QString& temporary, const QString& directory, const QString& hash); Result publish(const QString& temporary, const QString& directory, const QString& hash);

10
tools/chatgui/src/avatars/avatarmanager.cpp

@ -52,8 +52,9 @@ void AvatarManager::initialize() {
const QString file = m_db->getUiState("avatar_default_file"); const QString file = m_db->getUiState("avatar_default_file");
if (mf_avatar_file_valid(file.toLatin1().constData())) m_defaultHash = file.left(64); if (mf_avatar_file_valid(file.toLatin1().constData())) m_defaultHash = file.left(64);
const QString directory = m_directory; const QString directory = m_directory;
m_pool.start(QRunnable::create([this, directory] { const QStringList pinned = m_defaultHash.isEmpty() ? QStringList() : QStringList{m_defaultHash};
bool ready = AvatarCache::initialize(directory); m_pool.start(QRunnable::create([this, directory, pinned] {
bool ready = AvatarCache::initialize(directory, pinned);
QMetaObject::invokeMethod(this, [this, ready] { QMetaObject::invokeMethod(this, [this, ready] {
m_ready = ready; m_ready = ready;
if (!ready) { emit statusChanged(text("Cannot open avatar cache")); return; } if (!ready) { emit statusChanged(text("Cannot open avatar cache")); return; }
@ -207,7 +208,8 @@ void AvatarManager::clearDefault() {
void AvatarManager::scheduleOwn(quint64 group) { void AvatarManager::scheduleOwn(quint64 group) {
if (!m_ready || m_preparing || !m_defaultServer || m_defaultHash.isEmpty() || !m_servers.value(group).contains(m_defaultServer) || if (!m_ready || m_preparing || !m_defaultServer || m_defaultHash.isEmpty() || !m_servers.value(group).contains(m_defaultServer) ||
!m_images.contains(m_defaultHash)) return; m_missing.contains(m_defaultHash)) return;
if (!m_images.contains(m_defaultHash)) { loadCache(m_defaultHash); return; }
const QString context = key(group, m_db->myNodeId()); const QString context = key(group, m_db->myNodeId());
if (!m_refs.contains(context) || m_retry.value(context) > QDateTime::currentMSecsSinceEpoch()) return; if (!m_refs.contains(context) || m_retry.value(context) > QDateTime::currentMSecsSinceEpoch()) return;
const Ref old = m_refs.value(context); const Ref old = m_refs.value(context);
@ -297,7 +299,7 @@ void AvatarManager::onTransfer(const uint8_t* data, int length) {
if (job.upload) { publish(job); return; } if (job.upload) { publish(job); return; }
const Job snapshot = job; const QString directory = m_directory; job.stage = 101; const Job snapshot = job; const QString directory = m_directory; job.stage = 101;
QStringList pinned; if (!m_defaultHash.isEmpty()) pinned << m_defaultHash; QStringList pinned; if (!m_defaultHash.isEmpty()) pinned << m_defaultHash;
for (const auto& ref : m_refs) if (m_wanted.contains(key(ref.group, ref.owner))) pinned << ref.hash; for (const auto& active : m_jobs) if (active.upload) pinned << active.ref.hash;
m_pool.start(QRunnable::create([this, snapshot, directory, pinned] { m_pool.start(QRunnable::create([this, snapshot, directory, pinned] {
const auto result = AvatarCache::publish(snapshot.local, directory, snapshot.ref.hash); AvatarCache::prune(directory, pinned); const auto result = AvatarCache::publish(snapshot.local, directory, snapshot.ref.hash); AvatarCache::prune(directory, pinned);
QMetaObject::invokeMethod(this, [this, snapshot, result] { QMetaObject::invokeMethod(this, [this, snapshot, result] {

Loading…
Cancel
Save