summaryrefslogtreecommitdiff
path: root/rockwork/appstoreclient.cpp
diff options
context:
space:
mode:
authorAndrew Branson <andrew.branson@cern.ch>2016-02-11 23:55:16 +0100
committerAndrew Branson <andrew.branson@cern.ch>2016-02-11 23:55:16 +0100
commit29aaea2d80a9eb1715b6cddfac2d2aacf76358bd (patch)
tree012795b6bec16c72f38d33cff46324c9a0225868 /rockwork/appstoreclient.cpp
launchpad ~mzanetti/rockwork/trunk r87
Diffstat (limited to 'rockwork/appstoreclient.cpp')
-rw-r--r--rockwork/appstoreclient.cpp323
1 files changed, 323 insertions, 0 deletions
diff --git a/rockwork/appstoreclient.cpp b/rockwork/appstoreclient.cpp
new file mode 100644
index 0000000..ac87510
--- /dev/null
+++ b/rockwork/appstoreclient.cpp
@@ -0,0 +1,323 @@
+#include "appstoreclient.h"
+#include "applicationsmodel.h"
+
+#include <QNetworkAccessManager>
+#include <QNetworkRequest>
+#include <QNetworkReply>
+#include <QUrlQuery>
+#include <QJsonDocument>
+#include <QJsonParseError>
+
+#include <libintl.h>
+
+/* Known params for pebble api
+ query.addQueryItem("offset", QString::number(offset));
+ query.addQueryItem("limit", QString::number(limit));
+ query.addQueryItem("image_ratio", "1"); // Not sure yet what this does
+ query.addQueryItem("filter_hardware", "true");
+ query.addQueryItem("firmware_version", "3");
+ query.addQueryItem("hardware", hardwarePlatform);
+ query.addQueryItem("platform", "all");
+*/
+
+AppStoreClient::AppStoreClient(QObject *parent):
+ QObject(parent),
+ m_nam(new QNetworkAccessManager(this)),
+ m_model(new ApplicationsModel(this))
+{
+}
+
+ApplicationsModel *AppStoreClient::model() const
+{
+ return m_model;
+}
+
+int AppStoreClient::limit() const
+{
+ return m_limit;
+}
+
+void AppStoreClient::setLimit(int limit)
+{
+ m_limit = limit;
+ emit limitChanged();
+}
+
+QString AppStoreClient::hardwarePlatform() const
+{
+ return m_hardwarePlatform;
+}
+
+void AppStoreClient::setHardwarePlatform(const QString &hardwarePlatform)
+{
+ m_hardwarePlatform = hardwarePlatform;
+ emit hardwarePlatformChanged();
+}
+
+bool AppStoreClient::busy() const
+{
+ return m_busy;
+}
+
+void AppStoreClient::fetchHome(Type type)
+{
+ m_model->clear();
+ setBusy(true);
+
+ QUrlQuery query;
+ query.addQueryItem("firmware_version", "3");
+ if (!m_hardwarePlatform.isEmpty()) {
+ query.addQueryItem("hardware", m_hardwarePlatform);
+ query.addQueryItem("filter_hardware", "true");
+ }
+
+ QString url;
+ if (type == TypeWatchapp) {
+ url = "https://api2.getpebble.com/v2/home/apps";
+ } else {
+ url = "https://api2.getpebble.com/v2/home/watchfaces";
+ }
+ QUrl storeUrl(url);
+ storeUrl.setQuery(query);
+ QNetworkRequest request(storeUrl);
+
+ qDebug() << "fetching home" << storeUrl.toString();
+ QNetworkReply *reply = m_nam->get(request);
+ connect(reply, &QNetworkReply::finished, [this, reply]() {
+ QByteArray data = reply->readAll();
+ reply->deleteLater();
+
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(data);
+ QVariantMap resultMap = jsonDoc.toVariant().toMap();
+
+ QHash<QString, QStringList> collections;
+ foreach (const QVariant &entry, resultMap.value("collections").toList()) {
+ QStringList appIds;
+ foreach (const QVariant &appId, entry.toMap().value("application_ids").toList()) {
+ appIds << appId.toString();
+ }
+ QString slug = entry.toMap().value("slug").toString();
+ collections[slug] = appIds;
+ m_model->insertGroup(slug, entry.toMap().value("name").toString(), entry.toMap().value("links").toMap().value("apps").toString());
+ }
+
+ QHash<QString, QString> categoryNames;
+ foreach (const QVariant &entry, resultMap.value("categories").toList()) {
+ categoryNames[entry.toMap().value("id").toString()] = entry.toMap().value("name").toString();
+ }
+
+ foreach (const QVariant &entry, jsonDoc.toVariant().toMap().value("applications").toList()) {
+ AppItem* item = parseAppItem(entry.toMap());
+ foreach (const QString &collection, collections.keys()) {
+ if (collections.value(collection).contains(item->storeId())) {
+ item->setGroupId(collection);
+ break;
+ }
+ }
+ item->setCategory(categoryNames.value(entry.toMap().value("category_id").toString()));
+
+ qDebug() << "have entry" << item->name() << item->groupId() << item->companion();
+
+ if (item->groupId().isEmpty() || item->companion()) {
+ // Skip items that we couldn't match to a collection
+ // Also skip apps that need a companion
+ delete item;
+ continue;
+ }
+ m_model->insert(item);
+ }
+ setBusy(false);
+ });
+
+
+}
+
+void AppStoreClient::fetchLink(const QString &link)
+{
+ m_model->clear();
+ setBusy(true);
+
+ QUrl storeUrl(link);
+ QUrlQuery query(storeUrl);
+ query.removeQueryItem("limit");
+ // We fetch one more than we actually want so we can see if we need to display
+ // a next button
+ query.addQueryItem("limit", QString::number(m_limit + 1));
+ int currentOffset = query.queryItemValue("offset").toInt();
+ query.removeQueryItem("offset");
+ query.addQueryItem("offset", QString::number(qMax(0, currentOffset - 1)));
+ if (!query.hasQueryItem("hardware")) {
+ query.addQueryItem("hardware", m_hardwarePlatform);
+ query.addQueryItem("filter_hardware", "true");
+ }
+ storeUrl.setQuery(query);
+ QNetworkRequest request(storeUrl);
+ qDebug() << "fetching link" << request.url();
+
+ QNetworkReply *reply = m_nam->get(request);
+ connect(reply, &QNetworkReply::finished, [this, reply]() {
+ qDebug() << "fetch reply";
+ QByteArray data = reply->readAll();
+ reply->deleteLater();
+
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(data);
+ QVariantMap resultMap = jsonDoc.toVariant().toMap();
+
+ bool haveMore = false;
+ foreach (const QVariant &entry, resultMap.value("data").toList()) {
+ if (model()->rowCount() >= m_limit) {
+ haveMore = true;
+ break;
+ }
+ AppItem *item = parseAppItem(entry.toMap());
+ if (item->companion()) {
+ // For now just skip items with companions
+ delete item;
+ } else {
+ m_model->insert(item);
+ }
+ }
+
+ if (resultMap.contains("links") && resultMap.value("links").toMap().contains("nextPage") &&
+ !resultMap.value("links").toMap().value("nextPage").isNull()) {
+ int currentOffset = resultMap.value("offset").toInt();
+ QString nextLink = resultMap.value("links").toMap().value("nextPage").toString();
+
+ if (currentOffset > 0) {
+ QUrl previousLink(nextLink);
+ QUrlQuery query(previousLink);
+ query.removeQueryItem("limit");
+ query.addQueryItem("limit", QString::number(m_limit + 1));
+ query.removeQueryItem("offset");
+ query.addQueryItem("offset", QString::number(qMax(0, currentOffset - m_limit + 1)));
+ previousLink.setQuery(query);
+ m_model->addLink(previousLink.toString(), gettext("Previous"));
+ }
+ if (haveMore) {
+ m_model->addLink(nextLink, gettext("Next"));
+ }
+ }
+ setBusy(false);
+ });
+
+}
+
+void AppStoreClient::fetchAppDetails(const QString &appId)
+{
+ QUrl url("https://api2.getpebble.com/v2/apps/id/" + appId);
+ QUrlQuery query;
+ if (!m_hardwarePlatform.isEmpty()) {
+ query.addQueryItem("hardware", m_hardwarePlatform);
+ }
+ url.setQuery(query);
+
+ QNetworkRequest request(url);
+ QNetworkReply * reply = m_nam->get(request);
+ connect(reply, &QNetworkReply::finished, [this, reply, appId]() {
+ reply->deleteLater();
+ AppItem *item = m_model->findByStoreId(appId);
+ if (!item) {
+ qWarning() << "Can't find item with id" << appId;
+ return;
+ }
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll());
+ QVariantMap replyMap = jsonDoc.toVariant().toMap().value("data").toList().first().toMap();
+ if (replyMap.contains("header_images") && replyMap.value("header_images").toList().count() > 0) {
+ item->setHeaderImage(replyMap.value("header_images").toList().first().toMap().value("orig").toString());
+ }
+ item->setVendor(replyMap.value("author").toString());
+ item->setVersion(replyMap.value("latest_release").toMap().value("version").toString());
+ item->setIsWatchFace(replyMap.value("type").toString() == "watchface");
+ });
+}
+
+void AppStoreClient::search(const QString &searchString, Type type)
+{
+ m_model->clear();
+ setBusy(true);
+
+ QUrl url("https://bujatnzd81-dsn.algolia.io/1/indexes/pebble-appstore-production");
+ QUrlQuery query;
+ query.addQueryItem("x-algolia-api-key", "8dbb11cdde0f4f9d7bf787e83ac955ed");
+ query.addQueryItem("x-algolia-application-id", "BUJATNZD81");
+ query.addQueryItem("query", searchString);
+ QStringList filters;
+ if (type == TypeWatchapp) {
+ filters.append("watchapp");
+ } else if (type == TypeWatchface) {
+ filters.append("watchface");
+ }
+ filters.append(m_hardwarePlatform);
+ query.addQueryItem("tagFilters", filters.join(","));
+ url.setQuery(query);
+
+ QNetworkRequest request(url);
+ qDebug() << "Search query:" << url;
+ QNetworkReply *reply = m_nam->get(request);
+ connect(reply, &QNetworkReply::finished, [this, reply]() {
+ m_model->clear();
+ setBusy(false);
+
+ reply->deleteLater();
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll());
+
+ QVariantMap resultMap = jsonDoc.toVariant().toMap();
+ foreach (const QVariant &entry, resultMap.value("hits").toList()) {
+ AppItem *item = parseAppItem(entry.toMap());
+ m_model->insert(item);
+// qDebug() << "have item" << item->name() << item->icon();
+ }
+ qDebug() << "Found" << m_model->rowCount() << "items";
+ });
+}
+
+AppItem* AppStoreClient::parseAppItem(const QVariantMap &map)
+{
+ AppItem *item = new AppItem();
+ item->setStoreId(map.value("id").toString());
+ item->setName(map.value("title").toString());
+ if (!map.value("list_image").toString().isEmpty()) {
+ item->setIcon(map.value("list_image").toString());
+ } else {
+ item->setIcon(map.value("list_image").toMap().value("144x144").toString());
+ }
+ item->setDescription(map.value("description").toString());
+ item->setHearts(map.value("hearts").toInt());
+ item->setCategory(map.value("category_name").toString());
+ item->setCompanion(!map.value("companions").toMap().value("android").isNull() || !map.value("companions").toMap().value("ios").isNull());
+
+ QVariantList screenshotsList = map.value("screenshot_images").toList();
+ // try to get more hardware specific screenshots. The store search keeps them in a subgroup.
+ if (map.contains("asset_collections")) {
+ foreach (const QVariant &assetCollection, map.value("asset_collections").toList()) {
+ if (assetCollection.toMap().value("hardware_platform").toString() == m_hardwarePlatform) {
+ screenshotsList = assetCollection.toMap().value("screenshots").toList();
+ break;
+ }
+ }
+ }
+ QStringList screenshotImages;
+ foreach (const QVariant &screenshotItem, screenshotsList) {
+ if (!screenshotItem.toString().isEmpty()) {
+ screenshotImages << screenshotItem.toString();
+ } else if (screenshotItem.toMap().count() > 0) {
+ screenshotImages << screenshotItem.toMap().first().toString();
+ }
+ }
+ item->setScreenshotImages(screenshotImages);
+// qDebug() << "setting screenshot images" << item->screenshotImages();
+
+ // The search seems to return references to invalid icon images. if we detect that, we'll replace it with a screenshot
+ if (item->icon().contains("aOUhkV1R1uCqCVkKY5Dv") && !item->screenshotImages().isEmpty()) {
+ item->setIcon(item->screenshotImages().first());
+ }
+
+ return item;
+}
+
+void AppStoreClient::setBusy(bool busy)
+{
+ m_busy = busy;
+ emit busyChanged();
+}
+