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