AvatarCache.cpp 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. #include "AvatarCache.h"
  2. #include "AppConfig.h"
  3. #include "HttpFetch.h"
  4. #include "ImClient.h"
  5. #include "LocalStore.h"
  6. #include "Protocol.h"
  7. #include "UiHelpers.h"
  8. #include <QCoreApplication>
  9. #include <QDir>
  10. #include <QFile>
  11. #include <QFileInfo>
  12. #include <QLabel>
  13. #include <QPointer>
  14. #include <QVariant>
  15. AvatarCache &AvatarCache::instance()
  16. {
  17. static AvatarCache s;
  18. return s;
  19. }
  20. AvatarCache::AvatarCache(QObject *parent)
  21. : QObject(parent)
  22. {
  23. }
  24. QString AvatarCache::cacheDir() const
  25. {
  26. return LocalStore::userSubdir(ImClient::instance().uid(), QStringLiteral("avatars"));
  27. }
  28. QString AvatarCache::filePath(qint64 uid, int ver) const
  29. {
  30. const QString dir = cacheDir();
  31. if (dir.isEmpty())
  32. return {};
  33. return dir + QStringLiteral("/%1_%2.jpg").arg(uid).arg(ver);
  34. }
  35. int AvatarCache::diskVer(qint64 uid) const
  36. {
  37. QDir dir(cacheDir());
  38. int best = 0;
  39. const QString prefix = QString::number(uid) + QLatin1Char('_');
  40. const QStringList files = dir.entryList(QStringList() << (prefix + QStringLiteral("*.jpg")), QDir::Files);
  41. for (const QString &f : files)
  42. {
  43. const QString stem = QFileInfo(f).completeBaseName();
  44. const int us = stem.lastIndexOf(QLatin1Char('_'));
  45. if (us < 0)
  46. continue;
  47. bool ok = false;
  48. const int v = stem.mid(us + 1).toInt(&ok);
  49. if (ok && v > best)
  50. best = v;
  51. }
  52. return best;
  53. }
  54. int AvatarCache::resolveVer(qint64 uid, int ver)
  55. {
  56. int best = m_ver.value(uid, 0);
  57. if (best <= 0)
  58. {
  59. best = diskVer(uid);
  60. if (best > 0)
  61. m_ver.insert(uid, best);
  62. }
  63. if (ver > best)
  64. return ver;
  65. if (best > 0)
  66. return best;
  67. return ver;
  68. }
  69. void AvatarCache::invalidate(qint64 uid)
  70. {
  71. const QString prefix = QString::number(uid) + QLatin1Char('_');
  72. for (auto it = m_pix.begin(); it != m_pix.end(); )
  73. {
  74. if (it.key().startsWith(prefix) || it.key().startsWith(QString::number(uid) + QLatin1Char('/')))
  75. it = m_pix.erase(it);
  76. else
  77. ++it;
  78. }
  79. m_ver.remove(uid);
  80. }
  81. void AvatarCache::remember(qint64 uid, int ver, const QByteArray &bytes)
  82. {
  83. if (uid <= 0 || ver <= 0 || bytes.isEmpty())
  84. return;
  85. const QString path = filePath(uid, ver);
  86. QDir().mkpath(QFileInfo(path).absolutePath());
  87. QFile f(path);
  88. if (f.open(QIODevice::WriteOnly))
  89. {
  90. f.write(bytes);
  91. f.flush();
  92. f.close();
  93. }
  94. invalidate(uid);
  95. m_ver.insert(uid, ver);
  96. emit updated(uid);
  97. }
  98. void AvatarCache::ensure(qint64 uid, int ver)
  99. {
  100. if (uid <= 0 || ver <= 0 || m_fetching.contains(uid))
  101. return;
  102. int have = m_ver.value(uid, 0);
  103. if (have <= 0)
  104. have = diskVer(uid);
  105. if (have >= ver && QFile::exists(filePath(uid, have)))
  106. return;
  107. if (QFile::exists(filePath(uid, ver)))
  108. return;
  109. m_fetching.insert(uid);
  110. QPointer<AvatarCache> self(this);
  111. auto takeHttp = [self, uid](const QString &path, int gotVer) {
  112. if (!self)
  113. return;
  114. const QString http = AppConfig::instance().resourceUrl(path);
  115. if (http.isEmpty())
  116. {
  117. self->m_fetching.remove(uid);
  118. return;
  119. }
  120. HttpFetch::instance().get(http, [self, uid, gotVer](bool ok, const QByteArray &bytes, const QString &) {
  121. if (!self)
  122. return;
  123. if (ok && !bytes.isEmpty() && gotVer > 0)
  124. self->remember(uid, gotVer, bytes);
  125. self->m_fetching.remove(uid);
  126. });
  127. };
  128. ImClient::instance().getAvatar(uid, [self, uid, ver, takeHttp](const QJsonObject &reply) {
  129. if (!self)
  130. return;
  131. if (!ImClient::ok(reply))
  132. {
  133. self->m_fetching.remove(uid);
  134. return;
  135. }
  136. int got = static_cast<int>(Proto::jsonI64(reply, QStringLiteral("ver")));
  137. if (got <= 0)
  138. got = static_cast<int>(Proto::jsonI64(reply, QStringLiteral("avatar_ver")));
  139. if (got <= 0)
  140. got = ver;
  141. const QString url = reply.value(QStringLiteral("url")).toString();
  142. if (!url.isEmpty())
  143. {
  144. takeHttp(url, got);
  145. return;
  146. }
  147. const QByteArray bytes = QByteArray::fromBase64(
  148. reply.value(QStringLiteral("data")).toString().toLatin1());
  149. if (!bytes.isEmpty() && got > 0)
  150. self->remember(uid, got, bytes);
  151. self->m_fetching.remove(uid);
  152. });
  153. }
  154. void AvatarCache::probe(qint64 uid)
  155. {
  156. if (uid <= 0 || m_probing.contains(uid) || m_fetching.contains(uid))
  157. return;
  158. if (resolveVer(uid, 0) > 0)
  159. return;
  160. m_probing.insert(uid);
  161. QPointer<AvatarCache> self(this);
  162. ImClient::instance().getProfile(uid, [self, uid](const QJsonObject &reply) {
  163. if (!self)
  164. return;
  165. self->m_probing.remove(uid);
  166. if (!ImClient::ok(reply))
  167. return;
  168. const int ver = static_cast<int>(Proto::jsonI64(reply, QStringLiteral("avatar_ver")));
  169. if (ver <= 0)
  170. return;
  171. self->m_ver.insert(uid, ver);
  172. self->ensure(uid, ver);
  173. emit self->updated(uid);
  174. });
  175. }
  176. void AvatarCache::applyTo(QLabel *label, qint64 uid, int ver, const QString &name, int size)
  177. {
  178. if (!label)
  179. return;
  180. label->setAlignment(Qt::AlignCenter);
  181. label->setProperty("avatarUid", QVariant::fromValue(uid));
  182. label->setProperty("avatarVer", resolveVer(uid, ver));
  183. label->setProperty("avatarName", name);
  184. label->setProperty("avatarSize", size);
  185. if (!label->property("avatarBound").toBool())
  186. {
  187. label->setProperty("avatarBound", true);
  188. connect(this, &AvatarCache::updated, label, [this, label](qint64 id) {
  189. if (!label || id != label->property("avatarUid").toLongLong())
  190. return;
  191. const qint64 u = label->property("avatarUid").toLongLong();
  192. const int v = resolveVer(u, label->property("avatarVer").toInt());
  193. label->setProperty("avatarVer", v);
  194. const QString n = label->property("avatarName").toString();
  195. const int s = label->property("avatarSize").toInt();
  196. label->setPixmap(pixmap(u, v, n, s));
  197. });
  198. }
  199. label->setPixmap(pixmap(uid, ver, name, size));
  200. if (uid > 0 && resolveVer(uid, ver) <= 0)
  201. probe(uid);
  202. }
  203. QPixmap AvatarCache::pixmap(qint64 uid, int ver, const QString &name, int size)
  204. {
  205. ver = resolveVer(uid, ver);
  206. if (uid <= 0 || ver <= 0)
  207. return UiHelpers::avatarPixmap(name, size);
  208. const QString key = QStringLiteral("%1/%2/%3").arg(uid).arg(ver).arg(size);
  209. if (m_pix.contains(key))
  210. return m_pix.value(key);
  211. const QString path = filePath(uid, ver);
  212. QPixmap disk(path);
  213. if (disk.isNull())
  214. {
  215. QFile f(path);
  216. if (f.open(QIODevice::ReadOnly))
  217. disk.loadFromData(f.readAll(), "JPEG");
  218. }
  219. if (disk.isNull())
  220. {
  221. ensure(uid, ver);
  222. return UiHelpers::avatarPixmap(name, size);
  223. }
  224. const QPixmap rounded = UiHelpers::roundAvatar(disk, size);
  225. m_pix.insert(key, rounded);
  226. m_ver.insert(uid, ver);
  227. return rounded;
  228. }