Krita Source Code Documentation
Loading...
Searching...
No Matches
KisStorageModel.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2019 Boudewijn Rempt <boud@valdyas.org>
3 * SPDX-FileCopyrightText: 2023 L. E. Segovia <amy@amyspark.me>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7#include "KisStorageModel.h"
8
9#include <QBuffer>
10#include <QDir>
11#include <QFont>
12#include <QSqlError>
13#include <QSqlQuery>
14#include <KisResourceLocator.h>
15#include <KoResourcePaths.h>
18#include <QFileInfo>
19#include <kis_assert.h>
20
21#include <kconfig.h>
22#include <kconfiggroup.h>
23#include <ksharedconfig.h>
24
25#include <kis_debug.h>
26
28
32
44
46{
47 return s_instance;
48}
49
53
54int KisStorageModel::rowCount(const QModelIndex &parent) const
55{
56 if (parent.isValid()) {
57 return 0;
58 }
59 return d->storages.size();
60
61}
62
63int KisStorageModel::columnCount(const QModelIndex &parent) const
64{
65 if (parent.isValid()) {
66 return 0;
67 }
68
69 return (int)MetaData;
70}
71
72QImage KisStorageModel::getThumbnailFromQuery(const QSqlQuery &query)
73{
74 const QString storageLocation = query.value("location").toString();
75 const QString storageType = query.value("storage_type").toString();
76 const QString storageIdAsString = query.value("id").toString();
77
78 QImage img = KisResourceThumbnailCache::instance()->originalImage(storageLocation, storageType, storageIdAsString);
79 if (!img.isNull()) {
80 return img;
81 } else {
82 const int storageId = query.value("id").toInt();
83 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(storageId >= 0, img);
84
85 bool result = false;
86 QSqlQuery thumbQuery;
87 result = thumbQuery.prepare("SELECT thumbnail FROM storages WHERE id = :id");
88 if (!result) {
89 qWarning() << "Failed to prepare query for thumbnail of" << storageId << thumbQuery.lastError();
90 return img;
91 }
92
93 thumbQuery.bindValue(":id", storageId);
94
95 result = thumbQuery.exec();
96
97 if (!result) {
98 qWarning() << "Failed to execute query for thumbnail of" << storageId << thumbQuery.lastError();
99 return img;
100 }
101
102 if (!thumbQuery.next()) {
103 qWarning() << "Failed to find thumbnail of" << storageId;
104 return img;
105 }
106
107 QByteArray ba = thumbQuery.value("thumbnail").toByteArray();
108 QBuffer buf(&ba);
109 buf.open(QBuffer::ReadOnly);
110 img.load(&buf, "PNG");
111 KisResourceThumbnailCache::instance()->insert(storageLocation, storageType, storageIdAsString, img);
112 return img;
113 }
114}
115
116QVariant KisStorageModel::data(const QModelIndex &index, int role) const
117{
118 QVariant v;
119
120 if (!index.isValid()) return v;
121 if (index.row() > rowCount()) return v;
122 if (index.column() > (int)MetaData) return v;
123
124 if (role == Qt::FontRole) {
125 return QFont();
126 }
127
128 QString location = d->storages.at(index.row());
129
130 QSqlQuery query;
131
132 bool r = query.prepare(
133 "SELECT storages.id as id\n"
134 ", storage_types.name as storage_type\n"
135 ", location\n"
136 ", timestamp\n"
137 ", pre_installed\n"
138 ", active\n"
139 "FROM storages\n"
140 ", storage_types\n"
141 "WHERE storages.storage_type_id = storage_types.id\n"
142 "AND location = :location");
143
144 if (!r) {
145 qWarning() << "Could not prepare KisStorageModel data query" << query.lastError();
146 return v;
147 }
148
149 query.bindValue(":location", location);
150
151 r = query.exec();
152
153 if (!r) {
154 qWarning() << "Could not execute KisStorageModel data query" << query.lastError() << query.boundValues();
155 return v;
156 }
157
158 if (!query.first()) {
159 qWarning() << "KisStorageModel data query did not return anything";
160 return v;
161 }
162
163 if ((role == Qt::DisplayRole || role == Qt::EditRole) && index.column() == Active) {
164 return query.value("active");
165 } else {
166 switch (role) {
167 case Qt::DisplayRole:
168 {
169 switch(index.column()) {
170 case Id:
171 return query.value("id");
172 case StorageType:
173 return query.value("storage_type");
174 case Location:
175 return query.value("location");
176 case TimeStamp:
177 return QDateTime::fromSecsSinceEpoch(query.value("timestamp").value<int>()).toString();
178 case PreInstalled:
179 return query.value("pre_installed");
180 case Active:
181 return query.value("active");
182 case Thumbnail:
183 {
184 return getThumbnailFromQuery(query);
185 }
186 case DisplayName:
187 {
188 QMap<QString, QVariant> r = KisResourceLocator::instance()->metaDataForStorage(query.value("location").toString());
189 QVariant name = query.value("location");
190 if (r.contains(KisResourceStorage::s_meta_name) && !r[KisResourceStorage::s_meta_name].toString().isNull()) {
192 }
193 else if (r.contains(KisResourceStorage::s_meta_title) && !r[KisResourceStorage::s_meta_title].toString().isNull()) {
195 }
196 return name;
197 }
198 case Qt::UserRole + MetaData:
199 {
200 QMap<QString, QVariant> r = KisResourceLocator::instance()->metaDataForStorage(query.value("location").toString());
201 return r;
202 }
203 default:
204 return v;
205 }
206 }
207 case Qt::CheckStateRole: {
208 switch (index.column()) {
209 case PreInstalled:
210 if (query.value("pre_installed").toInt() == 0) {
211 return Qt::Unchecked;
212 } else {
213 return Qt::Checked;
214 }
215 case Active:
216 if (query.value("active").toInt() == 0) {
217 return Qt::Unchecked;
218 } else {
219 return Qt::Checked;
220 }
221 default:
222 return {};
223 }
224 }
225 case Qt::DecorationRole: {
226 if (index.column() == Thumbnail) {
227 return getThumbnailFromQuery(query);
228 }
229 return {};
230 }
231 case Qt::UserRole + Id:
232 return query.value("id");
233 case Qt::UserRole + DisplayName:
234 {
235 QMap<QString, QVariant> r = KisResourceLocator::instance()->metaDataForStorage(query.value("location").toString());
236 QVariant name = query.value("location");
237 if (r.contains(KisResourceStorage::s_meta_name) && !r[KisResourceStorage::s_meta_name].toString().isNull()) {
239 }
240 else if (r.contains(KisResourceStorage::s_meta_title) && !r[KisResourceStorage::s_meta_title].toString().isNull()) {
242 }
243 return name;
244 }
245 case Qt::UserRole + StorageType:
246 return query.value("storage_type");
247 case Qt::UserRole + Location:
248 return query.value("location");
249 case Qt::UserRole + TimeStamp:
250 return query.value("timestamp");
251 case Qt::UserRole + PreInstalled:
252 return query.value("pre_installed");
253 case Qt::UserRole + Active:
254 return query.value("active");
255 case Qt::UserRole + Thumbnail:
256 return getThumbnailFromQuery(query);
257 case Qt::UserRole + MetaData:
258 {
259 QMap<QString, QVariant> r = KisResourceLocator::instance()->metaDataForStorage(query.value("location").toString());
260 return r;
261 }
262
263 default:
264 ;
265 }
266 }
267
268 return v;
269}
270
271bool KisStorageModel::setData(const QModelIndex &index, const QVariant &value, int role)
272{
273 if (index.isValid()) {
274
275 if (role == Qt::CheckStateRole) {
276 QSqlQuery query;
277 bool r = query.prepare("UPDATE storages\n"
278 "SET active = :active\n"
279 "WHERE id = :id\n");
280 query.bindValue(":active", value);
281 query.bindValue(":id", index.data(Qt::UserRole + Id));
282
283 if (!r) {
284 qWarning() << "Could not prepare KisStorageModel update query" << query.lastError();
285 return false;
286 }
287
288 r = query.exec();
289
290 if (!r) {
291 qWarning() << "Could not execute KisStorageModel update query" << query.lastError();
292 return false;
293 }
294
295 }
296
297 Q_EMIT dataChanged(index, index, {role});
298
299 if (value.toBool()) {
300 Q_EMIT storageEnabled(data(index, Qt::UserRole + Location).toString());
301 }
302 else {
303 Q_EMIT storageDisabled(data(index, Qt::UserRole + Location).toString());
304 }
305
306 }
307 return true;
308}
309
310Qt::ItemFlags KisStorageModel::flags(const QModelIndex &index) const
311{
312 if (!index.isValid()) {
313 return Qt::NoItemFlags;
314 }
315 return QAbstractTableModel::flags(index) | Qt::ItemIsEditable | Qt::ItemNeverHasChildren;
316}
317
319{
320
321 if (!index.isValid()) return 0;
322 if (index.row() > rowCount()) return 0;
323 if (index.column() > (int)MetaData) return 0;
324
325 QString location = d->storages.at(index.row());
326
327 return KisResourceLocator::instance()->storageByLocation(KisResourceLocator::instance()->makeStorageLocationAbsolute(location));
328}
329
331{
332 QSqlQuery query;
333
334 bool r = query.prepare("SELECT location\n"
335 "FROM storages\n"
336 "WHERE storages.id = :storageId");
337
338 if (!r) {
339 qWarning() << "Could not prepare KisStorageModel data query" << query.lastError();
340 return 0;
341 }
342
343 query.bindValue(":storageId", storageId);
344
345 r = query.exec();
346
347 if (!r) {
348 qWarning() << "Could not execute KisStorageModel data query" << query.lastError() << query.boundValues();
349 return 0;
350 }
351
352 if (!query.first()) {
353 qWarning() << "KisStorageModel data query did not return anything";
354 return 0;
355 }
356
357 return KisResourceLocator::instance()->storageByLocation(KisResourceLocator::instance()->makeStorageLocationAbsolute(query.value("location").toString()));
358}
359
360QString findUnusedName(QString location, QString filename)
361{
362 // the Save Incremental Version incrementation in KisViewManager is way too complex for this task
363 // and in that case there is a specific file to increment, while here we need to find just
364 // an unused filename
365 QFileInfo info = QFileInfo(location + "/" + filename);
366 if (!info.exists()) {
367 return filename;
368 }
369
370 QString extension = info.suffix();
371 QString filenameNoExtension = filename.left(filename.length() - extension.length());
372
373
374 QDir dir = QDir(location);
375 QStringList similarEntries = dir.entryList(QStringList() << filenameNoExtension + "*");
376
377 QList<int> versions;
378 int maxVersionUsed = -1;
379 for (int i = 0; i < similarEntries.count(); i++) {
380 QString entry = similarEntries[i];
381 //QFileInfo fi = QFileInfo(entry);
382 if (!entry.endsWith(extension)) {
383 continue;
384 }
385 QString versionStr = entry.right(entry.length() - filenameNoExtension.length()); // strip the common part
386 versionStr = versionStr.left(versionStr.length() - extension.length());
387 if (!versionStr.startsWith("_")) {
388 continue;
389 }
390 versionStr = versionStr.right(versionStr.length() - 1); // strip '_'
391 // now the part left should be a number
392 bool ok;
393 int version = versionStr.toInt(&ok);
394 if (!ok) {
395 continue;
396 }
397 if (version > maxVersionUsed) {
398 maxVersionUsed = version;
399 }
400 }
401
402 int versionToUse = maxVersionUsed > -1 ? maxVersionUsed + 1 : 1;
403 int versionStringLength = 3;
404 QString baseNewVersion = QString::number(versionToUse);
405 while (baseNewVersion.length() < versionStringLength) {
406 baseNewVersion.prepend("0");
407 }
408
409 QString newFilename = filenameNoExtension + "_" + QString::number(versionToUse) + extension;
410 bool success = !QFileInfo(location + "/" + newFilename).exists();
411
412 if (!success) {
413 qCritical() << "The new filename for the bundle does exist." << newFilename;
414 }
415
416 return newFilename;
417
418}
419
420bool KisStorageModel::importStorage(QString filename, StorageImportOption importOption) const
421{
422 // 1. Copy the bundle/storage to the resource folder
423 QFileInfo oldFileInfo(filename);
424 QString newDir = KoResourcePaths::getAppDataLocation();
425 QString newName = oldFileInfo.fileName();
426 QString newLocation = newDir + '/' + newName;
427
428 QFileInfo newFileInfo(newLocation);
429 if (newFileInfo.exists()) {
430 if (importOption == Overwrite) {
431 //QFile::remove(newLocation);
432 return false;
433 } else if (importOption == Rename) {
434 newName = findUnusedName(newDir, newName);
435 newLocation = newDir + '/' + newName;
436 newFileInfo = QFileInfo(newLocation);
437 } else { // importOption == None
438 return false;
439 }
440 }
441 QFile::copy(filename, newLocation);
442
443 // 2. Add the bundle as a storage/update database
445 KIS_ASSERT(!storage.isNull());
446 if (storage.isNull()) { return false; }
447 if (!KisResourceLocator::instance()->addStorage(newLocation, storage)) {
448 qWarning() << "Could not add bundle to the storages" << newLocation;
449 return false;
450 }
451 return true;
452}
453
454QVariant KisStorageModel::headerData(int section, Qt::Orientation orientation, int role) const
455{
456 QVariant v = QVariant();
457 if (role != Qt::DisplayRole) {
458 return v;
459 }
460 if (orientation == Qt::Horizontal) {
461 switch(section) {
462 case Id:
463 return i18n("Id");
464 case StorageType:
465 return i18n("Type");
466 case Location:
467 return i18n("Location");
468 case TimeStamp:
469 return i18n("Creation Date");
470 case PreInstalled:
471 return i18n("Preinstalled");
472 case Active:
473 return i18n("Active");
474 case Thumbnail:
475 return i18n("Thumbnail");
476 case DisplayName:
477 return i18n("Name");
478 default:
479 v = QString::number(section);
480 }
481 return v;
482 }
483 return QAbstractTableModel::headerData(section, orientation, role);
484}
485
486void KisStorageModel::addStorage(const QString &location)
487{
488 beginInsertRows(QModelIndex(), rowCount(), rowCount());
489 d->storages.append(location);
490 endInsertRows();
491}
492
493void KisStorageModel::removeStorage(const QString &location)
494{
495 int row = d->storages.indexOf(QFileInfo(location).fileName());
496 beginRemoveRows(QModelIndex(), row, row);
497 d->storages.removeAt(row);
498 endRemoveRows();
499}
500
502{
503 beginResetModel();
504 resetQuery();
505 endResetModel();
506
508}
509
511{
512 QSqlQuery query;
513
514 bool r = query.prepare(
515 "SELECT location\n"
516 "FROM storages\n"
517 "ORDER BY id");
518 if (!r) {
519 qWarning() << "Could not prepare KisStorageModel query" << query.lastError();
520 }
521
522 r = query.exec();
523
524 if (!r) {
525 qWarning() << "Could not execute KisStorageModel query" << query.lastError();
526 }
527
528 d->storages.clear();
529 while (query.next()) {
530 d->storages << query.value(0).toString();
531 }
532}
float value(const T *src, size_t ch)
qreal v
QList< QString > QStringList
Q_GLOBAL_STATIC(KisStoragePluginRegistry, s_instance)
QString findUnusedName(QString location, QString filename)
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
QMap< QString, QVariant > metaDataForStorage(const QString &storageLocation) const
metaDataForStorage
KisResourceStorageSP storageByLocation(const QString &location) const
void storageRemoved(const QString &location)
Emitted whenever a storage is removed.
void storageAdded(const QString &location)
Emitted whenever a storage is added.
void storagesBulkSynchronizationFinished()
void storageResynchronized(const QString &storage, bool isBulkResynchronization)
static KisResourceLocator * instance()
static const QString s_meta_title
static const QString s_meta_name
void insert(const QString &storageLocation, const QString &resourceType, const QString &filename, const QImage &image)
static KisResourceThumbnailCache * instance()
QImage originalImage(const QString &storageLocation, const QString &resourceType, const QString &filename) const
static KisStorageModel * instance()
void storageDisabled(const QString &storage)
~KisStorageModel() override
bool importStorage(QString filename, StorageImportOption importOption) const
static QImage getThumbnailFromQuery(const QSqlQuery &query)
bool setData(const QModelIndex &index, const QVariant &value, int role) override
Enable and disable the storage represented by index.
QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const override
KisStorageModel(QObject *parent=0)
void removeStorage(const QString &location)
This is called when a storage really is deleted both from database and anywhere else.
QScopedPointer< Private > d
int columnCount(const QModelIndex &parent=QModelIndex()) const override
void addStorage(const QString &location)
Called whenever a storage is added.
void storageEnabled(const QString &storage)
void storageResynchronized(const QString &storage, bool isBulkResynchronization)
Emitted when an individual storage is initialized.
KisResourceStorageSP storageForIndex(const QModelIndex &index) const
int rowCount(const QModelIndex &parent=QModelIndex()) const override
KisResourceStorageSP storageForId(const int storageId) const
Qt::ItemFlags flags(const QModelIndex &index) const override
QVariant data(const QModelIndex &index, int role) const override
void slotStoragesBulkSynchronizationFinished()
called when storages finished synchronization process
void storagesBulkSynchronizationFinished()
Emitted on loading when all the storages are finished initialization.
static QString getAppDataLocation()
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_ASSERT(cond)
Definition kis_assert.h:33