Krita Source Code Documentation
Loading...
Searching...
No Matches
KoResourceBundle.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2014 Victor Lafon metabolic.ewilan @hotmail.fr
3 *
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7#include "KoResourceBundle.h"
8
9#include <QBuffer>
10#include <QByteArray>
11#include <QCryptographicHash>
12#include <QDate>
13#include <QDir>
14#include <QMessageBox>
15#include <QPainter>
16#include <QProcessEnvironment>
17#include <QScopedPointer>
18#include <QStringList>
19
20#include <klocalizedstring.h>
21
22#include <KisMimeDatabase.h>
24#include <KoMD5Generator.h>
25#include <KoResourcePaths.h>
26#include <KoStore.h>
27#include <KoXmlWriter.h>
28#include "KisStoragePlugin.h"
31#include <KisResourceModel.h>
32#include <KoMD5Generator.h>
33
34#include <KritaVersionWrapper.h>
35
36#include <kis_debug.h>
38
39
40KoResourceBundle::KoResourceBundle(QString const& fileName)
41 : m_filename(fileName),
42 m_bundleVersion("1")
43{
45}
46
50
52{
53 return QString(".bundle");
54}
55
57{
58 if (m_filename.isEmpty()) return false;
59 QScopedPointer<KoStore> resourceStore(KoStore::createStore(m_filename, KoStore::Read, "application/x-krita-resourcebundle", KoStore::Zip));
60
61 if (!resourceStore || resourceStore->bad()) {
62 qWarning() << "Could not open store on bundle" << m_filename;
63 return false;
64 }
65
66 m_metadata.clear();
67
68 if (resourceStore->open("META-INF/manifest.xml")) {
69 if (!m_manifest.load(resourceStore->device())) {
70 qWarning() << "Could not open manifest for bundle" << m_filename;
71 return false;
72 }
73 resourceStore->close();
74
76 if (!resourceStore->hasFile(ref.resourcePath)) {
78 qWarning() << "Bundle" << filename() << "is broken. File" << ref.resourcePath << "is missing";
79 }
80 }
81
82 } else {
83 qWarning() << "Could not load META-INF/manifest.xml";
84 return false;
85 }
86
87 bool versionFound = false;
88 if (!readMetaData(resourceStore.data())) {
89 qWarning() << "Could not load meta.xml";
90 return false;
91 }
92
93 if (resourceStore->open("preview.png")) {
94 // Workaround for some OS (Debian, Ubuntu), where loading directly from the QIODevice
95 // fails with "libpng error: IDAT: CRC error"
96 QByteArray data = resourceStore->device()->readAll();
97 QBuffer buffer(&data);
98 m_thumbnail.load(&buffer, "PNG");
99 resourceStore->close();
100 } else {
101 qWarning() << "Could not open preview.png";
102 }
103
104 /*
105 * If no version is found it's an old bundle with md5 hashes to fix, or if some manifest resource entry
106 * doesn't not correspond to a file the bundle is "broken", in both cases we need to recreate the bundle.
107 */
108 if (!versionFound) {
110 }
111
112 return true;
113}
114
116{
117 return false;
118}
119
120bool saveResourceToStore(const QString &filename, KoResourceSP resource, KoStore *store, const QString &resType, KisResourceModel &model)
121{
122 if (!resource) {
123 qWarning() << "No Resource";
124 return false;
125 }
126
127 if (!resource->valid()) {
128 qWarning() << "Resource is not valid";
129 return false;
130 }
131 if (!store || store->bad()) {
132 qWarning() << "No Store or Store is Bad";
133 return false;
134 }
135
136 QBuffer buf;
137 buf.open(QFile::WriteOnly);
138
139 bool response = model.exportResource(resource, &buf);
140 if (!response) {
141 qWarning() << "Cannot save to device";
142 return false;
143 }
144
145 if (!store->open(resType + "/" + filename)) {
146 qWarning() << "Could not open file in store for resource";
147 return false;
148 }
149
150 qint64 size = store->write(buf.data());
151 store->close();
152 buf.close();
153 if (size != buf.size()) {
154 qWarning() << "Cannot save resource to the store" << size << buf.size();
155 return false;
156 }
157
158 if (!resource->thumbnailPath().isEmpty()) {
159 // hack for MyPaint brush presets previews
160 const QImage thumbnail = resource->thumbnail();
161
162 // clone resource to find out the file path for its preview
163 KoResourceSP clonedResource = resource->clone();
164 clonedResource->setFilename(filename);
165
166 if (!store->open(resType + "/" + clonedResource->thumbnailPath())) {
167 qWarning() << "Could not open file in store for resource thumbnail";
168 return false;
169 }
170 QBuffer buf;
171 buf.open(QFile::ReadWrite);
172 thumbnail.save(&buf, "PNG");
173
174 int size2 = store->write(buf.data());
175 if (size2 != buf.size()) {
176 qWarning() << "Cannot save thumbnail to the store" << size << buf.size();
177 }
178 store->close();
179 buf.close();
180 }
181
182
183 return size == buf.size();
184}
185
187{
188 if (m_filename.isEmpty()) return false;
189
191 setMetaData(KisResourceStorage::s_meta_creation_date, QLocale::c().toString(QDate::currentDate(), QStringLiteral("dd/MM/yyyy")));
192 }
193 setMetaData(KisResourceStorage::s_meta_dc_date, QLocale::c().toString(QDate::currentDate(), QStringLiteral("dd/MM/yyyy")));
194
195 QDir bundleDir = KoResourcePaths::saveLocation("data", "bundles");
196 bundleDir.cdUp();
197
198 QScopedPointer<KoStore> store(KoStore::createStore(m_filename, KoStore::Write, "application/x-krita-resourcebundle", KoStore::Zip));
199
200 if (!store || store->bad()) return false;
201
202 Q_FOREACH (const QString &resType, m_manifest.types()) {
203 KisResourceModel model(resType);
205 Q_FOREACH (const KoResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) {
206 KoResourceSP res;
207 if (ref.resourceId >= 0) res = model.resourceForId(ref.resourceId);
208 if (!res) res = model.resourcesForMD5(ref.md5sum).first();
209 if (!res) res = model.resourcesForFilename(QFileInfo(ref.resourcePath).fileName()).first();
210 if (!res) {
211 qWarning() << "Could not find resource" << resType << ref.resourceId << ref.md5sum << ref.resourcePath;
212 continue;
213 }
214
215 if (!saveResourceToStore(ref.filenameInBundle, res, store.data(), resType, model)) {
216 qWarning() << "Could not save resource" << resType << res->name();
217 }
218 }
219 }
220
221 if (!m_thumbnail.isNull()) {
222 QByteArray byteArray;
223 QBuffer buffer(&byteArray);
224 m_thumbnail.save(&buffer, "PNG");
225 if (!store->open("preview.png")) qWarning() << "Could not open preview.png";
226 if (store->write(byteArray) != buffer.size()) qWarning() << "Could not write preview.png";
227 store->close();
228 }
229
230 saveManifest(store);
231
232 saveMetadata(store);
233
234 store->finalize();
235
236 return true;
237}
238
239bool KoResourceBundle::saveToDevice(QIODevice */*dev*/) const
240{
241 return false;
242}
243
244void KoResourceBundle::setMetaData(const QString &key, const QString &value)
245{
246 m_metadata.insert(key, value);
247}
248
249const QString KoResourceBundle::metaData(const QString &key, const QString &defaultValue) const
250{
251 if (m_metadata.contains(key)) {
252 return m_metadata[key];
253 }
254 else {
255 return defaultValue;
256 }
257}
258
259void KoResourceBundle::addResource(QString resourceType, QString filePath, QVector<KisTagSP> fileTagList, const QString md5sum, const int resourceId, const QString filenameInBundle)
260{
261 QStringList tags;
262 Q_FOREACH(KisTagSP tag, fileTagList) {
263 tags << tag->url();
264 }
265 m_manifest.addResource(resourceType, filePath, tags, md5sum, resourceId, filenameInBundle);
266}
267
272
277
279{
280 if (!image.isNull()) {
282 m_thumbnail = m_thumbnail.scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation);
283 }
284 else {
285 m_thumbnail = QImage(256, 256, QImage::Format_ARGB32);
286 QPainter gc(&m_thumbnail);
287 gc.fillRect(0, 0, 256, 256, Qt::red);
288 gc.end();
289 }
290}
291
292void KoResourceBundle::writeMeta(const QString &metaTag, KoXmlWriter *writer)
293{
294 if (m_metadata.contains(metaTag)) {
295 QByteArray mt = metaTag.toUtf8();
296 QByteArray tx = m_metadata[metaTag].toUtf8();
297 writer->startElement(mt);
298 writer->addTextNode(tx);
299 writer->endElement();
300 }
301}
302
303void KoResourceBundle::writeUserDefinedMeta(const QString &metaTag, KoXmlWriter *writer)
304{
305 if (m_metadata.contains(metaTag)) {
306 writer->startElement("meta:meta-userdefined");
307 writer->addAttribute("meta:name", metaTag);
308 writer->addAttribute("meta:value", m_metadata[metaTag]);
309 writer->endElement();
310 }
311}
312
314{
315 if (!resourceStore->open("meta.xml")) {
316 qWarning() << "Could not open meta.xml for" << m_filename;
317 return false;
318 }
319
320 QDomDocument doc;
321 if (!doc.setContent(resourceStore->device())) {
322 qWarning() << "Could not parse meta.xml for" << m_filename;
323 return false;
324 }
325
326 const QDomElement root = doc.documentElement();
327 if (root.tagName() != "meta:meta") {
328 qWarning() << "Expected meta:meta element root, but found"
329 << root.tagName();
330 return false;
331 }
332
333 QDomElement e;
334 for (e = root.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
335 QString name = e.tagName();
336 QString value = e.text();
337 if (name == "meta:meta-userdefined") {
338 name = e.attribute("meta:name");
339 value = e.attribute("meta:value");
340
341 if (name == "tag") {
343 continue;
344 }
345
346 if (name != "email" &&
347 name != "license" &&
348 name != "website") {
349 qWarning() << "Unrecognized metadata: "
350 << e.tagName()
351 << name
352 << value;
353 }
354
355 m_metadata.insert(name, value);
356 name = "meta:" + name;
357 } else if (name == "cd:creator") {
358 // Bundles from some versions have prefix 'cd' instead of 'dc'.
359 name = "dc:creator";
360 }
361
362 if (!m_metadata.contains(name)) {
363 m_metadata.insert(name, value);
364 }
365 }
366
367 resourceStore->close();
368 return true;
369}
370
371void KoResourceBundle::saveMetadata(QScopedPointer<KoStore> &store)
372{
373 QBuffer buf;
374
375 store->open("meta.xml");
376 buf.open(QBuffer::WriteOnly);
377
378 KoXmlWriter metaWriter(&buf);
379 metaWriter.startDocument("office:document-meta");
380 metaWriter.startElement("meta:meta");
381 metaWriter.addAttribute("xmlns:meta", KisResourceStorage::s_xmlns_meta);
382 metaWriter.addAttribute("xmlns:dc", KisResourceStorage::s_xmlns_dc);
383
385
386 QByteArray ba1 = KisResourceStorage::s_meta_version.toUtf8();
387 metaWriter.startElement(ba1);
388 QByteArray ba2 = m_bundleVersion.toUtf8();
389 metaWriter.addTextNode(ba2);
390 metaWriter.endElement();
391
402
403 // For compatibility
404 writeUserDefinedMeta("email", &metaWriter);
405 writeUserDefinedMeta("license", &metaWriter);
406 writeUserDefinedMeta("website", &metaWriter);
407
408
409 Q_FOREACH (const QString &tag, m_bundletags) {
410 QByteArray ba1 = KisResourceStorage::s_meta_user_defined.toUtf8();
411 QByteArray ba2 = KisResourceStorage::s_meta_name.toUtf8();
412 QByteArray ba3 = KisResourceStorage::s_meta_value.toUtf8();
413 metaWriter.startElement(ba1);
414 metaWriter.addAttribute(ba2, "tag");
415 metaWriter.addAttribute(ba3, tag);
416 metaWriter.endElement();
417 }
418
419 metaWriter.endElement(); // meta:meta
420 metaWriter.endDocument();
421
422 buf.close();
423 store->write(buf.data());
424 store->close();
425}
426
427void KoResourceBundle::saveManifest(QScopedPointer<KoStore> &store)
428{
429 store->open("META-INF/manifest.xml");
430 QBuffer buf;
431 buf.open(QBuffer::WriteOnly);
432 m_manifest.save(&buf);
433 buf.close();
434 store->write(buf.data());
435 store->close();
436}
437
439{
440 return m_manifest.files().count();
441}
442
447
448KoResourceSP KoResourceBundle::resource(const QString &resourceType, const QString &filepath)
449{
450 QString mime = KisMimeDatabase::mimeTypeForSuffix(filepath);
452 if (!loader) {
453 qWarning() << "Could not create loader for" << resourceType << filepath << mime;
454 return 0;
455 }
456
457 QStringList parts = filepath.split('/', Qt::SkipEmptyParts);
458
459 Q_ASSERT(parts.size() == 2);
460
461 KoResourceSP resource = loader->create(parts[1]);
462 return loadResource(resource) ? resource : 0;
463}
464
465bool KoResourceBundle::exportResource(const QString &resourceType, const QString &fileName, QIODevice *device)
466{
467 if (m_filename.isEmpty()) return false;
468
469 QScopedPointer<KoStore> resourceStore(KoStore::createStore(m_filename, KoStore::Read, "application/x-krita-resourcebundle", KoStore::Zip));
470
471 if (!resourceStore || resourceStore->bad()) {
472 qWarning() << "Could not open store on bundle" << m_filename;
473 return false;
474 }
475 const QString filePath = QString("%1/%2").arg(resourceType).arg(fileName);
476
477 if (!resourceStore->open(filePath)) {
478 qWarning() << "Could not open file in bundle" << filePath;
479 return false;
480 }
481
482 device->write(resourceStore->device()->readAll());
483
484 return true;
485}
486
488{
489 if (m_filename.isEmpty()) return false;
490
491 const QString resourceType = resource->resourceType().first;
492
493 QScopedPointer<KoStore> resourceStore(KoStore::createStore(m_filename, KoStore::Read, "application/x-krita-resourcebundle", KoStore::Zip));
494
495 if (!resourceStore || resourceStore->bad()) {
496 qWarning() << "Could not open store on bundle" << m_filename;
497 return false;
498 }
499 const QString fileName = QString("%1/%2").arg(resourceType).arg(resource->filename());
500
501 if (!resourceStore->open(fileName)) {
502 qWarning() << "Could not open file in bundle" << fileName;
503 return false;
504 }
505
506 if (!resource->loadFromDevice(resourceStore->device(),
508 qWarning() << "Could not load the resource from the bundle" << resourceType << fileName << m_filename;
509 return false;
510 }
511
512 resourceStore->close();
513
514 if ((resource->image().isNull() || resource->thumbnail().isNull()) && !resource->thumbnailPath().isNull()) {
515
516 if (!resourceStore->open(resourceType + '/' + resource->thumbnailPath())) {
517 qWarning() << "Could not open thumbnail in bundle" << resource->thumbnailPath();
518 return false;
519 }
520
521 QImage img;
522 img.load(resourceStore->device(), QFileInfo(resource->thumbnailPath()).completeSuffix().toLatin1());
523 resource->setImage(img);
524 resource->updateThumbnail();
525
526 resourceStore->close();
527 }
528
529 return true;
530}
531
532QString KoResourceBundle::resourceMd5(const QString &url)
533{
534 QString result;
535
536 if (m_filename.isEmpty()) return result;
537
538 QScopedPointer<KoStore> resourceStore(KoStore::createStore(m_filename, KoStore::Read, "application/x-krita-resourcebundle", KoStore::Zip));
539
540 if (!resourceStore || resourceStore->bad()) {
541 qWarning() << "Could not open store on bundle" << m_filename;
542 return result;
543 }
544 if (!resourceStore->open(url)) {
545 qWarning() << "Could not open file in bundle" << url;
546 return result;
547 }
548
549 result = KoMD5Generator::generateHash(resourceStore->device());
550 resourceStore->close();
551
552 return result;
553}
554
556{
557 return m_thumbnail;
558}
559
561{
562 return m_filename;
563}
float value(const T *src, size_t ch)
bool saveResourceToStore(const QString &filename, KoResourceSP resource, KoStore *store, const QString &resType, KisResourceModel &model)
static KisResourcesInterfaceSP instance()
static QString mimeTypeForSuffix(const QString &suffix)
Find the mimetype for a given extension. The extension may have the form "*.xxx" or "xxx".
The KisResourceLoader class is an abstract interface class that must be implemented by actual resourc...
virtual KoResourceSP create(const QString &name)=0
static KisResourceLoaderRegistry * instance()
KisResourceLoaderBase * loader(const QString &resourceType, const QString &mimetype) const
The KisResourceModel class provides the main access to resources. It is possible to filter the resour...
KoResourceSP resourceForId(int id) const
bool exportResource(KoResourceSP resource, QIODevice *device) override
exportResource exports a resource into a QIODevice
QVector< KoResourceSP > resourcesForMD5(const QString md5sum) const
QVector< KoResourceSP > resourcesForFilename(QString fileName) const
void setResourceFilter(ResourceFilter filter) override
static const QString s_meta_author
static const QString s_meta_title
static const QString s_meta_user_defined
static const QString s_meta_creation_date
static const QString s_meta_value
static const QString s_meta_email
static const QString s_meta_license
static const QString s_xmlns_meta
static const QString s_meta_description
static const QString s_meta_dc_date
static const QString s_meta_website
static const QString s_meta_generator
static const QString s_meta_version
static const QString s_meta_name
static const QString s_xmlns_dc
static const QString s_meta_initial_creator
static const QString s_meta_creator
static QString generateHash(const QString &filename)
generateHash reads the given file and generates a hex-encoded md5sum for the file.
bool save(QIODevice *device)
save the ResourceBundleManifest to the given device
bool load(QIODevice *device)
load the ResourceBundleManifest from the given device
void removeResource(ResourceReference &resource)
void addResource(const QString &fileType, const QString &fileName, const QStringList &tagFileList, const QString &md5, const int resourceId=-1, const QString filenameInBundle="")
addTag : Add a file tag as a child of the fileType tag.
QList< ResourceReference > files(const QString &type=QString()) const
KoResourceBundle(QString const &fileName)
ResourceBundle : Ctor *.
void saveMetadata(QScopedPointer< KoStore > &store)
saveMetadata: saves bundle metadata
void saveManifest(QScopedPointer< KoStore > &store)
saveManifest: saves bundle manifest
KoResourceSP resource(const QString &resourceType, const QString &filepath)
QMap< QString, QString > m_metadata
bool save()
save : Save this resource.
bool loadFromDevice(QIODevice *dev)
QStringList resourceTypes() const
QString filename() const
void addResource(QString fileType, QString filePath, QVector< KisTagSP > fileTagList, const QString md5sum, const int resourceId=-1, const QString filenameInBundle="")
addFile : Add a file to the bundle
QSet< QString > m_bundletags
void writeMeta(const QString &metaTag, KoXmlWriter *writer)
bool load()
load : Load this resource.
bool readMetaData(KoStore *resourceStore)
QList< QString > getTagsList()
bool saveToDevice(QIODevice *dev) const
KoResourceBundleManifest & manifest()
void setMetaData(const QString &key, const QString &value)
addMeta : Add a Metadata to the resource
bool loadResource(KoResourceSP resource)
QString defaultFileExtension() const
defaultFileExtension
bool exportResource(const QString &resourceType, const QString &fileName, QIODevice *device)
virtual ~KoResourceBundle()
~ResourceBundle : Dtor
void writeUserDefinedMeta(const QString &metaTag, KoXmlWriter *writer)
const QString metaData(const QString &key, const QString &defaultValue=QString()) const
void setThumbnail(QImage)
QString resourceMd5(const QString &url)
KoResourceBundleManifest m_manifest
static QString saveLocation(const QString &type, const QString &suffix=QString(), bool create=true)
qint64 write(const QByteArray &data)
Definition KoStore.cpp:198
QIODevice * device() const
Definition KoStore.cpp:171
bool close()
Definition KoStore.cpp:156
@ Read
Definition KoStore.h:29
@ Write
Definition KoStore.h:29
@ Zip
Definition KoStore.h:30
bool bad() const
Definition KoStore.cpp:414
static KoStore * createStore(const QString &fileName, Mode mode, const QByteArray &appIdentification=QByteArray(), Backend backend=Auto, bool writeMimetype=true)
Definition KoStore.cpp:39
bool open(const QString &name)
Definition KoStore.cpp:109
void startElement(const char *tagName, bool indentInside=true)
void endDocument()
Call this to terminate an XML document.
void startDocument(const char *rootElemName, const char *publicId=0, const char *systemId=0)
void addTextNode(const QString &str)
void endElement()
void addAttribute(const char *attrName, const QString &value)
Definition KoXmlWriter.h:61
KRITAVERSION_EXPORT QString versionString(bool checkGit=false)