1 | /* |
2 | SPDX-FileCopyrightText: 2016 Dan Leinir Turthra Jensen <admin@leinir.dk> |
3 | |
4 | SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL |
5 | */ |
6 | |
7 | #include "quickitemsmodel.h" |
8 | #include "core/commentsmodel.h" |
9 | #include "downloadlinkinfo.h" |
10 | #include "itemsmodel.h" |
11 | #include "quickengine.h" |
12 | |
13 | #include <KShell> |
14 | #include <QProcess> |
15 | |
16 | class ItemsModelPrivate |
17 | { |
18 | public: |
19 | ItemsModelPrivate(ItemsModel *qq) |
20 | : q(qq) |
21 | , model(nullptr) |
22 | , engine(nullptr) |
23 | { |
24 | } |
25 | ItemsModel *q; |
26 | KNSCore::ItemsModel *model; |
27 | Engine *engine; |
28 | |
29 | QHash<QString, KNSCore::CommentsModel *> ; |
30 | |
31 | bool initModel() |
32 | { |
33 | if (model) { |
34 | return true; |
35 | } |
36 | if (!engine) { |
37 | return false; |
38 | } |
39 | model = new KNSCore::ItemsModel(engine, q); |
40 | |
41 | q->connect(sender: engine, signal: &Engine::signalProvidersLoaded, context: engine, slot: &Engine::reloadEntries); |
42 | // Entries have been fetched and should be shown: |
43 | q->connect(sender: engine, signal: &Engine::signalEntriesLoaded, context: model, slot: [this](const KNSCore::Entry::List &entries) { |
44 | model->slotEntriesLoaded(entries); |
45 | }); |
46 | q->connect(sender: engine, signal: &Engine::entryEvent, context: model, slot: [this](const KNSCore::Entry &entry, KNSCore::Entry::EntryEvent event) { |
47 | if (event == KNSCore::Entry::DetailsLoadedEvent && engine->filter() != KNSCore::Provider::Installed |
48 | && engine->filter() != KNSCore::Provider::Updates) { |
49 | model->slotEntriesLoaded(entries: KNSCore::Entry::List{entry}); |
50 | } |
51 | }); |
52 | |
53 | // Check if we need intermediate states |
54 | q->connect(sender: engine, signal: &Engine::entryEvent, context: q, slot: [this](const KNSCore::Entry &entry, KNSCore::Entry::EntryEvent event) { |
55 | onEntryEvent(entry, event); |
56 | }); |
57 | q->connect(sender: engine, signal: &Engine::signalResetView, context: model, slot: &KNSCore::ItemsModel::clearEntries); |
58 | |
59 | q->connect(sender: model, signal: &KNSCore::ItemsModel::loadPreview, context: engine, slot: &Engine::loadPreview); |
60 | q->connect(sender: engine, signal: &Engine::entryPreviewLoaded, context: model, slot: &KNSCore::ItemsModel::slotEntryPreviewLoaded); |
61 | |
62 | q->connect(sender: model, signal: &KNSCore::ItemsModel::rowsInserted, context: q, slot: &ItemsModel::rowsInserted); |
63 | q->connect(sender: model, signal: &KNSCore::ItemsModel::rowsRemoved, context: q, slot: &ItemsModel::rowsRemoved); |
64 | q->connect(sender: model, signal: &KNSCore::ItemsModel::dataChanged, context: q, slot: &ItemsModel::dataChanged); |
65 | q->connect(sender: model, signal: &KNSCore::ItemsModel::modelAboutToBeReset, context: q, slot: &ItemsModel::modelAboutToBeReset); |
66 | q->connect(sender: model, signal: &KNSCore::ItemsModel::modelReset, context: q, slot: &ItemsModel::modelReset); |
67 | return true; |
68 | } |
69 | |
70 | void onEntryEvent(const KNSCore::Entry &entry, KNSCore::Entry::EntryEvent event) |
71 | { |
72 | if (event == KNSCore::Entry::StatusChangedEvent) { |
73 | model->slotEntryChanged(entry); |
74 | Q_EMIT q->entryChanged(entry); |
75 | |
76 | // If we update/uninstall an entry we have to update the UI, see BUG: 425135 |
77 | if (engine->filter() == KNSCore::Provider::Updates && entry.status() != KNSCore::Entry::Updateable && entry.status() != KNSCore::Entry::Updating) { |
78 | model->removeEntry(entry); |
79 | } else if (engine->filter() == KNSCore::Provider::Installed && entry.status() == KNSCore::Entry::Deleted) { |
80 | model->removeEntry(entry); |
81 | } |
82 | } |
83 | |
84 | if (event == KNSCore::Entry::DetailsLoadedEvent) { |
85 | model->slotEntryChanged(entry); |
86 | Q_EMIT q->entryChanged(entry); |
87 | } |
88 | } |
89 | }; |
90 | |
91 | ItemsModel::ItemsModel(QObject *parent) |
92 | : QAbstractListModel(parent) |
93 | , d(new ItemsModelPrivate(this)) |
94 | { |
95 | } |
96 | |
97 | ItemsModel::~ItemsModel() = default; |
98 | |
99 | QHash<int, QByteArray> ItemsModel::roleNames() const |
100 | { |
101 | static const QHash<int, QByteArray> roles = QHash<int, QByteArray>{ |
102 | {Qt::DisplayRole, "display" }, |
103 | {NameRole, "name" }, |
104 | {UniqueIdRole, "uniqueId" }, |
105 | {CategoryRole, "category" }, |
106 | {HomepageRole, "homepage" }, |
107 | {AuthorRole, "author" }, |
108 | {LicenseRole, "license" }, |
109 | {ShortSummaryRole, "shortSummary" }, |
110 | {SummaryRole, "summary" }, |
111 | {ChangelogRole, "changelog" }, |
112 | {VersionRole, "version" }, |
113 | {ReleaseDateRole, "releaseDate" }, |
114 | {UpdateVersionRole, "updateVersion" }, |
115 | {UpdateReleaseDateRole, "updateReleaseDate" }, |
116 | {PayloadRole, "payload" }, |
117 | {Qt::DecorationRole, "decoration" }, |
118 | {PreviewsSmallRole, "previewsSmall" }, |
119 | {PreviewsRole, "previews" }, |
120 | {InstalledFilesRole, "installedFiles" }, |
121 | {UnInstalledFilesRole, "uninstalledFiles" }, |
122 | {RatingRole, "rating" }, |
123 | {NumberOfCommentsRole, "numberOfComments" }, |
124 | {DownloadCountRole, "downloadCount" }, |
125 | {NumberFansRole, "numberFans" }, |
126 | {NumberKnowledgebaseEntriesRole, "numberKnowledgebaseEntries" }, |
127 | {KnowledgebaseLinkRole, "knowledgebaseLink" }, |
128 | {DownloadLinksRole, "downloadLinks" }, |
129 | {DonationLinkRole, "donationLink" }, |
130 | {ProviderIdRole, "providerId" }, |
131 | {SourceRole, "source" }, |
132 | {EntryRole, "entry" }, |
133 | }; |
134 | return roles; |
135 | } |
136 | |
137 | int ItemsModel::rowCount(const QModelIndex &parent) const |
138 | { |
139 | if (parent.isValid()) { |
140 | return 0; |
141 | } |
142 | if (d->initModel()) { |
143 | return d->model->rowCount(parent: QModelIndex()); |
144 | } |
145 | return 0; |
146 | } |
147 | |
148 | QVariant ItemsModel::data(const QModelIndex &index, int role) const |
149 | { |
150 | if (index.isValid() && d->initModel()) { |
151 | KNSCore::Entry entry = d->model->data(index: d->model->index(row: index.row()), role: Qt::UserRole).value<KNSCore::Entry>(); |
152 | switch (role) { |
153 | case NameRole: |
154 | case Qt::DisplayRole: |
155 | return entry.name(); |
156 | case EntryRole: |
157 | return QVariant::fromValue(value: entry); |
158 | case UniqueIdRole: |
159 | return entry.uniqueId(); |
160 | case CategoryRole: |
161 | return entry.category(); |
162 | case HomepageRole: |
163 | return entry.homepage(); |
164 | break; |
165 | case AuthorRole: { |
166 | KNSCore::Author author = entry.author(); |
167 | QVariantMap returnAuthor; |
168 | returnAuthor[QStringLiteral("id" )] = author.id(); |
169 | returnAuthor[QStringLiteral("name" )] = author.name(); |
170 | returnAuthor[QStringLiteral("email" )] = author.email(); |
171 | returnAuthor[QStringLiteral("homepage" )] = author.homepage(); |
172 | returnAuthor[QStringLiteral("jabber" )] = author.jabber(); |
173 | returnAuthor[QStringLiteral("avatarUrl" )] = author.avatarUrl(); |
174 | returnAuthor[QStringLiteral("description" )] = author.description(); |
175 | return returnAuthor; |
176 | } break; |
177 | case LicenseRole: |
178 | return entry.license(); |
179 | case ShortSummaryRole: |
180 | return entry.shortSummary(); |
181 | case SummaryRole: |
182 | return entry.summary(); |
183 | case ChangelogRole: |
184 | return entry.changelog(); |
185 | case VersionRole: |
186 | return entry.version(); |
187 | case ReleaseDateRole: |
188 | return entry.releaseDate(); |
189 | case UpdateVersionRole: |
190 | return entry.updateVersion(); |
191 | case UpdateReleaseDateRole: |
192 | return entry.updateReleaseDate(); |
193 | case PayloadRole: |
194 | return entry.payload(); |
195 | case Qt::DecorationRole: |
196 | return entry.previewUrl(type: KNSCore::Entry::PreviewSmall1); |
197 | case PreviewsSmallRole: { |
198 | QStringList previews; |
199 | previews << entry.previewUrl(type: KNSCore::Entry::PreviewSmall1); |
200 | previews << entry.previewUrl(type: KNSCore::Entry::PreviewSmall2); |
201 | previews << entry.previewUrl(type: KNSCore::Entry::PreviewSmall3); |
202 | while (!previews.isEmpty() && previews.last().isEmpty()) { |
203 | previews.takeLast(); |
204 | } |
205 | return previews; |
206 | } |
207 | case PreviewsRole: { |
208 | QStringList previews; |
209 | previews << entry.previewUrl(type: KNSCore::Entry::PreviewBig1); |
210 | previews << entry.previewUrl(type: KNSCore::Entry::PreviewBig2); |
211 | previews << entry.previewUrl(type: KNSCore::Entry::PreviewBig3); |
212 | while (!previews.isEmpty() && previews.last().isEmpty()) { |
213 | previews.takeLast(); |
214 | } |
215 | return previews; |
216 | } |
217 | case InstalledFilesRole: |
218 | return entry.installedFiles(); |
219 | case UnInstalledFilesRole: |
220 | return entry.uninstalledFiles(); |
221 | case RatingRole: |
222 | return entry.rating(); |
223 | case NumberOfCommentsRole: |
224 | return entry.numberOfComments(); |
225 | case DownloadCountRole: |
226 | return entry.downloadCount(); |
227 | case NumberFansRole: |
228 | return entry.numberFans(); |
229 | case NumberKnowledgebaseEntriesRole: |
230 | return entry.numberKnowledgebaseEntries(); |
231 | case KnowledgebaseLinkRole: |
232 | return entry.knowledgebaseLink(); |
233 | case DownloadLinksRole: { |
234 | // This would be good to cache... but it also needs marking as dirty, somehow... |
235 | const QList<KNSCore::Entry::DownloadLinkInformation> dllinks = entry.downloadLinkInformationList(); |
236 | QVariantList list; |
237 | for (const KNSCore::Entry::DownloadLinkInformation &link : dllinks) { |
238 | list.append(t: QVariant::fromValue(value: DownloadLinkInfo(link))); |
239 | } |
240 | if (list.isEmpty() && !entry.payload().isEmpty()) { |
241 | KNSCore::Entry::DownloadLinkInformation data; |
242 | data.descriptionLink = entry.payload(); |
243 | list.append(t: QVariant::fromValue(value: DownloadLinkInfo(data))); |
244 | } |
245 | return QVariant::fromValue(value: list); |
246 | } |
247 | case DonationLinkRole: |
248 | return entry.donationLink(); |
249 | case ProviderIdRole: |
250 | return entry.providerId(); |
251 | case SourceRole: { |
252 | KNSCore::Entry::Source src = entry.source(); |
253 | switch (src) { |
254 | case KNSCore::Entry::Cache: |
255 | return QStringLiteral("Cache" ); |
256 | case KNSCore::Entry::Online: |
257 | return QStringLiteral("Online" ); |
258 | case KNSCore::Entry::Registry: |
259 | return QStringLiteral("Registry" ); |
260 | default: |
261 | return QStringLiteral("Unknown source - shouldn't be possible" ); |
262 | } |
263 | } |
264 | case CommentsModelRole: { |
265 | KNSCore::CommentsModel *{nullptr}; |
266 | if (!d->commentsModels.contains(key: entry.uniqueId())) { |
267 | commentsModel = new KNSCore::CommentsModel(d->engine); |
268 | commentsModel->setEntry(entry); |
269 | d->commentsModels[entry.uniqueId()] = commentsModel; |
270 | } else { |
271 | commentsModel = d->commentsModels[entry.uniqueId()]; |
272 | } |
273 | return QVariant::fromValue(value: commentsModel); |
274 | } |
275 | default: |
276 | return QStringLiteral("Unknown role" ); |
277 | } |
278 | } |
279 | return QVariant(); |
280 | } |
281 | |
282 | bool ItemsModel::canFetchMore(const QModelIndex &parent) const |
283 | { |
284 | return !parent.isValid() && d->engine && d->engine->categoriesMetadata().count() > 0; |
285 | } |
286 | |
287 | void ItemsModel::fetchMore(const QModelIndex &parent) |
288 | { |
289 | if (parent.isValid() || !d->engine) { |
290 | return; |
291 | } |
292 | d->engine->requestMoreData(); |
293 | } |
294 | |
295 | QObject *ItemsModel::engine() const |
296 | { |
297 | return d->engine; |
298 | } |
299 | |
300 | void ItemsModel::setEngine(QObject *newEngine) |
301 | { |
302 | if (d->engine != newEngine) { |
303 | beginResetModel(); |
304 | d->engine = qobject_cast<Engine *>(object: newEngine); |
305 | if (d->model) { |
306 | d->model->deleteLater(); |
307 | d->model = nullptr; |
308 | } |
309 | Q_EMIT engineChanged(); |
310 | endResetModel(); |
311 | } |
312 | } |
313 | |
314 | int ItemsModel::indexOfEntryId(const QString &providerId, const QString &entryId) |
315 | { |
316 | int idx{-1}; |
317 | if (d->engine && d->model) { |
318 | for (int i = 0; i < rowCount(); ++i) { |
319 | KNSCore::Entry testEntry = d->model->data(index: d->model->index(row: i), role: Qt::UserRole).value<KNSCore::Entry>(); |
320 | if (providerId == testEntry.providerId() && entryId == testEntry.uniqueId()) { |
321 | idx = i; |
322 | break; |
323 | } |
324 | } |
325 | } |
326 | return idx; |
327 | } |
328 | |
329 | #include "moc_quickitemsmodel.cpp" |
330 | |