File indexing completed on 2024-06-23 05:07:03

0001 /*
0002     SPDX-FileCopyrightText: 2014 Daniel Vrátil <dvratil@redhat.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "tagmodifyhandler.h"
0008 
0009 #include "connection.h"
0010 #include "shared/akranges.h"
0011 #include "storage/datastore.h"
0012 #include "storage/querybuilder.h"
0013 #include "tagfetchhelper.h"
0014 
0015 #include "private/imapset_p.h"
0016 
0017 using namespace Akonadi;
0018 using namespace Akonadi::Server;
0019 using namespace AkRanges;
0020 
0021 TagModifyHandler::TagModifyHandler(AkonadiServer &akonadi)
0022     : Handler(akonadi)
0023 {
0024 }
0025 
0026 bool TagModifyHandler::parseStream()
0027 {
0028     const auto &cmd = Protocol::cmdCast<Protocol::ModifyTagCommand>(m_command);
0029 
0030     Tag changedTag = Tag::retrieveById(cmd.tagId());
0031     if (!changedTag.isValid()) {
0032         return failureResponse("No such tag");
0033     }
0034 
0035     QSet<QByteArray> changes;
0036 
0037     // Retrieve all tag's attributes
0038     const TagAttribute::List attributes = TagAttribute::retrieveFiltered(TagAttribute::tagIdFullColumnName(), cmd.tagId());
0039     const auto attributesMap = attributes | Views::transform([](const auto &attr) {
0040                                    return std::make_pair(attr.type(), attr);
0041                                })
0042         | Actions::toQMap;
0043 
0044     if (cmd.modifiedParts() & Protocol::ModifyTagCommand::ParentId) {
0045         if (cmd.parentId() != changedTag.parentId()) {
0046             changedTag.setParentId(cmd.parentId());
0047             changes << AKONADI_PARAM_PARENT;
0048         }
0049     }
0050 
0051     if (cmd.modifiedParts() & Protocol::ModifyTagCommand::Type) {
0052         TagType type = TagType::retrieveById(changedTag.typeId());
0053         const QString newTypeName = QString::fromUtf8(cmd.type());
0054         if (newTypeName != type.name()) {
0055             const TagType newType = TagType::retrieveByNameOrCreate(newTypeName);
0056             if (!newType.isValid()) {
0057                 return failureResponse("Failed to create new tag type");
0058             }
0059             changedTag.setTagType(newType);
0060             changes << AKONADI_PARAM_MIMETYPE;
0061         }
0062     }
0063 
0064     bool tagRemoved = false;
0065     if (cmd.modifiedParts() & Protocol::ModifyTagCommand::RemoteId) {
0066         if (!connection()->context().resource().isValid()) {
0067             return failureResponse("Only resources can change tag remote ID");
0068         }
0069 
0070         // Simply using remove() doesn't work since we need two arguments
0071         QueryBuilder qb(TagRemoteIdResourceRelation::tableName(), QueryBuilder::Delete);
0072         qb.addValueCondition(TagRemoteIdResourceRelation::tagIdColumn(), Query::Equals, cmd.tagId());
0073         qb.addValueCondition(TagRemoteIdResourceRelation::resourceIdColumn(), Query::Equals, connection()->context().resource().id());
0074         qb.exec();
0075 
0076         if (!cmd.remoteId().isEmpty()) {
0077             TagRemoteIdResourceRelation remoteIdRelation;
0078             remoteIdRelation.setRemoteId(QString::fromUtf8(cmd.remoteId()));
0079             remoteIdRelation.setResourceId(connection()->context().resource().id());
0080             remoteIdRelation.setTag(changedTag);
0081             if (!remoteIdRelation.insert()) {
0082                 return failureResponse("Failed to insert remotedid resource relation");
0083             }
0084         } else {
0085             const int tagRidsCount = TagRemoteIdResourceRelation::count(TagRemoteIdResourceRelation::tagIdColumn(), changedTag.id());
0086             // We just removed the last RID of the tag, which means that no other
0087             // resource owns this tag, so we have to remove it to simulate tag
0088             // removal
0089             if (tagRidsCount == 0) {
0090                 if (!storageBackend()->removeTags(Tag::List() << changedTag)) {
0091                     return failureResponse("Failed to remove tag");
0092                 }
0093                 tagRemoved = true;
0094             }
0095         }
0096         // Do not notify about remoteid changes, otherwise we bounce back and forth
0097         // between resources recording it's change and updating the remote id.
0098     }
0099 
0100     if (cmd.modifiedParts() & Protocol::ModifyTagCommand::RemovedAttributes) {
0101         const auto attrNames = cmd.removedAttributes();
0102         for (const QByteArray &attrName : attrNames) {
0103             TagAttribute attribute = attributesMap.value(attrName);
0104             TagAttribute::remove(attribute.id());
0105             changes << attrName;
0106         }
0107     }
0108 
0109     if (cmd.modifiedParts() & Protocol::ModifyTagCommand::Attributes) {
0110         const QMap<QByteArray, QByteArray> attrs = cmd.attributes();
0111         for (auto iter = attrs.cbegin(), end = attrs.cend(); iter != end; ++iter) {
0112             if (attributesMap.contains(iter.key())) {
0113                 TagAttribute attribute = attributesMap.value(iter.key());
0114                 attribute.setValue(iter.value());
0115                 if (!attribute.update()) {
0116                     return failureResponse("Failed to update attribute");
0117                 }
0118             } else {
0119                 TagAttribute attribute;
0120                 attribute.setTagId(cmd.tagId());
0121                 attribute.setType(iter.key());
0122                 attribute.setValue(iter.value());
0123                 if (!attribute.insert()) {
0124                     return failureResponse("Failed to insert attribute");
0125                 }
0126             }
0127             changes << iter.key();
0128         }
0129     }
0130 
0131     if (!tagRemoved) {
0132         if (!changedTag.update()) {
0133             return failureResponse("Failed to store changes");
0134         }
0135         if (!changes.isEmpty()) {
0136             storageBackend()->notificationCollector()->tagChanged(changedTag);
0137         }
0138 
0139         ImapSet set;
0140         set.add(QList<qint64>() << cmd.tagId());
0141 
0142         Protocol::TagFetchScope fetchScope;
0143         fetchScope.setFetchRemoteID(true);
0144         fetchScope.setFetchAllAttributes(true);
0145 
0146         Scope scope;
0147         scope.setUidSet(set);
0148         TagFetchHelper helper(connection(), scope, fetchScope);
0149         if (!helper.fetchTags()) {
0150             return failureResponse("Failed to fetch response");
0151         }
0152     } else {
0153         successResponse<Protocol::DeleteTagResponse>();
0154     }
0155 
0156     return successResponse<Protocol::ModifyTagResponse>();
0157 }