File indexing completed on 2024-04-28 03:51:53

0001 /*
0002     SPDX-FileCopyrightText: 2012-2015 Vishesh Handa <me@vhanda.in>
0003 
0004     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include <QCoreApplication>
0008 #include <QCommandLineParser>
0009 #include <QFile>
0010 
0011 #include <KAboutData>
0012 #include <KLocalizedString>
0013 #include <KFormat>
0014 #include <QProcess>
0015 #include <QTextStream>
0016 #include <QFileInfo>
0017 #include <QLocale>
0018 
0019 #include <QDBusConnection>
0020 #include <QDBusConnectionInterface>
0021 
0022 #include "global.h"
0023 #include "database.h"
0024 #include "transaction.h"
0025 #include "databasesize.h"
0026 #include "config.h"
0027 
0028 #include "indexer.h"
0029 #include "indexerconfig.h"
0030 #include "idutils.h"
0031 #include "fileindexerconfig.h"
0032 #include "monitorcommand.h"
0033 #include "schedulerinterface.h"
0034 #include "maininterface.h"
0035 #include "indexerstate.h"
0036 #include "configcommand.h"
0037 #include "statuscommand.h"
0038 
0039 using namespace Baloo;
0040 
0041 void start()
0042 {
0043     const QString exe = QStringLiteral(KDE_INSTALL_FULL_LIBEXECDIR_KF "/baloo_file");
0044     QProcess::startDetached(exe, QStringList());
0045 }
0046 
0047 int main(int argc, char* argv[])
0048 {
0049     QCoreApplication app(argc, argv);
0050 
0051     KAboutData aboutData(QStringLiteral("baloo"), i18n("balooctl"), QStringLiteral(PROJECT_VERSION));
0052     aboutData.addAuthor(i18n("Vishesh Handa"), QString(), QStringLiteral("vhanda@kde.org"));
0053 
0054     KAboutData::setApplicationData(aboutData);
0055 
0056     QCommandLineParser parser;
0057     parser.addPositionalArgument(QStringLiteral("command"), i18n("The command to execute"));
0058 
0059     parser.addPositionalArgument(QStringLiteral("status"), i18n("Print the status of the indexer"));
0060     parser.addPositionalArgument(QStringLiteral("enable"), i18n("Enable the file indexer"));
0061     parser.addPositionalArgument(QStringLiteral("disable"), i18n("Disable the file indexer"));
0062     parser.addPositionalArgument(QStringLiteral("purge"), i18n("Remove the index database"));
0063     parser.addPositionalArgument(QStringLiteral("suspend"), i18n("Suspend the file indexer"));
0064     parser.addPositionalArgument(QStringLiteral("resume"), i18n("Resume the file indexer"));
0065     parser.addPositionalArgument(QStringLiteral("check"), i18n("Check for any unindexed files and index them"));
0066     parser.addPositionalArgument(QStringLiteral("index"), i18n("Index the specified files"));
0067     parser.addPositionalArgument(QStringLiteral("clear"), i18n("Forget the specified files"));
0068     parser.addPositionalArgument(QStringLiteral("config"), i18n("Modify the Baloo configuration"));
0069     parser.addPositionalArgument(QStringLiteral("monitor"), i18n("Monitor the file indexer"));
0070     parser.addPositionalArgument(QStringLiteral("indexSize"), i18n("Display the disk space used by index"));
0071     parser.addPositionalArgument(QStringLiteral("failed"), i18n("Display files which could not be indexed"));
0072 
0073     QString statusFormatDescription = i18nc("Format to use for status command, %1|%2|%3 are option values, %4 is a CLI command",
0074                                             "Output format <%1|%2|%3>.\nThe default format is \"%1\".\nOnly applies to \"%4\"",
0075                                                 QStringLiteral("multiline"),
0076                                                 QStringLiteral("json"),
0077                                                 QStringLiteral("simple"),
0078                                                 QStringLiteral("balooctl status <file>"));
0079     parser.addOption({{QStringLiteral("f"), QStringLiteral("format")},
0080                      statusFormatDescription, i18n("format"), QStringLiteral("multiline")});
0081 
0082     parser.addVersionOption();
0083     parser.addHelpOption();
0084 
0085     parser.process(app);
0086     if (parser.positionalArguments().isEmpty()) {
0087         parser.showHelp(1);
0088     }
0089 
0090     QTextStream out(stdout);
0091 
0092     QString command = parser.positionalArguments().first();
0093 
0094     org::kde::baloo::main mainInterface(QStringLiteral("org.kde.baloo"),
0095                                                 QStringLiteral("/"),
0096                                                 QDBusConnection::sessionBus());
0097 
0098     org::kde::baloo::scheduler schedulerinterface(QStringLiteral("org.kde.baloo"),
0099                                         QStringLiteral("/scheduler"),
0100                                         QDBusConnection::sessionBus());
0101 
0102     if (command == QLatin1String("config")) {
0103         ConfigCommand command;
0104         return command.exec(parser);
0105     }
0106 
0107     if (command == QLatin1String("status")) {
0108         StatusCommand commandStatus;
0109         return commandStatus.exec(parser);
0110     }
0111 
0112     if (command == QLatin1String("enable") || command == QLatin1String("disable")) {
0113         bool isEnabled = false;
0114         if (command == QLatin1String("enable")) {
0115             isEnabled = true;
0116         }
0117         else if (command == QLatin1String("disable")) {
0118             isEnabled = false;
0119         }
0120 
0121         IndexerConfig cfg;
0122         cfg.setFileIndexingEnabled(isEnabled);
0123 
0124         if (isEnabled) {
0125             bool running = mainInterface.isValid();
0126             if (running) {
0127                 out << "File Indexer already running\n";
0128             } else {
0129                 out << "Enabling and starting the File Indexer\n";
0130                 start();
0131             }
0132         } else {
0133             out << "Disabling and stopping the File Indexer\n";
0134 
0135             mainInterface.quit();
0136         }
0137 
0138         return 0;
0139     }
0140 
0141     if (command == QLatin1String("purge")) {
0142         bool running = mainInterface.isValid();
0143 
0144         if (running) {
0145             mainInterface.quit();
0146             out << "Stopping the File Indexer ...";
0147             for (int i = 5 * 60; i; --i) {
0148                 QCoreApplication::processEvents();
0149                 if (!mainInterface.isValid()) {
0150                     break;
0151                 }
0152                 out << "." << Qt::flush;
0153                 QThread::msleep(200);
0154             }
0155             if (!mainInterface.isValid()) {
0156                 out << " - done\n";
0157             } else {
0158                 out << " - failed to stop!\n";
0159                 return 1;
0160             }
0161         }
0162 
0163         const QString path = fileIndexDbPath() + QStringLiteral("/index");
0164         QFile(path).remove();
0165         out << "Deleted the index database\n";
0166 
0167         if (running) {
0168             start();
0169             out << "Restarting the File Indexer\n";
0170         }
0171 
0172         return 0;
0173     }
0174 
0175     if (command == QLatin1String("suspend")) {
0176         schedulerinterface.suspend();
0177         out << "File Indexer suspended\n";
0178         return 0;
0179     }
0180 
0181     if (command == QLatin1String("resume")) {
0182         schedulerinterface.resume();
0183         out << "File Indexer resumed\n";
0184         return 0;
0185     }
0186 
0187     if (command == QLatin1String("check")) {
0188         schedulerinterface.checkUnindexedFiles();
0189         out << "Started search for unindexed files\n";
0190         return 0;
0191     }
0192 
0193     if (command == QLatin1String("index")) {
0194         if (parser.positionalArguments().size() < 2) {
0195             out << "Please enter a filename to index\n";
0196             return 1;
0197         }
0198 
0199         Database *db = globalDatabaseInstance();
0200         if (!db->open(Database::ReadWriteDatabase)) {
0201             out << "Baloo Index could not be opened\n";
0202             return 1;
0203         }
0204 
0205         Transaction tr(db, Transaction::ReadWrite);
0206 
0207         for (int i = 1; i < parser.positionalArguments().size(); ++i) {
0208             const QString url = QFileInfo(parser.positionalArguments().at(i)).absoluteFilePath();
0209             quint64 id = filePathToId(QFile::encodeName(url));
0210             if (id == 0) {
0211                 out << "Could not stat file: " << url << '\n';
0212                 continue;
0213             }
0214             if (tr.inPhaseOne(id))  {
0215                 out << "Skipping: " << url << " Reason: Already scheduled for indexing\n";
0216                 continue;
0217             }
0218             if (!tr.documentData(id).isEmpty()) {
0219                 out << "Skipping: " << url << " Reason: Already indexed\n";
0220                 continue;
0221             }
0222             Indexer indexer(url, &tr);
0223             out << "Indexing " << url << '\n';
0224             indexer.index();
0225         }
0226         tr.commit();
0227         out << "File(s) indexed\n";
0228 
0229         return 0;
0230     }
0231 
0232     if (command == QLatin1String("clear")) {
0233         if (parser.positionalArguments().size() < 2) {
0234             out << "Please enter a filename to index\n";
0235             return 1;
0236         }
0237 
0238         Database *db = globalDatabaseInstance();
0239         if (!db->open(Database::ReadWriteDatabase)) {
0240             out << "Baloo Index could not be opened\n";
0241             return 1;
0242         }
0243 
0244         Transaction tr(db, Transaction::ReadWrite);
0245 
0246         for (int i = 1; i < parser.positionalArguments().size(); ++i) {
0247             const QString url = QFileInfo(parser.positionalArguments().at(i)).absoluteFilePath();
0248             quint64 id = filePathToId(QFile::encodeName(url));
0249             if (id == 0) {
0250                 id = tr.documentId(QFile::encodeName(url));
0251                 if (id == 0) {
0252                     out << "File not found on filesystem or in DB: " << url << '\n';
0253                     continue;
0254                 } else {
0255                     out << "File has been deleted, clearing from DB: " << url << '\n';
0256                 }
0257             } else {
0258                 out << "Clearing " << url << '\n';
0259             }
0260 
0261             tr.removeDocument(id);
0262         }
0263         tr.commit();
0264         out << "File(s) cleared\n";
0265 
0266         return 0;
0267     }
0268 
0269     if (command == QLatin1String("failed")) {
0270         Database *db = globalDatabaseInstance();
0271         if (!db->open(Database::ReadOnlyDatabase)) {
0272             out << "Baloo Index could not be opened\n";
0273             return 1;
0274         }
0275 
0276         Transaction tr(db, Transaction::ReadOnly);
0277 
0278         const quint64 limit = 128;
0279         const QVector<quint64> failedIds = tr.failedIds(limit);
0280         if (failedIds.isEmpty()) {
0281             out << "All Files were indexed successfully\n";
0282             return 0;
0283         }
0284 
0285         out << "The following files could not be indexed:\n";
0286         for (auto id : failedIds) {
0287             out << tr.documentUrl(id) << '\n';
0288         }
0289         if (failedIds.size() == limit) {
0290             out << "... list truncated\n";
0291         }
0292         return 0;
0293     }
0294 
0295     if (command == QLatin1String("indexSize")) {
0296         Database *db = globalDatabaseInstance();
0297         if (!db->open(Database::ReadOnlyDatabase)) {
0298             out << "Baloo Index could not be opened\n";
0299             return 1;
0300         }
0301 
0302         DatabaseSize size;
0303         {
0304             Transaction tr(db, Transaction::ReadOnly);
0305             size = tr.dbSize();
0306         }
0307         uint totalDataSize = size.expectedSize;
0308 
0309         KFormat format(QLocale::system());
0310         auto prFunc = [&](const QString& name, uint size) {
0311             out.setFieldWidth(20);
0312             out << name;
0313             out.setFieldWidth(0);
0314             out << ":";
0315             out.setFieldWidth(15);
0316             out << format.formatByteSize(size, 2);
0317             out.setFieldWidth(10);
0318             out << QString::number((100.0 * size / totalDataSize), 'f', 3);
0319             out.setFieldWidth(0);
0320             out << " %\n";
0321         };
0322 
0323         out << "File Size: " << format.formatByteSize(size.actualSize, 2) << "\n";
0324         out << "Used:      " << format.formatByteSize(totalDataSize, 2) << "\n\n";
0325         prFunc(QStringLiteral("PostingDB"), size.postingDb);
0326         prFunc(QStringLiteral("PositionDB"), size.positionDb);
0327         prFunc(QStringLiteral("DocTerms"), size.docTerms);
0328         prFunc(QStringLiteral("DocFilenameTerms"), size.docFilenameTerms);
0329         prFunc(QStringLiteral("DocXattrTerms"), size.docXattrTerms);
0330         prFunc(QStringLiteral("IdTree"), size.idTree);
0331         prFunc(QStringLiteral("IdFileName"), size.idFilename);
0332         prFunc(QStringLiteral("DocTime"), size.docTime);
0333         prFunc(QStringLiteral("DocData"), size.docData);
0334         prFunc(QStringLiteral("ContentIndexingDB"), size.contentIndexingIds);
0335         prFunc(QStringLiteral("FailedIdsDB"), size.failedIds);
0336         prFunc(QStringLiteral("MTimeDB"), size.mtimeDb);
0337 
0338         return 0;
0339     }
0340 
0341     if (command == QLatin1String("monitor")) {
0342         MonitorCommand mon;
0343         return mon.exec(parser);
0344     }
0345 
0346     /*
0347      TODO: Make separate executable
0348      if (command == QLatin1String("checkDb")) {
0349         Database *db = globalDatabaseInstance();
0350         if (!db->open(Database::ReadOnlyDatabase)) {
0351             out << "Baloo Index could not be opened\n";
0352             return 1;
0353         }
0354 
0355         Transaction tr(db, Transaction::ReadOnly);
0356         tr.checkPostingDbinTermsDb();
0357         tr.checkTermsDbinPostingDb();
0358         out << "Checking file paths .. "<< '\n';
0359         tr.checkFsTree();
0360         return 0;
0361     }
0362     */
0363 
0364     parser.showHelp(1);
0365     return 0;
0366 }