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

0001 /*
0002     SPDX-FileCopyrightText: 2012-2013 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 <algorithm>
0008 
0009 #include <QCoreApplication>
0010 #include <QCommandLineParser>
0011 #include <QCommandLineOption>
0012 #include <QDateTime>
0013 #include <QFile>
0014 #include <QTextStream>
0015 
0016 #include <KAboutData>
0017 #include <KLocalizedString>
0018 
0019 #include <QJsonDocument>
0020 #include <QJsonObject>
0021 
0022 #include "global.h"
0023 #include "idutils.h"
0024 #include "database.h"
0025 #include "transaction.h"
0026 
0027 #include <unistd.h>
0028 #include <KFileMetaData/PropertyInfo>
0029 
0030 QString colorString(const QString& input, int color)
0031 {
0032     static bool isTty = isatty(fileno(stdout));
0033     if(isTty) {
0034         QString colorStart = QStringLiteral("\033[0;%1m").arg(color);
0035         QLatin1String colorEnd("\033[0;0m");
0036 
0037         return colorStart + input + colorEnd;
0038     } else {
0039         return input;
0040     }
0041 }
0042 
0043 inline KFileMetaData::PropertyMultiMap variantToPropertyMultiMap(const QVariantMap &varMap)
0044 {
0045     KFileMetaData::PropertyMultiMap propMap;
0046     QVariantMap::const_iterator it = varMap.constBegin();
0047     for (; it != varMap.constEnd(); ++it) {
0048         int p = it.key().toInt();
0049         propMap.insert(static_cast<KFileMetaData::Property::Property>(p), it.value());
0050     }
0051     return propMap;
0052 }
0053 
0054 int main(int argc, char* argv[])
0055 {
0056     QCoreApplication app(argc, argv);
0057 
0058     KAboutData aboutData(QStringLiteral("balooshow"),
0059                          i18n("Baloo Show"),
0060                          QStringLiteral(PROJECT_VERSION),
0061                          i18n("The Baloo data Viewer - A debugging tool"),
0062                          KAboutLicense::GPL,
0063                          i18n("(c) 2012, Vishesh Handa"));
0064 
0065     KAboutData::setApplicationData(aboutData);
0066 
0067     QCommandLineParser parser;
0068     parser.addPositionalArgument(QStringLiteral("files"), i18n("Urls, document ids or inodes of the files"), QStringLiteral("[file|id|inode...]"));
0069     parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("x"),
0070                                         i18n("Print internal info")));
0071     parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("i"),
0072                                         i18n("Arguments are interpreted as inode numbers (requires -d)")));
0073     parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("d"),
0074                                         i18n("Device id for the files"), QStringLiteral("deviceId"), QString()));
0075     parser.addHelpOption();
0076     parser.process(app);
0077 
0078     const QStringList args = parser.positionalArguments();
0079 
0080     if (args.isEmpty()) {
0081         parser.showHelp(1);
0082     }
0083 
0084     QTextStream stream(stdout);
0085 
0086     bool useInodes = parser.isSet(QStringLiteral("i"));
0087     quint32 devId;
0088     if (useInodes) {
0089         bool ok;
0090         devId = parser.value(QStringLiteral("d")).toULong(&ok, 10);
0091         if (!ok) {
0092             devId = parser.value(QStringLiteral("d")).toULong(&ok, 16);
0093         }
0094     }
0095 
0096     if (useInodes && devId == 0) {
0097         stream << i18n("Error: -i requires specifying a device (-d <deviceId>)") << '\n';
0098         parser.showHelp(1);
0099     }
0100 
0101     Baloo::Database *db = Baloo::globalDatabaseInstance();
0102     if (!db->open(Baloo::Database::ReadOnlyDatabase)) {
0103         stream << i18n("The Baloo index could not be opened. Please run \"%1\" to see if Baloo is enabled and working.",
0104         QStringLiteral("balooctl status")) << '\n';
0105         return 1;
0106     }
0107 
0108     Baloo::Transaction tr(db, Baloo::Transaction::ReadOnly);
0109 
0110     for (QString url : args) {
0111         quint64 fid = 0;
0112         QString internalUrl;
0113         if (!useInodes) {
0114             if (QFile::exists(url)) {
0115                 quint64 fsFid = Baloo::filePathToId(QFile::encodeName(url));
0116                 fid = tr.documentId(QFile::encodeName(url));
0117                 internalUrl = QFile::decodeName(tr.documentUrl(fsFid));
0118 
0119                 if (fid && fid != fsFid) {
0120                     stream << i18n("The document IDs of the Baloo DB and the filesystem are different:") << '\n';
0121                     auto dbInode = Baloo::idToInode(fid);
0122                     auto fsInode = Baloo::idToInode(fsFid);
0123                     auto dbDevId = Baloo::idToDeviceId(fid);
0124                     auto fsDevId = Baloo::idToDeviceId(fsFid);
0125 
0126                     stream << "Url: " << url << "\n";
0127                     stream << "ID:       " << fid << " (DB) <-> " << fsFid << " (FS)\n";
0128                     stream << "Inode:    " << dbInode << " (DB) " << (dbInode == fsInode ? "== " : "<-> ") << fsInode << " (FS)\n";
0129                     stream << "DeviceID: " << dbDevId << " (DB) " << (dbDevId == fsDevId ? "== " : "<-> ") << fsDevId << " (FS)\n";
0130                 }
0131                 fid = fsFid;
0132             } else {
0133                 bool ok;
0134                 fid = url.toULongLong(&ok, 10);
0135                 if (!ok) {
0136                     fid = url.toULongLong(&ok, 16);
0137                 }
0138                 if (!ok) {
0139                     stream << i18n("%1: Not a valid url or document id", url) << '\n';
0140                     continue;
0141                 }
0142                 url = QFile::decodeName(tr.documentUrl(fid));
0143                 internalUrl = url;
0144             }
0145 
0146         } else {
0147             bool ok;
0148             quint32 inode = url.toULong(&ok, 10);
0149             if (!ok) {
0150                 inode = url.toULong(&ok, 16);
0151             }
0152             if (!ok) {
0153                 stream << i18n("%1: Failed to parse inode number", url) << '\n';
0154                 continue;
0155             }
0156 
0157             fid = Baloo::devIdAndInodeToId(devId, inode);
0158             url = QFile::decodeName(tr.documentUrl(fid));
0159             internalUrl = url;
0160         }
0161 
0162         if (fid) {
0163             stream << colorString(QString::number(fid, 16), 31) << ' ';
0164             stream << colorString(QString::number(Baloo::idToDeviceId(fid)), 28) << ' ';
0165             stream << colorString(QString::number(Baloo::idToInode(fid)), 28) << ' ';
0166         }
0167         if (fid && tr.hasDocument(fid)) {
0168             stream << colorString(url, 32);
0169             if (!internalUrl.isEmpty() && internalUrl != url) {
0170                 // The document is know by a different name inside the DB,
0171                 // e.g. a hardlink, or untracked rename
0172                 stream << QLatin1String(" [") << internalUrl << QLatin1Char(']');
0173             }
0174             stream << '\n';
0175         }
0176         else {
0177             stream << i18n("%1: No index information found", url) << '\n';
0178             continue;
0179         }
0180 
0181         Baloo::DocumentTimeDB::TimeInfo time = tr.documentTimeInfo(fid);
0182         stream << QStringLiteral("\tMtime: %1 ").arg(time.mTime)
0183                << QDateTime::fromSecsSinceEpoch(time.mTime).toString(Qt::ISODate)
0184                << QStringLiteral("\n\tCtime: %1 ").arg(time.cTime)
0185                << QDateTime::fromSecsSinceEpoch(time.cTime).toString(Qt::ISODate)
0186               << '\n';
0187 
0188         const QJsonDocument jdoc = QJsonDocument::fromJson(tr.documentData(fid));
0189         const QVariantMap varMap = jdoc.object().toVariantMap();
0190         KFileMetaData::PropertyMultiMap propMap = variantToPropertyMultiMap(varMap);
0191         if (!propMap.isEmpty()) {
0192             stream << "\tCached properties:" << '\n';
0193         }
0194         for (auto it = propMap.constBegin(); it != propMap.constEnd(); ++it) {
0195             QString str;
0196             if (it.value().typeId() == QMetaType::QVariantList) {
0197                 QStringList list;
0198                 const auto vars = it.value().toList();
0199                 for (const QVariant& var : vars) {
0200                     list << var.toString();
0201                 }
0202                 str = list.join(QLatin1String(", "));
0203             } else {
0204                 str = it.value().toString();
0205             }
0206 
0207             KFileMetaData::PropertyInfo pi(it.key());
0208             stream << "\t\t" << pi.displayName() << ": " << str << '\n';
0209         }
0210 
0211         if (parser.isSet(QStringLiteral("x"))) {
0212             QVector<QByteArray> terms = tr.documentTerms(fid);
0213             QVector<QByteArray> fileNameTerms = tr.documentFileNameTerms(fid);
0214             QVector<QByteArray> xAttrTerms = tr.documentXattrTerms(fid);
0215 
0216             auto join = [](const QVector<QByteArray>& v) {
0217                 QByteArray ba;
0218                 for (const QByteArray& arr : v) {
0219                     ba.append(arr);
0220                     ba.append(' ');
0221                 }
0222                 return QString::fromUtf8(ba);
0223             };
0224 
0225             auto propertiesBegin = std::stable_partition(terms.begin(), terms.end(),
0226                 [](const auto & t) { return t.isEmpty() || t[0] < 'A' || t[0] > 'Z'; });
0227             const QVector<QByteArray> propertyTerms{propertiesBegin, terms.end()};
0228             terms.erase(propertiesBegin, terms.end());
0229 
0230             stream << "\n" << i18n("Internal Info") << "\n";
0231             stream << i18n("File Name Terms: %1", join(fileNameTerms)) << "\n";
0232             stream << i18n("%1 Terms: %2", QStringLiteral("XAttr"), join(xAttrTerms)) << '\n';
0233             stream << i18n("Plain Text Terms: %1", join(terms)) << "\n";
0234             stream << i18n("Property Terms: %1", join(propertyTerms)) << "\n";
0235 
0236             QHash<int, QStringList> propertyWords;
0237             KLocalizedString errorPrefix = ki18nc("Prefix string for internal errors", "Internal Error - %1");
0238 
0239             for (const QByteArray& arr : propertyTerms) {
0240                 auto arrAsPrintable = [arr]() {
0241                     return QString::fromLatin1(arr.toPercentEncoding());
0242                 };
0243 
0244                 if (arr.length() < 1) {
0245                     auto error = QStringLiteral("malformed term (short): '%1'\n").arg(arrAsPrintable());
0246                     stream << errorPrefix.subs(error).toString();
0247                     continue;
0248                 }
0249 
0250                 const QString word = QString::fromUtf8(arr);
0251 
0252                 if (word[0] == QLatin1Char('X')) {
0253                         if (word.length() < 4) {
0254                             // 'X<num>-<value>
0255                             auto error = QStringLiteral("malformed property term (short): '%1' in '%2'\n").arg(word, arrAsPrintable());
0256                             stream << errorPrefix.subs(error).toString();
0257                             continue;
0258                         }
0259                         const int posOfNonNumeric = word.indexOf(QLatin1Char('-'), 2);
0260                         if ((posOfNonNumeric < 0) || ((posOfNonNumeric + 1) == word.length())) {
0261                             auto error = QStringLiteral("malformed property term (no data): '%1' in '%2'\n").arg(word, arrAsPrintable());
0262                             stream << errorPrefix.subs(error).toString();
0263                             continue;
0264                         }
0265 
0266                         bool ok;
0267                         const QStringView prop = QStringView(word).mid(1, posOfNonNumeric - 1);
0268                         int propNum = prop.toInt(&ok);
0269                         if (!ok) {
0270                             auto error = QStringLiteral("malformed property term (bad index): '%1' in '%2'\n").arg(prop, arrAsPrintable());
0271                             stream << errorPrefix.subs(error).toString();
0272                             continue;
0273                         }
0274 
0275                         const QString value = word.mid(posOfNonNumeric + 1);
0276                         propertyWords[propNum].append(value);
0277                 }
0278             }
0279 
0280             for (auto it = propertyWords.constBegin(); it != propertyWords.constEnd(); it++) {
0281                 auto prop = static_cast<KFileMetaData::Property::Property>(it.key());
0282                 KFileMetaData::PropertyInfo pi(prop);
0283 
0284                 stream << pi.name() << ": " << it.value().join(QLatin1Char(' ')) << '\n';
0285             }
0286         }
0287     }
0288     stream.flush();
0289     return 0;
0290 }