mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 08:08:42 +00:00
1660 lines
55 KiB
C++
1660 lines
55 KiB
C++
/*
|
|
* Copyright (c) 2013 Damien.Grauser (damien.grauser@pev-geneve.ch)
|
|
*
|
|
* 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 "ShareDialog.h"
|
|
#include "Athlete.h"
|
|
#include "Settings.h"
|
|
#include <QUrl>
|
|
#include <QHttpMultiPart>
|
|
#include "mvjson.h"
|
|
#include "TimeUtils.h"
|
|
#include "Units.h"
|
|
#include "VeloHeroUploader.h"
|
|
#include "TrainingstagebuchUploader.h"
|
|
#include "SportPlusHealthUploader.h"
|
|
#include "HelpWhatsThis.h"
|
|
|
|
// access to metrics
|
|
#include "RideMetric.h"
|
|
#include "JsonRideFile.h"
|
|
#include "TcxRideFile.h"
|
|
|
|
#ifdef Q_CC_MSVC
|
|
#include <QtZlib/zlib.h>>
|
|
#else
|
|
#include <zlib.h>
|
|
#endif
|
|
|
|
#include <assert.h>
|
|
|
|
bool
|
|
ShareDialogUploader::canUpload( QString &err )
|
|
{
|
|
(void)err;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
ShareDialogUploader::wasUploaded()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// just ignore handshake errors
|
|
void
|
|
ShareDialogUploader::onSslErrors(QNetworkReply *reply, const QList<QSslError>&)
|
|
{
|
|
reply->ignoreSslErrors();
|
|
}
|
|
|
|
//
|
|
// Utility function to create a QByteArray of data in GZIP format
|
|
// This is essentially the same as qCompress but creates it in
|
|
// GZIP format (with recquisite headers) instead of ZLIB's format
|
|
// which has less filename info in the header
|
|
//
|
|
static QByteArray zCompress(const QByteArray &source)
|
|
{
|
|
// int size is source.size()
|
|
// const char *data is source.data()
|
|
z_stream strm;
|
|
|
|
strm.zalloc = Z_NULL;
|
|
strm.zfree = Z_NULL;
|
|
strm.opaque = Z_NULL;
|
|
|
|
// note that (15+16) below means windowbits+_16_ adds the gzip header/footer
|
|
deflateInit2(&strm, Z_BEST_COMPRESSION, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY);
|
|
|
|
// input data
|
|
strm.avail_in = source.size();
|
|
strm.next_in = (Bytef *)source.data();
|
|
|
|
// output data - on stack not heap, will be released
|
|
QByteArray dest(source.size()/2, '\0'); // should compress by 50%, if not don't bother
|
|
|
|
strm.avail_out = source.size()/2;
|
|
strm.next_out = (Bytef *)dest.data();
|
|
|
|
// now compress!
|
|
deflate(&strm, Z_FINISH);
|
|
|
|
// return byte array on the stack
|
|
return QByteArray(dest.data(), (source.size()/2) - strm.avail_out);
|
|
}
|
|
|
|
ShareDialog::ShareDialog(Context *context, RideItem *item) :
|
|
context(context)
|
|
{
|
|
setWindowTitle(tr("Share your activity"));
|
|
HelpWhatsThis *help = new HelpWhatsThis(this);
|
|
this->setWhatsThis(help->getWhatsThisText(HelpWhatsThis::MenuBar_Share_Online));
|
|
|
|
// make the dialog a resonable size
|
|
setMinimumWidth(550);
|
|
setMinimumHeight(400);
|
|
|
|
ride = item;
|
|
|
|
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
|
QGroupBox *groupBox1 = new QGroupBox(tr("Choose which sites you wish to share on: "));
|
|
|
|
QGridLayout *vbox1 = new QGridLayout();
|
|
unsigned int col = 0;
|
|
|
|
QString err;
|
|
|
|
stravaUploader = new StravaUploader(context, ride, this);
|
|
stravaChk = new QCheckBox(tr("Strava"));
|
|
if( ! stravaUploader->canUpload( err ) ){
|
|
stravaChk->setEnabled( false );
|
|
} else if( ! stravaUploader->wasUploaded() ){
|
|
stravaChk->setChecked( true );
|
|
}
|
|
vbox1->addWidget(stravaChk,0,col++);
|
|
|
|
rideWithGpsUploader = new RideWithGpsUploader(context, ride, this);
|
|
rideWithGPSChk = new QCheckBox(tr("Ride With GPS"));
|
|
if( ! rideWithGpsUploader->canUpload( err ) ){
|
|
rideWithGPSChk->setEnabled( false );
|
|
} else if( ! rideWithGpsUploader->wasUploaded() ){
|
|
rideWithGPSChk->setChecked( true );
|
|
}
|
|
vbox1->addWidget(rideWithGPSChk,0,col++);
|
|
|
|
todaysPlanUploader = new TodaysPlanUploader(context, ride, this);
|
|
todaysPlanChk = new QCheckBox(tr("Today's Plan"));
|
|
if( ! todaysPlanUploader->canUpload( err ) ){
|
|
todaysPlanChk->setEnabled( false );
|
|
} else if( ! todaysPlanUploader->wasUploaded() ){
|
|
todaysPlanChk->setChecked( true );
|
|
}
|
|
vbox1->addWidget(todaysPlanChk,0,col++);
|
|
|
|
cyclingAnalyticsUploader = new CyclingAnalyticsUploader(context, ride, this);
|
|
cyclingAnalyticsChk = new QCheckBox(tr("Cycling Analytics"));
|
|
if( ! cyclingAnalyticsUploader->canUpload( err ) ){
|
|
cyclingAnalyticsChk->setEnabled( false );
|
|
} else if( ! cyclingAnalyticsUploader->wasUploaded() ){
|
|
cyclingAnalyticsChk->setChecked( true );
|
|
}
|
|
vbox1->addWidget(cyclingAnalyticsChk,0,col++);
|
|
|
|
selfLoopsUploader = new SelfLoopsUploader(context, ride, this);
|
|
selfLoopsChk = new QCheckBox(tr("Selfloops"));
|
|
if( ! selfLoopsUploader->canUpload( err ) ){
|
|
selfLoopsChk->setEnabled( false );
|
|
} else if( ! selfLoopsUploader->wasUploaded() ){
|
|
selfLoopsChk->setChecked( true );
|
|
}
|
|
vbox1->addWidget(selfLoopsChk,0,col++);
|
|
|
|
veloHeroUploader = new VeloHeroUploader(context, ride, this);
|
|
veloHeroChk = new QCheckBox(tr("VeloHero"));
|
|
if( ! veloHeroUploader->canUpload( err ) ){
|
|
veloHeroChk->setEnabled( false );
|
|
} else if( ! veloHeroUploader->wasUploaded() ){
|
|
veloHeroChk->setChecked( true );
|
|
}
|
|
vbox1->addWidget(veloHeroChk,0,col++);
|
|
|
|
trainingstagebuchUploader = new TrainingstagebuchUploader(context, ride, this);
|
|
trainingstagebuchChk = new QCheckBox(tr("Trainingstagebuch.org"));
|
|
if( ! trainingstagebuchUploader->canUpload( err ) ){
|
|
trainingstagebuchChk->setEnabled( false );
|
|
} else if( ! trainingstagebuchUploader->wasUploaded() ){
|
|
trainingstagebuchChk->setChecked( true );
|
|
}
|
|
vbox1->addWidget(trainingstagebuchChk,0,col++);
|
|
|
|
sportplushealthUploader = new SportPlusHealthUploader(context, ride, this);
|
|
sportplushealthChk = new QCheckBox(tr("SportPlusHealth"));
|
|
if( ! sportplushealthUploader->canUpload( err ) ){
|
|
sportplushealthChk->setEnabled( false );
|
|
} else if( ! sportplushealthUploader->wasUploaded() ){
|
|
sportplushealthChk->setChecked( true );
|
|
}
|
|
vbox1->addWidget(sportplushealthChk,0,col++);
|
|
|
|
//garminUploader = new GarminUploader(context, ride, this); // not in 3.1
|
|
//garminChk = new QCheckBox(tr("Garmin Connect"));
|
|
//garminChk->setVisible(false);
|
|
//if( ! garminUploader->canUpload( err ) ){
|
|
// garminChk->setEnabled( false );
|
|
//}
|
|
//if( ! garminUploader->wasUploaded() ){
|
|
// garminChk->setChecked( true );
|
|
//}
|
|
//vbox1->addWidget(garminChk,0,col++);
|
|
|
|
|
|
groupBox1->setLayout(vbox1);
|
|
mainLayout->addWidget(groupBox1);
|
|
|
|
QGroupBox *groupBox2 = new QGroupBox(tr("Choose a name for your activity: "));
|
|
|
|
titleEdit = new QLineEdit();
|
|
|
|
// If we have a ride, and it can be opened then lets set the default for the title
|
|
// We try metadata fields; Title, then Name then Route then Workout Code
|
|
if (ride && ride->ride()) {
|
|
|
|
// is "Title" set?
|
|
if (!ride->ride()->getTag("Title", "").isEmpty()) {
|
|
titleEdit->setText(ride->ride()->getTag("Title", ""));
|
|
} else {
|
|
|
|
// is "Name" set?
|
|
if (!ride->ride()->getTag("Name", "").isEmpty()) {
|
|
titleEdit->setText(ride->ride()->getTag("Name", ""));
|
|
} else {
|
|
|
|
// is "Route" set?
|
|
if (!ride->ride()->getTag("Route", "").isEmpty()) {
|
|
titleEdit->setText(ride->ride()->getTag("Route", ""));
|
|
} else {
|
|
|
|
// is Workout Code set?
|
|
if (!ride->ride()->getTag("Workout Code", "").isEmpty()) {
|
|
titleEdit->setText(ride->ride()->getTag("Workout Code", ""));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
QGridLayout *vbox2 = new QGridLayout();
|
|
vbox2->addWidget(titleEdit,0,0);
|
|
|
|
groupBox2->setLayout(vbox2);
|
|
mainLayout->addWidget(groupBox2);
|
|
|
|
QGroupBox *groupBox3 = new QGroupBox(tr("Choose which data series you wish to send: "));
|
|
|
|
//gpsChk = new QCheckBox(tr("GPS"));
|
|
altitudeChk = new QCheckBox(tr("Altitude"));
|
|
powerChk = new QCheckBox(tr("Power"));
|
|
cadenceChk = new QCheckBox(tr("Cadence"));
|
|
heartrateChk = new QCheckBox(tr("Heartrate"));
|
|
privateChk = new QCheckBox(tr("Private"));
|
|
commuteChk = new QCheckBox(tr("Commute"));
|
|
trainerChk = new QCheckBox(tr("Trainer"));
|
|
|
|
if (ride) {
|
|
const RideFileDataPresent *dataPresent = ride->ride()->areDataPresent();
|
|
altitudeChk->setEnabled(dataPresent->alt);
|
|
altitudeChk->setChecked(dataPresent->alt);
|
|
powerChk->setEnabled(dataPresent->watts);
|
|
powerChk->setChecked(dataPresent->watts);
|
|
cadenceChk->setEnabled(dataPresent->cad);
|
|
cadenceChk->setChecked(dataPresent->cad);
|
|
heartrateChk->setEnabled(dataPresent->hr);
|
|
heartrateChk->setChecked(dataPresent->hr);
|
|
}
|
|
|
|
QGridLayout *vbox3 = new QGridLayout();
|
|
//vbox3->addWidget(gpsChk,0,0);
|
|
vbox3->addWidget(powerChk,0,0);
|
|
vbox3->addWidget(altitudeChk,0,1);
|
|
vbox3->addWidget(cadenceChk,1,0);
|
|
vbox3->addWidget(heartrateChk,1,1);
|
|
vbox3->addWidget(privateChk, 0, 2);
|
|
vbox3->addWidget(commuteChk, 1, 2);
|
|
vbox3->addWidget(trainerChk, 0, 3);
|
|
|
|
groupBox3->setLayout(vbox3);
|
|
mainLayout->addWidget(groupBox3);
|
|
|
|
|
|
// show progress
|
|
QVBoxLayout *progressLayout = new QVBoxLayout;
|
|
progressBar = new QProgressBar(this);
|
|
progressLabel = new QLabel("", this);
|
|
errorLabel = new QLabel("", this);
|
|
|
|
progressLayout->addWidget(progressBar);
|
|
progressLayout->addWidget(progressLabel);
|
|
progressLayout->addWidget(errorLabel);
|
|
|
|
QHBoxLayout *buttonLayout = new QHBoxLayout;
|
|
|
|
uploadButton = new QPushButton(tr("&Upload Activity"), this);
|
|
buttonLayout->addWidget(uploadButton);
|
|
closeButton = new QPushButton(tr("&Close"), this);
|
|
buttonLayout->addStretch();
|
|
buttonLayout->addWidget(closeButton);
|
|
|
|
mainLayout->addLayout(progressLayout);
|
|
mainLayout->addLayout(buttonLayout);
|
|
|
|
connect(uploadButton, SIGNAL(clicked()), this, SLOT(upload()));
|
|
connect(closeButton, SIGNAL(clicked()), this, SLOT(reject()));
|
|
}
|
|
|
|
|
|
void
|
|
ShareDialog::upload()
|
|
{
|
|
show();
|
|
|
|
if ( !rideWithGPSChk->isChecked() && !selfLoopsChk->isChecked()
|
|
&& !veloHeroChk->isChecked() && !trainingstagebuchChk->isChecked()
|
|
&& !stravaChk->isChecked() && !cyclingAnalyticsChk->isChecked()
|
|
&& !todaysPlanChk->isChecked()
|
|
&& !sportplushealthChk->isChecked() //&& !garminChk->isChecked()
|
|
) {
|
|
QMessageBox aMsgBox;
|
|
aMsgBox.setText(tr("No share site selected !"));
|
|
aMsgBox.exec();
|
|
return;
|
|
}
|
|
|
|
uploadButton->setEnabled(false);
|
|
|
|
shareSiteCount = 0;
|
|
progressBar->setValue(0);
|
|
progressLabel->setText("");
|
|
errorLabel->setText("");
|
|
|
|
if (stravaChk->isChecked()) {
|
|
shareSiteCount ++;
|
|
}
|
|
if (rideWithGPSChk->isChecked()) {
|
|
shareSiteCount ++;
|
|
}
|
|
if (todaysPlanChk->isChecked()) {
|
|
shareSiteCount ++;
|
|
}
|
|
if (cyclingAnalyticsChk->isChecked()) {
|
|
shareSiteCount ++;
|
|
}
|
|
if (selfLoopsChk->isChecked()) {
|
|
shareSiteCount ++;
|
|
}
|
|
if (veloHeroChk->isChecked()) {
|
|
shareSiteCount ++;
|
|
}
|
|
if (trainingstagebuchChk->isChecked()) {
|
|
shareSiteCount ++;
|
|
}
|
|
if (sportplushealthChk->isChecked()) {
|
|
shareSiteCount ++;
|
|
}
|
|
//if (garminChk->isChecked()) {
|
|
// shareSiteCount ++;
|
|
//}
|
|
|
|
if (stravaChk->isEnabled() && stravaChk->isChecked()) {
|
|
doUploader( stravaUploader );
|
|
}
|
|
if (rideWithGPSChk->isEnabled() && rideWithGPSChk->isChecked()) {
|
|
doUploader( rideWithGpsUploader );
|
|
}
|
|
if (todaysPlanChk->isEnabled() && todaysPlanChk->isChecked()) {
|
|
doUploader( todaysPlanUploader );
|
|
}
|
|
if (cyclingAnalyticsChk->isEnabled() && cyclingAnalyticsChk->isChecked()) {
|
|
doUploader( cyclingAnalyticsUploader );
|
|
}
|
|
if (selfLoopsChk->isEnabled() && selfLoopsChk->isChecked()) {
|
|
doUploader( selfLoopsUploader );
|
|
}
|
|
if (veloHeroChk->isEnabled() && veloHeroChk->isChecked()) {
|
|
doUploader( veloHeroUploader );
|
|
}
|
|
if (trainingstagebuchChk->isEnabled() && trainingstagebuchChk->isChecked()) {
|
|
doUploader( trainingstagebuchUploader );
|
|
}
|
|
if (sportplushealthChk->isEnabled() && sportplushealthChk->isChecked()) {
|
|
sportplushealthUploader->insertedName = QString(titleEdit->text()).toLatin1();
|
|
doUploader( sportplushealthUploader );
|
|
}
|
|
//if (garminChk->isEnabled() && garminChk->isChecked()) {
|
|
// doUploader( garminUploader );
|
|
//}
|
|
|
|
uploadButton->setEnabled(true);
|
|
}
|
|
|
|
void
|
|
ShareDialog::okClicked()
|
|
{
|
|
dialog->accept();
|
|
return;
|
|
}
|
|
|
|
void
|
|
ShareDialog::closeClicked()
|
|
{
|
|
dialog->reject();
|
|
return;
|
|
}
|
|
|
|
|
|
void
|
|
ShareDialog::doUploader( ShareDialogUploader *uploader )
|
|
{
|
|
assert(uploader);
|
|
|
|
if( uploader->wasUploaded() ){
|
|
dialog = new QDialog();
|
|
QVBoxLayout *layout = new QVBoxLayout;
|
|
|
|
QVBoxLayout *layoutLabel = new QVBoxLayout();
|
|
QLabel *label = new QLabel();
|
|
label->setText(tr("This activity is marked as already on %1. Are you sure you want to upload it?")
|
|
.arg(uploader->name()) );
|
|
layoutLabel->addWidget(label);
|
|
|
|
QPushButton *ok = new QPushButton(tr("OK"), dialog);
|
|
QPushButton *cancel = new QPushButton(tr("Cancel"), dialog);
|
|
QHBoxLayout *buttons = new QHBoxLayout();
|
|
buttons->addStretch();
|
|
buttons->addWidget(cancel);
|
|
buttons->addWidget(ok);
|
|
|
|
connect(ok, SIGNAL(clicked()), this, SLOT(okClicked()));
|
|
connect(cancel, SIGNAL(clicked()), this, SLOT(closeClicked()));
|
|
|
|
layout->addLayout(layoutLabel);
|
|
layout->addLayout(buttons);
|
|
|
|
dialog->setLayout(layout);
|
|
|
|
if (!dialog->exec()) return;
|
|
}
|
|
|
|
uploader->upload();
|
|
}
|
|
|
|
//
|
|
// Uploader
|
|
//
|
|
|
|
// Strava
|
|
// ----------------
|
|
|
|
StravaUploader::StravaUploader(Context *context, RideItem *ride, ShareDialog *parent) :
|
|
ShareDialogUploader( tr("Strava"), context, ride, parent)
|
|
{
|
|
stravaUploadId = ride->ride()->getTag("Strava uploadId", "0").toInt();
|
|
eventLoop = new QEventLoop(this);
|
|
networkManager = new QNetworkAccessManager(this);
|
|
}
|
|
|
|
bool
|
|
StravaUploader::canUpload( QString &err )
|
|
{
|
|
#ifdef GC_STRAVA_CLIENT_SECRET
|
|
token = appsettings->cvalue(context->athlete->cyclist, GC_STRAVA_TOKEN, "").toString();
|
|
if( token!="" )
|
|
return true;
|
|
|
|
err = tr("no Strava token set. Please authorize in Settings.");
|
|
#else
|
|
err = tr("Strava support isn't enabled in this build");
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
StravaUploader::wasUploaded()
|
|
{
|
|
return stravaUploadId>0;
|
|
}
|
|
|
|
void
|
|
StravaUploader::upload()
|
|
{
|
|
// OAuth no more login
|
|
token = appsettings->cvalue(context->athlete->cyclist, GC_STRAVA_TOKEN, "").toString();
|
|
if (token=="")
|
|
return;
|
|
|
|
requestUploadStrava();
|
|
|
|
if(!uploadSuccessful)
|
|
{
|
|
parent->progressLabel->setText(tr("Error uploading to Strava"));
|
|
}
|
|
else
|
|
{
|
|
//requestVerifyUpload();
|
|
parent->progressLabel->setText(tr("Successfully uploaded to Strava\n"));
|
|
}
|
|
}
|
|
|
|
// Documentation is at:
|
|
// https://strava.pbworks.com/w/page/39241255/v2%20upload%20create
|
|
void
|
|
StravaUploader::requestUploadStrava()
|
|
{
|
|
parent->progressLabel->setText(tr("Upload activity to Strava..."));
|
|
parent->progressBar->setValue(
|
|
parent->progressBar->value() + 10 / parent->shareSiteCount);
|
|
|
|
int year = ride->fileName.left(4).toInt();
|
|
int month = ride->fileName.mid(5,2).toInt();
|
|
int day = ride->fileName.mid(8,2).toInt();
|
|
int hour = ride->fileName.mid(11,2).toInt();
|
|
int minute = ride->fileName.mid(14,2).toInt();;
|
|
int second = ride->fileName.mid(17,2).toInt();;
|
|
|
|
QDate rideDate = QDate(year, month, day);
|
|
QTime rideTime = QTime(hour, minute, second);
|
|
QDateTime rideDateTime = QDateTime(rideDate, rideTime);
|
|
|
|
// trap network response from access manager
|
|
networkManager->disconnect();
|
|
connect(networkManager, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> & )), this,
|
|
SLOT(onSslErrors(QNetworkReply*, const QList<QSslError> & )));
|
|
connect(networkManager, SIGNAL(finished(QNetworkReply*)), this,
|
|
SLOT(requestUploadStravaFinished(QNetworkReply*)));
|
|
connect(networkManager, SIGNAL(finished(QNetworkReply *)), eventLoop,
|
|
SLOT(quit()));
|
|
|
|
TcxFileReader reader;
|
|
|
|
QUrl url = QUrl( "https://www.strava.com/api/v3/uploads" ); // The V3 API doc said "https://api.strava.com" but it is not working yet
|
|
QNetworkRequest request = QNetworkRequest(url);
|
|
|
|
//QString boundary = QString::number(qrand() * (90000000000) / (RAND_MAX + 1) + 10000000000, 16);
|
|
QString boundary = QVariant(qrand()).toString() +
|
|
QVariant(qrand()).toString() + QVariant(qrand()).toString();
|
|
|
|
QByteArray file = zCompress(reader.toByteArray(
|
|
context, ride->ride(),
|
|
parent->altitudeChk->isChecked(),
|
|
parent->powerChk->isChecked(),
|
|
parent->heartrateChk->isChecked(),
|
|
parent->cadenceChk->isChecked()));
|
|
|
|
// MULTIPART *****************
|
|
|
|
QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
|
multiPart->setBoundary(boundary.toLatin1());
|
|
|
|
QHttpPart accessTokenPart;
|
|
accessTokenPart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
|
QVariant("form-data; name=\"access_token\""));
|
|
accessTokenPart.setBody(token.toLatin1());
|
|
|
|
QHttpPart activityTypePart;
|
|
activityTypePart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
|
QVariant("form-data; name=\"activity_type\""));
|
|
if (ride->isRun)
|
|
activityTypePart.setBody("run");
|
|
else if (ride->isSwim)
|
|
activityTypePart.setBody("swim");
|
|
else
|
|
activityTypePart.setBody("ride");
|
|
|
|
QHttpPart activityNamePart;
|
|
activityNamePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"activity_name\""));
|
|
activityNamePart.setBody(QString(parent->titleEdit->text()).toLatin1());
|
|
|
|
QHttpPart dataTypePart;
|
|
dataTypePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"data_type\""));
|
|
dataTypePart.setBody("tcx.gz");
|
|
|
|
QHttpPart externalIdPart;
|
|
externalIdPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"external_id\""));
|
|
externalIdPart.setBody("Ride");
|
|
|
|
QHttpPart privatePart;
|
|
privatePart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
|
QVariant("form-data; name=\"private\""));
|
|
privatePart.setBody(parent->privateChk->isChecked() ? "1" : "0");
|
|
|
|
QHttpPart commutePart;
|
|
commutePart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
|
QVariant("form-data; name=\"commute\""));
|
|
commutePart.setBody(parent->commuteChk->isChecked() ? "1" : "0");
|
|
QHttpPart trainerPart;
|
|
trainerPart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
|
QVariant("form-data; name=\"trainer\""));
|
|
trainerPart.setBody(parent->trainerChk->isChecked() ? "1" : "0");
|
|
|
|
QHttpPart filePart;
|
|
filePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/xml"));
|
|
filePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"file\"; filename=\"file.tcx.gz\"; type=\"text/xml\""));
|
|
filePart.setBody(file);
|
|
|
|
multiPart->append(accessTokenPart);
|
|
multiPart->append(activityTypePart);
|
|
multiPart->append(activityNamePart);
|
|
multiPart->append(dataTypePart);
|
|
multiPart->append(externalIdPart);
|
|
multiPart->append(privatePart);
|
|
multiPart->append(commutePart);
|
|
multiPart->append(trainerPart);
|
|
multiPart->append(filePart);
|
|
networkManager->post(request, multiPart);
|
|
|
|
parent->progressBar->setValue(parent->progressBar->value()+30/parent->shareSiteCount);
|
|
parent->progressLabel->setText(tr("Upload... Sending to Strava"));
|
|
|
|
eventLoop->exec();
|
|
}
|
|
|
|
void
|
|
StravaUploader::requestUploadStravaFinished(QNetworkReply *reply)
|
|
{
|
|
parent->progressBar->setValue(parent->progressBar->value()+50/parent->shareSiteCount);
|
|
parent->progressLabel->setText(tr("Upload to Strava finished."));
|
|
|
|
uploadSuccessful = false;
|
|
|
|
QString response = reply->readLine();
|
|
//qDebug() << response;
|
|
|
|
// use a lightweight json parser to do this
|
|
QString uploadError="invalid response or parser error";
|
|
try {
|
|
|
|
// parse !
|
|
MVJSONReader jsonResponse(string(response.toLatin1()));
|
|
|
|
// get error field
|
|
if (jsonResponse.root) {
|
|
if (jsonResponse.root->hasField("error")) {
|
|
uploadError = jsonResponse.root->getFieldString("error").c_str();
|
|
} else {
|
|
uploadError = ""; // no error
|
|
}
|
|
|
|
// get upload_id, but if not available use id
|
|
if (jsonResponse.root->hasField("upload_id")) {
|
|
stravaUploadId = jsonResponse.root->getFieldInt("upload_id");
|
|
} else if (jsonResponse.root->hasField("id")) {
|
|
stravaUploadId = jsonResponse.root->getFieldInt("id");
|
|
} else {
|
|
stravaUploadId = 0;
|
|
}
|
|
} else {
|
|
uploadError = "no connection";
|
|
}
|
|
} catch(...) { // not really sure what exceptions to expect so do them all (bad, sorry)
|
|
uploadError=tr("invalid response or parser exception.");
|
|
}
|
|
|
|
if (uploadError.toLower() == "none" || uploadError.toLower() == "null")
|
|
uploadError = "";
|
|
|
|
if (uploadError.length()>0 || reply->error() != QNetworkReply::NoError)
|
|
{
|
|
//qDebug() << "Error " << reply->error() ;
|
|
//qDebug() << "Error " << uploadError;
|
|
parent->errorLabel->setText(parent->errorLabel->text()+ tr(" Error from Strava: ") + uploadError + "\n" );
|
|
}
|
|
else
|
|
{
|
|
|
|
ride->ride()->setTag("Strava uploadId", QString("%1").arg(stravaUploadId));
|
|
ride->setDirty(true);
|
|
|
|
//qDebug() << "uploadId: " << stravaUploadId;
|
|
|
|
parent->progressBar->setValue(parent->progressBar->value()+10/parent->shareSiteCount);
|
|
uploadSuccessful = true;
|
|
}
|
|
}
|
|
|
|
void
|
|
StravaUploader::requestVerifyUpload()
|
|
{
|
|
parent->progressBar->setValue(0);
|
|
parent->progressLabel->setText(tr("Processing..."));
|
|
|
|
// reconnect for verify
|
|
networkManager->disconnect();
|
|
connect(networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestVerifyUploadFinished(QNetworkReply*)));
|
|
connect(networkManager, SIGNAL(finished(QNetworkReply *)), eventLoop, SLOT(quit()));
|
|
QByteArray out;
|
|
|
|
QUrl url = QUrl("https://www.strava.com/api/v3/upload/status/"+QString("%1").arg(stravaUploadId)+"?token="+token);
|
|
QNetworkRequest request = QNetworkRequest(url);
|
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
|
|
networkManager->post(request, out);
|
|
eventLoop->exec();
|
|
}
|
|
|
|
void
|
|
StravaUploader::requestVerifyUploadFinished(QNetworkReply *reply)
|
|
{
|
|
uploadSuccessful = false;
|
|
if (reply->error() != QNetworkReply::NoError)
|
|
qDebug() << "Error from upload " <<reply->error();
|
|
else {
|
|
|
|
try {
|
|
|
|
// parse the response
|
|
QString response = reply->readLine();
|
|
MVJSONReader jsonResponse(string(response.toLatin1()));
|
|
|
|
// get values
|
|
uploadProgress = jsonResponse.root->getFieldInt("upload_progress");
|
|
uploadStatus = jsonResponse.root->getFieldString("upload_status").c_str();
|
|
stravaActivityId = jsonResponse.root->getFieldInt("activity_id");
|
|
|
|
} catch(...) {
|
|
|
|
// problem!
|
|
uploadProgress = 0;
|
|
uploadStatus = "response error or parser exception";
|
|
stravaActivityId = 0;
|
|
}
|
|
|
|
//qDebug() << "upload_progress: " << uploadProgress;
|
|
//qDebug() << "upload_status: " << uploadStatus;
|
|
parent->progressBar->setValue(uploadProgress);
|
|
parent->progressLabel->setText(uploadStatus);
|
|
|
|
// not done yet
|
|
if (stravaActivityId == 0 || uploadProgress < 97) {
|
|
requestVerifyUpload();
|
|
return;
|
|
}
|
|
|
|
// success
|
|
ride->ride()->setTag("Strava activityId", QString("%1").arg(stravaActivityId));
|
|
ride->setDirty(true);
|
|
uploadSuccessful = true;
|
|
}
|
|
}
|
|
|
|
// RideWithGps
|
|
// ----------------
|
|
|
|
RideWithGpsUploader::RideWithGpsUploader(Context *context, RideItem *ride, ShareDialog *parent) :
|
|
ShareDialogUploader( tr("Ride With GPS"), context, ride, parent)
|
|
{
|
|
rideWithGpsActivityId = ride->ride()->getTag("RideWithGPS tripid", "");
|
|
}
|
|
|
|
bool
|
|
RideWithGpsUploader::canUpload( QString &err )
|
|
{
|
|
QString username = appsettings->cvalue(context->athlete->cyclist, GC_RWGPSUSER).toString();
|
|
if( username.length() > 0 )
|
|
return true;
|
|
|
|
err = tr("no credentials set for RideWithGps. Please check Settings.");
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
RideWithGpsUploader::wasUploaded()
|
|
{
|
|
return rideWithGpsActivityId.length()>0;
|
|
}
|
|
|
|
void
|
|
RideWithGpsUploader::upload()
|
|
{
|
|
requestUploadRideWithGPS();
|
|
|
|
if(!uploadSuccessful)
|
|
{
|
|
parent->progressLabel->setText(tr("Error uploading to RideWithGPS")+"\n");
|
|
}
|
|
else
|
|
{
|
|
parent->progressLabel->setText(tr("Successfully uploaded to RideWithGPS"));
|
|
}
|
|
}
|
|
|
|
void
|
|
RideWithGpsUploader::requestUploadRideWithGPS()
|
|
{
|
|
parent->progressLabel->setText(tr("Upload..."));
|
|
parent->progressBar->setValue(parent->progressBar->value()+10/parent->shareSiteCount);
|
|
|
|
QEventLoop eventLoop;
|
|
QNetworkAccessManager networkMgr;
|
|
|
|
int prevSecs = 0;
|
|
long diffSecs = 0;
|
|
|
|
int year = ride->fileName.left(4).toInt();
|
|
int month = ride->fileName.mid(5,2).toInt();
|
|
int day = ride->fileName.mid(8,2).toInt();
|
|
int hour = ride->fileName.mid(11,2).toInt();
|
|
int minute = ride->fileName.mid(14,2).toInt();;
|
|
int second = ride->fileName.mid(17,2).toInt();;
|
|
|
|
QDate rideDate = QDate(year, month, day);
|
|
QTime rideTime = QTime(hour, minute, second);
|
|
QDateTime rideDateTime = QDateTime(rideDate, rideTime);
|
|
|
|
connect(&networkMgr, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> & )), this, SLOT(onSslErrors(QNetworkReply*, const QList<QSslError> & )));
|
|
connect(&networkMgr, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestUploadRideWithGPSFinished(QNetworkReply*)));
|
|
connect(&networkMgr, SIGNAL(finished(QNetworkReply *)), &eventLoop, SLOT(quit()));
|
|
QString out, data;
|
|
|
|
QVector<RideFilePoint*> vectorPoints = ride->ride()->dataPoints();
|
|
int totalSize = vectorPoints.size();
|
|
|
|
int size = 0;
|
|
|
|
QString username = appsettings->cvalue(context->athlete->cyclist, GC_RWGPSUSER).toString();
|
|
QString password = appsettings->cvalue(context->athlete->cyclist, GC_RWGPSPASS).toString();
|
|
|
|
// application/json
|
|
out += "{\"apikey\": \"p24n3a9e\", ";
|
|
out += "\"email\": \""+username+"\", ";
|
|
out += "\"password\": \""+password+"\", ";
|
|
out += "\"track_points\": \"";
|
|
|
|
data += "\[";
|
|
foreach (const RideFilePoint *point, ride->ride()->dataPoints())
|
|
{
|
|
size++;
|
|
|
|
if (point->secs == 0.0)
|
|
continue;
|
|
|
|
diffSecs = point->secs - prevSecs;
|
|
prevSecs = point->secs;
|
|
rideDateTime = rideDateTime.addSecs(diffSecs);
|
|
|
|
data += "{\"x\": ";
|
|
data += QString("%1").arg(point->lon,0,'f',GPS_COORD_TO_STRING);
|
|
data += ", \"y\": ";
|
|
data += QString("%1").arg(point->lat,0,'f',GPS_COORD_TO_STRING);
|
|
data += ", \"t\": ";
|
|
data += QString("%1").arg(rideDateTime.toTime_t());
|
|
|
|
if (parent->altitudeChk->isChecked()) {
|
|
data += ", \"e\": ";
|
|
data += QString("%1").arg(point->alt);
|
|
}
|
|
if (parent->powerChk->isChecked()) {
|
|
data += ", \"p\": ";
|
|
data += QString("%1").arg(point->watts);
|
|
}
|
|
if (parent->cadenceChk->isChecked()) {
|
|
data += ", \"c\": ";
|
|
data += QString("%1").arg(point->cad);
|
|
}
|
|
if (parent->heartrateChk->isChecked()) {
|
|
data += ", \"h\": ";
|
|
data += QString("%1").arg(point->hr);
|
|
}
|
|
|
|
data += "}";
|
|
|
|
if(size < totalSize)
|
|
data += ",";
|
|
}
|
|
data += "]";
|
|
out += data.replace("\"","\\\"");
|
|
out += "\"}";
|
|
|
|
QUrl url = QUrl("http://ridewithgps.com/trips.json");
|
|
QNetworkRequest request = QNetworkRequest(url);
|
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
|
|
parent->progressBar->setValue(parent->progressBar->value()+30/parent->shareSiteCount);
|
|
parent->progressLabel->setText(tr("Upload... Sending to RideWithGPS"));
|
|
|
|
networkMgr.post( request, out.toLatin1());
|
|
eventLoop.exec();
|
|
}
|
|
|
|
void
|
|
RideWithGpsUploader::requestUploadRideWithGPSFinished(QNetworkReply *reply)
|
|
{
|
|
parent->progressBar->setValue(parent->progressBar->value()+50/parent->shareSiteCount);
|
|
parent->progressLabel->setText(tr("Upload to RideWithGPS finished."));
|
|
uploadSuccessful = false;
|
|
|
|
QString uploadError;
|
|
int tripid = 0;
|
|
|
|
try {
|
|
|
|
// parse the response
|
|
QString response = reply->readAll();
|
|
MVJSONReader jsonResponse(string(response.toLatin1()));
|
|
|
|
// get values
|
|
uploadError = jsonResponse.root->getFieldString("error").c_str();
|
|
if (jsonResponse.root->hasField("trip")) {
|
|
tripid = jsonResponse.root->getField("trip")->getFieldInt("id");
|
|
}
|
|
|
|
} catch(...) {
|
|
|
|
// problem!
|
|
uploadError = "bad response or parser exception.";
|
|
}
|
|
|
|
// no error so clear
|
|
if (uploadError.toLower() == "none" || uploadError.toLower() == "null")
|
|
uploadError = "";
|
|
|
|
if (uploadError.length()>0 || reply->error() != QNetworkReply::NoError) {
|
|
//qDebug() << "Error " << reply->error();
|
|
//qDebug() << "Error; " << uploadError;
|
|
parent->errorLabel->setText(parent->errorLabel->text()+ tr(" Error from RideWithGPS: ") + uploadError + "\n" );
|
|
}
|
|
else
|
|
{
|
|
ride->ride()->setTag("RideWithGPS tripid", QString("%1").arg(tripid));
|
|
ride->setDirty(true);
|
|
|
|
//qDebug() << "tripid: " << tripid;
|
|
parent->progressBar->setValue(parent->progressBar->value()+10/parent->shareSiteCount);
|
|
uploadSuccessful = true;
|
|
}
|
|
}
|
|
|
|
// Today's Plan
|
|
// ----------------
|
|
|
|
TodaysPlanUploader::TodaysPlanUploader(Context *context, RideItem *ride, ShareDialog *parent) :
|
|
ShareDialogUploader(tr("TodaysPlan"), context, ride, parent)
|
|
{
|
|
todaysPlanUploadId = ride->ride()->getTag("TodaysPlan uploadId", "0").toInt();
|
|
}
|
|
|
|
bool
|
|
TodaysPlanUploader::canUpload( QString &err )
|
|
{
|
|
#ifdef GC_TODAYSPLAN_CLIENT_SECRET
|
|
token = appsettings->cvalue(context->athlete->cyclist, GC_TODAYSPLAN_TOKEN, "").toString();
|
|
if( token!="" )
|
|
return true;
|
|
|
|
err = tr("no Today's Plan token set. Please authorize in Settings.");
|
|
#else
|
|
err = tr("Today's Plan support isn't enabled in this build");
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
TodaysPlanUploader::wasUploaded()
|
|
{
|
|
return todaysPlanUploadId>0;
|
|
}
|
|
|
|
void
|
|
TodaysPlanUploader::upload()
|
|
{
|
|
// OAuth no more login
|
|
token = appsettings->cvalue(context->athlete->cyclist, GC_TODAYSPLAN_TOKEN, "").toString();
|
|
if (token=="")
|
|
return;
|
|
|
|
requestUploadTodaysPlan();
|
|
|
|
if(!uploadSuccessful)
|
|
{
|
|
parent->progressLabel->setText(tr("Error uploading to Today's Plan"));
|
|
}
|
|
else
|
|
{
|
|
parent->progressLabel->setText(tr("Successfully uploaded to Today's Plan"));
|
|
}
|
|
}
|
|
|
|
void
|
|
TodaysPlanUploader::requestUploadTodaysPlan()
|
|
{
|
|
parent->progressLabel->setText(tr("Upload to Today's Plan..."));
|
|
parent->progressBar->setValue(parent->progressBar->value()+10/parent->shareSiteCount);
|
|
|
|
QEventLoop eventLoop;
|
|
QNetworkAccessManager networkMgr;
|
|
|
|
connect(&networkMgr, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> & )), this, SLOT(onSslErrors(QNetworkReply*, const QList<QSslError> & )));
|
|
connect(&networkMgr, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestUploadTodaysPlanFinished(QNetworkReply*)));
|
|
connect(&networkMgr, SIGNAL(finished(QNetworkReply *)), &eventLoop, SLOT(quit()));
|
|
|
|
QString url = QString("%1/rest/files/upload")
|
|
.arg(appsettings->cvalue(context->athlete->cyclist, GC_TODAYSPLAN_URL, "https://whats.todaysplan.com.au").toString());
|
|
|
|
QNetworkRequest request(url);
|
|
|
|
QString boundary = QVariant(qrand()).toString()+QVariant(qrand()).toString()+QVariant(qrand()).toString();
|
|
|
|
//TcxFileReader reader;
|
|
JsonFileReader reader;
|
|
QByteArray file = zCompress(reader.toByteArray(context, ride->ride(), parent->altitudeChk->isChecked(), parent->powerChk->isChecked(), parent->heartrateChk->isChecked(), parent->cadenceChk->isChecked()));
|
|
QString rideName = ride->fileName;
|
|
rideName.append(".gz");
|
|
|
|
// MULTIPART *****************
|
|
|
|
QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
|
multiPart->setBoundary(boundary.toLatin1());
|
|
|
|
request.setRawHeader("Authorization", (QString("Bearer %1").arg(token)).toLatin1());
|
|
|
|
QHttpPart jsonPart;
|
|
jsonPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"json\""));
|
|
QString json = QString("{ filename: \"%1\"; name: \"%2\" }").arg(rideName).arg(parent->titleEdit->text());
|
|
jsonPart.setBody(json.toLatin1());
|
|
|
|
QHttpPart attachmentPart;
|
|
attachmentPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"attachment\"; type=\"text/xml\""));
|
|
attachmentPart.setBody(file);
|
|
|
|
multiPart->append(jsonPart);
|
|
multiPart->append(attachmentPart);
|
|
|
|
networkMgr.post(request, multiPart);
|
|
|
|
parent->progressBar->setValue(parent->progressBar->value()+30/parent->shareSiteCount);
|
|
parent->progressLabel->setText(tr("Upload... Sending to Today's Plan"));
|
|
|
|
eventLoop.exec();
|
|
}
|
|
|
|
void
|
|
TodaysPlanUploader::requestUploadTodaysPlanFinished(QNetworkReply *reply)
|
|
{
|
|
parent->progressBar->setValue(parent->progressBar->value()+50/parent->shareSiteCount);
|
|
parent->progressLabel->setText(tr("Upload to Today's Plan finished."));
|
|
uploadSuccessful = false;
|
|
|
|
QString uploadError;
|
|
try {
|
|
|
|
// parse the response
|
|
QString response = reply->readAll();
|
|
//qDebug() << "response" << response;
|
|
MVJSONReader jsonResponse(string(response.toLatin1()));
|
|
|
|
// get values
|
|
QString state = jsonResponse.root->getFieldString("state").c_str();
|
|
if (state == "error")
|
|
uploadError = jsonResponse.root->getFieldString("message").c_str();
|
|
todaysPlanUploadId = jsonResponse.root->getFieldInt("id");
|
|
|
|
} catch(...) {
|
|
|
|
// problem!
|
|
uploadError = "bad response or parser exception.";
|
|
todaysPlanUploadId = 0;
|
|
}
|
|
|
|
// if not there clean out
|
|
if (uploadError.toLower() == "none" || uploadError.toLower() == "null")
|
|
uploadError = "";
|
|
|
|
if (uploadError.length()>0 || reply->error() != QNetworkReply::NoError) {
|
|
//qDebug() << "Error " << reply->error() ;
|
|
//qDebug() << "Error " << uploadError;
|
|
parent->errorLabel->setText(parent->errorLabel->text()+ tr(" Error from Today's Plan: ") + uploadError + "\n" );
|
|
|
|
} else {
|
|
|
|
// Success
|
|
ride->ride()->setTag("TodaysPlan uploadId", QString("%1").arg(todaysPlanUploadId));
|
|
ride->setDirty(true);
|
|
|
|
//qDebug() << "uploadId: " << todaysPlanUploadId << rideName;
|
|
parent->progressBar->setValue(parent->progressBar->value()+10/parent->shareSiteCount);
|
|
uploadSuccessful = true;
|
|
}
|
|
}
|
|
|
|
// CyclingAnalytics
|
|
// ----------------
|
|
|
|
CyclingAnalyticsUploader::CyclingAnalyticsUploader(Context *context, RideItem *ride, ShareDialog *parent) :
|
|
ShareDialogUploader(tr("CyclingAnalytics"), context, ride, parent)
|
|
{
|
|
cyclingAnalyticsUploadId = ride->ride()->getTag("CyclingAnalytics uploadId", "0").toInt();
|
|
}
|
|
|
|
bool
|
|
CyclingAnalyticsUploader::canUpload( QString &err )
|
|
{
|
|
#ifdef GC_CYCLINGANALYTICS_CLIENT_SECRET
|
|
token = appsettings->cvalue(context->athlete->cyclist, GC_CYCLINGANALYTICS_TOKEN, "").toString();
|
|
if( token!="" )
|
|
return true;
|
|
|
|
err = tr("no CyclingAnalytics token set. Please authorize in Settings.");
|
|
#else
|
|
err = tr("CyclingAnalytics support isn't enabled in this build");
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
CyclingAnalyticsUploader::wasUploaded()
|
|
{
|
|
return cyclingAnalyticsUploadId>0;
|
|
}
|
|
|
|
void
|
|
CyclingAnalyticsUploader::upload()
|
|
{
|
|
// OAuth no more login
|
|
token = appsettings->cvalue(context->athlete->cyclist, GC_CYCLINGANALYTICS_TOKEN, "").toString();
|
|
if (token=="")
|
|
return;
|
|
|
|
requestUploadCyclingAnalytics();
|
|
|
|
if(!uploadSuccessful)
|
|
{
|
|
parent->progressLabel->setText(tr("Error uploading to CyclingAnalytics"));
|
|
}
|
|
else
|
|
{
|
|
parent->progressLabel->setText(tr("Successfully uploaded to CyclingAnalytics"));
|
|
}
|
|
}
|
|
|
|
void
|
|
CyclingAnalyticsUploader::requestUploadCyclingAnalytics()
|
|
{
|
|
parent->progressLabel->setText(tr("Upload to CyclingAnalytics..."));
|
|
parent->progressBar->setValue(parent->progressBar->value()+10/parent->shareSiteCount);
|
|
|
|
QEventLoop eventLoop;
|
|
QNetworkAccessManager networkMgr;
|
|
|
|
connect(&networkMgr, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> & )), this, SLOT(onSslErrors(QNetworkReply*, const QList<QSslError> & )));
|
|
connect(&networkMgr, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestUploadCyclingAnalyticsFinished(QNetworkReply*)));
|
|
connect(&networkMgr, SIGNAL(finished(QNetworkReply *)), &eventLoop, SLOT(quit()));
|
|
|
|
QUrl url = QUrl( "https://www.cyclinganalytics.com/api/me/upload" );
|
|
QNetworkRequest request = QNetworkRequest(url);
|
|
|
|
QString boundary = QVariant(qrand()).toString()+QVariant(qrand()).toString()+QVariant(qrand()).toString();
|
|
|
|
TcxFileReader reader;
|
|
QByteArray file = reader.toByteArray(context, ride->ride(), parent->altitudeChk->isChecked(), parent->powerChk->isChecked(), parent->heartrateChk->isChecked(), parent->cadenceChk->isChecked());
|
|
|
|
// MULTIPART *****************
|
|
|
|
QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
|
multiPart->setBoundary(boundary.toLatin1());
|
|
|
|
request.setRawHeader("Authorization", (QString("Bearer %1").arg(token)).toLatin1());
|
|
|
|
QHttpPart activityNamePart;
|
|
activityNamePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"title\""));
|
|
activityNamePart.setBody(QString(parent->titleEdit->text()).toLatin1());
|
|
|
|
QHttpPart dataTypePart;
|
|
dataTypePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"format\""));
|
|
dataTypePart.setBody("tcx");
|
|
|
|
QHttpPart filenamePart;
|
|
filenamePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"filename\""));
|
|
filenamePart.setBody("file.tcx");
|
|
|
|
QHttpPart filePart;
|
|
filePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"data\"; filename=\"file.tcx\"; type=\"text/xml\""));
|
|
filePart.setBody(file);
|
|
|
|
multiPart->append(activityNamePart);
|
|
multiPart->append(filenamePart);
|
|
multiPart->append(dataTypePart);
|
|
multiPart->append(filePart);
|
|
|
|
networkMgr.post(request, multiPart);
|
|
|
|
parent->progressBar->setValue(parent->progressBar->value()+30/parent->shareSiteCount);
|
|
parent->progressLabel->setText(tr("Upload... Sending to CyclingAnalytics"));
|
|
|
|
eventLoop.exec();
|
|
}
|
|
|
|
void
|
|
CyclingAnalyticsUploader::requestUploadCyclingAnalyticsFinished(QNetworkReply *reply)
|
|
{
|
|
parent->progressBar->setValue(parent->progressBar->value()+50/parent->shareSiteCount);
|
|
parent->progressLabel->setText(tr("Upload to CyclingAnalytics finished."));
|
|
uploadSuccessful = false;
|
|
|
|
QString uploadError;
|
|
try {
|
|
|
|
// parse the response
|
|
QString response = reply->readAll();
|
|
MVJSONReader jsonResponse(string(response.toLatin1()));
|
|
|
|
// get values
|
|
uploadError = jsonResponse.root->getFieldString("error").c_str();
|
|
cyclingAnalyticsUploadId = jsonResponse.root->getFieldInt("upload_id");
|
|
|
|
} catch(...) {
|
|
|
|
// problem!
|
|
uploadError = "bad response or parser exception.";
|
|
cyclingAnalyticsUploadId = 0;
|
|
}
|
|
|
|
// if not there clean out
|
|
if (uploadError.toLower() == "none" || uploadError.toLower() == "null")
|
|
uploadError = "";
|
|
|
|
if (uploadError.length()>0 || reply->error() != QNetworkReply::NoError) {
|
|
//qDebug() << "Error " << reply->error() ;
|
|
//qDebug() << "Error " << uploadError;
|
|
parent->errorLabel->setText(parent->errorLabel->text()+ tr(" Error from CyclingAnalytics: ") + uploadError + "\n" );
|
|
|
|
} else {
|
|
|
|
// Success
|
|
ride->ride()->setTag("CyclingAnalytics uploadId", QString("%1").arg(cyclingAnalyticsUploadId));
|
|
ride->setDirty(true);
|
|
|
|
//qDebug() << "uploadId: " << cyclingAnalyticsUploadId;
|
|
parent->progressBar->setValue(parent->progressBar->value()+10/parent->shareSiteCount);
|
|
uploadSuccessful = true;
|
|
}
|
|
}
|
|
|
|
// SelfLoops
|
|
// ----------------
|
|
|
|
SelfLoopsUploader::SelfLoopsUploader(Context *context, RideItem *ride, ShareDialog *parent) :
|
|
ShareDialogUploader(tr("SelfLoops"),context, ride, parent)
|
|
{
|
|
selfloopsUploadId = ride->ride()->getTag("Selfloops uploadId", "0").toInt();
|
|
selfloopsActivityId = ride->ride()->getTag("Selfloops activityId", "0").toInt();
|
|
}
|
|
|
|
bool
|
|
SelfLoopsUploader::canUpload( QString &err )
|
|
{
|
|
QString username = appsettings->cvalue(context->athlete->cyclist, GC_SELUSER).toString();
|
|
if( username.length() > 0 )
|
|
return true;
|
|
|
|
err = tr("no credentials set for SelfLoops. Please check Settings.");
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
SelfLoopsUploader::wasUploaded()
|
|
{
|
|
return selfloopsActivityId>0;
|
|
}
|
|
|
|
void
|
|
SelfLoopsUploader::upload()
|
|
{
|
|
requestUploadSelfLoops();
|
|
|
|
if(!uploadSuccessful)
|
|
{
|
|
parent->progressLabel->setText(tr("Error uploading to Selfloops"));
|
|
}
|
|
else
|
|
{
|
|
parent->progressLabel->setText(tr("Successfully uploaded to Selfloops"));
|
|
}
|
|
}
|
|
|
|
/*
|
|
make a multipart HTTP POST at the following path "/restapi/public/activities/upload.json"
|
|
on selflloops web site using SSL.
|
|
The requested parameters are:
|
|
- "email" the email of a valid SelfLoops account
|
|
- "pw" the password
|
|
- "tcxfile" the zipped TCX file (example: test.tcx.gz).
|
|
|
|
On success, response message contains a JSON encoded data
|
|
with the new "activity_id" created.
|
|
On error, SelfLoops response contains a JSON encoded data with "error_code" and "message" key.
|
|
*/
|
|
void
|
|
SelfLoopsUploader::requestUploadSelfLoops()
|
|
{
|
|
parent->progressLabel->setText(tr("Upload to Selfloops..."));
|
|
parent->progressBar->setValue(parent->progressBar->value()+10/parent->shareSiteCount);
|
|
|
|
QEventLoop eventLoop;
|
|
QNetworkAccessManager networkMgr;
|
|
|
|
connect(&networkMgr, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> & )), this, SLOT(onSslErrors(QNetworkReply*, const QList<QSslError> & )));
|
|
connect(&networkMgr, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestUploadSelfLoopsFinished(QNetworkReply*)));
|
|
connect(&networkMgr, SIGNAL(finished(QNetworkReply *)), &eventLoop, SLOT(quit()));
|
|
|
|
QUrl url = QUrl( "https://www.selfloops.com/restapi/public/activities/upload.json" );
|
|
QNetworkRequest request = QNetworkRequest(url);
|
|
|
|
QString boundary = QVariant(qrand()).toString()+QVariant(qrand()).toString()+QVariant(qrand()).toString();
|
|
|
|
// The TCX file have to be gzipped
|
|
TcxFileReader reader;
|
|
QByteArray file = zCompress(reader.toByteArray(context, ride->ride(), parent->altitudeChk->isChecked(), parent->powerChk->isChecked(), parent->heartrateChk->isChecked(), parent->cadenceChk->isChecked()));
|
|
|
|
QString username = appsettings->cvalue(context->athlete->cyclist, GC_SELUSER).toString();
|
|
QString password = appsettings->cvalue(context->athlete->cyclist, GC_SELPASS).toString();
|
|
|
|
// MULTIPART *****************
|
|
|
|
QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::MixedType);
|
|
multiPart->setBoundary(boundary.toLatin1());
|
|
|
|
QHttpPart emailPart;
|
|
emailPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"email\""));
|
|
emailPart.setBody(username.toLatin1());
|
|
|
|
QHttpPart passwordPart;
|
|
passwordPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"pw\""));
|
|
passwordPart.setBody(password.toLatin1());
|
|
|
|
QHttpPart filePart;
|
|
filePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"tcxfile\"; filename=\"myfile.tcx.gz\"; type=\"application/x-gzip\""));
|
|
filePart.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-gzip");
|
|
filePart.setBody(file);
|
|
|
|
multiPart->append(emailPart);
|
|
multiPart->append(passwordPart);
|
|
multiPart->append(filePart);
|
|
|
|
networkMgr.post(request, multiPart);
|
|
|
|
parent->progressBar->setValue(parent->progressBar->value()+30/parent->shareSiteCount);
|
|
parent->progressLabel->setText(tr("Upload... Sending to Selfloops"));
|
|
|
|
eventLoop.exec();
|
|
}
|
|
|
|
void
|
|
SelfLoopsUploader::requestUploadSelfLoopsFinished(QNetworkReply *reply)
|
|
{
|
|
parent->progressBar->setValue(parent->progressBar->value()+50/parent->shareSiteCount);
|
|
parent->progressLabel->setText(tr("Upload to Selfloops finished."));
|
|
|
|
uploadSuccessful = false;
|
|
|
|
int error;
|
|
QString uploadError;
|
|
try {
|
|
|
|
// parse the response
|
|
QString response = reply->readAll();
|
|
MVJSONReader jsonResponse(string(response.toLatin1()));
|
|
|
|
// get values
|
|
error = jsonResponse.root->getFieldInt("error_code");
|
|
uploadError = jsonResponse.root->getFieldString("message").c_str();
|
|
selfloopsActivityId = jsonResponse.root->getFieldInt("activity_id");
|
|
|
|
} catch(...) {
|
|
|
|
// problem!
|
|
error = 500;
|
|
uploadError = "bad response or parser exception.";
|
|
selfloopsActivityId = 0;
|
|
}
|
|
|
|
if (error>0 || reply->error() != QNetworkReply::NoError) {
|
|
//qDebug() << "Error " << reply->error() ;
|
|
//qDebug() << "Error " << uploadError;
|
|
parent->errorLabel->setText(parent->errorLabel->text()+ tr(" Error from Selfloops: ") + uploadError + "\n" );
|
|
|
|
} else {
|
|
|
|
//qDebug() << "activity: " << selfloopsActivityId;
|
|
|
|
ride->ride()->setTag("Selfloops activityId", QString("%1").arg(selfloopsActivityId));
|
|
ride->setDirty(true);
|
|
|
|
parent->progressBar->setValue(parent->progressBar->value()+10/parent->shareSiteCount);
|
|
uploadSuccessful = true;
|
|
}
|
|
}
|
|
|
|
#if 0 // NOT AVAILABLE -- COMMENTED OUT FOR VERSION 3.1
|
|
/******************/
|
|
/* Garmin Connect */
|
|
/******************/
|
|
|
|
GarminUploader::GarminUploader(Context *context, RideItem *ride, ShareDialog *parent) :
|
|
ShareDialogUploader( tr("Garmin Connect"), context, ride, parent)
|
|
{
|
|
garminUploadId = ride->ride()->getTag("Garmin Connect uploadId", "");
|
|
garminActivityId = ride->ride()->getTag("Garmin Connect activityId", "");
|
|
}
|
|
|
|
bool
|
|
GarminUploader::wasUploaded()
|
|
{
|
|
return garminActivityId.length()>0;
|
|
}
|
|
|
|
void
|
|
GarminUploader::upload()
|
|
{
|
|
requestFlowExecutionKey();
|
|
//requestUploadGarmin();
|
|
|
|
if(!uploadSuccessful)
|
|
{
|
|
parent->progressLabel->setText(tr("Error uploading to Garmin Connect"));
|
|
}
|
|
else
|
|
{
|
|
parent->progressLabel->setText(tr("Successfully uploaded to Garmin Connect"));
|
|
}
|
|
}
|
|
|
|
void
|
|
GarminUploader::requestFlowExecutionKey()
|
|
{
|
|
parent->progressLabel->setText(tr("Login to Garmin Connect..."));
|
|
parent->progressBar->setValue(parent->progressBar->value()+10/parent->shareSiteCount);
|
|
|
|
QEventLoop eventLoop;
|
|
|
|
connect(&networkMgr, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFlowExecutionKeyFinished(QNetworkReply*)));
|
|
connect(&networkMgr, SIGNAL(finished(QNetworkReply *)), &eventLoop, SLOT(quit()));
|
|
|
|
serverUrl = QUrl("https://sso.garmin.com/sso/login?service=http%3A%2F%2Fconnect.garmin.com%2Fpost-auth%2Flogin&webhost=olaxpw-connect07.garmin.com&source=http%3A%2F%2Fconnect.garmin.com%2Fde-DE%2Fsignin&redirectAfterAccountLoginUrl=http%3A%2F%2Fconnect.garmin.com%2Fpost-auth%2Flogin&redirectAfterAccountCreationUrl=http%3A%2F%2Fconnect.garmin.com%2Fpost-auth%2Flogin&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso&locale=en&id=gauth-widget&cssUrl=https%3A%2F%2Fstatic.garmincdn.com%2Fcom.garmin.connect%2Fui%2Fsrc-css%2Fgauth-custom.css&clientId=GarminConnect&rememberMeShown=true&rememberMeChecked=false&createAccountShown=true&openCreateAccount=false&usernameShown=true&displayNameShown=false&consumeServiceTicket=false&initialFocus=true&embedWidget=false");
|
|
|
|
QNetworkRequest request = QNetworkRequest(serverUrl);
|
|
|
|
qDebug() << "url" << serverUrl;
|
|
|
|
networkMgr.get(request);
|
|
|
|
// holding pattern
|
|
eventLoop.exec();
|
|
}
|
|
|
|
void
|
|
GarminUploader::requestFlowExecutionKeyFinished(QNetworkReply *reply)
|
|
{
|
|
parent->progressBar->setValue(parent->progressBar->value()+50/parent->shareSiteCount);
|
|
parent->progressLabel->setText(tr("Login to Garmin Connect finished."));
|
|
|
|
QString response = reply->readAll();
|
|
//qDebug() << "response" << response;
|
|
|
|
int i = response.indexOf("[", response.indexOf("flowExecutionKey"))+1;
|
|
int j = response.indexOf("]", i);
|
|
flowExecutionKey = response.mid(i, j-i);
|
|
qDebug() << "flowExecutionKey" << flowExecutionKey;
|
|
|
|
QVariant v = reply->header(QNetworkRequest::SetCookieHeader);
|
|
serverUrl = QUrl( "https://sso.garmin.com/sso/login" );
|
|
|
|
#if QT_VERSION > 0x050000
|
|
QUrlQuery params;
|
|
#else
|
|
QUrl params = serverUrl;
|
|
#endif
|
|
|
|
params.addQueryItem("service","http%3A%2F%2Fconnect.garmin.com%2Fpost-auth%2Flogin");
|
|
params.addQueryItem("clientId","GarminConnect");
|
|
params.addQueryItem("consumeServiceTicket","false");
|
|
|
|
#if QT_VERSION > 0x050000
|
|
serverUrl.setQuery(params);
|
|
#endif
|
|
|
|
qDebug() << "serverUrl" << serverUrl;
|
|
|
|
requestLoginGarmin();
|
|
}
|
|
|
|
void
|
|
GarminUploader::requestLoginGarmin()
|
|
{
|
|
qDebug() << "requestLoginGarmin()";
|
|
|
|
parent->progressLabel->setText(tr("Login to Garmin Connect..."));
|
|
parent->progressBar->setValue(parent->progressBar->value()+10/parent->shareSiteCount);
|
|
|
|
QEventLoop eventLoop;
|
|
|
|
disconnect(&networkMgr, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFlowExecutionKeyFinished(QNetworkReply*)));
|
|
connect(&networkMgr, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestLoginGarminFinished(QNetworkReply*)));
|
|
|
|
|
|
QNetworkRequest request = QNetworkRequest(serverUrl);
|
|
request.setHeader(QNetworkRequest::ContentTypeHeader,QVariant("application/x-www-form-urlencoded"));
|
|
|
|
QByteArray data;
|
|
#if QT_VERSION > 0x050000
|
|
QUrlQuery params;
|
|
#else
|
|
QUrl params;
|
|
#endif
|
|
|
|
params.addQueryItem("_eventId","submit");
|
|
params.addQueryItem("embed","true");
|
|
|
|
params.addQueryItem("lt",flowExecutionKey);
|
|
params.addQueryItem("username","grauser");
|
|
params.addQueryItem("password","_garmin55");
|
|
|
|
#if QT_VERSION > 0x050000
|
|
//data.append(params.query(QUrl::FullyEncoded));
|
|
data=params.query(QUrl::FullyEncoded).toUtf8();
|
|
#else
|
|
data=params.encodedQuery();
|
|
#endif
|
|
|
|
networkMgr.post(request, data);
|
|
|
|
// holding pattern
|
|
eventLoop.exec();
|
|
}
|
|
|
|
void
|
|
GarminUploader::requestLoginGarminFinished(QNetworkReply *reply)
|
|
{
|
|
qDebug() << "requestLoginGarminFinished()";
|
|
|
|
parent->progressBar->setValue(parent->progressBar->value()+50/parent->shareSiteCount);
|
|
parent->progressLabel->setText(tr("Login to Garmin Connect finished."));
|
|
|
|
QString response = reply->readAll();
|
|
//qDebug() << "response2" << response;
|
|
|
|
if (reply->error() != QNetworkReply::NoError)
|
|
{
|
|
qDebug() << "Error " << reply->error() ;
|
|
} else {
|
|
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
qDebug() << "statusCode " << statusCode;
|
|
|
|
if(statusCode == 200) {
|
|
|
|
sc = se.evaluate("("+response+")");
|
|
QString response_url = sc.property("response_url").toString();
|
|
qDebug() << "response_url" << response_url;
|
|
|
|
int i = response.indexOf("?ticket=")+8;
|
|
int j = response.indexOf("'", i);
|
|
ticket = response.mid(i, j-i);
|
|
qDebug() << "ticket" << ticket;
|
|
|
|
requestUploadGarmin();
|
|
}
|
|
else if(statusCode == 302) { // or statusCode == 301
|
|
QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
|
|
qDebug() << "redirectUrl " << redirectUrl;
|
|
//serverUrl = redirectUrl;
|
|
//requestLoginGarmin();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void
|
|
GarminUploader::requestUploadGarmin()
|
|
{
|
|
qDebug() << "requestUploadGarmin()";
|
|
|
|
parent->progressLabel->setText(tr("Upload to Garmin Connect..."));
|
|
parent->progressBar->setValue(parent->progressBar->value()+10/parent->shareSiteCount);
|
|
|
|
QEventLoop eventLoop;
|
|
|
|
disconnect(&networkMgr, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestLoginGarminFinished(QNetworkReply*)));
|
|
connect(&networkMgr, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestUploadGarminFinished(QNetworkReply*)));
|
|
|
|
// #/proxy/upload-service-1.1/json/upload/.fit
|
|
// fit_file = FITIO.Dump(activity)
|
|
// files = {"data": ("tap-sync-" + str(os.getpid()) + "-" + activity.UID + ".fit", fit_file)}
|
|
// cookies = self._get_cookies(record=serviceRecord)
|
|
// self._rate_limit()
|
|
// res = requests.post("http://connect.garmin.com/proxy/upload-service-1.1/json/upload/.tcx", files=files, cookies=cookies)
|
|
// res = res.json()["detailedImportResult"]
|
|
|
|
|
|
QUrl url = QUrl( "http://connect.garmin.com/proxy/upload-service-1.1/json/upload/.tcx" );
|
|
QNetworkRequest request = QNetworkRequest(url);
|
|
|
|
QString boundary = QVariant(qrand()).toString()+QVariant(qrand()).toString()+QVariant(qrand()).toString();
|
|
|
|
// The TCX file have to be gzipped
|
|
TcxFileReader reader;
|
|
QByteArray file = reader.toByteArray(context, ride->ride(), parent->altitudeChk->isChecked(), parent->powerChk->isChecked(), parent->heartrateChk->isChecked(), parent->cadenceChk->isChecked());
|
|
|
|
|
|
// MULTIPART *****************
|
|
|
|
QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::MixedType);
|
|
multiPart->setBoundary(boundary.toLatin1());
|
|
|
|
|
|
QHttpPart filePart;
|
|
filePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"tcxfile\"; filename=\"myfile.tcx.\"; type=\"application/x-gzip\""));
|
|
filePart.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-gzip");
|
|
filePart.setBody(file);
|
|
|
|
multiPart->append(filePart);
|
|
|
|
networkMgr.post(request, multiPart);
|
|
|
|
parent->progressBar->setValue(parent->progressBar->value()+30/parent->shareSiteCount);
|
|
parent->progressLabel->setText(tr("Upload... Sending to Garmin Connect"));
|
|
|
|
eventLoop.exec();
|
|
}
|
|
|
|
void
|
|
GarminUploader::requestUploadGarminFinished(QNetworkReply *reply)
|
|
{
|
|
qDebug() << "requestUploadGarminFinished()";
|
|
|
|
parent->progressBar->setValue(parent->progressBar->value()+50/parent->shareSiteCount);
|
|
parent->progressLabel->setText(tr("Upload to Garmin Connect finished."));
|
|
|
|
uploadSuccessful = false;
|
|
|
|
QString response = reply->readAll();
|
|
qDebug() << "response" << response;
|
|
|
|
sc = se.evaluate("("+response+")");
|
|
QString error = sc.property("error_code").toString();
|
|
QString uploadError = sc.property("message").toString();
|
|
|
|
if (error.length()>0 || reply->error() != QNetworkReply::NoError)
|
|
{
|
|
qDebug() << "Error " << reply->error() ;
|
|
qDebug() << "Error " << uploadError;
|
|
parent->errorLabel->setText(parent->errorLabel->text()+ tr(" Error from Garmin Connect: ") + uploadError + "\n" );
|
|
}
|
|
else
|
|
{
|
|
garminActivityId = sc.property("activity_id").toString();
|
|
|
|
ride->ride()->setTag("Garmin Connect activityId", garminActivityId);
|
|
ride->setDirty(true);
|
|
|
|
qDebug() << "activity: " << garminActivityId;
|
|
|
|
parent->progressBar->setValue(parent->progressBar->value()+10/parent->shareSiteCount);
|
|
uploadSuccessful = true;
|
|
}
|
|
}
|
|
|
|
#endif
|