Isolate avatar preparation generations and guard reentrant signals

This commit is contained in:
evgeny committed 2026-10-27 12:19:31 +01:00
1 parent d307492d22
commit 8c30c8e6ce
2 files changed
+23 -6

No files matched your search

+17 -5
View File
@@ -7,6 +7,7 @@
#include <QFile>
#include <QFileInfo>
#include <QMetaObject>
#include <QPointer>
#include <QRandomGenerator>
#include <QRunnable>
#include <QStandardPaths>
@@ -270,12 +271,14 @@ void AvatarManager::saveDefault() {
const QByteArray value = m_defaultServer ? (QString::number(m_defaultServer, 16) + '/' + m_defaultHash + ".json").toLatin1() : QByteArray("-");
const char name[] = "avatar_default";
memcpy(q->data, name, sizeof(name)); memcpy(q->data + sizeof(name), value.constData(), value.size());
gui_bridge_post_uasync_fn(chat_core_save_ui_state_trampoline, q);
if (gui_bridge_post_uasync_owned_fn(chat_core_save_ui_state_trampoline, q) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "avatar: default preference queue rejected"); u_free(q);
}
}
void AvatarManager::uploadDefault(const QString& source, quint64 server, const QRectF& crop) {
if (!m_ready || m_preparing || !server || !gui_bridge_is_uasync_ready()) return;
m_preparing = true; emit statusChanged(text("Preparing avatar…"));
m_preparing = true;
const QString directory = m_directory; const quint64 generation = ++m_generation; cancelUploads();
QStringList pinned; if (!m_defaultHash.isEmpty()) pinned << m_defaultHash;
for (const auto& job : m_jobs) if (job.upload) pinned << job.ref.hash;
@@ -283,8 +286,11 @@ void AvatarManager::uploadDefault(const QString& source, quint64 server, const Q
const auto result = AvatarCache::prepare(source, directory, crop);
if (!result.image.isNull()) { auto keep = pinned; keep << result.hash; AvatarCache::prune(directory, keep); }
QMetaObject::invokeMethod(this, [this, result, server, generation] {
if (generation != m_generation) {
DEBUG_DEBUG(DEBUG_CATEGORY_MEDIA, "avatar: discard stale preparation generation=%llu current=%llu",
(unsigned long long)generation, (unsigned long long)m_generation); return;
}
m_preparing = false;
if (generation != m_generation) return;
if (result.image.isNull()) {
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "avatar: preparation failed reason=%s", qPrintable(result.error));
emit statusChanged(text("Cannot prepare avatar: %1").arg(result.error)); return;
@@ -292,11 +298,17 @@ void AvatarManager::uploadDefault(const QString& source, quint64 server, const Q
m_defaultHash = result.hash; m_defaultServer = server; saveDefault();
m_records[result.hash] = result.record;
m_images.insert(result.hash, new QImage(result.image), qMax(1, int(result.image.sizeInBytes() / 1024)));
m_missing.remove(result.hash); emit serversChanged(); emit statusChanged(text("Uploading avatar…"));
m_missing.remove(result.hash);
QPointer<AvatarManager> self(this);
emit serversChanged();
if (!self || generation != m_generation) return;
emit statusChanged(text("Uploading avatar…"));
if (!self || generation != m_generation) return;
for (auto group : m_servers.keys()) { m_retry.remove(key(group, m_db->myNodeId())); scheduleOwn(group); }
pump();
}, Qt::QueuedConnection);
}));
emit statusChanged(text("Preparing avatar…"));
}
void AvatarManager::cancelUploads() {
@@ -309,7 +321,7 @@ void AvatarManager::cancelUploads() {
void AvatarManager::clearDefault() {
if (!m_ready || !gui_bridge_is_uasync_ready()) return;
++m_generation; cancelUploads(); m_defaultHash.clear(); m_defaultServer = 0; saveDefault();
++m_generation; m_preparing = false; cancelUploads(); m_defaultHash.clear(); m_defaultServer = 0; saveDefault();
for (const auto& ref : m_refs) if (ref.owner == m_db->myNodeId()) {
Job job; job.ref = ref; job.ref.server = 0; job.ref.hash.clear(); job.upload = true; job.generation = m_generation;
enqueue(job);
+6 -1
View File
@@ -1,4 +1,5 @@
/* Один менеджер на аккаунт: подписанные ссылки, общий кеш и ограниченная очередь передач. */
/* Один менеджер на аккаунт: подписанные ссылки, общий кеш и ограниченная очередь передач.
* Методы/сигналы — GUI-поток; DbManager заимствуется и должен переживать менеджер. */
#pragma once
#include "avatarcache.h"
#include <QObject>
@@ -26,6 +27,10 @@ public:
void onPublished(const uint8_t* data, int length);
QImage image(const QString& channel, quint64 owner);
QMap<quint64, QString> servers() const;
/* Начинает worker-подготовку локального source; требует ready, ненулевой server и готовое ядро.
* crop — нормализованный после EXIF квадрат из selectAvatarCrop, пустой — центральный квадрат.
* Пока текущая подготовка занята, повтор игнорируется; clearDefault отменяет поколение и позволяет новый upload.
* Устаревший worker не меняет default/preparing. Из обработчиков сигналов подготовки можно удалить manager. */
void uploadDefault(const QString& source, quint64 server, const QRectF& crop = {});
void openGallery(const QString& channel, quint64 owner);
void closeGallery();