diff --git a/tools/chatgui/src/avatars/avatarmanager.cpp b/tools/chatgui/src/avatars/avatarmanager.cpp index b7528d62..96ba1b97 100644 --- a/tools/chatgui/src/avatars/avatarmanager.cpp +++ b/tools/chatgui/src/avatars/avatarmanager.cpp @@ -154,6 +154,10 @@ QImage AvatarManager::fetch(const Ref& ref, bool original, bool gallery) { Job job; job.ref = ref; job.original = original; job.gallery = gallery; job.generation = m_generation; const auto record = m_records.value(ref.hash); job.file = record.original.isEmpty() ? ref.hash + ".json" : (original ? record.original : record.preview); + if (record.original.isEmpty()) { + for (const auto& active : m_jobs) if (!active.upload && active.ref.hash == ref.hash && active.file == job.file) return {}; + for (const auto& queued : m_queue) if (!queued.upload && queued.ref.hash == ref.hash && queued.file == job.file) return {}; + } enqueue(job); pump(); return {}; } @@ -168,20 +172,41 @@ void AvatarManager::openGallery(const QString& channel, quint64 owner) { } void AvatarManager::closeGallery() { + m_galleryEpoch++; if (m_listId && gui_bridge_is_uasync_ready()) { auto* q = (mf_cancel_request*)u_calloc(1, sizeof(mf_cancel_request)); if (q) { q->inst = gui_bridge_get_inst(); q->id = m_listId; gui_bridge_post_uasync_fn(member_files_gui_cancel, q); } else DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "avatar: gallery cancel allocation failed"); } m_listId = 0; m_galleryRef = {}; m_gallery.clear(); m_catalog.clear(); m_listRetries = 0; - QQueue keep; while (!m_queue.isEmpty()) { const auto job = m_queue.dequeue(); if (!job.gallery) keep.enqueue(job); } + auto needed = [this](const Job& job) { + const QString context = key(job.ref.group, job.ref.owner); + return !job.original && m_wanted.contains(context) && m_refs.value(context).hash == job.ref.hash; + }; + for (auto id : m_jobs.keys()) { + auto& job = m_jobs[id]; + if (!job.gallery) continue; + if (needed(job)) { job.gallery = false; continue; } + if (gui_bridge_is_uasync_ready()) { + auto* q = (mf_cancel_request*)u_calloc(1, sizeof(mf_cancel_request)); + if (q) { q->inst = gui_bridge_get_inst(); q->id = id; gui_bridge_post_uasync_fn(member_files_gui_cancel, q); } + else DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "avatar: cancel allocation failed id=%llu", (unsigned long long)id); + } + finish(id, MF_CANCELLED); + } + QQueue keep; while (!m_queue.isEmpty()) { + auto job = m_queue.dequeue(); if (job.gallery && needed(job)) job.gallery = false; + if (!job.gallery) keep.enqueue(job); + } m_queue = keep; } void AvatarManager::listGallery(quint64 cursor, const QByteArray& version) { + if (!gui_bridge_is_uasync_ready() || !m_galleryRef.server) return; auto* q = (mf_gui_request*)u_calloc(1, sizeof(mf_gui_request)); if (!q) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "avatar: catalog allocation failed"); emit galleryError(text("Cannot load avatars")); return; } do { m_listId = QRandomGenerator::global()->generate64(); } while (!m_listId || m_jobs.contains(m_listId)); + m_listCursor = cursor; q->inst = gui_bridge_get_inst(); q->group = m_galleryRef.group; q->server = m_galleryRef.server; q->message.kind = MF_REQUEST; q->message.command = MF_LIST; q->message.request_id = m_listId; q->message.owner = m_galleryRef.owner; q->message.cursor = cursor; memcpy(q->message.path, "avatars", 8); @@ -312,7 +337,7 @@ void AvatarManager::enqueue(const Job& job) { for (const auto& old : m_queue) if ((old.upload == job.upload && old.ref.group == job.ref.group && old.ref.owner == job.ref.owner && (job.upload || (old.ref.hash == job.ref.hash && old.original == job.original))) || (!job.upload && !old.upload && old.ref.hash == job.ref.hash && old.original == job.original)) return; - if (job.upload) m_queue.prepend(job); else m_queue.enqueue(job); + if (job.upload || job.original) m_queue.prepend(job); else m_queue.enqueue(job); } void AvatarManager::pump() { @@ -378,9 +403,9 @@ void AvatarManager::onResponse(const uint8_t* data, int length) { response.owner != m_galleryRef.owner || response.command != MF_LIST || strcmp(response.path, "avatars")) return; m_listId = 0; if ((response.status == MF_STALE || response.status == MF_BUSY) && m_listRetries++ < 3) { - m_catalog.clear(); const Ref source = m_galleryRef; - QTimer::singleShot(1000, this, [this, source] { - if (source.group == m_galleryRef.group && source.owner == m_galleryRef.owner && source.server == m_galleryRef.server) listGallery(); + m_catalog.clear(); const quint64 epoch = m_galleryEpoch; + QTimer::singleShot(1000, this, [this, epoch] { + if (epoch == m_galleryEpoch) listGallery(); }); return; } if (response.status) { @@ -400,7 +425,13 @@ void AvatarManager::onResponse(const uint8_t* data, int length) { m_catalog[QString::fromLatin1(file).left(64)] = mf_get64(p + 11); at += MF_ENTRY_HEADER + n; } - if (response.more) { listGallery(response.cursor, QByteArray((const char*)response.version, 16)); return; } + if (response.more) { + if (response.cursor <= m_listCursor) { + DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "avatar: catalog cursor stalled=%llu", (unsigned long long)response.cursor); + emit galleryError(text("Invalid directory response")); return; + } + listGallery(response.cursor, QByteArray((const char*)response.version, 16)); return; + } m_gallery = m_catalog.keys(); std::sort(m_gallery.begin(), m_gallery.end(), [this](const QString& a, const QString& b) { return m_catalog[a] != m_catalog[b] ? m_catalog[a] > m_catalog[b] : a < b; @@ -445,7 +476,7 @@ void AvatarManager::onTransfer(const uint8_t* data, int length) { if (snapshot.file.endsWith(".json")) { m_records[snapshot.ref.hash] = result.record; auto& job = m_jobs[snapshot.id]; job.file = snapshot.original ? result.record.original : result.record.preview; - transfer(job); return; + transfer(job); notify(snapshot.ref.hash); return; } const QString cacheKey = snapshot.ref.hash + (snapshot.original ? ":original" : ""); m_images.insert(cacheKey, new QImage(result.image), qMax(1, int(result.image.sizeInBytes() / 1024))); diff --git a/tools/chatgui/src/avatars/avatarmanager.h b/tools/chatgui/src/avatars/avatarmanager.h index 01d1caa9..018ee369 100644 --- a/tools/chatgui/src/avatars/avatarmanager.h +++ b/tools/chatgui/src/avatars/avatarmanager.h @@ -80,6 +80,7 @@ private: QStringList m_gallery; QMap m_catalog; quint64 m_listId = 0; + quint64 m_galleryEpoch = 0, m_listCursor = 0; unsigned m_listRetries = 0; QThreadPool m_pool; QTimer m_timer;