1/****************************************************************************
2**
3** Copyright (C) 2015 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the QtLocation module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL3$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or later as published by the Free
28** Software Foundation and appearing in the file LICENSE.GPL included in
29** the packaging of this file. Please review the following information to
30** ensure the GNU General Public License version 2.0 requirements will be
31** met: http://www.gnu.org/licenses/gpl-2.0.html.
32**
33** $QT_END_LICENSE$
34**
35****************************************************************************/
36
37#include "qplacemanagerengine_nokiav2.h"
38
39#include "placesv2/qplacecategoriesreplyhere.h"
40#include "placesv2/qplacecontentreplyimpl.h"
41#include "placesv2/qplacesearchsuggestionreplyimpl.h"
42#include "placesv2/qplacesearchreplyhere.h"
43#include "placesv2/qplacedetailsreplyimpl.h"
44#include "placesv2/qplaceidreplyimpl.h"
45#include "qgeonetworkaccessmanager.h"
46#include "qgeouriprovider.h"
47#include "uri_constants.h"
48#include "qgeoerror_messages.h"
49
50#include <QCoreApplication>
51#include <QtCore/QFile>
52#include <QtCore/QJsonArray>
53#include <QtCore/QJsonDocument>
54#include <QtCore/QJsonObject>
55#include <QtCore/QRegularExpression>
56#include <QtCore/QStandardPaths>
57#include <QtCore/QUrlQuery>
58#include <QtNetwork/QNetworkProxy>
59#include <QtNetwork/QNetworkProxyFactory>
60
61#include <QtLocation/QPlaceContentRequest>
62#include <QtPositioning/QGeoCircle>
63
64QT_BEGIN_NAMESPACE
65
66static const char FIXED_CATEGORIES_string[] =
67 "eat-drink\0"
68 "going-out\0"
69 "sights-museums\0"
70 "transport\0"
71 "accommodation\0"
72 "shopping\0"
73 "leisure-outdoor\0"
74 "administrative-areas-buildings\0"
75 "natural-geographical\0"
76 "petrol-station\0"
77 "atm-bank-exchange\0"
78 "toilet-rest-area\0"
79 "hospital-health-care-facility\0"
80 "eat-drink|restaurant\0" // subcategories always start after relative parent category
81 "eat-drink|coffee-tea\0"
82 "eat-drink|snacks-fast-food\0"
83 "transport|airport"
84 "\0";
85
86static const int FIXED_CATEGORIES_indices[] = {
87 0, 10, 20, 35, 45, 59, 68, 84,
88 115, 136, 151, 169, 186, 216, 237, 258,
89 285, -1
90};
91
92static const char * const NokiaIcon = "nokiaIcon";
93static const char * const IconPrefix = "iconPrefix";
94static const char * const NokiaIconGenerated = "nokiaIconGenerated";
95
96static const char * const IconThemeKey = "places.icons.theme";
97static const char * const LocalDataPathKey = "places.local_data_path";
98
99class CategoryParser
100{
101public:
102 CategoryParser();
103 bool parse(const QString &fileName);
104
105 QPlaceCategoryTree tree() const { return m_tree; }
106 QHash<QString, QUrl> restIdToIconHash() const { return m_restIdToIconHash; }
107
108 QString errorString() const;
109
110private:
111 void processCategory(int level, const QString &id,
112 const QString &parentId = QString());
113
114 QJsonObject m_exploreObject;
115 QPlaceCategoryTree m_tree;
116 QString m_errorString;
117
118 QHash<QString, QUrl> m_restIdToIconHash;
119};
120
121CategoryParser::CategoryParser()
122{
123}
124
125bool CategoryParser::parse(const QString &fileName)
126{
127 m_exploreObject = QJsonObject();
128 m_tree.clear();
129 m_errorString.clear();
130
131 QFile mappingFile(fileName);
132
133 if (mappingFile.open(flags: QIODevice::ReadOnly)) {
134 QJsonDocument document = QJsonDocument::fromJson(json: mappingFile.readAll());
135 if (document.isObject()) {
136 QJsonObject docObject = document.object();
137 if (docObject.contains(QStringLiteral("offline_explore"))) {
138 m_exploreObject = docObject.value(QStringLiteral("offline_explore"))
139 .toObject();
140 if (m_exploreObject.contains(QStringLiteral("ROOT"))) {
141 processCategory(level: 0, id: QString());
142 return true;
143 }
144 } else {
145 m_errorString = fileName
146 + QStringLiteral("does not contain the offline_explore property");
147 return false;
148 }
149 } else {
150 m_errorString = fileName + QStringLiteral("is not an json object");
151 return false;
152 }
153 }
154 m_errorString = QString::fromLatin1(str: "Unable to open ") + fileName;
155 return false;
156}
157
158void CategoryParser::processCategory(int level, const QString &id, const QString &parentId)
159{
160 //We are basing the tree on a DAG from the input file, however we are simplyfing
161 //this into a 2 level tree, and a given category only has one parent
162 //
163 // A->B->Z
164 // A->C->Z
165 // Z in this case is not in the tree because it is 3 levels deep.
166 //
167 // X->Z
168 // Y->Z
169 // Only one of these is shown in the tree since Z can only have one parent
170 // the choice made between X and Y is arbitrary.
171 const int maxLevel = 2;
172 PlaceCategoryNode node;
173 node.category.setCategoryId(id);
174 node.parentId = parentId;
175
176 m_tree.insert(akey: node.category.categoryId(), avalue: node);
177 //this is simply to mark the node as being visited.
178 //a proper assignment to the tree happens at the end of function
179
180 QJsonObject categoryJson = m_exploreObject.value(key: id.isEmpty()
181 ? QStringLiteral("ROOT") : id).toObject();
182 QJsonArray children = categoryJson.value(QStringLiteral("children")).toArray();
183
184 if (level + 1 <= maxLevel && !categoryJson.contains(QStringLiteral("final"))) {
185 for (int i = 0; i < children.count(); ++i) {
186 QString childId = children.at(i).toString();
187 if (!m_tree.contains(akey: childId)) {
188 node.childIds.append(t: childId);
189 processCategory(level: level + 1, id: childId, parentId: id);
190 }
191 }
192 }
193
194 m_tree.insert(akey: node.category.categoryId(), avalue: node);
195}
196
197QPlaceManagerEngineNokiaV2::QPlaceManagerEngineNokiaV2(
198 QGeoNetworkAccessManager *networkManager,
199 const QVariantMap &parameters,
200 QGeoServiceProvider::Error *error,
201 QString *errorString)
202 : QPlaceManagerEngine(parameters)
203 , m_manager(networkManager)
204 , m_uriProvider(new QGeoUriProvider(this, parameters, QStringLiteral("here.places.host"), PLACES_HOST))
205{
206 Q_ASSERT(networkManager);
207 m_manager->setParent(this);
208
209 m_locales.append(t: QLocale());
210
211 m_appId = parameters.value(QStringLiteral("here.app_id")).toString();
212 m_appCode = parameters.value(QStringLiteral("here.token")).toString();
213
214 m_theme = parameters.value(akey: IconThemeKey, adefaultValue: QString()).toString();
215
216 if (m_theme == QStringLiteral("default"))
217 m_theme.clear();
218
219 m_localDataPath = parameters.value(akey: LocalDataPathKey, adefaultValue: QString()).toString();
220 if (m_localDataPath.isEmpty()) {
221 QStringList dataLocations = QStandardPaths::standardLocations(type: QStandardPaths::GenericDataLocation);
222
223 if (!dataLocations.isEmpty() && !dataLocations.first().isEmpty()) {
224 m_localDataPath = dataLocations.first()
225 + QStringLiteral("/here/qtlocation/data");
226 }
227 }
228
229 if (error)
230 *error = QGeoServiceProvider::NoError;
231
232 if (errorString)
233 errorString->clear();
234}
235
236QPlaceManagerEngineNokiaV2::~QPlaceManagerEngineNokiaV2() {}
237
238QPlaceDetailsReply *QPlaceManagerEngineNokiaV2::getPlaceDetails(const QString &placeId)
239{
240 QUrl requestUrl(QString::fromLatin1(str: "http://") + m_uriProvider->getCurrentHost() +
241 QStringLiteral("/places/v1/places/") + placeId);
242
243 QUrlQuery queryItems;
244
245 queryItems.addQueryItem(QStringLiteral("tf"), QStringLiteral("html"));
246 //queryItems.append(qMakePair<QString, QString>(QStringLiteral("size"), QString::number(5)));
247 //queryItems.append(qMakePair<QString, QString>(QStringLiteral("image_dimensions"), QStringLiteral("w64-h64,w100")));
248
249 requestUrl.setQuery(queryItems);
250
251 QNetworkReply *networkReply = sendRequest(url: requestUrl);
252
253 QPlaceDetailsReplyImpl *reply = new QPlaceDetailsReplyImpl(networkReply, this);
254 reply->setPlaceId(placeId);
255 connect(sender: reply, SIGNAL(finished()), receiver: this, SLOT(replyFinished()));
256 connect(sender: reply, SIGNAL(error(QPlaceReply::Error,QString)),
257 receiver: this, SLOT(replyError(QPlaceReply::Error,QString)));
258
259 return reply;
260}
261
262QPlaceContentReply *QPlaceManagerEngineNokiaV2::getPlaceContent(const QPlaceContentRequest &request)
263{
264 QNetworkReply *networkReply = 0;
265
266 if (request.contentContext().userType() == qMetaTypeId<QUrl>()) {
267 QUrl u = request.contentContext().value<QUrl>();
268
269 networkReply = sendRequest(url: u);
270 } else {
271 QUrl requestUrl(QString::fromLatin1(str: "http://") + m_uriProvider->getCurrentHost() +
272 QStringLiteral("/places/v1/places/") + request.placeId() +
273 QStringLiteral("/media/"));
274
275 QUrlQuery queryItems;
276
277 switch (request.contentType()) {
278 case QPlaceContent::ImageType:
279 requestUrl.setPath(path: requestUrl.path() + QStringLiteral("images"));
280
281 queryItems.addQueryItem(QStringLiteral("tf"), QStringLiteral("html"));
282
283 if (request.limit() > 0)
284 queryItems.addQueryItem(QStringLiteral("size"), value: QString::number(request.limit()));
285
286 //queryItems.append(qMakePair<QString, QString>(QStringLiteral("image_dimensions"), QStringLiteral("w64-h64,w100")));
287
288 requestUrl.setQuery(queryItems);
289
290 networkReply = sendRequest(url: requestUrl);
291 break;
292 case QPlaceContent::ReviewType:
293 requestUrl.setPath(path: requestUrl.path() + QStringLiteral("reviews"));
294
295 queryItems.addQueryItem(QStringLiteral("tf"), QStringLiteral("html"));
296
297 if (request.limit() > 0)
298 queryItems.addQueryItem(QStringLiteral("size"), value: QString::number(request.limit()));
299
300 requestUrl.setQuery(queryItems);
301
302 networkReply = sendRequest(url: requestUrl);
303 break;
304 case QPlaceContent::EditorialType:
305 requestUrl.setPath(path: requestUrl.path() + QStringLiteral("editorials"));
306
307 queryItems.addQueryItem(QStringLiteral("tf"), QStringLiteral("html"));
308
309 if (request.limit() > 0)
310 queryItems.addQueryItem(QStringLiteral("size"), value: QString::number(request.limit()));
311
312 requestUrl.setQuery(queryItems);
313
314 networkReply = sendRequest(url: requestUrl);
315 break;
316 case QPlaceContent::NoType:
317 default:
318 ;
319 }
320 }
321
322 QPlaceContentReply *reply = new QPlaceContentReplyImpl(request, networkReply, this);
323 connect(sender: reply, SIGNAL(finished()), receiver: this, SLOT(replyFinished()));
324 connect(sender: reply, SIGNAL(error(QPlaceReply::Error,QString)),
325 receiver: this, SLOT(replyError(QPlaceReply::Error,QString)));
326
327 if (!networkReply) {
328 QMetaObject::invokeMethod(obj: reply, member: "setError", type: Qt::QueuedConnection,
329 Q_ARG(QPlaceReply::Error, QPlaceReply::UnsupportedError),
330 Q_ARG(QString, QString("Retrieval of given content type not supported.")));
331 }
332
333 return reply;
334}
335
336static bool addAtForBoundingArea(const QGeoShape &area,
337 QUrlQuery *queryItems)
338{
339 QGeoCoordinate center = area.center();
340 if (!center.isValid())
341 return false;
342
343 queryItems->addQueryItem(QStringLiteral("at"),
344 value: QString::number(center.latitude()) +
345 QLatin1Char(',') +
346 QString::number(center.longitude()));
347 return true;
348}
349
350QPlaceSearchReply *QPlaceManagerEngineNokiaV2::search(const QPlaceSearchRequest &query)
351{
352 bool unsupported = false;
353
354 unsupported |= query.visibilityScope() != QLocation::UnspecifiedVisibility &&
355 query.visibilityScope() != QLocation::PublicVisibility;
356
357 // Both a search term and search categories are not supported.
358 unsupported |= !query.searchTerm().isEmpty() && !query.categories().isEmpty();
359
360 //only a recommendation id by itself is supported.
361 unsupported |= !query.recommendationId().isEmpty()
362 && (!query.searchTerm().isEmpty() || !query.categories().isEmpty()
363 || query.searchArea().type() != QGeoShape::UnknownType);
364
365 if (unsupported) {
366 QPlaceSearchReplyHere *reply = new QPlaceSearchReplyHere(query, 0, this);
367 connect(sender: reply, SIGNAL(finished()), receiver: this, SLOT(replyFinished()));
368 connect(sender: reply, SIGNAL(error(QPlaceReply::Error,QString)),
369 receiver: this, SLOT(replyError(QPlaceReply::Error,QString)));
370 QMetaObject::invokeMethod(obj: reply, member: "setError", type: Qt::QueuedConnection,
371 Q_ARG(QPlaceReply::Error, QPlaceReply::BadArgumentError),
372 Q_ARG(QString, "Unsupported search request options specified."));
373 return reply;
374 }
375
376 QUrlQuery queryItems;
377
378 // Check that the search area is valid for all searches except recommendation and proposed
379 // searches, which do not need search centers.
380 if (query.recommendationId().isEmpty() && !query.searchContext().isValid()) {
381 if (!addAtForBoundingArea(area: query.searchArea(), queryItems: &queryItems)) {
382 QPlaceSearchReplyHere *reply = new QPlaceSearchReplyHere(query, 0, this);
383 connect(sender: reply, SIGNAL(finished()), receiver: this, SLOT(replyFinished()));
384 connect(sender: reply, SIGNAL(error(QPlaceReply::Error,QString)),
385 receiver: this, SLOT(replyError(QPlaceReply::Error,QString)));
386 QMetaObject::invokeMethod(obj: reply, member: "setError", type: Qt::QueuedConnection,
387 Q_ARG(QPlaceReply::Error, QPlaceReply::BadArgumentError),
388 Q_ARG(QString, "Invalid search area provided"));
389 return reply;
390 }
391 }
392
393 QNetworkReply *networkReply = 0;
394
395 if (query.searchContext().userType() == qMetaTypeId<QUrl>()) {
396 // provided search context
397 QUrl u = query.searchContext().value<QUrl>();
398
399 typedef QPair<QString, QString> QueryItem;
400 QList<QueryItem> queryItemList = queryItems.queryItems(encoding: QUrl::FullyEncoded);
401 queryItems = QUrlQuery(u);
402 foreach (const QueryItem &item, queryItemList)
403 queryItems.addQueryItem(key: item.first, value: item.second);
404
405 if (query.limit() > 0)
406 queryItems.addQueryItem(QStringLiteral("size"), value: QString::number(query.limit()));
407
408 u.setQuery(queryItems);
409
410 networkReply = sendRequest(url: u);
411 } else if (!query.searchTerm().isEmpty()) {
412 // search term query
413 QUrl requestUrl(QString::fromLatin1(str: "http://") + m_uriProvider->getCurrentHost() +
414 QStringLiteral("/places/v1/discover/search"));
415
416 queryItems.addQueryItem(QStringLiteral("q"), value: query.searchTerm());
417 queryItems.addQueryItem(QStringLiteral("tf"), QStringLiteral("html"));
418
419 if (query.limit() > 0) {
420 queryItems.addQueryItem(QStringLiteral("size"),
421 value: QString::number(query.limit()));
422 }
423
424 requestUrl.setQuery(queryItems);
425
426 QNetworkReply *networkReply = sendRequest(url: requestUrl);
427
428 QPlaceSearchReplyHere *reply = new QPlaceSearchReplyHere(query, networkReply, this);
429 connect(sender: reply, SIGNAL(finished()), receiver: this, SLOT(replyFinished()));
430 connect(sender: reply, SIGNAL(error(QPlaceReply::Error,QString)),
431 receiver: this, SLOT(replyError(QPlaceReply::Error,QString)));
432
433 return reply;
434 } else if (!query.recommendationId().isEmpty()) {
435 QUrl requestUrl(QString::fromLatin1(str: "http://") + m_uriProvider->getCurrentHost() +
436 QStringLiteral("/places/v1/places/") + query.recommendationId() +
437 QStringLiteral("/related/recommended"));
438
439 queryItems.addQueryItem(QStringLiteral("tf"), QStringLiteral("html"));
440
441 requestUrl.setQuery(queryItems);
442
443 networkReply = sendRequest(url: requestUrl);
444 } else {
445 // category search
446 QUrl requestUrl(QStringLiteral("http://") + m_uriProvider->getCurrentHost() +
447 QStringLiteral("/places/v1/discover/explore"));
448
449 QStringList ids;
450 foreach (const QPlaceCategory &category, query.categories())
451 ids.append(t: category.categoryId());
452
453 QUrlQuery queryItems;
454
455 if (!ids.isEmpty())
456 queryItems.addQueryItem(QStringLiteral("cat"), value: ids.join(QStringLiteral(",")));
457
458 addAtForBoundingArea(area: query.searchArea(), queryItems: &queryItems);
459
460 queryItems.addQueryItem(QStringLiteral("tf"), QStringLiteral("html"));
461
462 if (query.limit() > 0) {
463 queryItems.addQueryItem(QStringLiteral("size"),
464 value: QString::number(query.limit()));
465 }
466
467 requestUrl.setQuery(queryItems);
468
469 networkReply = sendRequest(url: requestUrl);
470 }
471
472 QPlaceSearchReplyHere *reply = new QPlaceSearchReplyHere(query, networkReply, this);
473 connect(sender: reply, SIGNAL(finished()), receiver: this, SLOT(replyFinished()));
474 connect(sender: reply, SIGNAL(error(QPlaceReply::Error,QString)),
475 receiver: this, SLOT(replyError(QPlaceReply::Error,QString)));
476
477 return reply;
478}
479
480QPlaceSearchSuggestionReply *QPlaceManagerEngineNokiaV2::searchSuggestions(const QPlaceSearchRequest &query)
481{
482 bool unsupported = false;
483
484 unsupported |= query.visibilityScope() != QLocation::UnspecifiedVisibility &&
485 query.visibilityScope() != QLocation::PublicVisibility;
486
487 unsupported |= !query.categories().isEmpty();
488 unsupported |= !query.recommendationId().isEmpty();
489
490 if (unsupported) {
491 QPlaceSearchSuggestionReplyImpl *reply = new QPlaceSearchSuggestionReplyImpl(0, this);
492 connect(sender: reply, SIGNAL(finished()), receiver: this, SLOT(replyFinished()));
493 connect(sender: reply, SIGNAL(error(QPlaceReply::Error,QString)),
494 receiver: this, SLOT(replyError(QPlaceReply::Error,QString)));
495 QMetaObject::invokeMethod(obj: reply, member: "setError", type: Qt::QueuedConnection,
496 Q_ARG(QPlaceReply::Error, QPlaceReply::BadArgumentError),
497 Q_ARG(QString, "Unsupported search request options specified."));
498 return reply;
499 }
500
501 QUrl requestUrl(QString::fromLatin1(str: "http://") + m_uriProvider->getCurrentHost() +
502 QStringLiteral("/places/v1/suggest"));
503
504 QUrlQuery queryItems;
505
506 queryItems.addQueryItem(QStringLiteral("q"), value: query.searchTerm());
507
508 if (!addAtForBoundingArea(area: query.searchArea(), queryItems: &queryItems)) {
509 QPlaceSearchSuggestionReplyImpl *reply = new QPlaceSearchSuggestionReplyImpl(0, this);
510 connect(sender: reply, SIGNAL(finished()), receiver: this, SLOT(replyFinished()));
511 connect(sender: reply, SIGNAL(error(QPlaceReply::Error,QString)),
512 receiver: this, SLOT(replyError(QPlaceReply::Error,QString)));
513 QMetaObject::invokeMethod(obj: reply, member: "setError", type: Qt::QueuedConnection,
514 Q_ARG(QPlaceReply::Error, QPlaceReply::BadArgumentError),
515 Q_ARG(QString, "Invalid search area provided"));
516 return reply;
517 }
518
519 requestUrl.setQuery(queryItems);
520
521 QNetworkReply *networkReply = sendRequest(url: requestUrl);
522
523 QPlaceSearchSuggestionReplyImpl *reply = new QPlaceSearchSuggestionReplyImpl(networkReply, this);
524 connect(sender: reply, SIGNAL(finished()), receiver: this, SLOT(replyFinished()));
525 connect(sender: reply, SIGNAL(error(QPlaceReply::Error,QString)),
526 receiver: this, SLOT(replyError(QPlaceReply::Error,QString)));
527
528 return reply;
529}
530
531QPlaceIdReply *QPlaceManagerEngineNokiaV2::savePlace(const QPlace &place)
532{
533 QPlaceIdReplyImpl *reply = new QPlaceIdReplyImpl(QPlaceIdReply::SavePlace, this);
534 reply->setId(place.placeId());
535 QMetaObject::invokeMethod(obj: reply, member: "setError", type: Qt::QueuedConnection,
536 Q_ARG(QPlaceReply::Error, QPlaceReply::UnsupportedError),
537 Q_ARG(QString, QCoreApplication::translate(NOKIA_PLUGIN_CONTEXT_NAME, SAVING_PLACE_NOT_SUPPORTED)));
538 connect(sender: reply, SIGNAL(finished()), receiver: this, SLOT(replyFinished()));
539 connect(sender: reply, SIGNAL(error(QPlaceReply::Error,QString)),
540 receiver: this, SLOT(replyError(QPlaceReply::Error,QString)));
541 return reply;
542}
543
544QPlaceIdReply *QPlaceManagerEngineNokiaV2::removePlace(const QString &placeId)
545{
546 QPlaceIdReplyImpl *reply = new QPlaceIdReplyImpl(QPlaceIdReply::RemovePlace, this);
547 reply->setId(placeId);
548 QMetaObject::invokeMethod(obj: reply, member: "setError", type: Qt::QueuedConnection,
549 Q_ARG(QPlaceReply::Error, QPlaceReply::UnsupportedError),
550 Q_ARG(QString, QCoreApplication::translate(NOKIA_PLUGIN_CONTEXT_NAME, REMOVING_PLACE_NOT_SUPPORTED)));
551 connect(sender: reply, SIGNAL(finished()), receiver: this, SLOT(replyFinished()));
552 connect(sender: reply, SIGNAL(error(QPlaceReply::Error,QString)),
553 receiver: this, SLOT(replyError(QPlaceReply::Error,QString)));
554 return reply;
555}
556
557QPlaceIdReply *QPlaceManagerEngineNokiaV2::saveCategory(const QPlaceCategory &category, const QString &parentId)
558{
559 Q_UNUSED(parentId);
560
561 QPlaceIdReplyImpl *reply = new QPlaceIdReplyImpl(QPlaceIdReply::SaveCategory, this);
562 reply->setId(category.categoryId());
563 QMetaObject::invokeMethod(obj: reply, member: "setError", type: Qt::QueuedConnection,
564 Q_ARG(QPlaceReply::Error, QPlaceReply::UnsupportedError),
565 Q_ARG(QString, QCoreApplication::translate(NOKIA_PLUGIN_CONTEXT_NAME, SAVING_CATEGORY_NOT_SUPPORTED)));
566 connect(sender: reply, SIGNAL(finished()), receiver: this, SLOT(replyFinished()));
567 connect(sender: reply, SIGNAL(error(QPlaceReply::Error,QString)),
568 receiver: this, SLOT(replyError(QPlaceReply::Error,QString)));
569 return reply;
570}
571
572QPlaceIdReply *QPlaceManagerEngineNokiaV2::removeCategory(const QString &categoryId)
573{
574 QPlaceIdReplyImpl *reply = new QPlaceIdReplyImpl(QPlaceIdReply::RemoveCategory, this);
575 reply->setId(categoryId);
576 QMetaObject::invokeMethod(obj: reply, member: "setError", type: Qt::QueuedConnection,
577 Q_ARG(QPlaceReply::Error, QPlaceReply::UnsupportedError),
578 Q_ARG(QString, QCoreApplication::translate(NOKIA_PLUGIN_CONTEXT_NAME, REMOVING_CATEGORY_NOT_SUPPORTED)));
579 connect(sender: reply, SIGNAL(finished()), receiver: this, SLOT(replyFinished()));
580 connect(sender: reply, SIGNAL(error(QPlaceReply::Error,QString)),
581 receiver: this, SLOT(replyError(QPlaceReply::Error,QString)));
582 return reply;
583}
584
585QPlaceReply *QPlaceManagerEngineNokiaV2::initializeCategories()
586{
587 if (m_categoryReply)
588 return m_categoryReply.data();
589
590 m_tempTree.clear();
591 CategoryParser parser;
592
593 if (parser.parse(fileName: m_localDataPath + QStringLiteral("/offline/offline-mapping.json"))) {
594 m_tempTree = parser.tree();
595 } else {
596 PlaceCategoryNode rootNode;
597
598 for (int i = 0; FIXED_CATEGORIES_indices[i] != -1; ++i) {
599 const QString id = QString::fromLatin1(str: FIXED_CATEGORIES_string +
600 FIXED_CATEGORIES_indices[i]);
601
602 int subCatDivider = id.indexOf(c: QChar('|'));
603 if (subCatDivider >= 0) {
604 // found a sub category
605 const QString subCategoryId = id.mid(position: subCatDivider+1);
606 const QString parentCategoryId = id.left(n: subCatDivider);
607
608 if (m_tempTree.contains(akey: parentCategoryId)) {
609 PlaceCategoryNode node;
610 node.category.setCategoryId(subCategoryId);
611 node.parentId = parentCategoryId;
612
613 // find parent
614 PlaceCategoryNode &parent = m_tempTree[parentCategoryId];
615 parent.childIds.append(t: subCategoryId);
616 m_tempTree.insert(akey: subCategoryId, avalue: node);
617 }
618
619 } else {
620 PlaceCategoryNode node;
621 node.category.setCategoryId(id);
622
623 m_tempTree.insert(akey: id, avalue: node);
624 rootNode.childIds.append(t: id);
625 }
626 }
627
628 m_tempTree.insert(akey: QString(), avalue: rootNode);
629 }
630
631 //request all categories in the tree from the server
632 //because we don't want the root node, we skip it
633 for (auto it = m_tempTree.keyBegin(), end = m_tempTree.keyEnd(); it != end; ++it) {
634 if (*it == QString())
635 continue;
636 QUrl requestUrl(QString::fromLatin1(str: "http://") + m_uriProvider->getCurrentHost() +
637 QStringLiteral("/places/v1/categories/places/") + *it);
638 QNetworkReply *networkReply = sendRequest(url: requestUrl);
639 connect(sender: networkReply, SIGNAL(finished()), receiver: this, SLOT(categoryReplyFinished()));
640 connect(sender: networkReply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)),
641 receiver: this, SLOT(categoryReplyError()));
642
643 m_categoryRequests.insert(akey: *it, avalue: networkReply);
644 }
645
646 QPlaceCategoriesReplyHere *reply = new QPlaceCategoriesReplyHere(this);
647 connect(sender: reply, SIGNAL(finished()), receiver: this, SLOT(replyFinished()));
648 connect(sender: reply, SIGNAL(error(QPlaceReply::Error,QString)),
649 receiver: this, SLOT(replyError(QPlaceReply::Error,QString)));
650
651 m_categoryReply = reply;
652 return reply;
653}
654
655QString QPlaceManagerEngineNokiaV2::parentCategoryId(const QString &categoryId) const
656{
657 return m_categoryTree.value(akey: categoryId).parentId;
658}
659
660QStringList QPlaceManagerEngineNokiaV2::childCategoryIds(const QString &categoryId) const
661{
662 return m_categoryTree.value(akey: categoryId).childIds;
663}
664
665QPlaceCategory QPlaceManagerEngineNokiaV2::category(const QString &categoryId) const
666{
667 return m_categoryTree.value(akey: categoryId).category;
668}
669
670QList<QPlaceCategory> QPlaceManagerEngineNokiaV2::childCategories(const QString &parentId) const
671{
672 QList<QPlaceCategory> results;
673 foreach (const QString &childId, m_categoryTree.value(parentId).childIds)
674 results.append(t: m_categoryTree.value(akey: childId).category);
675 return results;
676}
677
678QList<QLocale> QPlaceManagerEngineNokiaV2::locales() const
679{
680 return m_locales;
681}
682
683void QPlaceManagerEngineNokiaV2::setLocales(const QList<QLocale> &locales)
684{
685 m_locales = locales;
686}
687
688QPlaceIcon QPlaceManagerEngineNokiaV2::icon(const QString &remotePath,
689 const QList<QPlaceCategory> &categories) const
690{
691 QPlaceIcon icon;
692 QVariantMap params;
693
694 QRegularExpression rx("(.*)(/icons/categories/.*)");
695 QRegularExpressionMatch match = rx.match(subject: remotePath);
696
697 QString iconPrefix;
698 QString nokiaIcon;
699 if (match.hasMatch() && !match.capturedRef(nth: 1).isEmpty() && !match.capturedRef(nth: 2).isEmpty()) {
700 iconPrefix = match.captured(nth: 1);
701 nokiaIcon = match.captured(nth: 2);
702
703 if (QFile::exists(fileName: m_localDataPath + nokiaIcon))
704 iconPrefix = QString::fromLatin1(str: "file://") + m_localDataPath;
705
706 params.insert(akey: NokiaIcon, avalue: nokiaIcon);
707 params.insert(akey: IconPrefix, avalue: iconPrefix);
708
709 foreach (const QPlaceCategory &category, categories) {
710 if (category.icon().parameters().value(akey: NokiaIcon) == nokiaIcon) {
711 params.insert(akey: NokiaIconGenerated, avalue: true);
712 break;
713 }
714 }
715 } else {
716 QString path = remotePath + (!m_theme.isEmpty()
717 ? QLatin1Char('.') + m_theme : QString());
718 params.insert(akey: QPlaceIcon::SingleUrl, avalue: QUrl(path));
719
720 if (!nokiaIcon.isEmpty()) {
721 params.insert(akey: NokiaIcon, avalue: nokiaIcon);
722 params.insert(akey: IconPrefix, avalue: iconPrefix);
723 params.insert(akey: NokiaIconGenerated, avalue: true);
724 }
725 }
726
727 icon.setParameters(params);
728
729 if (!icon.isEmpty())
730 icon.setManager(manager());
731
732 return icon;
733}
734
735QUrl QPlaceManagerEngineNokiaV2::constructIconUrl(const QPlaceIcon &icon,
736 const QSize &size) const
737{
738 Q_UNUSED(size);
739 QVariantMap params = icon.parameters();
740 QString nokiaIcon = params.value(akey: NokiaIcon).toString();
741
742 if (!nokiaIcon.isEmpty()) {
743 nokiaIcon.append(s: !m_theme.isEmpty() ?
744 QLatin1Char('.') + m_theme : QString());
745
746 if (params.contains(akey: IconPrefix)) {
747 return QUrl(params.value(akey: IconPrefix).toString() +
748 nokiaIcon);
749 } else {
750 return QUrl(QString::fromLatin1(str: "file://") + m_localDataPath
751 + nokiaIcon);
752 }
753 }
754
755 return QUrl();
756}
757
758void QPlaceManagerEngineNokiaV2::replyFinished()
759{
760 QPlaceReply *reply = qobject_cast<QPlaceReply *>(object: sender());
761 if (reply)
762 emit finished(reply);
763}
764
765void QPlaceManagerEngineNokiaV2::replyError(QPlaceReply::Error error_, const QString &errorString)
766{
767 QPlaceReply *reply = qobject_cast<QPlaceReply *>(object: sender());
768 if (reply)
769 emit error(reply, error: error_, errorString);
770}
771
772void QPlaceManagerEngineNokiaV2::categoryReplyFinished()
773{
774 QNetworkReply *reply = qobject_cast<QNetworkReply *>(object: sender());
775 if (!reply)
776 return;
777
778 QString categoryId;
779
780 if (reply->error() == QNetworkReply::NoError) {
781 QJsonDocument document = QJsonDocument::fromJson(json: reply->readAll());
782 if (!document.isObject()) {
783 if (m_categoryReply) {
784 QMetaObject::invokeMethod(obj: m_categoryReply.data(), member: "setError", type: Qt::QueuedConnection,
785 Q_ARG(QPlaceReply::Error, QPlaceReply::ParseError),
786 Q_ARG(QString, QCoreApplication::translate(NOKIA_PLUGIN_CONTEXT_NAME, PARSE_ERROR)));
787 }
788 return;
789 }
790
791 QJsonObject category = document.object();
792
793 categoryId = category.value(QStringLiteral("categoryId")).toString();
794 if (m_tempTree.contains(akey: categoryId)) {
795 PlaceCategoryNode node = m_tempTree.value(akey: categoryId);
796 node.category.setName(category.value(QStringLiteral("name")).toString());
797 node.category.setCategoryId(categoryId);
798 node.category.setIcon(icon(remotePath: category.value(QStringLiteral("icon")).toString()));
799
800 m_tempTree.insert(akey: categoryId, avalue: node);
801 }
802 } else {
803 categoryId = m_categoryRequests.key(avalue: reply);
804 PlaceCategoryNode rootNode = m_tempTree.value(akey: QString());
805 rootNode.childIds.removeAll(t: categoryId);
806 m_tempTree.insert(akey: QString(), avalue: rootNode);
807 m_tempTree.remove(akey: categoryId);
808 }
809
810 m_categoryRequests.remove(akey: categoryId);
811 reply->deleteLater();
812
813 if (m_categoryRequests.isEmpty()) {
814 m_categoryTree = m_tempTree;
815 m_tempTree.clear();
816
817 if (m_categoryReply)
818 m_categoryReply.data()->emitFinished();
819 }
820}
821
822void QPlaceManagerEngineNokiaV2::categoryReplyError()
823{
824 if (m_categoryReply) {
825 QMetaObject::invokeMethod(obj: m_categoryReply.data(), member: "setError", type: Qt::QueuedConnection,
826 Q_ARG(QPlaceReply::Error, QPlaceReply::CommunicationError),
827 Q_ARG(QString, QCoreApplication::translate(NOKIA_PLUGIN_CONTEXT_NAME, NETWORK_ERROR)));
828 }
829}
830
831QNetworkReply *QPlaceManagerEngineNokiaV2::sendRequest(const QUrl &url)
832{
833 QUrlQuery queryItems(url);
834 queryItems.addQueryItem(QStringLiteral("app_id"), value: m_appId);
835 queryItems.addQueryItem(QStringLiteral("app_code"), value: m_appCode);
836
837 QUrl requestUrl = url;
838 requestUrl.setQuery(queryItems);
839
840 QNetworkRequest request;
841 request.setUrl(requestUrl);
842
843 request.setRawHeader(headerName: "Accept", value: "application/json");
844 request.setRawHeader(headerName: "Accept-Language", value: createLanguageString());
845
846 return m_manager->get(request);
847}
848
849QByteArray QPlaceManagerEngineNokiaV2::createLanguageString() const
850{
851 QByteArray language;
852
853 QList<QLocale> locales = m_locales;
854 if (locales.isEmpty())
855 locales << QLocale();
856
857 foreach (const QLocale &loc, locales) {
858 language.append(a: loc.name().replace(i: 2, len: 1, after: QLatin1Char('-')).toLatin1());
859 language.append(s: ", ");
860 }
861 language.chop(n: 2);
862
863 return language;
864}
865
866QT_END_NAMESPACE
867

source code of qtlocation/src/plugins/geoservices/nokia/qplacemanagerengine_nokiav2.cpp