@@ -1,5 +1,6 @@
11/*
22 * Copyright (C) 2016 Canonical, Ltd.
3+ * Copyright (C) 2020 UBports Foundation.
34 *
45 * This program is free software; you can redistribute it and/or modify
56 * it under the terms of the GNU General Public License as published by
@@ -20,28 +21,31 @@
2021
2122#include <QDebug>
2223#include <QDateTime>
24+#include <QtConcurrentRun>
25+
26+static std::shared_ptr<LauncherItem> makeSharedLauncherItem(
27+ const QString &appId, const QString &name, const QString &icon, QObject * parent)
28+{
29+ return std::shared_ptr<LauncherItem>(
30+ new LauncherItem(appId, name, icon, parent),
31+ [] (LauncherItem *item) { item->deleteLater(); });
32+}
2333
2434AppDrawerModel::AppDrawerModel(QObject *parent):
2535 AppDrawerModelInterface(parent),
2636 m_ual(new UalWrapper(this)),
27− m_xdgWatcher(new XdgWatcher(this))
37+ m_xdgWatcher(new XdgWatcher(this)),
38+ m_refreshing(false)
2839{
29− Q_FOREACH (const QString &appId, UalWrapper::installedApps()) {
30− UalWrapper::AppInfo info = UalWrapper::getApplicationInfo(appId);
31− if (!info.valid) {
32− qWarning() << "Failed to get app info for app" << appId;
33− continue;
34− }
35− LauncherItem* item = new LauncherItem(appId, info.name, info.icon, this);
36− item->setKeywords(info.keywords);
37− item->setPopularity(info.popularity);
38− m_list.append(item);
39− }
40+ connect(&m_refreshFutureWatcher, &QFutureWatcher<ItemList>::finished,
41+ this, &AppDrawerModel::onRefreshFinished);
4042
4143 // keep this a queued connection as it's coming from another thread.
4244 connect(m_xdgWatcher, &XdgWatcher::appAdded, this, &AppDrawerModel::appAdded, Qt::QueuedConnection);
4345 connect(m_xdgWatcher, &XdgWatcher::appRemoved, this, &AppDrawerModel::appRemoved, Qt::QueuedConnection);
4446 connect(m_xdgWatcher, &XdgWatcher::appInfoChanged, this, &AppDrawerModel::appInfoChanged, Qt::QueuedConnection);
47+
48+ refresh();
4549}
4650
4751int AppDrawerModel::rowCount(const QModelIndex &parent) const
@@ -70,6 +74,10 @@ QVariant AppDrawerModel::data(const QModelIndex &index, int role) const
7074
7175void AppDrawerModel::appAdded(const QString &appId)
7276{
77+ if (m_refreshing)
78+ // Will be replaced by the refresh result anyway.
79+ return;
80+
7381 UalWrapper::AppInfo info = UalWrapper::getApplicationInfo(appId);
7482 if (!info.valid) {
7583 qWarning() << "App added signal received but failed to get app info for app" << appId;
@@ -77,15 +85,19 @@ void AppDrawerModel::appAdded(const QString &appId)
7785 }
7886
7987 beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
80− LauncherItem* item = new LauncherItem(appId, info.name, info.icon, this);
88+ auto item = makeSharedLauncherItem(appId, info.name, info.icon, /* parent */ nullptr);
8189 item->setKeywords(info.keywords);
8290 item->setPopularity(info.popularity);
83− m_list.append(item);
91+ m_list.append(std::move(item));
8492 endInsertRows();
8593}
8694
8795void AppDrawerModel::appRemoved(const QString &appId)
8896{
97+ if (m_refreshing)
98+ // Will be replaced by the refresh result anyway.
99+ return;
100+
89101 int idx = -1;
90102 for (int i = 0; i < m_list.count(); i++) {
91103 if (m_list.at(i)->appId() == appId) {
@@ -98,13 +110,17 @@ void AppDrawerModel::appRemoved(const QString &appId)
98110 return;
99111 }
100112 beginRemoveRows(QModelIndex(), idx, idx);
101− m_list.takeAt(idx)->deleteLater();
113+ m_list.removeAt(idx);
102114 endRemoveRows();
103115}
104116
105117void AppDrawerModel::appInfoChanged(const QString &appId)
106118{
107− LauncherItem *item = nullptr;
119+ if (m_refreshing)
120+ // Will be replaced by the refresh result anyway.
121+ return;
122+
123+ std::shared_ptr<LauncherItem> item;
108124 int idx = -1;
109125
110126 for(int i = 0; i < m_list.count(); i++) {
@@ -123,3 +139,54 @@ void AppDrawerModel::appInfoChanged(const QString &appId)
123139 item->setPopularity(info.popularity);
124140 Q_EMIT dataChanged(index(idx), index(idx), {AppDrawerModelInterface::RoleUsage});
125141}
142+
143+bool AppDrawerModel::refreshing() {
144+ return m_refreshing;
145+}
146+
147+void AppDrawerModel::refresh() {
148+ if (m_refreshing)
149+ return;
150+
151+ m_refreshFutureWatcher.setFuture(QtConcurrent::run([](QThread *thread) {
152+ ItemList list;
153+
154+ Q_FOREACH (const QString &appId, UalWrapper::installedApps()) {
155+ UalWrapper::AppInfo info = UalWrapper::getApplicationInfo(appId);
156+ if (!info.valid) {
157+ qWarning() << "Failed to get app info for app" << appId;
158+ continue;
159+ }
160+ // We don't pass parent in because this may run after the model is destroyed.
161+ // (And, in fact, we can't, because the model is in a diferent thread.)
162+ auto item = makeSharedLauncherItem(appId, info.name, info.icon, /* parent */ nullptr);
163+ item->setKeywords(info.keywords);
164+ item->setPopularity(info.popularity);
165+ item->moveToThread(thread);
166+ list.append(std::move(item));
167+ }
168+
169+ return list;
170+ }, this->thread()));
171+
172+ m_refreshing = true;
173+ Q_EMIT refreshingChanged();
174+}
175+
176+void AppDrawerModel::onRefreshFinished() {
177+ if (m_refreshFutureWatcher.isCanceled())
178+ // This is the result of setting canceled future below.
179+ return;
180+
181+ beginResetModel();
182+
183+ m_list = m_refreshFutureWatcher.result();
184+ // Remove the future & its result, so that future modifications won't
185+ // create a copy.
186+ m_refreshFutureWatcher.setFuture(QFuture<ItemList>());
187+
188+ endResetModel();
189+
190+ m_refreshing = false;
191+ Q_EMIT refreshingChanged();
192+}