| 1 | /* |
| 2 | SPDX-FileCopyrightText: 2012-2015 Vishesh Handa <me@vhanda.in> |
| 3 | |
| 4 | SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL |
| 5 | */ |
| 6 | |
| 7 | #include <QCoreApplication> |
| 8 | #include <QCommandLineParser> |
| 9 | #include <QFile> |
| 10 | |
| 11 | #include <KAboutData> |
| 12 | #include <KLocalizedString> |
| 13 | #include <KFormat> |
| 14 | #include <QProcess> |
| 15 | #include <QTextStream> |
| 16 | #include <QFileInfo> |
| 17 | #include <QLocale> |
| 18 | |
| 19 | #include <QDBusConnection> |
| 20 | #include <QDBusConnectionInterface> |
| 21 | |
| 22 | #include "global.h" |
| 23 | #include "database.h" |
| 24 | #include "transaction.h" |
| 25 | #include "databasesize.h" |
| 26 | #include "config.h" |
| 27 | |
| 28 | #include "indexer.h" |
| 29 | #include "indexerconfig.h" |
| 30 | #include "idutils.h" |
| 31 | #include "fileindexerconfig.h" |
| 32 | #include "monitorcommand.h" |
| 33 | #include "schedulerinterface.h" |
| 34 | #include "maininterface.h" |
| 35 | #include "indexerstate.h" |
| 36 | #include "configcommand.h" |
| 37 | #include "statuscommand.h" |
| 38 | |
| 39 | #include "clearcommand.h" |
| 40 | #include "indexcommand.h" |
| 41 | |
| 42 | using namespace Baloo; |
| 43 | |
| 44 | void start() |
| 45 | { |
| 46 | const QString exe = QStringLiteral(KDE_INSTALL_FULL_LIBEXECDIR_KF "/baloo_file" ); |
| 47 | QProcess::startDetached(program: exe, arguments: QStringList()); |
| 48 | } |
| 49 | |
| 50 | int main(int argc, char* argv[]) |
| 51 | { |
| 52 | QCoreApplication app(argc, argv); |
| 53 | |
| 54 | KAboutData aboutData(QStringLiteral("baloo" ), i18n("balooctl" ), QStringLiteral(PROJECT_VERSION)); |
| 55 | aboutData.addAuthor(i18n("Vishesh Handa" ), task: QString(), QStringLiteral("vhanda@kde.org" )); |
| 56 | |
| 57 | KAboutData::setApplicationData(aboutData); |
| 58 | |
| 59 | QCommandLineParser parser; |
| 60 | parser.addPositionalArgument(QStringLiteral("command" ), i18n("The command to execute" )); |
| 61 | |
| 62 | parser.addPositionalArgument(QStringLiteral("status" ), i18n("Print the status of the indexer" )); |
| 63 | parser.addPositionalArgument(QStringLiteral("enable" ), i18n("Enable the file indexer" )); |
| 64 | parser.addPositionalArgument(QStringLiteral("disable" ), i18n("Disable the file indexer" )); |
| 65 | parser.addPositionalArgument(QStringLiteral("purge" ), i18n("Remove the index database" )); |
| 66 | parser.addPositionalArgument(QStringLiteral("suspend" ), i18n("Suspend the file indexer" )); |
| 67 | parser.addPositionalArgument(QStringLiteral("resume" ), i18n("Resume the file indexer" )); |
| 68 | parser.addPositionalArgument(QStringLiteral("check" ), i18n("Check for any unindexed files and index them" )); |
| 69 | parser.addPositionalArgument(QStringLiteral("index" ), i18n("Index the specified files" )); |
| 70 | parser.addPositionalArgument(QStringLiteral("clear" ), i18n("Forget the specified files" )); |
| 71 | parser.addPositionalArgument(QStringLiteral("config" ), i18n("Modify the Baloo configuration" )); |
| 72 | parser.addPositionalArgument(QStringLiteral("monitor" ), i18n("Monitor the file indexer" )); |
| 73 | parser.addPositionalArgument(QStringLiteral("indexSize" ), i18n("Display the disk space used by index" )); |
| 74 | parser.addPositionalArgument(QStringLiteral("failed" ), i18n("Display files which could not be indexed" )); |
| 75 | |
| 76 | QString statusFormatDescription = i18nc("Format to use for status command, %1|%2|%3 are option values, %4 is a CLI command" , |
| 77 | "Output format <%1|%2|%3>.\nThe default format is \"%1\".\nOnly applies to \"%4\"" , |
| 78 | QStringLiteral("multiline" ), |
| 79 | QStringLiteral("json" ), |
| 80 | QStringLiteral("simple" ), |
| 81 | QStringLiteral("balooctl status <file>" )); |
| 82 | |
| 83 | parser.addOption(commandLineOption: {{QStringLiteral("f" ), QStringLiteral("format" )}, statusFormatDescription, i18n("format" ), QStringLiteral("multiline" )}); |
| 84 | |
| 85 | parser.addVersionOption(); |
| 86 | parser.addHelpOption(); |
| 87 | |
| 88 | parser.process(app); |
| 89 | if (parser.positionalArguments().isEmpty()) { |
| 90 | parser.showHelp(exitCode: 1); |
| 91 | } |
| 92 | |
| 93 | QTextStream out(stdout); |
| 94 | |
| 95 | QString command = parser.positionalArguments().first(); |
| 96 | |
| 97 | org::kde::baloo::main mainInterface(QStringLiteral("org.kde.baloo" ), |
| 98 | QStringLiteral("/" ), |
| 99 | QDBusConnection::sessionBus()); |
| 100 | |
| 101 | org::kde::baloo::scheduler schedulerinterface(QStringLiteral("org.kde.baloo" ), |
| 102 | QStringLiteral("/scheduler" ), |
| 103 | QDBusConnection::sessionBus()); |
| 104 | |
| 105 | // Deal with the various balooctl commands: |
| 106 | |
| 107 | if (command == QLatin1String("config" )) { |
| 108 | ConfigCommand command; |
| 109 | return command.exec(parser); |
| 110 | } |
| 111 | |
| 112 | if (command == QLatin1String("status" )) { |
| 113 | StatusCommand commandStatus; |
| 114 | return commandStatus.exec(parser); |
| 115 | } |
| 116 | |
| 117 | if (command == QLatin1String("enable" ) || command == QLatin1String("disable" )) { |
| 118 | bool isEnabled = false; |
| 119 | if (command == QLatin1String("enable" )) { |
| 120 | isEnabled = true; |
| 121 | } |
| 122 | else if (command == QLatin1String("disable" )) { |
| 123 | isEnabled = false; |
| 124 | } |
| 125 | |
| 126 | IndexerConfig cfg; |
| 127 | cfg.setFileIndexingEnabled(isEnabled); |
| 128 | |
| 129 | if (isEnabled) { |
| 130 | bool running = mainInterface.isValid(); |
| 131 | if (running) { |
| 132 | out << "File Indexer already running\n" ; |
| 133 | } else { |
| 134 | out << "Enabling and starting the File Indexer\n" ; |
| 135 | start(); |
| 136 | } |
| 137 | } else { |
| 138 | out << "Disabling and stopping the File Indexer\n" ; |
| 139 | |
| 140 | mainInterface.quit(); |
| 141 | } |
| 142 | |
| 143 | return 0; |
| 144 | } |
| 145 | |
| 146 | if (command == QLatin1String("purge" )) { |
| 147 | bool running = mainInterface.isValid(); |
| 148 | |
| 149 | if (running) { |
| 150 | mainInterface.quit(); |
| 151 | out << "Stopping the File Indexer ..." ; |
| 152 | for (int i = 5 * 60; i; --i) { |
| 153 | QCoreApplication::processEvents(); |
| 154 | if (!mainInterface.isValid()) { |
| 155 | break; |
| 156 | } |
| 157 | out << "." << Qt::flush; |
| 158 | QThread::msleep(200); |
| 159 | } |
| 160 | if (!mainInterface.isValid()) { |
| 161 | out << " - done\n" ; |
| 162 | } else { |
| 163 | out << " - failed to stop!\n" ; |
| 164 | return 1; |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | const QString path = fileIndexDbPath() + QStringLiteral("/index" ); |
| 169 | QFile(path).remove(); |
| 170 | out << "Deleted the index database\n" ; |
| 171 | |
| 172 | if (running) { |
| 173 | start(); |
| 174 | out << "Restarting the File Indexer\n" ; |
| 175 | } |
| 176 | |
| 177 | return 0; |
| 178 | } |
| 179 | |
| 180 | if (command == QLatin1String("suspend" )) { |
| 181 | schedulerinterface.suspend(); |
| 182 | out << "File Indexer suspended\n" ; |
| 183 | return 0; |
| 184 | } |
| 185 | |
| 186 | if (command == QLatin1String("resume" )) { |
| 187 | schedulerinterface.resume(); |
| 188 | out << "File Indexer resumed\n" ; |
| 189 | return 0; |
| 190 | } |
| 191 | |
| 192 | if (command == QLatin1String("check" )) { |
| 193 | schedulerinterface.checkUnindexedFiles(); |
| 194 | out << "Started search for unindexed files\n" ; |
| 195 | return 0; |
| 196 | } |
| 197 | |
| 198 | if (command == QLatin1String("index" )) { |
| 199 | IndexCommand commandIndex; |
| 200 | return commandIndex.exec(parser); |
| 201 | } |
| 202 | |
| 203 | if (command == QLatin1String("clear" )) { |
| 204 | ClearCommand commandClear; |
| 205 | return commandClear.exec(parser); |
| 206 | } |
| 207 | |
| 208 | if (command == QLatin1String("failed" )) { |
| 209 | Database *db = globalDatabaseInstance(); |
| 210 | if (db->open(mode: Database::ReadOnlyDatabase) != Database::OpenResult::Success) { |
| 211 | out << "Baloo Index could not be opened\n" ; |
| 212 | return 1; |
| 213 | } |
| 214 | |
| 215 | Transaction tr(db, Transaction::ReadOnly); |
| 216 | |
| 217 | const quint64 limit = 128; |
| 218 | const QVector<quint64> failedIds = tr.failedIds(limit); |
| 219 | if (failedIds.isEmpty()) { |
| 220 | out << "All Files were indexed successfully\n" ; |
| 221 | return 0; |
| 222 | } |
| 223 | |
| 224 | out << "The following files could not be indexed:\n" ; |
| 225 | for (auto id : failedIds) { |
| 226 | out << tr.documentUrl(id) << '\n'; |
| 227 | } |
| 228 | if (failedIds.size() == limit) { |
| 229 | out << "... list truncated\n" ; |
| 230 | } |
| 231 | return 0; |
| 232 | } |
| 233 | |
| 234 | if (command == QLatin1String("indexSize" )) { |
| 235 | Database *db = globalDatabaseInstance(); |
| 236 | if (db->open(mode: Database::ReadOnlyDatabase) != Database::OpenResult::Success) { |
| 237 | out << "Baloo Index could not be opened\n" ; |
| 238 | return 1; |
| 239 | } |
| 240 | |
| 241 | DatabaseSize size; |
| 242 | { |
| 243 | Transaction tr(db, Transaction::ReadOnly); |
| 244 | size = tr.dbSize(); |
| 245 | } |
| 246 | uint totalDataSize = size.expectedSize; |
| 247 | |
| 248 | KFormat format(QLocale::system()); |
| 249 | auto prFunc = [&](const QString& name, uint size) { |
| 250 | out.setFieldWidth(20); |
| 251 | out << name; |
| 252 | out.setFieldWidth(0); |
| 253 | out << ":" ; |
| 254 | out.setFieldWidth(15); |
| 255 | out << format.formatByteSize(size, precision: 2); |
| 256 | out.setFieldWidth(10); |
| 257 | out << QString::number((100.0 * size / totalDataSize), format: 'f', precision: 3); |
| 258 | out.setFieldWidth(0); |
| 259 | out << " %\n" ; |
| 260 | }; |
| 261 | |
| 262 | out << "File Size: " << format.formatByteSize(size: size.actualSize, precision: 2) << "\n" ; |
| 263 | out << "Used: " << format.formatByteSize(size: totalDataSize, precision: 2) << "\n\n" ; |
| 264 | prFunc(QStringLiteral("PostingDB" ), size.postingDb); |
| 265 | prFunc(QStringLiteral("PositionDB" ), size.positionDb); |
| 266 | prFunc(QStringLiteral("DocTerms" ), size.docTerms); |
| 267 | prFunc(QStringLiteral("DocFilenameTerms" ), size.docFilenameTerms); |
| 268 | prFunc(QStringLiteral("DocXattrTerms" ), size.docXattrTerms); |
| 269 | prFunc(QStringLiteral("IdTree" ), size.idTree); |
| 270 | prFunc(QStringLiteral("IdFileName" ), size.idFilename); |
| 271 | prFunc(QStringLiteral("DocTime" ), size.docTime); |
| 272 | prFunc(QStringLiteral("DocData" ), size.docData); |
| 273 | prFunc(QStringLiteral("ContentIndexingDB" ), size.contentIndexingIds); |
| 274 | prFunc(QStringLiteral("FailedIdsDB" ), size.failedIds); |
| 275 | prFunc(QStringLiteral("MTimeDB" ), size.mtimeDb); |
| 276 | |
| 277 | return 0; |
| 278 | } |
| 279 | |
| 280 | if (command == QLatin1String("monitor" )) { |
| 281 | MonitorCommand mon; |
| 282 | return mon.exec(parser); |
| 283 | } |
| 284 | |
| 285 | /* |
| 286 | TODO: Make separate executable |
| 287 | if (command == QLatin1String("checkDb")) { |
| 288 | Database *db = globalDatabaseInstance(); |
| 289 | if (!db->open(Database::ReadOnlyDatabase)) { |
| 290 | out << "Baloo Index could not be opened\n"; |
| 291 | return 1; |
| 292 | } |
| 293 | |
| 294 | Transaction tr(db, Transaction::ReadOnly); |
| 295 | tr.checkPostingDbinTermsDb(); |
| 296 | tr.checkTermsDbinPostingDb(); |
| 297 | out << "Checking file paths .. "<< '\n'; |
| 298 | tr.checkFsTree(); |
| 299 | return 0; |
| 300 | } |
| 301 | */ |
| 302 | |
| 303 | parser.showHelp(exitCode: 1); |
| 304 | return 0; |
| 305 | } |
| 306 | |