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