Files
GoldenCheetah/src/ChartExchange.cpp
2015-12-13 18:23:49 +01:00

506 lines
15 KiB
C++

/*
* Copyright (c) 2015 Joern Rischmueller (joern.rm@gmail.com)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "Secrets.h"
#include "ChartExchange.h"
#include "LTMChartParser.h"
#include "GcUpgrade.h"
#include <QtGlobal>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QMessageBox>
#include <QJsonParseError>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QEventLoop>
#include <QXmlInputSource>
#include <QXmlSimpleReader>
ChartExchangeClient::ChartExchangeClient()
{
g_nam = new QNetworkAccessManager(this);
// general handling for sslErrors
connect(g_nam, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)), this,
SLOT(sslErrors(QNetworkReply*,QList<QSslError>)));
// check if SSL is available - if not - message and end
if (!QSslSocket::supportsSsl()) {
noSSLlib = true;
}
// common definitions used
g_chart_url_base = QString("https://%1.appspot.com").arg(GC_CLOUD_DB_APP_NAME);
g_chart_url_base.append("/v1/chart/");
g_header_content_type = QVariant("application/json");
g_header_basic_auth = "Basic ";
g_header_basic_auth.append(GC_CLOUD_DB_BASIC_AUTH);
}
ChartExchangeClient::~ChartExchangeClient() {
delete g_nam;
}
bool
ChartExchangeClient::postChart(ChartAPIv1 chart) {
// check if Athlete ID is filled
if (chart.CreatorId.isEmpty()) return false;
// first create the JSON object
// only a subset of fields is required for POST
QJsonObject json;
json.insert("name", QJsonValue(chart.Name));
json.insert("description", chart.Description);
json.insert("language", chart.Language);
json.insert("gcversion", chart.GcVersion);
json.insert("chartxml", chart.ChartXML);
QString image;
image.append(chart.Image.toBase64());
json.insert("image", image);
json.insert("creatorid", chart.CreatorId);
json.insert("creatornick", chart.CreatorNick);
json.insert("creatoremail", chart.CreatorEmail);
QJsonDocument document;
document.setObject(json);
QUrl url = QUrl(g_chart_url_base);
QNetworkRequest request;
request.setUrl(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, g_header_content_type);
request.setRawHeader("Authorization", g_header_basic_auth);
g_reply = g_nam->post(request, document.toJson());
// blocking request
QEventLoop loop;
connect(g_reply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
if (g_reply->error() != QNetworkReply::NoError) {
QVariant statusCode = g_reply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
if ( !statusCode.isValid() )
return false;
int httpstatus = statusCode.toInt();
switch (httpstatus) {
case 402:
// out of quota
break;
default:
break;
// out of quota
}
// TODO Add error Handling
};
return true;
}
ChartAPIv1
ChartExchangeClient::getChartByID(qint64 id) {
QString u = g_chart_url_base;
u.append(QString::number(id, 10));
QUrl url = QUrl(u);
QNetworkRequest request;
request.setUrl(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, g_header_content_type);
request.setRawHeader("Authorization", g_header_basic_auth);
g_reply = g_nam->get(request);
// blocking request
QEventLoop loop;
connect(g_reply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
if (g_reply->error() != QNetworkReply::NoError) {
// TODO Add error Handling
};
// result
QByteArray result = g_reply->readAll();
QList<ChartAPIv1>* charts = new QList<ChartAPIv1>;
unmarshallAPIv1(result, charts);
ChartAPIv1 chart;
if (charts->size() > 0) {
chart = charts->at(0);
}
return chart;
}
QList<ChartAPIv1>*
ChartExchangeClient::getChartsByDate(QDateTime changedAfter) {
QString u = g_chart_url_base;
QUrl url(u);
// TODO - changedAfter ?
QNetworkRequest request;
request.setUrl(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, g_header_content_type);
request.setRawHeader("Authorization", g_header_basic_auth);
g_reply = g_nam->get(request);
// blocking request
QEventLoop loop;
connect(g_reply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
if (g_reply->error() != QNetworkReply::NoError) {
// TODO Add error Handling
};
// result
QByteArray result = g_reply->readAll();
QList<ChartAPIv1>* charts = new QList<ChartAPIv1>;
unmarshallAPIv1(result, charts);
return charts;
}
//
// Trap SSL errors
//
void
ChartExchangeClient::sslErrors(QNetworkReply* reply ,QList<QSslError> errors)
{
QString errorString = "";
foreach (const QSslError e, errors ) {
if (!errorString.isEmpty())
errorString += ", ";
errorString += e.errorString();
}
QMessageBox::warning(NULL, tr("HTTP"), tr("SSL error(s) has occurred: %1").arg(errorString));
reply->ignoreSslErrors();
}
// Internal Methods
bool
ChartExchangeClient::unmarshallAPIv1(QByteArray json, QList<ChartAPIv1> *charts) {
QJsonParseError parseError;
QJsonDocument document = QJsonDocument::fromJson(json, &parseError);
// all these things should not happen and we have not valid object to return
if (parseError.error != QJsonParseError::NoError || document.isEmpty() || document.isNull()) {
return false;
}
// do we have a single object or an array ?
if (document.isObject()) {
ChartAPIv1 chart;
QJsonObject object = document.object();
unmarshallAPIv1Object(&object, &chart);
charts->append(chart);
} else if (document.isArray()) {
QJsonArray array(document.array());
for (int i = 0; i< array.size(); i++) {
QJsonValue value = array.at(i);
if (value.isObject()) {
ChartAPIv1 chart;
QJsonObject object = value.toObject();
unmarshallAPIv1Object(&object, &chart);
charts->append(chart);
}
}
}
return true;
}
void
ChartExchangeClient::unmarshallAPIv1Object(QJsonObject* object, ChartAPIv1* chart) {
chart->Id = object->value("id").toDouble();
chart->Name = object->value("name").toString();
chart->Description = object->value("description").toString();
chart->Language = object->value("language").toString();
chart->GcVersion = object->value("gcversion").toString();
chart->ChartXML = object->value("chartxml").toString();
chart->ChartVersion = object->value("chartversion").toInt();
QString imageString = object->value("image").toString();
QByteArray ba;
ba.append(imageString);
chart->Image = QByteArray::fromBase64(ba);
chart->LastChanged = QDateTime::fromString(object->value("lastChange").toString(), "yyyy-MM-ddTHH:mm:ssZ");
chart->CreatorId = object->value("creatorId").toString();
chart->CreatorNick = object->value("creatorNick").toString();
chart->CreatorEmail = object->value("creatorEmail").toString();
chart->Curated = object->value("curated").toBool();
}
//------------------------------------------------------------------------------------------------------------
// Dialog to show and retrieve Shared Charts
//------------------------------------------------------------------------------------------------------------
// define size if image preview at one place
static const int chartImageWidth = 320;
static const int chartImageHeight = 240;
ChartExchangeRetrieveDialog::ChartExchangeRetrieveDialog() {
client = new ChartExchangeClient();
allPresets = getAllSharedPresets();
setWindowTitle(tr("Select a Chart"));
setMinimumHeight(500);
setMinimumWidth(700);
tableWidget = new QTableWidget(0, 3, this);
tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
QPalette p = tableWidget->palette();
//TODO set colors for the selection as needed
tableWidget->setPalette(p);
tableWidget->setContentsMargins(0,0,0,0);
tableWidget->horizontalHeader()->setStretchLastSection(true);
tableWidget->setHorizontalHeaderLabels(QStringList() << tr("Preview") << tr("Name") << tr("Description"));
tableWidget->verticalHeader()->setVisible(false);
// the preview shall have a dedicated size
tableWidget->setColumnWidth(0, chartImageWidth);
getData();
selectButton = new QPushButton(tr("Selected"), this);
cancelButton = new QPushButton(tr("Cancel"), this);
selectButton->setEnabled(true);
cancelButton->setEnabled(true);
connect(selectButton, SIGNAL(clicked()), this, SLOT(selectedClicked()));
connect(cancelButton, SIGNAL(clicked()), this, SLOT(cancelClicked()));
connect(tableWidget, SIGNAL(itemDoubleClicked(QTableWidgetItem*)), this, SLOT(selectedClicked(QTableWidgetItem*)));
QHBoxLayout *buttonLayout = new QHBoxLayout;
buttonLayout->addWidget(cancelButton);
buttonLayout->addStretch();
buttonLayout->addWidget(selectButton);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(tableWidget);
mainLayout->addLayout(buttonLayout);
}
ChartExchangeRetrieveDialog::~ChartExchangeRetrieveDialog() {
delete client;
}
QList<ChartExchangePresets>*
ChartExchangeRetrieveDialog::getAllSharedPresets() {
QList<ChartExchangePresets>* presets = new QList<ChartExchangePresets>;
QList<ChartAPIv1>* allCharts = client->getChartsByDate(QDateTime::currentDateTime());
for (int i = 0; i<allCharts->length(); i++) {
ChartAPIv1 chart = allCharts->at(i);
ChartExchangePresets preset;
preset.name = chart.Name;
preset.description = chart.Description;
preset.image.loadFromData(chart.Image,"JPG");
QXmlInputSource source;
source.setData(chart.ChartXML);
QXmlSimpleReader xmlReader;
LTMChartParser handler;
xmlReader.setContentHandler(&handler);
xmlReader.setErrorHandler(&handler);
xmlReader.parse( source );
preset.ltmSettings = handler.getSettings().at(0); //only one LTMSettings Object is stored
presets->append(preset);
}
delete allCharts;
return presets;
}
void
ChartExchangeRetrieveDialog::getData()
{
// clean current
tableWidget->clearContents();
tableWidget->setRowCount(0);
for (int i = 0; i< allPresets->size(); i++ ) {
tableWidget->insertRow(i);
ChartExchangePresets preset = allPresets->at(i);
QTableWidgetItem *newPxItem = new QTableWidgetItem("");
newPxItem->setData(Qt::DecorationRole, QVariant(preset.image.scaled(chartImageWidth, chartImageHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation)));
newPxItem->setSizeHint(QSize(chartImageWidth, chartImageHeight));
tableWidget->setItem(i, 0, newPxItem);
tableWidget->setRowHeight(i, chartImageHeight);
QTableWidgetItem *newNameItem = new QTableWidgetItem(preset.name);
tableWidget->setItem(i, 1, newNameItem);
QTableWidgetItem *newDescriptionItem = new QTableWidgetItem(preset.description);
tableWidget->setItem(i, 2, newDescriptionItem);
}
}
void
ChartExchangeRetrieveDialog::selectedClicked() {
// check if an item of the table is selected
if (tableWidget->selectedItems().size()>0)
{
// the selectionMode allows only 1 item to be selected at a time
QTableWidgetItem* s = tableWidget->selectedItems().at(0);
if (s->row() >= 0 && s->row() <= allPresets->count()) {
selected = allPresets->at(s->row()).ltmSettings;
selected.name = selected.title = allPresets->at(s->row()).name;
accept();
}
}
}
void
ChartExchangeRetrieveDialog::cancelClicked() {
reject();
}
//------------------------------------------------------------------------------------------------------------
// Dialog for publishing Chart Details
//------------------------------------------------------------------------------------------------------------
ChartExchangePublishDialog::ChartExchangePublishDialog(ChartAPIv1 data) : data(data) {
QLabel *chartName = new QLabel(tr("Chart Name"));
name = new QLineEdit();
name->setText(tr("<Chart Name>"));
QLabel *nickLabel = new QLabel(tr("Nickname"));
nickName = new QLineEdit();
QLabel *emailLabel = new QLabel(tr("E-Mail"));
email = new QLineEdit();
QLabel* gcVersionLabel = new QLabel(tr("Version Details"));
QString versionString = VERSION_STRING;
versionString.append(" : " + data.GcVersion);
gcVersionString = new QLabel(versionString);
QLabel* creatorIdLabel = new QLabel(tr("Creator UUid"));
creatorId = new QLabel(data.CreatorId);
QGridLayout *detailsLayout = new QGridLayout;
detailsLayout->addWidget(chartName, 0, 0, Qt::AlignLeft);
detailsLayout->addWidget(name, 0, 1, 1, 3);
detailsLayout->addWidget(nickLabel, 1, 0, Qt::AlignLeft);
detailsLayout->addWidget(nickName, 1, 1);
detailsLayout->addWidget(emailLabel, 1, 2, Qt::AlignLeft);
detailsLayout->addWidget(email, 1, 3);
detailsLayout->addWidget(gcVersionLabel, 2, 0, Qt::AlignLeft);
detailsLayout->addWidget(gcVersionString, 2, 1);
detailsLayout->addWidget(creatorIdLabel, 2, 2, Qt::AlignLeft);
detailsLayout->addWidget(creatorId, 2, 3);
description = new QTextEdit();
description->setAcceptRichText(false);
description->setText(tr("<Enter the description of the chart here>"));
QPixmap *chartImage = new QPixmap();
chartImage->loadFromData(data.Image);
image = new QLabel();
image->setPixmap(chartImage->scaled(chartImageWidth*2, chartImageHeight*2, Qt::KeepAspectRatio, Qt::SmoothTransformation));
publishButton = new QPushButton(tr("Publish"), this);
cancelButton = new QPushButton(tr("Cancel"), this);
publishButton->setEnabled(true);
cancelButton->setEnabled(true);
connect(publishButton, SIGNAL(clicked()), this, SLOT(publishClicked()));
connect(cancelButton, SIGNAL(clicked()), this, SLOT(cancelClicked()));
QHBoxLayout *buttonLayout = new QHBoxLayout;
buttonLayout->addWidget(cancelButton);
buttonLayout->addStretch();
buttonLayout->addWidget(publishButton);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->addLayout(detailsLayout);
mainLayout->addWidget(description);
mainLayout->addWidget(image);
mainLayout->addLayout(buttonLayout);
}
ChartExchangePublishDialog::~ChartExchangePublishDialog() {
}
void
ChartExchangePublishDialog::publishClicked() {
data.Name = name->text();
data.Description = description->toPlainText();
data.CreatorEmail = email->text();
data.CreatorNick = nickName->text();
accept();
}
void
ChartExchangePublishDialog::cancelClicked() {
reject();
}