File indexing completed on 2025-01-05 04:49:48

0001 /*
0002  * SPDX-FileCopyrightText: 2016 Daniel Vrátil <dvratil@kde.org>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-or-later
0005  *
0006  */
0007 
0008 #include "eventmodel.h"
0009 #include "pimeventsplugin_debug.h"
0010 
0011 #include <Akonadi/IncidenceChanger>
0012 
0013 #include <Akonadi/AttributeFactory>
0014 #include <Akonadi/CollectionColorAttribute>
0015 #include <Akonadi/CollectionFetchScope>
0016 #include <Akonadi/EntityDisplayAttribute>
0017 #include <Akonadi/ItemFetchJob>
0018 #include <Akonadi/ItemFetchScope>
0019 #include <Akonadi/Monitor>
0020 
0021 EventModel::EventModel(QObject *parent)
0022     : Akonadi::CalendarBase(parent)
0023 {
0024     Akonadi::AttributeFactory::registerAttribute<Akonadi::CollectionColorAttribute>();
0025 }
0026 
0027 EventModel::~EventModel() = default;
0028 
0029 void EventModel::createMonitor()
0030 {
0031     if (mMonitor) {
0032         return;
0033     }
0034 
0035     mMonitor = new Akonadi::Monitor(this);
0036     mMonitor->setObjectName(QLatin1StringView("PlasmaEventModelMonitor"));
0037     mMonitor->itemFetchScope().fetchFullPayload(true);
0038     mMonitor->collectionFetchScope().fetchAttribute<Akonadi::EntityDisplayAttribute>();
0039     mMonitor->collectionFetchScope().fetchAttribute<Akonadi::CollectionColorAttribute>();
0040     mMonitor->fetchCollection(true);
0041 
0042     connect(mMonitor, &Akonadi::Monitor::itemAdded, this, [this](const Akonadi::Item &item) {
0043         // This is super-ugly, but the only way how to insert into CalendarBase
0044         // without having direct access to CalendarBasePrivate.
0045         // changeId is luckily ignored by CalendarBase.
0046         Q_EMIT incidenceChanger()->createFinished(0, item, Akonadi::IncidenceChanger::ResultCodeSuccess, QString());
0047     });
0048     connect(mMonitor, &Akonadi::Monitor::itemChanged, this, [this](const Akonadi::Item &item) {
0049         if (!item.hasPayload<KCalendarCore::Incidence::Ptr>()) {
0050             qCDebug(PIMEVENTSPLUGIN_LOG) << "Item" << item.id() << "has no payload!";
0051             return;
0052         }
0053 
0054         auto incidence = item.payload<KCalendarCore::Incidence::Ptr>();
0055         if (!incidence) {
0056             return; // HUH?!
0057         }
0058         const KCalendarCore::Incidence::Ptr oldIncidence = this->incidence(incidence->instanceIdentifier());
0059         if (!oldIncidence) {
0060             // Change for event we don't know about -> discard
0061             return;
0062         }
0063 
0064         // Unfortunately the plasma applet does not handle event moves
0065         // so we have to simulate via remove+add
0066         if (oldIncidence->allDay() != incidence->allDay() || oldIncidence->dtStart() != incidence->dtStart()
0067             || oldIncidence->dateTime(KCalendarCore::IncidenceBase::RoleEnd) != incidence->dateTime(KCalendarCore::IncidenceBase::RoleEnd)) {
0068             Q_EMIT incidenceChanger()->deleteFinished(0, {item.id()}, Akonadi::IncidenceChanger::ResultCodeSuccess, QString());
0069             Q_EMIT incidenceChanger()->createFinished(0, item, Akonadi::IncidenceChanger::ResultCodeSuccess, QString());
0070         } else {
0071             Q_EMIT incidenceChanger()->modifyFinished(0, item, Akonadi::IncidenceChanger::ResultCodeSuccess, QString());
0072         }
0073     });
0074     connect(mMonitor, &Akonadi::Monitor::itemRemoved, this, [this](const Akonadi::Item &item) {
0075         Q_EMIT incidenceChanger()->deleteFinished(0, {item.id()}, Akonadi::IncidenceChanger::ResultCodeSuccess, QString());
0076     });
0077     connect(mMonitor, &Akonadi::Monitor::collectionRemoved, this, &EventModel::removeCalendar);
0078 }
0079 
0080 void EventModel::addCalendar(const Akonadi::Collection &col)
0081 {
0082     if (!mCols.contains(col)) {
0083         mCols.push_back(col);
0084 
0085         createMonitor();
0086         mMonitor->setCollectionMonitored(col, true);
0087 
0088         populateCollection(col);
0089     }
0090 }
0091 
0092 void EventModel::removeCalendar(const Akonadi::Collection &col)
0093 {
0094     auto it = std::find(mCols.begin(), mCols.end(), col);
0095     if (it != mCols.end()) {
0096         mCols.erase(it);
0097         if (mMonitor) {
0098             mMonitor->setCollectionMonitored(col, false);
0099         }
0100 
0101         removeCollection(col);
0102     }
0103 }
0104 
0105 QList<Akonadi::Collection> EventModel::collections() const
0106 {
0107     return mCols;
0108 }
0109 
0110 Akonadi::Collection EventModel::collection(qint64 id) const
0111 {
0112     auto it = std::find(mCols.cbegin(), mCols.cend(), Akonadi::Collection(id));
0113     return it == mCols.cend() ? Akonadi::Collection(id) : *it;
0114 }
0115 
0116 void EventModel::populateCollection(const Akonadi::Collection &col)
0117 {
0118     qCDebug(PIMEVENTSPLUGIN_LOG) << "Populating events from collection" << col.id();
0119     auto job = new Akonadi::ItemFetchJob(col, this);
0120     job->fetchScope().fetchFullPayload(true);
0121     job->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
0122     job->setDeliveryOption(Akonadi::ItemFetchJob::EmitItemsInBatches);
0123     mFetchJobs.insert(col.id(), job);
0124     connect(job, &Akonadi::ItemFetchJob::itemsReceived, this, &EventModel::onItemsReceived);
0125     connect(job, &Akonadi::ItemFetchJob::result, job, [this, col](KJob *job) {
0126         mFetchJobs.remove(col.id());
0127         auto fetch = qobject_cast<Akonadi::ItemFetchJob *>(job);
0128         qCDebug(PIMEVENTSPLUGIN_LOG) << "Received" << fetch->count() << "events for collection" << col.id();
0129     });
0130 }
0131 
0132 void EventModel::onItemsReceived(const Akonadi::Item::List &items)
0133 {
0134     qCDebug(PIMEVENTSPLUGIN_LOG) << "Batch: received" << items.count() << "items";
0135     for (const auto &item : items) {
0136         if (item.hasPayload<KCalendarCore::Incidence::Ptr>()) {
0137             Q_EMIT incidenceChanger()->createFinished(0, item, Akonadi::IncidenceChanger::ResultCodeSuccess, QString());
0138         } else {
0139             qCDebug(PIMEVENTSPLUGIN_LOG) << "Item" << item.id() << "has no payload";
0140         }
0141     }
0142 }
0143 
0144 void EventModel::removeCollection(const Akonadi::Collection &col)
0145 {
0146     if (KJob *job = mFetchJobs.take(col.id())) {
0147         disconnect(job, nullptr, this, nullptr);
0148         job->kill();
0149     }
0150 
0151     const auto items = this->items(col.id());
0152     qCDebug(PIMEVENTSPLUGIN_LOG) << "Removing" << items.count() << "events for collection" << col.id();
0153     if (items.isEmpty()) {
0154         return;
0155     }
0156 
0157     QList<Akonadi::Item::Id> ids;
0158     ids.reserve(items.size());
0159     std::transform(items.cbegin(), items.cend(), std::back_inserter(ids), std::mem_fn(&Akonadi::Item::id));
0160 
0161     Q_EMIT incidenceChanger()->deleteFinished(0, ids, Akonadi::IncidenceChanger::ResultCodeSuccess, QString());
0162 }
0163 
0164 #include "moc_eventmodel.cpp"