Files
GoldenCheetah/src/VeloHeroUploader.cpp
Mark Liversedge d743fdd3ec No Assert in RideMetric.h
.. gets rid of an iritating compiler warning when
   compiling with VS2015 and MSVC

.. is good because user metrics aren't fixed so the
   assert assumption is wrong now

.. means we have to add assert.h to all the source
   files that had it from including RideMetric.h
   and want to use it.
2015-12-24 15:26:29 +00:00

398 lines
10 KiB
C++

/*
* Copyright (c) 2012 Rainer Clasen <bj@zuto.de>
* Copyright (c) 2014 Nils Knieling copied from TtbDialog.cpp
*
* 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 "VeloHeroUploader.h"
#include "ShareDialog.h"
#include "Athlete.h"
#include "Settings.h"
#include "TimeUtils.h"
#include "Units.h"
#include <stdio.h>
#include <assert.h>
// access to metrics
#include "PwxRideFile.h"
/*
* - reuse single QNetworkAccessManager for all requests to allow
* connection re-use.
*
* - replies must be handled differently - based on request, but there's
* just a single mgr.finish() handler.
*
* - can't connect() to QNetworkReply::finish, as there's a
* slight chance for a race condition: request finished between call to
* mgr.post() and connect(). Therefore have to connect(QNetworkAccessManager::finish)
*
* - can't subclass QNetworkRequest and add attribute, as
* QNetworkAccessManager only takes a reference and clones a plain
* QNetworkRequest from it.
*
* - so... hack around this with currentRequest + dispatchRequest TODO: fix
*/
const QString VELOHERO_URL( "http://app.velohero.com" );
class VeloHeroParser : public QXmlDefaultHandler
{
public:
friend class VeloHeroUploader;
bool startElement( const QString&, const QString&, const QString&,
const QXmlAttributes& )
{
cdata = "";
return true;
};
bool endElement( const QString&, const QString&, const QString& qName )
{
if( qName == "error" ){
error = cdata;
return true;
}
return true;
}
bool characters( const QString& str)
{
cdata += str;
return true;
};
protected:
QString cdata;
QString error;
};
VeloHeroUploader::VeloHeroUploader(Context *context, RideItem *ride, ShareDialog *parent ) :
ShareDialogUploader( tr("VeloHero"), context, ride, parent)
{
exerciseId = ride->ride()->getTag("VeloHeroExercise", "");
connect(&networkMgr, SIGNAL(finished(QNetworkReply*)), this,
SLOT(dispatchReply(QNetworkReply*)));
}
bool
VeloHeroUploader::canUpload( QString &err )
{
QString username = appsettings->cvalue(context->athlete->cyclist, GC_VELOHEROUSER).toString();
if( username.length() > 0 ){
return true;
}
err = tr("Cannot upload to VeloHero without credentials. Check Settings");
return false;
}
bool
VeloHeroUploader::wasUploaded()
{
return exerciseId.length() > 0;
}
void
VeloHeroUploader::upload()
{
uploadSuccessful = false;
requestSession();
if( !uploadSuccessful ){
parent->progressLabel->setText(tr("Error uploading to VeloHero"));
} else {
parent->progressLabel->setText(tr("successfully uploaded to VeloHero as %1")
.arg(exerciseId));
ride->ride()->setTag("VeloHeroExercise", exerciseId );
ride->setDirty(true);
}
}
void
VeloHeroUploader::requestSession()
{
parent->progressLabel->setText(tr("getting new VeloHero session..."));
currentRequest = reqSession;
QString username = appsettings->cvalue(context->athlete->cyclist, GC_VELOHEROUSER).toString();
QString password = appsettings->cvalue(context->athlete->cyclist, GC_VELOHEROPASS).toString();
#if QT_VERSION > 0x050000
QUrlQuery urlquery;
#else
QUrl urlquery( VELOHERO_URL + "/sso" );
#endif
urlquery.addQueryItem( "view", "xml" );
urlquery.addQueryItem( "user", username );
urlquery.addQueryItem( "pass", password );
#if QT_VERSION > 0x050000
QUrl url (VELOHERO_URL + "/sso");
url.setQuery(urlquery.query());
QNetworkRequest request = QNetworkRequest(url);
#else
QNetworkRequest request = QNetworkRequest(urlquery);
#endif
request.setRawHeader( "Accept-Encoding", "identity" );
request.setRawHeader( "Accept", "application/xml" );
request.setRawHeader( "Accept-Charset", "utf-8" );
networkMgr.get( request );
parent->progressBar->setValue(parent->progressBar->value()+10/parent->shareSiteCount);
eventLoop.exec();
}
void
VeloHeroUploader::requestUpload()
{
assert(sessionId.length() > 0 );
parent->progressLabel->setText(tr("preparing VeloHero data ..."));
QHttpMultiPart *body = new QHttpMultiPart( QHttpMultiPart::FormDataType );
QHttpPart textPart;
textPart.setHeader(QNetworkRequest::ContentDispositionHeader,
QVariant("form-data; name=\"upload_submit\""));
textPart.setBody("hrm");
body->append( textPart );
QString fname = context->athlete->home->temp().absoluteFilePath(".velohero-upload.pwx" );
QFile *uploadFile = new QFile( fname );
uploadFile->setParent(body);
PwxFileReader reader;
reader.writeRideFile(context, ride->ride(), *uploadFile );
parent->progressBar->setValue(parent->progressBar->value()+20/parent->shareSiteCount);
int limit = 16777216; // 16MB
if( uploadFile->size() >= limit ){
parent->errorLabel->setText(tr("temporary file too large for upload: %1 > %1 bytes")
.arg(uploadFile->size())
.arg(limit) );
eventLoop.quit();
return;
}
QHttpPart filePart;
filePart.setHeader(QNetworkRequest::ContentTypeHeader,
QVariant("application/octet-stream"));
filePart.setHeader(QNetworkRequest::ContentDispositionHeader,
QVariant("form-data; name=\"file\"; filename=\"gc-upload-velohero.pwx\""));
uploadFile->open(QIODevice::ReadOnly);
filePart.setBodyDevice(uploadFile);
body->append( filePart );
parent->progressLabel->setText(tr("sending to VeloHero..."));
currentRequest = reqUpload;
#if QT_VERSION > 0x050000
QUrlQuery urlquery;
#else
QUrl urlquery( VELOHERO_URL + "/upload/file" );
#endif
urlquery.addQueryItem( "view", "xml" );
urlquery.addQueryItem( "sso", sessionId );
#if QT_VERSION > 0x050000
QUrl url (VELOHERO_URL + "/upload/file");
url.setQuery(urlquery.query());
QNetworkRequest request = QNetworkRequest(url);
#else
QNetworkRequest request = QNetworkRequest(urlquery);
#endif
request.setRawHeader( "Accept-Encoding", "identity" );
request.setRawHeader( "Accept", "application/xml" );
request.setRawHeader( "Accept-Charset", "utf-8" );
QNetworkReply *reply = networkMgr.post( request, body );
body->setParent( reply );
}
void
VeloHeroUploader::dispatchReply( QNetworkReply *reply )
{
if( reply->error() != QNetworkReply::NoError ){
parent->errorLabel->setText( tr("request failed: ")
+ reply->errorString() );
eventLoop.exec();
return;
}
QVariant status = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
if( ! status.isValid() || status.toInt() != 200 ){
QVariant msg( reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute ) );
parent->errorLabel->setText( tr("request failed, server response: %1 %2")
.arg(status.toInt())
.arg(msg.toString()) );
eventLoop.quit();
return;
}
switch( currentRequest ){
case reqSession:
finishSession( reply );
break;
case reqUpload:
finishUpload( reply );
break;
}
}
class VeloHeroSessionParser : public VeloHeroParser
{
public:
friend class VeloHeroUploader;
bool endElement( const QString& a, const QString&b, const QString& qName )
{
if( qName == "session" ){
session = cdata;
return true;
}
return VeloHeroParser::endElement( a, b, qName );
};
protected:
QString session;
};
void
VeloHeroUploader::finishSession(QNetworkReply *reply)
{
parent->progressBar->setValue(parent->progressBar->value()+10/parent->shareSiteCount);
VeloHeroSessionParser handler;
QXmlInputSource source( reply );
QXmlSimpleReader reader;
reader.setContentHandler( &handler );
if( ! reader.parse( source ) ){
parent->errorLabel->setText(tr("failed to parse session response: ")
+handler.errorString());
eventLoop.quit();
return;
}
if( handler.error.length() > 0 ){
parent->errorLabel->setText(tr("failed to get new session: ")
+handler.error );
eventLoop.quit();
return;
}
sessionId = handler.session;
if( sessionId.length() == 0 ){
parent->errorLabel->setText(tr("got empty session"));
eventLoop.quit();
return;
}
requestUpload();
}
class VeloHeroUploadParser : public VeloHeroParser
{
public:
friend class VeloHeroUploader;
bool endElement( const QString& a, const QString&b, const QString& qName )
{
if( qName == "id" ){
id = cdata;
return true;
}
return VeloHeroParser::endElement( a, b, qName );
};
protected:
QString id;
};
void
VeloHeroUploader::finishUpload(QNetworkReply *reply)
{
parent->progressBar->setValue(parent->progressBar->value()+60/parent->shareSiteCount);
VeloHeroUploadParser handler;
QXmlInputSource source( reply );
QXmlSimpleReader reader;
reader.setContentHandler( &handler );
if( ! reader.parse( source ) ){
parent->errorLabel->setText(tr("failed to parse upload response: ")
+handler.errorString());
eventLoop.quit();
return;
}
if( handler.error.length() > 0 ){
parent->errorLabel->setText(tr("failed to upload file: ")
+handler.error );
eventLoop.quit();
return;
}
exerciseId = handler.id;
if( exerciseId.length() == 0 ){
parent->errorLabel->setText(tr("got empty exercise"));
eventLoop.quit();
return;
}
uploadSuccessful = true;
eventLoop.quit();
}