File indexing completed on 2024-05-19 04:27:36

0001 /*
0002  * SPDX-FileCopyrightText: 2018 Boudewijn Rempt <boud@valdyas.org>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include "KisMemoryStorage.h"
0008 
0009 #include <QVector>
0010 #include <QFileInfo>
0011 
0012 #include <KisMimeDatabase.h>
0013 #include <kis_debug.h>
0014 #include <KisTag.h>
0015 #include <KisResourceStorage.h>
0016 #include <QBuffer>
0017 #include <KisGlobalResourcesInterface.h>
0018 #include <kis_pointer_utils.h>
0019 #include <KoMD5Generator.h>
0020 #include <kis_assert.h>
0021 
0022 
0023 struct StoredResource
0024 {
0025     QDateTime timestamp;
0026     QSharedPointer<QByteArray> data;
0027     KoResourceSP resource;
0028 };
0029 
0030 class MemoryTagIterator : public KisResourceStorage::TagIterator
0031 {
0032 public:
0033 
0034     MemoryTagIterator(QVector<KisTagSP> /*tags*/, const QString &resourceType)
0035         : m_resourceType(resourceType)
0036     {
0037     }
0038 
0039     bool hasNext() const override
0040     {
0041         return false;
0042     }
0043 
0044     void next() override
0045     {
0046     }
0047 
0048     KisTagSP tag() const override
0049     {
0050         return nullptr;
0051     }
0052 
0053 private:
0054     QString m_resourceType;
0055 };
0056 
0057 
0058 class MemoryItem : public KisResourceStorage::ResourceItem
0059 {
0060 public:
0061     ~MemoryItem() override {}
0062 };
0063 
0064 
0065 class KisMemoryStorage::Private {
0066 public:
0067     QHash<QString, QHash<QString, StoredResource>> resourcesNew;
0068     QHash<QString, QVector<KisTagSP>> tags;
0069     QMap<QString, QVariant> metadata;
0070 };
0071 
0072 
0073 KisMemoryStorage::KisMemoryStorage(const QString &location)
0074     : KisStoragePlugin(location)
0075     , d(new Private)
0076 {
0077 }
0078 
0079 KisMemoryStorage::~KisMemoryStorage()
0080 {
0081 }
0082 
0083 KisMemoryStorage::KisMemoryStorage(const KisMemoryStorage &rhs)
0084     : KisStoragePlugin(rhs.location())
0085     , d(new Private)
0086 {
0087     *this = rhs;
0088     d->resourcesNew = rhs.d->resourcesNew;
0089     d->tags = rhs.d->tags;
0090     d->metadata = rhs.d->metadata;
0091 }
0092 
0093 KisMemoryStorage &KisMemoryStorage::operator=(const KisMemoryStorage &rhs)
0094 {
0095     if (this != &rhs) {
0096         d->resourcesNew = rhs.d->resourcesNew;
0097 
0098         Q_FOREACH(const QString &key, rhs.d->tags.keys()) {
0099             Q_FOREACH(const KisTagSP tag, rhs.d->tags[key]) {
0100                 if (!d->tags.contains(key)) {
0101                     d->tags[key] = QVector<KisTagSP>();
0102                 }
0103                 d->tags[key] << tag->clone();
0104             }
0105         }
0106     }
0107     return *this;
0108 }
0109 
0110 bool KisMemoryStorage::saveAsNewVersion(const QString &resourceType, KoResourceSP resource)
0111 {
0112     QHash<QString, StoredResource> &typedResources =
0113         d->resourcesNew[resourceType];
0114 
0115     auto checkExists =
0116         [&typedResources] (const QString &filename) {
0117             return typedResources.contains(filename);
0118         };
0119 
0120     const QString newFilename =
0121         KisStorageVersioningHelper::chooseUniqueName(resource, 0, checkExists);
0122 
0123     if (newFilename.isEmpty()) return false;
0124 
0125     resource->setFilename(newFilename);
0126 
0127     StoredResource storedResource;
0128     storedResource.timestamp = QDateTime::currentDateTime();
0129     storedResource.data.reset(new QByteArray());
0130     QBuffer buffer(storedResource.data.data());
0131     buffer.open(QIODevice::WriteOnly);
0132     bool result = resource->saveToDevice(&buffer);
0133     buffer.close();
0134     if (!result) {
0135         storedResource.resource = resource;
0136     }
0137 
0138     typedResources.insert(newFilename, storedResource);
0139 
0140     return true;
0141 }
0142 
0143 KisResourceStorage::ResourceItem KisMemoryStorage::resourceItem(const QString &url)
0144 {
0145     MemoryItem item;
0146     item.url = url;
0147     item.folder = QString();
0148     item.lastModified = QDateTime::fromMSecsSinceEpoch(0);
0149     return item;
0150 }
0151 
0152 bool KisMemoryStorage::loadVersionedResource(KoResourceSP resource)
0153 {
0154     const QString resourceType = resource->resourceType().first;
0155     const QString resourceFilename = resource->filename();
0156 
0157     bool retval = false;
0158 
0159     if (d->resourcesNew.contains(resourceType) &&
0160         d->resourcesNew[resourceType].contains(resourceFilename)) {
0161 
0162         const StoredResource &storedResource =
0163             d->resourcesNew[resourceType][resourceFilename];
0164 
0165         if (storedResource.data->size() > 0) {
0166             QBuffer buffer(storedResource.data.data());
0167             buffer.open(QIODevice::ReadOnly);
0168             resource->loadFromDevice(&buffer, KisGlobalResourcesInterface::instance());
0169         } else {
0170             KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(storedResource.data->size() > 0, false);
0171             qWarning() << "Cannot load resource from device in KisMemoryStorage::loadVersionedResource";
0172             return false;
0173         }
0174         retval = true;
0175     }
0176 
0177     return retval;
0178 }
0179 
0180 bool KisMemoryStorage::importResource(const QString &url, QIODevice *device)
0181 {
0182 
0183 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
0184     QStringList parts = url.split('/', Qt::SkipEmptyParts);
0185 #else
0186     QStringList parts = url.split('/', QString::SkipEmptyParts);
0187 #endif
0188 
0189     Q_ASSERT(parts.size() == 2);
0190 
0191     const QString resourceType = parts[0];
0192     const QString resourceFilename = parts[1];
0193 
0194     // we cannot overwrite existing file by API convention
0195     if (d->resourcesNew.contains(resourceType) &&
0196         d->resourcesNew[resourceType].contains(resourceFilename)) {
0197         return false;
0198     }
0199 
0200     StoredResource storedResource;
0201     storedResource.timestamp = QDateTime::currentDateTime();
0202     storedResource.data.reset(new QByteArray(device->readAll()));
0203 
0204     QHash<QString, StoredResource> &typedResources =
0205         d->resourcesNew[resourceType];
0206     typedResources.insert(resourceFilename, storedResource);
0207 
0208     return true;
0209 }
0210 
0211 bool KisMemoryStorage::exportResource(const QString &url, QIODevice *device)
0212 {
0213 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
0214     QStringList parts = url.split('/', Qt::SkipEmptyParts);
0215 #else
0216     QStringList parts = url.split('/', QString::SkipEmptyParts);
0217 #endif
0218     Q_ASSERT(parts.size() == 2);
0219 
0220     const QString resourceType = parts[0];
0221     const QString resourceFilename = parts[1];
0222 
0223     if (!d->resourcesNew.contains(resourceType) ||
0224         !d->resourcesNew[resourceType].contains(resourceFilename)) {
0225         return false;
0226     }
0227 
0228     const StoredResource &storedResource =
0229         d->resourcesNew[resourceType][resourceFilename];
0230 
0231     if (!storedResource.data) {
0232         qWarning() << "Stored resource doesn't have a serialized representation!";
0233         return false;
0234     }
0235 
0236     device->write(*storedResource.data);
0237     return true;
0238 }
0239 
0240 bool KisMemoryStorage::addResource(const QString &resourceType,  KoResourceSP resource)
0241 {
0242     QHash<QString, StoredResource> &typedResources = d->resourcesNew[resourceType];
0243 
0244     if (typedResources.contains(resource->filename())) {
0245         return true;
0246     };
0247 
0248     StoredResource storedResource;
0249     storedResource.timestamp = QDateTime::currentDateTime();
0250     storedResource.data.reset(new QByteArray());
0251     if (resource->isSerializable()) {
0252         QBuffer buffer(storedResource.data.data());
0253         buffer.open(QIODevice::WriteOnly);
0254         if (!resource->saveToDevice(&buffer)) {
0255             storedResource.resource = resource;
0256         }
0257         buffer.close();
0258     } else {
0259         storedResource.resource = resource;
0260     }
0261 
0262     typedResources.insert(resource->filename(), storedResource);
0263 
0264     return true;
0265 }
0266 
0267 QString KisMemoryStorage::resourceMd5(const QString &url)
0268 {
0269 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
0270     QStringList parts = url.split('/', Qt::SkipEmptyParts);
0271 #else
0272     QStringList parts = url.split('/', QString::SkipEmptyParts);
0273 #endif
0274 
0275     Q_ASSERT(parts.size() == 2);
0276 
0277     const QString resourceType = parts[0];
0278     const QString resourceFilename = parts[1];
0279 
0280     QString result;
0281 
0282     if (d->resourcesNew.contains(resourceType) &&
0283         d->resourcesNew[resourceType].contains(resourceFilename)) {
0284 
0285         const StoredResource &storedResource =
0286             d->resourcesNew[resourceType][resourceFilename];
0287 
0288         if (storedResource.data->size() > 0 || storedResource.resource.isNull()) {
0289             result = KoMD5Generator::generateHash(*storedResource.data);
0290         } else {
0291             result = storedResource.resource->md5Sum();
0292         }
0293     }
0294 
0295     return result;
0296 }
0297 
0298 QSharedPointer<KisResourceStorage::ResourceIterator> KisMemoryStorage::resources(const QString &resourceType)
0299 {
0300     QVector<VersionedResourceEntry> entries;
0301 
0302 
0303     QHash<QString, StoredResource> &typedResources =
0304         d->resourcesNew[resourceType];
0305 
0306     for (auto it = typedResources.begin(); it != typedResources.end(); ++it) {
0307         VersionedResourceEntry entry;
0308         entry.filename = it.key();
0309         entry.lastModified = it.value().timestamp;
0310         entry.tagList = {}; // TODO
0311         entry.resourceType = resourceType;
0312         entries.append(entry);
0313 
0314     }
0315 
0316     KisStorageVersioningHelper::detectFileVersions(entries);
0317 
0318     return toQShared(new KisVersionedStorageIterator(entries, this));
0319 }
0320 
0321 QSharedPointer<KisResourceStorage::TagIterator> KisMemoryStorage::tags(const QString &resourceType)
0322 {
0323     return QSharedPointer<KisResourceStorage::TagIterator>(new MemoryTagIterator(d->tags[resourceType], resourceType));
0324 }
0325 
0326 void KisMemoryStorage::setMetaData(const QString &key, const QVariant &value)
0327 {
0328     d->metadata[key] = value;
0329 }
0330 
0331 QStringList KisMemoryStorage::metaDataKeys() const
0332 {
0333     return QStringList() << KisResourceStorage::s_meta_name;
0334 }
0335 
0336 QVariant KisMemoryStorage::metaData(const QString &key) const
0337 {
0338     QVariant r;
0339     if (d->metadata.contains(key)) {
0340         r = d->metadata[key];
0341     }
0342     return r;
0343 }