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

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 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
0101     QStringList parts = url.split('/', Qt::SkipEmptyParts);
0102 #else
0103     QStringList parts = url.split('/', QString::SkipEmptyParts);
0104 #endif
0105     Q_ASSERT(parts.size() == 2);
0106     item.folder = parts[0];
0107     item.resourceType = parts[0];
0108     item.lastModified = QFileInfo(d->bundle->filename()).lastModified();
0109     return item;
0110 }
0111 
0112 bool KisBundleStorage::loadVersionedResource(KoResourceSP resource)
0113 {
0114     bool foundVersionedFile = false;
0115 
0116     const QString resourceType = resource->resourceType().first;
0117     const QString resourceUrl = resourceType + "/" + resource->filename();
0118 
0119     const QString bundleSaveLocation = location() + "_modified" + "/" + resourceType;
0120 
0121     if (QDir(bundleSaveLocation).exists()) {
0122         const QString fn = bundleSaveLocation  + "/" + resource->filename();
0123         const QFileInfo fi(fn);
0124         if (fi.exists()) {
0125             foundVersionedFile = true;
0126 
0127             QFile f(fn);
0128             if (!f.open(QFile::ReadOnly)) {
0129                 qWarning() << "Could not open resource file for reading" << fn;
0130                 return false;
0131             }
0132             if (!resource->loadFromDevice(&f, KisGlobalResourcesInterface::instance())) {
0133                 qWarning() << "Could not reload resource file" << fn;
0134                 return false;
0135             }
0136 
0137             sanitizeResourceFileNameCase(resource, fi.dir());
0138 
0139             // Check for the thumbnail
0140             if ((resource->image().isNull() || resource->thumbnail().isNull()) && !resource->thumbnailPath().isNull()) {
0141                 QImage img(bundleSaveLocation  + "/" +  '/' + resource->thumbnailPath());
0142                 resource->setImage(img);
0143                 resource->updateThumbnail();
0144             }
0145             f.close();
0146         }
0147     }
0148 
0149     if (!foundVersionedFile) {
0150         d->bundle->loadResource(resource);
0151     }
0152 
0153     return true;
0154 }
0155 
0156 QString KisBundleStorage::resourceMd5(const QString &url)
0157 {
0158     QString result;
0159 
0160     QFile modifiedFile(location() + "_modified" + "/" + url);
0161     if (modifiedFile.exists() && modifiedFile.open(QIODevice::ReadOnly)) {
0162         result = KoMD5Generator::generateHash(modifiedFile.readAll());
0163     } else {
0164         result = d->bundle->resourceMd5(url);
0165     }
0166 
0167     return result;
0168 }
0169 
0170 QSharedPointer<KisResourceStorage::ResourceIterator> KisBundleStorage::resources(const QString &resourceType)
0171 {
0172     QVector<VersionedResourceEntry> entries;
0173 
0174     QList<KoResourceBundleManifest::ResourceReference> references =
0175         d->bundle->manifest().files(resourceType);
0176 
0177     for (auto it = references.begin(); it != references.end(); ++it) {
0178         VersionedResourceEntry entry;
0179         // it->resourcePath() contains paths like "brushes/ink.png" or "brushes/subfolder/splash.png".
0180         // we need to cut off the first part and get "ink.png" in the first case,
0181         // but "subfolder/splash.png" in the second case in order for subfolders to work
0182         // so it cannot just use QFileInfo(verIt->url()).fileName() here.
0183         QString path = QDir::fromNativeSeparators(it->resourcePath); // make sure it uses Unix separators
0184         int folderEndIdx = path.indexOf("/");
0185         QString properFilenameWithSubfolders = path.right(path.length() - folderEndIdx - 1);
0186 
0187         entry.filename = properFilenameWithSubfolders;
0188         entry.lastModified = QFileInfo(location()).lastModified();
0189         entry.tagList = it->tagList;
0190         entry.resourceType = resourceType;
0191         entries.append(entry);
0192     }
0193 
0194     const QString bundleSaveLocation = location() + "_modified" + "/" + resourceType;
0195 
0196     QDirIterator it(bundleSaveLocation,
0197                     KisResourceLoaderRegistry::instance()->filters(resourceType),
0198                     QDir::Files | QDir::Readable,
0199                     QDirIterator::Subdirectories);;
0200 
0201     while (it.hasNext()) {
0202         it.next();
0203         QFileInfo info(it.fileInfo());
0204 
0205         VersionedResourceEntry entry;
0206         entry.filename = info.fileName();
0207         entry.lastModified = info.lastModified();
0208         entry.tagList = {}; // TODO
0209         entry.resourceType = resourceType;
0210         entries.append(entry);
0211     }
0212 
0213     KisStorageVersioningHelper::detectFileVersions(entries);
0214 
0215     return toQShared(new KisVersionedStorageIterator(entries, this));
0216 }
0217 
0218 QSharedPointer<KisResourceStorage::TagIterator> KisBundleStorage::tags(const QString &resourceType)
0219 {
0220     return QSharedPointer<KisResourceStorage::TagIterator>(new BundleTagIterator(d->bundle.data(), resourceType));
0221 }
0222 
0223 QImage KisBundleStorage::thumbnail() const
0224 {
0225     return d->bundle->image();
0226 }
0227 
0228 QStringList KisBundleStorage::metaDataKeys() const
0229 {
0230 
0231     return QStringList() << KisResourceStorage::s_meta_generator
0232                          << KisResourceStorage::s_meta_author
0233                          << KisResourceStorage::s_meta_title
0234                          << KisResourceStorage::s_meta_description
0235                          << KisResourceStorage::s_meta_initial_creator
0236                          << KisResourceStorage::s_meta_creator
0237                          << KisResourceStorage::s_meta_creation_date
0238                          << KisResourceStorage::s_meta_dc_date
0239                          << KisResourceStorage::s_meta_user_defined
0240                          << KisResourceStorage::s_meta_name
0241                          << KisResourceStorage::s_meta_value
0242                          << KisResourceStorage::s_meta_version;
0243 
0244 }
0245 
0246 QVariant KisBundleStorage::metaData(const QString &key) const
0247 {
0248     return d->bundle->metaData(key);
0249 }
0250 
0251 bool KisBundleStorage::saveAsNewVersion(const QString &resourceType, KoResourceSP resource)
0252 {
0253     QString bundleSaveLocation = location() + "_modified" + "/" + resourceType;
0254 
0255     if (!QDir(bundleSaveLocation).exists()) {
0256         QDir().mkpath(bundleSaveLocation);
0257     }
0258 
0259     return KisStorageVersioningHelper::addVersionedResource(bundleSaveLocation, resource, 1);
0260 }
0261 
0262 bool KisBundleStorage::exportResource(const QString &url, QIODevice *device)
0263 {
0264 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
0265     QStringList parts = url.split('/', Qt::SkipEmptyParts);
0266 #else
0267     QStringList parts = url.split('/', QString::SkipEmptyParts);
0268 #endif
0269     Q_ASSERT(parts.size() == 2);
0270 
0271     const QString resourceType = parts[0];
0272     const QString resourceFileName = parts[1];
0273 
0274     bool foundVersionedFile = false;
0275 
0276     const QString bundleSaveLocation = location() + "_modified" + "/" + resourceType;
0277 
0278     if (QDir(bundleSaveLocation).exists()) {
0279         const QString fn = bundleSaveLocation  + "/" + resourceFileName;
0280         if (QFileInfo(fn).exists()) {
0281             foundVersionedFile = true;
0282 
0283             QFile f(fn);
0284             if (!f.open(QFile::ReadOnly)) {
0285                 qWarning() << "Could not open resource file for reading" << fn;
0286                 return false;
0287             }
0288 
0289             device->write(f.readAll());
0290         }
0291     }
0292 
0293     if (!foundVersionedFile) {
0294         d->bundle->exportResource(resourceType, resourceFileName, device);
0295     }
0296 
0297     return true;
0298 }