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"