File indexing completed on 2024-12-22 05:01:13
0001 /* 0002 * SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "incompleteindexdialog.h" 0008 #include "kmail_debug.h" 0009 #include "kmkernel.h" 0010 #include "ui_incompleteindexdialog.h" 0011 0012 #include <KDescendantsProxyModel> 0013 #include <KLocalizedString> 0014 #include <QAbstractItemView> 0015 #include <QProgressDialog> 0016 0017 #include <Akonadi/EntityMimeTypeFilterModel> 0018 #include <Akonadi/EntityTreeModel> 0019 0020 #include <PimCommon/PimUtil> 0021 #include <PimCommonAkonadi/MailUtil> 0022 0023 #include <KConfigGroup> 0024 #include <KSharedConfig> 0025 #include <KWindowConfig> 0026 #include <QDBusInterface> 0027 #include <QDBusMetaType> 0028 #include <QDialogButtonBox> 0029 #include <QHBoxLayout> 0030 #include <QTimer> 0031 #include <QWindow> 0032 #include <chrono> 0033 0034 using namespace std::chrono_literals; 0035 Q_DECLARE_METATYPE(Qt::CheckState) 0036 Q_DECLARE_METATYPE(QList<qint64>) 0037 0038 class SearchCollectionProxyModel : public QSortFilterProxyModel 0039 { 0040 public: 0041 explicit SearchCollectionProxyModel(const QList<qint64> &unindexedCollections, QObject *parent = nullptr) 0042 : QSortFilterProxyModel(parent) 0043 { 0044 mFilterCollections.reserve(unindexedCollections.size()); 0045 for (qint64 col : unindexedCollections) { 0046 mFilterCollections.insert(col, true); 0047 } 0048 } 0049 0050 [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override 0051 { 0052 if (role == Qt::CheckStateRole) { 0053 if (index.isValid() && index.column() == 0) { 0054 const qint64 colId = collectionIdForIndex(index); 0055 return mFilterCollections.value(colId) ? Qt::Checked : Qt::Unchecked; 0056 } 0057 } 0058 0059 return QSortFilterProxyModel::data(index, role); 0060 } 0061 0062 bool setData(const QModelIndex &index, const QVariant &data, int role) override 0063 { 0064 if (role == Qt::CheckStateRole) { 0065 if (index.isValid() && index.column() == 0) { 0066 const qint64 colId = collectionIdForIndex(index); 0067 mFilterCollections[colId] = data.value<Qt::CheckState>(); 0068 return true; 0069 } 0070 } 0071 0072 return QSortFilterProxyModel::setData(index, data, role); 0073 } 0074 0075 [[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override 0076 { 0077 if (index.isValid() && index.column() == 0) { 0078 return QSortFilterProxyModel::flags(index) | Qt::ItemIsUserCheckable; 0079 } else { 0080 return QSortFilterProxyModel::flags(index); 0081 } 0082 } 0083 0084 protected: 0085 [[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override 0086 { 0087 const QModelIndex source_idx = sourceModel()->index(source_row, 0, source_parent); 0088 const qint64 colId = sourceModel()->data(source_idx, Akonadi::EntityTreeModel::CollectionIdRole).toLongLong(); 0089 return mFilterCollections.contains(colId); 0090 } 0091 0092 private: 0093 [[nodiscard]] qint64 collectionIdForIndex(const QModelIndex &index) const 0094 { 0095 return data(index, Akonadi::EntityTreeModel::CollectionIdRole).toLongLong(); 0096 } 0097 0098 private: 0099 QHash<qint64, bool> mFilterCollections; 0100 }; 0101 0102 namespace 0103 { 0104 static const char myIncompleteIndexDialogGroupName[] = "IncompleteIndexDialog"; 0105 } 0106 0107 IncompleteIndexDialog::IncompleteIndexDialog(const QList<qint64> &unindexedCollections, QWidget *parent) 0108 : QDialog(parent) 0109 , mUi(new Ui::IncompleteIndexDialog) 0110 { 0111 auto mainLayout = new QHBoxLayout(this); 0112 auto w = new QWidget(this); 0113 mainLayout->addWidget(w); 0114 qDBusRegisterMetaType<QList<qint64>>(); 0115 0116 mUi->setupUi(w); 0117 0118 Akonadi::EntityTreeModel *etm = KMKernel::self()->entityTreeModel(); 0119 auto mimeProxy = new Akonadi::EntityMimeTypeFilterModel(this); 0120 mimeProxy->addMimeTypeInclusionFilter(Akonadi::Collection::mimeType()); 0121 mimeProxy->setSourceModel(etm); 0122 0123 auto flatProxy = new KDescendantsProxyModel(this); 0124 flatProxy->setDisplayAncestorData(true); 0125 flatProxy->setAncestorSeparator(QStringLiteral(" / ")); 0126 flatProxy->setSourceModel(mimeProxy); 0127 0128 auto proxy = new SearchCollectionProxyModel(unindexedCollections, this); 0129 proxy->setSourceModel(flatProxy); 0130 0131 mUi->collectionView->setModel(proxy); 0132 0133 mUi->collectionView->setEditTriggers(QAbstractItemView::NoEditTriggers); 0134 connect(mUi->selectAllBtn, &QPushButton::clicked, this, &IncompleteIndexDialog::selectAll); 0135 connect(mUi->unselectAllBtn, &QPushButton::clicked, this, &IncompleteIndexDialog::unselectAll); 0136 mUi->buttonBox->button(QDialogButtonBox::Ok)->setText(i18n("Reindex")); 0137 mUi->buttonBox->button(QDialogButtonBox::Cancel)->setText(i18n("Search Anyway")); 0138 connect(mUi->buttonBox, &QDialogButtonBox::accepted, this, &IncompleteIndexDialog::waitForIndexer); 0139 connect(mUi->buttonBox, &QDialogButtonBox::rejected, this, &IncompleteIndexDialog::reject); 0140 readConfig(); 0141 } 0142 0143 IncompleteIndexDialog::~IncompleteIndexDialog() 0144 { 0145 writeConfig(); 0146 } 0147 0148 void IncompleteIndexDialog::readConfig() 0149 { 0150 create(); // ensure a window is created 0151 windowHandle()->resize(QSize(500, 400)); 0152 KConfigGroup group(KSharedConfig::openStateConfig(), QLatin1StringView(myIncompleteIndexDialogGroupName)); 0153 KWindowConfig::restoreWindowSize(windowHandle(), group); 0154 resize(windowHandle()->size()); // workaround for QTBUG-40584 0155 } 0156 0157 void IncompleteIndexDialog::writeConfig() 0158 { 0159 KConfigGroup group(KSharedConfig::openStateConfig(), QLatin1StringView(myIncompleteIndexDialogGroupName)); 0160 KWindowConfig::saveWindowSize(windowHandle(), group); 0161 group.sync(); 0162 } 0163 0164 void IncompleteIndexDialog::selectAll() 0165 { 0166 updateAllSelection(true); 0167 } 0168 0169 void IncompleteIndexDialog::unselectAll() 0170 { 0171 updateAllSelection(false); 0172 } 0173 0174 void IncompleteIndexDialog::updateAllSelection(bool select) 0175 { 0176 QAbstractItemModel *model = mUi->collectionView->model(); 0177 for (int i = 0, cnt = model->rowCount(); i < cnt; ++i) { 0178 const QModelIndex idx = model->index(i, 0, QModelIndex()); 0179 model->setData(idx, select ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); 0180 } 0181 } 0182 0183 QList<qlonglong> IncompleteIndexDialog::collectionsToReindex() const 0184 { 0185 QList<qlonglong> res; 0186 0187 QAbstractItemModel *model = mUi->collectionView->model(); 0188 for (int i = 0, cnt = model->rowCount(); i < cnt; ++i) { 0189 const QModelIndex idx = model->index(i, 0, QModelIndex()); 0190 if (model->data(idx, Qt::CheckStateRole).toInt() == Qt::Checked) { 0191 res.push_back(model->data(idx, Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>().id()); 0192 } 0193 } 0194 0195 return res; 0196 } 0197 0198 void IncompleteIndexDialog::waitForIndexer() 0199 { 0200 mIndexer = new QDBusInterface(PimCommon::MailUtil::indexerServiceName(), 0201 QStringLiteral("/"), 0202 QStringLiteral("org.freedesktop.Akonadi.Indexer"), 0203 QDBusConnection::sessionBus(), 0204 this); 0205 0206 if (!mIndexer->isValid()) { 0207 qCWarning(KMAIL_LOG) << "Invalid indexer dbus interface "; 0208 accept(); 0209 return; 0210 } 0211 mIndexingQueue = collectionsToReindex(); 0212 if (mIndexingQueue.isEmpty()) { 0213 accept(); 0214 return; 0215 } 0216 0217 mProgressDialog = new QProgressDialog(this); 0218 mProgressDialog->setWindowTitle(i18nc("@title:window", "Indexing")); 0219 mProgressDialog->setMaximum(mIndexingQueue.size()); 0220 mProgressDialog->setValue(0); 0221 mProgressDialog->setLabelText(i18n("Indexing Collections...")); 0222 connect(mProgressDialog, &QDialog::rejected, this, &IncompleteIndexDialog::slotStopIndexing); 0223 0224 connect(mIndexer, SIGNAL(collectionIndexingFinished(qlonglong)), this, SLOT(slotCurrentlyIndexingCollectionChanged(qlonglong))); 0225 0226 mIndexer->asyncCall(QStringLiteral("reindexCollections"), QVariant::fromValue(mIndexingQueue)); 0227 mProgressDialog->show(); 0228 } 0229 0230 void IncompleteIndexDialog::slotStopIndexing() 0231 { 0232 mProgressDialog->close(); 0233 reject(); 0234 } 0235 0236 void IncompleteIndexDialog::slotCurrentlyIndexingCollectionChanged(qlonglong colId) 0237 { 0238 const int idx = mIndexingQueue.indexOf(colId); 0239 if (idx > -1) { 0240 mIndexingQueue.removeAt(idx); 0241 mProgressDialog->setValue(mProgressDialog->maximum() - mIndexingQueue.size()); 0242 0243 if (mIndexingQueue.isEmpty()) { 0244 QTimer::singleShot(1s, this, &IncompleteIndexDialog::accept); 0245 } 0246 } 0247 } 0248 0249 #include "moc_incompleteindexdialog.cpp"