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 
64 QT_BEGIN_NAMESPACE
65 
66 static 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 
86 static 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 
92 static const char * const NokiaIcon = "nokiaIcon";
93 static const char * const IconPrefix = "iconPrefix";
94 static const char * const NokiaIconGenerated = "nokiaIconGenerated";
95 
96 static const char * const IconThemeKey = "places.icons.theme";
97 static const char * const LocalDataPathKey = "places.local_data_path";
98 
99 class CategoryParser
100 {
101 public:
102     CategoryParser();
103     bool parse(const QString &fileName);
104 
tree() const105     QPlaceCategoryTree tree() const { return m_tree; }
restIdToIconHash() const106     QHash<QString, QUrl> restIdToIconHash() const { return m_restIdToIconHash; }
107 
108     QString errorString() const;
109 
110 private:
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 
CategoryParser()121 CategoryParser::CategoryParser()
122 {
123 }
124 
parse(const QString & fileName)125 bool 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(QIODevice::ReadOnly)) {
134         QJsonDocument document = QJsonDocument::fromJson(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(0, 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("Unable to open ") + fileName;
155     return false;
156 }
157 
processCategory(int level,const QString & id,const QString & parentId)158 void 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(node.category.categoryId(), 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(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(childId)) {
188                 node.childIds.append(childId);
189                 processCategory(level + 1, childId, id);
190             }
191         }
192     }
193 
194     m_tree.insert(node.category.categoryId(), node);
195 }
196 
QPlaceManagerEngineNokiaV2(QGeoNetworkAccessManager * networkManager,const QVariantMap & parameters,QGeoServiceProvider::Error * error,QString * errorString)197 QPlaceManagerEngineNokiaV2::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(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(IconThemeKey, QString()).toString();
215 
216     if (m_theme == QStringLiteral("default"))
217         m_theme.clear();
218 
219     m_localDataPath = parameters.value(LocalDataPathKey, QString()).toString();
220     if (m_localDataPath.isEmpty()) {
221         QStringList dataLocations = QStandardPaths::standardLocations(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 
~QPlaceManagerEngineNokiaV2()236 QPlaceManagerEngineNokiaV2::~QPlaceManagerEngineNokiaV2() {}
237 
getPlaceDetails(const QString & placeId)238 QPlaceDetailsReply *QPlaceManagerEngineNokiaV2::getPlaceDetails(const QString &placeId)
239 {
240     QUrl requestUrl(QString::fromLatin1("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(requestUrl);
252 
253     QPlaceDetailsReplyImpl *reply = new QPlaceDetailsReplyImpl(networkReply, this);
254     reply->setPlaceId(placeId);
255     connect(reply, SIGNAL(finished()), this, SLOT(replyFinished()));
256     connect(reply, SIGNAL(error(QPlaceReply::Error,QString)),
257             this, SLOT(replyError(QPlaceReply::Error,QString)));
258 
259     return reply;
260 }
261 
getPlaceContent(const QPlaceContentRequest & request)262 QPlaceContentReply *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(u);
270     } else {
271         QUrl requestUrl(QString::fromLatin1("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(requestUrl.path() + QStringLiteral("images"));
280 
281             queryItems.addQueryItem(QStringLiteral("tf"), QStringLiteral("html"));
282 
283             if (request.limit() > 0)
284                 queryItems.addQueryItem(QStringLiteral("size"), 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(requestUrl);
291             break;
292         case QPlaceContent::ReviewType:
293             requestUrl.setPath(requestUrl.path() + QStringLiteral("reviews"));
294 
295             queryItems.addQueryItem(QStringLiteral("tf"), QStringLiteral("html"));
296 
297             if (request.limit() > 0)
298                 queryItems.addQueryItem(QStringLiteral("size"), QString::number(request.limit()));
299 
300             requestUrl.setQuery(queryItems);
301 
302             networkReply = sendRequest(requestUrl);
303             break;
304         case QPlaceContent::EditorialType:
305             requestUrl.setPath(requestUrl.path() + QStringLiteral("editorials"));
306 
307             queryItems.addQueryItem(QStringLiteral("tf"), QStringLiteral("html"));
308 
309             if (request.limit() > 0)
310                 queryItems.addQueryItem(QStringLiteral("size"), QString::number(request.limit()));
311 
312             requestUrl.setQuery(queryItems);
313 
314             networkReply = sendRequest(requestUrl);
315             break;
316         case QPlaceContent::NoType:
317         default:
318             ;
319         }
320     }
321 
322     QPlaceContentReply *reply = new QPlaceContentReplyImpl(request, networkReply, this);
323     connect(reply, SIGNAL(finished()), this, SLOT(replyFinished()));
324     connect(reply, SIGNAL(error(QPlaceReply::Error,QString)),
325             this, SLOT(replyError(QPlaceReply::Error,QString)));
326 
327     if (!networkReply) {
328         QMetaObject::invokeMethod(reply, "setError", 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 
addAtForBoundingArea(const QGeoShape & area,QUrlQuery * queryItems)336 static 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                              QString::number(center.latitude()) +
345                              QLatin1Char(',') +
346                              QString::number(center.longitude()));
347     return true;
348 }
349 
search(const QPlaceSearchRequest & query)350 QPlaceSearchReply *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(reply, SIGNAL(finished()), this, SLOT(replyFinished()));
368         connect(reply, SIGNAL(error(QPlaceReply::Error,QString)),
369                 this, SLOT(replyError(QPlaceReply::Error,QString)));
370         QMetaObject::invokeMethod(reply, "setError", 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(query.searchArea(), &queryItems)) {
382             QPlaceSearchReplyHere *reply = new QPlaceSearchReplyHere(query, 0, this);
383             connect(reply, SIGNAL(finished()), this, SLOT(replyFinished()));
384             connect(reply, SIGNAL(error(QPlaceReply::Error,QString)),
385                     this, SLOT(replyError(QPlaceReply::Error,QString)));
386             QMetaObject::invokeMethod(reply, "setError", 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(QUrl::FullyEncoded);
401         queryItems = QUrlQuery(u);
402         foreach (const QueryItem &item, queryItemList)
403             queryItems.addQueryItem(item.first, item.second);
404 
405         if (query.limit() > 0)
406             queryItems.addQueryItem(QStringLiteral("size"), QString::number(query.limit()));
407 
408         u.setQuery(queryItems);
409 
410         networkReply = sendRequest(u);
411     } else if (!query.searchTerm().isEmpty()) {
412         // search term query
413         QUrl requestUrl(QString::fromLatin1("http://") + m_uriProvider->getCurrentHost() +
414                         QStringLiteral("/places/v1/discover/search"));
415 
416         queryItems.addQueryItem(QStringLiteral("q"), query.searchTerm());
417         queryItems.addQueryItem(QStringLiteral("tf"), QStringLiteral("html"));
418 
419         if (query.limit() > 0) {
420             queryItems.addQueryItem(QStringLiteral("size"),
421                                     QString::number(query.limit()));
422         }
423 
424         requestUrl.setQuery(queryItems);
425 
426         QNetworkReply *networkReply = sendRequest(requestUrl);
427 
428         QPlaceSearchReplyHere *reply = new QPlaceSearchReplyHere(query, networkReply, this);
429         connect(reply, SIGNAL(finished()), this, SLOT(replyFinished()));
430         connect(reply, SIGNAL(error(QPlaceReply::Error,QString)),
431                 this, SLOT(replyError(QPlaceReply::Error,QString)));
432 
433         return reply;
434     } else if (!query.recommendationId().isEmpty()) {
435         QUrl requestUrl(QString::fromLatin1("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(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(category.categoryId());
452 
453         QUrlQuery queryItems;
454 
455         if (!ids.isEmpty())
456             queryItems.addQueryItem(QStringLiteral("cat"), ids.join(QStringLiteral(",")));
457 
458         addAtForBoundingArea(query.searchArea(), &queryItems);
459 
460         queryItems.addQueryItem(QStringLiteral("tf"), QStringLiteral("html"));
461 
462         if (query.limit() > 0) {
463             queryItems.addQueryItem(QStringLiteral("size"),
464                                     QString::number(query.limit()));
465         }
466 
467         requestUrl.setQuery(queryItems);
468 
469         networkReply = sendRequest(requestUrl);
470     }
471 
472     QPlaceSearchReplyHere *reply = new QPlaceSearchReplyHere(query, networkReply, this);
473     connect(reply, SIGNAL(finished()), this, SLOT(replyFinished()));
474     connect(reply, SIGNAL(error(QPlaceReply::Error,QString)),
475             this, SLOT(replyError(QPlaceReply::Error,QString)));
476 
477     return reply;
478 }
479 
searchSuggestions(const QPlaceSearchRequest & query)480 QPlaceSearchSuggestionReply *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(reply, SIGNAL(finished()), this, SLOT(replyFinished()));
493         connect(reply, SIGNAL(error(QPlaceReply::Error,QString)),
494                 this, SLOT(replyError(QPlaceReply::Error,QString)));
495         QMetaObject::invokeMethod(reply, "setError", 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("http://") + m_uriProvider->getCurrentHost() +
502                     QStringLiteral("/places/v1/suggest"));
503 
504     QUrlQuery queryItems;
505 
506     queryItems.addQueryItem(QStringLiteral("q"), query.searchTerm());
507 
508     if (!addAtForBoundingArea(query.searchArea(), &queryItems)) {
509         QPlaceSearchSuggestionReplyImpl *reply = new QPlaceSearchSuggestionReplyImpl(0, this);
510         connect(reply, SIGNAL(finished()), this, SLOT(replyFinished()));
511         connect(reply, SIGNAL(error(QPlaceReply::Error,QString)),
512                 this, SLOT(replyError(QPlaceReply::Error,QString)));
513         QMetaObject::invokeMethod(reply, "setError", 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(requestUrl);
522 
523     QPlaceSearchSuggestionReplyImpl *reply = new QPlaceSearchSuggestionReplyImpl(networkReply, this);
524     connect(reply, SIGNAL(finished()), this, SLOT(replyFinished()));
525     connect(reply, SIGNAL(error(QPlaceReply::Error,QString)),
526             this, SLOT(replyError(QPlaceReply::Error,QString)));
527 
528     return reply;
529 }
530 
savePlace(const QPlace & place)531 QPlaceIdReply *QPlaceManagerEngineNokiaV2::savePlace(const QPlace &place)
532 {
533     QPlaceIdReplyImpl *reply = new QPlaceIdReplyImpl(QPlaceIdReply::SavePlace, this);
534     reply->setId(place.placeId());
535     QMetaObject::invokeMethod(reply, "setError", 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(reply, SIGNAL(finished()), this, SLOT(replyFinished()));
539     connect(reply, SIGNAL(error(QPlaceReply::Error,QString)),
540             this, SLOT(replyError(QPlaceReply::Error,QString)));
541     return reply;
542 }
543 
removePlace(const QString & placeId)544 QPlaceIdReply *QPlaceManagerEngineNokiaV2::removePlace(const QString &placeId)
545 {
546     QPlaceIdReplyImpl *reply = new QPlaceIdReplyImpl(QPlaceIdReply::RemovePlace, this);
547     reply->setId(placeId);
548     QMetaObject::invokeMethod(reply, "setError", 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(reply, SIGNAL(finished()), this, SLOT(replyFinished()));
552     connect(reply, SIGNAL(error(QPlaceReply::Error,QString)),
553             this, SLOT(replyError(QPlaceReply::Error,QString)));
554     return reply;
555 }
556 
saveCategory(const QPlaceCategory & category,const QString & parentId)557 QPlaceIdReply *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(reply, "setError", 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(reply, SIGNAL(finished()), this, SLOT(replyFinished()));
567     connect(reply, SIGNAL(error(QPlaceReply::Error,QString)),
568             this, SLOT(replyError(QPlaceReply::Error,QString)));
569     return reply;
570 }
571 
removeCategory(const QString & categoryId)572 QPlaceIdReply *QPlaceManagerEngineNokiaV2::removeCategory(const QString &categoryId)
573 {
574     QPlaceIdReplyImpl *reply = new QPlaceIdReplyImpl(QPlaceIdReply::RemoveCategory, this);
575     reply->setId(categoryId);
576     QMetaObject::invokeMethod(reply, "setError", 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(reply, SIGNAL(finished()), this, SLOT(replyFinished()));
580     connect(reply, SIGNAL(error(QPlaceReply::Error,QString)),
581             this, SLOT(replyError(QPlaceReply::Error,QString)));
582     return reply;
583 }
584 
initializeCategories()585 QPlaceReply *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(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(FIXED_CATEGORIES_string +
600                                                    FIXED_CATEGORIES_indices[i]);
601 
602             int subCatDivider = id.indexOf(QChar('|'));
603             if (subCatDivider >= 0) {
604                 // found a sub category
605                 const QString subCategoryId = id.mid(subCatDivider+1);
606                 const QString parentCategoryId = id.left(subCatDivider);
607 
608                 if (m_tempTree.contains(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(subCategoryId);
616                     m_tempTree.insert(subCategoryId, node);
617                 }
618 
619             } else {
620                 PlaceCategoryNode node;
621                 node.category.setCategoryId(id);
622 
623                 m_tempTree.insert(id, node);
624                 rootNode.childIds.append(id);
625             }
626         }
627 
628         m_tempTree.insert(QString(), 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("http://") + m_uriProvider->getCurrentHost() +
637                         QStringLiteral("/places/v1/categories/places/") + *it);
638         QNetworkReply *networkReply = sendRequest(requestUrl);
639         connect(networkReply, SIGNAL(finished()), this, SLOT(categoryReplyFinished()));
640         connect(networkReply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)),
641                 this, SLOT(categoryReplyError()));
642 
643         m_categoryRequests.insert(*it, networkReply);
644     }
645 
646     QPlaceCategoriesReplyHere *reply = new QPlaceCategoriesReplyHere(this);
647     connect(reply, SIGNAL(finished()), this, SLOT(replyFinished()));
648     connect(reply, SIGNAL(error(QPlaceReply::Error,QString)),
649             this, SLOT(replyError(QPlaceReply::Error,QString)));
650 
651     m_categoryReply = reply;
652     return reply;
653 }
654 
parentCategoryId(const QString & categoryId) const655 QString QPlaceManagerEngineNokiaV2::parentCategoryId(const QString &categoryId) const
656 {
657     return m_categoryTree.value(categoryId).parentId;
658 }
659 
childCategoryIds(const QString & categoryId) const660 QStringList QPlaceManagerEngineNokiaV2::childCategoryIds(const QString &categoryId) const
661 {
662     return m_categoryTree.value(categoryId).childIds;
663 }
664 
category(const QString & categoryId) const665 QPlaceCategory QPlaceManagerEngineNokiaV2::category(const QString &categoryId) const
666 {
667     return m_categoryTree.value(categoryId).category;
668 }
669 
childCategories(const QString & parentId) const670 QList<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(m_categoryTree.value(childId).category);
675     return results;
676 }
677 
locales() const678 QList<QLocale> QPlaceManagerEngineNokiaV2::locales() const
679 {
680     return m_locales;
681 }
682 
setLocales(const QList<QLocale> & locales)683 void QPlaceManagerEngineNokiaV2::setLocales(const QList<QLocale> &locales)
684 {
685     m_locales = locales;
686 }
687 
icon(const QString & remotePath,const QList<QPlaceCategory> & categories) const688 QPlaceIcon 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(remotePath);
696 
697     QString iconPrefix;
698     QString nokiaIcon;
699     if (match.hasMatch() && !match.capturedRef(1).isEmpty() && !match.capturedRef(2).isEmpty()) {
700             iconPrefix = match.captured(1);
701             nokiaIcon = match.captured(2);
702 
703         if (QFile::exists(m_localDataPath + nokiaIcon))
704             iconPrefix = QString::fromLatin1("file://") + m_localDataPath;
705 
706         params.insert(NokiaIcon, nokiaIcon);
707         params.insert(IconPrefix, iconPrefix);
708 
709         foreach (const QPlaceCategory &category, categories) {
710             if (category.icon().parameters().value(NokiaIcon) == nokiaIcon) {
711                 params.insert(NokiaIconGenerated, true);
712                 break;
713             }
714         }
715     } else {
716         QString path = remotePath + (!m_theme.isEmpty()
717                                      ? QLatin1Char('.') + m_theme : QString());
718         params.insert(QPlaceIcon::SingleUrl, QUrl(path));
719 
720         if (!nokiaIcon.isEmpty()) {
721             params.insert(NokiaIcon, nokiaIcon);
722             params.insert(IconPrefix, iconPrefix);
723             params.insert(NokiaIconGenerated, true);
724         }
725     }
726 
727     icon.setParameters(params);
728 
729     if (!icon.isEmpty())
730         icon.setManager(manager());
731 
732     return icon;
733 }
734 
constructIconUrl(const QPlaceIcon & icon,const QSize & size) const735 QUrl 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(NokiaIcon).toString();
741 
742     if (!nokiaIcon.isEmpty()) {
743         nokiaIcon.append(!m_theme.isEmpty() ?
744                          QLatin1Char('.') + m_theme : QString());
745 
746         if (params.contains(IconPrefix)) {
747             return QUrl(params.value(IconPrefix).toString() +
748                         nokiaIcon);
749         } else {
750             return QUrl(QString::fromLatin1("file://") + m_localDataPath
751                                      + nokiaIcon);
752         }
753     }
754 
755     return QUrl();
756 }
757 
replyFinished()758 void QPlaceManagerEngineNokiaV2::replyFinished()
759 {
760     QPlaceReply *reply = qobject_cast<QPlaceReply *>(sender());
761     if (reply)
762         emit finished(reply);
763 }
764 
replyError(QPlaceReply::Error error_,const QString & errorString)765 void QPlaceManagerEngineNokiaV2::replyError(QPlaceReply::Error error_, const QString &errorString)
766 {
767     QPlaceReply *reply = qobject_cast<QPlaceReply *>(sender());
768     if (reply)
769         emit error(reply, error_, errorString);
770 }
771 
categoryReplyFinished()772 void QPlaceManagerEngineNokiaV2::categoryReplyFinished()
773 {
774     QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
775     if (!reply)
776         return;
777 
778     QString categoryId;
779 
780     if (reply->error() == QNetworkReply::NoError) {
781         QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
782         if (!document.isObject()) {
783             if (m_categoryReply) {
784                 QMetaObject::invokeMethod(m_categoryReply.data(), "setError", 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(categoryId)) {
795             PlaceCategoryNode node = m_tempTree.value(categoryId);
796             node.category.setName(category.value(QStringLiteral("name")).toString());
797             node.category.setCategoryId(categoryId);
798             node.category.setIcon(icon(category.value(QStringLiteral("icon")).toString()));
799 
800             m_tempTree.insert(categoryId, node);
801         }
802     } else {
803         categoryId = m_categoryRequests.key(reply);
804         PlaceCategoryNode rootNode = m_tempTree.value(QString());
805         rootNode.childIds.removeAll(categoryId);
806         m_tempTree.insert(QString(), rootNode);
807         m_tempTree.remove(categoryId);
808     }
809 
810     m_categoryRequests.remove(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 
categoryReplyError()822 void QPlaceManagerEngineNokiaV2::categoryReplyError()
823 {
824     if (m_categoryReply) {
825         QMetaObject::invokeMethod(m_categoryReply.data(), "setError", Qt::QueuedConnection,
826                                   Q_ARG(QPlaceReply::Error, QPlaceReply::CommunicationError),
827                                   Q_ARG(QString, QCoreApplication::translate(NOKIA_PLUGIN_CONTEXT_NAME, NETWORK_ERROR)));
828     }
829 }
830 
sendRequest(const QUrl & url)831 QNetworkReply *QPlaceManagerEngineNokiaV2::sendRequest(const QUrl &url)
832 {
833     QUrlQuery queryItems(url);
834     queryItems.addQueryItem(QStringLiteral("app_id"), m_appId);
835     queryItems.addQueryItem(QStringLiteral("app_code"), m_appCode);
836 
837     QUrl requestUrl = url;
838     requestUrl.setQuery(queryItems);
839 
840     QNetworkRequest request;
841     request.setUrl(requestUrl);
842 
843     request.setRawHeader("Accept", "application/json");
844     request.setRawHeader("Accept-Language", createLanguageString());
845 
846     return m_manager->get(request);
847 }
848 
createLanguageString() const849 QByteArray 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(loc.name().replace(2, 1, QLatin1Char('-')).toLatin1());
859         language.append(", ");
860     }
861     language.chop(2);
862 
863     return language;
864 }
865 
866 QT_END_NAMESPACE
867