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