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 }