File indexing completed on 2025-01-05 04:46:26
0001 /* 0002 SPDX-FileCopyrightText: 2007 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "resourcescheduler_p.h" 0008 0009 #include "recursivemover_p.h" 0010 #include <QDBusConnection> 0011 0012 #include "akonadiagentbase_debug.h" 0013 #include "private/instance_p.h" 0014 #include <KLocalizedString> 0015 0016 #include <QDBusInterface> 0017 #include <QTimer> 0018 0019 using namespace Akonadi; 0020 using namespace std::chrono_literals; 0021 qint64 ResourceScheduler::Task::latestSerial = 0; 0022 static QDBusAbstractInterface *s_resourcetracker = nullptr; 0023 0024 /// @cond PRIVATE 0025 0026 ResourceScheduler::ResourceScheduler(QObject *parent) 0027 : QObject(parent) 0028 { 0029 } 0030 0031 void ResourceScheduler::scheduleFullSync() 0032 { 0033 Task t; 0034 t.type = SyncAll; 0035 TaskList &queue = queueForTaskType(t.type); 0036 if (queue.contains(t) || mCurrentTask == t) { 0037 return; 0038 } 0039 queue << t; 0040 signalTaskToTracker(t, "SyncAll"); 0041 scheduleNext(); 0042 } 0043 0044 void ResourceScheduler::scheduleCollectionTreeSync() 0045 { 0046 Task t; 0047 t.type = SyncCollectionTree; 0048 TaskList &queue = queueForTaskType(t.type); 0049 if (queue.contains(t) || mCurrentTask == t) { 0050 return; 0051 } 0052 queue << t; 0053 signalTaskToTracker(t, "SyncCollectionTree"); 0054 scheduleNext(); 0055 } 0056 0057 void ResourceScheduler::scheduleTagSync() 0058 { 0059 Task t; 0060 t.type = SyncTags; 0061 TaskList &queue = queueForTaskType(t.type); 0062 if (queue.contains(t) || mCurrentTask == t) { 0063 return; 0064 } 0065 queue << t; 0066 signalTaskToTracker(t, "SyncTags"); 0067 scheduleNext(); 0068 } 0069 0070 void ResourceScheduler::scheduleRelationSync() 0071 { 0072 Task t; 0073 t.type = SyncRelations; 0074 TaskList &queue = queueForTaskType(t.type); 0075 if (queue.contains(t) || mCurrentTask == t) { 0076 return; 0077 } 0078 queue << t; 0079 signalTaskToTracker(t, "SyncRelations"); 0080 scheduleNext(); 0081 } 0082 0083 void ResourceScheduler::scheduleSync(const Collection &col) 0084 { 0085 Task t; 0086 t.type = SyncCollection; 0087 t.collection = col; 0088 TaskList &queue = queueForTaskType(t.type); 0089 if (queue.contains(t) || mCurrentTask == t) { 0090 return; 0091 } 0092 queue << t; 0093 signalTaskToTracker(t, "SyncCollection", QString::number(col.id())); 0094 scheduleNext(); 0095 } 0096 0097 void ResourceScheduler::scheduleAttributesSync(const Collection &collection) 0098 { 0099 Task t; 0100 t.type = SyncCollectionAttributes; 0101 t.collection = collection; 0102 0103 TaskList &queue = queueForTaskType(t.type); 0104 if (queue.contains(t) || mCurrentTask == t) { 0105 return; 0106 } 0107 queue << t; 0108 signalTaskToTracker(t, "SyncCollectionAttributes", QString::number(collection.id())); 0109 scheduleNext(); 0110 } 0111 0112 void ResourceScheduler::scheduleItemFetch(const Akonadi::Item &item, const QSet<QByteArray> &parts, const QList<QDBusMessage> &msgs, qint64 parentId) 0113 0114 { 0115 Task t; 0116 t.type = FetchItem; 0117 t.items << item; 0118 t.itemParts = parts; 0119 t.dbusMsgs = msgs; 0120 t.argument = parentId; 0121 0122 TaskList &queue = queueForTaskType(t.type); 0123 queue << t; 0124 0125 signalTaskToTracker(t, "FetchItem", QString::number(item.id())); 0126 scheduleNext(); 0127 } 0128 0129 void ResourceScheduler::scheduleItemsFetch(const Item::List &items, const QSet<QByteArray> &parts, const QDBusMessage &msg) 0130 { 0131 Task t; 0132 t.type = FetchItems; 0133 t.items = items; 0134 t.itemParts = parts; 0135 0136 // if the current task does already fetch the requested item, break here but 0137 // keep the dbus message, so we can send the reply later on 0138 if (mCurrentTask == t) { 0139 mCurrentTask.dbusMsgs << msg; 0140 return; 0141 } 0142 0143 // If this task is already in the queue, merge with it. 0144 TaskList &queue = queueForTaskType(t.type); 0145 const int idx = queue.indexOf(t); 0146 if (idx != -1) { 0147 queue[idx].dbusMsgs << msg; 0148 return; 0149 } 0150 0151 t.dbusMsgs << msg; 0152 queue << t; 0153 0154 QStringList ids; 0155 ids.reserve(items.size()); 0156 for (const auto &item : items) { 0157 ids.push_back(QString::number(item.id())); 0158 } 0159 signalTaskToTracker(t, "FetchItems", ids.join(QLatin1StringView(", "))); 0160 scheduleNext(); 0161 } 0162 0163 void ResourceScheduler::scheduleResourceCollectionDeletion() 0164 { 0165 Task t; 0166 t.type = DeleteResourceCollection; 0167 TaskList &queue = queueForTaskType(t.type); 0168 if (queue.contains(t) || mCurrentTask == t) { 0169 return; 0170 } 0171 queue << t; 0172 signalTaskToTracker(t, "DeleteResourceCollection"); 0173 scheduleNext(); 0174 } 0175 0176 void ResourceScheduler::scheduleCacheInvalidation(const Collection &collection) 0177 { 0178 Task t; 0179 t.type = InvalideCacheForCollection; 0180 t.collection = collection; 0181 TaskList &queue = queueForTaskType(t.type); 0182 if (queue.contains(t) || mCurrentTask == t) { 0183 return; 0184 } 0185 queue << t; 0186 signalTaskToTracker(t, "InvalideCacheForCollection", QString::number(collection.id())); 0187 scheduleNext(); 0188 } 0189 0190 void ResourceScheduler::scheduleChangeReplay() 0191 { 0192 Task t; 0193 t.type = ChangeReplay; 0194 TaskList &queue = queueForTaskType(t.type); 0195 // see ResourceBase::changeProcessed() for why we do not check for mCurrentTask == t here like in the other tasks 0196 if (queue.contains(t)) { 0197 return; 0198 } 0199 queue << t; 0200 signalTaskToTracker(t, "ChangeReplay"); 0201 scheduleNext(); 0202 } 0203 0204 void ResourceScheduler::scheduleMoveReplay(const Collection &movedCollection, RecursiveMover *mover) 0205 { 0206 Task t; 0207 t.type = RecursiveMoveReplay; 0208 t.collection = movedCollection; 0209 t.argument = QVariant::fromValue(mover); 0210 TaskList &queue = queueForTaskType(t.type); 0211 0212 if (queue.contains(t) || mCurrentTask == t) { 0213 return; 0214 } 0215 0216 queue << t; 0217 signalTaskToTracker(t, "RecursiveMoveReplay", QString::number(t.collection.id())); 0218 scheduleNext(); 0219 } 0220 0221 void Akonadi::ResourceScheduler::scheduleFullSyncCompletion() 0222 { 0223 Task t; 0224 t.type = SyncAllDone; 0225 TaskList &queue = queueForTaskType(t.type); 0226 // no compression here, all this does is emitting a D-Bus signal anyway, and compression can trigger races on the receiver side with the signal being lost 0227 queue << t; 0228 signalTaskToTracker(t, "SyncAllDone"); 0229 scheduleNext(); 0230 } 0231 0232 void Akonadi::ResourceScheduler::scheduleCollectionTreeSyncCompletion() 0233 { 0234 Task t; 0235 t.type = SyncCollectionTreeDone; 0236 TaskList &queue = queueForTaskType(t.type); 0237 // no compression here, all this does is emitting a D-Bus signal anyway, and compression can trigger races on the receiver side with the signal being lost 0238 queue << t; 0239 signalTaskToTracker(t, "SyncCollectionTreeDone"); 0240 scheduleNext(); 0241 } 0242 0243 void Akonadi::ResourceScheduler::scheduleCustomTask(QObject *receiver, 0244 const char *methodName, 0245 const QVariant &argument, 0246 ResourceBase::SchedulePriority priority) 0247 { 0248 Task t; 0249 t.type = Custom; 0250 t.receiver = receiver; 0251 t.methodName = methodName; 0252 t.argument = argument; 0253 QueueType queueType = GenericTaskQueue; 0254 if (priority == ResourceBase::AfterChangeReplay) { 0255 queueType = AfterChangeReplayQueue; 0256 } else if (priority == ResourceBase::Prepend) { 0257 queueType = PrependTaskQueue; 0258 } 0259 TaskList &queue = mTaskList[queueType]; 0260 0261 if (queue.contains(t)) { 0262 return; 0263 } 0264 0265 switch (priority) { 0266 case ResourceBase::Prepend: 0267 queue.prepend(t); 0268 break; 0269 default: 0270 queue.append(t); 0271 break; 0272 } 0273 0274 signalTaskToTracker(t, "Custom-" + t.methodName); 0275 scheduleNext(); 0276 } 0277 0278 void ResourceScheduler::taskDone() 0279 { 0280 if (isEmpty()) { 0281 Q_EMIT status(AgentBase::Idle, i18nc("@info:status Application ready for work", "Ready")); 0282 } 0283 0284 if (s_resourcetracker) { 0285 const QList<QVariant> argumentList = {QString::number(mCurrentTask.serial), QString()}; 0286 s_resourcetracker->asyncCallWithArgumentList(QStringLiteral("jobEnded"), argumentList); 0287 } 0288 0289 mCurrentTask = Task(); 0290 mCurrentTasksQueue = -1; 0291 scheduleNext(); 0292 } 0293 0294 void ResourceScheduler::itemFetchDone(const QString &msg) 0295 { 0296 Q_ASSERT(mCurrentTask.type == FetchItem); 0297 0298 TaskList &queue = queueForTaskType(mCurrentTask.type); 0299 0300 const qint64 parentId = mCurrentTask.argument.toLongLong(); 0301 // msg is empty, there was no error 0302 if (msg.isEmpty() && !queue.isEmpty()) { 0303 Task &nextTask = queue[0]; 0304 // If the next task is FetchItem too... 0305 if (nextTask.type != mCurrentTask.type || nextTask.argument.toLongLong() != parentId) { 0306 // If the next task is not FetchItem or the next FetchItem task has 0307 // different parentId then this was the last task in the series, so 0308 // send the DBus replies. 0309 mCurrentTask.sendDBusReplies(msg); 0310 } 0311 } else { 0312 // msg was not empty, there was an error. 0313 // remove all subsequent FetchItem tasks with the same parentId 0314 auto iter = queue.begin(); 0315 while (iter != queue.end()) { 0316 if (iter->type != mCurrentTask.type || iter->argument.toLongLong() == parentId) { 0317 iter = queue.erase(iter); 0318 continue; 0319 } else { 0320 break; 0321 } 0322 } 0323 0324 // ... and send DBus reply with the error message 0325 mCurrentTask.sendDBusReplies(msg); 0326 } 0327 0328 taskDone(); 0329 } 0330 0331 void ResourceScheduler::deferTask() 0332 { 0333 if (mCurrentTask.type == Invalid) { 0334 return; 0335 } 0336 0337 if (s_resourcetracker) { 0338 const QList<QVariant> argumentList = {QString::number(mCurrentTask.serial), QString()}; 0339 s_resourcetracker->asyncCallWithArgumentList(QStringLiteral("jobEnded"), argumentList); 0340 } 0341 0342 Task t = mCurrentTask; 0343 mCurrentTask = Task(); 0344 0345 Q_ASSERT(mCurrentTasksQueue >= 0 && mCurrentTasksQueue < NQueueCount); 0346 mTaskList[mCurrentTasksQueue].prepend(t); 0347 mCurrentTasksQueue = -1; 0348 0349 signalTaskToTracker(t, "DeferedTask"); 0350 0351 scheduleNext(); 0352 } 0353 0354 bool ResourceScheduler::isEmpty() 0355 { 0356 for (int i = 0; i < NQueueCount; ++i) { 0357 if (!mTaskList[i].isEmpty()) { 0358 return false; 0359 } 0360 } 0361 return true; 0362 } 0363 0364 void ResourceScheduler::scheduleNext() 0365 { 0366 if (mCurrentTask.type != Invalid || isEmpty() || !mOnline) { 0367 return; 0368 } 0369 QTimer::singleShot(0s, this, &ResourceScheduler::executeNext); 0370 } 0371 0372 void ResourceScheduler::executeNext() 0373 { 0374 if (mCurrentTask.type != Invalid || isEmpty()) { 0375 return; 0376 } 0377 0378 for (int i = 0; i < NQueueCount; ++i) { 0379 if (!mTaskList[i].isEmpty()) { 0380 mCurrentTask = mTaskList[i].takeFirst(); 0381 mCurrentTasksQueue = i; 0382 break; 0383 } 0384 } 0385 0386 if (s_resourcetracker) { 0387 const QList<QVariant> argumentList = {QString::number(mCurrentTask.serial)}; 0388 s_resourcetracker->asyncCallWithArgumentList(QStringLiteral("jobStarted"), argumentList); 0389 } 0390 0391 switch (mCurrentTask.type) { 0392 case SyncAll: 0393 Q_EMIT executeFullSync(); 0394 break; 0395 case SyncCollectionTree: 0396 Q_EMIT executeCollectionTreeSync(); 0397 break; 0398 case SyncCollection: 0399 Q_EMIT executeCollectionSync(mCurrentTask.collection); 0400 break; 0401 case SyncCollectionAttributes: 0402 Q_EMIT executeCollectionAttributesSync(mCurrentTask.collection); 0403 break; 0404 case SyncTags: 0405 Q_EMIT executeTagSync(); 0406 break; 0407 case FetchItem: 0408 Q_EMIT executeItemFetch(mCurrentTask.items.at(0), mCurrentTask.itemParts); 0409 break; 0410 case FetchItems: 0411 Q_EMIT executeItemsFetch(mCurrentTask.items, mCurrentTask.itemParts); 0412 break; 0413 case DeleteResourceCollection: 0414 Q_EMIT executeResourceCollectionDeletion(); 0415 break; 0416 case InvalideCacheForCollection: 0417 Q_EMIT executeCacheInvalidation(mCurrentTask.collection); 0418 break; 0419 case ChangeReplay: 0420 Q_EMIT executeChangeReplay(); 0421 break; 0422 case RecursiveMoveReplay: 0423 Q_EMIT executeRecursiveMoveReplay(mCurrentTask.argument.value<RecursiveMover *>()); 0424 break; 0425 case SyncAllDone: 0426 Q_EMIT fullSyncComplete(); 0427 break; 0428 case SyncCollectionTreeDone: 0429 Q_EMIT collectionTreeSyncComplete(); 0430 break; 0431 case SyncRelations: 0432 Q_EMIT executeRelationSync(); 0433 break; 0434 case Custom: { 0435 const QByteArray methodSig = mCurrentTask.methodName + QByteArray("(QVariant)"); 0436 const bool hasSlotWithVariant = mCurrentTask.receiver->metaObject()->indexOfMethod(methodSig.constData()) != -1; 0437 bool success = false; 0438 if (hasSlotWithVariant) { 0439 success = QMetaObject::invokeMethod(mCurrentTask.receiver, mCurrentTask.methodName.constData(), Q_ARG(QVariant, mCurrentTask.argument)); 0440 Q_ASSERT_X(success || !mCurrentTask.argument.isValid(), 0441 "ResourceScheduler::executeNext", 0442 "Valid argument was provided but the method wasn't found"); 0443 } 0444 if (!success) { 0445 success = QMetaObject::invokeMethod(mCurrentTask.receiver, mCurrentTask.methodName.constData()); 0446 } 0447 0448 if (!success) { 0449 qCCritical(AKONADIAGENTBASE_LOG) << "Could not invoke slot" << mCurrentTask.methodName << "on" << mCurrentTask.receiver << "with argument" 0450 << mCurrentTask.argument; 0451 } 0452 break; 0453 } 0454 default: { 0455 qCCritical(AKONADIAGENTBASE_LOG) << "Unhandled task type" << mCurrentTask.type; 0456 dump(); 0457 Q_ASSERT(false); 0458 } 0459 } 0460 } 0461 0462 ResourceScheduler::Task ResourceScheduler::currentTask() const 0463 { 0464 return mCurrentTask; 0465 } 0466 0467 ResourceScheduler::Task &ResourceScheduler::currentTask() 0468 { 0469 return mCurrentTask; 0470 } 0471 0472 void ResourceScheduler::setOnline(bool state) 0473 { 0474 if (mOnline == state) { 0475 return; 0476 } 0477 mOnline = state; 0478 if (mOnline) { 0479 scheduleNext(); 0480 } else { 0481 if (mCurrentTask.type != Invalid) { 0482 // abort running task 0483 queueForTaskType(mCurrentTask.type).prepend(mCurrentTask); 0484 mCurrentTask = Task(); 0485 mCurrentTasksQueue = -1; 0486 } 0487 // abort pending synchronous tasks, might take longer until the resource goes online again 0488 TaskList &itemFetchQueue = queueForTaskType(FetchItem); 0489 qint64 parentId = -1; 0490 Task lastTask; 0491 for (QList<Task>::iterator it = itemFetchQueue.begin(); it != itemFetchQueue.end();) { 0492 if ((*it).type == FetchItem) { 0493 qint64 idx = it->argument.toLongLong(); 0494 if (parentId == -1) { 0495 parentId = idx; 0496 } 0497 if (idx != parentId) { 0498 // Only emit the DBus reply once we reach the last taskwith the 0499 // same "idx" 0500 lastTask.sendDBusReplies(i18nc("@info", "Job canceled.")); 0501 parentId = idx; 0502 } 0503 lastTask = (*it); 0504 it = itemFetchQueue.erase(it); 0505 if (s_resourcetracker) { 0506 const QList<QVariant> argumentList = {QString::number(mCurrentTask.serial), i18nc("@info", "Job canceled.")}; 0507 s_resourcetracker->asyncCallWithArgumentList(QStringLiteral("jobEnded"), argumentList); 0508 } 0509 } else { 0510 ++it; 0511 } 0512 } 0513 } 0514 } 0515 0516 void ResourceScheduler::signalTaskToTracker(const Task &task, const QByteArray &taskType, const QString &debugString) 0517 { 0518 // if there's a job tracer running, tell it about the new job 0519 if (!s_resourcetracker) { 0520 const QString suffix = Akonadi::Instance::identifier().isEmpty() ? QString() : QLatin1Char('-') + Akonadi::Instance::identifier(); 0521 if (QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.akonadiconsole") + suffix)) { 0522 s_resourcetracker = new QDBusInterface(QLatin1StringView("org.kde.akonadiconsole") + suffix, 0523 QStringLiteral("/resourcesJobtracker"), 0524 QStringLiteral("org.freedesktop.Akonadi.JobTracker"), 0525 QDBusConnection::sessionBus(), 0526 nullptr); 0527 } 0528 } 0529 0530 if (s_resourcetracker) { 0531 const QList<QVariant> argumentList = QList<QVariant>() << static_cast<AgentBase *>(parent())->identifier() // "session" (in our case resource) 0532 << QString::number(task.serial) // "job" 0533 << QString() // "parent job" 0534 << QString::fromLatin1(taskType) // "job type" 0535 << debugString // "job debugging string" 0536 ; 0537 s_resourcetracker->asyncCallWithArgumentList(QStringLiteral("jobCreated"), argumentList); 0538 } 0539 } 0540 0541 void ResourceScheduler::collectionRemoved(const Akonadi::Collection &collection) 0542 { 0543 if (!collection.isValid()) { // should not happen, but you never know... 0544 return; 0545 } 0546 TaskList &queue = queueForTaskType(SyncCollection); 0547 for (QList<Task>::iterator it = queue.begin(); it != queue.end();) { 0548 if ((*it).type == SyncCollection && (*it).collection == collection) { 0549 it = queue.erase(it); 0550 qCDebug(AKONADIAGENTBASE_LOG) << " erasing"; 0551 } else { 0552 ++it; 0553 } 0554 } 0555 } 0556 0557 void ResourceScheduler::Task::sendDBusReplies(const QString &errorMsg) 0558 { 0559 for (const QDBusMessage &msg : std::as_const(dbusMsgs)) { 0560 qCDebug(AKONADIAGENTBASE_LOG) << "Sending dbus reply for method" << methodName << "with error" << errorMsg; 0561 QDBusMessage reply; 0562 if (!errorMsg.isEmpty()) { 0563 reply = msg.createErrorReply(QDBusError::Failed, errorMsg); 0564 } else if (msg.member() == QLatin1StringView("requestItemDelivery")) { 0565 reply = msg.createReply(); 0566 } else if (msg.member().isEmpty()) { 0567 continue; // unittest calls scheduleItemFetch with empty QDBusMessage 0568 } else { 0569 qCCritical(AKONADIAGENTBASE_LOG) << "ResourceScheduler: got unexpected method name :" << msg.member(); 0570 } 0571 QDBusConnection::sessionBus().send(reply); 0572 } 0573 } 0574 0575 ResourceScheduler::QueueType ResourceScheduler::queueTypeForTaskType(TaskType type) 0576 { 0577 switch (type) { 0578 case ChangeReplay: 0579 case RecursiveMoveReplay: 0580 return ChangeReplayQueue; 0581 case FetchItem: 0582 case FetchItems: 0583 case SyncCollectionAttributes: 0584 return UserActionQueue; 0585 default: 0586 return GenericTaskQueue; 0587 } 0588 } 0589 0590 ResourceScheduler::TaskList &ResourceScheduler::queueForTaskType(TaskType type) 0591 { 0592 const QueueType qt = queueTypeForTaskType(type); 0593 return mTaskList[qt]; 0594 } 0595 0596 void ResourceScheduler::dump() const 0597 { 0598 qCDebug(AKONADIAGENTBASE_LOG) << dumpToString(); 0599 } 0600 0601 QString ResourceScheduler::dumpToString() const 0602 { 0603 QString ret; 0604 QTextStream str(&ret); 0605 str << "ResourceScheduler: " << (mOnline ? "Online" : "Offline") << '\n'; 0606 str << " current task: " << mCurrentTask << '\n'; 0607 for (int i = 0; i < NQueueCount; ++i) { 0608 const TaskList &queue = mTaskList[i]; 0609 if (queue.isEmpty()) { 0610 str << " queue " << i << " is empty" << '\n'; 0611 } else { 0612 str << " queue " << i << " " << queue.size() << " tasks:\n"; 0613 const QList<Task>::const_iterator queueEnd(queue.constEnd()); 0614 for (QList<Task>::const_iterator it = queue.constBegin(); it != queueEnd; ++it) { 0615 str << " " << (*it) << '\n'; 0616 } 0617 } 0618 } 0619 str.flush(); 0620 return ret; 0621 } 0622 0623 void ResourceScheduler::clear() 0624 { 0625 qCDebug(AKONADIAGENTBASE_LOG) << "Clearing ResourceScheduler queues:"; 0626 for (int i = 0; i < NQueueCount; ++i) { 0627 TaskList &queue = mTaskList[i]; 0628 queue.clear(); 0629 } 0630 mCurrentTask = Task(); 0631 mCurrentTasksQueue = -1; 0632 } 0633 0634 void Akonadi::ResourceScheduler::cancelQueues() 0635 { 0636 for (int i = 0; i < NQueueCount; ++i) { 0637 TaskList &queue = mTaskList[i]; 0638 if (s_resourcetracker) { 0639 for (const Task &t : queue) { 0640 QList<QVariant> argumentList{QString::number(t.serial), QString()}; 0641 s_resourcetracker->asyncCallWithArgumentList(QStringLiteral("jobEnded"), argumentList); 0642 } 0643 } 0644 queue.clear(); 0645 } 0646 } 0647 0648 static const char s_taskTypes[][27] = {"Invalid (no task)", 0649 "SyncAll", 0650 "SyncCollectionTree", 0651 "SyncCollection", 0652 "SyncCollectionAttributes", 0653 "SyncTags", 0654 "FetchItem", 0655 "FetchItems", 0656 "ChangeReplay", 0657 "RecursiveMoveReplay", 0658 "DeleteResourceCollection", 0659 "InvalideCacheForCollection", 0660 "SyncAllDone", 0661 "SyncCollectionTreeDone", 0662 "SyncRelations", 0663 "Custom"}; 0664 0665 QTextStream &Akonadi::operator<<(QTextStream &d, const ResourceScheduler::Task &task) 0666 { 0667 d << task.serial << " " << s_taskTypes[task.type] << " "; 0668 if (task.type != ResourceScheduler::Invalid) { 0669 if (task.collection.isValid()) { 0670 d << "collection " << task.collection.id() << " "; 0671 } 0672 if (!task.items.isEmpty()) { 0673 QStringList ids; 0674 ids.reserve(task.items.size()); 0675 for (const auto &item : std::as_const(task.items)) { 0676 ids.push_back(QString::number(item.id())); 0677 } 0678 d << "items " << ids.join(QLatin1StringView(", ")) << " "; 0679 } 0680 if (!task.methodName.isEmpty()) { 0681 d << task.methodName << " " << task.argument.toString(); 0682 } 0683 } 0684 return d; 0685 } 0686 0687 QDebug Akonadi::operator<<(QDebug d, const ResourceScheduler::Task &task) 0688 { 0689 QString s; 0690 QTextStream str(&s); 0691 str << task; 0692 d << s; 0693 return d; 0694 } 0695 0696 /// @endcond 0697 0698 #include "moc_resourcescheduler_p.cpp"