File indexing completed on 2024-06-23 05:07:00
0001 /* 0002 SPDX-FileCopyrightText: 2006 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "collectionmodifyhandler.h" 0008 0009 #include "akonadi.h" 0010 #include "akonadiserver_debug.h" 0011 #include "cachecleaner.h" 0012 #include "connection.h" 0013 #include "handlerhelper.h" 0014 #include "intervalcheck.h" 0015 #include "search/searchmanager.h" 0016 #include "shared/akranges.h" 0017 #include "storage/collectionqueryhelper.h" 0018 #include "storage/datastore.h" 0019 #include "storage/itemretriever.h" 0020 #include "storage/selectquerybuilder.h" 0021 #include "storage/transaction.h" 0022 0023 using namespace Akonadi; 0024 using namespace Akonadi::Server; 0025 using namespace AkRanges; 0026 0027 CollectionModifyHandler::CollectionModifyHandler(AkonadiServer &akonadi) 0028 : Handler(akonadi) 0029 { 0030 } 0031 0032 bool CollectionModifyHandler::parseStream() 0033 { 0034 const auto &cmd = Protocol::cmdCast<Protocol::ModifyCollectionCommand>(m_command); 0035 0036 Collection collection = HandlerHelper::collectionFromScope(cmd.collection(), connection()->context()); 0037 if (!collection.isValid()) { 0038 return failureResponse("No such collection"); 0039 } 0040 0041 CacheCleanerInhibitor inhibitor(akonadi(), false); 0042 0043 if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::ParentID) { 0044 const Collection newParent = Collection::retrieveById(cmd.parentId()); 0045 if (newParent.isValid() && collection.parentId() != newParent.id() && collection.resourceId() != newParent.resourceId()) { 0046 inhibitor.inhibit(); 0047 ItemRetriever retriever(akonadi().itemRetrievalManager(), connection(), connection()->context()); 0048 retriever.setCollection(collection, true); 0049 retriever.setRetrieveFullPayload(true); 0050 if (!retriever.exec()) { 0051 throw HandlerException(retriever.lastError()); 0052 } 0053 } 0054 } 0055 0056 DataStore *db = connection()->storageBackend(); 0057 Transaction transaction(db, QStringLiteral("MODIFY")); 0058 QList<QByteArray> changes; 0059 0060 if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::MimeTypes) { 0061 QStringList mts = cmd.mimeTypes(); 0062 const MimeType::List currentMts = collection.mimeTypes(); 0063 bool equal = true; 0064 for (const MimeType ¤tMt : currentMts) { 0065 const int removeMts = mts.removeAll(currentMt.name()); 0066 if (removeMts > 0) { 0067 continue; 0068 } 0069 equal = false; 0070 if (!collection.removeMimeType(currentMt)) { 0071 return failureResponse("Unable to remove collection mimetype"); 0072 } 0073 } 0074 if (!db->appendMimeTypeForCollection(collection.id(), mts)) { 0075 return failureResponse("Unable to add collection mimetypes"); 0076 } 0077 if (!equal || !mts.isEmpty()) { 0078 changes.append(AKONADI_PARAM_MIMETYPE); 0079 } 0080 } 0081 0082 if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::CachePolicy) { 0083 bool changed = false; 0084 const Protocol::CachePolicy newCp = cmd.cachePolicy(); 0085 if (collection.cachePolicyCacheTimeout() != newCp.cacheTimeout()) { 0086 collection.setCachePolicyCacheTimeout(newCp.cacheTimeout()); 0087 changed = true; 0088 } 0089 if (collection.cachePolicyCheckInterval() != newCp.checkInterval()) { 0090 collection.setCachePolicyCheckInterval(newCp.checkInterval()); 0091 changed = true; 0092 } 0093 if (collection.cachePolicyInherit() != newCp.inherit()) { 0094 collection.setCachePolicyInherit(newCp.inherit()); 0095 changed = true; 0096 } 0097 0098 QStringList parts = newCp.localParts(); 0099 std::sort(parts.begin(), parts.end()); 0100 const QString localParts = parts.join(QLatin1Char(' ')); 0101 if (collection.cachePolicyLocalParts() != localParts) { 0102 collection.setCachePolicyLocalParts(localParts); 0103 changed = true; 0104 } 0105 if (collection.cachePolicySyncOnDemand() != newCp.syncOnDemand()) { 0106 collection.setCachePolicySyncOnDemand(newCp.syncOnDemand()); 0107 changed = true; 0108 } 0109 0110 if (changed) { 0111 changes.append(AKONADI_PARAM_CACHEPOLICY); 0112 } 0113 } 0114 0115 if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::Name) { 0116 if (cmd.name() != collection.name()) { 0117 if (!CollectionQueryHelper::hasAllowedName(collection, cmd.name(), collection.parentId())) { 0118 return failureResponse("Collection with the same name exists already"); 0119 } 0120 collection.setName(cmd.name()); 0121 changes.append(AKONADI_PARAM_NAME); 0122 } 0123 } 0124 0125 if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::ParentID) { 0126 if (collection.parentId() != cmd.parentId()) { 0127 if (!db->moveCollection(collection, Collection::retrieveById(cmd.parentId()))) { 0128 return failureResponse("Unable to reparent collection"); 0129 } 0130 changes.append(AKONADI_PARAM_PARENT); 0131 } 0132 } 0133 0134 if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::RemoteID) { 0135 if (cmd.remoteId() != collection.remoteId() && !cmd.remoteId().isEmpty()) { 0136 if (!connection()->isOwnerResource(collection)) { 0137 qCWarning(AKONADISERVER_LOG) << "Invalid attempt to modify the collection remoteID from" << collection.remoteId() << "to" << cmd.remoteId(); 0138 return failureResponse("Only resources can modify remote identifiers"); 0139 } 0140 collection.setRemoteId(cmd.remoteId()); 0141 changes.append(AKONADI_PARAM_REMOTEID); 0142 } 0143 } 0144 0145 if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::RemoteRevision) { 0146 if (cmd.remoteRevision() != collection.remoteRevision()) { 0147 collection.setRemoteRevision(cmd.remoteRevision()); 0148 changes.append(AKONADI_PARAM_REMOTEREVISION); 0149 } 0150 } 0151 0152 if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::PersistentSearch) { 0153 bool changed = false; 0154 if (cmd.persistentSearchQuery() != collection.queryString()) { 0155 collection.setQueryString(cmd.persistentSearchQuery()); 0156 changed = true; 0157 } 0158 0159 QList<QByteArray> queryAttributes = collection.queryAttributes().toUtf8().split(' '); 0160 if (cmd.persistentSearchRemote() != queryAttributes.contains(AKONADI_PARAM_REMOTE)) { 0161 if (cmd.persistentSearchRemote()) { 0162 queryAttributes.append(AKONADI_PARAM_REMOTE); 0163 } else { 0164 queryAttributes.removeOne(AKONADI_PARAM_REMOTE); 0165 } 0166 changed = true; 0167 } 0168 if (cmd.persistentSearchRecursive() != queryAttributes.contains(AKONADI_PARAM_RECURSIVE)) { 0169 if (cmd.persistentSearchRecursive()) { 0170 queryAttributes.append(AKONADI_PARAM_RECURSIVE); 0171 } else { 0172 queryAttributes.removeOne(AKONADI_PARAM_RECURSIVE); 0173 } 0174 changed = true; 0175 } 0176 if (changed) { 0177 collection.setQueryAttributes(QString::fromLatin1(queryAttributes.join(' '))); 0178 } 0179 0180 QList<qint64> inCols = cmd.persistentSearchCollections(); 0181 std::sort(inCols.begin(), inCols.end()); 0182 const auto cols = inCols | Views::transform([](const auto col) { 0183 return QString::number(col); 0184 }) 0185 | Actions::toQList; 0186 const QString colStr = cols.join(QLatin1Char(' ')); 0187 if (colStr != collection.queryCollections()) { 0188 collection.setQueryCollections(colStr); 0189 changed = true; 0190 } 0191 0192 if (changed || cmd.modifiedParts() & Protocol::ModifyCollectionCommand::MimeTypes) { 0193 changes.append(AKONADI_PARAM_PERSISTENTSEARCH); 0194 } 0195 } 0196 0197 if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::ListPreferences) { 0198 if (cmd.enabled() != collection.enabled()) { 0199 collection.setEnabled(cmd.enabled()); 0200 changes.append(AKONADI_PARAM_ENABLED); 0201 } 0202 if (cmd.syncPref() != static_cast<Tristate>(collection.syncPref())) { 0203 collection.setSyncPref(static_cast<Collection::Tristate>(cmd.syncPref())); 0204 changes.append(AKONADI_PARAM_SYNC); 0205 } 0206 if (cmd.displayPref() != static_cast<Tristate>(collection.displayPref())) { 0207 collection.setDisplayPref(static_cast<Collection::Tristate>(cmd.displayPref())); 0208 changes.append(AKONADI_PARAM_DISPLAY); 0209 } 0210 if (cmd.indexPref() != static_cast<Tristate>(collection.indexPref())) { 0211 collection.setIndexPref(static_cast<Collection::Tristate>(cmd.indexPref())); 0212 changes.append(AKONADI_PARAM_INDEX); 0213 } 0214 } 0215 0216 if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::RemovedAttributes) { 0217 const auto attrs = cmd.removedAttributes(); 0218 for (const QByteArray &attr : attrs) { 0219 if (db->removeCollectionAttribute(collection, attr)) { 0220 changes.append(attr); 0221 } 0222 } 0223 } 0224 0225 if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::Attributes) { 0226 const QMap<QByteArray, QByteArray> attrs = cmd.attributes(); 0227 for (auto iter = attrs.cbegin(), end = attrs.cend(); iter != end; ++iter) { 0228 SelectQueryBuilder<CollectionAttribute> qb; 0229 qb.addValueCondition(CollectionAttribute::collectionIdColumn(), Query::Equals, collection.id()); 0230 qb.addValueCondition(CollectionAttribute::typeColumn(), Query::Equals, iter.key()); 0231 if (!qb.exec()) { 0232 return failureResponse("Unable to retrieve collection attribute"); 0233 } 0234 0235 const CollectionAttribute::List attrsList = qb.result(); 0236 if (attrsList.isEmpty()) { 0237 CollectionAttribute newAttr; 0238 newAttr.setCollectionId(collection.id()); 0239 newAttr.setType(iter.key()); 0240 newAttr.setValue(iter.value()); 0241 if (!newAttr.insert()) { 0242 return failureResponse("Unable to add collection attribute"); 0243 } 0244 changes.append(iter.key()); 0245 } else if (attrsList.size() == 1) { 0246 CollectionAttribute currAttr = attrsList.first(); 0247 if (currAttr.value() == iter.value()) { 0248 continue; 0249 } 0250 currAttr.setValue(iter.value()); 0251 if (!currAttr.update()) { 0252 return failureResponse("Unable to update collection attribute"); 0253 } 0254 changes.append(iter.key()); 0255 } else { 0256 return failureResponse("WTF: more than one attribute with the same name"); 0257 } 0258 } 0259 } 0260 0261 if (!changes.isEmpty()) { 0262 if (collection.hasPendingChanges() && !collection.update()) { 0263 return failureResponse("Unable to update collection"); 0264 } 0265 db->notificationCollector()->collectionChanged(collection, changes); 0266 // For backwards compatibility. Must be after the changed notification (otherwise the compression removes it). 0267 if (changes.contains(AKONADI_PARAM_ENABLED)) { 0268 if (collection.enabled()) { 0269 db->notificationCollector()->collectionSubscribed(collection); 0270 } else { 0271 db->notificationCollector()->collectionUnsubscribed(collection); 0272 } 0273 } 0274 if (!transaction.commit()) { 0275 return failureResponse("Unable to commit transaction"); 0276 } 0277 0278 // Only request Search update AFTER committing the transaction to avoid 0279 // transaction deadlock with SQLite 0280 if (changes.contains(AKONADI_PARAM_PERSISTENTSEARCH)) { 0281 akonadi().searchManager().updateSearch(collection); 0282 } 0283 } 0284 0285 return successResponse<Protocol::ModifyCollectionResponse>(); 0286 }