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 }