File indexing completed on 2024-05-19 04:27:43
0001 /* This file is part of the KDE project 0002 SPDX-FileCopyrightText: 2014 Victor Lafon <metabolic.ewilan@hotmail.fr> 0003 0004 SPDX-License-Identifier: LGPL-2.1-or-later 0005 */ 0006 #include "KoResourceBundleManifest.h" 0007 0008 #include <QList> 0009 #include <QSet> 0010 #include <QString> 0011 #include <QDomDocument> 0012 #include <QDomElement> 0013 #include <QDomNode> 0014 #include <QDomNodeList> 0015 #include <QFileInfo> 0016 0017 #include <KoXmlNS.h> 0018 #include <KoXmlWriter.h> 0019 0020 #include <kis_debug.h> 0021 0022 #include "KisResourceTypes.h" 0023 0024 QString resourceTypeToManifestType(const QString &type) { 0025 if (type.startsWith("ko_")) { 0026 return type.mid(3); 0027 } 0028 else if (type.startsWith("kis_")) { 0029 return type.mid(4); 0030 } 0031 else { 0032 return type; 0033 } 0034 } 0035 0036 QString manifestTypeToResourceType(const QString &type) { 0037 if (type == ResourceType::Patterns || type == ResourceType::Gradients || type == ResourceType::Palettes) { 0038 return "ko_" + type; 0039 } 0040 else { 0041 return "kis_" + type; 0042 } 0043 } 0044 0045 KoResourceBundleManifest::KoResourceBundleManifest() 0046 { 0047 } 0048 0049 KoResourceBundleManifest::~KoResourceBundleManifest() 0050 { 0051 } 0052 0053 bool KoResourceBundleManifest::load(QIODevice *device) 0054 { 0055 m_resources.clear(); 0056 if (!device->isOpen()) { 0057 if (!device->open(QIODevice::ReadOnly)) { 0058 return false; 0059 } 0060 } 0061 0062 QDomDocument manifestDocument; 0063 QString errorMessage; 0064 int errorLine; 0065 int errorColumn; 0066 if (!manifestDocument.setContent(device, true, &errorMessage, &errorLine, &errorColumn)) { 0067 warnKrita << "Error parsing manifest" << errorMessage 0068 << "line" << errorLine 0069 << "column" << errorColumn; 0070 return false; 0071 } 0072 0073 QDomElement root = manifestDocument.documentElement(); 0074 if (root.localName() != "manifest" || root.namespaceURI() != KoXmlNS::manifest) { 0075 return false; 0076 } 0077 0078 QDomElement e = root.firstChildElement("file-entry"); 0079 for (; !e.isNull(); e = e.nextSiblingElement("file-entry")) { 0080 if (!parseFileEntry(e)) { 0081 warnKrita << "Skipping invalid manifest entry" 0082 << "line" << e.lineNumber(); 0083 } 0084 } 0085 0086 return true; 0087 } 0088 0089 bool KoResourceBundleManifest::parseFileEntry(const QDomElement &e) 0090 { 0091 if (e.localName() != "file-entry" || e.namespaceURI() != KoXmlNS::manifest) { 0092 return false; 0093 } 0094 0095 QString fullPath = e.attributeNS(KoXmlNS::manifest, "full-path"); 0096 QString mediaType = e.attributeNS(KoXmlNS::manifest, "media-type"); 0097 QString md5sum = e.attributeNS(KoXmlNS::manifest, "md5sum"); 0098 QString version = e.attributeNS(KoXmlNS::manifest, "version"); 0099 0100 if (fullPath == "/" && mediaType == "application/x-krita-resourcebundle") { 0101 // The manifest always contains an entry for the bundle root. 0102 // This is not a resource, so skip it without indicating failure. 0103 return true; 0104 } else if (fullPath.isNull() || mediaType.isNull() || md5sum.isNull()) { 0105 return false; 0106 } 0107 0108 QStringList tagList; 0109 QDomElement t = e.firstChildElement("tags") 0110 .firstChildElement("tag"); 0111 for (; !t.isNull(); t = t.nextSiblingElement("tag")) { 0112 QString tag = t.text(); 0113 if (!tag.isNull()) { 0114 tagList.append(tag); 0115 } 0116 } 0117 0118 addResource(mediaType, fullPath, tagList, QByteArray::fromHex(md5sum.toLatin1()), -1); 0119 return true; 0120 } 0121 0122 bool KoResourceBundleManifest::save(QIODevice *device) 0123 { 0124 if (!device->isOpen()) { 0125 if (!device->open(QIODevice::WriteOnly)) { 0126 return false; 0127 } 0128 } 0129 KoXmlWriter manifestWriter(device); 0130 manifestWriter.startDocument("manifest:manifest"); 0131 manifestWriter.startElement("manifest:manifest"); 0132 manifestWriter.addAttribute("xmlns:manifest", KoXmlNS::manifest); 0133 manifestWriter.addAttribute("manifest:version", "1.2"); 0134 manifestWriter.addManifestEntry("/", "application/x-krita-resourcebundle"); 0135 0136 Q_FOREACH (QString resourceType, m_resources.uniqueKeys()) { 0137 Q_FOREACH (const ResourceReference &resource, m_resources[resourceType].values()) { 0138 manifestWriter.startElement("manifest:file-entry"); 0139 manifestWriter.addAttribute("manifest:media-type", resourceTypeToManifestType(resourceType)); 0140 // we cannot just use QFileInfo(resource.resourcePath).fileName() because it would cut off the subfolder 0141 // but the resourcePath is already correct, so let's just add the resourceType 0142 manifestWriter.addAttribute("manifest:full-path", resourceTypeToManifestType(resourceType) + "/" + resource.filenameInBundle); 0143 manifestWriter.addAttribute("manifest:md5sum", resource.md5sum); 0144 if (!resource.tagList.isEmpty()) { 0145 manifestWriter.startElement("manifest:tags"); 0146 Q_FOREACH (const QString tag, resource.tagList) { 0147 manifestWriter.startElement("manifest:tag"); 0148 manifestWriter.addTextNode(tag); 0149 manifestWriter.endElement(); 0150 } 0151 manifestWriter.endElement(); 0152 } 0153 manifestWriter.endElement(); 0154 } 0155 } 0156 0157 manifestWriter.endElement(); 0158 manifestWriter.endDocument(); 0159 0160 return true; 0161 } 0162 0163 void KoResourceBundleManifest::addResource(const QString &fileTypeName, const QString &fileName, const QStringList &fileTagList, const QString &md5, const int resourceId, const QString filenameInBundle) 0164 { 0165 ResourceReference ref(fileName, fileTagList, fileTypeName, md5, resourceId, filenameInBundle); 0166 if (!m_resources.contains(fileTypeName)) { 0167 m_resources[fileTypeName] = QMap<QString, ResourceReference>(); 0168 } 0169 m_resources[fileTypeName].insert(fileName, ref); 0170 } 0171 0172 void KoResourceBundleManifest::removeResource(KoResourceBundleManifest::ResourceReference &resource) 0173 { 0174 if (m_resources.contains(resource.fileTypeName)) { 0175 if (m_resources[resource.fileTypeName].contains(resource.resourcePath)) { 0176 m_resources[resource.fileTypeName].take(resource.resourcePath); 0177 } 0178 } 0179 } 0180 0181 QStringList KoResourceBundleManifest::types() const 0182 { 0183 return m_resources.keys(); 0184 } 0185 0186 QStringList KoResourceBundleManifest::tags() const 0187 { 0188 QSet<QString> tags; 0189 Q_FOREACH (const QString &type, m_resources.keys()) { 0190 Q_FOREACH (const ResourceReference &ref, m_resources[type].values()) { 0191 #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) 0192 tags += QSet<QString>(ref.tagList.begin(), ref.tagList.end()); 0193 #else 0194 tags += QSet<QString>::fromList(ref.tagList); 0195 #endif 0196 0197 } 0198 } 0199 #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) 0200 return QStringList(tags.begin(), tags.end()); 0201 #else 0202 return QStringList::fromSet(tags); 0203 #endif 0204 } 0205 0206 QList<KoResourceBundleManifest::ResourceReference> KoResourceBundleManifest::files(const QString &type) const 0207 { 0208 QList<ResourceReference> resources; 0209 0210 if (type.isEmpty()) { 0211 // If no type is specified we return all the resources. 0212 Q_FOREACH (const QString &type, m_resources.keys()) { 0213 resources += m_resources[type].values(); 0214 } 0215 } else if (m_resources.contains(type)) { 0216 resources = m_resources[type].values(); 0217 } 0218 0219 return resources; 0220 } 0221 0222 void KoResourceBundleManifest::removeFile(QString fileName) 0223 { 0224 QList<QString> tags; 0225 Q_FOREACH (const QString &type, m_resources.keys()) { 0226 if (m_resources[type].contains(fileName)) { 0227 m_resources[type].remove(fileName); 0228 } 0229 } 0230 } 0231