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