File indexing completed on 2024-05-19 04:27:38
0001 /* 0002 * SPDX-FileCopyrightText: 2018 Boudewijn Rempt <boud@valdyas.org> 0003 * 0004 * SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "KisResourceModel.h" 0008 0009 #include <QElapsedTimer> 0010 #include <QBuffer> 0011 #include <QImage> 0012 #include <QtSql> 0013 #include <QStringList> 0014 0015 #include <KisResourceLocator.h> 0016 #include <KisResourceCacheDb.h> 0017 0018 #include <KisResourceModelProvider.h> 0019 #include <KisStorageModel.h> 0020 #include <KisTagModel.h> 0021 #include <KisResourceTypes.h> 0022 #include <kis_debug.h> 0023 #include <KisGlobalResourcesInterface.h> 0024 0025 #include "KisResourceQueryMapper.h" 0026 0027 struct KisAllResourcesModel::Private { 0028 QSqlQuery resourcesQuery; 0029 QString resourceType; 0030 int columnCount {StorageActive}; 0031 int cachedRowCount {-1}; 0032 int externalResourcesRemovedCount {0}; 0033 }; 0034 0035 KisAllResourcesModel::KisAllResourcesModel(const QString &resourceType, QObject *parent) 0036 : QAbstractTableModel(parent) 0037 , d(new Private) 0038 { 0039 0040 /// we don't handle KisResourceLocator::storage{Added,Removed} signals 0041 /// here, we use per-resource notifications from KisResourceLocator instead 0042 0043 connect(KisStorageModel::instance(), SIGNAL(storageEnabled(const QString&)), this, SLOT(storageActiveStateChanged(const QString&))); 0044 connect(KisStorageModel::instance(), SIGNAL(storageDisabled(const QString&)), this, SLOT(storageActiveStateChanged(const QString&))); 0045 0046 connect(KisResourceLocator::instance(), SIGNAL(beginExternalResourceImport(QString, int)), this, SLOT(beginExternalResourceImport(QString, int))); 0047 connect(KisResourceLocator::instance(), SIGNAL(endExternalResourceImport(QString)), this, SLOT(endExternalResourceImport(QString))); 0048 0049 connect(KisResourceLocator::instance(), SIGNAL(beginExternalResourceRemove(QString, QVector<int>)), this, SLOT(beginExternalResourceRemove(QString, QVector<int>))); 0050 connect(KisResourceLocator::instance(), SIGNAL(endExternalResourceRemove(QString)), this, SLOT(endExternalResourceRemove(QString))); 0051 connect(KisResourceLocator::instance(), SIGNAL(resourceActiveStateChanged(QString, int)), this, SLOT(slotResourceActiveStateChanged(QString, int))); 0052 0053 d->resourceType = resourceType; 0054 0055 bool r = d->resourcesQuery.prepare("SELECT resources.id\n" 0056 ", resources.storage_id\n" 0057 ", resources.name\n" 0058 ", resources.filename\n" 0059 ", resources.tooltip\n" 0060 ", resources.status\n" 0061 ", resources.md5sum\n" 0062 ", storages.location\n" 0063 ", resource_types.name as resource_type\n" 0064 ", resources.status as resource_active\n" 0065 ", storages.active as storage_active\n" 0066 "FROM resources\n" 0067 ", resource_types\n" 0068 ", storages\n" 0069 "WHERE resources.resource_type_id = resource_types.id\n" 0070 "AND resources.storage_id = storages.id\n" 0071 "AND resource_types.name = :resource_type\n" 0072 "GROUP BY resources.name\n" 0073 ", resources.filename\n" 0074 ", resources.md5sum\n" 0075 "ORDER BY resources.id"); 0076 if (!r) { 0077 qWarning() << "Could not prepare KisAllResourcesModel query" << d->resourcesQuery.lastError(); 0078 } 0079 d->resourcesQuery.bindValue(":resource_type", d->resourceType); 0080 0081 resetQuery(); 0082 } 0083 0084 KisAllResourcesModel::~KisAllResourcesModel() 0085 { 0086 delete d; 0087 } 0088 0089 int KisAllResourcesModel::columnCount(const QModelIndex &parent) const 0090 { 0091 if (parent.isValid()) { 0092 return 0; 0093 } 0094 0095 return d->columnCount; 0096 } 0097 0098 QVariant KisAllResourcesModel::data(const QModelIndex &index, int role) const 0099 { 0100 0101 QVariant v; 0102 if (!index.isValid()) return v; 0103 0104 if (index.row() > rowCount()) return v; 0105 if (index.column() > d->columnCount) return v; 0106 0107 bool pos = const_cast<KisAllResourcesModel*>(this)->d->resourcesQuery.seek(index.row()); 0108 0109 if (pos) { 0110 v = KisResourceQueryMapper::variantFromResourceQuery(d->resourcesQuery, index.column(), role, false); 0111 } 0112 0113 return v; 0114 } 0115 0116 QVariant KisAllResourcesModel::headerData(int section, Qt::Orientation orientation, int role) const 0117 { 0118 if (role != Qt::DisplayRole) { 0119 return {}; 0120 } 0121 if (orientation == Qt::Horizontal) { 0122 switch (section) { 0123 case Id: 0124 return i18n("Id"); 0125 case StorageId: 0126 return i18n("Storage ID"); 0127 case Name: 0128 return i18n("Name"); 0129 case Filename: 0130 return i18n("File Name"); 0131 case Tooltip: 0132 return i18n("Tooltip"); 0133 case Thumbnail: 0134 return i18n("Image"); 0135 case Status: 0136 return i18n("Status"); 0137 case Location: 0138 return i18n("Location"); 0139 case ResourceType: 0140 return i18n("Resource Type"); 0141 case ResourceActive: 0142 return i18n("Active"); 0143 case StorageActive: 0144 return i18n("Storage Active"); 0145 case MD5: 0146 return i18n("md5sum"); 0147 case Tags: 0148 return i18n("Tags"); 0149 case LargeThumbnail: 0150 return i18n("Large Thumbnail"); 0151 case Dirty: 0152 return i18n("Dirty"); 0153 case MetaData: 0154 return i18n("Metadata"); 0155 default: 0156 return QString::number(section); 0157 } 0158 } 0159 return {}; 0160 } 0161 0162 bool KisAllResourcesModel::setData(const QModelIndex &index, const QVariant &value, int role) 0163 { 0164 if (index.isValid() && role == Qt::CheckStateRole && 0165 value.canConvert<bool>()) { 0166 0167 return setResourceActive(index, value.toBool()); 0168 } 0169 0170 return true; 0171 } 0172 0173 Qt::ItemFlags KisAllResourcesModel::flags(const QModelIndex &index) const 0174 { 0175 if (!index.isValid()) { 0176 return Qt::NoItemFlags; 0177 } 0178 return QAbstractTableModel::flags(index) | Qt::ItemIsEditable | Qt::ItemNeverHasChildren; 0179 } 0180 0181 KoResourceSP KisAllResourcesModel::resourceForIndex(QModelIndex index) const 0182 { 0183 KoResourceSP resource = 0; 0184 0185 if (!index.isValid()) return resource; 0186 if (index.row() > rowCount()) return resource; 0187 if (index.column() > d->columnCount) return resource; 0188 0189 bool pos = const_cast<KisAllResourcesModel*>(this)->d->resourcesQuery.seek(index.row()); 0190 if (pos) { 0191 int id = d->resourcesQuery.value("id").toInt(); 0192 resource = resourceForId(id); 0193 } 0194 return resource; 0195 } 0196 0197 KoResourceSP KisAllResourcesModel::resourceForId(int id) const 0198 { 0199 return KisResourceLocator::instance()->resourceForId(id); 0200 } 0201 0202 bool KisAllResourcesModel::resourceExists(const QString &md5, const QString &filename, const QString &name) 0203 { 0204 QSqlQuery q; 0205 0206 // md5 0207 0208 if (!md5.isEmpty()) { 0209 0210 bool r = q.prepare("SELECT resources.id AS id\n" 0211 "FROM resources\n" 0212 "WHERE md5sum = :md5sum"); 0213 if (!r) { 0214 qWarning() << "Could not prepare find resourceExists by md5 query" << q.lastError(); 0215 } 0216 0217 q.bindValue(":mdsum", md5); 0218 0219 r = q.exec(); 0220 0221 if (!r) { 0222 qWarning() << "Could not execute resourceExists by md5 query" << q.lastError(); 0223 } 0224 0225 if (q.first()) { 0226 return true; 0227 } 0228 } 0229 0230 // filename 0231 0232 if (!filename.isEmpty()) { 0233 0234 bool r = q.prepare("SELECT resources.id AS id\n" 0235 "FROM resources\n" 0236 "WHERE filename = :filename"); 0237 if (!r) { 0238 qWarning() << "Could not prepare find resourceExists by filename query" << q.lastError(); 0239 } 0240 0241 q.bindValue(":filename", filename); 0242 0243 r = q.exec(); 0244 0245 if (!r) { 0246 qWarning() << "Could not execute resourceExists by filename query" << q.lastError(); 0247 } 0248 0249 if (q.first()) { 0250 return true; 0251 } 0252 } 0253 0254 // name 0255 0256 if (!name.isEmpty()) { 0257 0258 bool r = q.prepare("SELECT resources.id AS id\n" 0259 "FROM resources\n" 0260 "WHERE name = :name"); 0261 if (!r) { 0262 qWarning() << "Could not prepare find resourceExists by name query" << q.lastError(); 0263 } 0264 0265 q.bindValue(":name", name); 0266 0267 r = q.exec(); 0268 if (!r) { 0269 qWarning() << "Could not execute resourceExists by name query" << q.lastError(); 0270 } 0271 0272 if (q.first()) { 0273 return true; 0274 } 0275 } 0276 0277 // failure 0278 0279 return false; 0280 } 0281 0282 QVector<KoResourceSP> KisAllResourcesModel::resourcesForFilename(QString filename) const 0283 { 0284 QVector<KoResourceSP> resources; 0285 0286 if (filename.isEmpty()) return resources; 0287 0288 QSqlQuery q; 0289 bool r = q.prepare("SELECT resources.id AS id\n" 0290 "FROM resources\n" 0291 ", resource_types\n" 0292 "WHERE resources.resource_type_id = resource_types.id\n" 0293 "AND resources.filename = :resource_filename\n" 0294 "AND resource_types.name = :resource_type\n"); 0295 if (!r) { 0296 qWarning() << "Could not prepare KisAllResourcesModel query for resource name" << q.lastError(); 0297 } 0298 q.bindValue(":resource_filename", filename); 0299 q.bindValue(":resource_type", d->resourceType); 0300 0301 r = q.exec(); 0302 if (!r) { 0303 qWarning() << "Could not select" << d->resourceType << "resources by filename" << q.lastError() << q.boundValues(); 0304 } 0305 0306 while (q.next()) { 0307 int id = q.value("id").toInt(); 0308 KoResourceSP resource = KisResourceLocator::instance()->resourceForId(id); 0309 if (resource) { 0310 resources << resource; 0311 } 0312 0313 } 0314 0315 return resources; 0316 } 0317 0318 QVector<KoResourceSP> KisAllResourcesModel::resourcesForName(const QString &name) const 0319 { 0320 QVector<KoResourceSP> resources; 0321 0322 if (name.isEmpty()) return resources; 0323 0324 KoResourceSP resource = 0; 0325 0326 QSqlQuery q; 0327 bool r = q.prepare("SELECT resources.id AS id\n" 0328 "FROM resources\n" 0329 ", resource_types\n" 0330 "WHERE resources.resource_type_id = resource_types.id\n" 0331 "AND resources.name = :resource_name\n" 0332 "AND resource_types.name = :resource_type\n"); 0333 if (!r) { 0334 qWarning() << "Could not prepare KisAllResourcesModel query for resource name" << q.lastError(); 0335 } 0336 0337 q.bindValue(":resource_type", d->resourceType); 0338 q.bindValue(":resource_name", name); 0339 0340 r = q.exec(); 0341 if (!r) { 0342 qWarning() << "Could not select" << d->resourceType << "resources by name" << q.lastError() << q.boundValues(); 0343 } 0344 0345 while (q.next()) { 0346 int id = q.value("id").toInt(); 0347 resource = KisResourceLocator::instance()->resourceForId(id); 0348 if (resource) { 0349 resources << resource; 0350 } 0351 } 0352 0353 return resources; 0354 } 0355 0356 0357 QVector<KoResourceSP> KisAllResourcesModel::resourcesForMD5(const QString &md5sum) const 0358 { 0359 QVector<KoResourceSP> resources; 0360 0361 if (md5sum.isEmpty()) return resources; 0362 0363 KoResourceSP resource = 0; 0364 0365 QSqlQuery q; 0366 bool r = q.prepare("SELECT resource_id AS id\n" 0367 "FROM versioned_resources\n" 0368 "WHERE md5sum = :md5sum"); 0369 if (!r) { 0370 qWarning() << "Could not prepare KisAllResourcesModel query for resource md5" << q.lastError(); 0371 } 0372 q.bindValue(":md5sum", md5sum); 0373 0374 r = q.exec(); 0375 if (!r) { 0376 qWarning() << "Could not select" << d->resourceType << "resources by md5" << q.lastError() << q.boundValues(); 0377 } 0378 0379 while (q.next()) { 0380 int id = q.value("id").toInt(); 0381 resource = KisResourceLocator::instance()->resourceForId(id); 0382 if (resource) { 0383 resources << resource; 0384 } 0385 } 0386 return resources; 0387 } 0388 0389 QModelIndex KisAllResourcesModel::indexForResource(KoResourceSP resource) const 0390 { 0391 if (!resource || !resource->valid() || resource->resourceId() < 0) return QModelIndex(); 0392 0393 // For now a linear seek to find the first resource with the right id 0394 return indexForResourceId(resource->resourceId()); 0395 } 0396 0397 QModelIndex KisAllResourcesModel::indexForResourceId(int resourceId) const 0398 { 0399 if (!d->resourcesQuery.first()) { 0400 return QModelIndex(); 0401 } 0402 0403 do { 0404 if (d->resourcesQuery.value("id").toInt() == resourceId) { 0405 return index(d->resourcesQuery.at(), 0); 0406 } 0407 } while (d->resourcesQuery.next()); 0408 0409 return QModelIndex(); 0410 } 0411 0412 bool KisAllResourcesModel::setResourceActive(const QModelIndex &index, bool value) 0413 { 0414 if (index.row() > rowCount()) return false; 0415 if (index.column() > d->columnCount) return false; 0416 0417 int resourceId = index.data(Qt::UserRole + Id).toInt(); 0418 if (!KisResourceLocator::instance()->setResourceActive(resourceId, value)) { 0419 qWarning() << "Failed to change active state of the resource" << resourceId; 0420 return false; 0421 } 0422 0423 return true; 0424 } 0425 //static int s_i6 {0}; 0426 0427 KoResourceSP KisAllResourcesModel::importResourceFile(const QString &filename, const bool allowOverwrite, const QString &storageId) 0428 { 0429 KoResourceSP importedResource = KisResourceLocator::instance()->importResourceFromFile(d->resourceType, filename, allowOverwrite, storageId); 0430 0431 if (!importedResource) { 0432 qWarning() << "Failed to import resource" << filename; 0433 } 0434 resetQuery(); 0435 0436 return importedResource; 0437 } 0438 0439 KoResourceSP KisAllResourcesModel::importResource(const QString &filename, QIODevice *device, const bool allowOverwrite, const QString &storageId) 0440 { 0441 KoResourceSP importedResource = KisResourceLocator::instance()->importResource(d->resourceType, filename, device, allowOverwrite, storageId); 0442 0443 if (!importedResource) { 0444 qWarning() << "Failed to import resource" << filename; 0445 } 0446 resetQuery(); 0447 0448 return importedResource; 0449 } 0450 0451 bool KisAllResourcesModel::importWillOverwriteResource(const QString &fileName, const QString &storageLocation) const 0452 { 0453 return KisResourceLocator::instance()->importWillOverwriteResource(d->resourceType, fileName, storageLocation); 0454 } 0455 0456 bool KisAllResourcesModel::exportResource(KoResourceSP resource, QIODevice *device) 0457 { 0458 bool res = KisResourceLocator::instance()->exportResource(resource, device); 0459 if (!res) { 0460 qWarning() << "Failed to export resource" << resource->signature(); 0461 } 0462 return res; 0463 } 0464 0465 bool KisAllResourcesModel::addResource(KoResourceSP resource, const QString &storageId) 0466 { 0467 if (!resource || !resource->valid()) { 0468 qWarning() << "Cannot add resource. Resource is null or not valid"; 0469 return false; 0470 } 0471 0472 bool r = true; 0473 beginInsertRows(QModelIndex(), rowCount(), rowCount()); 0474 if (!KisResourceLocator::instance()->addResource(d->resourceType, resource, storageId)) { 0475 qWarning() << "Failed to add resource" << resource->name(); 0476 r = false; 0477 } 0478 resetQuery(); 0479 endInsertRows(); 0480 0481 return r; 0482 } 0483 0484 bool KisAllResourcesModel::updateResource(KoResourceSP resource) 0485 { 0486 if (!resource || !resource->valid()) { 0487 qWarning() << "Cannot update resource. Resource is null or not valid"; 0488 return false; 0489 } 0490 0491 if (!KisResourceLocator::instance()->updateResource(d->resourceType, resource)) { 0492 qWarning() << "Failed to update resource" << resource; 0493 return false; 0494 } 0495 bool r = resetQuery(); 0496 QModelIndex index = indexForResource(resource); 0497 emit dataChanged(index, index); 0498 return r; 0499 } 0500 0501 bool KisAllResourcesModel::reloadResource(KoResourceSP resource) 0502 { 0503 if (!resource || !resource->valid()) { 0504 qWarning() << "Cannot reload resource. Resource is null or not valid"; 0505 return false; 0506 } 0507 0508 if (!KisResourceLocator::instance()->reloadResource(d->resourceType, resource)) { 0509 qWarning() << "Failed to reload resource" << resource; 0510 return false; 0511 } 0512 0513 /** 0514 * We don't have to call reset query here, because reloading a resource 0515 * doesn't change any database content. 0516 */ 0517 0518 QModelIndex index = indexForResource(resource); 0519 emit dataChanged(index, index); 0520 return true; 0521 } 0522 0523 bool KisAllResourcesModel::renameResource(KoResourceSP resource, const QString &name) 0524 { 0525 if (!resource || !resource->valid() || name.isEmpty()) { 0526 qWarning() << "Cannot rename resources. Resource is NULL or not valid or name is empty"; 0527 return false; 0528 } 0529 resource->setName(name); 0530 if (!KisResourceLocator::instance()->updateResource(d->resourceType, resource)) { 0531 qWarning() << "Failed to rename resource" << resource << name; 0532 return false; 0533 } 0534 bool r = resetQuery(); 0535 QModelIndex index = indexForResource(resource); 0536 emit dataChanged(index, index); 0537 return r; 0538 } 0539 0540 //static int s_i9 {0}; 0541 0542 bool KisAllResourcesModel::setResourceMetaData(KoResourceSP resource, QMap<QString, QVariant> metadata) 0543 { 0544 Q_ASSERT(resource->resourceId() > -1); 0545 return KisResourceLocator::instance()->setMetaDataForResource(resource->resourceId(), metadata); 0546 } 0547 0548 bool KisAllResourcesModel::resetQuery() 0549 { 0550 bool r = d->resourcesQuery.exec(); 0551 if (!r) { 0552 qWarning() << "Could not select" << d->resourceType << "resources" << d->resourcesQuery.lastError() << d->resourcesQuery.boundValues(); 0553 } 0554 d->cachedRowCount = -1; 0555 0556 return r; 0557 } 0558 0559 QVector<KisTagSP> KisAllResourcesModel::tagsForResource(int resourceId) const 0560 { 0561 bool r; 0562 0563 QSqlQuery q; 0564 0565 r = q.prepare("SELECT tags.url\n" 0566 "FROM tags\n" 0567 ", resource_tags\n" 0568 ", resource_types\n" 0569 "WHERE tags.active > 0\n" // make sure the tag is active 0570 "AND tags.id = resource_tags.tag_id\n" // join tags + resource_tags by tag_id 0571 "AND resource_tags.resource_id = :resource_id\n" 0572 "AND resource_types.id = tags.resource_type_id\n" // make sure we're looking for tags for a specific resource 0573 "AND resource_tags.active = 1\n"); // and the tag must be active 0574 if (!r) { 0575 qWarning() << "Could not prepare TagsForResource query" << q.lastError(); 0576 } 0577 0578 q.bindValue(":resource_id", resourceId); 0579 r = q.exec(); 0580 if (!r) { 0581 qWarning() << "Could not select tags for" << resourceId << q.lastError() << q.boundValues(); 0582 } 0583 0584 QVector<KisTagSP> tags; 0585 while (q.next()) { 0586 KisTagSP tag = KisResourceLocator::instance()->tagForUrl(q.value(0).toString(), d->resourceType); 0587 if (tag && tag->valid()) { 0588 tags << tag; 0589 } 0590 } 0591 return tags; 0592 } 0593 0594 0595 int KisAllResourcesModel::rowCount(const QModelIndex &parent) const 0596 { 0597 if (parent.isValid()) { 0598 return 0; 0599 } 0600 0601 if (d->cachedRowCount < 0) { 0602 /** 0603 * SQLite doesn't support COUNT(DISTINCT ...) over multiple columns, so 0604 * we need to concatenate them manually on the fly. But SQLite doesn't 0605 * support CONCAT function either, therefore we should use 0606 * concatenation operator it provides. 0607 */ 0608 0609 QSqlQuery q; 0610 bool r = q.prepare("SELECT COUNT(DISTINCT resources.name || resources.filename || resources.md5sum)\n" 0611 "FROM resources\n" 0612 ", resource_types\n" 0613 "WHERE resources.resource_type_id = resource_types.id\n" 0614 "AND resource_types.name = :resource_type\n"); 0615 if (!r) { 0616 qWarning() << "Could not prepare all resources rowcount query" << q.lastError(); 0617 return 0; 0618 } 0619 q.bindValue(":resource_type", d->resourceType); 0620 r = q.exec(); 0621 if (!r) { 0622 qWarning() << "Could not execute all resources rowcount query" << q.lastError() << q.boundValues(); 0623 return 0; 0624 } 0625 q.first(); 0626 0627 const_cast<KisAllResourcesModel*>(this)->d->cachedRowCount = q.value(0).toInt(); 0628 } 0629 0630 return d->cachedRowCount; 0631 } 0632 0633 void KisAllResourcesModel::storageActiveStateChanged(const QString &location) 0634 { 0635 const QVector<int> resourceIds = KisResourceCacheDb::resourcesForStorage(d->resourceType, location); 0636 if (resourceIds.isEmpty()) return; 0637 0638 resetQuery(); 0639 0640 Q_FOREACH (int resourceId, resourceIds) { 0641 QModelIndex index = indexForResourceId(resourceId); 0642 0643 if (index.isValid()) { 0644 Q_EMIT dataChanged(index, index, {Qt::UserRole + KisAbstractResourceModel::StorageActive}); 0645 } 0646 } 0647 } 0648 0649 void KisAllResourcesModel::beginExternalResourceImport(const QString &resourceType, int numResources) 0650 { 0651 if (resourceType != d->resourceType) return; 0652 0653 beginInsertRows(QModelIndex(), rowCount(), rowCount() + numResources - 1); 0654 } 0655 0656 void KisAllResourcesModel::endExternalResourceImport(const QString &resourceType) 0657 { 0658 if (resourceType != d->resourceType) return; 0659 0660 resetQuery(); 0661 endInsertRows(); 0662 } 0663 0664 void KisAllResourcesModel::beginExternalResourceRemove(const QString &resourceType, const QVector<int> &resourceIds) 0665 { 0666 if (resourceType != d->resourceType) return; 0667 0668 Q_FOREACH (int resourceId, resourceIds) { 0669 const QModelIndex index = indexForResourceId(resourceId); 0670 if (index.isValid()) { 0671 beginRemoveRows(QModelIndex(), index.row(), index.row()); 0672 d->externalResourcesRemovedCount++; 0673 } else { 0674 // it's fine if the index is invalid; it probably means it's one of the duplicates (another resource with the same type and content was already in the database) 0675 dbgResources << "KisAllResourcesModel::beginExternalResourceRemove got invalid index" << index << "for resourceId" << resourceId 0676 << "of type" << resourceType << "(possibly the resource was deduplicated via sql query and that's why it doesn't appear in the model)"; 0677 } 0678 } 0679 } 0680 0681 void KisAllResourcesModel::endExternalResourceRemove(const QString &resourceType) 0682 { 0683 if (resourceType != d->resourceType) return; 0684 0685 if (d->externalResourcesRemovedCount > 0) { 0686 resetQuery(); 0687 } 0688 for (int i = 0; i < d->externalResourcesRemovedCount; i++) { 0689 endRemoveRows(); 0690 } 0691 0692 d->externalResourcesRemovedCount = 0; 0693 } 0694 0695 void KisAllResourcesModel::slotResourceActiveStateChanged(const QString &resourceType, int resourceId) 0696 { 0697 if (resourceType != d->resourceType) return; 0698 if (resourceId < 0) return; 0699 0700 resetQuery(); 0701 0702 QModelIndex index = indexForResourceId(resourceId); 0703 0704 if (index.isValid()) { 0705 Q_EMIT dataChanged(index, index, {Qt::CheckStateRole, Qt::UserRole + KisAbstractResourceModel::ResourceActive}); 0706 } 0707 } 0708 0709 struct KisResourceModel::Private 0710 { 0711 ResourceFilter resourceFilter {ShowActiveResources}; 0712 StorageFilter storageFilter {ShowActiveStorages}; 0713 bool showOnlyUntaggedResources {false}; 0714 }; 0715 0716 KisResourceModel::KisResourceModel(const QString &type, QObject *parent) 0717 : QSortFilterProxyModel(parent) 0718 , d(new Private) 0719 { 0720 setSourceModel(KisResourceModelProvider::resourceModel(type)); 0721 } 0722 0723 KisResourceModel::~KisResourceModel() 0724 { 0725 delete d; 0726 } 0727 0728 void KisResourceModel::setResourceFilter(ResourceFilter filter) 0729 { 0730 if (d->resourceFilter != filter) { 0731 d->resourceFilter = filter; 0732 invalidateFilter(); 0733 } 0734 } 0735 0736 void KisResourceModel::setStorageFilter(StorageFilter filter) 0737 { 0738 if (d->storageFilter != filter) { 0739 d->storageFilter = filter; 0740 invalidateFilter(); 0741 } 0742 } 0743 0744 void KisResourceModel::showOnlyUntaggedResources(bool showOnlyUntagged) 0745 { 0746 d->showOnlyUntaggedResources = showOnlyUntagged; 0747 invalidateFilter(); 0748 } 0749 0750 KoResourceSP KisResourceModel::resourceForIndex(QModelIndex index) const 0751 { 0752 KisAbstractResourceModel *source = dynamic_cast<KisAbstractResourceModel*>(sourceModel()); 0753 if (source) { 0754 return source->resourceForIndex(mapToSource(index)); 0755 } 0756 return 0; 0757 } 0758 0759 QModelIndex KisResourceModel::indexForResource(KoResourceSP resource) const 0760 { 0761 KisAbstractResourceModel *source = dynamic_cast<KisAbstractResourceModel*>(sourceModel()); 0762 if (source) { 0763 return mapFromSource(source->indexForResource(resource)); 0764 } 0765 return QModelIndex(); 0766 } 0767 0768 QModelIndex KisResourceModel::indexForResourceId(int resourceId) const 0769 { 0770 KisAbstractResourceModel *source = dynamic_cast<KisAbstractResourceModel*>(sourceModel()); 0771 if (source) { 0772 return mapFromSource(source->indexForResourceId(resourceId)); 0773 } 0774 return QModelIndex(); 0775 } 0776 0777 bool KisResourceModel::setResourceActive(const QModelIndex &index, bool value) 0778 { 0779 KisAbstractResourceModel *source = dynamic_cast<KisAbstractResourceModel*>(sourceModel()); 0780 if (source) { 0781 return source->setResourceActive(mapToSource(index), value); 0782 } 0783 return false; 0784 } 0785 0786 KoResourceSP KisResourceModel::importResourceFile(const QString &filename, const bool allowOverwrite, const QString &storageId) 0787 { 0788 KisAbstractResourceModel *source = dynamic_cast<KisAbstractResourceModel*>(sourceModel()); 0789 KoResourceSP res; 0790 if (source) { 0791 res = source->importResourceFile(filename, allowOverwrite, storageId); 0792 } 0793 invalidate(); 0794 return res; 0795 } 0796 0797 KoResourceSP KisResourceModel::importResource(const QString &filename, QIODevice *device, const bool allowOverwrite, const QString &storageId) 0798 { 0799 KisAbstractResourceModel *source = dynamic_cast<KisAbstractResourceModel*>(sourceModel()); 0800 KoResourceSP res; 0801 if (source) { 0802 res = source->importResource(filename, device, allowOverwrite, storageId); 0803 } 0804 invalidate(); 0805 return res; 0806 } 0807 0808 bool KisResourceModel::importWillOverwriteResource(const QString &fileName, const QString &storageLocation) const 0809 { 0810 KisAbstractResourceModel *source = dynamic_cast<KisAbstractResourceModel*>(sourceModel()); 0811 return source && source->importWillOverwriteResource(fileName, storageLocation); 0812 } 0813 0814 bool KisResourceModel::exportResource(KoResourceSP resource, QIODevice *device) 0815 { 0816 KisAbstractResourceModel *source = dynamic_cast<KisAbstractResourceModel*>(sourceModel()); 0817 bool res = false; 0818 if (source) { 0819 res = source->exportResource(resource, device); 0820 } 0821 return res; 0822 } 0823 0824 bool KisResourceModel::addResource(KoResourceSP resource, const QString &storageId) 0825 { 0826 KisAllResourcesModel *source = qobject_cast<KisAllResourcesModel*>(sourceModel()); 0827 bool updateInsteadOfAdd = false; 0828 bool result = false; 0829 0830 // Check whether the resource already existed, in that case, we will update 0831 // and possibly reactivate the resource 0832 QSqlQuery q; 0833 0834 if (!q.prepare("SELECT resources.id\n" 0835 ", resources.md5sum\n" 0836 ", storages.location\n" 0837 ", resource_types.name\n" 0838 "FROM resources\n" 0839 ", storages\n" 0840 ", resource_types\n" 0841 "WHERE resources.name = :name\n" 0842 "AND resources.storage_id = storages.id\n" 0843 "AND resources.resource_type_id = resource_types.id\n" 0844 "AND resources.status = 0")) { 0845 qWarning() << "Could not create KisResourceModel::addResource query" << q.lastError(); 0846 } 0847 0848 q.bindValue(":name", resource->name()); 0849 0850 if (!q.exec()) { 0851 qWarning() << "Could not execute KisResourceModel::addResource query" << q.lastError(); 0852 } 0853 0854 while (q.next()) { 0855 int id = q.value(0).toInt(); 0856 QString md5sum = q.value(1).toString(); 0857 QString storageLocation = q.value(2).toString(); 0858 QString resourceType = q.value(3).toString(); 0859 0860 0861 QSqlQuery q2; 0862 0863 if (!q2.prepare("SELECT MAX(version)\n" 0864 "FROM versioned_resources\n" 0865 "WHERE resource_id = :id")) { 0866 qWarning() << "Could not prepare versioned_resources query" << q.lastError(); 0867 } 0868 0869 q2.bindValue(":id", id); 0870 0871 if (!q2.exec()) { 0872 qWarning() << "Could not execute versioned_resources query" << q.lastError(); 0873 } 0874 0875 if (!q2.first()) { 0876 qWarning() << "No resource version found with id" << id; 0877 } 0878 0879 q.first(); 0880 0881 int version = q2.value(0).toInt(); 0882 0883 if (resourceType == resource->resourceType().first) { 0884 resource->setResourceId(id); 0885 resource->setVersion(version); 0886 resource->setMD5Sum(md5sum); 0887 resource->setActive(true); 0888 resource->setStorageLocation(storageLocation); 0889 bool result = updateResource(resource); 0890 updateInsteadOfAdd = result; 0891 break; 0892 } 0893 } 0894 0895 if (!updateInsteadOfAdd) { 0896 result = source->addResource(resource, storageId); 0897 } 0898 0899 if (result) { 0900 invalidate(); 0901 } 0902 0903 return result; 0904 } 0905 0906 bool KisResourceModel::updateResource(KoResourceSP resource) 0907 { 0908 KisAbstractResourceModel *source = dynamic_cast<KisAbstractResourceModel*>(sourceModel()); 0909 if (source) { 0910 return source->updateResource(resource); 0911 } 0912 return false; 0913 } 0914 0915 bool KisResourceModel::reloadResource(KoResourceSP resource) 0916 { 0917 KisAbstractResourceModel *source = dynamic_cast<KisAbstractResourceModel*>(sourceModel()); 0918 if (source) { 0919 return source->reloadResource(resource); 0920 } 0921 return false; 0922 } 0923 0924 bool KisResourceModel::renameResource(KoResourceSP resource, const QString &name) 0925 { 0926 KisAbstractResourceModel *source = dynamic_cast<KisAbstractResourceModel*>(sourceModel()); 0927 if (source) { 0928 return source->renameResource(resource, name); 0929 } 0930 return false; 0931 } 0932 0933 bool KisResourceModel::setResourceMetaData(KoResourceSP resource, QMap<QString, QVariant> metadata) 0934 { 0935 KisAbstractResourceModel *source = dynamic_cast<KisAbstractResourceModel*>(sourceModel()); 0936 if (source) { 0937 return source->setResourceMetaData(resource, metadata); 0938 } 0939 return false; 0940 } 0941 0942 bool KisResourceModel::filterAcceptsColumn(int /*source_column*/, const QModelIndex &/*source_parent*/) const 0943 { 0944 return true; 0945 } 0946 0947 bool KisResourceModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const 0948 { 0949 QModelIndex idx = sourceModel()->index(source_row, 0, source_parent); 0950 0951 if (idx.isValid()) { 0952 int id = idx.data(Qt::UserRole + KisAbstractResourceModel::Id).toInt(); 0953 0954 if (d->showOnlyUntaggedResources) { 0955 0956 QString queryString = ("SELECT COUNT(*)\n" 0957 "FROM resources\n" 0958 ", storages\n" 0959 "WHERE resources.id IN (select resource_id FROM resource_tags WHERE active = 1)\n" 0960 "AND storages.id = resources.storage_id\n" 0961 "AND resources.id = :resource_id\n"); 0962 0963 if (d->resourceFilter == ShowActiveResources) { 0964 queryString.append("AND resources.status > 0\n"); 0965 } 0966 else if (d->resourceFilter == ShowInactiveResources) { 0967 queryString.append("AND resources.status = 0\n"); 0968 } 0969 0970 if (d->storageFilter == ShowActiveStorages) { 0971 queryString.append("AND storages.active > 0\n"); 0972 } 0973 else if (d->storageFilter == ShowInactiveStorages) { 0974 queryString.append("AND storages.active = 0\n"); 0975 } 0976 0977 QSqlQuery q; 0978 0979 if (!q.prepare((queryString))) { 0980 qWarning() << "KisResourceModel: Could not prepare resource_tags query" << q.lastError(); 0981 } 0982 0983 q.bindValue(":resource_id", id); 0984 0985 if (!q.exec()) { 0986 qWarning() << "KisResourceModel: Could not execute resource_tags query" << q.lastError() << q.boundValues(); 0987 } 0988 0989 q.first(); 0990 if (q.value(0).toInt() > 0) { 0991 return false; 0992 } 0993 } 0994 } 0995 0996 return filterResource(idx); 0997 } 0998 0999 bool KisResourceModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const 1000 { 1001 QString nameLeft = sourceModel()->data(source_left, Qt::UserRole + KisAbstractResourceModel::Name).toString(); 1002 QString nameRight = sourceModel()->data(source_right, Qt::UserRole + KisAbstractResourceModel::Name).toString(); 1003 1004 return nameLeft.toLower() < nameRight.toLower(); 1005 } 1006 1007 QVector<KoResourceSP> KisResourceModel::filterByColumn(const QString filter, KisAbstractResourceModel::Columns column) const 1008 { 1009 QVector<KoResourceSP> resources; 1010 for (int i = 0; i < rowCount(); ++i) { 1011 QModelIndex idx = index(i, 0); 1012 if (idx.isValid() && data(idx, Qt::UserRole + column).toString() == filter) { 1013 resources << resourceForIndex(idx); 1014 } 1015 } 1016 1017 return resources; 1018 } 1019 1020 bool KisResourceModel::filterResource(const QModelIndex &idx) const 1021 { 1022 if (d->resourceFilter == ShowAllResources && d->storageFilter == ShowAllStorages) { 1023 return true; 1024 } 1025 1026 ResourceFilter resourceActive = (ResourceFilter)sourceModel()->data(idx, Qt::UserRole + KisAbstractResourceModel::ResourceActive).toInt(); 1027 StorageFilter storageActive = (StorageFilter)sourceModel()->data(idx, Qt::UserRole + KisAbstractResourceModel::StorageActive).toInt(); 1028 1029 if (d->resourceFilter == ShowAllResources) { 1030 if (storageActive == d->storageFilter) { 1031 return true; 1032 } 1033 } 1034 1035 if (d->storageFilter == ShowAllStorages) { 1036 if (resourceActive == d->resourceFilter) { 1037 return true; 1038 } 1039 } 1040 1041 if ((storageActive == d->storageFilter) && (resourceActive == d->resourceFilter)) { 1042 return true; 1043 } 1044 1045 return false; 1046 } 1047 1048 1049 KoResourceSP KisResourceModel::resourceForId(int id) const 1050 { 1051 KoResourceSP res = static_cast<KisAllResourcesModel*>(sourceModel())->resourceForId(id); 1052 QModelIndex idx = indexForResource(res); 1053 if (idx.isValid()) { 1054 return res; 1055 } 1056 return 0; 1057 } 1058 1059 QVector<KoResourceSP> KisResourceModel::resourcesForFilename(QString filename) const 1060 { 1061 return filterByColumn(filename, KisAllResourcesModel::Filename); 1062 1063 } 1064 1065 QVector<KoResourceSP> KisResourceModel::resourcesForName(QString name) const 1066 { 1067 return filterByColumn(name, KisAllResourcesModel::Name); 1068 } 1069 1070 QVector<KoResourceSP> KisResourceModel::resourcesForMD5(const QString md5sum) const 1071 { 1072 return filterByColumn(md5sum, KisAllResourcesModel::MD5); 1073 } 1074 1075 QVector<KisTagSP> KisResourceModel::tagsForResource(int resourceId) const 1076 { 1077 return static_cast<KisAllResourcesModel*>(sourceModel())->tagsForResource(resourceId); 1078 }