File indexing completed on 2024-05-12 05:10:35
0001 /* 0002 SPDX-FileCopyrightText: 2013 Sérgio Martins <iamsergio@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0 0005 */ 0006 0007 #include "calendarjanitor.h" 0008 #include "collectionloader.h" 0009 0010 #include <Akonadi/CalendarUtils> 0011 0012 #include <KCalendarCore/Alarm> 0013 #include <KCalendarCore/Attachment> 0014 #include <KCalendarCore/Event> 0015 #include <KCalendarCore/Journal> 0016 #include <KCalendarCore/Todo> 0017 0018 #include <KLocalizedString> 0019 0020 #include <QCoreApplication> 0021 #include <QList> 0022 #include <QTextStream> 0023 0024 #define TEXT_WIDTH 75 0025 0026 static void print(const QString &message, bool newline = true) 0027 { 0028 QTextStream out(stdout); 0029 out << message; 0030 if (newline) { 0031 out << "\n"; 0032 } 0033 } 0034 0035 static void bailOut() 0036 { 0037 print(i18n("Bailing out. Fix your akonadi setup first. These kind of errors should not happen.")); 0038 qApp->exit(-1); 0039 } 0040 0041 static bool collectionIsReadOnly(const Akonadi::Collection &collection) 0042 { 0043 return !(collection.rights() & Akonadi::Collection::CanChangeItem) || !(collection.rights() & Akonadi::Collection::CanDeleteItem); 0044 } 0045 0046 static bool incidenceIsOld(const KCalendarCore::Incidence::Ptr &incidence) 0047 { 0048 if (incidence->recurs() || incidence->type() == KCalendarCore::Incidence::TypeJournal) { 0049 return false; 0050 } 0051 0052 QDateTime datetime = incidence->dtStart(); 0053 if (!datetime.isValid() && incidence->type() == KCalendarCore::Incidence::TypeTodo) { 0054 datetime = incidence->dateTime(KCalendarCore::Incidence::RoleEnd); 0055 } 0056 0057 return datetime.isValid() && datetime.daysTo(QDateTime::currentDateTime()) > 365; 0058 } 0059 0060 CalendarJanitor::CalendarJanitor(const Options &options, QObject *parent) 0061 : QObject(parent) 0062 , m_collectionLoader(new CollectionLoader(this)) 0063 , m_options(options) 0064 , m_currentSanityCheck(Options::CheckNone) 0065 , m_pendingModifications(0) 0066 , m_pendingDeletions(0) 0067 , m_strippingOldAlarms(false) 0068 , m_returnCode(0) 0069 { 0070 m_changer = new Akonadi::IncidenceChanger(this); 0071 m_changer->setShowDialogsOnError(false); 0072 connect(m_changer, &Akonadi::IncidenceChanger::modifyFinished, this, &CalendarJanitor::onModifyFinished); 0073 connect(m_changer, &Akonadi::IncidenceChanger::deleteFinished, this, &CalendarJanitor::onDeleteFinished); 0074 connect(m_collectionLoader, &CollectionLoader::loaded, this, &CalendarJanitor::onCollectionsFetched); 0075 } 0076 0077 void CalendarJanitor::start() 0078 { 0079 m_collectionLoader->load(); 0080 } 0081 0082 void CalendarJanitor::onCollectionsFetched(bool success) 0083 { 0084 if (!success) { 0085 print(i18n("Error while fetching collections")); 0086 Q_EMIT finished(false); 0087 qApp->exit(-1); 0088 return; 0089 } 0090 0091 const auto collections = m_collectionLoader->collections(); 0092 for (const Akonadi::Collection &collection : collections) { 0093 if (m_options.testCollection(collection.id())) { 0094 m_collectionsToProcess << collection; 0095 } 0096 } 0097 0098 if (m_collectionsToProcess.isEmpty()) { 0099 print(i18n("There are no collections to process.")); 0100 qApp->exit((-1)); 0101 return; 0102 } 0103 0104 // Load all items: 0105 m_calendar = Akonadi::FetchJobCalendar::Ptr(new Akonadi::FetchJobCalendar()); 0106 connect(m_calendar.data(), &Akonadi::FetchJobCalendar::loadFinished, this, &CalendarJanitor::onItemsFetched); 0107 } 0108 0109 void CalendarJanitor::onItemsFetched(bool success, const QString &errorMessage) 0110 { 0111 if (!success) { 0112 print(errorMessage); 0113 Q_EMIT finished(false); 0114 qApp->exit(-1); 0115 return; 0116 } 0117 0118 // Start processing collections 0119 processNextCollection(); 0120 } 0121 0122 void CalendarJanitor::onModifyFinished(int changeId, const Akonadi::Item &item, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorMessage) 0123 { 0124 Q_UNUSED(changeId) 0125 if (resultCode != Akonadi::IncidenceChanger::ResultCodeSuccess) { 0126 print(i18n("Error while modifying incidence: %1", errorMessage)); 0127 bailOut(); 0128 return; 0129 } 0130 if (!m_options.stripOldAlarms()) { 0131 print(i18n("Fixed item %1", item.id())); 0132 } 0133 0134 m_pendingModifications--; 0135 if (m_pendingModifications == 0) { 0136 runNextTest(); 0137 } 0138 } 0139 0140 void CalendarJanitor::onDeleteFinished(int changeId, 0141 const QList<Akonadi::Item::Id> &items, 0142 Akonadi::IncidenceChanger::ResultCode resultCode, 0143 const QString &errorMessage) 0144 { 0145 Q_UNUSED(changeId) 0146 if (resultCode != Akonadi::IncidenceChanger::ResultCodeSuccess) { 0147 print(i18n("Error while deleting incidence: %1", errorMessage)); 0148 bailOut(); 0149 return; 0150 } 0151 print(i18n("Deleted item %1", items.first())); 0152 m_pendingDeletions--; 0153 if (m_pendingDeletions == 0) { 0154 runNextTest(); 0155 } 0156 } 0157 0158 void CalendarJanitor::processNextCollection() 0159 { 0160 m_itemsToProcess.clear(); 0161 m_currentSanityCheck = Options::CheckNone; 0162 m_strippingOldAlarms = false; 0163 0164 if (m_collectionsToProcess.isEmpty()) { 0165 print(QLatin1Char('\n') + QString().leftJustified(TEXT_WIDTH, QLatin1Char('*'))); 0166 Q_EMIT finished(true); 0167 qApp->exit(m_returnCode); 0168 return; 0169 } 0170 0171 m_currentCollection = m_collectionsToProcess.takeFirst(); 0172 print(QLatin1Char('\n') + QString().leftJustified(TEXT_WIDTH, QLatin1Char('*'))); 0173 print(i18n("Processing collection %1 (id=%2)...", m_currentCollection.displayName(), m_currentCollection.id())); 0174 0175 if (collectionIsReadOnly(m_currentCollection)) { 0176 if (m_options.action() == Options::ActionScanAndFix) { 0177 print(i18n("Collection is read only, disabling fix mode.")); 0178 } else if (m_options.stripOldAlarms()) { 0179 print(i18n("Collection is read only, skipping it.")); 0180 processNextCollection(); 0181 return; 0182 } 0183 } 0184 0185 m_itemsToProcess = m_calendar->items(m_currentCollection.id()); 0186 if (m_itemsToProcess.isEmpty()) { 0187 print(i18n("Collection is empty, ignoring it.")); 0188 processNextCollection(); 0189 } else { 0190 m_incidenceMap.clear(); 0191 for (const Akonadi::Item &item : std::as_const(m_itemsToProcess)) { 0192 KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item); 0193 Q_ASSERT(incidence); 0194 m_incidenceMap.insert(incidence->instanceIdentifier(), incidence); 0195 m_incidenceToItem.insert(incidence, item); 0196 } 0197 runNextTest(); 0198 } 0199 } 0200 0201 void CalendarJanitor::runNextTest() 0202 { 0203 if (m_options.stripOldAlarms()) { 0204 if (!m_strippingOldAlarms) { 0205 m_strippingOldAlarms = true; 0206 stripOldAlarms(); 0207 } else { 0208 processNextCollection(); 0209 } 0210 0211 return; 0212 } 0213 0214 int currentType = static_cast<int>(m_currentSanityCheck); 0215 m_currentSanityCheck = static_cast<Options::SanityCheck>(currentType + 1); 0216 0217 switch (m_currentSanityCheck) { 0218 case Options::CheckEmptySummary: 0219 sanityCheck1(); 0220 break; 0221 case Options::CheckEmptyUid: 0222 sanityCheck2(); 0223 break; 0224 case Options::CheckEventDates: 0225 sanityCheck3(); 0226 break; 0227 case Options::CheckTodoDates: 0228 sanityCheck4(); 0229 break; 0230 case Options::CheckJournalDates: 0231 sanityCheck5(); 0232 break; 0233 case Options::CheckOrphans: 0234 sanityCheck6(); 0235 break; 0236 case Options::CheckDuplicateUIDs: 0237 sanityCheck7(); 0238 break; 0239 case Options::CheckStats: 0240 sanityCheck8(); 0241 break; 0242 case Options::CheckOrphanRecurId: 0243 sanityCheck9(); 0244 break; 0245 case Options::CheckCount: 0246 processNextCollection(); 0247 break; 0248 default: 0249 Q_ASSERT(false); 0250 } 0251 } 0252 0253 void CalendarJanitor::sanityCheck1() 0254 { 0255 beginTest(i18n("Checking for incidences with empty summary and description...")); 0256 0257 for (const Akonadi::Item &item : std::as_const(m_itemsToProcess)) { 0258 KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item); 0259 if (incidence->summary().isEmpty() && incidence->description().isEmpty() && incidence->attachments().isEmpty()) { 0260 printFound(item); 0261 deleteIncidence(item); 0262 } 0263 } 0264 0265 endTest(); 0266 } 0267 0268 void CalendarJanitor::sanityCheck2() 0269 { 0270 beginTest(i18n("Checking for incidences with empty UID...")); 0271 0272 for (const Akonadi::Item &item : std::as_const(m_itemsToProcess)) { 0273 KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item); 0274 if (incidence->uid().isEmpty()) { 0275 printFound(item); 0276 if (m_fixingEnabled) { 0277 incidence->recreate(); 0278 m_pendingModifications++; 0279 m_changer->modifyIncidence(item); 0280 } 0281 } 0282 } 0283 0284 endTest(); 0285 } 0286 0287 void CalendarJanitor::sanityCheck3() 0288 { 0289 beginTest(i18n("Checking for events with invalid DTSTART...")); 0290 for (const Akonadi::Item &item : std::as_const(m_itemsToProcess)) { 0291 KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item); 0292 KCalendarCore::Event::Ptr event = incidence.dynamicCast<KCalendarCore::Event>(); 0293 if (!event) { 0294 continue; 0295 } 0296 0297 QDateTime start = event->dtStart(); 0298 QDateTime end = event->dtEnd(); 0299 0300 bool modify = false; 0301 QString message; 0302 if (!start.isValid() && end.isValid()) { 0303 modify = true; 0304 printFound(item); 0305 event->setDtStart(end); 0306 } else if (!start.isValid() && !end.isValid()) { 0307 modify = true; 0308 printFound(item); 0309 event->setDtStart(QDateTime::currentDateTime()); 0310 event->setDtEnd(event->dtStart().addSecs(3600)); 0311 } 0312 0313 if (modify) { 0314 if (m_fixingEnabled) { 0315 m_changer->modifyIncidence(item); 0316 m_pendingModifications++; 0317 } 0318 } 0319 } 0320 0321 endTest(); 0322 } 0323 0324 void CalendarJanitor::sanityCheck4() 0325 { 0326 beginTest(i18n("Checking for recurring to-dos with invalid DTSTART...")); 0327 for (const Akonadi::Item &item : std::as_const(m_itemsToProcess)) { 0328 KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item); 0329 KCalendarCore::Todo::Ptr todo = incidence.dynamicCast<KCalendarCore::Todo>(); 0330 if (!todo) { 0331 continue; 0332 } 0333 0334 QDateTime start = todo->dtStart(); 0335 QDateTime due = todo->dtDue(); 0336 bool modify = false; 0337 if (todo->recurs() && !start.isValid() && due.isValid()) { 0338 modify = true; 0339 printFound(item); 0340 todo->setDtStart(due); 0341 } 0342 0343 if (todo->recurs() && !start.isValid() && !due.isValid()) { 0344 modify = true; 0345 printFound(item); 0346 todo->setDtStart(QDateTime::currentDateTime()); 0347 } 0348 0349 if (modify) { 0350 if (m_fixingEnabled) { 0351 m_changer->modifyIncidence(item); 0352 m_pendingModifications++; 0353 } 0354 } 0355 } 0356 0357 endTest(); 0358 } 0359 0360 void CalendarJanitor::sanityCheck5() 0361 { 0362 beginTest(i18n("Checking for journals with invalid DTSTART...")); 0363 for (const Akonadi::Item &item : std::as_const(m_itemsToProcess)) { 0364 KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item); 0365 if (incidence->type() != KCalendarCore::Incidence::TypeJournal) { 0366 continue; 0367 } 0368 0369 if (!incidence->dtStart().isValid()) { 0370 printFound(item); 0371 incidence->setDtStart(QDateTime::currentDateTime()); 0372 if (m_fixingEnabled) { 0373 m_changer->modifyIncidence(item); 0374 m_pendingModifications++; 0375 } 0376 } 0377 } 0378 endTest(); 0379 } 0380 0381 void CalendarJanitor::sanityCheck6() 0382 { 0383 beginTest(i18n("Checking for orphans...")); // Incidences without a parent 0384 0385 for (const Akonadi::Item &item : std::as_const(m_itemsToProcess)) { 0386 KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item); 0387 const QString parentUid = incidence->relatedTo(); 0388 if (!parentUid.isEmpty() && !m_calendar->incidence(parentUid)) { 0389 printFound(item, i18n("The following incidences are children of nonexistent parents")); 0390 if (m_fixingEnabled) { 0391 incidence->setRelatedTo(QString()); 0392 m_changer->modifyIncidence(item); 0393 m_pendingModifications++; 0394 } 0395 } 0396 } 0397 0398 endTest(true, i18n("In fix mode these children will be unparented."), i18n("Children were successfully unparented.")); 0399 } 0400 0401 void CalendarJanitor::sanityCheck7() 0402 { 0403 beginTest(i18n("Checking for duplicate UIDs...")); 0404 0405 for (const Akonadi::Item &item : std::as_const(m_itemsToProcess)) { 0406 KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item); 0407 const QList<KCalendarCore::Incidence::Ptr> existingIncidences = m_incidenceMap.values(incidence->instanceIdentifier()); 0408 0409 if (existingIncidences.count() == 1) { 0410 continue; 0411 } 0412 0413 for (const KCalendarCore::Incidence::Ptr &existingIncidence : existingIncidences) { 0414 if (existingIncidence != incidence && *incidence == *existingIncidence) { 0415 printFound(item); 0416 deleteIncidence(item); 0417 break; 0418 } 0419 } 0420 } 0421 0422 for (const Akonadi::Item &item : std::as_const(m_itemsToProcess)) { 0423 KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item); 0424 const QList<KCalendarCore::Incidence::Ptr> existingIncidences = m_incidenceMap.values(incidence->instanceIdentifier()); 0425 0426 if (existingIncidences.count() == 1) { 0427 continue; 0428 } 0429 0430 for (int i = 1; i < existingIncidences.count(); ++i) { 0431 printFound(item); 0432 if (m_fixingEnabled) { 0433 KCalendarCore::Incidence::Ptr existingIncidence = existingIncidences.at(i); 0434 Akonadi::Item item = m_incidenceToItem.value(existingIncidence); 0435 Q_ASSERT(item.isValid()); 0436 if (item.isValid()) { 0437 existingIncidence->recreate(); 0438 m_changer->modifyIncidence(item); 0439 m_pendingModifications++; 0440 m_incidenceMap.remove(incidence->instanceIdentifier(), existingIncidence); 0441 } 0442 } 0443 } 0444 } 0445 0446 endTest(); 0447 } 0448 0449 static void printStat(const QString &message, int arg) 0450 { 0451 if (arg > 0) { 0452 print(message.leftJustified(50), false); 0453 const QString s = QStringLiteral(": %1"); 0454 print(s.arg(arg)); 0455 } 0456 } 0457 0458 void CalendarJanitor::sanityCheck8() 0459 { 0460 beginTest(i18n("Gathering statistics...")); 0461 print(QStringLiteral("\n")); 0462 0463 int numOldAlarms = 0; 0464 int numAttachments = 0; 0465 int totalAttachmentSize = 0; 0466 int numOldIncidences = 0; 0467 int numEmptyRID = 0; 0468 QHash<KCalendarCore::Incidence::IncidenceType, int> m_counts; 0469 0470 for (const Akonadi::Item &item : std::as_const(m_itemsToProcess)) { 0471 KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item); 0472 if (!incidence->attachments().isEmpty()) { 0473 const auto attachments = incidence->attachments(); 0474 for (const KCalendarCore::Attachment &attachment : attachments) { 0475 if (!attachment.isUri()) { 0476 numAttachments++; 0477 totalAttachmentSize += attachment.size(); 0478 } 0479 } 0480 } 0481 0482 m_counts[incidence->type()]++; 0483 0484 if (incidenceIsOld(incidence)) { 0485 if (!incidence->alarms().isEmpty()) { 0486 numOldAlarms++; 0487 } 0488 numOldIncidences++; 0489 } 0490 0491 numAttachments += incidence->attachments().count(); 0492 0493 if (item.remoteId().isEmpty()) { 0494 numEmptyRID++; 0495 } 0496 } 0497 0498 printStat(i18n("Events"), m_counts[KCalendarCore::Incidence::TypeEvent]); 0499 printStat(i18n("Todos"), m_counts[KCalendarCore::Incidence::TypeTodo]); 0500 printStat(i18n("Journals"), m_counts[KCalendarCore::Incidence::TypeJournal]); 0501 printStat(i18n("Passed events and to-dos (>365 days)"), numOldIncidences); 0502 printStat(i18n("Old incidences with alarms"), numOldAlarms); 0503 printStat(i18n("Inline attachments"), numAttachments); 0504 printStat(i18n("Items with empty remote id [!!]"), numEmptyRID); 0505 0506 if (totalAttachmentSize < 1024) { 0507 printStat(i18n("Total size of inline attachments (bytes)"), totalAttachmentSize); 0508 } else { 0509 printStat(i18n("Total size of inline attachments (KB)"), totalAttachmentSize / 1024); 0510 } 0511 0512 if (numEmptyRID > 0) { 0513 m_returnCode = -2; 0514 } 0515 0516 endTest(/**print=*/false); 0517 } 0518 0519 void CalendarJanitor::sanityCheck9() 0520 { 0521 beginTest(i18n("Checking for RECURRING-ID incidences with nonexistent master incidence...")); 0522 for (const Akonadi::Item &item : std::as_const(m_itemsToProcess)) { 0523 KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item); 0524 if (incidence->recurs() && incidence->hasRecurrenceId() && !m_calendar->incidence(incidence->uid())) { 0525 printFound(item); 0526 if (m_fixingEnabled) { 0527 bool modified = false; 0528 0529 QDateTime recId = incidence->recurrenceId(); 0530 QDateTime start = incidence->dtStart(); 0531 QDateTime end = incidence->dateTime(KCalendarCore::Incidence::RoleEnd); 0532 0533 KCalendarCore::Event::Ptr event = incidence.dynamicCast<KCalendarCore::Event>(); 0534 KCalendarCore::Todo::Ptr todo = incidence.dynamicCast<KCalendarCore::Todo>(); 0535 0536 if (event && start.isValid() && end.isValid()) { 0537 modified = true; 0538 const int duration = start.daysTo(end.toTimeSpec(start.timeSpec())); 0539 incidence->setDtStart(recId); 0540 event->setDtEnd(recId.addDays(duration)); 0541 } else if (todo && start.isValid()) { 0542 modified = true; 0543 incidence->setDtStart(recId); 0544 0545 if (end.isValid()) { 0546 const int duration = start.daysTo(end.toTimeSpec(start.timeSpec())); 0547 todo->setDtDue(recId.addDays(duration)); 0548 } 0549 } 0550 0551 if (modified) { 0552 m_pendingModifications++; 0553 incidence->recreate(); // change uid 0554 incidence->clearRecurrence(); // make it non-recurring 0555 incidence->setRecurrenceId(QDateTime()); 0556 m_changer->modifyIncidence(item); 0557 } 0558 } 0559 } 0560 } 0561 0562 endTest(true, i18n("In fix mode the RECURRING-ID property will be unset and UID changed."), i18n("Recurrence cleared.")); 0563 } 0564 0565 void CalendarJanitor::stripOldAlarms() 0566 { 0567 beginTest(i18n("Deleting alarms older than 365 days...")); 0568 0569 for (const Akonadi::Item &item : std::as_const(m_itemsToProcess)) { 0570 KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item); 0571 if (!incidence->alarms().isEmpty() && incidenceIsOld(incidence)) { 0572 incidence->clearAlarms(); 0573 m_pendingModifications++; 0574 m_changer->modifyIncidence(item); 0575 } 0576 } 0577 0578 endTest(); 0579 } 0580 0581 static QString dateString(const KCalendarCore::Incidence::Ptr &incidence) 0582 { 0583 QDateTime start = incidence->dtStart(); 0584 QDateTime end = incidence->dateTime(KCalendarCore::Incidence::RoleEnd); 0585 QString str = QLatin1StringView("DTSTART=") + (start.isValid() ? start.toString() : i18n("invalid")) + QLatin1StringView("; "); 0586 0587 if (incidence->type() == KCalendarCore::Incidence::TypeJournal) { 0588 return str; 0589 } 0590 0591 str += QLatin1StringView("\n "); 0592 0593 if (incidence->type() == KCalendarCore::Incidence::TypeTodo) { 0594 str += QLatin1StringView("DTDUE="); 0595 } else if (incidence->type() == KCalendarCore::Incidence::TypeEvent) { 0596 str += QLatin1StringView("DTEND="); 0597 } 0598 0599 str += (start.isValid() ? end.toString() : i18n("invalid")) + QLatin1StringView("; "); 0600 0601 if (incidence->recurs()) { 0602 str += i18n("recurrent"); 0603 } 0604 0605 return str; 0606 } 0607 0608 void CalendarJanitor::printFound(const Akonadi::Item &item, const QString &explanation) 0609 { 0610 KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item); 0611 m_numDamaged++; 0612 if (m_numDamaged == 1) { 0613 print(QStringLiteral(" [!!]")); 0614 if (!explanation.isEmpty()) { 0615 print(QStringLiteral(" "), false); 0616 print(explanation, false); 0617 print(QStringLiteral(":\n")); 0618 } 0619 } 0620 print(QLatin1StringView(" * ") + i18n("Found buggy incidence:")); 0621 print(QLatin1StringView(" ") + i18n("id=%1; summary=\"%2\"", item.id(), incidence->summary())); 0622 print(QLatin1StringView(" ") + dateString(incidence)); 0623 } 0624 0625 void CalendarJanitor::beginTest(const QString &message) 0626 { 0627 m_numDamaged = 0; 0628 m_fixingEnabled = m_options.action() == Options::ActionScanAndFix && !collectionIsReadOnly(m_currentCollection); 0629 print(message.leftJustified(TEXT_WIDTH), false); 0630 } 0631 0632 void CalendarJanitor::endTest(bool printEnabled, const QString &fixExplanation, const QString &fixExplanation2) 0633 { 0634 if (m_numDamaged == 0 && printEnabled) { 0635 print(QStringLiteral(" [OK]")); 0636 } else if (m_numDamaged > 0) { 0637 print(QStringLiteral("\n "), false); 0638 if (m_options.action() == Options::ActionScanAndFix) { 0639 print(fixExplanation2); 0640 } else { 0641 print(fixExplanation); 0642 } 0643 0644 print(QString()); 0645 } 0646 0647 if (m_pendingDeletions == 0 && m_pendingModifications == 0) { 0648 runNextTest(); 0649 } 0650 } 0651 0652 void CalendarJanitor::deleteIncidence(const Akonadi::Item &item) 0653 { 0654 if (m_fixingEnabled && !collectionIsReadOnly(m_currentCollection)) { 0655 m_pendingDeletions++; 0656 m_changer->deleteIncidence(item); 0657 KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item); 0658 m_incidenceMap.remove(incidence->instanceIdentifier(), incidence); 0659 m_incidenceToItem.remove(incidence); 0660 } 0661 } 0662 0663 #include "moc_calendarjanitor.cpp"