From ea8df0e3ec833b8d129f3d34bb59da089f86b82a Mon Sep 17 00:00:00 2001 From: Mark Liversedge Date: Tue, 10 Apr 2018 14:04:25 +0100 Subject: [PATCH] Calendar Cloud Services Configuration .. configuring calendar access was lost when refactoring the cloud account config. .. we use the cloud service framework for the config steps (Oauth/user+pass etc). .. existing code for interacting with the service in CalendarDownload.cpp and CalDAV.cpp is left untouched for now since it isn't used in many places. --- src/Cloud/AddCloudWizard.cpp | 6 +-- src/Cloud/AddCloudWizard.h | 2 +- src/Cloud/CalDAV.h | 4 +- src/Cloud/CalDAVCloud.cpp | 83 ++++++++++++++++++++++++++++++++ src/Cloud/CalDAVCloud.h | 91 ++++++++++++++++++++++++++++++++++++ src/Cloud/OAuthDialog.cpp | 35 ++++++++++++-- src/Cloud/OAuthDialog.h | 1 + src/Gui/ConfigDialog.cpp | 2 +- src/src.pro | 20 ++++---- 9 files changed, 221 insertions(+), 23 deletions(-) create mode 100644 src/Cloud/CalDAVCloud.cpp create mode 100644 src/Cloud/CalDAVCloud.h diff --git a/src/Cloud/AddCloudWizard.cpp b/src/Cloud/AddCloudWizard.cpp index a63c9d97c..27230ad04 100644 --- a/src/Cloud/AddCloudWizard.cpp +++ b/src/Cloud/AddCloudWizard.cpp @@ -110,14 +110,12 @@ AddClass::AddClass(AddCloudWizard *parent) : QWizardPage(parent), wizard(parent) mapper->setMapping(p, CloudService::Measures); layout->addWidget(p); -#if 0 // DEPRECATING (?) - // Activities - p = new QCommandLinkButton(tr("Calendar"), tr("Sync planned workouts to WebDAV and CalDAV calendars.")); + // Calendar + p = new QCommandLinkButton(tr("Calendar"), tr("Sync planned workouts to WebDAV and CalDAV calendars like Google Calendar.")); p->setStyleSheet(QString("font-size: %1px;").arg(font.pointSizeF() * dpiXFactor)); connect(p, SIGNAL(clicked()), mapper, SLOT(map())); mapper->setMapping(p, CloudService::Calendar); layout->addWidget(p); -#endif setFinalPage(false); } diff --git a/src/Cloud/AddCloudWizard.h b/src/Cloud/AddCloudWizard.h index e7517f845..28dc5c409 100644 --- a/src/Cloud/AddCloudWizard.h +++ b/src/Cloud/AddCloudWizard.h @@ -137,7 +137,7 @@ class AddAuth : public QWizardPage public slots: void initializePage(); bool validatePage(); - int nextId() const { return wizard->cloudService->type() & CloudService::Measures ? 90 : (hasAthlete ? 25 : 30); } + int nextId() const { return wizard->cloudService->type() & (CloudService::Measures|CloudService::Calendar) ? 90 : (hasAthlete ? 25 : 30); } void updateServiceSettings(); void doAuth(); diff --git a/src/Cloud/CalDAV.h b/src/Cloud/CalDAV.h index e8c8835df..0799344ca 100644 --- a/src/Cloud/CalDAV.h +++ b/src/Cloud/CalDAV.h @@ -58,13 +58,13 @@ class CalDAV : public QObject Q_OBJECT G_OBJECT +public: enum action { Options, PropFind, Put, Get, Events, Report, None }; typedef enum action ActionType; - enum type { Standard, Google }; + enum type { Standard, Google, Webcal }; typedef enum type CalDAVType; -public: CalDAV(Context *context); public slots: diff --git a/src/Cloud/CalDAVCloud.cpp b/src/Cloud/CalDAVCloud.cpp new file mode 100644 index 000000000..8f7cc134a --- /dev/null +++ b/src/Cloud/CalDAVCloud.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2017 Mark Liversedge (liversedge@gmail.com) + * 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 "CalDAVCloud.h" +#include "Athlete.h" +#include "Settings.h" +#include "mvjson.h" +#include +#include +#include +#include +#include +#include + + +#ifndef CALDAV_DEBUG +#define CALDAV_DEBUG false +#endif +#ifdef Q_CC_MSVC +#define printd(fmt, ...) do { \ + if (CALDAV_DEBUG) { \ + printf("[%s:%d %s] " fmt , __FILE__, __LINE__, \ + __FUNCTION__, __VA_ARGS__); \ + fflush(stdout); \ + } \ +} while(0) +#else +#define printd(fmt, args...) \ + do { \ + if (CALDAV_DEBUG) { \ + printf("[%s:%d %s] " fmt , __FILE__, __LINE__, \ + __FUNCTION__, ##args); \ + fflush(stdout); \ + } \ + } while(0) +#endif + +CalDAVCloud::CalDAVCloud(Context *context, CalDAV::type variant) : CloudService(context), context(context), variant(variant) { + + // config + if (variant == CalDAV::Google) { + settings.insert(OAuthToken, GC_GOOGLE_CALENDAR_REFRESH_TOKEN); + } else if (variant == CalDAV::Webcal) { + settings.insert(URL, GC_WEBCAL_URL); + } else { + settings.insert(URL, GC_DVURL); + settings.insert(Username, GC_DVUSER); + settings.insert(Password, GC_DVPASS); + } +} + +CalDAVCloud::~CalDAVCloud() {} + +QImage CalDAVCloud::logo() const +{ + return QImage(":images/services/google.png"); +} + +static bool addCalDAVCloud() { + CloudServiceFactory::instance().addService(new CalDAVCloud(NULL, CalDAV::Google)); + CloudServiceFactory::instance().addService(new CalDAVCloud(NULL, CalDAV::Standard)); + CloudServiceFactory::instance().addService(new CalDAVCloud(NULL, CalDAV::Webcal)); + return true; +} + +static bool add = addCalDAVCloud(); + diff --git a/src/Cloud/CalDAVCloud.h b/src/Cloud/CalDAVCloud.h new file mode 100644 index 000000000..d8bc07082 --- /dev/null +++ b/src/Cloud/CalDAVCloud.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2018 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 + */ + + + +// +// Calendars are still downloaded and managed using the old code +// See CalDAV.cpp and CalendarDownload.cpp +// +// This is a stub service to use the configuration framework provided +// by the CloudService API to make it seamless for users +// +// As part of v4 planning development we may revisit this and refactor +// this class to support open/read/write etc +// + +#ifndef GC_CalDAVCloud_h +#define GC_CalDAVCloud_h + +#include "CloudService.h" +#include "CalDAV.h" + +class QNetworkReply; +class QNetworkAccessManager; + +class CalDAVCloud : public CloudService { + + Q_OBJECT + + public: + + int type() const { return CloudService::Calendar; } + int capabilities() const { return OAuth; } + + QString id() const { + switch(variant) { + case CalDAV::Google: return "Google Calendar"; + case CalDAV::Webcal: return "Web Calendar"; + default: + case CalDAV::Standard: return "CalDAV Calendar"; + } + } + + QString uiName() const { + switch(variant) { + case CalDAV::Google: return "Google Calendar"; + case CalDAV::Webcal: return "Web Calendar"; + default: + case CalDAV::Standard: return "CalDAV Calendar"; + } + } + + QString description() const { + switch(variant) { + case CalDAV::Google: return tr("Google Calendar using CalDAV protocol and authenticate using Google Account"); + case CalDAV::Webcal: return tr("Web Calendar using iCal format as a web resource"); + default: + case CalDAV::Standard: return tr("Generic CalDAV Calendar such as Apple iCloud calendar"); + } + } + + QImage logo() const; + + // we can be instantiated as a generic calDAV service or a Google + // CalDAV service. We don't have separate implementations, so at startup + // we register one of each with the cloud service factory + CalDAVCloud(Context *context, CalDAV::type variant); + CloudService *clone(Context *context) { return new CalDAVCloud(context, variant); } + ~CalDAVCloud(); + + private: + Context *context; + CalDAV::type variant; + +}; +#endif diff --git a/src/Cloud/OAuthDialog.cpp b/src/Cloud/OAuthDialog.cpp index 76adbbfdd..93d72da10 100644 --- a/src/Cloud/OAuthDialog.cpp +++ b/src/Cloud/OAuthDialog.cpp @@ -46,6 +46,7 @@ OAuthDialog::OAuthDialog(Context *context, OAuthSite site, CloudService *service if (service->id() == "Strava") site = this->site = STRAVA; if (service->id() == "Dropbox") site = this->site = DROPBOX; if (service->id() == "Cycling Analytics") site = this->site = CYCLING_ANALYTICS; + if (service->id() == "Google Calendar") site = this->site = GOOGLE_CALENDAR; if (service->id() == "Google Drive") site = this->site = GOOGLE_DRIVE; if (service->id() == "University of Kent") site = this->site = KENTUNI; if (service->id() == "Today's Plan") site = this->site = TODAYSPLAN; @@ -129,6 +130,14 @@ OAuthDialog::OAuthDialog(Context *context, OAuthSite site, CloudService *service #if QT_VERSION >= 0x050000 + } else if (site == GOOGLE_CALENDAR) { + // OAUTH 2.0 - Google flow for installed applications + urlstr = QString("https://accounts.google.com/o/oauth2/auth?"); + urlstr.append("scope=https://www.googleapis.com/auth/calendar&"); + urlstr.append("redirect_uri=urn:ietf:wg:oauth:2.0:oob&"); + urlstr.append("response_type=code&"); + urlstr.append("client_id=").append(GC_GOOGLE_CALENDAR_CLIENT_ID); + } else if (site == GOOGLE_DRIVE) { const QString scope = service->getSetting(GC_GOOGLE_DRIVE_AUTH_SCOPE, "drive.appdata").toString(); @@ -194,7 +203,7 @@ OAuthDialog::OAuthDialog(Context *context, OAuthSite site, CloudService *service // // STEP 1: LOGIN AND AUTHORISE THE APPLICATION // - if (site == DROPBOX || site == STRAVA || site == CYCLING_ANALYTICS || site == POLAR || site == SPORTTRACKS || site == GOOGLE_DRIVE || site == KENTUNI || site == TODAYSPLAN || site == WITHINGS) { + if (site == DROPBOX || site == STRAVA || site == CYCLING_ANALYTICS || site == POLAR || site == SPORTTRACKS || site == GOOGLE_CALENDAR || site == GOOGLE_DRIVE || site == KENTUNI || site == TODAYSPLAN || site == WITHINGS) { url = QUrl(urlstr); view->setUrl(url); @@ -363,7 +372,7 @@ void OAuthDialog::loadFinished(bool ok) { - if (site == GOOGLE_DRIVE || site == KENTUNI) { + if (site == GOOGLE_CALENDAR || site == GOOGLE_DRIVE || site == KENTUNI) { if (ok && url.toString().startsWith("https://accounts.google.com/o/oauth2/auth")) { @@ -380,9 +389,15 @@ OAuthDialog::loadFinished(bool ok) QUrl params; #endif QString urlstr = "https://www.googleapis.com/oauth2/v3/token?"; - params.addQueryItem("client_id", GC_GOOGLE_DRIVE_CLIENT_ID); + if (site == GOOGLE_CALENDAR) { + params.addQueryItem("client_id", GC_GOOGLE_CALENDAR_CLIENT_ID); + } else if (site == GOOGLE_DRIVE || site == KENTUNI) { + params.addQueryItem("client_id", GC_GOOGLE_DRIVE_CLIENT_ID); + } - if (site == GOOGLE_DRIVE || site == KENTUNI) { + if (site == GOOGLE_CALENDAR) { + params.addQueryItem("client_secret", GC_GOOGLE_CALENDAR_CLIENT_SECRET); + } else if (site == GOOGLE_DRIVE || site == KENTUNI) { params.addQueryItem("client_secret", GC_GOOGLE_DRIVE_CLIENT_SECRET); } @@ -481,7 +496,7 @@ OAuthDialog::networkRequestFinished(QNetworkReply *reply) // if we failed to extract then we have a big problem // google uses a refresh token so trap for them only - if (((site == GOOGLE_DRIVE || site == KENTUNI) && refresh_token == "") || access_token == "") { + if (((site == GOOGLE_CALENDAR || site == GOOGLE_DRIVE || site == KENTUNI) && refresh_token == "") || access_token == "") { // Something failed. // Only Google uses both refresh and access tokens. @@ -561,6 +576,16 @@ OAuthDialog::networkRequestFinished(QNetworkReply *reply) QMessageBox information(QMessageBox::Information, tr("Information"), info); information.exec(); + } else if (site == GOOGLE_CALENDAR) { + // remove the Google Page first + url = QUrl("http://www.goldencheetah.org"); + view->setUrl(url); + appsettings->setCValue(context->athlete->cyclist, GC_GOOGLE_CALENDAR_REFRESH_TOKEN, refresh_token); + QString info = QString(tr("Google Calendar authorization was successful.")); + QMessageBox information(QMessageBox::Information, + tr("Information"), info); + information.exec(); + } else if (site == KENTUNI) { service->setSetting(GC_UOK_GOOGLE_DRIVE_REFRESH_TOKEN, refresh_token); diff --git a/src/Cloud/OAuthDialog.h b/src/Cloud/OAuthDialog.h index 395740a6f..e6c945410 100644 --- a/src/Cloud/OAuthDialog.h +++ b/src/Cloud/OAuthDialog.h @@ -64,6 +64,7 @@ public: STRAVA, DROPBOX, CYCLING_ANALYTICS, + GOOGLE_CALENDAR, GOOGLE_DRIVE, SPORTTRACKS, TODAYSPLAN, diff --git a/src/Gui/ConfigDialog.cpp b/src/Gui/ConfigDialog.cpp index b13f61a28..769c148d6 100644 --- a/src/Gui/ConfigDialog.cpp +++ b/src/Gui/ConfigDialog.cpp @@ -49,7 +49,7 @@ ConfigDialog::ConfigDialog(QDir _home, Context *context) : QToolBar *head = addToolBar(tr("Options")); head->setMovable(false); // oops! - setMinimumSize(700 *dpiXFactor,650 *dpiYFactor); //changed for hidpi sizing + setMinimumSize(800 *dpiXFactor,650 *dpiYFactor); //changed for hidpi sizing #endif // center diff --git a/src/src.pro b/src/src.pro index ee0e1ce82..8b48c08c7 100644 --- a/src/src.pro +++ b/src/src.pro @@ -720,11 +720,11 @@ HEADERS += Charts/Aerolab.h Charts/AerolabWindow.h Charts/AllPlot.h Charts/AllPl } # cloud services -HEADERS += Cloud/BodyMeasuresDownload.h Cloud/CalendarDownload.h Cloud/CloudService.h Cloud/LocalFileStore.h \ - Cloud/OAuthDialog.h Cloud/OAuthManager.h Cloud/TodaysPlanBodyMeasures.h Cloud/WithingsDownload.h \ - Cloud/Strava.h Cloud/CyclingAnalytics.h Cloud/RideWithGPS.h Cloud/TrainingsTageBuch.h \ - Cloud/Selfloops.h Cloud/Velohero.h Cloud/SportsPlusHealth.h Cloud/AddCloudWizard.h \ - Cloud/Withings.h Cloud/HrvMeasuresDownload.h Cloud/Xert.h +HEADERS += Cloud/BodyMeasuresDownload.h Cloud/CalDAVCloud.h Cloud/CalendarDownload.h Cloud/CloudService.h \ + Cloud/LocalFileStore.h Cloud/OAuthDialog.h Cloud/OAuthManager.h Cloud/TodaysPlanBodyMeasures.h \ + Cloud/WithingsDownload.h Cloud/Strava.h Cloud/CyclingAnalytics.h Cloud/RideWithGPS.h \ + Cloud/TrainingsTageBuch.h Cloud/Selfloops.h Cloud/Velohero.h Cloud/SportsPlusHealth.h \ + Cloud/AddCloudWizard.h Cloud/Withings.h Cloud/HrvMeasuresDownload.h Cloud/Xert.h # core data HEADERS += Core/Athlete.h Core/Context.h Core/DataFilter.h Core/FreeSearch.h Core/GcCalendarModel.h Core/GcUpgrade.h \ @@ -813,11 +813,11 @@ SOURCES += Charts/Aerolab.cpp Charts/AerolabWindow.cpp Charts/AllPlot.cpp Charts } ## Cloud Services / Web resources -SOURCES += Cloud/BodyMeasuresDownload.cpp Cloud/CalendarDownload.cpp Cloud/CloudService.cpp Cloud/LocalFileStore.cpp \ - Cloud/OAuthDialog.cpp Cloud/OAuthManager.cpp Cloud/TodaysPlanBodyMeasures.cpp Cloud/WithingsDownload.cpp \ - Cloud/Strava.cpp Cloud/CyclingAnalytics.cpp Cloud/RideWithGPS.cpp Cloud/TrainingsTageBuch.cpp \ - Cloud/Selfloops.cpp Cloud/Velohero.cpp Cloud/SportsPlusHealth.cpp Cloud/AddCloudWizard.cpp \ - Cloud/Withings.cpp Cloud/HrvMeasuresDownload.cpp Cloud/Xert.cpp +SOURCES += Cloud/BodyMeasuresDownload.cpp Cloud/CalDAVCloud.cpp Cloud/CalendarDownload.cpp Cloud/CloudService.cpp \ + Cloud/LocalFileStore.cpp Cloud/OAuthDialog.cpp Cloud/OAuthManager.cpp Cloud/TodaysPlanBodyMeasures.cpp \ + Cloud/WithingsDownload.cpp Cloud/Strava.cpp Cloud/CyclingAnalytics.cpp Cloud/RideWithGPS.cpp \ + Cloud/TrainingsTageBuch.cpp Cloud/Selfloops.cpp Cloud/Velohero.cpp Cloud/SportsPlusHealth.cpp \ + Cloud/AddCloudWizard.cpp Cloud/Withings.cpp Cloud/HrvMeasuresDownload.cpp Cloud/Xert.cpp ## Core Data Structures SOURCES += Core/Athlete.cpp Core/Context.cpp Core/DataFilter.cpp Core/FreeSearch.cpp Core/GcUpgrade.cpp Core/IdleTimer.cpp \