#include "AvatarCache.h" #include "AppConfig.h" #include "HttpFetch.h" #include "ImClient.h" #include "LocalStore.h" #include "Protocol.h" #include "UiHelpers.h" #include #include #include #include #include #include #include AvatarCache &AvatarCache::instance() { static AvatarCache s; return s; } AvatarCache::AvatarCache(QObject *parent) : QObject(parent) { } QString AvatarCache::cacheDir() const { return LocalStore::userSubdir(ImClient::instance().uid(), QStringLiteral("avatars")); } QString AvatarCache::filePath(qint64 uid, int ver) const { const QString dir = cacheDir(); if (dir.isEmpty()) return {}; return dir + QStringLiteral("/%1_%2.jpg").arg(uid).arg(ver); } int AvatarCache::diskVer(qint64 uid) const { QDir dir(cacheDir()); int best = 0; const QString prefix = QString::number(uid) + QLatin1Char('_'); const QStringList files = dir.entryList(QStringList() << (prefix + QStringLiteral("*.jpg")), QDir::Files); for (const QString &f : files) { const QString stem = QFileInfo(f).completeBaseName(); const int us = stem.lastIndexOf(QLatin1Char('_')); if (us < 0) continue; bool ok = false; const int v = stem.mid(us + 1).toInt(&ok); if (ok && v > best) best = v; } return best; } int AvatarCache::resolveVer(qint64 uid, int ver) { int best = m_ver.value(uid, 0); if (best <= 0) { best = diskVer(uid); if (best > 0) m_ver.insert(uid, best); } if (ver > best) return ver; if (best > 0) return best; return ver; } void AvatarCache::invalidate(qint64 uid) { const QString prefix = QString::number(uid) + QLatin1Char('_'); for (auto it = m_pix.begin(); it != m_pix.end(); ) { if (it.key().startsWith(prefix) || it.key().startsWith(QString::number(uid) + QLatin1Char('/'))) it = m_pix.erase(it); else ++it; } m_ver.remove(uid); } void AvatarCache::remember(qint64 uid, int ver, const QByteArray &bytes) { if (uid <= 0 || ver <= 0 || bytes.isEmpty()) return; const QString path = filePath(uid, ver); QDir().mkpath(QFileInfo(path).absolutePath()); QFile f(path); if (f.open(QIODevice::WriteOnly)) { f.write(bytes); f.flush(); f.close(); } invalidate(uid); m_ver.insert(uid, ver); emit updated(uid); } void AvatarCache::ensure(qint64 uid, int ver) { if (uid <= 0 || ver <= 0 || m_fetching.contains(uid)) return; int have = m_ver.value(uid, 0); if (have <= 0) have = diskVer(uid); if (have >= ver && QFile::exists(filePath(uid, have))) return; if (QFile::exists(filePath(uid, ver))) return; m_fetching.insert(uid); QPointer self(this); auto takeHttp = [self, uid](const QString &path, int gotVer) { if (!self) return; const QString http = AppConfig::instance().resourceUrl(path); if (http.isEmpty()) { self->m_fetching.remove(uid); return; } HttpFetch::instance().get(http, [self, uid, gotVer](bool ok, const QByteArray &bytes, const QString &) { if (!self) return; if (ok && !bytes.isEmpty() && gotVer > 0) self->remember(uid, gotVer, bytes); self->m_fetching.remove(uid); }); }; ImClient::instance().getAvatar(uid, [self, uid, ver, takeHttp](const QJsonObject &reply) { if (!self) return; if (!ImClient::ok(reply)) { self->m_fetching.remove(uid); return; } int got = static_cast(Proto::jsonI64(reply, QStringLiteral("ver"))); if (got <= 0) got = static_cast(Proto::jsonI64(reply, QStringLiteral("avatar_ver"))); if (got <= 0) got = ver; const QString url = reply.value(QStringLiteral("url")).toString(); if (!url.isEmpty()) { takeHttp(url, got); return; } const QByteArray bytes = QByteArray::fromBase64( reply.value(QStringLiteral("data")).toString().toLatin1()); if (!bytes.isEmpty() && got > 0) self->remember(uid, got, bytes); self->m_fetching.remove(uid); }); } void AvatarCache::probe(qint64 uid) { if (uid <= 0 || m_probing.contains(uid) || m_fetching.contains(uid)) return; if (resolveVer(uid, 0) > 0) return; m_probing.insert(uid); QPointer self(this); ImClient::instance().getProfile(uid, [self, uid](const QJsonObject &reply) { if (!self) return; self->m_probing.remove(uid); if (!ImClient::ok(reply)) return; const int ver = static_cast(Proto::jsonI64(reply, QStringLiteral("avatar_ver"))); if (ver <= 0) return; self->m_ver.insert(uid, ver); self->ensure(uid, ver); emit self->updated(uid); }); } void AvatarCache::applyTo(QLabel *label, qint64 uid, int ver, const QString &name, int size) { if (!label) return; label->setAlignment(Qt::AlignCenter); label->setProperty("avatarUid", QVariant::fromValue(uid)); label->setProperty("avatarVer", resolveVer(uid, ver)); label->setProperty("avatarName", name); label->setProperty("avatarSize", size); if (!label->property("avatarBound").toBool()) { label->setProperty("avatarBound", true); connect(this, &AvatarCache::updated, label, [this, label](qint64 id) { if (!label || id != label->property("avatarUid").toLongLong()) return; const qint64 u = label->property("avatarUid").toLongLong(); const int v = resolveVer(u, label->property("avatarVer").toInt()); label->setProperty("avatarVer", v); const QString n = label->property("avatarName").toString(); const int s = label->property("avatarSize").toInt(); label->setPixmap(pixmap(u, v, n, s)); }); } label->setPixmap(pixmap(uid, ver, name, size)); if (uid > 0 && resolveVer(uid, ver) <= 0) probe(uid); } QPixmap AvatarCache::pixmap(qint64 uid, int ver, const QString &name, int size) { ver = resolveVer(uid, ver); if (uid <= 0 || ver <= 0) return UiHelpers::avatarPixmap(name, size); const QString key = QStringLiteral("%1/%2/%3").arg(uid).arg(ver).arg(size); if (m_pix.contains(key)) return m_pix.value(key); const QString path = filePath(uid, ver); QPixmap disk(path); if (disk.isNull()) { QFile f(path); if (f.open(QIODevice::ReadOnly)) disk.loadFromData(f.readAll(), "JPEG"); } if (disk.isNull()) { ensure(uid, ver); return UiHelpers::avatarPixmap(name, size); } const QPixmap rounded = UiHelpers::roundAvatar(disk, size); m_pix.insert(key, rounded); m_ver.insert(uid, ver); return rounded; }