added upload to trainingstagebuch.org

This commit is contained in:
Rainer Clasen
2012-04-26 22:50:49 +02:00
parent 5f5e6dc1b6
commit b6e32ca49f
8 changed files with 624 additions and 0 deletions

View File

@@ -58,6 +58,7 @@
#include "BatchExportDialog.h"
#include "StravaDialog.h"
#include "RideWithGPSDialog.h"
#include "TtbDialog.h"
#include "TwitterDialog.h"
#include "WithingsDownload.h"
#include "CalendarDownload.h"
@@ -681,6 +682,10 @@ MainWindow::MainWindow(const QDir &home) :
connect(rideWithGPSAction, SIGNAL(triggered(bool)), this, SLOT(uploadRideWithGPSAction()));
rideMenu->addAction(rideWithGPSAction);
ttbAction = new QAction("Upload to Trainingstagebuch...", this);
connect(ttbAction, SIGNAL(triggered(bool)), this, SLOT(uploadTtb()));
rideMenu->addAction(ttbAction);
rideMenu->addSeparator ();
rideMenu->addAction(tr("&Save activity"), this, SLOT(saveRide()), tr("Ctrl+S"));
rideMenu->addAction(tr("D&elete activity..."), this, SLOT(deleteRide()));
@@ -928,9 +933,14 @@ MainWindow::setActivityMenu()
if (tripid == "") rideWithGPSAction->setEnabled(true);
else rideWithGPSAction->setEnabled(false);
activityId = ride->ride()->getTag("TtbExercise", "");
if (activityId == "") ttbAction->setEnabled(true);
else ttbAction->setEnabled(false);
} else {
stravaAction->setEnabled(false);
rideWithGPSAction->setEnabled(false);
ttbAction->setEnabled(false);
}
}
@@ -1772,6 +1782,24 @@ MainWindow::uploadRideWithGPSAction()
}
}
/*----------------------------------------------------------------------
* trainingstagebuch.org
*--------------------------------------------------------------------*/
void
MainWindow::uploadTtb()
{
QTreeWidgetItem *_item = treeWidget->currentItem();
if (_item==NULL || _item->type() != RIDE_TYPE) return;
RideItem *item = dynamic_cast<RideItem*>(_item);
if (item) { // menu is disabled anyway, but belt and braces
TtbDialog d(this, item);
d.exec();
}
}
/*----------------------------------------------------------------------
* ErgDB
*--------------------------------------------------------------------*/

View File

@@ -236,6 +236,7 @@ class MainWindow : public QMainWindow
void exportMetrics();
void uploadStrava();
void uploadRideWithGPSAction();
void uploadTtb();
void downloadErgDB();
void manualProcess(QString);
#ifdef GC_HAVE_SOAP
@@ -346,6 +347,7 @@ class MainWindow : public QMainWindow
QAction *sideView;
QAction *toolView;
QAction *stravaAction, *rideWithGPSAction;
QAction *ttbAction;
QMenu *windowMenu;
GcBubble *bubble;
GcCalendar *gcCalendar;

View File

@@ -492,6 +492,11 @@ CredentialsPage::CredentialsPage(QWidget *parent, MainWindow *mainWindow) : QScr
QLabel *rwgpsuserLabel = new QLabel(tr("Username"));
QLabel *rwgpspassLabel = new QLabel(tr("Password"));
QLabel *ttb = new QLabel(tr("Trainingstagebuch"));
ttb->setFont(current);
QLabel *ttbuserLabel = new QLabel(tr("Username"));
QLabel *ttbpassLabel = new QLabel(tr("Password"));
QLabel *wip = new QLabel(tr("Withings Wifi Scales"));
wip->setFont(current);
@@ -560,6 +565,13 @@ CredentialsPage::CredentialsPage(QWidget *parent, MainWindow *mainWindow) : QScr
rideWithGPSPass->setEchoMode(QLineEdit::Password);
rideWithGPSPass->setText(appsettings->cvalue(mainWindow->cyclist, GC_RWGPSPASS, "").toString());
ttbUser = new QLineEdit(this);
ttbUser->setText(appsettings->cvalue(mainWindow->cyclist, GC_TTBUSER, "").toString());
ttbPass = new QLineEdit(this);
ttbPass->setEchoMode(QLineEdit::Password);
ttbPass->setText(appsettings->cvalue(mainWindow->cyclist, GC_TTBPASS, "").toString());
wiURL = new QLineEdit(this);
wiURL->setText(appsettings->cvalue(mainWindow->cyclist, GC_WIURL, "http://wbsapi.withings.net/").toString());
@@ -613,6 +625,9 @@ CredentialsPage::CredentialsPage(QWidget *parent, MainWindow *mainWindow) : QScr
grid->addWidget(dvurlLabel, 27,0);
grid->addWidget(dvuserLabel, 28,0);
grid->addWidget(dvpassLabel, 29,0);
grid->addWidget(ttb, 30,0);
grid->addWidget(ttbuserLabel, 31,0);
grid->addWidget(ttbpassLabel, 32,0);
grid->addWidget(tpURL, 1, 1, 0);
grid->addWidget(tpUser, 2, 1, Qt::AlignLeft | Qt::AlignVCenter);
@@ -643,6 +658,9 @@ CredentialsPage::CredentialsPage(QWidget *parent, MainWindow *mainWindow) : QScr
grid->addWidget(dvUser, 27, 1, Qt::AlignLeft | Qt::AlignVCenter);
grid->addWidget(dvPass, 28, 1, Qt::AlignLeft | Qt::AlignVCenter);
grid->addWidget(ttbUser, 31, 1, Qt::AlignLeft | Qt::AlignVCenter);
grid->addWidget(ttbPass, 32, 1, Qt::AlignLeft | Qt::AlignVCenter);
grid->setColumnStretch(0,0);
grid->setColumnStretch(1,3);
@@ -669,6 +687,8 @@ CredentialsPage::saveClicked()
appsettings->setCValue(mainWindow->cyclist, GC_STRPASS, stravaPass->text());
appsettings->setCValue(mainWindow->cyclist, GC_RWGPSUSER, rideWithGPSUser->text());
appsettings->setCValue(mainWindow->cyclist, GC_RWGPSPASS, rideWithGPSPass->text());
appsettings->setCValue(mainWindow->cyclist, GC_TTBUSER, ttbUser->text());
appsettings->setCValue(mainWindow->cyclist, GC_TTBPASS, ttbPass->text());
appsettings->setCValue(mainWindow->cyclist, GC_TPTYPE, tpType->currentIndex());
appsettings->setCValue(mainWindow->cyclist, GC_TWURL, twitterURL->text());
appsettings->setCValue(mainWindow->cyclist, GC_WIURL, wiURL->text());

View File

@@ -163,6 +163,9 @@ class CredentialsPage : public QScrollArea
QLineEdit *rideWithGPSUser;
QLineEdit *rideWithGPSPass;
QLineEdit *ttbUser;
QLineEdit *ttbPass;
QLineEdit *wiURL; // url for withings
QLineEdit *wiUser;
QLineEdit *wiPass;

View File

@@ -94,6 +94,8 @@
#define GC_STRPASS "str/pass"
#define GC_RWGPSUSER "rwgps/user"
#define GC_RWGPSPASS "rwgps/pass"
#define GC_TTBUSER "ttb/user"
#define GC_TTBPASS "ttb/pass"
#define GC_WIURL "wi/url"
#define GC_WIUSER "wi/user"
#define GC_WIKEY "wi/key"

487
src/TtbDialog.cpp Normal file
View File

@@ -0,0 +1,487 @@
/*
* Copyright (c) 2012 Rainer Clasen <bj@zuto.de>
*
* 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 "TtbDialog.h"
#include "Settings.h"
#include <QHttp>
#include <QUrl>
#include <QHttpMultiPart>
#include <QXmlInputSource>
#include <QXmlSimpleReader>
#include <QXmlDefaultHandler>
#include "TimeUtils.h"
#include "Units.h"
#include <stdio.h>
// acccess to metrics
#include "TcxRideFile.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 TTB_URL( "http://trainingstagebuch.org" );
class TtbParser : public QXmlDefaultHandler
{
public:
friend class TtbDialog;
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;
};
TtbDialog::TtbDialog(MainWindow *mainWindow, RideItem *item) :
mainWindow(mainWindow),
ride( item ),
proMember( false )
{
assert(mainWindow);
assert(ride);
exerciseId = ride->ride()->getTag("TtbExercise", "");
setWindowTitle("Trainingstagebuch");
QVBoxLayout *mainLayout = new QVBoxLayout(this);
QVBoxLayout *progressLayout = new QVBoxLayout;
progressLabel = new QLabel("", this);
progressBar = new QProgressBar(this);
progressLayout->addWidget(progressBar);
progressLayout->addWidget(progressLabel);
QHBoxLayout *buttonLayout = new QHBoxLayout;
closeButton = new QPushButton(tr("&Cancel"), this);
buttonLayout->addStretch();
buttonLayout->addWidget(closeButton);
mainLayout->addLayout(progressLayout);
mainLayout->addLayout(buttonLayout);
connect(closeButton, SIGNAL(clicked()), this, SLOT(closeClicked()));
connect(&networkMgr, SIGNAL(finished(QNetworkReply*)), this,
SLOT(dispatchReply(QNetworkReply*)));
show();
uploadToTtb();
}
void
TtbDialog::uploadToTtb()
{
// TODO: check for user/pw
// TODO: ask for overwrite
progressBar->setValue(0);
requestSettings();
}
void
TtbDialog::closeClicked()
{
reject();
}
void
TtbDialog::requestSettings()
{
progressLabel->setText(tr("getting Settings..."));
currentRequest = reqSettings;
QString username = appsettings->cvalue(mainWindow->cyclist, GC_TTBUSER).toString();
QString password = appsettings->cvalue(mainWindow->cyclist, GC_TTBPASS).toString();
QUrl url( TTB_URL + "/settings/list" );
url.addQueryItem( "view", "xml" );
url.addQueryItem( "user", username );
url.addQueryItem( "pass", password );
QNetworkRequest request = QNetworkRequest(url);
request.setRawHeader( "Accept-Encoding", "identity" );
request.setRawHeader( "Accept", "application/xml" );
request.setRawHeader( "Accept-Charset", "utf-8" );
networkMgr.get( request );
progressBar->setValue(2);
}
void
TtbDialog::requestSession()
{
progressLabel->setText(tr("getting new Session..."));
currentRequest = reqSession;
QString username = appsettings->cvalue(mainWindow->cyclist, GC_TTBUSER).toString();
QString password = appsettings->cvalue(mainWindow->cyclist, GC_TTBPASS).toString();
QUrl url( TTB_URL + "/login/sso" );
url.addQueryItem( "view", "xml" );
url.addQueryItem( "user", username );
url.addQueryItem( "pass", password );
QNetworkRequest request = QNetworkRequest(url);
request.setRawHeader( "Accept-Encoding", "identity" );
request.setRawHeader( "Accept", "application/xml" );
request.setRawHeader( "Accept-Charset", "utf-8" );
networkMgr.get( request );
progressBar->setValue(6);
}
void
TtbDialog::requestUpload()
{
assert(sessionId.length() > 0 );
progressLabel->setText(tr("preparing upload ..."));
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 = mainWindow->home.absoluteFilePath(".ttbupload.tcx" );
QFile *uploadFile = new QFile( fname );
uploadFile->setParent(body);
TcxFileReader reader;
reader.writeRideFile( mainWindow, ride->ride(), *uploadFile );
progressBar->setValue(12);
int limit = proMember
? 8 * 1024 * 1024
: 4 * 1024 * 1024;
if( uploadFile->size() >= limit ){
progressLabel->setText(tr("temporary file too large for upload: %1 > %1 bytes")
.arg(uploadFile->size())
.arg(limit) );
closeButton->setText(tr("&Close"));
return;
}
QHttpPart filePart;
filePart.setHeader(QNetworkRequest::ContentTypeHeader,
QVariant("application/occtet-stream"));
filePart.setHeader(QNetworkRequest::ContentDispositionHeader,
QVariant("form-data; name=\"file\"; filename=\"gc-upload-ttb.tcx\""));
uploadFile->open(QIODevice::ReadOnly);
filePart.setBodyDevice(uploadFile);
body->append( filePart );
progressLabel->setText(tr("uploading ..."));
currentRequest = reqUpload;
QUrl url( TTB_URL + "/file/upload" );
url.addQueryItem( "view", "xml" );
url.addQueryItem( "sso", sessionId );
QNetworkRequest request = QNetworkRequest(url);
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 );
connect(reply, SIGNAL(uploadProgress(qint64,qint64)), this,
SLOT(uploadProgress(qint64,qint64)));
}
void
TtbDialog::uploadProgress(qint64 sent, qint64 total)
{
if( total < 0 )
return;
progressBar->setValue( 12.0 + sent / total * 88 );
}
void
TtbDialog::dispatchReply( QNetworkReply *reply )
{
if( reply->error() != QNetworkReply::NoError ){
progressLabel->setText( tr("request failed: ")
+ reply->errorString() );
closeButton->setText(tr("&Close"));
return;
}
QVariant status = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
if( ! status.isValid() || status.toInt() != 200 ){
QVariant msg( reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute ) );
progressLabel->setText( tr("request failed, Server response: %1 %2")
.arg(status.toInt())
.arg(msg.toString()) );
closeButton->setText(tr("&Close"));
return;
}
switch( currentRequest ){
case reqSettings:
finishSettings( reply );
break;
case reqSession:
finishSession( reply );
break;
case reqUpload:
finishUpload( reply );
break;
}
}
class TtbSettingsParser : public TtbParser
{
public:
friend class TtbDialog;
TtbSettingsParser() :
pro(false),
reFalse("\\s*(0|false|no|)\\s*",Qt::CaseInsensitive )
{};
bool endElement( const QString& a, const QString&b, const QString& qName )
{
if( qName == "pro" ){
pro = ! reFalse.exactMatch(cdata);
return true;
} else if( qName == "session" ){
session = cdata;
return true;
}
return TtbParser::endElement( a, b, qName );
};
protected:
bool pro;
QString session;
QRegExp reFalse;
};
void
TtbDialog::finishSettings(QNetworkReply *reply)
{
progressBar->setValue(4);
TtbSettingsParser handler;
QXmlInputSource source( reply );
QXmlSimpleReader reader;
reader.setContentHandler( &handler );
if( ! reader.parse( source ) ){
progressLabel->setText(tr("failed to parse Settings response: ")
+handler.errorString());
closeButton->setText(tr("&Close"));
return;
}
if( handler.error.length() > 0 ){
progressLabel->setText(tr("failed to get settings: ")
+handler.error );
closeButton->setText(tr("&Close"));
return;
}
sessionId = handler.session;
proMember = handler.pro;
if( sessionId.length() == 0 ){
requestSession();
} else {
requestUpload();
}
}
class TtbSessionParser : public TtbParser
{
public:
friend class TtbDialog;
bool endElement( const QString& a, const QString&b, const QString& qName )
{
if( qName == "session" ){
session = cdata;
return true;
}
return TtbParser::endElement( a, b, qName );
};
protected:
QString session;
};
void
TtbDialog::finishSession(QNetworkReply *reply)
{
progressBar->setValue(8);
TtbSessionParser handler;
QXmlInputSource source( reply );
QXmlSimpleReader reader;
reader.setContentHandler( &handler );
if( ! reader.parse( source ) ){
progressLabel->setText(tr("failed to parse Session response: ")
+handler.errorString());
closeButton->setText(tr("&Close"));
return;
}
if( handler.error.length() > 0 ){
progressLabel->setText(tr("failed to get new session: ")
+handler.error );
closeButton->setText(tr("&Close"));
return;
}
sessionId = handler.session;
if( sessionId.length() == 0 ){
progressLabel->setText(tr("got empty session"));
closeButton->setText(tr("&Close"));
return;
}
requestUpload();
}
class TtbUploadParser : public TtbParser
{
public:
friend class TtbDialog;
bool endElement( const QString& a, const QString&b, const QString& qName )
{
if( qName == "id" ){
id = cdata;
return true;
}
return TtbParser::endElement( a, b, qName );
};
protected:
QString id;
};
void
TtbDialog::finishUpload(QNetworkReply *reply)
{
progressBar->setValue(100);
closeButton->setText(tr("&Close"));
TtbUploadParser handler;
QXmlInputSource source( reply );
QXmlSimpleReader reader;
reader.setContentHandler( &handler );
if( ! reader.parse( source ) ){
progressLabel->setText(tr("failed to parse upload response: ")
+handler.errorString());
return;
}
if( handler.error.length() > 0 ){
progressLabel->setText(tr("failed to upload file: ")
+handler.error );
return;
}
exerciseId = handler.id;
if( exerciseId.length() == 0 ){
progressLabel->setText(tr("got empty exercise"));
return;
}
progressLabel->setText(tr("successfully uploaded as %1")
.arg(exerciseId));
ride->ride()->setTag("TtbExercise", exerciseId );
ride->setDirty(true);
accept();
}

80
src/TtbDialog.h Normal file
View File

@@ -0,0 +1,80 @@
/*
* Copyright (c) 2011 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
*/
#ifndef TTBDIALOG_H
#define TTBDIALOG_H
#include "GoldenCheetah.h"
#include <QObject>
#include <QtGui>
#include "MainWindow.h"
#include "RideItem.h"
class TtbDialog : public QDialog
{
Q_OBJECT
G_OBJECT
public:
TtbDialog(MainWindow *mainWindow, RideItem *item);
signals:
public slots:
void uploadToTtb();
private slots:
void uploadProgress(qint64 sent, qint64 total);
void dispatchReply( QNetworkReply *reply );
void requestSettings();
void finishSettings(QNetworkReply *reply);
void requestSession();
void finishSession(QNetworkReply *reply);
void requestUpload();
void finishUpload(QNetworkReply *reply);
void closeClicked();
private:
enum requestType {
reqSettings,
reqSession,
reqUpload,
};
MainWindow *mainWindow;
RideItem *ride;
QProgressBar *progressBar;
QLabel *progressLabel;
QPushButton *closeButton;
QNetworkAccessManager networkMgr;
requestType currentRequest;
bool proMember;
QString sessionId;
QString exerciseId;
};
#endif // TTBDIALOG_H

View File

@@ -345,6 +345,7 @@ HEADERS += \
TrainTool.h \
TreeMapWindow.h \
TreeMapPlot.h \
TtbDialog.h \
Units.h \
WeeklySummaryWindow.h \
WeeklyViewItemDelegate.h \
@@ -529,6 +530,7 @@ SOURCES += \
TrainTool.cpp \
TreeMapWindow.cpp \
TreeMapPlot.cpp \
TtbDialog.cpp \
TRIMPPoints.cpp \
WattsPerKilogram.cpp \
WithingsDownload.cpp \