File indexing completed on 2024-05-12 15:59:55
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 return false; 0068 } 0069 0070 if (!errorMessage.isEmpty()) { 0071 warnKrita << "Error parsing manifest" << errorMessage << "line" << errorLine << "column" << errorColumn; 0072 return false; 0073 } 0074 0075 // First find the manifest:manifest node. 0076 QDomNode n = manifestDocument.firstChild(); 0077 for (; !n.isNull(); n = n.nextSibling()) { 0078 if (!n.isElement()) { 0079 continue; 0080 } 0081 if (n.toElement().localName() == "manifest" && n.toElement().namespaceURI() == KoXmlNS::manifest) { 0082 break; 0083 } 0084 } 0085 0086 if (n.isNull()) { 0087 // "Could not find manifest:manifest"; 0088 return false; 0089 } 0090 0091 // Now loop through the children of the manifest:manifest and 0092 // store all the manifest:file-entry elements. 0093 const QDomElement manifestElement = n.toElement(); 0094 for (n = manifestElement.firstChild(); !n.isNull(); n = n.nextSibling()) { 0095 0096 if (!n.isElement()) 0097 continue; 0098 0099 QDomElement el = n.toElement(); 0100 if (!(el.localName() == "file-entry" && el.namespaceURI() == KoXmlNS::manifest)) 0101 continue; 0102 0103 QString fullPath = el.attributeNS(KoXmlNS::manifest, "full-path", QString()); 0104 QString mediaType = el.attributeNS(KoXmlNS::manifest, "media-type", QString()); 0105 QString md5sum = el.attributeNS(KoXmlNS::manifest, "md5sum", QString()); 0106 QString version = el.attributeNS(KoXmlNS::manifest, "version", QString()); 0107 0108 QStringList tagList; 0109 QDomNode tagNode = n.firstChildElement().firstChildElement(); 0110 while (!tagNode.isNull()) { 0111 if (tagNode.firstChild().isText()) { 0112 tagList.append(tagNode.firstChild().toText().data()); 0113 } 0114 tagNode = tagNode.nextSibling(); 0115 } 0116 0117 // Only if fullPath is valid, should we store this entry. 0118 // If not, we don't bother to find out exactly what is wrong, we just skip it. 0119 if (!fullPath.isNull() && !mediaType.isEmpty() && !md5sum.isEmpty()) { 0120 addResource(mediaType, fullPath, tagList, QByteArray::fromHex(md5sum.toLatin1()), -1); 0121 } 0122 } 0123 0124 return true; 0125 } 0126 0127 bool KoResourceBundleManifest::save(QIODevice *device) 0128 { 0129 if (!device->isOpen()) { 0130 if (!device->open(QIODevice::WriteOnly)) { 0131 return false; 0132 } 0133 } 0134 KoXmlWriter manifestWriter(device); 0135 manifestWriter.startDocument("manifest:manifest"); 0136 manifestWriter.startElement("manifest:manifest"); 0137 manifestWriter.addAttribute("xmlns:manifest", KoXmlNS::manifest); 0138 manifestWriter.addAttribute("manifest:version", "1.2"); 0139 manifestWriter.addManifestEntry("/", "application/x-krita-resourcebundle"); 0140 0141 Q_FOREACH (QString resourceType, m_resources.uniqueKeys()) { 0142 Q_FOREACH (const ResourceReference &resource, m_resources[resourceType].values()) { 0143 manifestWriter.startElement("manifest:file-entry"); 0144 manifestWriter.addAttribute("manifest:media-type", resourceTypeToManifestType(resourceType)); 0145 // we cannot just use QFileInfo(resource.resourcePath).fileName() because it would cut off the subfolder 0146 // but the resourcePath is already correct, so let's just add the resourceType 0147 manifestWriter.addAttribute("manifest:full-path", resourceTypeToManifestType(resourceType) + "/" + resource.filenameInBundle); 0148 manifestWriter.addAttribute("manifest:md5sum", resource.md5sum); 0149 if (!resource.tagList.isEmpty()) { 0150 manifestWriter.startElement("manifest:tags"); 0151 Q_FOREACH (const QString tag, resource.tagList) { 0152 manifestWriter.startElement("manifest:tag"); 0153 manifestWriter.addTextNode(tag); 0154 manifestWriter.endElement(); 0155 } 0156 manifestWriter.endElement(); 0157 } 0158 manifestWriter.endElement(); 0159 } 0160 } 0161 0162 manifestWriter.endElement(); 0163 manifestWriter.endDocument(); 0164 0165 return true; 0166 } 0167 0168 void KoResourceBundleManifest::addResource(const QString &fileTypeName, const QString &fileName, const QStringList &fileTagList, const QString &md5, const int resourceId, const QString filenameInBundle) 0169 { 0170 ResourceReference ref(fileName, fileTagList, fileTypeName, md5, resourceId, filenameInBundle); 0171 if (!m_resources.contains(fileTypeName)) { 0172 m_resources[fileTypeName] = QMap<QString, ResourceReference>(); 0173 } 0174 m_resources[fileTypeName].insert(fileName, ref); 0175 } 0176 0177 void KoResourceBundleManifest::removeResource(KoResourceBundleManifest::ResourceReference &resource) 0178 { 0179 if (m_resources.contains(resource.fileTypeName)) { 0180 if (m_resources[resource.fileTypeName].contains(resource.resourcePath)) { 0181 m_resources[resource.fileTypeName].take(resource.resourcePath); 0182 } 0183 } 0184 } 0185 0186 QStringList KoResourceBundleManifest::types() const 0187 { 0188 return m_resources.keys(); 0189 } 0190 0191 QStringList KoResourceBundleManifest::tags() const 0192 { 0193 QSet<QString> tags; 0194 Q_FOREACH (const QString &type, m_resources.keys()) { 0195 Q_FOREACH (const ResourceReference &ref, m_resources[type].values()) { 0196 #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) 0197 tags += QSet<QString>(ref.tagList.begin(), ref.tagList.end()); 0198 #else 0199 tags += QSet<QString>::fromList(ref.tagList); 0200 #endif 0201 0202 } 0203 } 0204 #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) 0205 return QStringList(tags.begin(), tags.end()); 0206 #else 0207 return QStringList::fromSet(tags); 0208 #endif 0209 } 0210 0211 QList<KoResourceBundleManifest::ResourceReference> KoResourceBundleManifest::files(const QString &type) const 0212 { 0213 // If no type is specified we return all the resources 0214 if(type.isEmpty()) { 0215 QList<ResourceReference> resources; 0216 QList<QMap<QString, ResourceReference> >::iterator i; 0217 QList<QMap<QString, ResourceReference> > values = m_resources.values(); 0218 for(i = values.begin(); i != values.end(); ++i) { 0219 resources.append(i->values()); 0220 } 0221 0222 return resources; 0223 } 0224 else if (!m_resources.contains(type)) { 0225 return QList<KoResourceBundleManifest::ResourceReference>(); 0226 } 0227 return m_resources[type].values(); 0228 } 0229 0230 void KoResourceBundleManifest::removeFile(QString fileName) 0231 { 0232 QList<QString> tags; 0233 Q_FOREACH (const QString &type, m_resources.keys()) { 0234 if (m_resources[type].contains(fileName)) { 0235 m_resources[type].remove(fileName); 0236 } 0237 } 0238 } 0239