Unity 8
appdrawermodel.cpp
1 /*
2  * Copyright (C) 2016 Canonical, Ltd.
3  * Copyright (C) 2020 UBports Foundation.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; version 3.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "appdrawermodel.h"
19 #include "ualwrapper.h"
20 #include "xdgwatcher.h"
21 
22 #include <QDebug>
23 #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 }
33 
34 AppDrawerModel::AppDrawerModel(QObject *parent):
35  AppDrawerModelInterface(parent),
36  m_ual(new UalWrapper(this)),
37  m_xdgWatcher(new XdgWatcher(this)),
38  m_refreshing(false)
39 {
40  connect(&m_refreshFutureWatcher, &QFutureWatcher<ItemList>::finished,
41  this, &AppDrawerModel::onRefreshFinished);
42 
43  // keep this a queued connection as it's coming from another thread.
44  connect(m_xdgWatcher, &XdgWatcher::appAdded, this, &AppDrawerModel::appAdded, Qt::QueuedConnection);
45  connect(m_xdgWatcher, &XdgWatcher::appRemoved, this, &AppDrawerModel::appRemoved, Qt::QueuedConnection);
46  connect(m_xdgWatcher, &XdgWatcher::appInfoChanged, this, &AppDrawerModel::appInfoChanged, Qt::QueuedConnection);
47 
48  refresh();
49 }
50 
51 int AppDrawerModel::rowCount(const QModelIndex &parent) const
52 {
53  Q_UNUSED(parent)
54  return m_list.count();
55 }
56 
57 QVariant AppDrawerModel::data(const QModelIndex &index, int role) const
58 {
59  switch (role) {
60  case RoleAppId:
61  return m_list.at(index.row())->appId();
62  case RoleName:
63  return m_list.at(index.row())->name();
64  case RoleIcon:
65  return m_list.at(index.row())->icon();
66  case RoleKeywords:
67  return m_list.at(index.row())->keywords();
68  case RoleUsage:
69  return m_list.at(index.row())->popularity();
70  }
71 
72  return QVariant();
73 }
74 
75 void AppDrawerModel::appAdded(const QString &appId)
76 {
77  if (m_refreshing)
78  // Will be replaced by the refresh result anyway.
79  return;
80 
81  UalWrapper::AppInfo info = UalWrapper::getApplicationInfo(appId);
82  if (!info.valid) {
83  qWarning() << "App added signal received but failed to get app info for app" << appId;
84  return;
85  }
86 
87  beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
88  auto item = makeSharedLauncherItem(appId, info.name, info.icon, /* parent */ nullptr);
89  item->setKeywords(info.keywords);
90  item->setPopularity(info.popularity);
91  m_list.append(std::move(item));
92  endInsertRows();
93 }
94 
95 void AppDrawerModel::appRemoved(const QString &appId)
96 {
97  if (m_refreshing)
98  // Will be replaced by the refresh result anyway.
99  return;
100 
101  int idx = -1;
102  for (int i = 0; i < m_list.count(); i++) {
103  if (m_list.at(i)->appId() == appId) {
104  idx = i;
105  break;
106  }
107  }
108  if (idx < 0) {
109  qWarning() << "App removed signal received but app doesn't seem to be in the drawer model";
110  return;
111  }
112  beginRemoveRows(QModelIndex(), idx, idx);
113  m_list.removeAt(idx);
114  endRemoveRows();
115 }
116 
117 void AppDrawerModel::appInfoChanged(const QString &appId)
118 {
119  if (m_refreshing)
120  // Will be replaced by the refresh result anyway.
121  return;
122 
123  std::shared_ptr<LauncherItem> item;
124  int idx = -1;
125 
126  for(int i = 0; i < m_list.count(); i++) {
127  if (m_list.at(i)->appId() == appId) {
128  item = m_list.at(i);
129  idx = i;
130  break;
131  }
132  }
133 
134  if (!item) {
135  return;
136  }
137 
138  UalWrapper::AppInfo info = m_ual->getApplicationInfo(appId);
139  item->setPopularity(info.popularity);
140  Q_EMIT dataChanged(index(idx), index(idx), {AppDrawerModelInterface::RoleUsage});
141 }
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 }