/* * 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 #include #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 > #else #include #endif #include 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&) { 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 & )), this, SLOT(onSslErrors(QNetworkReply*, const QList & ))); 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 " <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 & )), this, SLOT(onSslErrors(QNetworkReply*, const QList & ))); connect(&networkMgr, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestUploadRideWithGPSFinished(QNetworkReply*))); connect(&networkMgr, SIGNAL(finished(QNetworkReply *)), &eventLoop, SLOT(quit())); QString out, data; QVector 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 & )), this, SLOT(onSslErrors(QNetworkReply*, const QList & ))); 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 & )), this, SLOT(onSslErrors(QNetworkReply*, const QList & ))); 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 & )), this, SLOT(onSslErrors(QNetworkReply*, const QList & ))); 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