File indexing completed on 2024-05-19 05:11:18
0001 /* 0002 SPDX-FileCopyrightText: 2013 Sérgio Martins <iamsergio@gmail.com> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "incidencechanger_p.h" 0008 #include "akonadicalendar_debug.h" 0009 #include "calendarutils.h" 0010 #include "utils_p.h" 0011 #include <Akonadi/CollectionFetchJob> 0012 #include <Akonadi/CollectionFetchScope> 0013 #include <Akonadi/ItemCreateJob> 0014 #include <KCalendarCore/Incidence> 0015 #include <QDialog> 0016 #include <QString> 0017 0018 using namespace Akonadi; 0019 0020 void Change::emitUserDialogClosedAfterChange(Akonadi::ITIPHandlerHelper::SendResult status) 0021 { 0022 Q_EMIT dialogClosedAfterChange(id, status); 0023 } 0024 0025 void Change::emitUserDialogClosedBeforeChange(Akonadi::ITIPHandlerHelper::SendResult status) 0026 { 0027 Q_EMIT dialogClosedBeforeChange(id, status); 0028 } 0029 0030 void IncidenceChangerPrivate::loadCollections() 0031 { 0032 if (isLoadingCollections()) { 0033 // Collections are already loading 0034 return; 0035 } 0036 0037 m_collectionFetchJob = new Akonadi::CollectionFetchJob(Akonadi::Collection::root(), Akonadi::CollectionFetchJob::Recursive); 0038 0039 m_collectionFetchJob->fetchScope().setContentMimeTypes(KCalendarCore::Incidence::mimeTypes()); 0040 connect(m_collectionFetchJob, &KJob::result, this, &IncidenceChangerPrivate::onCollectionsLoaded); 0041 m_collectionFetchJob->start(); 0042 } 0043 0044 Collection::List IncidenceChangerPrivate::collectionsForMimeType(const QString &mimeType, const Collection::List &collections) 0045 { 0046 Collection::List result; 0047 for (const Akonadi::Collection &collection : collections) { 0048 if (collection.contentMimeTypes().contains(mimeType)) { 0049 result << collection; 0050 } 0051 } 0052 0053 return result; 0054 } 0055 0056 void IncidenceChangerPrivate::onCollectionsLoaded(KJob *job) 0057 { 0058 Q_ASSERT(!mPendingCreations.isEmpty()); 0059 if (job->error() != 0 || !m_collectionFetchJob) { 0060 qCritical() << "Error loading collections:" << job->errorString(); 0061 return; 0062 } 0063 0064 Q_ASSERT(job == m_collectionFetchJob); 0065 Akonadi::Collection::List allCollections; 0066 const auto collections = m_collectionFetchJob->collections(); 0067 for (const Akonadi::Collection &collection : collections) { 0068 if (collection.rights() & Akonadi::Collection::CanCreateItem) { 0069 allCollections << collection; 0070 } 0071 } 0072 0073 m_collectionFetchJob = nullptr; 0074 bool canceled = false; 0075 0076 // These two will never be true, maybe even assert 0077 bool noAcl = false; 0078 bool invalidCollection = false; 0079 Collection collectionToUse; 0080 const auto lstPendingCreations = mPendingCreations; 0081 for (const Change::Ptr &change : lstPendingCreations) { 0082 mPendingCreations.removeAll(change); 0083 0084 if (canceled) { 0085 change->resultCode = IncidenceChanger::ResultCodeUserCanceled; 0086 continue; 0087 } 0088 0089 if (noAcl) { 0090 change->resultCode = IncidenceChanger::ResultCodePermissions; 0091 continue; 0092 } 0093 0094 if (invalidCollection) { 0095 change->resultCode = IncidenceChanger::ResultCodeInvalidUserCollection; 0096 continue; 0097 } 0098 0099 if (collectionToUse.isValid()) { 0100 // We don't show the dialog multiple times 0101 step2CreateIncidence(change, collectionToUse); 0102 continue; 0103 } 0104 0105 KCalendarCore::Incidence::Ptr incidence = CalendarUtils::incidence(change->newItem); 0106 Collection::List candidateCollections = collectionsForMimeType(incidence->mimeType(), allCollections); 0107 if (candidateCollections.count() == 1 && candidateCollections.first().isValid()) { 0108 // We only have 1 writable collection, don't bother the user with a dialog 0109 collectionToUse = candidateCollections.first(); 0110 qCDebug(AKONADICALENDAR_LOG) << "Only one collection exists, will not show collection dialog: " << collectionToUse.displayName(); 0111 step2CreateIncidence(change, collectionToUse); 0112 continue; 0113 } 0114 0115 // Lets ask the user which collection to use: 0116 int dialogCode; 0117 QWidget *parent = change->parentWidget; 0118 0119 const QStringList mimeTypes(incidence->mimeType()); 0120 collectionToUse = CalendarUtils::selectCollection(parent, /*by-ref*/ dialogCode, mimeTypes, mDefaultCollection); 0121 if (dialogCode != QDialog::Accepted) { 0122 qCDebug(AKONADICALENDAR_LOG) << "User canceled collection choosing"; 0123 change->resultCode = IncidenceChanger::ResultCodeUserCanceled; 0124 canceled = true; 0125 cancelTransaction(); 0126 continue; 0127 } 0128 0129 if (collectionToUse.isValid() && !hasRights(collectionToUse, IncidenceChanger::ChangeTypeCreate)) { 0130 qCWarning(AKONADICALENDAR_LOG) << "No ACLs for incidence creation"; 0131 const QString errorMessage = showErrorDialog(IncidenceChanger::ResultCodePermissions, parent); 0132 change->resultCode = IncidenceChanger::ResultCodePermissions; 0133 change->errorString = errorMessage; 0134 noAcl = true; 0135 cancelTransaction(); 0136 continue; 0137 } 0138 0139 // TODO: add unit test for these two situations after reviewing API 0140 if (!collectionToUse.isValid()) { 0141 qCritical() << "Invalid collection selected. Can't create incidence."; 0142 change->resultCode = IncidenceChanger::ResultCodeInvalidUserCollection; 0143 const QString errorString = showErrorDialog(IncidenceChanger::ResultCodeInvalidUserCollection, parent); 0144 change->errorString = errorString; 0145 invalidCollection = true; 0146 cancelTransaction(); 0147 continue; 0148 } 0149 0150 step2CreateIncidence(change, collectionToUse); 0151 } 0152 } 0153 0154 bool IncidenceChangerPrivate::isLoadingCollections() const 0155 { 0156 return m_collectionFetchJob != nullptr; 0157 } 0158 0159 void IncidenceChangerPrivate::step1DetermineDestinationCollection(const Change::Ptr &change, const Akonadi::Collection &collection) 0160 { 0161 QWidget *parent = change->parentWidget.data(); 0162 if (collection.isValid() && hasRights(collection, IncidenceChanger::ChangeTypeCreate)) { 0163 // The collection passed always has priority 0164 step2CreateIncidence(change, collection); 0165 } else { 0166 switch (mDestinationPolicy) { 0167 case IncidenceChanger::DestinationPolicyDefault: 0168 if (mDefaultCollection.isValid() && hasRights(mDefaultCollection, IncidenceChanger::ChangeTypeCreate)) { 0169 step2CreateIncidence(change, mDefaultCollection); 0170 break; 0171 } 0172 qCWarning(AKONADICALENDAR_LOG) << "Destination policy is to use the default collection." 0173 << "But it's invalid or doesn't have proper ACLs." 0174 << "isValid = " << mDefaultCollection.isValid() 0175 << "has ACLs = " << hasRights(mDefaultCollection, IncidenceChanger::ChangeTypeCreate); 0176 // else fallthrough, and ask the user. 0177 [[fallthrough]]; 0178 case IncidenceChanger::DestinationPolicyAsk: 0179 mPendingCreations << change; 0180 loadCollections(); // Now we wait, collections are being loaded async 0181 break; 0182 case IncidenceChanger::DestinationPolicyNeverAsk: { 0183 const bool hasRights = this->hasRights(mDefaultCollection, IncidenceChanger::ChangeTypeCreate); 0184 if (mDefaultCollection.isValid() && hasRights) { 0185 step2CreateIncidence(change, mDefaultCollection); 0186 } else { 0187 const QString errorString = showErrorDialog(IncidenceChanger::ResultCodeInvalidDefaultCollection, parent); 0188 qCritical() << errorString << "; rights are " << hasRights; 0189 change->resultCode = hasRights ? IncidenceChanger::ResultCodeInvalidDefaultCollection : IncidenceChanger::ResultCodePermissions; 0190 change->errorString = errorString; 0191 cancelTransaction(); 0192 } 0193 break; 0194 } 0195 default: 0196 // Never happens 0197 Q_ASSERT_X(false, "createIncidence()", "unknown destination policy"); 0198 cancelTransaction(); 0199 } 0200 } 0201 } 0202 0203 void IncidenceChangerPrivate::step2CreateIncidence(const Change::Ptr &change, const Akonadi::Collection &collection) 0204 { 0205 Q_ASSERT(change); 0206 0207 mLastCollectionUsed = collection; 0208 0209 auto createJob = new ItemCreateJob(change->newItem, collection, parentJob(change)); 0210 mChangeForJob.insert(createJob, change); 0211 0212 if (mBatchOperationInProgress) { 0213 AtomicOperation *atomic = mAtomicOperations[mLatestAtomicOperationId]; 0214 Q_ASSERT(atomic); 0215 atomic->addChange(change); 0216 } 0217 0218 // QueuedConnection because of possible sync exec calls. 0219 connect(createJob, &KJob::result, this, &IncidenceChangerPrivate::handleCreateJobResult, Qt::QueuedConnection); 0220 0221 mChangeById.insert(change->id, change); 0222 } 0223 0224 #include "moc_incidencechanger_p.cpp"