File indexing completed on 2024-11-24 04:55:02
0001 /* 0002 * SPDX-FileCopyrightText: 2013 Aleix Pol Gonzalez <aleixpol@blue-systems.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "SnapResource.h" 0008 #include "SnapBackend.h" 0009 #include <KLocalizedString> 0010 #include <QBuffer> 0011 #include <QDebug> 0012 #include <QImageReader> 0013 #include <QProcess> 0014 #include <QStandardItemModel> 0015 0016 #ifdef SNAP_MARKDOWN 0017 #include <Snapd/MarkdownParser> 0018 #endif 0019 0020 #include <appstream/AppStreamUtils.h> 0021 #include <utils.h> 0022 0023 using namespace Qt::StringLiterals; 0024 0025 QDebug operator<<(QDebug debug, const QSnapdPlug &plug) 0026 { 0027 QDebugStateSaver saver(debug); 0028 debug.nospace() << "QSnapdPlug("; 0029 debug.nospace() << "name:" << plug.name() << ','; 0030 debug.nospace() << "snap:" << plug.snap() << ','; 0031 debug.nospace() << "label:" << plug.label() << ','; 0032 debug.nospace() << "interface:" << plug.interface() << ','; 0033 // debug.nospace() << "connectionCount:" << plug.connectionSlotCount(); 0034 debug.nospace() << ')'; 0035 return debug; 0036 } 0037 0038 QDebug operator<<(QDebug debug, const QSnapdSlot &slot) 0039 { 0040 QDebugStateSaver saver(debug); 0041 debug.nospace() << "QSnapdSlot("; 0042 debug.nospace() << "name:" << slot.name() << ','; 0043 debug.nospace() << "label:" << slot.label() << ','; 0044 debug.nospace() << "snap:" << slot.snap() << ','; 0045 debug.nospace() << "interface:" << slot.interface() << ','; 0046 // debug.nospace() << "connectionCount:" << slot.connectionSlotCount(); 0047 debug.nospace() << ')'; 0048 return debug; 0049 } 0050 0051 QDebug operator<<(QDebug debug, const QSnapdPlug *plug) 0052 { 0053 QDebugStateSaver saver(debug); 0054 debug.nospace() << "*" << *plug; 0055 return debug; 0056 } 0057 0058 QDebug operator<<(QDebug debug, const QSnapdSlot *slot) 0059 { 0060 QDebugStateSaver saver(debug); 0061 debug.nospace() << "*" << *slot; 0062 return debug; 0063 } 0064 0065 const QStringList SnapResource::s_topObjects({QStringLiteral("qrc:/qml/PermissionsButton.qml") 0066 #ifdef SNAP_CHANNELS 0067 , 0068 QStringLiteral("qrc:/qml/ChannelsButton.qml") 0069 #endif 0070 }); 0071 0072 SnapResource::SnapResource(QSharedPointer<QSnapdSnap> snap, AbstractResource::State state, SnapBackend *backend) 0073 : AbstractResource(backend) 0074 , m_state(state) 0075 , m_snap(snap) 0076 { 0077 setObjectName(snap->name()); 0078 } 0079 0080 QSnapdClient *SnapResource::client() const 0081 { 0082 auto backend = qobject_cast<SnapBackend *>(parent()); 0083 return backend->client(); 0084 } 0085 0086 QString SnapResource::availableVersion() const 0087 { 0088 return installedVersion(); 0089 } 0090 0091 QStringList SnapResource::categories() 0092 { 0093 return {QStringLiteral("Application")}; 0094 } 0095 0096 QString SnapResource::comment() 0097 { 0098 return m_snap->summary(); 0099 } 0100 0101 quint64 SnapResource::size() 0102 { 0103 // return isInstalled() ? m_snap->installedSize() : m_snap->downloadSize(); 0104 return m_snap->downloadSize(); 0105 } 0106 0107 QVariant SnapResource::icon() const 0108 { 0109 if (m_icon.isNull()) { 0110 m_icon = [this]() -> QVariant { 0111 const auto iconPath = m_snap->icon(); 0112 if (iconPath.isEmpty()) 0113 return QStringLiteral("package-x-generic"); 0114 0115 if (!iconPath.startsWith(QLatin1Char('/'))) 0116 return QUrl(iconPath); 0117 0118 auto req = client()->getIcon(packageName()); 0119 connect(req, &QSnapdGetIconRequest::complete, this, &SnapResource::gotIcon); 0120 req->runAsync(); 0121 return {}; 0122 }(); 0123 } 0124 return m_icon; 0125 } 0126 0127 void SnapResource::gotIcon() 0128 { 0129 auto req = qobject_cast<QSnapdGetIconRequest *>(sender()); 0130 if (req->error()) { 0131 qWarning() << "icon error" << req->errorString(); 0132 return; 0133 } 0134 0135 auto icon = req->icon(); 0136 0137 QBuffer buffer; 0138 buffer.setData(icon->data()); 0139 QImageReader reader(&buffer); 0140 0141 auto theIcon = QVariant::fromValue<QImage>(reader.read()); 0142 if (theIcon != m_icon) { 0143 m_icon = theIcon; 0144 Q_EMIT iconChanged(); 0145 } 0146 } 0147 0148 QString SnapResource::installedVersion() const 0149 { 0150 return m_snap->version(); 0151 } 0152 0153 QJsonArray SnapResource::licenses() 0154 { 0155 return AppStreamUtils::licenses(m_snap->license()); 0156 } 0157 0158 #ifdef SNAP_MARKDOWN 0159 static QString serialize_node(QSnapdMarkdownNode &node); 0160 0161 static QString serialize_children(QSnapdMarkdownNode &node) 0162 { 0163 QString result; 0164 for (int i = 0; i < node.childCount(); i++) { 0165 QScopedPointer<QSnapdMarkdownNode> child(node.child(i)); 0166 result += serialize_node(*child); 0167 } 0168 return result; 0169 } 0170 0171 static QString serialize_node(QSnapdMarkdownNode &node) 0172 { 0173 switch (node.type()) { 0174 case QSnapdMarkdownNode::NodeTypeText: 0175 return node.text().toHtmlEscaped(); 0176 0177 case QSnapdMarkdownNode::NodeTypeParagraph: 0178 return QLatin1String("<p>") + serialize_children(node) + QLatin1String("</p>\n"); 0179 0180 case QSnapdMarkdownNode::NodeTypeUnorderedList: 0181 return QLatin1String("<ul>\n") + serialize_children(node) + QLatin1String("</ul>\n"); 0182 0183 case QSnapdMarkdownNode::NodeTypeListItem: 0184 if (node.childCount() == 0) 0185 return QLatin1String("<li></li>\n"); 0186 if (node.childCount() == 1) { 0187 QScopedPointer<QSnapdMarkdownNode> child(node.child(0)); 0188 if (child->type() == QSnapdMarkdownNode::NodeTypeParagraph) 0189 return QLatin1String("<li>") + serialize_children(*child) + QLatin1String("</li>\n"); 0190 } 0191 return QLatin1String("<li>\n") + serialize_children(node) + QLatin1String("</li>\n"); 0192 0193 case QSnapdMarkdownNode::NodeTypeCodeBlock: 0194 return QLatin1String("<pre><code>") + serialize_children(node) + QLatin1String("</code></pre>\n"); 0195 0196 case QSnapdMarkdownNode::NodeTypeCodeSpan: 0197 return QLatin1String("<code>") + serialize_children(node) + QLatin1String("</code>"); 0198 0199 case QSnapdMarkdownNode::NodeTypeEmphasis: 0200 return QLatin1String("<em>") + serialize_children(node) + QLatin1String("</em>"); 0201 0202 case QSnapdMarkdownNode::NodeTypeStrongEmphasis: 0203 return QLatin1String("<strong>") + serialize_children(node) + QLatin1String("</strong>"); 0204 0205 case QSnapdMarkdownNode::NodeTypeUrl: 0206 return serialize_children(node); 0207 0208 default: 0209 return QString(); 0210 } 0211 } 0212 #endif 0213 0214 QString SnapResource::longDescription() 0215 { 0216 #ifdef SNAP_MARKDOWN 0217 QSnapdMarkdownParser parser(QSnapdMarkdownParser::MarkdownVersion0); 0218 QList<QSnapdMarkdownNode> nodes = parser.parse(m_snap->description()); 0219 QString result; 0220 for (int i = 0; i < nodes.size(); i++) 0221 result += serialize_node(nodes[i]); 0222 return result; 0223 #else 0224 return m_snap->description(); 0225 #endif 0226 } 0227 0228 QString SnapResource::name() const 0229 { 0230 return m_snap->title().isEmpty() ? m_snap->name() : m_snap->title(); 0231 } 0232 0233 QString SnapResource::origin() const 0234 { 0235 return QStringLiteral("Snap"); 0236 } 0237 0238 QString SnapResource::packageName() const 0239 { 0240 return m_snap->name(); 0241 } 0242 0243 QString SnapResource::section() 0244 { 0245 return QStringLiteral("snap"); 0246 } 0247 0248 AbstractResource::State SnapResource::state() 0249 { 0250 return m_state; 0251 } 0252 0253 void SnapResource::setState(AbstractResource::State state) 0254 { 0255 if (m_state != state) { 0256 m_state = state; 0257 Q_EMIT stateChanged(); 0258 } 0259 } 0260 0261 void SnapResource::fetchChangelog() 0262 { 0263 QString log; 0264 Q_EMIT changelogFetched(log); 0265 } 0266 0267 void SnapResource::fetchScreenshots() 0268 { 0269 Screenshots screenshots; 0270 #ifdef SNAP_MEDIA 0271 for (int i = 0, c = m_snap->mediaCount(); i < c; ++i) { 0272 QScopedPointer<QSnapdMedia> media(m_snap->media(i)); 0273 if (media->type() == QLatin1String("screenshot")) 0274 screenshots << QUrl(media->url()); 0275 } 0276 #else 0277 for (int i = 0, c = m_snap->screenshotCount(); i < c; ++i) { 0278 QScopedPointer<QSnapdScreenshot> screenshot(m_snap->screenshot(i)); 0279 screenshots << QUrl(screenshot->url()); 0280 } 0281 #endif 0282 Q_EMIT screenshotsFetched(screenshots); 0283 } 0284 0285 void SnapResource::invokeApplication() const 0286 { 0287 QProcess::startDetached(QStringLiteral("snap"), {QStringLiteral("run"), packageName()}); 0288 } 0289 0290 AbstractResource::Type SnapResource::type() const 0291 { 0292 return m_snap->snapType() != QSnapdEnums::SnapTypeApp ? Application : Technical; 0293 } 0294 0295 void SnapResource::setSnap(const QSharedPointer<QSnapdSnap> &snap) 0296 { 0297 Q_ASSERT(snap->name() == m_snap->name()); 0298 if (m_snap == snap) 0299 return; 0300 0301 const bool newSize = m_snap->installedSize() != snap->installedSize() || m_snap->downloadSize() != snap->downloadSize(); 0302 m_snap = snap; 0303 if (newSize) 0304 Q_EMIT sizeChanged(); 0305 0306 Q_EMIT newSnap(); 0307 } 0308 0309 QDate SnapResource::releaseDate() const 0310 { 0311 return {}; 0312 } 0313 0314 class PlugsModel : public QStandardItemModel 0315 { 0316 Q_OBJECT 0317 public: 0318 enum Roles { 0319 PlugNameRole = Qt::UserRole + 1, 0320 SlotSnapRole, 0321 SlotNameRole, 0322 }; 0323 0324 PlugsModel(SnapResource *res, SnapBackend *backend, QObject *parent) 0325 : QStandardItemModel(parent) 0326 , m_res(res) 0327 , m_backend(backend) 0328 { 0329 auto roles = roleNames(); 0330 roles.insert(Qt::CheckStateRole, "checked"); 0331 setItemRoleNames(roles); 0332 0333 auto req = backend->client()->getInterfaces(); 0334 req->runSync(); 0335 0336 QHash<QString, QVector<QSnapdSlot *>> slotsForInterface; 0337 for (int i = 0; i < req->slotCount(); ++i) { 0338 const auto slot = req->slot(i); 0339 slot->setParent(this); 0340 slotsForInterface[slot->interface()].append(slot); 0341 } 0342 0343 const auto snap = m_res->snap(); 0344 for (int i = 0; i < req->plugCount(); ++i) { 0345 const QScopedPointer<QSnapdPlug> plug(req->plug(i)); 0346 if (plug->snap() == snap->name()) { 0347 if (plug->interface() == QLatin1String("content")) 0348 continue; 0349 0350 const auto theSlots = slotsForInterface.value(plug->interface()); 0351 for (auto slot : theSlots) { 0352 auto item = new QStandardItem; 0353 if (plug->label().isEmpty()) 0354 item->setText(plug->name()); 0355 else 0356 item->setText(i18n("%1 - %2", plug->name(), plug->label())); 0357 0358 // qDebug() << "xxx" << plug->name() << plug->label() << plug->interface() << slot->snap() << "slot:" << slot->name() << 0359 // slot->snap() << slot->interface() << slot->label(); 0360 item->setCheckable(true); 0361 item->setCheckState(plug->connectionCount() > 0 ? Qt::Checked : Qt::Unchecked); 0362 item->setData(plug->name(), PlugNameRole); 0363 item->setData(slot->snap(), SlotSnapRole); 0364 item->setData(slot->name(), SlotNameRole); 0365 appendRow(item); 0366 } 0367 } 0368 } 0369 } 0370 0371 Q_SIGNALS: 0372 void error(InlineMessage *message); 0373 0374 private: 0375 bool setData(const QModelIndex &index, const QVariant &value, int role) override 0376 { 0377 if (role != Qt::CheckStateRole) 0378 return QStandardItemModel::setData(index, value, role); 0379 0380 auto item = itemFromIndex(index); 0381 Q_ASSERT(item); 0382 const QString plugName = item->data(PlugNameRole).toString(); 0383 const QString slotSnap = item->data(SlotSnapRole).toString(); 0384 const QString slotName = item->data(SlotNameRole).toString(); 0385 0386 QSnapdRequest *req; 0387 0388 const auto snap = m_res->snap(); 0389 if (item->checkState() == Qt::Checked) { 0390 req = m_backend->client()->disconnectInterface(snap->name(), plugName, slotSnap, slotName); 0391 } else { 0392 req = m_backend->client()->connectInterface(snap->name(), plugName, slotSnap, slotName); 0393 } 0394 req->runSync(); 0395 if (req->error()) { 0396 qWarning() << "snapd error" << req->errorString(); 0397 Q_EMIT error(new InlineMessage(InlineMessage::Error, u"error"_s, req->errorString())); 0398 } 0399 return req->error() == QSnapdRequest::NoError; 0400 } 0401 0402 SnapResource *const m_res; 0403 SnapBackend *const m_backend; 0404 }; 0405 0406 QAbstractItemModel *SnapResource::plugs(QObject *p) 0407 { 0408 if (!isInstalled()) 0409 return nullptr; 0410 0411 return new PlugsModel(this, qobject_cast<SnapBackend *>(parent()), p); 0412 } 0413 0414 QString SnapResource::appstreamId() const 0415 { 0416 const QStringList ids 0417 #if defined(SNAP_COMMON_IDS) 0418 = m_snap->commonIds() 0419 #endif 0420 ; 0421 return ids.isEmpty() ? QLatin1String("io.snapcraft.") + m_snap->name() + QLatin1Char('-') + m_snap->id() : ids.first(); 0422 } 0423 0424 QStringList SnapResource::topObjects() const 0425 { 0426 return s_topObjects; 0427 } 0428 0429 QString SnapResource::channel() const 0430 { 0431 #ifdef SNAP_PUBLISHER 0432 auto req = client()->getSnap(packageName()); 0433 #else 0434 auto req = client()->listOne(packageName()); 0435 #endif 0436 req->runSync(); 0437 return req->error() ? QString() : req->snap()->trackingChannel(); 0438 } 0439 0440 QString SnapResource::author() const 0441 { 0442 #ifdef SNAP_PUBLISHER 0443 QString author = m_snap->publisherDisplayName(); 0444 if (m_snap->publisherValidation() == QSnapdEnums::PublisherValidationVerified) { 0445 author += QStringLiteral(" ✅"); 0446 } 0447 #else 0448 QString author; 0449 #endif 0450 0451 return author; 0452 } 0453 0454 void SnapResource::setChannel(const QString &channelName) 0455 { 0456 #ifdef SNAP_CHANNELS 0457 Q_ASSERT(isInstalled()); 0458 auto request = client()->switchChannel(m_snap->name(), channelName); 0459 0460 const auto currentChannel = channel(); 0461 request->runAsync(); 0462 connect(request, &QSnapdRequest::complete, this, [this, currentChannel]() { 0463 const auto newChannel = channel(); 0464 if (newChannel != currentChannel) { 0465 Q_EMIT channelChanged(newChannel); 0466 } 0467 }); 0468 #endif 0469 } 0470 0471 void SnapResource::refreshSnap() 0472 { 0473 auto request = client()->find(QSnapdClient::FindFlag::MatchName, m_snap->name()); 0474 connect(request, &QSnapdRequest::complete, this, [this, request]() { 0475 if (request->error()) { 0476 qWarning() << "error" << request->error() << ": " << request->errorString(); 0477 return; 0478 } 0479 Q_ASSERT(request->snapCount() == 1); 0480 setSnap(QSharedPointer<QSnapdSnap>(request->snap(0))); 0481 }); 0482 request->runAsync(); 0483 } 0484 0485 #ifdef SNAP_CHANNELS 0486 class Channels : public QObject 0487 { 0488 Q_OBJECT 0489 Q_PROPERTY(QList<QObject *> channels READ channels NOTIFY channelsChanged) 0490 0491 public: 0492 Channels(SnapResource *res, QObject *parent) 0493 : QObject(parent) 0494 , m_res(res) 0495 { 0496 if (res->snap()->channelCount() == 0) 0497 res->refreshSnap(); 0498 else 0499 refreshChannels(); 0500 0501 connect(res, &SnapResource::newSnap, this, &Channels::refreshChannels); 0502 } 0503 0504 void refreshChannels() 0505 { 0506 qDeleteAll(m_channels); 0507 m_channels.clear(); 0508 0509 auto s = m_res->snap(); 0510 for (int i = 0, c = s->channelCount(); i < c; ++i) { 0511 auto channel = s->channel(i); 0512 channel->setParent(this); 0513 m_channels << channel; 0514 } 0515 Q_EMIT channelsChanged(); 0516 } 0517 0518 QList<QObject *> channels() const 0519 { 0520 return m_channels; 0521 } 0522 0523 Q_SIGNALS: 0524 void channelsChanged(); 0525 0526 private: 0527 QList<QObject *> m_channels; 0528 SnapResource *const m_res; 0529 }; 0530 0531 #endif 0532 0533 QObject *SnapResource::channels(QObject *parent) 0534 { 0535 #ifdef SNAP_CHANNELS 0536 return new Channels(this, parent); 0537 #else 0538 return nullptr; 0539 #endif 0540 } 0541 0542 #include "SnapResource.moc"