File indexing completed on 2024-06-02 05:20:58
0001 /* 0002 SPDX-FileCopyrightText: 2015-2016 Krzysztof Nowicki <krissn@op.pl> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "ewsfetchfoldersjob.h" 0008 0009 #include <Akonadi/CollectionFetchJob> 0010 #include <Akonadi/CollectionFetchScope> 0011 #include <Akonadi/CollectionMoveJob> 0012 #include <Akonadi/CollectionStatistics> 0013 #include <KCalendarCore/Event> 0014 #include <KCalendarCore/Todo> 0015 #include <KContacts/Addressee> 0016 #include <KContacts/ContactGroup> 0017 #include <KMime/Message> 0018 0019 #include "ewsclient.h" 0020 #include "ewseffectiverights.h" 0021 #include "ewsgetfolderrequest.h" 0022 #include "ewsresource_debug.h" 0023 #include "ewssyncfolderhierarchyrequest.h" 0024 0025 using namespace Akonadi; 0026 0027 static const EwsPropertyField propPidTagContainerClass(0x3613, EwsPropTypeString); 0028 0029 static constexpr int fetchBatchSize = 50; 0030 0031 class EwsFetchFoldersJobPrivate : public QObject 0032 { 0033 public: 0034 EwsFetchFoldersJobPrivate(EwsFetchFoldersJob *parent, EwsClient &client, const Collection &rootCollection); 0035 ~EwsFetchFoldersJobPrivate() override; 0036 0037 void processRemoteFolders(); 0038 Collection createFolderCollection(const EwsFolder &folder); 0039 0040 void buildCollectionList(); 0041 void buildChildCollectionList(const Collection &col); 0042 public Q_SLOTS: 0043 void remoteFolderFullFetchDone(KJob *job); 0044 void remoteFolderIdFullFetchDone(KJob *job); 0045 void remoteFolderDetailFetchDone(KJob *job); 0046 0047 public: 0048 EwsClient &mClient; 0049 int mPendingFetchJobs; 0050 int mPendingMoveJobs; 0051 EwsId::List mRemoteFolderIds; 0052 0053 const Collection &mRootCollection; 0054 0055 EwsFolder::List mRemoteChangedFolders; // Contains details of folders that need update 0056 // (either created or changed) 0057 QHash<QString, Akonadi::Collection> mCollectionMap; 0058 QMultiHash<QString, QString> mParentMap; 0059 0060 EwsFetchFoldersJob *q_ptr; 0061 Q_DECLARE_PUBLIC(EwsFetchFoldersJob) 0062 }; 0063 0064 EwsFetchFoldersJobPrivate::EwsFetchFoldersJobPrivate(EwsFetchFoldersJob *parent, EwsClient &client, const Collection &rootCollection) 0065 : QObject(parent) 0066 , mClient(client) 0067 , mRootCollection(rootCollection) 0068 , q_ptr(parent) 0069 { 0070 mPendingFetchJobs = 0; 0071 mPendingMoveJobs = 0; 0072 } 0073 0074 EwsFetchFoldersJobPrivate::~EwsFetchFoldersJobPrivate() = default; 0075 0076 void EwsFetchFoldersJobPrivate::remoteFolderFullFetchDone(KJob *job) 0077 { 0078 Q_Q(EwsFetchFoldersJob); 0079 0080 auto req = qobject_cast<EwsSyncFolderHierarchyRequest *>(job); 0081 if (!req) { 0082 qCWarning(EWSRES_LOG) << QStringLiteral("Invalid EwsSyncFolderHierarchyRequest job object"); 0083 q->setErrorMsg(QStringLiteral("Invalid EwsSyncFolderHierarchyRequest job object")); 0084 q->emitResult(); 0085 return; 0086 } 0087 0088 if (req->error()) { 0089 /* It has been reported that the SyncFolderHierarchyRequest can fail with Internal Server 0090 * Error (possibly because of a large number of folders). In order to work around this 0091 * try to fallback to fetching just the folder identifiers and retrieve the details later. */ 0092 qCDebug(EWSRES_LOG) << QStringLiteral("Full fetch failed. Trying to fetch ids only."); 0093 0094 auto syncFoldersReq = new EwsSyncFolderHierarchyRequest(mClient, this); 0095 syncFoldersReq->setFolderId(EwsId(EwsDIdMsgFolderRoot)); 0096 EwsFolderShape shape(EwsShapeIdOnly); 0097 syncFoldersReq->setFolderShape(shape); 0098 connect(syncFoldersReq, &EwsSyncFolderHierarchyRequest::result, this, &EwsFetchFoldersJobPrivate::remoteFolderIdFullFetchDone); 0099 q->addSubjob(syncFoldersReq); 0100 syncFoldersReq->start(); 0101 0102 return; 0103 } 0104 0105 const auto reqChanges{req->changes()}; 0106 for (const EwsSyncFolderHierarchyRequest::Change &ch : reqChanges) { 0107 if (ch.type() == EwsSyncFolderHierarchyRequest::Create) { 0108 mRemoteChangedFolders.append(ch.folder()); 0109 } else { 0110 q->setErrorMsg(QStringLiteral("Got non-create change for full sync.")); 0111 q->emitResult(); 0112 return; 0113 } 0114 } 0115 0116 if (req->includesLastItem()) { 0117 processRemoteFolders(); 0118 0119 buildCollectionList(); 0120 0121 q->mSyncState = req->syncState(); 0122 0123 q->emitResult(); 0124 } else { 0125 auto syncFoldersReq = new EwsSyncFolderHierarchyRequest(mClient, this); 0126 syncFoldersReq->setFolderId(EwsId(EwsDIdMsgFolderRoot)); 0127 EwsFolderShape shape; 0128 shape << propPidTagContainerClass; 0129 shape << EwsPropertyField(QStringLiteral("folder:EffectiveRights")); 0130 shape << EwsPropertyField(QStringLiteral("folder:ParentFolderId")); 0131 syncFoldersReq->setFolderShape(shape); 0132 syncFoldersReq->setSyncState(req->syncState()); 0133 connect(syncFoldersReq, &EwsSyncFolderHierarchyRequest::result, this, &EwsFetchFoldersJobPrivate::remoteFolderFullFetchDone); 0134 syncFoldersReq->start(); 0135 } 0136 } 0137 0138 void EwsFetchFoldersJobPrivate::remoteFolderIdFullFetchDone(KJob *job) 0139 { 0140 Q_Q(EwsFetchFoldersJob); 0141 0142 auto req = qobject_cast<EwsSyncFolderHierarchyRequest *>(job); 0143 if (!req) { 0144 qCWarning(EWSRES_LOG) << QStringLiteral("Invalid EwsSyncFolderHierarchyRequest job object"); 0145 q->setErrorMsg(QStringLiteral("Invalid EwsSyncFolderHierarchyRequest job object")); 0146 q->emitResult(); 0147 return; 0148 } 0149 0150 if (req->error()) { 0151 return; 0152 } 0153 0154 const auto reqChanges{req->changes()}; 0155 for (const EwsSyncFolderHierarchyRequest::Change &ch : reqChanges) { 0156 if (ch.type() == EwsSyncFolderHierarchyRequest::Create) { 0157 mRemoteFolderIds.append(ch.folder()[EwsFolderFieldFolderId].value<EwsId>()); 0158 } else { 0159 q->setErrorMsg(QStringLiteral("Got non-create change for full sync.")); 0160 q->emitResult(); 0161 return; 0162 } 0163 } 0164 0165 if (req->includesLastItem()) { 0166 EwsFolderShape shape(EwsShapeDefault); 0167 shape << propPidTagContainerClass; 0168 shape << EwsPropertyField(QStringLiteral("folder:EffectiveRights")); 0169 shape << EwsPropertyField(QStringLiteral("folder:ParentFolderId")); 0170 mPendingFetchJobs = 0; 0171 0172 for (int i = 0, total = mRemoteFolderIds.size(); i < total; i += fetchBatchSize) { 0173 auto folderReq = new EwsGetFolderRequest(mClient, this); 0174 folderReq->setFolderIds(mRemoteFolderIds.mid(i, fetchBatchSize)); 0175 folderReq->setFolderShape(shape); 0176 connect(folderReq, &EwsSyncFolderHierarchyRequest::result, this, &EwsFetchFoldersJobPrivate::remoteFolderDetailFetchDone); 0177 folderReq->start(); 0178 q->addSubjob(folderReq); 0179 mPendingFetchJobs++; 0180 } 0181 0182 qCDebugNC(EWSRES_LOG) << QStringLiteral("Starting %1 folder fetch jobs.").arg(mPendingFetchJobs); 0183 0184 q->mSyncState = req->syncState(); 0185 } else { 0186 auto syncFoldersReq = new EwsSyncFolderHierarchyRequest(mClient, this); 0187 syncFoldersReq->setFolderId(EwsId(EwsDIdMsgFolderRoot)); 0188 EwsFolderShape shape(EwsShapeIdOnly); 0189 syncFoldersReq->setFolderShape(shape); 0190 syncFoldersReq->setSyncState(req->syncState()); 0191 connect(syncFoldersReq, &EwsSyncFolderHierarchyRequest::result, this, &EwsFetchFoldersJobPrivate::remoteFolderIdFullFetchDone); 0192 q->addSubjob(syncFoldersReq); 0193 syncFoldersReq->start(); 0194 } 0195 } 0196 0197 void EwsFetchFoldersJobPrivate::remoteFolderDetailFetchDone(KJob *job) 0198 { 0199 Q_Q(EwsFetchFoldersJob); 0200 0201 auto req = qobject_cast<EwsGetFolderRequest *>(job); 0202 if (!req) { 0203 qCWarning(EWSRES_LOG) << QStringLiteral("Invalid EwsGetFolderRequest job object"); 0204 q->setErrorMsg(QStringLiteral("Invalid EwsGetFolderRequest job object")); 0205 q->emitResult(); 0206 return; 0207 } 0208 0209 if (req->error()) { 0210 return; 0211 } 0212 0213 const auto reqResponses{req->responses()}; 0214 for (const EwsGetFolderRequest::Response &resp : reqResponses) { 0215 if (resp.isSuccess()) { 0216 mRemoteChangedFolders.append(resp.folder()); 0217 } else { 0218 qCWarningNC(EWSRES_LOG) << QStringLiteral("Failed to fetch folder details."); 0219 } 0220 } 0221 0222 mPendingFetchJobs--; 0223 qCDebugNC(EWSRES_LOG) << QStringLiteral("%1 folder fetch jobs pending").arg(mPendingFetchJobs); 0224 0225 if (mPendingFetchJobs == 0) { 0226 qCDebugNC(EWSRES_LOG) << QStringLiteral("All folder fetch jobs complete"); 0227 0228 processRemoteFolders(); 0229 0230 buildCollectionList(); 0231 0232 q->emitResult(); 0233 } 0234 } 0235 0236 void EwsFetchFoldersJobPrivate::processRemoteFolders() 0237 { 0238 /* mCollectionMap contains the global collection list keyed by the EWS ID. */ 0239 /* mParentMap contains the parent->child map for each collection. */ 0240 0241 /* Iterate over all changed folders. */ 0242 for (const EwsFolder &folder : std::as_const(mRemoteChangedFolders)) { 0243 /* Create a collection for each folder. */ 0244 Collection c = createFolderCollection(folder); 0245 0246 /* Insert it into the global collection list. */ 0247 mCollectionMap.insert(c.remoteId(), c); 0248 0249 /* Determine the parent and insert a parent->child relationship. 0250 * Don't use Collection::setParentCollection() yet as the collection object will be updated 0251 * which will cause the parent->child relationship to be broken. This happens because the 0252 * collection object holds a copy of the parent collection object. An update to that 0253 * object in the list will not be visible in the copy inside of the child object. */ 0254 auto parentId = folder[EwsFolderFieldParentFolderId].value<EwsId>(); 0255 mParentMap.insert(parentId.id(), c.remoteId()); 0256 } 0257 } 0258 0259 void EwsFetchFoldersJobPrivate::buildCollectionList() 0260 { 0261 Q_Q(EwsFetchFoldersJob); 0262 0263 q->mFolders.append(mRootCollection); 0264 buildChildCollectionList(mRootCollection); 0265 0266 if (!mCollectionMap.isEmpty()) { 0267 q->setErrorMsg(QStringLiteral("Found orphaned collections")); 0268 } 0269 } 0270 0271 void EwsFetchFoldersJobPrivate::buildChildCollectionList(const Collection &col) 0272 { 0273 Q_Q(EwsFetchFoldersJob); 0274 0275 const QStringList children = mParentMap.values(col.remoteId()); 0276 for (const QString &childId : children) { 0277 Collection child(mCollectionMap.take(childId)); 0278 child.setParentCollection(col); 0279 q->mFolders.append(child); 0280 buildChildCollectionList(child); 0281 } 0282 } 0283 0284 Collection EwsFetchFoldersJobPrivate::createFolderCollection(const EwsFolder &folder) 0285 { 0286 Collection collection; 0287 collection.setName(folder[EwsFolderFieldDisplayName].toString()); 0288 QStringList mimeTypes; 0289 QString contClass = folder[propPidTagContainerClass].toString(); 0290 mimeTypes.append(Collection::mimeType()); 0291 switch (folder.type()) { 0292 case EwsFolderTypeCalendar: 0293 mimeTypes.append(KCalendarCore::Event::eventMimeType()); 0294 break; 0295 case EwsFolderTypeContacts: 0296 mimeTypes.append(KContacts::Addressee::mimeType()); 0297 mimeTypes.append(KContacts::ContactGroup::mimeType()); 0298 break; 0299 case EwsFolderTypeTasks: 0300 mimeTypes.append(KCalendarCore::Todo::todoMimeType()); 0301 break; 0302 case EwsFolderTypeMail: 0303 if (contClass == QLatin1StringView("IPF.Note") || contClass.isEmpty()) { 0304 mimeTypes.append(KMime::Message::mimeType()); 0305 } 0306 break; 0307 default: 0308 break; 0309 } 0310 collection.setContentMimeTypes(mimeTypes); 0311 Collection::Rights colRights; 0312 auto ewsRights = folder[EwsFolderFieldEffectiveRights].value<EwsEffectiveRights>(); 0313 // FIXME: For now full read/write support is only implemented for e-mail. In order to avoid 0314 // potential problems block write access to all other folder types. 0315 if (folder.type() == EwsFolderTypeMail) { 0316 if (ewsRights.canDelete()) { 0317 colRights |= Collection::CanDeleteCollection | Collection::CanDeleteItem; 0318 } 0319 if (ewsRights.canModify()) { 0320 colRights |= Collection::CanChangeCollection | Collection::CanChangeItem; 0321 } 0322 if (ewsRights.canCreateContents()) { 0323 colRights |= Collection::CanCreateItem; 0324 } 0325 if (ewsRights.canCreateHierarchy()) { 0326 colRights |= Collection::CanCreateCollection; 0327 } 0328 } 0329 collection.setRights(colRights); 0330 auto id = folder[EwsFolderFieldFolderId].value<EwsId>(); 0331 collection.setRemoteId(id.id()); 0332 collection.setRemoteRevision(id.changeKey()); 0333 0334 return collection; 0335 } 0336 0337 EwsFetchFoldersJob::EwsFetchFoldersJob(EwsClient &client, const Akonadi::Collection &rootCollection, QObject *parent) 0338 : EwsJob(parent) 0339 , d_ptr(new EwsFetchFoldersJobPrivate(this, client, rootCollection)) 0340 { 0341 qRegisterMetaType<EwsId::List>(); 0342 } 0343 0344 EwsFetchFoldersJob::~EwsFetchFoldersJob() = default; 0345 0346 void EwsFetchFoldersJob::start() 0347 { 0348 Q_D(const EwsFetchFoldersJob); 0349 0350 auto syncFoldersReq = new EwsSyncFolderHierarchyRequest(d->mClient, this); 0351 syncFoldersReq->setFolderId(EwsId(EwsDIdMsgFolderRoot)); 0352 EwsFolderShape shape; 0353 shape << propPidTagContainerClass; 0354 shape << EwsPropertyField(QStringLiteral("folder:EffectiveRights")); 0355 shape << EwsPropertyField(QStringLiteral("folder:ParentFolderId")); 0356 syncFoldersReq->setFolderShape(shape); 0357 if (!mSyncState.isNull()) { 0358 syncFoldersReq->setSyncState(mSyncState); 0359 } 0360 connect(syncFoldersReq, &EwsSyncFolderHierarchyRequest::result, d, &EwsFetchFoldersJobPrivate::remoteFolderFullFetchDone); 0361 // Don't add this as a subjob as the error is handled in its own way rather than throwing an 0362 // error code to the parent. 0363 0364 syncFoldersReq->start(); 0365 } 0366 0367 #include "moc_ewsfetchfoldersjob.cpp"