Files
GoldenCheetah/deprecated/VeloHeroUploader.cpp

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( "https://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();
}