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 }