File indexing completed on 2024-05-19 05:13:19
0001 /* 0002 * SPDX-FileCopyrightText: 2014 Daniel Vrátil <dvratil@redhat.com> 0003 * 0004 * SPDX-License-Identifier: LGPL-2.1-or-later 0005 * 0006 */ 0007 0008 #include "tagpropertiesdialog.h" 0009 #include <Akonadi/DbAccess> 0010 0011 #include <KLocalizedString> 0012 0013 #include <Akonadi/AttributeFactory> 0014 #include <QDialogButtonBox> 0015 #include <QSqlError> 0016 #include <QSqlQuery> 0017 0018 using namespace Akonadi; 0019 0020 TagPropertiesDialog::TagPropertiesDialog(QWidget *parent) 0021 : QDialog(parent) 0022 { 0023 setupUi(); 0024 } 0025 0026 TagPropertiesDialog::TagPropertiesDialog(const Akonadi::Tag &tag, QWidget *parent) 0027 : QDialog(parent) 0028 , mTag(tag) 0029 , mChanged(false) 0030 { 0031 setupUi(); 0032 } 0033 0034 TagPropertiesDialog::~TagPropertiesDialog() = default; 0035 0036 Tag TagPropertiesDialog::tag() const 0037 { 0038 return mTag; 0039 } 0040 0041 bool TagPropertiesDialog::changed() const 0042 { 0043 return mChanged; 0044 } 0045 0046 void TagPropertiesDialog::setupUi() 0047 { 0048 auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); 0049 QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); 0050 okButton->setDefault(true); 0051 okButton->setShortcut(Qt::CTRL | Qt::Key_Return); 0052 connect(buttonBox, &QDialogButtonBox::accepted, this, &TagPropertiesDialog::slotAccept); 0053 connect(buttonBox, &QDialogButtonBox::rejected, this, &TagPropertiesDialog::reject); 0054 0055 auto widget = new QWidget(this); 0056 ui.setupUi(widget); 0057 auto mainLayout = new QVBoxLayout(this); 0058 mainLayout->addWidget(widget); 0059 mainLayout->addWidget(buttonBox); 0060 0061 connect(ui.addAttrButton, &QPushButton::clicked, this, &TagPropertiesDialog::addAttributeClicked); 0062 connect(ui.deleteAttrButton, &QPushButton::clicked, this, &TagPropertiesDialog::deleteAttributeClicked); 0063 0064 connect(ui.addRIDButton, &QPushButton::clicked, this, &TagPropertiesDialog::addRIDClicked); 0065 connect(ui.deleteRIDButton, &QPushButton::clicked, this, &TagPropertiesDialog::deleteRIDClicked); 0066 0067 Attribute::List attributes = mTag.attributes(); 0068 mAttributesModel = new QStandardItemModel(attributes.size(), 2, this); 0069 connect(mAttributesModel, &QStandardItemModel::itemChanged, this, &TagPropertiesDialog::attributeChanged); 0070 QStringList labels; 0071 labels << i18n("Attribute") << i18n("Value"); 0072 mAttributesModel->setHorizontalHeaderLabels(labels); 0073 0074 mRemoteIdsModel = new QStandardItemModel(this); 0075 connect(mRemoteIdsModel, &QStandardItemModel::itemChanged, this, &TagPropertiesDialog::remoteIdChanged); 0076 mRemoteIdsModel->setColumnCount(2); 0077 labels.clear(); 0078 labels << i18n("Resource") << i18n("Remote ID"); 0079 mRemoteIdsModel->setHorizontalHeaderLabels(labels); 0080 ui.ridsView->setModel(mRemoteIdsModel); 0081 0082 if (mTag.isValid()) { 0083 ui.idLabel->setText(QString::number(mTag.id())); 0084 ui.typeEdit->setText(QLatin1StringView(mTag.type())); 0085 ui.gidEdit->setText(QLatin1StringView(mTag.gid())); 0086 ui.parentIdLabel->setText(QString::number(mTag.parent().id())); 0087 0088 for (int i = 0; i < attributes.count(); ++i) { 0089 QModelIndex index = mAttributesModel->index(i, 0); 0090 Q_ASSERT(index.isValid()); 0091 mAttributesModel->setData(index, QLatin1StringView(attributes[i]->type())); 0092 mAttributesModel->item(i, 0)->setEditable(false); 0093 index = mAttributesModel->index(i, 1); 0094 Q_ASSERT(index.isValid()); 0095 mAttributesModel->setData(index, QLatin1StringView(attributes[i]->serialized())); 0096 mAttributesModel->item(i, 1)->setEditable(true); 0097 } 0098 0099 { 0100 // There is (intentionally) no way to retrieve Tag RID for another 0101 // resource than in the current context. Since Akonadi Console has 0102 // not resource context at all, we need to retrieve the IDs the hard way 0103 QSqlQuery query(DbAccess::database()); 0104 query.prepare( 0105 QStringLiteral("SELECT ResourceTable.name, TagRemoteIdResourceRelationTable.remoteId " 0106 "FROM TagRemoteIdResourceRelationTable " 0107 "LEFT JOIN ResourceTable ON ResourceTable.id = TagRemoteIdResourceRelationTable.resourceId " 0108 "WHERE TagRemoteIdResourceRelationTable.tagid = ?")); 0109 query.addBindValue(mTag.id()); 0110 if (query.exec()) { 0111 while (query.next()) { 0112 QList<QStandardItem *> items; 0113 auto item = new QStandardItem(query.value(0).toString()); 0114 item->setEditable(false); 0115 items << item; 0116 item = new QStandardItem(query.value(1).toString()); 0117 item->setEditable(true); 0118 items << item; 0119 mRemoteIdsModel->appendRow(items); 0120 } 0121 } else { 0122 qCritical() << query.executedQuery(); 0123 qCritical() << query.lastError().text(); 0124 } 0125 } 0126 } else { 0127 ui.idLabel->setVisible(false); 0128 ui.idLabelBuddy->setVisible(false); 0129 if (mTag.parent().isValid()) { 0130 ui.parentIdLabel->setText(QString::number(mTag.parent().id())); 0131 } else { 0132 ui.parentIdLabel->setVisible(false); 0133 ui.parentIdLabelBuddy->setVisible(false); 0134 } 0135 // Since we are using direct SQL to update RIDs, we cannot do this 0136 // when creating a new Tag, because the tag is created by caller after 0137 // this dialog is closed 0138 ui.tabWidget->setTabEnabled(2, false); 0139 } 0140 0141 ui.attrsView->setModel(mAttributesModel); 0142 } 0143 0144 void TagPropertiesDialog::addAttributeClicked() 0145 { 0146 const QString newType = ui.newAttrEdit->text(); 0147 if (newType.isEmpty()) { 0148 return; 0149 } 0150 ui.newAttrEdit->clear(); 0151 0152 mChangedAttrs.insert(newType); 0153 mRemovedAttrs.remove(newType); 0154 mChanged = true; 0155 0156 const int row = mAttributesModel->rowCount(); 0157 mAttributesModel->insertRow(row); 0158 const QModelIndex index = mAttributesModel->index(row, 0); 0159 Q_ASSERT(index.isValid()); 0160 mAttributesModel->setData(index, newType); 0161 mAttributesModel->item(row, 0)->setEditable(false); 0162 mAttributesModel->setItem(row, 1, new QStandardItem); 0163 mAttributesModel->item(row, 1)->setEditable(true); 0164 } 0165 0166 void TagPropertiesDialog::deleteAttributeClicked() 0167 { 0168 const QModelIndexList selection = ui.attrsView->selectionModel()->selectedRows(); 0169 if (selection.count() != 1) { 0170 return; 0171 } 0172 const QString attr = selection.first().data().toString(); 0173 mChangedAttrs.remove(attr); 0174 mRemovedAttrs.insert(attr); 0175 mChanged = true; 0176 mAttributesModel->removeRow(selection.first().row()); 0177 } 0178 0179 void TagPropertiesDialog::attributeChanged(QStandardItem *item) 0180 { 0181 const QString attr = mAttributesModel->data(mAttributesModel->index(item->row(), 0)).toString(); 0182 mRemovedAttrs.remove(attr); 0183 mChangedAttrs.insert(attr); 0184 mChanged = true; 0185 } 0186 0187 void TagPropertiesDialog::addRIDClicked() 0188 { 0189 const QString newResource = ui.newRIDEdit->text(); 0190 if (newResource.isEmpty()) { 0191 return; 0192 } 0193 ui.newRIDEdit->clear(); 0194 0195 mChangedRIDs.insert(newResource); 0196 mRemovedRIDs.remove(newResource); 0197 // Don't change mChanged here, we will handle this internally 0198 0199 const int row = mRemoteIdsModel->rowCount(); 0200 mRemoteIdsModel->insertRow(row); 0201 const QModelIndex index = mRemoteIdsModel->index(row, 0); 0202 Q_ASSERT(index.isValid()); 0203 mRemoteIdsModel->setData(index, newResource); 0204 mRemoteIdsModel->item(row, 0)->setEditable(false); 0205 mRemoteIdsModel->setItem(row, 1, new QStandardItem); 0206 mRemoteIdsModel->item(row, 1)->setEditable(true); 0207 } 0208 0209 void TagPropertiesDialog::deleteRIDClicked() 0210 { 0211 const QModelIndexList selection = ui.ridsView->selectionModel()->selectedRows(); 0212 if (selection.count() != 1) { 0213 return; 0214 } 0215 const QString res = selection.first().data().toString(); 0216 mChangedRIDs.remove(res); 0217 mRemovedRIDs.insert(res); 0218 // Don't change mChanged here, we will handle this internally 0219 mRemoteIdsModel->removeRow(selection.first().row()); 0220 } 0221 0222 void TagPropertiesDialog::remoteIdChanged(QStandardItem *item) 0223 { 0224 const QString res = mRemoteIdsModel->data(mRemoteIdsModel->index(item->row(), 0)).toString(); 0225 mRemovedRIDs.remove(res); 0226 mChangedRIDs.insert(res); 0227 // Don't change mChanged here, we will handle this internally 0228 } 0229 0230 void TagPropertiesDialog::slotAccept() 0231 { 0232 mChanged |= (mTag.type() != ui.typeEdit->text().toLatin1()); 0233 mChanged |= (mTag.gid() != ui.gidEdit->text().toLatin1()); 0234 0235 if (!mChanged && mChangedRIDs.isEmpty() && mRemovedRIDs.isEmpty()) { 0236 QDialog::accept(); 0237 return; 0238 } 0239 0240 mTag.setType(ui.typeEdit->text().toLatin1()); 0241 mTag.setGid(ui.gidEdit->text().toLatin1()); 0242 0243 for (const QString &removedAttr : std::as_const(mRemovedAttrs)) { 0244 mTag.removeAttribute(removedAttr.toLatin1()); 0245 } 0246 for (int i = 0; i < mAttributesModel->rowCount(); ++i) { 0247 const QModelIndex typeIndex = mAttributesModel->index(i, 0); 0248 Q_ASSERT(typeIndex.isValid()); 0249 if (!mChangedAttrs.contains(typeIndex.data().toString())) { 0250 continue; 0251 } 0252 const QModelIndex valueIndex = mAttributesModel->index(i, 1); 0253 Attribute *attr = AttributeFactory::createAttribute(mAttributesModel->data(typeIndex).toString().toLatin1()); 0254 if (!attr) { 0255 continue; 0256 } 0257 attr->deserialize(mAttributesModel->data(valueIndex).toString().toLatin1()); 0258 mTag.addAttribute(attr); 0259 } 0260 0261 bool queryOK = true; 0262 if (mTag.isValid() && (!mRemovedRIDs.isEmpty() || !mChangedRIDs.isEmpty())) { 0263 DbAccess::database().transaction(); 0264 } 0265 0266 if (mTag.isValid() && !mRemovedRIDs.isEmpty()) { 0267 QSqlQuery query(DbAccess::database()); 0268 QString queryStr = QStringLiteral( 0269 "DELETE FROM TagRemoteIdResourceRelationTable " 0270 "WHERE tagId = ? AND " 0271 "resourceId IN (SELECT id " 0272 "FROM ResourceTable " 0273 "WHERE "); 0274 QStringList conds; 0275 for (int i = 0; i < mRemovedRIDs.count(); ++i) { 0276 conds << QStringLiteral("name = ?"); 0277 } 0278 queryStr += conds.join(QLatin1StringView(" OR ")) + QLatin1Char(')'); 0279 query.prepare(queryStr); 0280 query.addBindValue(mTag.id()); 0281 for (const QString &removedRid : std::as_const(mRemovedRIDs)) { 0282 query.addBindValue(removedRid); 0283 } 0284 if (!query.exec()) { 0285 qCritical() << query.executedQuery(); 0286 qCritical() << query.lastError().text(); 0287 queryOK = false; 0288 } 0289 } 0290 if (queryOK && mTag.isValid() && !mChangedRIDs.isEmpty()) { 0291 QMap<QString, qint64> resourceNameToIdMap; 0292 QList<qint64> existingResourceRecords; 0293 { 0294 QSqlQuery query(DbAccess::database()); 0295 QString queryStr = QStringLiteral("SELECT id, name FROM ResourceTable WHERE "); 0296 QStringList conds; 0297 for (int i = 0; i < mChangedRIDs.count(); ++i) { 0298 conds << QStringLiteral("name = ?"); 0299 } 0300 queryStr += conds.join(QLatin1StringView(" OR ")); 0301 query.prepare(queryStr); 0302 for (const QString &res : std::as_const(mChangedRIDs)) { 0303 query.addBindValue(res); 0304 } 0305 if (!query.exec()) { 0306 qCritical() << query.executedQuery(); 0307 qCritical() << query.lastError().text(); 0308 queryOK = false; 0309 } 0310 0311 while (query.next()) { 0312 resourceNameToIdMap[query.value(1).toString()] = query.value(0).toLongLong(); 0313 } 0314 } 0315 0316 // This is a workaround for PSQL not supporting UPSERTs 0317 { 0318 QSqlQuery query(DbAccess::database()); 0319 query.prepare(QStringLiteral("SELECT resourceId FROM TagRemoteIdResourceRelationTable WHERE tagId = ?")); 0320 query.addBindValue(mTag.id()); 0321 if (!query.exec()) { 0322 qCritical() << query.executedQuery(); 0323 qCritical() << query.lastError().text(); 0324 queryOK = false; 0325 } 0326 0327 while (query.next()) { 0328 existingResourceRecords << query.value(0).toLongLong(); 0329 } 0330 } 0331 0332 for (int i = 0; i < mRemoteIdsModel->rowCount() && queryOK; ++i) { 0333 const QModelIndex resIndex = mRemoteIdsModel->index(i, 0); 0334 Q_ASSERT(resIndex.isValid()); 0335 if (!mChangedRIDs.contains(resIndex.data().toString())) { 0336 continue; 0337 } 0338 const QModelIndex valueIndex = mRemoteIdsModel->index(i, 1); 0339 0340 QSqlQuery query(DbAccess::database()); 0341 const qlonglong resourceId = resourceNameToIdMap[resIndex.data().toString()]; 0342 if (existingResourceRecords.contains(resourceId)) { 0343 query.prepare(QStringLiteral("UPDATE TagRemoteIdResourceRelationTable SET remoteId = ? WHERE tagId = ? AND resourceId = ?")); 0344 query.addBindValue(valueIndex.data().toString()); 0345 query.addBindValue(mTag.id()); 0346 query.addBindValue(resourceId); 0347 } else { 0348 query.prepare(QStringLiteral("INSERT INTO TagRemoteIdResourceRelationTable (tagId, resourceId, remoteId) VALUES (?, ?, ?)")); 0349 query.addBindValue(mTag.id()); 0350 query.addBindValue(resourceId); 0351 query.addBindValue(valueIndex.data().toString()); 0352 } 0353 if (!query.exec()) { 0354 qCritical() << query.executedQuery(); 0355 qCritical() << query.lastError().text(); 0356 queryOK = false; 0357 break; 0358 } 0359 } 0360 } 0361 0362 if (mTag.isValid() && (!mRemovedRIDs.isEmpty() || !mChangedRIDs.isEmpty())) { 0363 if (queryOK) { 0364 DbAccess::database().commit(); 0365 } else { 0366 DbAccess::database().rollback(); 0367 } 0368 } 0369 0370 QDialog::accept(); 0371 } 0372 0373 #include "moc_tagpropertiesdialog.cpp"