PolarFlow Cloud Service - 1 of 6

.. add template code and definition. This is not a functional
   update, it is to get the basics in place and run through
   CI to check for cross-platform issues.

.. part 2 to implement oauth

.. part 3 to implement configuration

.. part 4 to implement readdir()

.. part 5 to implement readFile()

.. part 6 to update CloudSyncDialog for download only services
This commit is contained in:
Mark Liversedge
2017-04-08 09:47:21 +01:00
parent 2197cfebb1
commit 514f24535d
8 changed files with 322 additions and 0 deletions

226
src/Cloud/PolarFlow.cpp Normal file
View File

@@ -0,0 +1,226 @@
/*
* Copyright (c) 2016 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 "PolarFlow.h"
#include "Athlete.h"
#include "Settings.h"
#include <QByteArray>
#include <QHttpMultiPart>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonValue>
#ifndef POLARFLOW_DEBUG
// TODO(gille): This should be a command line flag.
#define POLARFLOW_DEBUG false
#endif
#ifdef Q_CC_MSVC
#define printd(fmt, ...) do { \
if (POLARFLOW_DEBUG) { \
printf("[%s:%d %s] " fmt , __FILE__, __LINE__, \
__FUNCTION__, __VA_ARGS__); \
fflush(stdout); \
} \
} while(0)
#else
#define printd(fmt, args...) \
do { \
if (POLARFLOW_DEBUG) { \
printf("[%s:%d %s] " fmt , __FILE__, __LINE__, \
__FUNCTION__, ##args); \
fflush(stdout); \
} \
} while(0)
#endif
PolarFlow::PolarFlow(Context *context) : CloudService(context), context(context), root_(NULL) {
if (context) {
nam = new QNetworkAccessManager(this);
connect(nam, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> & )), this, SLOT(onSslErrors(QNetworkReply*, const QList<QSslError> & )));
}
uploadCompression = gzip; // gzip
downloadCompression = none;
useMetric = true; // distance and duration metadata
// config
settings.insert(OAuthToken, GC_POLARFLOW_TOKEN);
}
PolarFlow::~PolarFlow() {
if (context) delete nam;
}
void
PolarFlow::onSslErrors(QNetworkReply *reply, const QList<QSslError>&)
{
reply->ignoreSslErrors();
}
// open by connecting and getting a basic list of folders available
bool
PolarFlow::open(QStringList &errors)
{
printd("PolarFlow::open\n");
// do we have a token
QString token = getSetting(GC_POLARFLOW_TOKEN, "").toString();
if (token == "") {
errors << "You must authorise with PolarFlow first";
return false;
}
// use the configed URL
QString url = QString("%1/rest/users/delegates/users")
.arg(getSetting(GC_POLARFLOW_URL, "https://whats.todaysplan.com.au").toString());
printd("URL used: %s\n", url.toStdString().c_str());
// request using the bearer token
QNetworkRequest request(url);
request.setRawHeader("Authorization", (QString("Bearer %1").arg(token)).toLatin1());
QNetworkReply *reply = nam->get(request);
// blocking request
QEventLoop loop;
connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
if (reply->error() != QNetworkReply::NoError) {
qDebug() << "error" << reply->errorString();
errors << tr("Network Problem reading PolarFlow data");
return false;
}
// did we get a good response ?
QByteArray r = reply->readAll();
printd("response: %s\n", r.toStdString().c_str());
QJsonParseError parseError;
QJsonDocument document = QJsonDocument::fromJson(r, &parseError);
// if path was returned all is good, lets set root
if (parseError.error == QJsonParseError::NoError) {
printd("NoError");
// we have a root
root_ = newCloudServiceEntry();
// path name
root_->name = "/";
root_->isDir = true;
root_->size = 0;
} else {
errors << tr("problem parsing PolarFlow data");
}
// ok so far ?
if (errors.count()) return false;
return true;
}
bool
PolarFlow::close()
{
printd("PolarFlow::close\n");
// nothing to do for now
return true;
}
QList<CloudServiceEntry*>
PolarFlow::readdir(QString path, QStringList &errors, QDateTime from, QDateTime to)
{
printd("PolarFlow::readdir(%s)\n", path.toStdString().c_str());
QList<CloudServiceEntry*> returning;
// do we have a token
QString token = getSetting(GC_POLARFLOW_TOKEN, "").toString();
if (token == "") {
errors << tr("You must authorise with Today's Plan first");
return returning;
}
// all good ?
return returning;
}
// read a file at location (relative to home) into passed array
bool
PolarFlow::readFile(QByteArray *data, QString remotename, QString remoteid)
{
printd("PolarFlow::readFile(%s)\n", remotename.toStdString().c_str());
// this must be performed asyncronously and call made
// to notifyReadComplete(QByteArray &data, QString remotename, QString message) when done
// do we have a token ?
QString token = getSetting(GC_POLARFLOW_TOKEN, "").toString();
if (token == "") return false;
// lets connect and get basic info on the root directory
QString url = QString("%1/rest/files/download/%2")
.arg(getSetting(GC_POLARFLOW_URL, "https://whats.todaysplan.com.au").toString())
.arg(remoteid);
printd("url:%s\n", url.toStdString().c_str());
// request using the bearer token
QNetworkRequest request(url);
request.setRawHeader("Authorization", (QString("Bearer %1").arg(token)).toLatin1());
// put the file
QNetworkReply *reply = nam->get(request);
// remember
mapReply(reply,remotename);
buffers.insert(reply,data);
// catch finished signal
connect(reply, SIGNAL(finished()), this, SLOT(readFileCompleted()));
connect(reply, SIGNAL(readyRead()), this, SLOT(readyRead()));
return true;
}
void
PolarFlow::readyRead()
{
QNetworkReply *reply = static_cast<QNetworkReply*>(QObject::sender());
buffers.value(reply)->append(reply->readAll());
}
void
PolarFlow::readFileCompleted()
{
printd("PolarFlow::readFileCompleted\n");
QNetworkReply *reply = static_cast<QNetworkReply*>(QObject::sender());
printd("reply:%s\n", buffers.value(reply)->toStdString().c_str());
notifyReadComplete(buffers.value(reply), replyName(reply), tr("Completed."));
}
static bool addPolarFlow() {
CloudServiceFactory::instance().addService(new PolarFlow(NULL));
return true;
}
static bool add = addPolarFlow();

83
src/Cloud/PolarFlow.h Normal file
View File

@@ -0,0 +1,83 @@
/*
* Copyright (c) 2017 Mark Liversedge (liversedge@gmail.com)
*
* 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 GC_PolarFlow_h
#define GC_PolarFlow_h
#include "CloudService.h"
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QImage>
// OAuth domain (requires client id and secret)
#define GC_POLARFLOW_OAUTH_URL "https://https://flow.polar.com/"
// Access token (requires OAuth token)
#define GC_POLARFLOW_TOKEN_URL "https://https://polarremote.com/v2/oauth2/token"
// Request URL (requires access token)
#define GC_POLARFLOW_URL "https://www.polaraccesslink.com/"
class PolarFlow : public CloudService {
Q_OBJECT
public:
PolarFlow(Context *context);
CloudService *clone(Context *context) { return new PolarFlow(context); }
~PolarFlow();
QString name() const { return (tr("PolarFlow")); }
QString description() const { return (tr("Download from the popular Polar website.")); }
QImage logo() const { return QImage(":images/services/polarflow.png"); }
// polar
virtual int capabilities() const { return OAuth | Download | Query; }
// open/connect and close/disconnect
bool open(QStringList &errors);
bool close();
// read a file
bool readFile(QByteArray *data, QString remotename, QString remoteid);
// dirent style api
CloudServiceEntry *root() { return root_; }
QList<CloudServiceEntry*> readdir(QString path, QStringList &errors, QDateTime from, QDateTime to);
public slots:
// getting data
void readyRead(); // a readFile operation has work to do
void readFileCompleted();
private:
Context *context;
QNetworkAccessManager *nam;
QNetworkReply *reply;
CloudServiceEntry *root_;
QMap<QNetworkReply*, QByteArray*> buffers;
QString userId;
private slots:
void onSslErrors(QNetworkReply *reply, const QList<QSslError>&error);
};
#endif

View File

@@ -93,4 +93,7 @@
#define GC_CLOUD_DB_APP_NAME "__GC_CLOUD_DB_APP_NAME__"
#endif
#ifndef GC_POLARFLOW_CLIENT_SECRET
#define GC_POLARFLOW_CLIENT_SECRET "__GC_POLARFLOW_CLIENT_SECRET__"
#endif
#endif

View File

@@ -55,6 +55,11 @@
#define GC_DROPBOX_CLIENT_ID "753fbblhri06ah3"
#endif
// PolarFlow client ID
#ifndef GC_POLARFLOW_CLIENT_ID
#define GC_POLARFLOW_CLIENT_ID "Not defined"
#endif
/*
* GoldenCheetah Properties are stored in different locations, depending on the prefix defined in the property name. The following different prefixes are supported
* <system>
@@ -314,6 +319,8 @@
#define GC_SIXCYCLE_USER "<athlete-private>sixcycle_user"
#define GC_SIXCYCLE_PASS "<athlete-private>sixcycle_pass"
#define GC_SIXCYCLE_URL "<athlete-private>sixcycle_url"
// Polar Flow
#define GC_POLARFLOW_TOKEN "<athlete-private>polarflow_token"
// --------------------------------------------------------------------------------
#include <QSettings>

View File

@@ -176,5 +176,6 @@
<file>images/services/todaysplan.png</file>
<file>images/services/trainingstagebuch.png</file>
<file>images/services/velohero.png</file>
<file>images/services/polarflow.png</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@@ -623,6 +623,8 @@ greaterThan(QT_MAJOR_VERSION, 4) {
greaterThan(QT_MINOR_VERSION, 3) {
SOURCES += Cloud/SixCycle.cpp
HEADERS += Cloud/SixCycle.h
SOURCES += Cloud/PolarFlow.cpp
HEADERS += Cloud/PolarFlow.h
SOURCES += Cloud/TodaysPlan.cpp
HEADERS += Cloud/TodaysPlan.h
}