Krita Source Code Documentation
Loading...
Searching...
No Matches
KoQuaZipStore.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2019 Boudewijn Rempt <boud@valdyas.org>
3 *
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6#include "KoQuaZipStore.h"
7#include "KoStore_p.h"
8
9#include <StoreDebug.h>
10
11#include <zlib.h>
12#include <quazip.h>
13#include <quazipfile.h>
14#include <quazipdir.h>
15#include <quazipfileinfo.h>
16#include <quazipnewinfo.h>
17
18#include <QTemporaryFile>
19#include <QTextCodec>
20#include <QByteArray>
21#include <QBuffer>
22
23#include <KConfig>
24#include <KSharedConfig>
25#include <KConfigGroup>
26
28
31
32 QuaZip *archive {0};
33 QuaZipFile *currentFile {0};
35 bool directoryListCached {false};
36 int compressionLevel {Z_DEFAULT_COMPRESSION};
37 bool usingSaveFile {false};
38 QByteArray cache;
39 QBuffer buffer;
40};
41
42
43KoQuaZipStore::KoQuaZipStore(const QString &_filename, KoStore::Mode _mode, const QByteArray &appIdentification, bool writeMimetype)
44 : KoStore(_mode, writeMimetype)
45 , dd(new Private())
46{
47 Q_D(KoStore);
48 d->localFileName = _filename;
49 dd->archive = new QuaZip(_filename);
50 init(appIdentification);
51
52}
53
54KoQuaZipStore::KoQuaZipStore(QIODevice *dev, KoStore::Mode _mode, const QByteArray &appIdentification, bool writeMimetype)
55 : KoStore(_mode, writeMimetype)
56 , dd(new Private())
57{
58 dd->archive = new QuaZip(dev);
59 init(appIdentification);
60}
61
63{
64 Q_D(KoStore);
65
66 if (d->good && dd->currentFile && dd->currentFile->isOpen()) {
67 dd->currentFile->close();
68 }
69
70 if (!d->finalized) {
71 finalize();
72 }
73
74
75 // NOTE:
76 // If the dd->currentFile is corrupt (and ->getZipError() returns an error code)
77 // QuaZip cannot really close it properly (and I don't see an accessible function to reset the error code).
78 // Therefore the destructor thinks the file is open and tries to close it.
79 // And closing the file means checking if the zip archive associated with the file is open or not.
80 // Therefore the archive must exist when the file is being closed, therefore also when it's being deleted.
81 // In comparison, the archive can be deleted whenever and it doesn't check the current file.
82 // Therefore we gotta delete the file first, then the zip archive.
83
84 // From QuaZip code comments for `QuaZipFile(QuaZip *zip, QObject *parent =nullptr);`:
85 // "* Summary: do not close \c zip object or change its current file as
86 // * long as QuaZipFile is open."
87
88 if (dd->currentFile) {
89 delete dd->currentFile;
90 }
91 delete dd->archive;
92
93}
94
96{
97
98 if (enabled) {
99 dd->compressionLevel = Z_DEFAULT_COMPRESSION;
100 }
101 else {
102 dd->compressionLevel = Z_NO_COMPRESSION;
103 }
104}
105
106qint64 KoQuaZipStore::write(const char *_data, qint64 _len)
107{
108 Q_D(KoStore);
109 if (_len == 0) return 0;
110
111 if (!d->isOpen) {
112 errorStore << "KoStore: You must open before writing" << Qt::endl;
113 return 0;
114 }
115
116 if (d->mode != Write) {
117 errorStore << "KoStore: Can not write to store that is opened for reading" << Qt::endl;
118 return 0;
119 }
120
121 qint64 nwritten = dd->buffer.write(_data, _len);
122 d->size += nwritten;
123 return nwritten;
124}
125
127{
128 // If in Read mode, we can assume the directory listing won't change between invocations.
129 if(mode() == Read) {
130 if(!dd->directoryListCached) {
131 dd->directoryListCache = dd->archive->getFileNameList();
132 dd->directoryListCached = true;
133 }
134 return dd->directoryListCache;
135 }
136 else {
137 return dd->archive->getFileNameList();
138 }
139}
140
141void KoQuaZipStore::init(const QByteArray &appIdentification)
142{
143 Q_D(KoStore);
144
145 bool enableZip64 = false;
146 if (appIdentification == "application/x-krita") {
147 enableZip64 = KSharedConfig::openConfig()->group("").readEntry<bool>("UseZip64", false);
148 }
149
150 dd->archive->setDataDescriptorWritingEnabled(false);
151 dd->archive->setZip64Enabled(enableZip64);
152 dd->archive->setFileNameCodec("UTF-8");
153 dd->usingSaveFile = dd->archive->getIoDevice() && dd->archive->getIoDevice()->inherits("QSaveFile");
154 dd->archive->setAutoClose(!dd->usingSaveFile);
155
156 d->good = dd->archive->open(d->mode == Write ? QuaZip::mdCreate : QuaZip::mdUnzip);
157
158 if (!d->good) {
159 return;
160 }
161
162 if (d->mode == Write) {
163 if (d->writeMimetype) {
164 QuaZipFile f(dd->archive);
165 QuaZipNewInfo newInfo("mimetype");
166 newInfo.setPermissions(QFileDevice::ReadOwner | QFileDevice::ReadGroup | QFileDevice::ReadOther);
167 if (!f.open(QIODevice::WriteOnly, newInfo, 0, 0, 0, Z_NO_COMPRESSION)) {
168 d->good = false;
169 return;
170 }
171 f.write(appIdentification);
172 f.close();
173 }
174 }
175 else {
176 debugStore << dd->archive->getEntriesCount() << directoryList();
177 d->good = dd->archive->getEntriesCount();
178 }
179}
180
182{
183 Q_D(KoStore);
184
185 d->stream = 0;
186 if (d->good && !dd->usingSaveFile) {
187 dd->archive->close();
188 }
189 return dd->archive->getZipError() == ZIP_OK;
190
191}
192
193bool KoQuaZipStore::openWrite(const QString &name)
194{
195 Q_D(KoStore);
196 QString fixedPath = name;
197 fixedPath.replace("//", "/");
198
199 delete d->stream;
200 d->stream = 0; // Not used when writing
201
202 delete dd->currentFile;
203 dd->currentFile = new QuaZipFile(dd->archive);
204 QuaZipNewInfo newInfo(fixedPath);
205 newInfo.setPermissions(QFileDevice::ReadOwner | QFileDevice::ReadGroup | QFileDevice::ReadOther);
206 bool r = dd->currentFile->open(QIODevice::WriteOnly, newInfo, 0, 0, Z_DEFLATED, dd->compressionLevel);
207 if (!r) {
208 qWarning() << "Could not open" << name << dd->currentFile->getZipError();
209 }
210
211 dd->cache = QByteArray();
212 dd->buffer.setBuffer(&dd->cache);
213 dd->buffer.open(QBuffer::WriteOnly);
214
215 return r;
216}
217
218bool KoQuaZipStore::openRead(const QString &name)
219{
220 Q_D(KoStore);
221
222 QString fixedPath = name;
223 fixedPath.replace("//", "/");
224
225 delete d->stream;
226 d->stream = 0;
227 delete dd->currentFile;
228 dd->currentFile = 0;
229
230 if (!currentPath().isEmpty() && !fixedPath.startsWith(currentPath())) {
231 fixedPath = currentPath() + '/' + fixedPath;
232 }
233
234 if (!d->substituteThis.isEmpty()) {
235 fixedPath = fixedPath.replace(d->substituteThis, d->substituteWith);
236 }
237
238 if (!dd->archive->setCurrentFile(fixedPath)) {
239 qWarning() << "\t\tCould not set current file" << dd->archive->getZipError() << fixedPath;
240 return false;
241 }
242
243 dd->currentFile = new QuaZipFile(dd->archive);
244 if (!dd->currentFile->open(QIODevice::ReadOnly)) {
245 qWarning() << "\t\t\tBut could not open!!!" << dd->archive->getZipError();
246 return false;
247 }
248 d->stream = dd->currentFile;
249 d->size = dd->currentFile->size();
250 return true;
251}
252
254{
255 Q_D(KoStore);
256
257 bool r = true;
258 if (dd->currentFile->write(dd->cache) != dd->cache.size()) {
259 // write() returns number of bytes written, or -1 in case of error
260 // let's allow write 0 bytes in the cache, when needed
261 qWarning() << "Could not write buffer to the file";
262 r = false;
263 }
264 dd->buffer.close();
265 dd->currentFile->close();
266 d->stream = 0;
267 return (r && dd->currentFile->getZipError() == ZIP_OK);
268}
269
271{
272 Q_D(KoStore);
273 d->stream = 0;
274 return true;
275}
276
277bool KoQuaZipStore::enterRelativeDirectory(const QString & /*path*/)
278{
279 return true;
280}
281
283{
284 QString fixedPath = path;
285 fixedPath.replace("//", "/");
286
287 if (fixedPath.isEmpty()) {
288 fixedPath = "/";
289 }
290
291 QuaZipDir currentDir (dd->archive, fixedPath);
292
293 return currentDir.exists();
294}
295
296bool KoQuaZipStore::fileExists(const QString &absPath) const
297{
298 Q_D(const KoStore);
299
300 QString fixedPath = absPath;
301 fixedPath.replace("//", "/");
302
303 if (!d->substituteThis.isEmpty()) {
304 fixedPath = fixedPath.replace(d->substituteThis, d->substituteWith);
305 }
306
307 return directoryList().contains(fixedPath);
308}
#define debugStore
Definition StoreDebug.h:15
#define errorStore
Definition StoreDebug.h:17
KoQuaZipStore(const QString &_filename, Mode _mode, const QByteArray &appIdentification, bool writeMimetype=true)
QStringList directoryList() const override
bool enterRelativeDirectory(const QString &dirName) override
bool openRead(const QString &name) override
bool fileExists(const QString &absPath) const override
void init(const QByteArray &appIdentification)
bool closeRead() override
bool doFinalize() override
~KoQuaZipStore() override
void setCompressionEnabled(bool enabled) override
bool closeWrite() override
qint64 write(const char *_data, qint64 _len) override
bool enterAbsoluteDirectory(const QString &path) override
const QScopedPointer< Private > dd
bool openWrite(const QString &name) override
@ Read
Definition KoStore.h:29
@ Write
Definition KoStore.h:29
Mode mode() const
Definition KoStore.cpp:420
QString currentPath() const
Definition KoStore.cpp:281
bool finalize()
Definition KoStore.cpp:395