File indexing completed on 2025-01-05 04:46:55
0001 /* 0002 SPDX-FileCopyrightText: 2017 Daniel Vrátil <dvratil@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "collectiontreecache.h" 0008 #include "akonadiserver_debug.h" 0009 #include "commandcontext.h" 0010 #include "selectquerybuilder.h" 0011 0012 #include "private/scope_p.h" 0013 0014 #include <QStack> 0015 0016 #include <algorithm> 0017 #include <iterator> 0018 #include <map> 0019 #include <tuple> 0020 0021 using namespace Akonadi::Server; 0022 0023 namespace 0024 { 0025 enum Column { 0026 IdColumn, 0027 ParentIdColumn, 0028 RIDColumn, 0029 DisplayPrefColumn, 0030 SyncPrefColumn, 0031 IndexPrefColumn, 0032 EnabledColumn, 0033 ReferencedColumn, 0034 ResourceNameColumn, 0035 }; 0036 } 0037 0038 CollectionTreeCache::Node::Node() 0039 : parent(nullptr) 0040 , lruCounter(0) 0041 , id(-1) 0042 { 0043 } 0044 0045 CollectionTreeCache::Node::Node(const Collection &col) 0046 : parent(nullptr) 0047 , id(col.id()) 0048 , collection(col) 0049 0050 { 0051 } 0052 0053 CollectionTreeCache::Node::~Node() 0054 { 0055 qDeleteAll(children); 0056 } 0057 0058 void CollectionTreeCache::Node::appendChild(Node *child) 0059 { 0060 child->parent = this; 0061 children.push_back(child); 0062 } 0063 0064 void CollectionTreeCache::Node::removeChild(Node *child) 0065 { 0066 child->parent = nullptr; 0067 children.removeOne(child); 0068 } 0069 0070 CollectionTreeCache::CollectionTreeCache() 0071 : AkThread(QStringLiteral("CollectionTreeCache")) 0072 { 0073 } 0074 0075 CollectionTreeCache::~CollectionTreeCache() 0076 { 0077 quitThread(); 0078 } 0079 0080 void CollectionTreeCache::init() 0081 { 0082 AkThread::init(); 0083 0084 QWriteLocker locker(&mLock); 0085 0086 mRoot = new Node; 0087 mRoot->id = 0; 0088 mRoot->parent = nullptr; 0089 mNodeLookup.insert(0, mRoot); 0090 0091 SelectQueryBuilder<Collection> qb; 0092 qb.addSortColumn(Collection::idFullColumnName(), Query::Ascending); 0093 0094 if (!qb.exec()) { 0095 qCCritical(AKONADISERVER_LOG) << "Failed to initialize Collection tree cache!"; 0096 return; 0097 } 0098 0099 // std's reverse iterators makes processing pendingNodes much easier. 0100 std::multimap<qint64 /* parentID */, Node *> pendingNodes; 0101 const auto collections = qb.result(); 0102 for (const auto &col : collections) { 0103 auto parent = mNodeLookup.value(col.parentId(), nullptr); 0104 0105 auto node = new Node(col); 0106 0107 if (parent) { 0108 parent->appendChild(node); 0109 mNodeLookup.insert(node->id, node); 0110 } else { 0111 pendingNodes.insert({col.parentId(), node}); 0112 } 0113 } 0114 0115 if (!pendingNodes.empty()) { 0116 int inserts = 0; 0117 0118 // Thanks to the SQL results being ordered by ID we already inserted most 0119 // of the nodes in the loop above and here we only handle the rare cases 0120 // when child has ID lower than its parent, i.e. moved collections. 0121 // 0122 // In theory we may need multiple passes to insert all nodes, but we can 0123 // optimize by iterating the ordered map in reverse order. This way we find 0124 // the parents with higher IDs first and their children later, thus needing 0125 // fewer passes to process all the nodes. 0126 auto it = pendingNodes.rbegin(); 0127 while (!pendingNodes.empty()) { 0128 auto parent = mNodeLookup.value(it->first, nullptr); 0129 if (!parent) { 0130 // Parent of this node is still somewhere in pendingNodes, let's skip 0131 // this one for now and try again in the next iteration 0132 ++it; 0133 } else { 0134 auto node = it->second; 0135 parent->appendChild(node); 0136 mNodeLookup.insert(node->id, node); 0137 pendingNodes.erase((++it).base()); 0138 ++inserts; 0139 } 0140 0141 if (it == pendingNodes.rend()) { 0142 if (Q_UNLIKELY(inserts == 0)) { 0143 // This means we iterated through the entire pendingNodes but did 0144 // not manage to insert any collection to the node tree. That 0145 // means that there is an unreferenced collection in the database 0146 // that points to an invalid parent (or has a parent which points 0147 // to an invalid parent etc.). This should not happen 0148 // anymore with DB constraints, but who knows... 0149 qCWarning(AKONADISERVER_LOG) << "Found unreferenced Collections!"; 0150 auto unref = pendingNodes.begin(); 0151 while (unref != pendingNodes.end()) { 0152 qCWarning(AKONADISERVER_LOG) << "\tCollection" << unref->second->id << "references an invalid parent" << it->first; 0153 // Remove the unreferenced collection from the map 0154 delete unref->second; 0155 unref = pendingNodes.erase(unref); 0156 } 0157 qCWarning(AKONADISERVER_LOG) << "Please run \"akonadictl fsck\" to correct the inconsistencies!"; 0158 // pendingNodes should be empty now so break the loop here 0159 break; 0160 } 0161 0162 it = pendingNodes.rbegin(); 0163 inserts = 0; 0164 } 0165 } 0166 } 0167 0168 Q_ASSERT(pendingNodes.empty()); 0169 Q_ASSERT(mNodeLookup.size() == collections.count() + 1 /* root */); 0170 // Now we should have a complete tree built, yay! 0171 } 0172 0173 void CollectionTreeCache::quit() 0174 { 0175 delete mRoot; 0176 0177 AkThread::quit(); 0178 } 0179 0180 void CollectionTreeCache::collectionAdded(const Collection &col) 0181 { 0182 QWriteLocker locker(&mLock); 0183 0184 auto parent = mNodeLookup.value(col.parentId(), nullptr); 0185 if (!parent) { 0186 qCWarning(AKONADISERVER_LOG) << "Received a new collection (" << col.id() << ") with unknown parent (" << col.parentId() << ")"; 0187 return; 0188 } 0189 0190 auto node = new Node(col); 0191 parent->appendChild(node); 0192 mNodeLookup.insert(node->id, node); 0193 } 0194 0195 void CollectionTreeCache::collectionChanged(const Collection &col) 0196 { 0197 QWriteLocker locker(&mLock); 0198 0199 auto node = mNodeLookup.value(col.id(), nullptr); 0200 if (!node) { 0201 qCWarning(AKONADISERVER_LOG) << "Received an unknown changed collection (" << col.id() << ")"; 0202 return; 0203 } 0204 0205 // Only update non-expired nodes 0206 if (node->collection.isValid()) { 0207 node->collection = col; 0208 } 0209 } 0210 0211 void CollectionTreeCache::collectionMoved(const Collection &col) 0212 { 0213 QWriteLocker locker(&mLock); 0214 0215 auto node = mNodeLookup.value(col.id(), nullptr); 0216 if (!node) { 0217 qCWarning(AKONADISERVER_LOG) << "Received an unknown moved collection (" << col.id() << ")"; 0218 return; 0219 } 0220 auto oldParent = node->parent; 0221 0222 auto newParent = mNodeLookup.value(col.parentId(), nullptr); 0223 if (!newParent) { 0224 qCWarning(AKONADISERVER_LOG) << "Received a moved collection (" << col.id() << ") with an unknown move destination (" << col.parentId() << ")"; 0225 return; 0226 } 0227 0228 oldParent->removeChild(node); 0229 newParent->appendChild(node); 0230 if (node->collection.isValid()) { 0231 node->collection = col; 0232 } 0233 } 0234 0235 void CollectionTreeCache::collectionRemoved(const Collection &col) 0236 { 0237 QWriteLocker locker(&mLock); 0238 0239 auto node = mNodeLookup.value(col.id(), nullptr); 0240 if (!node) { 0241 qCWarning(AKONADISERVER_LOG) << "Received unknown removed collection (" << col.id() << ")"; 0242 return; 0243 } 0244 0245 auto parent = node->parent; 0246 parent->removeChild(node); 0247 mNodeLookup.remove(node->id); 0248 delete node; 0249 } 0250 0251 CollectionTreeCache::Node *CollectionTreeCache::findNode(const QString &rid, const QString &resource) const 0252 { 0253 QReadLocker locker(&mLock); 0254 0255 // Find a subtree that belongs to the respective resource 0256 auto root = std::find_if(mRoot->children.cbegin(), mRoot->children.cend(), [resource](Node *node) { 0257 // resource().name() may seem expensive, but really 0258 // there are only few resources and they are all cached 0259 // in memory. 0260 return node->collection.resource().name() == resource; 0261 }); 0262 if (root == mRoot->children.cend()) { 0263 return nullptr; 0264 } 0265 0266 return findNode((*root), [rid](Node *node) { 0267 return node->collection.remoteId() == rid; 0268 }); 0269 } 0270 0271 QList<Collection> CollectionTreeCache::retrieveCollections(CollectionTreeCache::Node *root, int depth, int ancestorDepth) const 0272 { 0273 QReadLocker locker(&mLock); 0274 0275 QList<Node *> nodes; 0276 // Get all ancestors for root 0277 Node *parent = root->parent; 0278 for (int i = 0; i < ancestorDepth && parent != nullptr; ++i) { 0279 nodes.push_back(parent); 0280 parent = parent->parent; 0281 } 0282 0283 struct StackTuple { 0284 Node *node; 0285 int depth; 0286 }; 0287 QStack<StackTuple> stack; 0288 stack.push({root, 0}); 0289 while (!stack.isEmpty()) { 0290 auto c = stack.pop(); 0291 if (c.depth > depth) { 0292 break; 0293 } 0294 0295 if (c.node->id > 0) { // skip root 0296 nodes.push_back(c.node); 0297 } 0298 0299 for (auto child : std::as_const(c.node->children)) { 0300 stack.push({child, c.depth + 1}); 0301 } 0302 } 0303 0304 QList<Collection> cols; 0305 QList<Node *> missing; 0306 for (auto node : nodes) { 0307 if (node->collection.isValid()) { 0308 cols.push_back(node->collection); 0309 } else { 0310 missing.push_back(node); 0311 } 0312 } 0313 0314 if (!missing.isEmpty()) { 0315 // TODO: Check if no-one else is currently retrieving the same collections 0316 SelectQueryBuilder<Collection> qb; 0317 Query::Condition cond(Query::Or); 0318 for (auto node : std::as_const(missing)) { 0319 cond.addValueCondition(Collection::idFullColumnName(), Query::Equals, node->id); 0320 } 0321 qb.addCondition(cond); 0322 if (!qb.exec()) { 0323 qCWarning(AKONADISERVER_LOG) << "Failed to retrieve collections from the database"; 0324 return {}; 0325 } 0326 0327 const auto results = qb.result(); 0328 if (results.size() != missing.size()) { 0329 qCWarning(AKONADISERVER_LOG) << "Could not obtain all missing collections! Node tree refers to a non-existent collection"; 0330 } 0331 0332 cols += results; 0333 0334 // Relock for write 0335 // TODO: Needs a better lock-upgrade mechanism 0336 locker.unlock(); 0337 QWriteLocker wLocker(&mLock); 0338 for (auto node : std::as_const(missing)) { 0339 auto it = std::find_if(results.cbegin(), results.cend(), [node](const Collection &col) { 0340 return node->id == col.id(); 0341 }); 0342 if (Q_UNLIKELY(it == results.cend())) { 0343 continue; 0344 } 0345 0346 node->collection = *it; 0347 } 0348 } 0349 0350 return cols; 0351 } 0352 0353 QList<Collection> 0354 CollectionTreeCache::retrieveCollections(const Scope &scope, int depth, int ancestorDepth, const QString &resource, CommandContext *context) const 0355 { 0356 if (scope.isEmpty()) { 0357 return retrieveCollections(mRoot, depth, ancestorDepth); 0358 } else if (scope.scope() == Scope::Rid) { 0359 // Caller must ensure! 0360 Q_ASSERT(!resource.isEmpty() || (context && context->resource().isValid())); 0361 0362 Node *node = nullptr; 0363 if (!resource.isEmpty()) { 0364 node = findNode(scope.rid(), resource); 0365 } else if (context && context->resource().isValid()) { 0366 node = findNode(scope.rid(), context->resource().name()); 0367 } else { 0368 return {}; 0369 } 0370 0371 if (Q_LIKELY(node)) { 0372 return retrieveCollections(node, depth, ancestorDepth); 0373 } 0374 } else if (scope.scope() == Scope::Uid) { 0375 Node *node = mNodeLookup.value(scope.uid()); 0376 if (Q_LIKELY(node)) { 0377 return retrieveCollections(node, depth, ancestorDepth); 0378 } 0379 } 0380 0381 return {}; 0382 } 0383 0384 #include "moc_collectiontreecache.cpp"