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 }