Changeset View
Changeset View
Standalone View
Standalone View
plugins/messageviewer/bodypartformatter/pkpass/pkpassfile.cpp
- This file was added.
1 | /* | ||||
---|---|---|---|---|---|
2 | Copyright (c) 2017 Volker Krause <vkrause@kde.org> | ||||
3 | | ||||
4 | This library is free software; you can redistribute it and/or modify it | ||||
5 | under the terms of the GNU Library General Public License as published by | ||||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||||
7 | option) any later version. | ||||
8 | | ||||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||||
12 | License for more details. | ||||
13 | | ||||
14 | You should have received a copy of the GNU Library General Public License | ||||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||||
17 | 02110-1301, USA. | ||||
18 | */ | ||||
19 | | ||||
20 | #include "pkpassfile.h" | ||||
21 | #include "pkpassboardingpass.h" | ||||
22 | | ||||
23 | #include <KZip> | ||||
24 | #include <prison/Prison> | ||||
25 | | ||||
26 | #include <QBuffer> | ||||
27 | #include <QDebug> | ||||
28 | #include <QImage> | ||||
29 | #include <QJsonArray> | ||||
30 | #include <QJsonDocument> | ||||
31 | #include <QJsonObject> | ||||
32 | #include <QLocale> | ||||
33 | #include <QTextCodec> | ||||
34 | | ||||
35 | PkPassFile::PkPassFile(const QString &passType, QObject* parent) | ||||
36 | : QObject (parent) | ||||
37 | , m_passType(passType) | ||||
38 | { | ||||
39 | } | ||||
40 | | ||||
41 | PkPassFile::~PkPassFile() = default; | ||||
42 | | ||||
43 | QJsonObject PkPassFile::data() const | ||||
44 | { | ||||
45 | return m_passObj; | ||||
46 | } | ||||
47 | | ||||
48 | QJsonObject PkPassFile::passData() const | ||||
49 | { | ||||
50 | return m_passObj.value(m_passType).toObject(); | ||||
51 | } | ||||
52 | | ||||
53 | QString PkPassFile::message(const QString& key) const | ||||
54 | { | ||||
55 | const auto it = m_messages.constFind(key); | ||||
56 | if (it != m_messages.constEnd()) | ||||
57 | return it.value(); | ||||
58 | return key; | ||||
59 | } | ||||
60 | | ||||
61 | QString PkPassFile::backgroundColor() const | ||||
62 | { | ||||
63 | return m_passObj.value(QLatin1String("backgroundColor")).toString(); | ||||
64 | } | ||||
65 | | ||||
66 | QString PkPassFile::foregroundColor() const | ||||
67 | { | ||||
68 | return m_passObj.value(QLatin1String("foregroundColor")).toString(); | ||||
69 | } | ||||
70 | | ||||
71 | QString PkPassFile::labelColor() const | ||||
72 | { | ||||
73 | const auto c = m_passObj.value(QLatin1String("labelColor")).toString(); | ||||
74 | if (!c.isEmpty()) | ||||
75 | return c; | ||||
76 | return foregroundColor(); | ||||
77 | } | ||||
78 | | ||||
79 | QString PkPassFile::logoText() const | ||||
80 | { | ||||
81 | return message(m_passObj.value(QLatin1String("logoText")).toString()); | ||||
82 | } | ||||
83 | | ||||
84 | QImage PkPassFile::logo() const | ||||
85 | { | ||||
86 | auto file = m_zip->directory()->file(QStringLiteral("logo.png")); | ||||
87 | if (!file) | ||||
88 | return {}; | ||||
89 | std::unique_ptr<QIODevice> dev(file->createDevice()); | ||||
90 | return QImage::fromData(dev->readAll()); | ||||
91 | } | ||||
92 | | ||||
93 | QImage PkPassFile::barcode() const | ||||
94 | { | ||||
95 | const auto barcodeData = data().value(QLatin1String("barcode")).toObject(); | ||||
96 | const auto formatName = barcodeData.value(QLatin1String("format")).toString(); | ||||
97 | const auto msg = barcodeData.value(QLatin1String("message")).toString(); | ||||
98 | // TODO: consider messageEncoding, once Prison supports that | ||||
99 | if (formatName.isEmpty() || msg.isEmpty()) | ||||
100 | return {}; | ||||
101 | | ||||
102 | std::unique_ptr<Prison::AbstractBarcode> code; | ||||
103 | if (formatName == QLatin1String("PKBarcodeFormatQR")) | ||||
104 | code.reset(Prison::createBarcode(Prison::QRCode)); | ||||
105 | else if (formatName == QLatin1String("PKBarcodeFormatPDF417")) | ||||
106 | {} // TODO | ||||
107 | else if (formatName == QLatin1String("PKBarcodeFormatAztec")) | ||||
108 | code.reset(Prison::createBarcode(Prison::Aztec)); | ||||
109 | else if (formatName == QLatin1String("PKBarcodeFormatCode128")) | ||||
110 | {} // TODO | ||||
111 | | ||||
112 | if (!code) | ||||
113 | return {}; | ||||
114 | code->setData(msg); | ||||
115 | return code->toImage(code->minimumSize() * 4); | ||||
116 | } | ||||
117 | | ||||
118 | QString PkPassFile::barcodeAltText() const | ||||
119 | { | ||||
120 | return data().value(QLatin1String("barcode")).toObject().value(QLatin1String("altText")).toString(); | ||||
121 | } | ||||
122 | | ||||
123 | QVector<PkPassField> PkPassFile::auxiliaryFields() const | ||||
124 | { | ||||
125 | return fields(QLatin1String("auxiliaryFields")); | ||||
126 | } | ||||
127 | | ||||
128 | QVector<PkPassField> PkPassFile::backFields() const | ||||
129 | { | ||||
130 | return fields(QLatin1String("backFields")); | ||||
131 | } | ||||
132 | | ||||
133 | QVector<PkPassField> PkPassFile::headerFields() const | ||||
134 | { | ||||
135 | return fields(QLatin1String("headerFields")); | ||||
136 | } | ||||
137 | | ||||
138 | QVector<PkPassField> PkPassFile::primaryFields() const | ||||
139 | { | ||||
140 | return fields(QLatin1String("primaryFields")); | ||||
141 | } | ||||
142 | | ||||
143 | QVector<PkPassField> PkPassFile::secondaryFields() const | ||||
144 | { | ||||
145 | return fields(QLatin1String("secondaryFields")); | ||||
146 | } | ||||
147 | | ||||
148 | PkPassFile* PkPassFile::fromData(const QByteArray &data, QObject *parent) | ||||
149 | { | ||||
150 | std::unique_ptr<QBuffer> buffer(new QBuffer); | ||||
151 | buffer->setData(data); | ||||
152 | buffer->open(QBuffer::ReadOnly); | ||||
153 | | ||||
154 | std::unique_ptr<KZip> zip(new KZip(buffer.get())); | ||||
155 | if (!zip->open(QIODevice::ReadOnly)) | ||||
156 | return nullptr; | ||||
157 | | ||||
158 | // extract pass.json | ||||
159 | auto file = zip->directory()->file(QStringLiteral("pass.json")); | ||||
160 | if (!file) | ||||
161 | return nullptr; | ||||
162 | std::unique_ptr<QIODevice> dev(file->createDevice()); | ||||
163 | const auto passObj = QJsonDocument::fromJson(dev->readAll()).object(); | ||||
164 | | ||||
165 | PkPassFile *pass = nullptr; | ||||
166 | if (passObj.contains(QLatin1String("boardingPass"))) | ||||
167 | pass = new PkPassBoardingPass(parent); | ||||
168 | // TODO: coupon, eventTicket, storeCard, generic | ||||
169 | else | ||||
170 | pass = new PkPassFile(QStringLiteral("generic"), parent); | ||||
171 | | ||||
172 | pass->m_buffer = std::move(buffer); | ||||
173 | pass->m_zip = std::move(zip); | ||||
174 | pass->m_passObj = passObj; | ||||
175 | pass->parse(); | ||||
176 | return pass; | ||||
177 | } | ||||
178 | | ||||
179 | void PkPassFile::parse() | ||||
180 | { | ||||
181 | // find the message catalog | ||||
182 | auto lang = QLocale().name(); | ||||
183 | auto idx = lang.indexOf(QLatin1Char('_')); | ||||
184 | if (idx > 0) | ||||
185 | lang = lang.left(idx); | ||||
186 | lang += QLatin1String(".lproj"); | ||||
187 | if (!parseMessages(lang)) | ||||
188 | parseMessages(QStringLiteral("en.lproj")); | ||||
189 | } | ||||
190 | | ||||
191 | static int indexOfUnquoted(const QString &catalog, QLatin1Char c, int start) | ||||
192 | { | ||||
193 | for (int i = start; i < catalog.size(); ++i) { | ||||
194 | if (catalog.at(i) == c) | ||||
195 | return i; | ||||
196 | if (catalog.at(i) == QLatin1Char('\\')) | ||||
197 | ++i; | ||||
198 | } | ||||
199 | | ||||
200 | return -1; | ||||
201 | } | ||||
202 | | ||||
203 | static QString unquote(const QStringRef &str) | ||||
204 | { | ||||
205 | QString res; | ||||
206 | res.reserve(str.size()); | ||||
207 | for (int i = 0; i < str.size(); ++i) { | ||||
208 | const auto c1 = str.at(i); | ||||
209 | if (c1 == QLatin1Char('\\') && i < str.size() - 1) { | ||||
210 | const auto c2 = str.at(i + 1); | ||||
211 | if (c2 == QLatin1Char('r')) { | ||||
212 | res.push_back(QLatin1Char('\r')); | ||||
213 | } else if (c2 == QLatin1Char('n')) { | ||||
214 | res.push_back(QLatin1Char('\n')); | ||||
215 | } else if (c2 == QLatin1Char('\\')) { | ||||
216 | res.push_back(c2); | ||||
217 | } else { | ||||
218 | res.push_back(c1); | ||||
219 | res.push_back(c2); | ||||
220 | } | ||||
221 | ++i; | ||||
222 | } else { | ||||
223 | res.push_back(c1); | ||||
224 | } | ||||
225 | } | ||||
226 | return res; | ||||
227 | } | ||||
228 | | ||||
229 | bool PkPassFile::parseMessages(const QString &lang) | ||||
230 | { | ||||
231 | auto entry = m_zip->directory()->entry(lang); | ||||
232 | if (!entry || !entry->isDirectory()) | ||||
233 | return false; | ||||
234 | | ||||
235 | auto dir = dynamic_cast<const KArchiveDirectory*>(entry); | ||||
236 | auto file = dir->file(QStringLiteral("pass.strings")); | ||||
237 | if (!file) | ||||
238 | return false; | ||||
239 | | ||||
240 | std::unique_ptr<QIODevice> dev(file->createDevice()); | ||||
241 | auto codec = QTextCodec::codecForName("UTF-16BE"); | ||||
242 | const auto catalog = codec->toUnicode(dev->readAll()); | ||||
243 | | ||||
244 | int idx = 0; | ||||
245 | while (idx < catalog.size()) { | ||||
246 | // key | ||||
247 | const auto keyBegin = indexOfUnquoted(catalog, QLatin1Char('"'), idx) + 1; | ||||
248 | if (keyBegin < 1) | ||||
249 | break; | ||||
250 | const auto keyEnd = indexOfUnquoted(catalog, QLatin1Char('"'), keyBegin); | ||||
251 | if (keyEnd <= keyBegin) | ||||
252 | break; | ||||
253 | | ||||
254 | // value | ||||
255 | const auto valueBegin = indexOfUnquoted(catalog, QLatin1Char('"'), keyEnd + 2) + 1; // there's at least also the '=' | ||||
256 | if (valueBegin <= keyEnd) | ||||
257 | break; | ||||
258 | const auto valueEnd = indexOfUnquoted(catalog, QLatin1Char('"'), valueBegin); | ||||
259 | if (valueEnd <= valueBegin) | ||||
260 | break; | ||||
261 | | ||||
262 | const auto key = catalog.mid(keyBegin, keyEnd - keyBegin); | ||||
263 | const auto value = unquote(catalog.midRef(valueBegin, valueEnd - valueBegin)); | ||||
264 | m_messages.insert(key, value); | ||||
265 | idx = valueEnd + 1; // there's at least the linebreak and/or a ';' | ||||
266 | } | ||||
267 | | ||||
268 | return !m_messages.isEmpty(); | ||||
269 | } | ||||
270 | | ||||
271 | QVector<PkPassField> PkPassFile::fields(const QLatin1String &fieldType) const | ||||
272 | { | ||||
273 | const auto a = passData().value(fieldType).toArray(); | ||||
274 | QVector<PkPassField> f; | ||||
275 | f.reserve(a.size()); | ||||
276 | foreach (const auto &v, a) | ||||
277 | f.push_back(PkPassField{v.toObject(), this}); | ||||
278 | return f; | ||||
279 | } |