File indexing completed on 2024-05-12 15:59:48

0001 /*
0002  * SPDX-FileCopyrightText: 2018 Boudewijn Rempt <boud@valdyas.org>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include "KisBundleStorage.h"
0008 
0009 #include <QDebug>
0010 #include <QFileInfo>
0011 #include <QDir>
0012 #include <QDirIterator>
0013 
0014 #include <KisTag.h>
0015 #include "KisResourceStorage.h"
0016 #include <KoMD5Generator.h>
0017 #include "KoResourceBundle.h"
0018 #include "KoResourceBundleManifest.h"
0019 #include <KisGlobalResourcesInterface.h>
0020 
0021 #include <KisResourceLoaderRegistry.h>
0022 #include <kis_pointer_utils.h>
0023 #include <kis_debug.h>
0024 
0025 class KisBundleStorage::Private {
0026 public:
0027     Private(KisBundleStorage *_q) : q(_q) {}
0028 
0029     KisBundleStorage *q;
0030     QScopedPointer<KoResourceBundle> bundle;
0031 };
0032 
0033 
0034 class BundleTagIterator : public KisResourceStorage::TagIterator
0035 {
0036 public:
0037 
0038     BundleTagIterator(KoResourceBundle *bundle, const QString &resourceType)
0039         : m_bundle(bundle)
0040         , m_resourceType(resourceType)
0041     {
0042         QList<KoResourceBundleManifest::ResourceReference> resources = m_bundle->manifest().files(resourceType);
0043         Q_FOREACH(const KoResourceBundleManifest::ResourceReference &resourceReference, resources) {
0044             Q_FOREACH(const QString &tagname, resourceReference.tagList) {
0045                 if (!m_tags.contains(tagname)){
0046                     KisTagSP tag = QSharedPointer<KisTag>(new KisTag());
0047                     tag->setName(tagname);
0048                     tag->setComment(tagname);
0049                     tag->setUrl(tagname);
0050                     tag->setResourceType(resourceType);
0051                     tag->setValid(true);
0052                     m_tags[tagname] = tag;
0053                 }
0054 
0055                 m_tags[tagname]->setDefaultResources(m_tags[tagname]->defaultResources()
0056                                                      << QFileInfo(resourceReference.resourcePath).fileName());
0057             }
0058         }
0059         m_tagIterator.reset(new QListIterator<KisTagSP>(m_tags.values()));
0060     }
0061 
0062     bool hasNext() const override
0063     {
0064         return m_tagIterator->hasNext();
0065     }
0066 
0067     void next() override
0068     {
0069         const_cast<BundleTagIterator*>(this)->m_tag = m_tagIterator->next();
0070     }
0071     KisTagSP tag() const override { return m_tag; }
0072 
0073 private:
0074     QHash<QString, KisTagSP> m_tags;
0075     KoResourceBundle *m_bundle {0};
0076     QString m_resourceType;
0077     QScopedPointer<QListIterator<KisTagSP> > m_tagIterator;
0078     KisTagSP m_tag;
0079 };
0080 
0081 
0082 KisBundleStorage::KisBundleStorage(const QString &location)
0083     : KisStoragePlugin(location)
0084     , d(new Private(this))
0085 {
0086     d->bundle.reset(new KoResourceBundle(location));
0087     if (!d->bundle->load()) {
0088         qWarning() << "Could not load bundle" << location;
0089     }
0090 }
0091 
0092 KisBundleStorage::~KisBundleStorage()
0093 {
0094 }
0095 
0096 KisResourceStorage::ResourceItem KisBundleStorage::resourceItem(const QString &url)
0097 {
0098     KisResourceStorage::ResourceItem item;
0099     item.url = url;
0100     QStringList parts = url.split('/', QString::SkipEmptyParts);
0101     Q_ASSERT(parts.size() == 2);
0102     item.folder = parts[0];
0103     item.resourceType = parts[0];
0104     item.lastModified = QFileInfo(d->bundle->filename()).lastModified();
0105     return item;
0106 }
0107 
0108 bool KisBundleStorage::loadVersionedResource(KoResourceSP resource)
0109 {
0110     bool foundVersionedFile = false;
0111 
0112     const QString resourceType = resource->resourceType().first;
0113     const QString resourceUrl = resourceType + "/" + resource->filename();
0114 
0115     const QString bundleSaveLocation = location() + "_modified" + "/" + resourceType;
0116 
0117     if (QDir(bundleSaveLocation).exists()) {
0118         const QString fn = bundleSaveLocation  + "/" + resource->filename();
0119         const QFileInfo fi(fn);
0120         if (fi.exists()) {
0121             foundVersionedFile = true;
0122 
0123             QFile f(fn);
0124             if (!f.open(QFile::ReadOnly)) {
0125                 qWarning() << "Could not open resource file for reading" << fn;
0126                 return false;
0127             }
0128             if (!resource->loadFromDevice(&f, KisGlobalResourcesInterface::instance())) {
0129                 qWarning() << "Could not reload resource file" << fn;
0130                 return false;
0131             }
0132 
0133             sanitizeResourceFileNameCase(resource, fi.dir());
0134 
0135             // Check for the thumbnail
0136             if ((resource->image().isNull() || resource->thumbnail().isNull()) && !resource->thumbnailPath().isNull()) {
0137                 QImage img(bundleSaveLocation  + "/" +  '/' + resource->thumbnailPath());
0138                 resource->setImage(img);
0139                 resource->updateThumbnail();
0140             }
0141             f.close();
0142         }
0143     }
0144 
0145     if (!foundVersionedFile) {
0146         d->bundle->loadResource(resource);
0147     }
0148 
0149     return true;
0150 }
0151 
0152 QString KisBundleStorage::resourceMd5(const QString &url)
0153 {
0154     QString result;
0155 
0156     QFile modifiedFile(location() + "_modified" + "/" + url);
0157     if (modifiedFile.exists() && modifiedFile.open(QIODevice::ReadOnly)) {
0158         result = KoMD5Generator::generateHash(modifiedFile.readAll());
0159     } else {
0160         result = d->bundle->resourceMd5(url);
0161     }
0162 
0163     return result;
0164 }
0165 
0166 QSharedPointer<KisResourceStorage::ResourceIterator> KisBundleStorage::resources(const QString &resourceType)
0167 {
0168     QVector<VersionedResourceEntry> entries;
0169 
0170     QList<KoResourceBundleManifest::ResourceReference> references =
0171         d->bundle->manifest().files(resourceType);
0172 
0173     for (auto it = references.begin(); it != references.end(); ++it) {
0174         VersionedResourceEntry entry;
0175         // it->resourcePath() contains paths like "brushes/ink.png" or "brushes/subfolder/splash.png".
0176         // we need to cut off the first part and get "ink.png" in the first case,
0177         // but "subfolder/splash.png" in the second case in order for subfolders to work
0178         // so it cannot just use QFileInfo(verIt->url()).fileName() here.
0179         QString path = QDir::fromNativeSeparators(it->resourcePath); // make sure it uses Unix separators
0180         int folderEndIdx = path.indexOf("/");
0181         QString properFilenameWithSubfolders = path.right(path.length() - folderEndIdx - 1);
0182 
0183         entry.filename = properFilenameWithSubfolders;
0184         entry.lastModified = QFileInfo(location()).lastModified();
0185         entry.tagList = it->tagList;
0186         entry.resourceType = resourceType;
0187         entries.append(entry);
0188     }
0189 
0190     const QString bundleSaveLocation = location() + "_modified" + "/" + resourceType;
0191 
0192     QDirIterator it(bundleSaveLocation,
0193                     KisResourceLoaderRegistry::instance()->filters(resourceType),
0194                     QDir::Files | QDir::Readable,
0195                     QDirIterator::Subdirectories);;
0196 
0197     while (it.hasNext()) {
0198         it.next();
0199         QFileInfo info(it.fileInfo());
0200 
0201         VersionedResourceEntry entry;
0202         entry.filename = info.fileName();
0203         entry.lastModified = info.lastModified();
0204         entry.tagList = {}; // TODO
0205         entry.resourceType = resourceType;
0206         entries.append(entry);
0207     }
0208 
0209     KisStorageVersioningHelper::detectFileVersions(entries);
0210 
0211     return toQShared(new KisVersionedStorageIterator(entries, this));
0212 }
0213 
0214 QSharedPointer<KisResourceStorage::TagIterator> KisBundleStorage::tags(const QString &resourceType)
0215 {
0216     return QSharedPointer<KisResourceStorage::TagIterator>(new BundleTagIterator(d->bundle.data(), resourceType));
0217 }
0218 
0219 QImage KisBundleStorage::thumbnail() const
0220 {
0221     return d->bundle->image();
0222 }
0223 
0224 QStringList KisBundleStorage::metaDataKeys() const
0225 {
0226 
0227     return QStringList() << KisResourceStorage::s_meta_generator
0228                          << KisResourceStorage::s_meta_author
0229                          << KisResourceStorage::s_meta_title
0230                          << KisResourceStorage::s_meta_description
0231                          << KisResourceStorage::s_meta_initial_creator
0232                          << KisResourceStorage::s_meta_creator
0233                          << KisResourceStorage::s_meta_creation_date
0234                          << KisResourceStorage::s_meta_dc_date
0235                          << KisResourceStorage::s_meta_user_defined
0236                          << KisResourceStorage::s_meta_name
0237                          << KisResourceStorage::s_meta_value
0238                          << KisResourceStorage::s_meta_version;
0239 
0240 }
0241 
0242 QVariant KisBundleStorage::metaData(const QString &key) const
0243 {
0244     return d->bundle->metaData(key);
0245 }
0246 
0247 bool KisBundleStorage::saveAsNewVersion(const QString &resourceType, KoResourceSP resource)
0248 {
0249     QString bundleSaveLocation = location() + "_modified" + "/" + resourceType;
0250 
0251     if (!QDir(bundleSaveLocation).exists()) {
0252         QDir().mkpath(bundleSaveLocation);
0253     }
0254 
0255     return KisStorageVersioningHelper::addVersionedResource(bundleSaveLocation, resource, 1);
0256 }
0257 
0258 bool KisBundleStorage::exportResource(const QString &url, QIODevice *device)
0259 {
0260     QStringList parts = url.split('/', QString::SkipEmptyParts);
0261     Q_ASSERT(parts.size() == 2);
0262 
0263     const QString resourceType = parts[0];
0264     const QString resourceFileName = parts[1];
0265 
0266     bool foundVersionedFile = false;
0267 
0268     const QString bundleSaveLocation = location() + "_modified" + "/" + resourceType;
0269 
0270     if (QDir(bundleSaveLocation).exists()) {
0271         const QString fn = bundleSaveLocation  + "/" + resourceFileName;
0272         if (QFileInfo(fn).exists()) {
0273             foundVersionedFile = true;
0274 
0275             QFile f(fn);
0276             if (!f.open(QFile::ReadOnly)) {
0277                 qWarning() << "Could not open resource file for reading" << fn;
0278                 return false;
0279             }
0280 
0281             device->write(f.readAll());
0282         }
0283     }
0284 
0285     if (!foundVersionedFile) {
0286         d->bundle->exportResource(resourceType, resourceFileName, device);
0287     }
0288 
0289     return true;
0290 }