File indexing completed on 2024-06-16 05:01:12
0001 /* 0002 * Copyright (C) 2017 Christian Mollekopf <mollekopf@kolabsys.com> 0003 * 0004 * This program is free software; you can redistribute it and/or modify 0005 * it under the terms of the GNU General Public License as published by 0006 * the Free Software Foundation; either version 2 of the License, or 0007 * (at your option) any later version. 0008 * 0009 * This program is distributed in the hope that it will be useful, 0010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0012 * GNU General Public License for more details. 0013 * 0014 * You should have received a copy of the GNU General Public License 0015 * along with this program; if not, write to the 0016 * Free Software Foundation, Inc., 0017 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 0018 */ 0019 0020 #include <QDebug> 0021 #include <QObject> // tr() 0022 #include <QFile> 0023 0024 #include "common/resource.h" 0025 #include "common/storage.h" 0026 #include "common/resourceconfig.h" 0027 #include "common/log.h" 0028 #include "common/storage.h" 0029 #include "common/definitions.h" 0030 #include "common/entitybuffer.h" 0031 #include "common/metadata_generated.h" 0032 #include "common/bufferutils.h" 0033 #include "common/fulltextindex.h" 0034 0035 #include "storage/key.h" 0036 0037 #include "sinksh_utils.h" 0038 #include "state.h" 0039 #include "syntaxtree.h" 0040 0041 namespace SinkInspect 0042 { 0043 0044 using Sink::Storage::Key; 0045 using Sink::Storage::Identifier; 0046 using Sink::Storage::Revision; 0047 0048 QString parse(const QByteArray &bytes) 0049 { 0050 if (Revision::isValidInternal(bytes)) { 0051 return Revision::fromInternalByteArray(bytes).toDisplayString(); 0052 } else if (Key::isValidInternal(bytes)) { 0053 return Key::fromInternalByteArray(bytes).toDisplayString(); 0054 } else if (Identifier::isValidInternal(bytes)) { 0055 return Identifier::fromInternalByteArray(bytes).toDisplayString(); 0056 } else { 0057 return QString::fromUtf8(bytes); 0058 } 0059 } 0060 0061 static QString operationName(int operation) 0062 { 0063 switch (operation) { 0064 case 1: return "Create"; 0065 case 2: return "Modify"; 0066 case 3: return "Delete"; 0067 } 0068 return {}; 0069 } 0070 0071 Syntax::List syntax(); 0072 0073 bool inspect(QStringList args, State &state) 0074 { 0075 if (args.isEmpty()) { 0076 state.printError(syntax()[0].usage()); 0077 return false; 0078 } 0079 0080 const auto resource = SinkshUtils::parseUid(args.takeFirst().toLatin1()); 0081 auto options = SyntaxTree::parseOptions(args); 0082 0083 Sink::Storage::DataStore storage(Sink::storageLocation(), resource, Sink::Storage::DataStore::ReadOnly); 0084 auto transaction = storage.createTransaction(Sink::Storage::DataStore::ReadOnly); 0085 0086 if (options.options.contains("validaterids")) { 0087 if (options.options.value("validaterids").isEmpty()) { 0088 state.printError(QObject::tr("Specify a type to validate.")); 0089 return false; 0090 } 0091 auto type = options.options.value("validaterids").first().toUtf8(); 0092 /* 0093 * Try to find all rid's for all uid's. 0094 * If we have entities without rid's that either means we have only created it locally or that we have a problem. 0095 */ 0096 Sink::Storage::DataStore syncStore(Sink::storageLocation(), resource + ".synchronization", Sink::Storage::DataStore::ReadOnly); 0097 auto syncTransaction = syncStore.createTransaction(Sink::Storage::DataStore::ReadOnly); 0098 0099 auto db = transaction.openDatabase(type + ".main", 0100 [&] (const Sink::Storage::DataStore::Error &e) { 0101 Q_ASSERT(false); 0102 state.printError(e.message); 0103 }, Sink::Storage::IntegerKeys); 0104 0105 auto ridMap = syncTransaction.openDatabase("localid.mapping." + type, 0106 [&] (const Sink::Storage::DataStore::Error &e) { 0107 Q_ASSERT(false); 0108 state.printError(e.message); 0109 }); 0110 0111 QHash<QByteArray, QByteArray> hash; 0112 0113 ridMap.scan("", [&] (const QByteArray &key, const QByteArray &data) { 0114 hash.insert(key, data); 0115 return true; 0116 }, 0117 [&](const Sink::Storage::DataStore::Error &e) { 0118 state.printError(e.message); 0119 }, 0120 false); 0121 0122 QSet<QByteArray> uids; 0123 db.scan("", [&] (const QByteArray &key, const QByteArray &data) { 0124 size_t revision = Sink::byteArrayToSizeT(key); 0125 uids.insert(Sink::Storage::DataStore::getUidFromRevision(transaction, revision).toDisplayByteArray()); 0126 return true; 0127 }, 0128 [&](const Sink::Storage::DataStore::Error &e) { 0129 state.printError(e.message); 0130 }, 0131 false); 0132 0133 int missing = 0; 0134 for (const auto &uid : uids) { 0135 if (!hash.remove(uid)) { 0136 missing++; 0137 qWarning() << "Failed to find RID for " << uid; 0138 } 0139 } 0140 if (missing) { 0141 qWarning() << "Found a total of " << missing << " missing rids"; 0142 } 0143 0144 //If we still have items in the hash it means we have rid mappings for entities 0145 //that no longer exist. 0146 if (!hash.isEmpty()) { 0147 qWarning() << "Have rids left: " << hash.size(); 0148 } else if (!missing) { 0149 qWarning() << "Everything is in order."; 0150 } 0151 0152 return false; 0153 } 0154 if (options.options.contains("fulltext")) { 0155 FulltextIndex index(resource, Sink::Storage::DataStore::ReadOnly); 0156 if (options.options.value("fulltext").isEmpty()) { 0157 state.printLine(QString("Total document count: ") + QString::number(index.getDoccount())); 0158 } else { 0159 const auto entityId = SinkshUtils::parseUid(options.options.value("fulltext").first().toUtf8()); 0160 const auto content = index.getIndexContent(entityId); 0161 if (!content.found) { 0162 state.printLine(QString("Failed to find the document with the id: ") + entityId); 0163 } else { 0164 state.printLine(QString("Found document with terms: ") + content.terms.join(", "), 1); 0165 } 0166 0167 } 0168 return false; 0169 } 0170 0171 if (options.options.contains("history")) { 0172 FulltextIndex index(resource, Sink::Storage::DataStore::ReadOnly); 0173 if (options.options.value("history").isEmpty()) { 0174 state.printLine(QString("Provide an entity uid to search for.")); 0175 } else { 0176 const auto entityId = SinkshUtils::parseUid(options.options.value("history").first().toUtf8()); 0177 0178 auto db = transaction.openDatabase("mail.main", 0179 [&] (const Sink::Storage::DataStore::Error &e) { 0180 Q_ASSERT(false); 0181 state.printError(e.message); 0182 }); 0183 //Print a by revision history for the given uid. 0184 for (size_t revision : Sink::Storage::DataStore::getRevisionsFromUid(transaction, Sink::Storage::Identifier::fromDisplayByteArray(entityId))) { 0185 db.scan(revision, [&] (const size_t &key, const QByteArray &data) { 0186 Sink::EntityBuffer buffer(const_cast<const char *>(data.data()), data.size()); 0187 if (!buffer.isValid()) { 0188 state.printError("Read invalid buffer from disk."); 0189 } else { 0190 const auto metadata = flatbuffers::GetRoot<Sink::Metadata>(buffer.metadataBuffer()); 0191 state.printLine(" Operation: " + operationName(metadata->operation()) 0192 + " Replay: " + (metadata->replayToSource() ? "true" : "false") 0193 + ((metadata->modifiedProperties() && metadata->modifiedProperties()->size() != 0) ? (" [" + Sink::BufferUtils::fromVector(*metadata->modifiedProperties()).join(", ")) + "]": "") 0194 + " Value size: " + QString::number(data.size()) 0195 ); 0196 } 0197 return true; 0198 }, 0199 [&](const Sink::Storage::DataStore::Error &e) { 0200 state.printError(e.message); 0201 }); 0202 } 0203 0204 } 0205 return false; 0206 } 0207 0208 auto dbs = options.options.value("db"); 0209 auto idFilter = options.options.value("filter"); 0210 0211 state.printLine(QString("Current revision: %1").arg(Sink::Storage::DataStore::maxRevision(transaction))); 0212 state.printLine(QString("Last clean revision: %1").arg(Sink::Storage::DataStore::cleanedUpRevision(transaction))); 0213 0214 auto databases = transaction.getDatabaseNames(); 0215 if (dbs.isEmpty()) { 0216 state.printLine("Available databases: "); 0217 for (const auto &db : databases) { 0218 state.printLine(db, 1); 0219 } 0220 return false; 0221 } 0222 auto dbName = dbs.value(0).toUtf8(); 0223 auto isMainDb = dbName.contains(".main"); 0224 if (!databases.contains(dbName)) { 0225 state.printError(QString("Database not available: ") + dbName); 0226 } 0227 0228 state.printLine(QString("Opening: ") + dbName); 0229 auto db = transaction.openDatabase(dbName, 0230 [&] (const Sink::Storage::DataStore::Error &e) { 0231 Q_ASSERT(false); 0232 state.printError(e.message); 0233 }); 0234 0235 QByteArray filter; 0236 if (!idFilter.isEmpty()) { 0237 filter = idFilter.first().toUtf8(); 0238 } 0239 0240 //Print rest of db 0241 bool findSubstringKeys = !filter.isEmpty(); 0242 int keySizeTotal = 0; 0243 int valueSizeTotal = 0; 0244 auto count = db.scan(filter, [&] (const QByteArray &key, const QByteArray &data) { 0245 keySizeTotal += key.size(); 0246 valueSizeTotal += data.size(); 0247 0248 const auto parsedKey = parse(key); 0249 0250 if (isMainDb) { 0251 Sink::EntityBuffer buffer(const_cast<const char *>(data.data()), data.size()); 0252 if (!buffer.isValid()) { 0253 state.printError("Read invalid buffer from disk: " + parsedKey); 0254 } else { 0255 const auto metadata = flatbuffers::GetRoot<Sink::Metadata>(buffer.metadataBuffer()); 0256 state.printLine("Key: " + parsedKey 0257 + " Operation: " + operationName(metadata->operation()) 0258 + " Replay: " + (metadata->replayToSource() ? "true" : "false") 0259 + ((metadata->modifiedProperties() && metadata->modifiedProperties()->size() != 0) ? (" [" + Sink::BufferUtils::fromVector(*metadata->modifiedProperties()).join(", ")) + "]": "") 0260 + " Value size: " + QString::number(data.size()) 0261 ); 0262 } 0263 } else { 0264 state.printLine("Key: " + parsedKey + "\tValue: " + parse(data)); 0265 } 0266 return true; 0267 }, 0268 [&](const Sink::Storage::DataStore::Error &e) { 0269 state.printError(e.message); 0270 }, 0271 findSubstringKeys); 0272 0273 state.printLine("Found " + QString::number(count) + " entries"); 0274 state.printLine("Keys take up " + QString::number(keySizeTotal) + " bytes => " + QString::number(keySizeTotal/1024) + " kb"); 0275 state.printLine("Values take up " + QString::number(valueSizeTotal) + " bytes => " + QString::number(valueSizeTotal/1024) + " kb"); 0276 return false; 0277 } 0278 0279 Syntax::List syntax() 0280 { 0281 Syntax state("inspect", QObject::tr("Inspect database for the resource requested"), 0282 &SinkInspect::inspect, Syntax::NotInteractive); 0283 0284 state.addPositionalArgument({"resource", "Resource to inspect."}); 0285 state.addParameter("db", {"database", "Which database to inspect"}); 0286 state.addParameter("filter", {"id", "A specific id to filter the results by (currently not working)"}); 0287 state.addParameter("validaterids", {"type", "Validate remote Ids of the given type"}); 0288 state.addParameter("fulltext", {"id", "If 'id' is not given, count the number of fulltext documents. Else, print the terms of the document with the given id"}); 0289 state.addParameter("history", {"id", "Print all revisions for the entity with the given id"}); 0290 0291 state.completer = &SinkshUtils::resourceCompleter; 0292 0293 return Syntax::List() << state; 0294 } 0295 0296 REGISTER_SYNTAX(SinkInspect) 0297 0298 }