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