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