File indexing completed on 2024-05-12 05:12:47

0001 /*
0002     SPDX-FileCopyrightText: 2009 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "notificationmonitor.h"
0008 #include "notificationfiltermodel.h"
0009 #include "notificationmodel.h"
0010 #include "utils.h"
0011 
0012 #include <Akonadi/ControlGui>
0013 
0014 #include <QCheckBox>
0015 #include <QFile>
0016 #include <QFileDialog>
0017 #include <QHeaderView>
0018 #include <QItemSelectionModel>
0019 #include <QJsonArray>
0020 #include <QJsonDocument>
0021 #include <QJsonObject>
0022 #include <QLabel>
0023 #include <QMenu>
0024 #include <QMessageBox>
0025 #include <QPushButton>
0026 #include <QSplitter>
0027 #include <QStandardItem>
0028 #include <QStandardItemModel>
0029 #include <QTreeView>
0030 #include <QVBoxLayout>
0031 
0032 #include <KConfigGroup>
0033 #include <KLocalizedString>
0034 #include <KSharedConfig>
0035 #include <Libkdepim/KCheckComboBox>
0036 
0037 using KPIM::KCheckComboBox;
0038 
0039 #ifndef COMPILE_WITH_UNITY_CMAKE_SUPPORT
0040 Q_DECLARE_METATYPE(Akonadi::ChangeNotification)
0041 #endif
0042 
0043 NotificationMonitor::NotificationMonitor(QWidget *parent)
0044     : QWidget(parent)
0045 {
0046     m_model = new NotificationModel(this);
0047     m_model->setEnabled(false); // since it can be slow, default to off
0048 
0049     m_filterModel = new NotificationFilterModel(this);
0050     m_filterModel->setSourceModel(m_model);
0051 
0052     auto layout = new QVBoxLayout(this);
0053     auto hLayout = new QHBoxLayout;
0054     layout->addLayout(hLayout);
0055 
0056     auto enableCB = new QCheckBox(this);
0057     enableCB->setText(i18n("Enable notification monitor"));
0058     enableCB->setChecked(m_model->isEnabled());
0059     connect(enableCB, &QCheckBox::toggled, m_model, &NotificationModel::setEnabled);
0060     hLayout->addWidget(enableCB);
0061 
0062     hLayout->addWidget(new QLabel(i18n("Types:"), this));
0063     hLayout->addWidget(mTypeFilterCombo = new KCheckComboBox(this));
0064     hLayout->addStretch();
0065     mTypeFilterCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents);
0066     mTypeFilterCombo->setMinimumWidth(fontMetrics().boundingRect(QStringLiteral("Subscription,Items,Collections")).width()
0067                                       + 60); // make it wide enough for most use cases
0068     m_filterModel->setTypeFilter(mTypeFilterCombo);
0069 
0070     m_splitter = new QSplitter(this);
0071     layout->addWidget(m_splitter);
0072 
0073     m_treeView = new QTreeView(this);
0074     m_treeView->setModel(m_filterModel);
0075     m_treeView->expandAll();
0076     m_treeView->setAlternatingRowColors(true);
0077     m_treeView->setContextMenuPolicy(Qt::CustomContextMenu);
0078     m_treeView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
0079     connect(m_treeView, &QTreeView::customContextMenuRequested, this, &NotificationMonitor::contextMenu);
0080     connect(m_treeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &NotificationMonitor::onNotificationSelected);
0081     m_splitter->addWidget(m_treeView);
0082 
0083     m_ntfView = new QTreeView(this);
0084     m_ntfView->setModel(new QStandardItemModel(this));
0085     m_ntfView->setEditTriggers(QAbstractItemView::NoEditTriggers);
0086     m_splitter->addWidget(m_ntfView);
0087 
0088     auto h = new QHBoxLayout;
0089     layout->addLayout(h);
0090 
0091     auto btn = new QPushButton(i18n("Save to File..."));
0092     connect(btn, &QPushButton::clicked, this, &NotificationMonitor::saveToFile);
0093     h->addWidget(btn);
0094     h->addStretch(1);
0095 
0096     onNotificationSelected({});
0097 
0098     KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("NotificationMonitor"));
0099     m_treeView->header()->restoreState(config.readEntry<QByteArray>("tv", QByteArray()));
0100     m_ntfView->header()->restoreState(config.readEntry<QByteArray>("ntfView", QByteArray()));
0101     m_splitter->restoreState(config.readEntry<QByteArray>("splitter", QByteArray()));
0102 
0103     Akonadi::ControlGui::widgetNeedsAkonadi(this);
0104 }
0105 
0106 NotificationMonitor::~NotificationMonitor()
0107 {
0108     KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("NotificationMonitor"));
0109     config.writeEntry("tv", m_treeView->header()->saveState());
0110     config.writeEntry("ntfView", m_ntfView->header()->saveState());
0111     config.writeEntry("splitter", m_splitter->saveState());
0112 }
0113 
0114 void NotificationMonitor::contextMenu(const QPoint & /*pos*/)
0115 {
0116     QMenu menu;
0117     menu.addAction(i18n("Clear View"), m_model, &NotificationModel::clear);
0118     menu.exec(QCursor::pos());
0119 }
0120 
0121 void NotificationMonitor::onNotificationSelected(const QModelIndex &index)
0122 {
0123     const auto state = m_ntfView->header()->saveState();
0124     populateNtfModel(index);
0125     m_ntfView->header()->restoreState(state);
0126     m_ntfView->expandAll();
0127 }
0128 
0129 void NotificationMonitor::populateNtfModel(const QModelIndex &index)
0130 {
0131     auto model = qobject_cast<QStandardItemModel *>(m_ntfView->model());
0132     model->clear();
0133     model->setHorizontalHeaderLabels({i18n("Properties"), i18n("Values")});
0134 
0135     const auto ntf = index.data(NotificationModel::NotificationRole).value<Akonadi::ChangeNotification>();
0136     if (!ntf.isValid()) {
0137         return;
0138     }
0139 
0140     appendRow(model, i18n("Timestamp"), ntf.timestamp().toString(Qt::ISODateWithMs));
0141     appendRow(model, i18n("Type"), index.sibling(index.row(), NotificationModel::TypeColumn).data().toString());
0142     appendRow(model, i18n("Listeners"), index.sibling(index.row(), NotificationModel::ListenersColumn).data().toString());
0143     switch (ntf.type()) {
0144     case Akonadi::ChangeNotification::Items:
0145         populateItemNtfTree(model, Akonadi::Protocol::cmdCast<Akonadi::Protocol::ItemChangeNotification>(ntf.notification()));
0146         break;
0147     case Akonadi::ChangeNotification::Collection:
0148         populateCollectionNtfTree(model, Akonadi::Protocol::cmdCast<Akonadi::Protocol::CollectionChangeNotification>(ntf.notification()));
0149         break;
0150     case Akonadi::ChangeNotification::Tag:
0151         populateTagNtfTree(model, Akonadi::Protocol::cmdCast<Akonadi::Protocol::TagChangeNotification>(ntf.notification()));
0152         break;
0153     case Akonadi::ChangeNotification::Relation:
0154         populateRelationNtfTree(model, Akonadi::Protocol::cmdCast<Akonadi::Protocol::RelationChangeNotification>(ntf.notification()));
0155         break;
0156     case Akonadi::ChangeNotification::Subscription:
0157         populateSubscriptionNtfTree(model, Akonadi::Protocol::cmdCast<Akonadi::Protocol::SubscriptionChangeNotification>(ntf.notification()));
0158         break;
0159     }
0160 }
0161 
0162 void NotificationMonitor::populateItemNtfTree(QStandardItemModel *model, const Akonadi::Protocol::ItemChangeNotification &ntf)
0163 {
0164     QString operation;
0165     switch (ntf.operation()) {
0166     case Akonadi::Protocol::ItemChangeNotification::Add:
0167         operation = i18n("Add");
0168         break;
0169     case Akonadi::Protocol::ItemChangeNotification::Modify:
0170         operation = i18n("Modify");
0171         break;
0172     case Akonadi::Protocol::ItemChangeNotification::Move:
0173         operation = i18n("Move");
0174         break;
0175     case Akonadi::Protocol::ItemChangeNotification::Remove:
0176         operation = i18n("Remove");
0177         break;
0178     case Akonadi::Protocol::ItemChangeNotification::Link:
0179         operation = i18n("Link");
0180         break;
0181     case Akonadi::Protocol::ItemChangeNotification::Unlink:
0182         operation = i18n("Unlink");
0183         break;
0184     case Akonadi::Protocol::ItemChangeNotification::ModifyFlags:
0185         operation = i18n("ModifyFlags");
0186         break;
0187     case Akonadi::Protocol::ItemChangeNotification::ModifyTags:
0188         operation = i18n("ModifyTags");
0189         break;
0190     case Akonadi::Protocol::ItemChangeNotification::ModifyRelations:
0191         operation = i18n("ModifyRelations");
0192         break;
0193     case Akonadi::Protocol::ItemChangeNotification::InvalidOp:
0194         operation = i18n("InvalidOp");
0195         break;
0196     }
0197 
0198     appendRow(model, i18n("Operation"), operation);
0199     appendRow(model, i18n("Resource"), QString::fromUtf8(ntf.resource()));
0200     appendRow(model, i18n("Parent Collection"), QString::number(ntf.parentCollection()));
0201     appendRow(model, i18n("Parent Dest Col"), QString::number(ntf.parentDestCollection()));
0202     appendRow(model, i18n("Destination Resource"), QString::fromUtf8(ntf.destinationResource()));
0203     appendRow(model, i18n("Item Parts"), toString(ntf.itemParts()));
0204     appendRow(model, i18n("Added Flags"), toString(ntf.addedFlags()));
0205     appendRow(model, i18n("Removed Flags"), toString(ntf.removedFlags()));
0206     appendRow(model, i18n("Added Tags"), toString(ntf.addedTags()));
0207     appendRow(model, i18n("Removed Tags"), toString(ntf.removedTags()));
0208     auto relationsItem = new QStandardItem(QStringLiteral("Added Relations"));
0209     const auto addedRelations = ntf.addedRelations();
0210     for (const auto &addedRelation : addedRelations) {
0211         auto item = new QStandardItem(
0212             QStringLiteral("%lld-%lld %s").arg(QString::number(addedRelation.leftId), QString::number(addedRelation.rightId), addedRelation.type));
0213         relationsItem->appendRow(item);
0214     }
0215     model->appendRow(relationsItem);
0216 
0217     relationsItem = new QStandardItem(i18n("Removed Relations"));
0218     const auto removedRelations = ntf.removedRelations();
0219     for (const auto &removedRelation : removedRelations) {
0220         auto item = new QStandardItem(
0221             QStringLiteral("%lld-%lld %s").arg(QString::number(removedRelation.leftId), QString::number(removedRelation.rightId), removedRelation.type));
0222         relationsItem->appendRow(item);
0223     }
0224     model->appendRow(relationsItem);
0225 
0226     appendRow(model, i18n("Must retrieve"), toString(ntf.mustRetrieve()));
0227 
0228     auto itemsItem = new QStandardItem(i18n("Items"));
0229     const auto &items = ntf.items();
0230     for (const auto &item : items) {
0231         auto i = new QStandardItem(QString::number(item.id()));
0232         populateItemTree(i, item);
0233         itemsItem->appendRow(i);
0234     }
0235     model->appendRow(itemsItem);
0236 }
0237 
0238 QStandardItem *NotificationMonitor::populateAncestorTree(QStandardItem *parent, const Akonadi::Protocol::Ancestor &ancestor)
0239 {
0240     appendRow(parent, i18n("id"), QString::number(ancestor.id()));
0241     appendRow(parent, i18n("remoteId"), ancestor.remoteId());
0242     appendRow(parent, i18n("name"), ancestor.name());
0243     populateAttributesTree(parent, ancestor.attributes());
0244     auto ancestorItem = new QStandardItem(i18n("Ancestor"));
0245     parent->appendRow(ancestorItem);
0246     return ancestorItem;
0247 }
0248 
0249 void NotificationMonitor::populateTagTree(QStandardItem *parent, const Akonadi::Protocol::FetchTagsResponse &tag)
0250 {
0251     appendRow(parent, i18n("ID"), QString::number(tag.id()));
0252     appendRow(parent, i18n("Parent ID"), QString::number(tag.parentId()));
0253     appendRow(parent, i18n("GID"), QString::fromUtf8(tag.gid()));
0254     appendRow(parent, i18n("Type"), QString::fromUtf8(tag.type()));
0255     appendRow(parent, i18n("Remote ID"), QString::fromUtf8(tag.remoteId()));
0256     populateAttributesTree(parent, tag.attributes());
0257 }
0258 
0259 void NotificationMonitor::populateAttributesTree(QStandardItem *parent, const Akonadi::Protocol::Attributes &attributes)
0260 {
0261     auto attributesItem = new QStandardItem(i18n("Attributes"));
0262     for (auto it = attributes.cbegin(), end = attributes.cend(); it != end; ++it) {
0263         appendRow(attributesItem, QString::fromUtf8(it.key()), QString::fromUtf8(it.value()));
0264     }
0265     parent->appendRow(attributesItem);
0266 }
0267 
0268 void NotificationMonitor::populateItemTree(QStandardItem *parent, const Akonadi::Protocol::FetchItemsResponse &item)
0269 {
0270     appendRow(parent, i18n("Revision"), QString::number(item.revision()));
0271     appendRow(parent, i18n("ParentID"), QString::number(item.parentId()));
0272     appendRow(parent, i18n("RemoteID"), item.remoteId());
0273     appendRow(parent, i18n("RemoteRev"), item.remoteRevision());
0274     appendRow(parent, i18n("GID"), item.gid());
0275     appendRow(parent, i18n("Size"), QString::number(item.size()));
0276     appendRow(parent, i18n("MimeType"), item.mimeType());
0277     appendRow(parent, i18n("MTime"), item.mTime().toString(Qt::ISODate));
0278     appendRow(parent, i18n("Flags"), toString(item.flags()));
0279     auto tagItem = new QStandardItem(i18n("Tags"));
0280     const auto tags = item.tags();
0281     for (const auto &tag : tags) {
0282         auto item = new QStandardItem(QString::number(tag.id()));
0283         populateTagTree(item, tag);
0284         tagItem->appendRow(item);
0285     }
0286     parent->appendRow(tagItem);
0287 
0288     appendRow(parent, i18n("VRefs"), toString(item.virtualReferences()));
0289     auto relationItem = new QStandardItem(i18n("Relations"));
0290     const auto relations = item.relations();
0291     for (const auto &relation : relations) {
0292         auto item = new QStandardItem(
0293             QStringLiteral("%lld-%lld %s").arg(QString::number(relation.left()), QString::number(relation.right()), QString::fromUtf8(relation.type())));
0294         relationItem->appendRow(item);
0295     }
0296     parent->appendRow(relationItem);
0297 
0298     const auto ancestors = item.ancestors();
0299     auto i = new QStandardItem(i18n("Ancestor"));
0300     parent->appendRow(i);
0301     for (const auto &ancestor : ancestors) {
0302         i = populateAncestorTree(i, ancestor);
0303     }
0304 
0305     auto partsItem = new QStandardItem(i18n("Parts"));
0306     const auto parts = item.parts();
0307     for (const auto &part : parts) {
0308         auto item = new QStandardItem(QString::fromUtf8(part.payloadName()));
0309         QString type;
0310         switch (part.metaData().storageType()) {
0311         case Akonadi::Protocol::PartMetaData::External:
0312             type = i18n("External");
0313             break;
0314         case Akonadi::Protocol::PartMetaData::Internal:
0315             type = i18n("Internal");
0316             break;
0317         case Akonadi::Protocol::PartMetaData::Foreign:
0318             type = i18n("Foreign");
0319             break;
0320         }
0321         appendRow(item, i18n("Size"), QString::number(part.metaData().size()));
0322         appendRow(item, i18n("Storage Type"), type);
0323         appendRow(item, i18n("Version"), QString::number(part.metaData().version()));
0324         appendRow(item, i18n("Data"), QString::fromUtf8(part.data().toHex()));
0325 
0326         partsItem->appendRow(item);
0327     }
0328     parent->appendRow(partsItem);
0329 }
0330 
0331 void NotificationMonitor::populateCollectionTree(QStandardItem *parent, const Akonadi::Protocol::FetchCollectionsResponse &collection)
0332 {
0333     appendRow(parent, i18n("ID"), QString::number(collection.id()));
0334     appendRow(parent, i18n("Parent ID"), QString::number(collection.parentId()));
0335     appendRow(parent, i18n("Name"), collection.name());
0336     appendRow(parent, i18n("Mime Types"), toString(static_cast<QList<QString>>(collection.mimeTypes())));
0337     appendRow(parent, i18n("Remote ID"), collection.remoteId());
0338     appendRow(parent, i18n("Remote Revision"), collection.remoteRevision());
0339     auto statsItem = new QStandardItem(i18n("Statistics"));
0340     const auto stats = collection.statistics();
0341     appendRow(statsItem, i18n("Count"), QString::number(stats.count()));
0342     appendRow(statsItem, i18n("Unseen"), QString::number(stats.unseen()));
0343     appendRow(statsItem, i18n("Size"), QString::number(stats.size()));
0344     parent->appendRow(statsItem);
0345     appendRow(parent, i18n("Search Query"), collection.searchQuery());
0346     appendRow(parent, i18n("Search Collections"), toString(collection.searchCollections()));
0347     auto i = new QStandardItem(i18n("Ancestor"));
0348     parent->appendRow(i);
0349     const auto ancestors = collection.ancestors();
0350     for (const auto &ancestor : ancestors) {
0351         i = populateAncestorTree(i, ancestor);
0352     }
0353     auto cpItem = new QStandardItem(i18n("Cache Policy"));
0354     const auto cp = collection.cachePolicy();
0355     appendRow(cpItem, i18n("Inherit"), toString(cp.inherit()));
0356     appendRow(cpItem, i18n("Check Interval"), QString::number(cp.checkInterval()));
0357     appendRow(cpItem, i18n("Cache Timeout"), QString::number(cp.cacheTimeout()));
0358     appendRow(cpItem, i18n("Sync on Demand"), toString(cp.syncOnDemand()));
0359     appendRow(cpItem, i18n("Local Parts"), toString(static_cast<QList<QString>>(cp.localParts())));
0360     parent->appendRow(cpItem);
0361 
0362     populateAttributesTree(parent, collection.attributes());
0363 
0364     appendRow(parent, i18n("Enabled"), toString(collection.enabled()));
0365     appendRow(parent, i18n("DisplayPref"), toString(collection.displayPref()));
0366     appendRow(parent, i18n("SyncPref"), toString(collection.syncPref()));
0367     appendRow(parent, i18n("IndexPref"), toString(collection.indexPref()));
0368     appendRow(parent, i18n("Virtual"), toString(collection.isVirtual()));
0369 }
0370 
0371 void NotificationMonitor::populateCollectionNtfTree(QStandardItemModel *model, const Akonadi::Protocol::CollectionChangeNotification &ntf)
0372 {
0373     QString operation;
0374     switch (ntf.operation()) {
0375     case Akonadi::Protocol::CollectionChangeNotification::Add:
0376         operation = i18n("Add");
0377         break;
0378     case Akonadi::Protocol::CollectionChangeNotification::Modify:
0379         operation = i18n("Modify");
0380         break;
0381     case Akonadi::Protocol::CollectionChangeNotification::Move:
0382         operation = i18n("Move");
0383         break;
0384     case Akonadi::Protocol::CollectionChangeNotification::Remove:
0385         operation = i18n("Remove");
0386         break;
0387     case Akonadi::Protocol::CollectionChangeNotification::Subscribe:
0388         operation = i18n("Subscribe");
0389         break;
0390     case Akonadi::Protocol::CollectionChangeNotification::Unsubscribe:
0391         operation = i18n("Unsubscribe");
0392         break;
0393     case Akonadi::Protocol::CollectionChangeNotification::InvalidOp:
0394         operation = i18n("InvalidIp");
0395         break;
0396     }
0397     appendRow(model, i18n("Operation"), operation);
0398     appendRow(model, i18n("Resource"), QString::fromUtf8(ntf.resource()));
0399     appendRow(model, i18n("Parent Collection"), QString::number(ntf.parentCollection()));
0400     appendRow(model, i18n("Parent Dest Collection"), QString::number(ntf.parentDestCollection()));
0401     appendRow(model, i18n("Destination Resource"), QString::fromUtf8(ntf.destinationResource()));
0402     appendRow(model, i18n("Changed Parts"), toString(ntf.changedParts()));
0403     auto item = new QStandardItem(i18n("Collection"));
0404     populateCollectionTree(item, ntf.collection());
0405     model->appendRow(item);
0406 }
0407 
0408 void NotificationMonitor::populateRelationNtfTree(QStandardItemModel *model, const Akonadi::Protocol::RelationChangeNotification &ntf)
0409 {
0410     QString operation;
0411     switch (ntf.operation()) {
0412     case Akonadi::Protocol::RelationChangeNotification::Add:
0413         operation = i18n("Add");
0414         break;
0415     case Akonadi::Protocol::RelationChangeNotification::Remove:
0416         operation = i18n("Remove");
0417         break;
0418     case Akonadi::Protocol::RelationChangeNotification::InvalidOp:
0419         operation = i18n("InvalidOp");
0420         break;
0421     }
0422     appendRow(model, i18n("Operation"), operation);
0423     auto item = new QStandardItem(i18n("Relation"));
0424     const auto rel = ntf.relation();
0425     appendRow(item, i18n("Left ID"), QString::number(rel.left()));
0426     appendRow(item, i18n("Left MimeType"), QString::fromUtf8(rel.leftMimeType()));
0427     appendRow(item, i18n("Right ID"), QString::number(rel.right()));
0428     appendRow(item, i18n("Right MimeType"), QString::fromUtf8(rel.rightMimeType()));
0429     appendRow(item, i18n("Remote ID"), QString::fromUtf8(rel.remoteId()));
0430     model->appendRow(item);
0431 }
0432 
0433 void NotificationMonitor::populateTagNtfTree(QStandardItemModel *model, const Akonadi::Protocol::TagChangeNotification &ntf)
0434 {
0435     QString operation;
0436     switch (ntf.operation()) {
0437     case Akonadi::Protocol::TagChangeNotification::Add:
0438         operation = i18n("Add");
0439         break;
0440     case Akonadi::Protocol::TagChangeNotification::Modify:
0441         operation = i18n("Modify");
0442         break;
0443     case Akonadi::Protocol::TagChangeNotification::Remove:
0444         operation = i18n("Remove");
0445         break;
0446     case Akonadi::Protocol::TagChangeNotification::InvalidOp:
0447         operation = i18n("InvalidOp");
0448         break;
0449     }
0450     appendRow(model, i18n("Operation"), operation);
0451     appendRow(model, i18n("Resource"), QString::fromUtf8(ntf.resource()));
0452     auto tagItem = new QStandardItem(i18n("Tag"));
0453     populateTagTree(tagItem, ntf.tag());
0454     model->appendRow(tagItem);
0455 }
0456 
0457 void NotificationMonitor::populateSubscriptionNtfTree(QStandardItemModel *model, const Akonadi::Protocol::SubscriptionChangeNotification &ntf)
0458 {
0459     QString operation;
0460     switch (ntf.operation()) {
0461     case Akonadi::Protocol::SubscriptionChangeNotification::Add:
0462         operation = i18n("Add");
0463         break;
0464     case Akonadi::Protocol::SubscriptionChangeNotification::Modify:
0465         operation = i18n("Modify");
0466         break;
0467     case Akonadi::Protocol::SubscriptionChangeNotification::Remove:
0468         operation = i18n("Remove");
0469         break;
0470     case Akonadi::Protocol::SubscriptionChangeNotification::InvalidOp:
0471         operation = i18n("InvalidOp");
0472         break;
0473     }
0474     appendRow(model, i18n("Operation"), operation);
0475     appendRow(model, i18n("Subscriber"), QString::fromUtf8(ntf.subscriber()));
0476     appendRow(model, i18n("Monitored Collections"), toString(ntf.collections()));
0477     appendRow(model, i18n("Monitored Items"), toString(ntf.items()));
0478     appendRow(model, i18n("Monitored Tags"), toString(ntf.tags()));
0479     QStringList types;
0480     const auto typesSet = ntf.types();
0481     for (const auto &type : typesSet) {
0482         switch (type) {
0483         case Akonadi::Protocol::ModifySubscriptionCommand::ItemChanges:
0484             types.push_back(i18n("Items"));
0485             break;
0486         case Akonadi::Protocol::ModifySubscriptionCommand::CollectionChanges:
0487             types.push_back(i18n("Collections"));
0488             break;
0489         case Akonadi::Protocol::ModifySubscriptionCommand::TagChanges:
0490             types.push_back(i18n("Tags"));
0491             break;
0492         case Akonadi::Protocol::ModifySubscriptionCommand::RelationChanges:
0493             types.push_back(i18n("Relations"));
0494             break;
0495         case Akonadi::Protocol::ModifySubscriptionCommand::SubscriptionChanges:
0496             types.push_back(i18n("Subscriptions"));
0497             break;
0498         case Akonadi::Protocol::ModifySubscriptionCommand::ChangeNotifications:
0499             types.push_back(i18n("Changes"));
0500             break;
0501         case Akonadi::Protocol::ModifySubscriptionCommand::NoType:
0502             types.push_back(i18n("No Type"));
0503             break;
0504         }
0505     }
0506     appendRow(model, i18n("Monitored Types"), types.join(QLatin1StringView(", ")));
0507     appendRow(model, i18n("Monitored Mime Types"), toString(ntf.mimeTypes()));
0508     appendRow(model, i18n("Monitored Resources"), toString(ntf.resources()));
0509     appendRow(model, i18n("Ignored Sessions"), toString(ntf.ignoredSessions()));
0510     appendRow(model, i18n("All Monitored"), toString(ntf.allMonitored()));
0511     appendRow(model, i18n("Exclusive"), toString(ntf.exclusive()));
0512 
0513     auto item = new QStandardItem(i18n("Item Fetch Scope"));
0514     const auto ifs = ntf.itemFetchScope();
0515     appendRow(item, i18n("Requested Parts"), toString(ifs.requestedParts()));
0516     appendRow(item, i18n("Changed Since"), ifs.changedSince().toString(Qt::ISODateWithMs));
0517     QString ancestorDepth;
0518     switch (ifs.ancestorDepth()) {
0519     case Akonadi::Protocol::ItemFetchScope::NoAncestor:
0520         ancestorDepth = i18n("No Ancestor");
0521         break;
0522     case Akonadi::Protocol::ItemFetchScope::ParentAncestor:
0523         ancestorDepth = i18n("Parent  Ancestor");
0524         break;
0525     case Akonadi::Protocol::ItemFetchScope::AllAncestors:
0526         ancestorDepth = i18n("All Ancestors");
0527         break;
0528     }
0529     appendRow(item, i18n("Ancestor Depth"), ancestorDepth);
0530     appendRow(item, i18n("Cache Only"), toString(ifs.cacheOnly()));
0531     appendRow(item, i18n("Check Cached Payload Parts Only"), toString(ifs.checkCachedPayloadPartsOnly()));
0532     appendRow(item, i18n("Full Payload"), toString(ifs.fullPayload()));
0533     appendRow(item, i18n("All Attributes"), toString(ifs.allAttributes()));
0534     appendRow(item, i18n("Fetch Size"), toString(ifs.fetchSize()));
0535     appendRow(item, i18n("Fetch MTime"), toString(ifs.fetchMTime()));
0536     appendRow(item, i18n("Fetch Remote Revision"), toString(ifs.fetchRemoteRevision()));
0537     appendRow(item, i18n("Ignore Errors"), toString(ifs.ignoreErrors()));
0538     appendRow(item, i18n("Fetch Flags"), toString(ifs.fetchFlags()));
0539     appendRow(item, i18n("Fetch RemoteID"), toString(ifs.fetchRemoteId()));
0540     appendRow(item, i18n("Fetch GID"), toString(ifs.fetchGID()));
0541     appendRow(item, i18n("Fetch Tags"), toString(ifs.fetchTags()));
0542     appendRow(item, i18n("Fetch Relations"), toString(ifs.fetchRelations()));
0543     appendRow(item, i18n("Fetch VRefs"), toString(ifs.fetchVirtualReferences()));
0544     model->appendRow(item);
0545 
0546     item = new QStandardItem(QStringLiteral("Collection Fetch Scope"));
0547     const auto cfs = ntf.collectionFetchScope();
0548     QString listFilter;
0549     switch (cfs.listFilter()) {
0550     case Akonadi::Protocol::CollectionFetchScope::NoFilter:
0551         listFilter = i18n("No Filter");
0552         break;
0553     case Akonadi::Protocol::CollectionFetchScope::Display:
0554         listFilter = i18n("Display");
0555         break;
0556     case Akonadi::Protocol::CollectionFetchScope::Enabled:
0557         listFilter = i18n("Enabled");
0558         break;
0559     case Akonadi::Protocol::CollectionFetchScope::Index:
0560         listFilter = i18n("Index");
0561         break;
0562     case Akonadi::Protocol::CollectionFetchScope::Sync:
0563         listFilter = i18n("Sync");
0564         break;
0565     }
0566     appendRow(item, i18n("List Filter"), listFilter);
0567     appendRow(item, i18n("Include Statistics"), toString(cfs.includeStatistics()));
0568     appendRow(item, i18n("Resource"), cfs.resource());
0569     appendRow(item, i18n("Content Mime Types"), cfs.contentMimeTypes().join(QLatin1StringView(", ")));
0570     appendRow(item, i18n("Attributes"), toString(cfs.attributes()));
0571     appendRow(item, i18n("Fetch ID Only"), toString(cfs.fetchIdOnly()));
0572     QString ancestorRetrieval;
0573     switch (cfs.ancestorRetrieval()) {
0574     case Akonadi::Protocol::CollectionFetchScope::All:
0575         ancestorRetrieval = i18n("All");
0576         break;
0577     case Akonadi::Protocol::CollectionFetchScope::Parent:
0578         ancestorRetrieval = i18n("Parent");
0579         break;
0580     case Akonadi::Protocol::CollectionFetchScope::None:
0581         ancestorRetrieval = i18n("None");
0582         break;
0583     }
0584     appendRow(item, i18n("Ancestor Retrieval"), ancestorRetrieval);
0585     appendRow(item, i18n("Ancestor Fetch ID Only"), toString(cfs.ancestorFetchIdOnly()));
0586     appendRow(item, i18n("Ancestor Attributes"), toString(cfs.ancestorAttributes()));
0587     appendRow(item, i18n("Ignore Retrieval Errors"), toString(cfs.ignoreRetrievalErrors()));
0588     model->appendRow(item);
0589 
0590     item = new QStandardItem(i18n("Tag Fetch Scope"));
0591     const Akonadi::Protocol::TagFetchScope tfs = ntf.tagFetchScope();
0592     appendRow(item, i18n("Fetch ID Only"), toString(tfs.fetchIdOnly()));
0593     appendRow(item, i18n("Fetch RemoteID"), toString(tfs.fetchRemoteID()));
0594     appendRow(item, i18n("Fetch All Attributes"), toString(tfs.fetchAllAttributes()));
0595     appendRow(item, i18n("Attributes"), toString(tfs.attributes()));
0596     model->appendRow(item);
0597 }
0598 
0599 void NotificationMonitor::saveToFile()
0600 {
0601     const auto filename = QFileDialog::getSaveFileName(this, QStringLiteral("Save to File..."));
0602     if (filename.isEmpty()) {
0603         return;
0604     }
0605 
0606     QFile file(filename);
0607     if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
0608         QMessageBox::warning(this, QStringLiteral("Error"), i18n("Failed to open file: %1").arg(file.errorString()));
0609         return;
0610     }
0611 
0612     QJsonObject json;
0613     auto ntfModel = qobject_cast<QStandardItemModel *>(m_ntfView->model());
0614     Q_ASSERT(ntfModel);
0615 
0616     QJsonArray rowArray;
0617     // Note that the use of m_model here means we save everything, not just what's visible (in case of filtering).
0618     const int rows = m_model->rowCount();
0619     for (int row = 0; row < rows; ++row) {
0620         QJsonObject rowObject;
0621         // The Ntf model has all the data that m_model has in its columns, apart from the session
0622         rowObject.insert(QStringLiteral("Session"), m_model->index(row, NotificationModel::SessionColumn).data().toString());
0623 
0624         populateNtfModel(m_model->index(row, 0));
0625         for (int r = 0, cnt = ntfModel->rowCount(); r < cnt; ++r) {
0626             const auto idx0 = ntfModel->index(r, 0);
0627             const auto idx1 = ntfModel->index(r, 1);
0628             rowObject.insert(idx0.data().toString(), QJsonValue::fromVariant(idx1.data()));
0629         }
0630 
0631         rowArray.append(rowObject);
0632     }
0633     json.insert(QStringLiteral("notifications"), rowArray);
0634     QJsonDocument saveDoc(json);
0635     file.write(saveDoc.toJson());
0636 }
0637 
0638 #include "moc_notificationmonitor.cpp"