From c5e15d26a08da879bffdfdc9e653666583007067 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Blin?=
 <sebastien.blin@savoirfairelinux.com>
Date: Thu, 30 May 2024 11:10:30 -0400
Subject: [PATCH] contactmodel: cache profiles for non contacts profile

If we're a member of a group swarm, we will receive profiles from
non contact (the other members of a conversation).
This patch try to get from cache before calling getContact() to be
able to retrieve profiles from non contacts if stored by the daemon

Change-Id: I864f1d5dd9f65232751e170b930606d23241d283
---
 src/app/utils.cpp              | 14 ++++-----
 src/libclient/contactmodel.cpp | 57 ++++++++++++++++++++--------------
 2 files changed, 41 insertions(+), 30 deletions(-)

diff --git a/src/app/utils.cpp b/src/app/utils.cpp
index fc8324666..81445d3d5 100644
--- a/src/app/utils.cpp
+++ b/src/app/utils.cpp
@@ -420,8 +420,14 @@ Utils::contactPhoto(LRCInstance* instance,
     try {
         auto& accInfo = instance->accountModel().getAccountInfo(
             accountId.isEmpty() ? instance->get_currentAccountId() : accountId);
-        auto contactInfo = accInfo.contactModel->getContact(contactUri);
         auto contactPhoto = accInfo.contactModel->avatar(contactUri);
+        if (!contactPhoto.isEmpty()) {
+            photo = imageFromBase64String(contactPhoto);
+            if (!photo.isNull())
+                return Utils::scaleAndFrame(photo, size);
+        }
+        // If no avatar is found, generate one
+        auto contactInfo = accInfo.contactModel->getContact(contactUri);
         auto bestName = accInfo.contactModel->bestNameForContact(contactUri);
         if (accInfo.profileInfo.type == profile::Type::SIP
             && contactInfo.profileInfo.type == profile::Type::TEMPORARY) {
@@ -429,12 +435,6 @@ Utils::contactPhoto(LRCInstance* instance,
         } else if (contactInfo.profileInfo.type == profile::Type::TEMPORARY
                    && contactInfo.profileInfo.uri.isEmpty()) {
             photo = Utils::fallbackAvatar(QString(), QString());
-        } else if (!contactPhoto.isEmpty()) {
-            photo = imageFromBase64String(contactPhoto);
-            if (photo.isNull()) {
-                auto avatarName = contactInfo.profileInfo.uri == bestName ? QString() : bestName;
-                photo = Utils::fallbackAvatar("jami:" + contactInfo.profileInfo.uri, avatarName);
-            }
         } else {
             auto avatarName = contactInfo.profileInfo.uri == bestName ? QString() : bestName;
             photo = Utils::fallbackAvatar("jami:" + contactInfo.profileInfo.uri, avatarName);
diff --git a/src/libclient/contactmodel.cpp b/src/libclient/contactmodel.cpp
index 05dd5ee65..87ad4f7fb 100644
--- a/src/libclient/contactmodel.cpp
+++ b/src/libclient/contactmodel.cpp
@@ -133,6 +133,8 @@ public:
 
     // Store if a profile is cached for a given URI.
     QSet<QString> cachedProfiles;
+    // For non contacts (such as members of a group)
+    QMap<QString, profile::Info> cachedProfilesInfo;
 
 public Q_SLOTS:
     /**
@@ -557,10 +559,9 @@ ContactModel::bestNameForContact(const QString& contactUri) const
         return owner.accountModel->bestNameForAccount(owner.id);
     QString res = contactUri;
     try {
-        auto contact = getContact(contactUri);
         auto alias = displayName(contactUri).simplified();
         if (alias.isEmpty()) {
-            return bestIdFromContactInfo(contact);
+            return bestIdFromContactInfo(getContact(contactUri));
         }
         return alias;
     } catch (const std::out_of_range&) {
@@ -1184,16 +1185,11 @@ ContactModelPimpl::slotProfileReceived(const QString& accountId,
         return;
     }
 
-    // Make sure this is for a contact and not the linked account,
-    // then just remove the URI from the cache list and notify.
-    std::lock_guard<std::mutex> lk(contactsMtx_);
-    if (contacts.find(peer) != contacts.end()) {
-        // Remove the URI from the cache list and notify.
-        cachedProfiles.remove(peer);
-        // This signal should be listened to in order to update contact display names
-        // and avatars in the client.
-        Q_EMIT linked.profileUpdated(peer);
-    }
+    // Remove the URI from the cache list and notify.
+    cachedProfiles.remove(peer);
+    // This signal should be listened to in order to update contact display names
+    // and avatars in the client.
+    Q_EMIT linked.profileUpdated(peer);
 }
 
 void
@@ -1236,30 +1232,45 @@ ContactModelPimpl::slotUserSearchEnded(const QString& accountId,
 
 template<typename Func>
 QString
-ContactModelPimpl::getCachedProfileProperty(const QString& contactUri, Func extractor)
+ContactModelPimpl::getCachedProfileProperty(const QString& peerUri, Func extractor)
 {
     std::lock_guard<std::mutex> lk(contactsMtx_);
     // For search results it's loaded and not in storage yet.
-    if (searchResult.contains(contactUri)) {
-        auto contact = searchResult.value(contactUri);
+    if (searchResult.contains(peerUri)) {
+        auto contact = searchResult.value(peerUri);
         return extractor(contact.profileInfo);
     }
 
     // Try to find the contact.
-    auto it = contacts.find(contactUri);
-    if (it == contacts.end()) {
-        return {};
-    }
+    auto it = contacts.find(peerUri);
+    auto isContact = it != contacts.end();
+    auto getPInfo = [&]() -> profile::Info& {
+        if (!isContact) {
+            auto itNonContact = cachedProfilesInfo.find(peerUri);
+            if (itNonContact != cachedProfilesInfo.end()) {
+                return *itNonContact;
+            } else {
+                profile::Info pInfo;
+                pInfo.uri = peerUri;
+                pInfo.type = profile::Type::TEMPORARY;
+                cachedProfilesInfo.insert(peerUri, pInfo);
+                return cachedProfilesInfo[peerUri];
+            }
+        } else {
+            return it->profileInfo;
+        }
+    };
+    profile::Info& pInfo = getPInfo();
 
     // If we have a profile that appears to be recently cached, return the extracted property.
-    if (cachedProfiles.contains(contactUri)) {
-        return extractor(it->profileInfo);
+    if (cachedProfiles.contains(peerUri)) {
+        return extractor(pInfo);
     }
 
     // Otherwise, update the profile info and return the extracted property.
-    updateCachedProfile(it->profileInfo);
+    updateCachedProfile(pInfo);
 
-    return extractor(it->profileInfo);
+    return extractor(pInfo);
 }
 
 } // namespace lrc
-- 
GitLab