File indexing completed on 2024-05-12 03:53:05

0001 /*
0002     SPDX-FileCopyrightText: 2018 Michael Heidelbach <heidelbach@web.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "global.h"
0008 #include "experimental/databasesanitizer.h"
0009 
0010 #include <KAboutData>
0011 #include <KLocalizedString>
0012 
0013 #include <QCoreApplication>
0014 #include <QCommandLineOption>
0015 #include <QCommandLineParser>
0016 #include <QStandardPaths>
0017 #include <QTextStream>
0018 #include <QList>
0019 #include <QElapsedTimer>
0020 
0021 using namespace Baloo;
0022 
0023 struct Command {
0024     const QString name;
0025     const QString description;
0026     const QStringList args;
0027     const QStringList options;
0028 };
0029 
0030 const auto options = QList<QCommandLineOption>{
0031     QCommandLineOption{
0032         QStringList{QStringLiteral("i"), QStringLiteral("device-id")},
0033         i18n("Filter by device id."
0034         "\n0 (default) does not filter and everything is printed."
0035         "\nPositive numbers are including filters printing only the mentioned device id."
0036         "\nNegative numbers are excluding filters printing everything but the mentioned device id."
0037         "\nMay be given multiple times."),
0038         i18n("integer"),
0039         nullptr
0040     },
0041     QCommandLineOption{
0042         QStringList{QStringLiteral("m"), QStringLiteral("missing-only")},
0043         i18n("List only inaccessible entries.\nOnly applies to \"%1\"", QStringLiteral("list"))
0044     },
0045     QCommandLineOption{
0046         QStringList{QStringLiteral("u"), QStringLiteral("mounted-only")},
0047         i18n("Act only on item on mounted devices")
0048     },
0049     QCommandLineOption{
0050         QStringList{QStringLiteral("D"), QStringLiteral("dry-run")},
0051         i18n("Print results of a cleaning operation, but do not change anything."
0052         "\nOnly applies to \"%1\" command", QStringLiteral("clean"))
0053     }
0054 };
0055 
0056 const auto commands = std::vector<Command>{
0057     Command{
0058         QStringLiteral("list"),
0059         i18n("List database contents. Use a regular expression as argument to filter output"),
0060         QStringList{
0061             QStringLiteral("pattern")
0062         },
0063         QStringList{
0064             QStringLiteral("missing-only"),
0065             QStringLiteral("device-id")
0066         }
0067     },
0068     Command{
0069         QStringLiteral("devices"),
0070         i18n("List devices"),
0071         QStringList{},
0072         QStringList{QStringLiteral("missing-only")}
0073     },
0074     /*TODO:
0075     Command{
0076         QStringLiteral("check"),
0077         i18n("Check database contents. "
0078         "Beware this may take very long to execute"),
0079         QStringList{},
0080         QStringList{}
0081     },
0082     */
0083     Command{
0084         QStringLiteral("clean"),
0085         i18n("Remove stale database entries"),
0086         QStringList{
0087             QStringLiteral("pattern")
0088         },
0089         QStringList{
0090             QStringLiteral("dry-run"),
0091             QStringLiteral("device-id"),
0092             QStringLiteral("mounted-only")
0093         }
0094     }
0095 };
0096 
0097 const QStringList allowedCommands()
0098 {
0099     QStringList names;
0100     for (const auto& c : commands) {
0101         names.append(c.name);
0102     }
0103     return names;
0104 }
0105 const QStringList getOptions(const QString& name)
0106 {
0107     for (const auto& c : commands) {
0108         if (c.name == name) {
0109             return c.options;
0110         }
0111     }
0112     return QStringList();
0113 }
0114 QString createDescription()
0115 {
0116     QStringList allowedcommands;
0117     for (const auto& c: commands) {
0118         auto options = getOptions(c.name);
0119         const QString optionStr = options.isEmpty()
0120             ? QString()
0121             : QStringLiteral(" [--%1]").arg(options.join(QLatin1String("] [--")));
0122 
0123         QString argumentStr;
0124         if (!c.args.isEmpty() ) {
0125             argumentStr = QStringLiteral(" [%1]").arg(c.args.join(QStringLiteral("] [")));
0126         }
0127 
0128         const QString commandStr = QStringLiteral("%1%2%3")
0129             .arg(c.name)
0130             .arg(optionStr)
0131             .arg(argumentStr);
0132 
0133         const QString str = QStringLiteral("%1 %2")
0134             .arg(commandStr, -58)
0135             .arg(c.description);
0136 
0137         allowedcommands.append(str);
0138     }
0139     const QString allCommandsStr = allowedcommands.join(QLatin1String("\n    "));
0140     return i18n("\n\nCommands:\n    %1", allCommandsStr);
0141 }
0142 
0143 int main(int argc, char* argv[])
0144 {
0145     QCoreApplication app(argc, argv);
0146     KAboutData aboutData(QStringLiteral("baloodb"),
0147                          i18n("Baloo Database Sanitizer"),
0148                          PROJECT_VERSION,
0149                          i18n("The Baloo Database Lister & Sanitizer"),
0150                          KAboutLicense::GPL,
0151                          i18n("(c) 2018, Michael Heidelbach"));
0152     aboutData.addAuthor(i18n("Michael Heidelbach"), i18n("Maintainer"), QStringLiteral("ottwolt@gmail.com"));
0153     KAboutData::setApplicationData(aboutData);
0154 
0155     QCommandLineParser parser;
0156     parser.addOptions(options);
0157     parser.addPositionalArgument(QStringLiteral("command"),
0158         i18n("The command to execute"),
0159         allowedCommands().join(QLatin1Char('|'))
0160     );
0161     parser.addPositionalArgument(QStringLiteral("pattern"),
0162         i18nc("Command", "A regular expression applied to the URL of database items"
0163             "\nExample: %1"
0164             , "baloodb list '^/media/videos/series'"
0165         )
0166     );
0167     const QString warnExperiment = QStringLiteral(
0168         "===\nPlease note: This is an experimental tool. Command line switches or their meaning may change.\n===");
0169 
0170     parser.setApplicationDescription(warnExperiment + createDescription());
0171     parser.addVersionOption();
0172     parser.addHelpOption();
0173 
0174     parser.process(app);
0175     if (parser.positionalArguments().isEmpty()) {
0176         qDebug() << "No command";
0177         parser.showHelp(1);
0178     }
0179 
0180     auto args = parser.positionalArguments();
0181     auto command = args.at(0);
0182     args.removeFirst();
0183 
0184     if(!allowedCommands().contains(command)) {
0185         qDebug() << "Unknown command" << command;
0186         parser.showHelp(1);
0187     }
0188 
0189     const auto optNames = parser.optionNames();
0190     const auto allowedOptions = getOptions(command);
0191 
0192     QVector<qint64> deviceIds;
0193     for (const auto& dev : parser.values(QStringLiteral("device-id"))) {
0194         deviceIds.append(dev.toInt());
0195     }
0196     const DatabaseSanitizer::ItemAccessFilters accessFilter = (
0197         parser.isSet(QStringLiteral("missing-only"))
0198         ? DatabaseSanitizer::IgnoreAvailable
0199         : DatabaseSanitizer::IgnoreNone
0200     ) | (
0201         parser.isSet(QStringLiteral("mounted-only"))
0202         ? DatabaseSanitizer::IgnoreUnmounted
0203         : DatabaseSanitizer::IgnoreNone
0204     );
0205     const QString pattern = args.isEmpty()
0206         ? QString()
0207         : args.at(0);
0208     const QSharedPointer<QRegularExpression> urlFilter(pattern.isEmpty()
0209         ? nullptr
0210         : new QRegularExpression{pattern});
0211 
0212     auto db = globalDatabaseInstance();
0213     QTextStream err(stderr);
0214     QElapsedTimer timer;
0215     timer.start();
0216 
0217     if (command == QLatin1String("list")) {
0218         if (!db->open(Database::ReadOnlyDatabase)) {
0219             err << i18n("Baloo Index could not be opened") << endl;
0220             return 1;
0221         }
0222         DatabaseSanitizer san(db, Transaction::ReadOnly);
0223         err << i18n("Listing database contents...") << endl;
0224         san.printList(deviceIds, accessFilter, urlFilter);
0225     } else if (command == QLatin1String("devices")) {
0226         if (!db->open(Database::ReadOnlyDatabase)) {
0227             err << i18n("Baloo Index could not be opened") << endl;
0228             return 1;
0229         }
0230         DatabaseSanitizer san(db, Transaction::ReadOnly);
0231         err << i18n("Listing database contents...") << endl;
0232         san.printDevices(deviceIds, accessFilter);
0233 
0234     } else if (command == QLatin1String("clean")) {
0235         auto dbMode = Database::ReadWriteDatabase;
0236         if (!db->open(dbMode)) {
0237             err << i18n("Baloo Index could not be opened") << endl;
0238             return 1;
0239         }
0240         DatabaseSanitizer san(db, Transaction::ReadWrite);
0241         err << i18n("Removing stale database contents...") << endl;
0242         san.removeStaleEntries(deviceIds, accessFilter, parser.isSet(QStringLiteral("dry-run")), urlFilter);
0243 
0244     } else if (command == QLatin1String("check")) {
0245         parser.showHelp(1);
0246        /* TODO: After check methods are improved
0247             Database *db = globalDatabaseInstance();
0248             if (!db->open(Database::ReadOnlyDatabase)) {
0249                 err << i18n("Baloo Index could not be opened") << endl;
0250                 return 1;
0251             }
0252             Transaction tr(db, Transaction::ReadOnly);
0253             err << i18n("Checking file paths ... ") << endl;
0254             tr.checkFsTree();
0255 
0256             err << i18n("Checking postings ... ") << endl;
0257             tr.checkTermsDbinPostingDb();
0258 
0259             err << i18n("Checking terms ... ") << endl;
0260             tr.checkPostingDbinTermsDb();
0261         */
0262     }
0263     err << i18n("Elapsed: %1 secs", timer.nsecsElapsed() / 1000000000.0) << endl;
0264 
0265     return 0;
0266 }