File indexing completed on 2024-05-19 15:06:40
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().type() == QVariant::List) { 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 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0268 const QStringView prop = QStringView(word).mid(1, posOfNonNumeric - 1); 0269 #else 0270 const QStringRef prop = word.midRef(1, posOfNonNumeric - 1); 0271 #endif 0272 int propNum = prop.toInt(&ok); 0273 if (!ok) { 0274 auto error = QStringLiteral("malformed property term (bad index): '%1' in '%2'\n").arg(prop, arrAsPrintable()); 0275 stream << errorPrefix.subs(error).toString(); 0276 continue; 0277 } 0278 0279 const QString value = word.mid(posOfNonNumeric + 1); 0280 propertyWords[propNum].append(value); 0281 } 0282 } 0283 0284 for (auto it = propertyWords.constBegin(); it != propertyWords.constEnd(); it++) { 0285 auto prop = static_cast<KFileMetaData::Property::Property>(it.key()); 0286 KFileMetaData::PropertyInfo pi(prop); 0287 0288 stream << pi.name() << ": " << it.value().join(QLatin1Char(' ')) << '\n'; 0289 } 0290 } 0291 } 0292 stream.flush(); 0293 return 0; 0294 }