Skip to content

Commit

Permalink
Sync Events with Calendar if remote is configured
Browse files Browse the repository at this point in the history
upload new Events to remote calendar and download to update the local calendar
Updates or deletes are not implemented
Fixes #1979
  • Loading branch information
amtriathlon committed Apr 10, 2018
1 parent c667f96 commit 5e43ec9
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 58 deletions.
181 changes: 131 additions & 50 deletions src/Cloud/CalDAV.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,29 @@ CalDAV::CalDAV(Context *context) : context(context), mode(None)
}


void CalDAV::getConfig() {
bool
CalDAV::getConfig() {

int t = appsettings->cvalue(context->athlete->cyclist, GC_DVCALDAVTYPE, "0").toInt();
if (t == 0) {
calDavType = Standard;
} else {
calDavType = Google;
};

if (calDavType == Standard) {
url = appsettings->cvalue(context->athlete->cyclist, GC_DVURL, "").toString();
} else { // calDavType = GOOGLE
calID = appsettings->cvalue(context->athlete->cyclist, GC_DVGOOGLE_CALID, "").toString();
url = googleCalDAVurl.arg(calID);
}

// check if we have an useful URL (not space and not the Google Default without CalID
if ((url == "" && calDavType == Standard) || (calID == "" && calDavType == Google)) {
return false;
}

return true;
}


Expand All @@ -56,10 +71,17 @@ void CalDAV::getConfig() {
bool
CalDAV::download(bool ignoreErrors)
{
getConfig();
ignoreDownloadErrors = ignoreErrors;
mode = Events;

if (!getConfig()) {
if (!ignoreDownloadErrors) {
QMessageBox::warning(context->mainWindow, tr("Missing Preferences"), tr("CalID or CalDAV Url is missing in preferences"));
}
mode = None;
return false;
}

if (calDavType == Standard) {
return doDownload();
} else { // calDavType = GOOGLE
Expand All @@ -73,23 +95,6 @@ bool
CalDAV::doDownload()
{

QString url; QString calID;
if (calDavType == Standard) {
url = appsettings->cvalue(context->athlete->cyclist, GC_DVURL, "").toString();
} else { // calDavType = GOOGLE
calID = appsettings->cvalue(context->athlete->cyclist, GC_DVGOOGLE_CALID, "").toString();
url = googleCalDAVurl.arg(calID);
}

// check if we have an useful URL (not space and not the Google Default without CalID
if ((url == "" && calDavType == Standard) || (calID == "" && calDavType == Google)) {
if (!ignoreDownloadErrors) {
QMessageBox::warning(context->mainWindow, tr("Missing Preferences"), tr("CalID or CalDAV Url is missing in preferences"));
}
mode = None;
return false;
}

QNetworkRequest request = QNetworkRequest(QUrl(url));

QByteArray *queryText = new QByteArray( "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
Expand Down Expand Up @@ -323,6 +328,66 @@ icalcomponent *createEvent(RideItem *rideItem)
return root;
}

// utility function to create a VCALENDAR from a single SeasonEvent
static
icalcomponent *createEvent(SeasonEvent *seasonEvent)
{
// calendar
icalcomponent *root = icalcomponent_new(ICAL_VCALENDAR_COMPONENT);

// calendar version
icalproperty *version = icalproperty_new_version("2.0");
icalcomponent_add_property(root, version);


icalcomponent *event = icalcomponent_new(ICAL_VEVENT_COMPONENT);

//
// Unique ID
//
QString id = seasonEvent->id;
if (id == "") {
id = QUuid::createUuid().toString() + "@" + "goldencheetah.org";
seasonEvent->id = id;
}
icalproperty *uid = icalproperty_new_uid(id.toLatin1());
icalcomponent_add_property(event, uid);

//
// START DATE
//
struct icaltimetype atime;
atime.year = seasonEvent->date.year();
atime.month = seasonEvent->date.month();
atime.day = seasonEvent->date.day();
atime.hour = 0;
atime.minute = 0;
atime.second = 0;
//atime.is_utc = 1; // this is UTC is_utc is redundant but kept for completeness
atime.is_date = 1; // this is a date
atime.is_daylight = 0; // no daylight savings - its UTC
atime.zone = icaltimezone_get_utc_timezone(); // set UTC timezone
icalproperty *dtstart = icalproperty_new_dtstart(atime);
icalcomponent_add_property(event, dtstart);

//
// PRIORITY
//
if (seasonEvent->priority > 0) {
icalproperty* priority = icalproperty_new_priority(seasonEvent->priority);
icalcomponent_add_property(event, priority);
}


// set title & description
icalcomponent_set_summary(event, seasonEvent->name.toLatin1());
icalcomponent_set_description(event, seasonEvent->description.toLatin1());

// put the event into root
icalcomponent_add_component(root, event);
return root;
}

// extract <calendar-data> entries and concatenate
// into a single string. This is from a query response
// where the VEVENTS are embedded within an XML document
Expand Down Expand Up @@ -382,46 +447,62 @@ static QString extractComponents(QString document)
bool
CalDAV::upload(RideItem *rideItem)
{
getConfig();
// is this a valid ride?
if (!rideItem || !rideItem->ride()) return false;

fileName = rideItem->fileName;
// create the ICal event
icalcomponent *vcard = createEvent(rideItem);
QByteArray vcardtext(icalcomponent_as_ical_string(vcard));
icalcomponent_free(vcard);

return upload(vcardtext);
}

//
// PUT a SeasonEvent
//

bool
CalDAV::upload(SeasonEvent *seasonEvent)
{
fileName = seasonEvent->id;
// create the ICal event
icalcomponent *vcard = createEvent(seasonEvent);
QByteArray vcardtxt(icalcomponent_as_ical_string(vcard));
icalcomponent_free(vcard);

return upload(vcardtxt);
}

bool
CalDAV::upload(QByteArray vcardtxt)
{
if (!getConfig()) {
QMessageBox::warning(context->mainWindow, tr("Missing Preferences"), tr("CalID or CalDAV Url is missing in preferences"));
return false;
}
mode = Put;
vcardtext = vcardtxt;
if (calDavType == Standard) {
return doUpload(rideItem);
return doUpload();
} else { // calDavType = GOOGLE
// after having the token the function defined in "mode" will be executed
itemForUpload = rideItem;
requestGoogleAccessTokenToExecute();
}
return true;
}


bool
CalDAV::doUpload(RideItem *rideItem)
CalDAV::doUpload()
{
// is this a valid ride?
if (!rideItem || !rideItem->ride()) return false;

QString url; QString calID;
if (calDavType == Standard) {
url = appsettings->cvalue(context->athlete->cyclist, GC_DVURL, "").toString();
} else { // calDavType = GOOGLE
calID = appsettings->cvalue(context->athlete->cyclist, GC_DVGOOGLE_CALID, "").toString();
url = googleCalDAVurl.arg(calID);
}

// check if we have an useful URL (not space and not the Google Default without CalID
if ((url == "" && calDavType == Standard) || (calID == "" && calDavType == Google)) {
QMessageBox::warning(context->mainWindow, tr("Missing Preferences"), tr("CalID or CalDAV Url is missing in preferences"));
mode = None;
return false;
}

// if URL does not end with "/" - just add it (for convenience)
if (!url.endsWith("/")) {
url += "/";
}
// lets upload to calendar
url += rideItem->fileName;
url += fileName;
url += ".ics";

// form the request
Expand All @@ -432,11 +513,6 @@ CalDAV::doUpload(RideItem *rideItem)
request.setRawHeader("Authorization", "Bearer "+googleCalendarAccessToken );
}

// create the ICal event
icalcomponent *vcard = createEvent(rideItem);
QByteArray vcardtext(icalcomponent_as_ical_string(vcard));
icalcomponent_free(vcard);

mode = Put;
QNetworkReply *reply = nam->put(request, vcardtext);
if (reply->error() != QNetworkReply::NoError) {
Expand Down Expand Up @@ -467,15 +543,20 @@ CalDAV::requestReply(QNetworkReply *reply)
case Report:
case Events:
context->athlete->rideCalendar->refreshRemote(extractComponents(response));
mode = None;
break;
default:
case Options:
case PropFind:
case Put:
//nothing at the moment
mode = None;
break;
case Put:
//refresh local calendar
mode = None;
download(false);
break;
}
mode = None;
}

//
Expand Down Expand Up @@ -591,7 +672,7 @@ CalDAV::googleNetworkRequestFinished(QNetworkReply* reply) {
}

// now we have a token and can do the requested jobs
if (mode == Put) doUpload(itemForUpload);
if (mode == Put) doUpload();
if (mode == Events) doDownload();
}

17 changes: 13 additions & 4 deletions src/Cloud/CalDAV.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
#include "RideFile.h"
#include "JsonRideFile.h"

// SeasonEvent
#include "Season.h"

// create a UUID
#include <QUuid>

Expand Down Expand Up @@ -84,6 +87,10 @@ public slots:

// Upload ride as a VEVENT
bool upload(RideItem *rideItem);
// Upload SeasonEvent as a VEVENT
bool upload(SeasonEvent *seasonEvent);
// Upload a VEVENT
bool upload(QByteArray vcardtext);

// Catch NAM signals ...
void requestReply(QNetworkReply *reply);
Expand All @@ -96,24 +103,26 @@ public slots:
// enable aynchronous up/download for Google
// since access token is temporarily valid only, it needs refresh before access to Google CALDAV
bool doDownload();
bool doUpload(RideItem *rideItem);
bool doUpload();

void getConfig();
bool getConfig();

private:

Context *context;
QNetworkAccessManager *nam;
ActionType mode;
CalDAVType calDavType;
QString url; QString calID;
QString googleCalDAVurl;
bool ignoreDownloadErrors;

// specific part to get Google Access Token
QNetworkAccessManager *googleNetworkAccessManager;
void requestGoogleAccessTokenToExecute();
QByteArray googleCalendarAccessToken;
RideItem *itemForUpload;
ActionType mode;
QString fileName;
QByteArray vcardtext;

};
#endif
3 changes: 2 additions & 1 deletion src/Core/Season.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,13 @@ class SeasonEvent
public:
static QStringList priorityList();

SeasonEvent(QString name, QDate date, int priority=0, QString description="") : name(name), date(date), priority(priority), description(description) {}
SeasonEvent(QString name, QDate date, int priority=0, QString description="", QString id="") : name(name), date(date), priority(priority), description(description), id(id) {}

QString name;
QDate date;
int priority;
QString description;
QString id; // unique id
};

class Season
Expand Down
8 changes: 5 additions & 3 deletions src/Core/SeasonParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ bool SeasonParser::endElement( const QString&, const QString&, const QString &qN
season.setSeed(buffer.trimmed().toInt());
} else if (qName == "event") {

season.events.append(SeasonEvent(Utils::unprotect(buffer), seasonDateToDate(dateString), priorityString.toInt(), eventDescription));
season.events.append(SeasonEvent(Utils::unprotect(buffer), seasonDateToDate(dateString), priorityString.toInt(), eventDescription, eventId));

} else if(qName == "season") {

Expand Down Expand Up @@ -120,6 +120,7 @@ bool SeasonParser::startElement( const QString&, const QString&, const QString &
if (attrs.qName(i) == "date") dateString=attrs.value(i);
else if (attrs.qName(i) == "priority") priorityString=attrs.value(i);
else if (attrs.qName(i) == "description") eventDescription = Utils::unprotect(attrs.value(i));
else if (attrs.qName(i) == "id") eventId = attrs.value(i);
}
}

Expand Down Expand Up @@ -236,11 +237,12 @@ SeasonParser::serialize(QString filename, QList<Season>Seasons)

foreach(SeasonEvent x, season.events) {

out<<QString("\t\t<event date=\"%1\" priority=\"%3\" description=\"%4\">\"%2\"</event>\n")
out<<QString("\t\t<event date=\"%1\" priority=\"%3\" description=\"%4\" id=\"%5\">\"%2\"</event>\n")
.arg(x.date.toString("yyyy-MM-dd"))
.arg(Utils::xmlprotect(x.name))
.arg(x.priority)
.arg(Utils::xmlprotect(x.description));
.arg(Utils::xmlprotect(x.description))
.arg(x.id);

}
out <<QString("\t</season>\n");
Expand Down
1 change: 1 addition & 0 deletions src/Core/SeasonParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,6 @@ class SeasonParser : public QXmlDefaultHandler
QString dateString;
QString priorityString;
QString eventDescription;
QString eventId;
};
#endif //SeasonParser
7 changes: 7 additions & 0 deletions src/Gui/LTMSidebar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#include "SeasonParser.h"
#include <QXmlInputSource>
#include <QXmlSimpleReader>
#include "CalDAV.h" // upload Events to remote calendar

// named searchs
#include "FreeSearch.h"
Expand Down Expand Up @@ -1277,6 +1278,12 @@ LTMSidebar::addEvent()
if (dialog.exec()) {

active = true;

// upload to remote calendar if configured
if (context->athlete->davCalendar->getConfig())
if (!context->athlete->davCalendar->upload(&myevent))
QMessageBox::warning(this, tr("Add Event"), tr("The new event could not be uploaded to your remote calendar."));

seasons->seasons[seasonindex].events.append(myevent);

QTreeWidgetItem *add = new QTreeWidgetItem(allEvents);
Expand Down

0 comments on commit 5e43ec9

Please sign in to comment.