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

0001 /*
0002     This file is part of Akonadi.
0003 
0004     SPDX-FileCopyrightText: 2009 Tobias Koenig <tokoe@kde.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include <xapian.h>
0010 
0011 #include "akonadiconsole_debug.h"
0012 #include "searchwidget.h"
0013 
0014 #include <Akonadi/ControlGui>
0015 #include <Akonadi/ItemFetchJob>
0016 #include <Akonadi/ItemFetchScope>
0017 #include <Akonadi/ItemSearchJob>
0018 #include <Akonadi/SearchQuery>
0019 
0020 #include <Core/SearchStore>
0021 #include <Xapian/xapiansearchstore.h>
0022 
0023 #include <KComboBox>
0024 #include <KConfigGroup>
0025 #include <KLocalizedString>
0026 #include <KMessageBox>
0027 #include <KSharedConfig>
0028 #include <QPlainTextEdit>
0029 #include <QTextBrowser>
0030 
0031 #include <QLabel>
0032 #include <QListView>
0033 #include <QPushButton>
0034 #include <QSplitter>
0035 #include <QStandardItemModel>
0036 #include <QTreeView>
0037 #include <QVBoxLayout>
0038 
0039 SearchWidget::SearchWidget(QWidget *parent)
0040     : QWidget(parent)
0041 {
0042     Akonadi::ControlGui::widgetNeedsAkonadi(this);
0043 
0044     auto layout = new QVBoxLayout(this);
0045     layout->setContentsMargins({});
0046 
0047     auto hbox = new QHBoxLayout;
0048 
0049     hbox->addWidget(new QLabel(i18n("Search store:"), this));
0050     mStoreCombo = new KComboBox(this);
0051     mStoreCombo->setObjectName(QLatin1StringView("SearchStoreCombo"));
0052     hbox->addWidget(mStoreCombo);
0053     hbox->addStretch();
0054     auto button = new QPushButton(i18n("Search"), this);
0055     hbox->addWidget(button);
0056     layout->addLayout(hbox);
0057 
0058     mVSplitter = new QSplitter(Qt::Vertical);
0059     mVSplitter->setObjectName(QLatin1StringView("SearchVSplitter"));
0060     auto w = new QWidget;
0061     auto vbox = new QVBoxLayout(w);
0062     vbox->addWidget(new QLabel(i18n("Search query:"), this));
0063     mQueryWidget = new QPlainTextEdit;
0064     vbox->addWidget(mQueryWidget);
0065     mVSplitter->addWidget(w);
0066 
0067     mHSplitter = new QSplitter(Qt::Horizontal);
0068     mHSplitter->setObjectName(QLatin1StringView("SearchHSplitter"));
0069     w = new QWidget;
0070     vbox = new QVBoxLayout(w);
0071     vbox->addWidget(new QLabel(i18n("Results (Documents):"), this));
0072     mDatabaseView = new QListView(this);
0073     mDatabaseView->setEditTriggers(QListView::NoEditTriggers);
0074     mDocumentModel = new QStandardItemModel(this);
0075     mDatabaseView->setModel(mDocumentModel);
0076     vbox->addWidget(mDatabaseView);
0077     mHSplitter->addWidget(w);
0078 
0079     w = new QWidget;
0080     vbox = new QVBoxLayout(w);
0081     vbox->addWidget(new QLabel(i18n("Document:")));
0082     mDocumentView = new QTreeView;
0083     mDocumentView->setEditTriggers(QTreeView::NoEditTriggers);
0084     mTermModel = new QStandardItemModel(this);
0085     mDocumentView->setModel(mTermModel);
0086     vbox->addWidget(mDocumentView);
0087     mHSplitter->addWidget(w);
0088 
0089     w = new QWidget;
0090     vbox = new QVBoxLayout(w);
0091     vbox->addWidget(new QLabel(i18n("Item:")));
0092     mItemView = new QTextBrowser;
0093     vbox->addWidget(mItemView);
0094     mHSplitter->addWidget(w);
0095 
0096     mVSplitter->addWidget(mHSplitter);
0097 
0098     layout->addWidget(mVSplitter);
0099 
0100     const auto stores = Akonadi::Search::SearchStore::searchStores();
0101     qDebug() << " stores " << stores.count();
0102     for (const auto &store : stores) {
0103         mStoreCombo->addItem(store->types().last(), QVariant::fromValue(store));
0104     }
0105 
0106     connect(button, &QPushButton::clicked, this, &SearchWidget::search);
0107     connect(mDatabaseView, &QListView::activated, this, &SearchWidget::fetchItem);
0108     connect(mStoreCombo, &KComboBox::currentIndexChanged, this, &SearchWidget::openStore);
0109 
0110     openStore(0);
0111 
0112     KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("SearchWidget"));
0113     mQueryWidget->setPlainText(config.readEntry("query"));
0114 }
0115 
0116 SearchWidget::~SearchWidget()
0117 {
0118     KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("SearchWidget"));
0119     config.writeEntry("query", mQueryWidget->toPlainText());
0120     config.sync();
0121     closeDataBase();
0122 }
0123 
0124 void SearchWidget::closeDataBase()
0125 {
0126     if (mDatabase) {
0127         mDatabase->close();
0128         delete mDatabase;
0129     }
0130 }
0131 
0132 void SearchWidget::openStore(int idx)
0133 {
0134     auto store = mStoreCombo->itemData(idx, Qt::UserRole).value<QSharedPointer<Akonadi::Search::SearchStore>>();
0135     auto xapianStore = store.objectCast<Akonadi::Search::XapianSearchStore>();
0136     Q_ASSERT(xapianStore);
0137 
0138     closeDataBase();
0139 
0140     try {
0141         qCDebug(AKONADICONSOLE_LOG) << "Opening store" << xapianStore->dbPath();
0142         mDatabase = new Xapian::Database(xapianStore->dbPath().toStdString());
0143     } catch (Xapian::Error &e) {
0144         xapianError(e);
0145         delete mDatabase;
0146         mDatabase = nullptr;
0147     }
0148 }
0149 
0150 void SearchWidget::xapianError(const Xapian::Error &e)
0151 {
0152     qCWarning(AKONADICONSOLE_LOG) << e.get_type() << QString::fromStdString(e.get_description()) << e.get_error_string();
0153     QMessageBox::critical(this, i18n("Xapian error"), QStringLiteral("%1: %2").arg(QString::fromUtf8(e.get_type()), QString::fromStdString(e.get_msg())));
0154 }
0155 
0156 void SearchWidget::search()
0157 {
0158     if (!mDatabase) {
0159         QMessageBox::critical(this, i18n("Error"), i18n("No Xapian database is opened"));
0160         return;
0161     }
0162 
0163     mDocumentModel->clear();
0164     try {
0165         const auto q = mQueryWidget->toPlainText().toStdString();
0166         auto it = mDatabase->postlist_begin(q);
0167         const auto end = mDatabase->postlist_end(q);
0168 
0169         if (it == end) {
0170             QMessageBox::information(this, i18n("Search"), i18n("No element found."));
0171         }
0172         for (; it != end; ++it) {
0173             auto item = new QStandardItem(QString::number(*it));
0174             item->setData(*it, Qt::UserRole);
0175             mDocumentModel->appendRow(item);
0176         }
0177     } catch (Xapian::Error &e) {
0178         xapianError(e);
0179         return;
0180     }
0181 }
0182 
0183 void SearchWidget::fetchItem(const QModelIndex &index)
0184 {
0185     if (!index.isValid()) {
0186         return;
0187     }
0188 
0189     const auto docId = index.data(Qt::UserRole).value<Xapian::docid>();
0190 
0191     try {
0192         const auto doc = mDatabase->get_document(docId);
0193 
0194         mTermModel->clear();
0195         mTermModel->setColumnCount(2);
0196         mTermModel->setHorizontalHeaderLabels({i18n("Term/Value"), i18n("WDF/Slot")});
0197 
0198         auto termsRoot = new QStandardItem(i18n("Terms"));
0199         mTermModel->appendRow(termsRoot);
0200         for (auto it = doc.termlist_begin(), end = doc.termlist_end(); it != end; ++it) {
0201             termsRoot->appendRow({new QStandardItem(QString::fromStdString(*it)), new QStandardItem(QString::number(it.get_wdf()))});
0202         }
0203 
0204         auto valuesRoot = new QStandardItem(i18n("Values"));
0205         mTermModel->appendRow(valuesRoot);
0206         const auto end = doc.values_end(); // Xapian 1.2 has different type for _begin() and _end() iters
0207         for (auto it = doc.values_begin(); it != end; ++it) {
0208             valuesRoot->appendRow({new QStandardItem(QString::fromStdString(*it)), new QStandardItem(QString::number(it.get_valueno()))});
0209         }
0210     } catch (const Xapian::Error &e) {
0211         xapianError(e);
0212         return;
0213     }
0214 
0215     auto fetchJob = new Akonadi::ItemFetchJob(Akonadi::Item(docId));
0216     fetchJob->fetchScope().fetchFullPayload();
0217     connect(fetchJob, &Akonadi::ItemFetchJob::result, this, &SearchWidget::itemFetched);
0218 }
0219 
0220 void SearchWidget::itemFetched(KJob *job)
0221 {
0222     mItemView->clear();
0223 
0224     if (job->error()) {
0225         KMessageBox::error(this, i18n("Error on fetching item"));
0226         return;
0227     }
0228 
0229     auto fetchJob = qobject_cast<Akonadi::ItemFetchJob *>(job);
0230     if (!fetchJob->items().isEmpty()) {
0231         const Akonadi::Item item = fetchJob->items().first();
0232         mItemView->setPlainText(QString::fromUtf8(item.payloadData()));
0233     }
0234 }
0235 
0236 #include "moc_searchwidget.cpp"