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 }