From 55a6d20e109c1060740b8b4966a1cd8172f6d4f8 Mon Sep 17 00:00:00 2001 From: Amin Bandali <amin.bandali@savoirfairelinux.com> Date: Tue, 27 Jul 2021 12:38:50 -0400 Subject: [PATCH] conversationsview: lazy load smartview using GLib main event loop Change-Id: I60b4461618b0cc4934a5dd69663a87aa61808464 --- src/conversationsview.cpp | 238 ++++++++++++++++++++++++++------------ 1 file changed, 161 insertions(+), 77 deletions(-) diff --git a/src/conversationsview.cpp b/src/conversationsview.cpp index f93e31bc..60cebefa 100644 --- a/src/conversationsview.cpp +++ b/src/conversationsview.cpp @@ -86,6 +86,26 @@ G_DEFINE_TYPE_WITH_PRIVATE(ConversationsView, conversations_view, GTK_TYPE_TREE_ #define CONVERSATIONS_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CONVERSATIONS_VIEW_TYPE, ConversationsViewPrivate)) +enum { + STATE_STARTED, /* start state */ + STATE_LOADING, /* feeding items to store */ + STATE_COMPLETE, /* feeding store to view */ + LAST_STATE +}; + +struct idle_data { + guint state; + guint id; + + GtkListStore *list_store; + GtkWidget *tree_view; + AccountInfoPointer const *accountInfo; + + gint num_items; + gint num_loaded; + GList *items; +}; + namespace { namespace details { class CppImpl @@ -315,6 +335,102 @@ render_time(G_GNUC_UNUSED GtkTreeViewColumn *tree_column, g_object_set(G_OBJECT(cell), "markup", "", NULL); } +static gboolean +load_conversations(gpointer data) +{ + struct idle_data *d = (struct idle_data *) data; + GtkTreeIter iter; + + g_assert(d->state == STATE_STARTED || d->state == STATE_LOADING); + + if (!d->items) { + d->state = STATE_COMPLETE; + return FALSE; + } + + if (!d->num_items) { + /* first run */ + d->num_items = g_list_length(d->items); + d->num_loaded = 0; + d->state = STATE_LOADING; + } + + const lrc::api::conversation::Info *conv = + (lrc::api::conversation::Info *) g_list_nth_data(d->items, d->num_loaded); + g_assert(conv != NULL); + + if (conv->participants.empty()) + return TRUE; + + auto contacts = (*d->accountInfo)->conversationModel->peersForConversation(conv->uid); + if (contacts.empty()) + return TRUE; + auto contactUri = contacts.front(); + try { + auto contactInfo = (*d->accountInfo)->contactModel->getContact(contactUri); + auto lastMessage = conv->interactions.empty() ? "" : + conv->interactions.at(conv->lastMessageUid).body; + std::replace(lastMessage.begin(), lastMessage.end(), '\n', ' '); + auto alias = (*d->accountInfo)->conversationModel->title(conv->uid); + alias.remove('\r'); + gtk_list_store_append(d->list_store, &iter); + gtk_list_store_set(d->list_store, &iter, + 0 /* col # */ , qUtf8Printable(conv->uid) /* celldata */, + 1 /* col # */ , qUtf8Printable(alias) /* celldata */, + 2 /* col # */ , qUtf8Printable(contactInfo.profileInfo.uri) /* celldata */, + 3 /* col # */ , qUtf8Printable(contactInfo.registeredName) /* celldata */, + 4 /* col # */ , qUtf8Printable(contactInfo.profileInfo.avatar) /* celldata */, + 5 /* col # */ , qUtf8Printable(lastMessage) /* celldata */, + -1 /* end */); + } catch (const std::out_of_range&) { + // ContactModel::getContact() exception + } + d->num_loaded++; + + if (d->num_loaded == d->num_items) { + /* loaded everything, can now change state and remove the + * idle callback function. after use, cleanup_load_items + * will be called. */ + d->state = STATE_COMPLETE; + d->num_loaded = 0; + d->num_items = 0; + d->items = NULL; + return FALSE; + } else { + return TRUE; + } +} + +static void +cleanup_load_conversations(gpointer data) +{ + struct idle_data *d = (struct idle_data *) data; + g_assert(d->state == STATE_COMPLETE); + gtk_tree_view_set_model(GTK_TREE_VIEW(d->tree_view), + GTK_TREE_MODEL(d->list_store)); + g_free(d); +} + +static void +lazy_load_conversations(AccountInfoPointer const *accountInfo, + GtkWidget *tree_view, + GtkListStore *list_store, + GList *items) +{ + struct idle_data *data = g_new(struct idle_data, 1); + data->state = STATE_STARTED; + data->num_items = 0; + data->num_loaded = 0; + data->list_store = list_store; + data->tree_view = tree_view; + data->accountInfo = accountInfo; + data->items = items; + data->id = g_idle_add_full(G_PRIORITY_HIGH_IDLE, + load_conversations, + data, + cleanup_load_conversations); +} + void update_conversation(ConversationsView *self, const std::string& uid) { auto priv = CONVERSATIONS_VIEW_GET_PRIVATE(self); @@ -366,70 +482,55 @@ update_conversation(ConversationsView *self, const std::string& uid) { } } -static GtkTreeModel* +static void create_and_fill_model(ConversationsView *self) { auto priv = CONVERSATIONS_VIEW_GET_PRIVATE(self); - auto store = gtk_list_store_new (6 /* # of cols */ , - G_TYPE_STRING, - G_TYPE_STRING, - G_TYPE_STRING, - G_TYPE_STRING, - G_TYPE_STRING, - G_TYPE_STRING, - G_TYPE_UINT); - if(!priv) GTK_TREE_MODEL (store); + auto store = gtk_list_store_new(6 /* # of cols */ , + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_UINT); + if (!priv) return; + GtkTreeIter iter; if (priv->cpp && !priv->cpp->status.isEmpty()) { - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, - 0 /* col # */ , "" /* celldata */, - 1 /* col # */ , qUtf8Printable(priv->cpp->status) /* celldata */, - 2 /* col # */ , "" /* celldata */, - 3 /* col # */ , "" /* celldata */, - 4 /* col # */ , "" /* celldata */, - 5 /* col # */ , "" /* celldata */, - -1 /* end */); + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, + 0 /* col # */ , "" /* celldata */, + 1 /* col # */ , qUtf8Printable(priv->cpp->status) /* celldata */, + 2 /* col # */ , "" /* celldata */, + 3 /* col # */ , "" /* celldata */, + 4 /* col # */ , "" /* celldata */, + 5 /* col # */ , "" /* celldata */, + -1 /* end */); } + GList *list = nullptr; + for (const lrc::api::conversation::Info& c : + (*priv->accountInfo_)->conversationModel->getAllSearchResults()) { + list = g_list_append(list, (gpointer) &c); + } + if (list) { + lazy_load_conversations(priv->accountInfo_, + (GtkWidget *) self, + store, list); + } - auto fillModelLambda = [&](const auto& queue) { - for (const lrc::api::conversation::Info& conversation : queue) { - if (conversation.participants.empty()) { - g_debug("Found conversation with empty list of participants - most likely the result of earlier bug."); - break; - } - - auto contacts = (*priv->accountInfo_)->conversationModel->peersForConversation(conversation.uid); - if (contacts.empty()) continue; - auto contactUri = contacts.front(); - try { - auto contactInfo = (*priv->accountInfo_)->contactModel->getContact(contactUri); - auto lastMessage = conversation.interactions.empty() ? "" : - conversation.interactions.at(conversation.lastMessageUid).body; - std::replace(lastMessage.begin(), lastMessage.end(), '\n', ' '); - gtk_list_store_append (store, &iter); - auto alias = (*priv->accountInfo_)->conversationModel->title(conversation.uid); - alias.remove('\r'); - gtk_list_store_set (store, &iter, - 0 /* col # */ , qUtf8Printable(conversation.uid) /* celldata */, - 1 /* col # */ , qUtf8Printable(alias) /* celldata */, - 2 /* col # */ , qUtf8Printable(contactInfo.profileInfo.uri) /* celldata */, - 3 /* col # */ , qUtf8Printable(contactInfo.registeredName) /* celldata */, - 4 /* col # */ , qUtf8Printable(contactInfo.profileInfo.avatar) /* celldata */, - 5 /* col # */ , qUtf8Printable(lastMessage) /* celldata */, - -1 /* end */); - } catch (const std::out_of_range&) { - // ContactModel::getContact() exception - } - } - }; - - fillModelLambda((*priv->accountInfo_)->conversationModel->getAllSearchResults()); - fillModelLambda((*priv->accountInfo_)->conversationModel->allFilteredConversations().get()); - - return GTK_TREE_MODEL (store); + list = nullptr; + for (const lrc::api::conversation::Info& c : + (*priv->accountInfo_)->conversationModel->allFilteredConversations().get()) { + list = g_list_append(list, (gpointer) &c); + } + if (list) { + lazy_load_conversations(priv->accountInfo_, + (GtkWidget *) self, + store, list); + } } static void @@ -667,10 +768,7 @@ build_conversations_view(ConversationsView *self) auto* priv = CONVERSATIONS_VIEW_GET_PRIVATE(self); gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(self), FALSE); - auto model = create_and_fill_model(self); - gtk_tree_view_set_model(GTK_TREE_VIEW(self), - GTK_TREE_MODEL(model)); - g_object_unref(model); + create_and_fill_model(self); gtk_tree_view_set_enable_search(GTK_TREE_VIEW(self), false); @@ -721,11 +819,7 @@ build_conversations_view(ConversationsView *self) &*(*priv->accountInfo_)->conversationModel, &lrc::api::ConversationModel::modelChanged, [self] () { - auto model = create_and_fill_model(self); - - gtk_tree_view_set_model(GTK_TREE_VIEW(self), - GTK_TREE_MODEL(model)); - g_object_unref(model); + create_and_fill_model(self); }); @@ -733,11 +827,7 @@ build_conversations_view(ConversationsView *self) &*(*priv->accountInfo_)->conversationModel, &lrc::api::ConversationModel::searchResultUpdated, [self] () { - auto model = create_and_fill_model(self); - - gtk_tree_view_set_model(GTK_TREE_VIEW(self), - GTK_TREE_MODEL(model)); - g_object_unref(model); + create_and_fill_model(self); }); priv->searchStatusChangedConnection_ = QObject::connect( &*(*priv->accountInfo_)->conversationModel, @@ -793,11 +883,7 @@ build_conversations_view(ConversationsView *self) &*(*priv->accountInfo_)->conversationModel, &lrc::api::ConversationModel::filterChanged, [self] () { - auto model = create_and_fill_model(self); - - gtk_tree_view_set_model(GTK_TREE_VIEW(self), - GTK_TREE_MODEL(model)); - g_object_unref(model); + create_and_fill_model(self); }); priv->callChangedConnection_ = QObject::connect( @@ -814,9 +900,7 @@ build_conversations_view(ConversationsView *self) gtk_tree_model_get(model, &iter, 0, &conversationUid, -1); // create updated model - auto new_model = create_and_fill_model(self); - gtk_tree_view_set_model(GTK_TREE_VIEW(self), GTK_TREE_MODEL(new_model)); - g_object_unref(new_model); + create_and_fill_model(self); // make sure conversation remains selected conversations_view_select_conversation(self, conversationUid); -- GitLab