File indexing completed on 2024-05-12 15:59:52

0001 /*
0002  * SPDX-FileCopyrightText: 2019 boud <boud@valdyas.org>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 #include "KisStorageModel.h"
0007 
0008 #include <QtSql>
0009 #include <QElapsedTimer>
0010 #include <KisResourceLocator.h>
0011 #include <KoResourcePaths.h>
0012 #include <KisResourceModelProvider.h>
0013 #include <QFileInfo>
0014 #include <kis_assert.h>
0015 
0016 #include <kconfig.h>
0017 #include <kconfiggroup.h>
0018 #include <ksharedconfig.h>
0019 #include <QList>
0020 
0021 Q_GLOBAL_STATIC(KisStorageModel, s_instance)
0022 
0023 struct KisStorageModel::Private {
0024     int cachedRowCount {-1};
0025     QList<QString> storages;
0026 };
0027 
0028 KisStorageModel::KisStorageModel(QObject *parent)
0029     : QAbstractTableModel(parent)
0030     , d(new Private())
0031 {
0032     connect(KisResourceLocator::instance(), SIGNAL(storageAdded(const QString&)), this, SLOT(addStorage(const QString&)));
0033     connect(KisResourceLocator::instance(), SIGNAL(storageRemoved(const QString&)), this, SLOT(removeStorage(const QString&)));
0034 
0035     QSqlQuery query;
0036 
0037     bool r = query.prepare("SELECT location\n"
0038                            "FROM   storages\n"
0039                            "ORDER BY id");
0040     if (!r) {
0041         qWarning() << "Could not prepare KisStorageModel query" << query.lastError();
0042     }
0043 
0044     r = query.exec();
0045 
0046     if (!r) {
0047         qWarning() << "Could not execute KisStorageModel query" << query.lastError();
0048     }
0049 
0050     while (query.next()) {
0051         d->storages << query.value(0).toString();
0052     }
0053 }
0054 
0055 KisStorageModel *KisStorageModel::instance()
0056 {
0057     return s_instance;
0058 }
0059 
0060 KisStorageModel::~KisStorageModel()
0061 {
0062 }
0063 
0064 int KisStorageModel::rowCount(const QModelIndex &parent) const
0065 {
0066     if (parent.isValid()) {
0067         return 0;
0068     }
0069     return d->storages.size();
0070 
0071 }
0072 
0073 int KisStorageModel::columnCount(const QModelIndex &parent) const
0074 {
0075     if (parent.isValid()) {
0076         return 0;
0077     }
0078 
0079     return (int)MetaData;
0080 }
0081 
0082 QVariant KisStorageModel::data(const QModelIndex &index, int role) const
0083 {
0084     QVariant v;
0085 
0086     if (!index.isValid()) return v;
0087     if (index.row() > rowCount()) return v;
0088     if (index.column() > (int)MetaData) return v;
0089 
0090     QString location = d->storages.at(index.row());
0091 
0092     QSqlQuery query;
0093 
0094     bool r = query.prepare("SELECT storages.id as id\n"
0095                            ",      storage_types.name as storage_type\n"
0096                            ",      location\n"
0097                            ",      timestamp\n"
0098                            ",      pre_installed\n"
0099                            ",      active\n"
0100                            ",      thumbnail\n"
0101                            "FROM   storages\n"
0102                            ",      storage_types\n"
0103                            "WHERE  storages.storage_type_id = storage_types.id\n"
0104                            "AND    location = :location");
0105 
0106     if (!r) {
0107         qWarning() << "Could not prepare KisStorageModel data query" << query.lastError();
0108         return v;
0109     }
0110 
0111     query.bindValue(":location", location);
0112 
0113     r = query.exec();
0114 
0115     if (!r) {
0116         qWarning() << "Could not execute KisStorageModel data query" << query.lastError() << query.boundValues();
0117         return v;
0118     }
0119 
0120     if (!query.first()) {
0121         qWarning() << "KisStorageModel data query did not return anything";
0122         return v;
0123     }
0124 
0125     if ((role == Qt::DisplayRole || role == Qt::EditRole) && index.column() == Active) {
0126         return query.value("active");
0127     }
0128     else
0129     {
0130         switch(role) {
0131         case Qt::DisplayRole:
0132         {
0133             switch(index.column()) {
0134             case Id:
0135                 return query.value("id");
0136             case StorageType:
0137                 return query.value("storage_type");
0138             case Location:
0139                 return query.value("location");
0140             case TimeStamp:
0141                 return query.value("timestamp");
0142             case PreInstalled:
0143                 return query.value("pre_installed");
0144             case Active:
0145                 return query.value("active");
0146             case Thumbnail:
0147             {
0148                 QByteArray ba = query.value("thumbnail").toByteArray();
0149                 QBuffer buf(&ba);
0150                 buf.open(QBuffer::ReadOnly);
0151                 QImage img;
0152                 img.load(&buf, "PNG");
0153                 return QVariant::fromValue<QImage>(img);
0154             }
0155             case DisplayName:
0156             {
0157                 QMap<QString, QVariant> r = KisResourceLocator::instance()->metaDataForStorage(query.value("location").toString());
0158                 QVariant name = query.value("location");
0159                 if (r.contains(KisResourceStorage::s_meta_name) && !r[KisResourceStorage::s_meta_name].isNull()) {
0160                     name = r[KisResourceStorage::s_meta_name];
0161                 }
0162                 else if (r.contains(KisResourceStorage::s_meta_title) && !r[KisResourceStorage::s_meta_title].isNull()) {
0163                     name = r[KisResourceStorage::s_meta_title];
0164                 }
0165                 return name;
0166             }
0167             case Qt::UserRole + MetaData:
0168             {
0169                 QMap<QString, QVariant> r = KisResourceLocator::instance()->metaDataForStorage(query.value("location").toString());
0170                 return r;
0171             }
0172             default:
0173                 return v;
0174             }
0175         }
0176         case Qt::UserRole + Id:
0177             return query.value("id");
0178         case Qt::UserRole + DisplayName:
0179         {
0180             QMap<QString, QVariant> r = KisResourceLocator::instance()->metaDataForStorage(query.value("location").toString());
0181             QVariant name = query.value("location");
0182             if (r.contains(KisResourceStorage::s_meta_name) && !r[KisResourceStorage::s_meta_name].isNull()) {
0183                 name = r[KisResourceStorage::s_meta_name];
0184             }
0185             else if (r.contains(KisResourceStorage::s_meta_title) && !r[KisResourceStorage::s_meta_title].isNull()) {
0186                 name = r[KisResourceStorage::s_meta_title];
0187             }
0188             return name;
0189         }
0190         case Qt::UserRole + StorageType:
0191             return query.value("storage_type");
0192         case Qt::UserRole + Location:
0193             return query.value("location");
0194         case Qt::UserRole + TimeStamp:
0195             return query.value("timestamp");
0196         case Qt::UserRole + PreInstalled:
0197             return query.value("pre_installed");
0198         case Qt::UserRole + Active:
0199             return query.value("active");
0200         case Qt::UserRole + Thumbnail:
0201         {
0202             QByteArray ba = query.value("thumbnail").toByteArray();
0203             QBuffer buf(&ba);
0204             buf.open(QBuffer::ReadOnly);
0205             QImage img;
0206             img.load(&buf, "PNG");
0207             return QVariant::fromValue<QImage>(img);
0208         }
0209         case Qt::UserRole + MetaData:
0210         {
0211             QMap<QString, QVariant> r = KisResourceLocator::instance()->metaDataForStorage(query.value("location").toString());
0212             return r;
0213         }
0214 
0215         default:
0216             ;
0217         }
0218     }
0219 
0220     return v;
0221 }
0222 
0223 bool KisStorageModel::setData(const QModelIndex &index, const QVariant &value, int role)
0224 {
0225     if (index.isValid()) {
0226 
0227         if (role == Qt::CheckStateRole) {
0228             QSqlQuery query;
0229             bool r = query.prepare("UPDATE storages\n"
0230                                    "SET    active = :active\n"
0231                                    "WHERE  id = :id\n");
0232             query.bindValue(":active", value);
0233             query.bindValue(":id", index.data(Qt::UserRole + Id));
0234 
0235             if (!r) {
0236                 qWarning() << "Could not prepare KisStorageModel update query" << query.lastError();
0237                 return false;
0238             }
0239 
0240             r = query.exec();
0241 
0242             if (!r) {
0243                 qWarning() << "Could not execute KisStorageModel update query" << query.lastError();
0244                 return false;
0245             }
0246 
0247         }
0248 
0249         emit dataChanged(index, index, {role});
0250 
0251         if (value.toBool()) {
0252             emit storageEnabled(data(index, Qt::UserRole + Location).toString());
0253         }
0254         else {
0255             emit storageDisabled(data(index, Qt::UserRole + Location).toString());
0256         }
0257 
0258     }
0259     return true;
0260 }
0261 
0262 Qt::ItemFlags KisStorageModel::flags(const QModelIndex &index) const
0263 {
0264     if (!index.isValid()) {
0265         return Qt::NoItemFlags;
0266     }
0267     return QAbstractTableModel::flags(index) | Qt::ItemIsEditable | Qt::ItemNeverHasChildren;
0268 }
0269 
0270 KisResourceStorageSP KisStorageModel::storageForIndex(const QModelIndex &index) const
0271 {
0272 
0273     if (!index.isValid()) return 0;
0274     if (index.row() > rowCount()) return 0;
0275     if (index.column() > (int)MetaData) return 0;
0276 
0277     QString location = d->storages.at(index.row());
0278 
0279     return KisResourceLocator::instance()->storageByLocation(KisResourceLocator::instance()->makeStorageLocationAbsolute(location));
0280 }
0281 
0282 KisResourceStorageSP KisStorageModel::storageForId(const int storageId) const
0283 {
0284     QSqlQuery query;
0285 
0286     bool r = query.prepare("SELECT location\n"
0287                            "FROM   storages\n"
0288                            "WHERE  storages.id = :storageId");
0289 
0290     if (!r) {
0291         qWarning() << "Could not prepare KisStorageModel data query" << query.lastError();
0292         return 0;
0293     }
0294 
0295     query.bindValue(":storageId", storageId);
0296 
0297     r = query.exec();
0298 
0299     if (!r) {
0300         qWarning() << "Could not execute KisStorageModel data query" << query.lastError() << query.boundValues();
0301         return 0;
0302     }
0303 
0304     if (!query.first()) {
0305         qWarning() << "KisStorageModel data query did not return anything";
0306         return 0;
0307     }
0308 
0309     return KisResourceLocator::instance()->storageByLocation(KisResourceLocator::instance()->makeStorageLocationAbsolute(query.value("location").toString()));
0310 }
0311 
0312 QString findUnusedName(QString location, QString filename)
0313 {
0314     // the Save Incremental Version incrementation in KisViewManager is way too complex for this task
0315     // and in that case there is a specific file to increment, while here we need to find just
0316     // an unusued filename
0317     QFileInfo info = QFileInfo(location + "/" + filename);
0318     if (!info.exists()) {
0319         return filename;
0320     }
0321 
0322     QString extension = info.suffix();
0323     QString filenameNoExtension = filename.left(filename.length() - extension.length());
0324 
0325 
0326     QDir dir = QDir(location);
0327     QStringList similarEntries = dir.entryList(QStringList() << filenameNoExtension + "*");
0328 
0329     QList<int> versions;
0330     int maxVersionUsed = -1;
0331     for (int i = 0; i < similarEntries.count(); i++) {
0332         QString entry = similarEntries[i];
0333         //QFileInfo fi = QFileInfo(entry);
0334         if (!entry.endsWith(extension)) {
0335             continue;
0336         }
0337         QString versionStr = entry.right(entry.length() - filenameNoExtension.length()); // strip the common part
0338         versionStr = versionStr.left(versionStr.length() - extension.length());
0339         if (!versionStr.startsWith("_")) {
0340             continue;
0341         }
0342         versionStr = versionStr.right(versionStr.length() - 1); // strip '_'
0343         // now the part left should be a number
0344         bool ok;
0345         int version = versionStr.toInt(&ok);
0346         if (!ok) {
0347             continue;
0348         }
0349         if (version > maxVersionUsed) {
0350             maxVersionUsed = version;
0351         }
0352     }
0353 
0354     int versionToUse = maxVersionUsed > -1 ? maxVersionUsed + 1 : 1;
0355     int versionStringLength = 3;
0356     QString baseNewVersion = QString::number(versionToUse);
0357     while (baseNewVersion.length() < versionStringLength) {
0358         baseNewVersion.prepend("0");
0359     }
0360 
0361     QString newFilename = filenameNoExtension + "_" + QString::number(versionToUse) + extension;
0362     bool success = !QFileInfo(location + "/" + newFilename).exists();
0363 
0364     if (!success) {
0365         qCritical() << "The new filename for the bundle does exist." << newFilename;
0366     }
0367 
0368     return newFilename;
0369 
0370 }
0371 
0372 bool KisStorageModel::importStorage(QString filename, StorageImportOption importOption) const
0373 {
0374     // 1. Copy the bundle/storage to the resource folder
0375     QFileInfo oldFileInfo(filename);
0376     QString newDir = KoResourcePaths::getAppDataLocation();
0377     QString newName = oldFileInfo.fileName();
0378     QString newLocation = newDir + '/' + newName;
0379 
0380     QFileInfo newFileInfo(newLocation);
0381     if (newFileInfo.exists()) {
0382         if (importOption == Overwrite) {
0383             //QFile::remove(newLocation);
0384             return false;
0385         } else if (importOption == Rename) {
0386             newName = findUnusedName(newDir, newName);
0387             newLocation = newDir + '/' + newName;
0388             newFileInfo = QFileInfo(newLocation);
0389         } else { // importOption == None
0390             return false;
0391         }
0392     }
0393     QFile::copy(filename, newLocation);
0394 
0395     // 2. Add the bundle as a storage/update database
0396     KisResourceStorageSP storage = QSharedPointer<KisResourceStorage>::create(newLocation);
0397     KIS_ASSERT(!storage.isNull());
0398     if (storage.isNull()) { return false; }
0399     if (!KisResourceLocator::instance()->addStorage(newLocation, storage)) {
0400         qWarning() << "Could not add bundle to the storages" << newLocation;
0401         return false;
0402     }
0403     return true;
0404 }
0405 
0406 QVariant KisStorageModel::headerData(int section, Qt::Orientation orientation, int role) const
0407 {
0408     QVariant v = QVariant();
0409     if (role != Qt::DisplayRole) {
0410         return v;
0411     }
0412     if (orientation == Qt::Horizontal) {
0413         switch(section) {
0414         case Id:
0415             return i18n("Id");
0416         case StorageType:
0417             return i18n("Type");
0418         case Location:
0419             return i18n("Location");
0420         case TimeStamp:
0421             return i18n("Creation Date");
0422         case PreInstalled:
0423             return i18n("Preinstalled");
0424         case Active:
0425             return i18n("Active");
0426         case Thumbnail:
0427             return i18n("Thumbnail");
0428         case DisplayName:
0429             return i18n("Name");
0430         default:
0431             v = QString::number(section);
0432         }
0433         return v;
0434     }
0435     return QAbstractTableModel::headerData(section, orientation, role);
0436 }
0437 
0438 void KisStorageModel::addStorage(const QString &location)
0439 {
0440     beginInsertRows(QModelIndex(), rowCount(), rowCount());
0441     d->storages.append(location);
0442     endInsertRows();
0443 }
0444 
0445 void KisStorageModel::removeStorage(const QString &location)
0446 {
0447     int row = d->storages.indexOf(QFileInfo(location).fileName());
0448     beginRemoveRows(QModelIndex(), row, row);
0449     d->storages.removeAt(row);
0450     endRemoveRows();
0451 }
0452 
0453