diff --git a/CMakeLists.txt b/CMakeLists.txt index d2a883c..5d4ea01 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,97 +1,95 @@ cmake_minimum_required(VERSION 3.0) set(PIM_VERSION "5.8.40") set(KGAPI_LIB_VERSION ${PIM_VERSION}) project(kgapi VERSION ${KGAPI_LIB_VERSION}) # ECM setup set(KF5_VERSION "5.44.0") find_package(ECM ${KF5_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) include(GenerateExportHeader) include(ECMGenerateHeaders) include(ECMGeneratePriFile) include(CMakePackageConfigHelpers) include(ECMPoQmTools) include(ECMSetupVersion) include(FeatureSummary) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) set(KCALENDARCORE_LIB_VERSION "5.7.80") set(KCONTACTS_LIB_VERSION "5.7.80") ecm_setup_version(PROJECT VARIABLE_PREFIX KGAPI VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kgapi_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KPimGAPIConfigVersion.cmake" SOVERSION 5 ) ############## Find Packages ############## set(REQUIRED_QT_VERSION "5.8.0") find_package(Qt5 ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Core Network Widgets WebEngineWidgets Xml ) find_package(KF5 ${KF5_VERSION} REQUIRED COMPONENTS KIO WindowSystem ) find_package(KF5CalendarCore ${KCALENDARCORE_LIB_VERSION} CONFIG REQUIRED) find_package(KF5Contacts ${KCONTACTS_LIB_VERSION} CONFIG REQUIRED) -add_definitions( -DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT ) -add_definitions("-DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII") +add_definitions(-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT) add_definitions(-DQT_NO_URL_CAST_FROM_STRING) -# TODO FIX! -add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x000000) +add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050800) ############## Targets ############## add_subdirectory(src) add_subdirectory(examples) if (BUILD_TESTING) add_subdirectory(autotests) endif() ############## CMake Config Files ############## set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KPimGAPI") set(KGAPI_KF5_COMPAT FALSE) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KPimGAPIConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KPimGAPIConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KPimGAPIConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KPimGAPIConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KPimGAPITargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KPimGAPITargets.cmake NAMESPACE KPim:: ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/kgapi_version.h" DESTINATION "${KDE_INSTALL_INCLUDEDIR}/KPim" COMPONENT Devel ) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/blogger/bloggerservice.cpp b/src/blogger/bloggerservice.cpp index ec2aa66..b45f4e8 100644 --- a/src/blogger/bloggerservice.cpp +++ b/src/blogger/bloggerservice.cpp @@ -1,180 +1,184 @@ /* * Copyright (C) 2014 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "bloggerservice.h" +#include + inline QUrl operator%(const QUrl &url, const QString &path) { return QUrl(url.toString() % QLatin1Char('/') % path); } namespace KGAPI2 { namespace BloggerService { namespace Private { static const QUrl GoogleApisUrl(QStringLiteral("https://www.googleapis.com")); auto commentBasePath(const QString &blogId, const QString &postId = QString(), const QString &commentId = QString()) -> QString { const auto post = !postId.isEmpty() ? (QLatin1String("/posts/") % postId) : QString(); const auto comment = !commentId.isEmpty() ? (QLatin1Char('/') % commentId) : QString(); const auto path = QLatin1String("blogger/v3/blogs/") % blogId % post % QLatin1String("/comments") % comment; return path; } auto pageBasePath(const QString &blogId, const QString &pageId = QString()) -> QString { const auto page = !pageId.isEmpty() ? (QLatin1Char('/') % pageId) : QString(); const auto path = QLatin1String("blogger/v3/blogs/") % blogId % QLatin1String("/pages") % page; return path; } auto postBasePath(const QString &blogId, const QString &postId = QString()) -> QString { const auto post = !postId.isEmpty() ? (QLatin1Char('/') % postId) : QString(); const auto path = QLatin1String("blogger/v3/blogs/") % blogId % QLatin1String("/posts") % post; return path; } } // namespace Private } // namespace BloggerService } // namespace KGAPI2 using namespace KGAPI2; using namespace KGAPI2::BloggerService::Private; QUrl BloggerService::fetchBlogByBlogIdUrl(const QString &blogId) { return GoogleApisUrl % QStringLiteral("/blogger/v3/blogs/") % blogId; } QUrl BloggerService::fetchBlogByBlogUrlUrl(const QString &blogUrl) { QUrl url = GoogleApisUrl % QStringLiteral("/blogger/v3/blogs/byurl"); - url.addQueryItem(QStringLiteral("url"), blogUrl); + QUrlQuery query(url); + query.addQueryItem(QStringLiteral("url"), blogUrl); + url.setQuery(query); return url; } QUrl BloggerService::fetchBlogsByUserIdUrl(const QString &userId) { return GoogleApisUrl % QStringLiteral("/blogger/v3/users/") % userId % QStringLiteral("/blogs"); } QUrl BloggerService::fetchCommentsUrl(const QString &blogId, const QString &postId, const QString &commentId) { return GoogleApisUrl % commentBasePath(blogId, postId, commentId); } QUrl BloggerService::approveCommentUrl(const QString &blogId, const QString &postId, const QString &commentId) { return GoogleApisUrl % commentBasePath(blogId, postId, commentId) % QStringLiteral("/approve"); } QUrl BloggerService::markCommentAsSpamUrl(const QString &blogId, const QString &postId, const QString &commentId) { return GoogleApisUrl % commentBasePath(blogId, postId, commentId) % QStringLiteral("/spam"); } QUrl BloggerService::deleteCommentUrl(const QString &blogId, const QString &postId, const QString &commentId) { return GoogleApisUrl % commentBasePath(blogId, postId, commentId); } QUrl BloggerService::deleteCommentContentUrl(const QString &blogId, const QString &postId, const QString &commentId) { return GoogleApisUrl % commentBasePath(blogId, postId, commentId) % QStringLiteral("/removecontent"); } QUrl BloggerService::fetchPageUrl(const QString &blogId, const QString &pageId) { return GoogleApisUrl % pageBasePath(blogId, pageId); } QUrl BloggerService::deletePageUrl(const QString &blogId, const QString &pageId) { return GoogleApisUrl % pageBasePath(blogId, pageId); } QUrl BloggerService::modifyPageUrl(const QString &blogId, const QString &pageId) { return GoogleApisUrl % pageBasePath(blogId, pageId); } QUrl BloggerService::createPageUrl(const QString &blogId) { return GoogleApisUrl % pageBasePath(blogId); } QUrl BloggerService::fetchPostUrl(const QString &blogId, const QString &postId) { return GoogleApisUrl % postBasePath(blogId, postId); } QUrl BloggerService::searchPostUrl(const QString &blogId) { return GoogleApisUrl % postBasePath(blogId) % QStringLiteral("/search"); } QUrl BloggerService::createPostUrl(const QString &blogId) { return GoogleApisUrl % postBasePath(blogId); } QUrl BloggerService::deletePostUrl(const QString &blogId, const QString &postId) { return GoogleApisUrl % postBasePath(blogId, postId); } QUrl BloggerService::modifyPostUrl(const QString &blogId, const QString &postId) { return GoogleApisUrl % postBasePath(blogId, postId); } QUrl BloggerService::publishPostUrl(const QString &blogId, const QString &postId) { return GoogleApisUrl % postBasePath(blogId, postId) % QStringLiteral("/publish"); } QUrl BloggerService::revertPostUrl(const QString &blogId, const QString &postId) { return GoogleApisUrl % postBasePath(blogId, postId) % QStringLiteral("/revert"); } diff --git a/src/blogger/comment.cpp b/src/blogger/comment.cpp index 9d76f2a..0e5c9af 100644 --- a/src/blogger/comment.cpp +++ b/src/blogger/comment.cpp @@ -1,248 +1,250 @@ /* * Copyright (C) 2014 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "comment.h" #include #include +#include using namespace KGAPI2; using namespace KGAPI2::Blogger; class Q_DECL_HIDDEN Comment::Private { public: Private(); static CommentPtr fromJSON(const QVariant &json); QString id; QString postId; QString blogId; QDateTime published; QDateTime updated; QString content; QString authorId; QString authorName; QUrl authorUrl; QUrl authorImageUrl; QString inReplyTo; QString status; }; Comment::Private::Private() { } Comment::Comment() : Object() , d(new Private) { } Comment::~Comment() { delete d; } QString Comment::id() const { return d->id; } void Comment::setId(const QString &id) { d->id = id; } QString Comment::postId() const { return d->postId; } void Comment::setPostId(const QString &postId) { d->postId = postId; } QString Comment::blogId() const { return d->blogId; } void Comment::setBlogId(const QString &blogId) { d->blogId = blogId; } QDateTime Comment::published() const { return d->published; } void Comment::setPublished(const QDateTime &published) { d->published = published; } QDateTime Comment::updated() const { return d->updated; } void Comment::setUpdated(const QDateTime &updated) { d->updated = updated; } QString Comment::content() const { return d->content; } void Comment::setContent(const QString &content) { d->content = content; } QString Comment::authorId() const { return d->authorId; } void Comment::setAuthorId(const QString &authorId) { d->authorId = authorId; } QString Comment::authorName() const { return d->authorName; } void Comment::setAuthorName(const QString &authorName) { d->authorName = authorName; } QUrl Comment::authorUrl() const { return d->authorUrl; } void Comment::setAuthorUrl(const QUrl &url) { d->authorUrl = url; } QUrl Comment::authorImageUrl() const { return d->authorImageUrl; } void Comment::setAuthorImageUrl(const QUrl &authorImageUrl) { d->authorImageUrl = authorImageUrl; } QString Comment::inReplyTo() const { return d->inReplyTo; } void Comment::setInReplyTo(const QString &inReplyTo) { d->inReplyTo = inReplyTo; } QString Comment::status() const { return d->status; } void Comment::setStatus(const QString &status) { d->status = status; } CommentPtr Comment::Private::fromJSON(const QVariant &json) { CommentPtr comment(new Comment); const QVariantMap map = json.toMap(); comment->d->id = map[QStringLiteral("id")].toString(); comment->d->postId = map[QStringLiteral("post")].toMap()[QStringLiteral("id")].toString(); comment->d->blogId = map[QStringLiteral("blog")].toMap()[QStringLiteral("id")].toString(); comment->d->published = QDateTime::fromString(map[QStringLiteral("published")].toString(), Qt::ISODate); comment->d->updated = QDateTime::fromString(map[QStringLiteral("updated")].toString(), Qt::ISODate); comment->d->content = map[QStringLiteral("content")].toString(); const QVariantMap author = map[QStringLiteral("author")].toMap(); comment->d->authorId = author[QStringLiteral("id")].toString(); comment->d->authorName = author[QStringLiteral("displayName")].toString(); comment->d->authorUrl = author[QStringLiteral("url")].toUrl(); comment->d->authorImageUrl = author[QStringLiteral("image")].toMap()[QStringLiteral("url")].toUrl(); comment->d->inReplyTo = map[QStringLiteral("inReplyTo")].toMap()[QStringLiteral("id")].toString(); comment->d->status = map[QStringLiteral("status")].toString(); return comment; } CommentPtr Comment::fromJSON(const QByteArray &rawData) { QJsonDocument document = QJsonDocument::fromJson(rawData); if (document.isNull()) { return CommentPtr(); } const QVariant json = document.toVariant(); const QVariantMap map = json.toMap(); if (map[QStringLiteral("kind")].toString() != QLatin1String("blogger#comment")) { return CommentPtr(); } return Comment::Private::fromJSON(map); } ObjectsList Comment::fromJSONFeed(const QByteArray &rawData, FeedData &feedData) { QJsonDocument document = QJsonDocument::fromJson(rawData); if (document.isNull()) { return ObjectsList(); } const QVariant json = document.toVariant(); const QVariantMap map = json.toMap(); if (map[QStringLiteral("kind")].toString() != QLatin1String("blogger#commentList")) { return ObjectsList(); } ObjectsList items; if (!map[QStringLiteral("nextPageToken")].toString().isEmpty()) { - QUrl requestUrl(feedData.requestUrl); - requestUrl.removeQueryItem(QStringLiteral("pageToken")); - requestUrl.addQueryItem(QStringLiteral("pageToken"), map[QStringLiteral("nextPageToken")].toString()); - feedData.nextPageUrl = requestUrl; + feedData.nextPageUrl = feedData.requestUrl; + QUrlQuery query(feedData.nextPageUrl); + query.removeQueryItem(QStringLiteral("pageToken")); + query.addQueryItem(QStringLiteral("pageToken"), map[QStringLiteral("nextPageToken")].toString()); + feedData.nextPageUrl.setQuery(query); } const QVariantList variantList = map[QStringLiteral("items")].toList(); items.reserve(variantList.size()); for (const QVariant &v : variantList) { items << Comment::Private::fromJSON(v); } return items; } diff --git a/src/blogger/commentfetchjob.cpp b/src/blogger/commentfetchjob.cpp index 6d49e7b..983e23a 100644 --- a/src/blogger/commentfetchjob.cpp +++ b/src/blogger/commentfetchjob.cpp @@ -1,215 +1,217 @@ /* * Copyright (C) 2014 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "commentfetchjob.h" #include "comment.h" #include "bloggerservice.h" #include "utils.h" #include "account.h" #include #include +#include using namespace KGAPI2; using namespace KGAPI2::Blogger; class Q_DECL_HIDDEN CommentFetchJob::Private { public: Private(const QString &blogId, const QString &postId, const QString &commentId, CommentFetchJob *parent); ~Private(); QNetworkRequest createRequest(const QUrl &url); QString blogId; QString postId; QString commentId; uint maxResults; QDateTime startDate; QDateTime endDate; bool fetchBodies; private: CommentFetchJob *q; }; CommentFetchJob::Private::Private(const QString &blogId_, const QString &postId_, const QString &commentId_, CommentFetchJob *parent) : blogId(blogId_) , postId(postId_) , commentId(commentId_) , maxResults(0) , fetchBodies(true) , q(parent) { } CommentFetchJob::Private::~Private() { } QNetworkRequest CommentFetchJob::Private::createRequest(const QUrl &url) { QNetworkRequest request; if (q->account()) { request.setRawHeader("Authorization", "Bearer " + q->account()->accessToken().toLatin1()); } request.setUrl(url); return request; } CommentFetchJob::CommentFetchJob(const QString &blogId, const AccountPtr &account, QObject *parent) : FetchJob(account, parent) , d(new Private(blogId, QString(), QString(), this)) { } CommentFetchJob::CommentFetchJob(const QString &blogId, const QString &postId, const AccountPtr &account, QObject *parent) : FetchJob(account, parent) , d(new Private(blogId, postId, QString(), this)) { } CommentFetchJob::CommentFetchJob(const QString &blogId, const QString &postId, const QString &commentId, const AccountPtr &account, QObject *parent) : FetchJob(account, parent) , d(new Private(blogId, postId, commentId, this)) { } CommentFetchJob::~CommentFetchJob() { delete d; } QDateTime CommentFetchJob::endDate() const { return d->endDate; } void CommentFetchJob::setEndDate(const QDateTime &endDate) { d->endDate = endDate; } QDateTime CommentFetchJob::startDate() const { return d->startDate; } void CommentFetchJob::setStartDate(const QDateTime &startDate) { d->startDate = startDate; } uint CommentFetchJob::maxResults() const { return d->maxResults; } void CommentFetchJob::setMaxResults(uint maxResults) { d->maxResults = maxResults; } bool CommentFetchJob::fetchBodies() const { return d->fetchBodies; } void CommentFetchJob::setFetchBodies(bool fetchBodies) { d->fetchBodies = fetchBodies; } void CommentFetchJob::start() { QUrl url = BloggerService::fetchCommentsUrl(d->blogId, d->postId, d->commentId); - + QUrlQuery query(url); if (d->startDate.isValid()) { - url.addQueryItem(QStringLiteral("startDate"), d->startDate.toString(Qt::ISODate)); + query.addQueryItem(QStringLiteral("startDate"), d->startDate.toString(Qt::ISODate)); } if (d->endDate.isValid()) { - url.addQueryItem(QStringLiteral("endDate"), d->endDate.toString(Qt::ISODate)); + query.addQueryItem(QStringLiteral("endDate"), d->endDate.toString(Qt::ISODate)); } if (d->maxResults > 0) { - url.addQueryItem(QStringLiteral("maxResults"), QString::number(d->maxResults)); + query.addQueryItem(QStringLiteral("maxResults"), QString::number(d->maxResults)); } - url.addQueryItem(QStringLiteral("fetchBodies"), Utils::bool2Str(d->fetchBodies)); + query.addQueryItem(QStringLiteral("fetchBodies"), Utils::bool2Str(d->fetchBodies)); if (account()) { - url.addQueryItem(QStringLiteral("view"), QStringLiteral("ADMIN")); + query.addQueryItem(QStringLiteral("view"), QStringLiteral("ADMIN")); } + url.setQuery(query); const QNetworkRequest request = d->createRequest(url); enqueueRequest(request); } ObjectsList CommentFetchJob::handleReplyWithItems(const QNetworkReply *reply, const QByteArray &rawData) { FeedData feedData; feedData.requestUrl = reply->request().url(); ObjectsList items; QString itemId; const QString contentType = reply->header(QNetworkRequest::ContentTypeHeader).toString(); ContentType ct = Utils::stringToContentType(contentType); if (ct == KGAPI2::JSON) { if (d->commentId.isEmpty()) { items = Comment::fromJSONFeed(rawData, feedData); } else { items << Comment::fromJSON(rawData); } } else { setError(KGAPI2::InvalidResponse); setErrorString(tr("Invalid response content type")); emitFinished(); return items; } if (feedData.nextPageUrl.isValid()) { const QNetworkRequest request = d->createRequest(feedData.nextPageUrl); enqueueRequest(request); } else { emitFinished(); } return items; } diff --git a/src/blogger/pagefetchjob.cpp b/src/blogger/pagefetchjob.cpp index 9ae717c..65955bf 100644 --- a/src/blogger/pagefetchjob.cpp +++ b/src/blogger/pagefetchjob.cpp @@ -1,144 +1,146 @@ /* * Copyright (C) 2014 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "pagefetchjob.h" #include "bloggerservice.h" #include "page.h" #include "utils.h" #include "account.h" #include #include +#include using namespace KGAPI2; using namespace KGAPI2::Blogger; class Q_DECL_HIDDEN PageFetchJob::Private { public: Private(const QString &blogId, const QString &pageId); QString blogId; QString pageId; bool fetchContent; StatusFilters statusFilter; }; PageFetchJob::Private::Private(const QString &blogId_, const QString &pageId_) : blogId(blogId_) , pageId(pageId_) , fetchContent(true) , statusFilter(All) { } PageFetchJob::PageFetchJob(const QString &blogId, const AccountPtr &account, QObject *parent) : FetchJob(account, parent) , d(new Private(blogId, QString())) { } PageFetchJob::PageFetchJob(const QString &blogId, const QString &pageId, const AccountPtr &account, QObject *parent) : FetchJob(account, parent) , d(new Private(blogId, pageId)) { } PageFetchJob::~PageFetchJob() { delete d; } bool PageFetchJob::fetchContent() const { return d->fetchContent; } void PageFetchJob::setFetchContent(bool fetchContent) { d->fetchContent = fetchContent; } PageFetchJob::StatusFilters PageFetchJob::statusFilter() const { return d->statusFilter; } void PageFetchJob::setStatusFilter(StatusFilters status) { d->statusFilter = status; } void PageFetchJob::start() { QUrl url = BloggerService::fetchPageUrl(d->blogId, d->pageId); - url.addQueryItem(QStringLiteral("fetchBodies"), Utils::bool2Str(d->fetchContent)); + QUrlQuery query(url); + query.addQueryItem(QStringLiteral("fetchBodies"), Utils::bool2Str(d->fetchContent)); if (d->statusFilter & Draft) { - url.addQueryItem(QStringLiteral("status"), QStringLiteral("draft")); + query.addQueryItem(QStringLiteral("status"), QStringLiteral("draft")); } if (d->statusFilter & Imported) { - url.addQueryItem(QStringLiteral("status"), QStringLiteral("imported")); + query.addQueryItem(QStringLiteral("status"), QStringLiteral("imported")); } if (d->statusFilter & Live) { - url.addQueryItem(QStringLiteral("status"), QStringLiteral("live")); + query.addQueryItem(QStringLiteral("status"), QStringLiteral("live")); } if (account()) { - url.addQueryItem(QStringLiteral("view"), QStringLiteral("ADMIN")); + query.addQueryItem(QStringLiteral("view"), QStringLiteral("ADMIN")); } - + url.setQuery(query); QNetworkRequest request(url); if (account()) { request.setRawHeader("Authorization", "Bearer " + account()->accessToken().toLatin1()); } enqueueRequest(request); } ObjectsList PageFetchJob::handleReplyWithItems(const QNetworkReply *reply, const QByteArray &rawData) { ObjectsList items; const QString contentType = reply->header(QNetworkRequest::ContentTypeHeader).toString(); ContentType ct = Utils::stringToContentType(contentType); if (ct == KGAPI2::JSON) { if (d->pageId.isEmpty()) { items = Page::fromJSONFeed(rawData); } else { items << Page::fromJSON(rawData); } } else { setError(KGAPI2::InvalidResponse); setErrorString(tr("Invalid response content type")); emitFinished(); return items; } emitFinished(); return items; } diff --git a/src/blogger/post.cpp b/src/blogger/post.cpp index 23cf722..a287859 100644 --- a/src/blogger/post.cpp +++ b/src/blogger/post.cpp @@ -1,366 +1,368 @@ /* * Copyright (C) 2014 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "post.h" #include +#include using namespace KGAPI2; using namespace KGAPI2::Blogger; class Q_DECL_HIDDEN Post::Private { public: Private(); static PostPtr fromJSON(const QVariant &json); static QVariant toJSON(const PostPtr &post); QString id; QString blogId; QDateTime published; QDateTime updated; QUrl url; QString title; QString content; QString authorId; QString authorName; QUrl authorUrl; QUrl authorImageUrl; uint commentsCount; QStringList labels; QVariant customMetaData; QString location; double latitude; double longitude; QList images; QString status; }; Post::Private::Private() : commentsCount(0) , latitude(-1) , longitude(-1) { } Post::Post() : d(new Private) { } Post::~Post() { delete d; } QString Post::id() { return d->id; } void Post::setId(const QString &id) { d->id = id; } QString Post::blogId() { return d->blogId; } void Post::setBlogId(const QString &id) { d->blogId = id; } QDateTime Post::published() const { return d->published; } void Post::setPublished(const QDateTime &published) { d->published = published; } QDateTime Post::updated() const { return d->updated; } void Post::setUpdated(const QDateTime &updated) { d->updated = updated; } QUrl Post::url() const { return d->url; } void Post::setUrl(const QUrl &url) { d->url = url; } QString Post::title() const { return d->title; } void Post::setTitle(const QString &title) { d->title = title; } QString Post::content() const { return d->content; } void Post::setContent(const QString &content) { d->content = content; } QString Post::authorId() const { return d->authorId; } QString Post::authorName() const { return d->authorName; } QUrl Post::authorUrl() const { return d->authorUrl; } QUrl Post::authorImageUrl() const { return d->authorImageUrl; } uint Post::commentsCount() const { return d->commentsCount; } QStringList Post::labels() const { return d->labels; } void Post::setLabels(const QStringList &labels) { d->labels = labels; } QVariant Post::customMetaData() const { return d->customMetaData; } void Post::setCustomMetaData(const QVariant &metadata) { d->customMetaData = metadata; } QString Post::location() const { return d->location; } void Post::setLocation(const QString &location) { d->location = location; } double Post::latitude() const { return d->latitude; } void Post::setLatitude(double lat) { d->latitude = lat; } double Post::longitude() const { return d->longitude; } void Post::setLongitute(double lng) { d->longitude = lng; } QList Post::images() const { return d->images; } void Post::setImages(const QList &images) { d->images = images; } QString Post::status() const { return d->status; } PostPtr Post::Private::fromJSON(const QVariant &json) { PostPtr post(new Post); const QVariantMap map = json.toMap(); post->d->id = map[QStringLiteral("id")].toString(); post->d->blogId = map[QStringLiteral("blog")].toMap()[QStringLiteral("id")].toString(); post->d->published = QDateTime::fromString(map[QStringLiteral("published")].toString(), Qt::ISODate); post->d->updated = QDateTime::fromString(map[QStringLiteral("updated")].toString(), Qt::ISODate); post->d->url = map[QStringLiteral("url")].toUrl(); post->d->title = map[QStringLiteral("title")].toString(); post->d->content = map[QStringLiteral("content")].toString(); const QVariantMap author = map[QStringLiteral("author")].toMap(); post->d->authorId = author[QStringLiteral("id")].toString(); post->d->authorName = author[QStringLiteral("displayName")].toString(); post->d->authorUrl = author[QStringLiteral("url")].toUrl(); post->d->authorImageUrl = author[QStringLiteral("image")].toMap()[QStringLiteral("url")].toUrl(); post->d->commentsCount = map[QStringLiteral("replies")].toMap()[QStringLiteral("totalItems")].toUInt(); post->d->labels = map[QStringLiteral("labels")].toStringList(); post->d->customMetaData = map[QStringLiteral("customMetaData")]; const QVariantMap location = map[QStringLiteral("location")].toMap(); post->d->location = location[QStringLiteral("name")].toString(); post->d->latitude = location[QStringLiteral("lat")].toDouble(); post->d->longitude = location[QStringLiteral("lng")].toDouble(); const QVariantList variantList = map[QStringLiteral("images")].toList(); for (const QVariant &url : variantList) { post->d->images << url.toMap()[QStringLiteral("url")].toUrl(); } post->d->status = map[QStringLiteral("status")].toString(); return post; } QVariant Post::Private::toJSON(const PostPtr &post) { QVariantMap json; json[QStringLiteral("kind")] = QStringLiteral("blogger#post"); if (!post->d->id.isEmpty()) { json[QStringLiteral("id")] = post->d->id; } if (!post->d->blogId.isEmpty()) { QVariantMap blog; blog[QStringLiteral("id")] = post->d->blogId; json[QStringLiteral("blog")] = blog; } if (post->d->published.isValid()) { json[QStringLiteral("published")] = post->d->published.toString(Qt::ISODate); } if (post->d->updated.isValid()) { json[QStringLiteral("updated")] = post->d->updated.toString(Qt::ISODate); } json[QStringLiteral("title")] = post->d->title; json[QStringLiteral("content")] = post->d->content; if (!post->d->labels.isEmpty()) { json[QStringLiteral("labels")] = post->d->labels; } if (!post->d->customMetaData.isNull()) { QJsonDocument document = QJsonDocument::fromVariant(post->d->customMetaData); json[QStringLiteral("customMetaData")] = document.toJson(QJsonDocument::Compact); } if (!post->d->location.isEmpty() && post->d->latitude > -1 && post->d->longitude > -1) { QVariantMap location; location[QStringLiteral("name")] = post->d->location; location[QStringLiteral("lat")] = post->d->latitude; location[QStringLiteral("lng")] = post->d->longitude; json[QStringLiteral("location")] = location; } if (!post->d->images.isEmpty()) { QVariantList images; for (const QUrl &url : qAsConst(post->d->images)) { QVariantMap image; image[QStringLiteral("url")] = url.toString(); images << image; } json[QStringLiteral("images")] = images; } return json; } PostPtr Post::fromJSON(const QByteArray &rawData) { QJsonDocument document = QJsonDocument::fromJson(rawData); if (document.isNull()) { return PostPtr(); } const QVariant json = document.toVariant(); const QVariantMap map = json.toMap(); if (map[QStringLiteral("kind")].toString() != QLatin1String("blogger#post")) { return PostPtr(); } return Private::fromJSON(map); } ObjectsList Post::fromJSONFeed(const QByteArray &rawData, FeedData &feedData) { QJsonDocument document = QJsonDocument::fromJson(rawData); if (document.isNull()) { return ObjectsList(); } const QVariant json = document.toVariant(); const QVariantMap map = json.toMap(); if (map[QStringLiteral("kind")].toString() != QLatin1String("blogger#postList")) { return ObjectsList(); } if (!map[QStringLiteral("nextPageToken")].toString().isEmpty()) { - QUrl requestUrl(feedData.requestUrl); - requestUrl.removeQueryItem(QStringLiteral("pageToken")); - requestUrl.addQueryItem(QStringLiteral("pageToken"), map[QStringLiteral("nextPageToken")].toString()); - feedData.nextPageUrl = requestUrl; + feedData.nextPageUrl = feedData.requestUrl; + QUrlQuery query(feedData.nextPageUrl); + query.removeQueryItem(QStringLiteral("pageToken")); + query.addQueryItem(QStringLiteral("pageToken"), map[QStringLiteral("nextPageToken")].toString()); + feedData.nextPageUrl.setQuery(query); } ObjectsList list; const QVariantList variantList = map[QStringLiteral("items")].toList(); list.reserve(variantList.size()); for (const QVariant &item : variantList) { list << Private::fromJSON(item); } return list; } QByteArray Post::toJSON(const PostPtr &post) { QJsonDocument document = QJsonDocument::fromVariant(Private::toJSON(post)); return document.toJson(QJsonDocument::Compact); } diff --git a/src/blogger/postcreatejob.cpp b/src/blogger/postcreatejob.cpp index 1621b68..308b18b 100644 --- a/src/blogger/postcreatejob.cpp +++ b/src/blogger/postcreatejob.cpp @@ -1,95 +1,98 @@ /* * Copyright (C) 2014 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "postcreatejob.h" #include "post.h" #include "bloggerservice.h" #include "account.h" #include "utils.h" #include #include +#include using namespace KGAPI2; using namespace KGAPI2::Blogger; class Q_DECL_HIDDEN PostCreateJob::Private { public: Private(const PostPtr &post, bool isDraft); PostPtr post; bool isDraft; }; PostCreateJob::Private::Private(const PostPtr &post_, bool isDraft_) : post(post_) , isDraft(isDraft_) { } PostCreateJob::PostCreateJob(const PostPtr &post, bool isDraft, const AccountPtr &account, QObject *parent) : CreateJob(account, parent) , d(new Private(post, isDraft)) { } PostCreateJob::~PostCreateJob() { delete d; } void PostCreateJob::start() { QUrl url = BloggerService::createPostUrl(d->post->blogId()); if (d->isDraft) { - url.addQueryItem(QStringLiteral("isDraft"), Utils::bool2Str(d->isDraft)); + QUrlQuery query(url); + query.addQueryItem(QStringLiteral("isDraft"), Utils::bool2Str(d->isDraft)); + url.setQuery(query); } QNetworkRequest request; request.setRawHeader("Authorization", "Bearer " + account()->accessToken().toLatin1()); request.setUrl(url); const QByteArray rawData = Post::toJSON(d->post); enqueueRequest(request, rawData, QStringLiteral("application/json")); } ObjectsList PostCreateJob::handleReplyWithItems(const QNetworkReply *reply, const QByteArray &rawData) { const QString contentType = reply->header(QNetworkRequest::ContentTypeHeader).toString(); ContentType ct = Utils::stringToContentType(contentType); ObjectsList items; if (ct != KGAPI2::JSON) { setError(KGAPI2::InvalidResponse); setErrorString(tr("Invalid response content type")); emitFinished(); return items; } items << Post::fromJSON(rawData); emitFinished(); return items; } diff --git a/src/blogger/postfetchjob.cpp b/src/blogger/postfetchjob.cpp index 9943551..a338bae 100644 --- a/src/blogger/postfetchjob.cpp +++ b/src/blogger/postfetchjob.cpp @@ -1,238 +1,240 @@ /* * Copyright (C) 2014 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "postfetchjob.h" #include "post.h" #include "bloggerservice.h" #include "account.h" #include "utils.h" #include #include +#include using namespace KGAPI2; using namespace KGAPI2::Blogger; class Q_DECL_HIDDEN PostFetchJob::Private { public: Private(const QString &blogId, const QString &postId, PostFetchJob *parent); QNetworkRequest createRequest(const QUrl &url); QString blogId; QString postId; bool fetchBodies; bool fetchImages; uint maxResults; QStringList filterLabels; QDateTime startDate; QDateTime endDate; StatusFilters statusFilter; private: PostFetchJob *q; }; PostFetchJob::Private::Private(const QString &blogId_, const QString &postId_, PostFetchJob *parent) : blogId(blogId_) , postId(postId_) , fetchBodies(true) , fetchImages(true) , maxResults(0) , statusFilter(All) , q(parent) { } QNetworkRequest PostFetchJob::Private::createRequest(const QUrl &url) { QNetworkRequest request; if (q->account()) { request.setRawHeader("Authorization", "Bearer " + q->account()->accessToken().toLatin1()); } request.setUrl(url); return request; } PostFetchJob::PostFetchJob(const QString &blogId, const AccountPtr &account, QObject *parent) : FetchJob(account, parent) , d(new Private(blogId, QString(), this)) { } PostFetchJob::PostFetchJob(const QString &blogId, const QString &postId, const AccountPtr &account, QObject *parent) : FetchJob(account, parent) , d(new Private(blogId, postId, this)) { } PostFetchJob::~PostFetchJob() { delete d; } bool PostFetchJob::fetchBodies() const { return d->fetchBodies; } void PostFetchJob::setFetchBodies(bool fetchBodies) { d->fetchBodies = fetchBodies; } bool PostFetchJob::fetchImages() const { return d->fetchImages; } void PostFetchJob::setFetchImages(bool fetchImages) { d->fetchImages = fetchImages; } uint PostFetchJob::maxResults() const { return d->maxResults; } void PostFetchJob::setMaxResults(uint maxResults) { d->maxResults = maxResults; } QStringList PostFetchJob::filterLabels() const { return d->filterLabels; } void PostFetchJob::setFilterLabels(const QStringList &labels) { d->filterLabels = labels; } QDateTime PostFetchJob::startDate() const { return d->startDate; } void PostFetchJob::setStartDate(const QDateTime &startDate) { d->startDate = startDate; } QDateTime PostFetchJob::endDate() const { return d->endDate; } void PostFetchJob::setEndDate(const QDateTime &endDate) { d->endDate = endDate; } void PostFetchJob::setStatusFilter(PostFetchJob::StatusFilters filter) { d->statusFilter = filter; } PostFetchJob::StatusFilters PostFetchJob::statusFilter() const { return d->statusFilter; } void PostFetchJob::start() { QUrl url = BloggerService::fetchPostUrl(d->blogId, d->postId); + QUrlQuery query(url); if (d->postId.isEmpty()) { if (d->startDate.isValid()) { - url.addQueryItem(QStringLiteral("startDate"), d->startDate.toString(Qt::ISODate)); + query.addQueryItem(QStringLiteral("startDate"), d->startDate.toString(Qt::ISODate)); } if (d->endDate.isValid()) { - url.addQueryItem(QStringLiteral("endDate"), d->endDate.toString(Qt::ISODate)); + query.addQueryItem(QStringLiteral("endDate"), d->endDate.toString(Qt::ISODate)); } if (d->maxResults > 0) { - url.addQueryItem(QStringLiteral("maxResults"), QString::number(d->maxResults)); + query.addQueryItem(QStringLiteral("maxResults"), QString::number(d->maxResults)); } if (!d->filterLabels.isEmpty()) - url.addQueryItem(QStringLiteral("labels"), d->filterLabels.join(QStringLiteral(","))); - url.addQueryItem(QStringLiteral("fetchBodies"), Utils::bool2Str(d->fetchBodies)); - url.addQueryItem(QStringLiteral("fetchImages"), Utils::bool2Str(d->fetchImages)); + query.addQueryItem(QStringLiteral("labels"), d->filterLabels.join(QStringLiteral(","))); + query.addQueryItem(QStringLiteral("fetchBodies"), Utils::bool2Str(d->fetchBodies)); + query.addQueryItem(QStringLiteral("fetchImages"), Utils::bool2Str(d->fetchImages)); } if (account()) { - url.addQueryItem(QStringLiteral("view"), QStringLiteral("ADMIN")); + query.addQueryItem(QStringLiteral("view"), QStringLiteral("ADMIN")); } if (d->statusFilter & Draft) { - url.addQueryItem(QStringLiteral("status"), QStringLiteral("draft")); + query.addQueryItem(QStringLiteral("status"), QStringLiteral("draft")); } if (d->statusFilter & Live) { - url.addQueryItem(QStringLiteral("status"), QStringLiteral("live")); + query.addQueryItem(QStringLiteral("status"), QStringLiteral("live")); } if (d->statusFilter & Scheduled) { - url.addQueryItem(QStringLiteral("status"), QStringLiteral("scheduled")); + query.addQueryItem(QStringLiteral("status"), QStringLiteral("scheduled")); } - + url.setQuery(query); const QNetworkRequest request = d->createRequest(url); enqueueRequest(request); } ObjectsList PostFetchJob::handleReplyWithItems(const QNetworkReply *reply, const QByteArray &rawData) { FeedData feedData; feedData.requestUrl = reply->request().url(); ObjectsList items; const QString contentType = reply->header(QNetworkRequest::ContentTypeHeader).toString(); ContentType ct = Utils::stringToContentType(contentType); if (ct == KGAPI2::JSON) { if (d->postId.isEmpty()) { items = Post::fromJSONFeed(rawData, feedData); } else { items << Post::fromJSON(rawData); } } else { setError(KGAPI2::InvalidResponse); setErrorString(tr("Invalid response content type")); emitFinished(); return items; } if (feedData.nextPageUrl.isValid()) { const QNetworkRequest request = d->createRequest(feedData.nextPageUrl); enqueueRequest(request); } else { emitFinished(); } return items; } diff --git a/src/blogger/postpublishjob.cpp b/src/blogger/postpublishjob.cpp index 253d28e..39b7d3e 100644 --- a/src/blogger/postpublishjob.cpp +++ b/src/blogger/postpublishjob.cpp @@ -1,150 +1,153 @@ /* * Copyright (C) 2014 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "postpublishjob.h" #include "post.h" #include "bloggerservice.h" #include "account.h" #include "utils.h" #include #include #include +#include using namespace KGAPI2; using namespace KGAPI2::Blogger; class Q_DECL_HIDDEN PostPublishJob::Private { public: Private(const QString &blogId, const QString &postId, PostPublishJob::PublishAction action, const QDateTime &publishDate = QDateTime()); QString blogId; QString postId; PublishAction action; QDateTime publishDate; ObjectPtr response; }; PostPublishJob::Private::Private(const QString &blogId_, const QString &postId_, PostPublishJob::PublishAction action_, const QDateTime &publishDate_) : blogId(blogId_) , postId(postId_) , action(action_) , publishDate(publishDate_) { } PostPublishJob::PostPublishJob(const PostPtr &post, PublishAction action, const AccountPtr &account, QObject *parent) : Job(account, parent) , d(new Private(post->blogId(), post->id(), action)) { } PostPublishJob::PostPublishJob(const QString &blogId, const QString &postId, PublishAction action, const AccountPtr &account, QObject *parent) : Job(account, parent) , d(new Private(blogId, postId, action)) { } PostPublishJob::PostPublishJob(const PostPtr &post, const QDateTime &publishDate, const AccountPtr &account, QObject *parent) : Job(account, parent) , d(new Private(post->blogId(), post->id(), Publish, publishDate)) { } PostPublishJob::PostPublishJob(const QString &blogId, const QString &postId, const QDateTime &publishDate, const AccountPtr &account, QObject *parent) : Job(account, parent) , d(new Private(blogId, postId, Publish, publishDate)) { } PostPublishJob::~PostPublishJob() { delete d; } ObjectPtr PostPublishJob::item() const { return d->response; } void PostPublishJob::start() { QUrl url; if (d->action == Blogger::PostPublishJob::Publish) { url = BloggerService::publishPostUrl(d->blogId, d->postId); + QUrlQuery query(url); if (d->publishDate.isValid()) { - url.addQueryItem(QStringLiteral("publishDate"), d->publishDate.toString(Qt::ISODate)); + query.addQueryItem(QStringLiteral("publishDate"), d->publishDate.toString(Qt::ISODate)); } + url.setQuery(query); } else { url = BloggerService::revertPostUrl(d->blogId, d->postId); } const QNetworkRequest request(url); enqueueRequest(request); } void PostPublishJob::dispatchRequest(QNetworkAccessManager *accessManager, const QNetworkRequest &request, const QByteArray &data, const QString &contentType) { Q_UNUSED(contentType); accessManager->post(request, data); } void PostPublishJob::handleReply(const QNetworkReply *reply, const QByteArray &rawData) { const QString contentType = reply->header(QNetworkRequest::ContentTypeHeader).toString(); ContentType ct = Utils::stringToContentType(contentType); if (ct != KGAPI2::JSON) { setError(KGAPI2::InvalidResponse); setErrorString(tr("Invalid response content type")); emitFinished(); } d->response = Post::fromJSON(rawData); emitFinished(); } diff --git a/src/blogger/postsearchjob.cpp b/src/blogger/postsearchjob.cpp index 0e5cab0..b9042bb 100644 --- a/src/blogger/postsearchjob.cpp +++ b/src/blogger/postsearchjob.cpp @@ -1,133 +1,135 @@ /* * Copyright (C) 2014 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "postsearchjob.h" #include "post.h" #include "bloggerservice.h" #include "account.h" #include "utils.h" #include #include +#include using namespace KGAPI2; using namespace KGAPI2::Blogger; class Q_DECL_HIDDEN PostSearchJob::Private { public: Private(const QString &blogId, const QString &query, PostSearchJob *parent); QNetworkRequest createRequest(const QUrl &url); QString blogId; QString query; bool fetchBodies; private: PostSearchJob *q; }; PostSearchJob::Private::Private(const QString &blogId_, const QString &query_, PostSearchJob *parent) : blogId(blogId_) , query(query_) , fetchBodies(true) , q(parent) { } QNetworkRequest PostSearchJob::Private::createRequest(const QUrl &url) { QNetworkRequest request; if (q->account()) { request.setRawHeader("Authorization", "Bearer " + q->account()->accessToken().toLatin1()); } request.setUrl(url); return request; } PostSearchJob::PostSearchJob(const QString &blogId, const QString &query, const AccountPtr &account, QObject *parent) : FetchJob(account, parent) , d(new Private(blogId, query, this)) { } PostSearchJob::~PostSearchJob() { delete d; } bool PostSearchJob::fetchBodies() const { return d->fetchBodies; } void PostSearchJob::setFetchBodies(bool fetchBodies) { d->fetchBodies = fetchBodies; } void PostSearchJob::start() { QUrl url = BloggerService::searchPostUrl(d->blogId); - url.addQueryItem(QStringLiteral("q"), d->query); - url.addQueryItem(QStringLiteral("fetchBodies"), Utils::bool2Str(d->fetchBodies)); - + QUrlQuery query(url); + query.addQueryItem(QStringLiteral("q"), d->query); + query.addQueryItem(QStringLiteral("fetchBodies"), Utils::bool2Str(d->fetchBodies)); + url.setQuery(query); const QNetworkRequest request = d->createRequest(url); enqueueRequest(request); } ObjectsList PostSearchJob::handleReplyWithItems(const QNetworkReply *reply, const QByteArray &rawData) { FeedData feedData; feedData.requestUrl = reply->request().url(); ObjectsList items; QString itemId; const QString contentType = reply->header(QNetworkRequest::ContentTypeHeader).toString(); ContentType ct = Utils::stringToContentType(contentType); if (ct == KGAPI2::JSON) { items = Post::fromJSONFeed(rawData, feedData); } else { setError(KGAPI2::InvalidResponse); setErrorString(tr("Invalid response content type")); emitFinished(); return items; } if (feedData.nextPageUrl.isValid()) { const QNetworkRequest request = d->createRequest(feedData.nextPageUrl); enqueueRequest(request); } else { emitFinished(); } return items; } diff --git a/src/calendar/calendarservice.cpp b/src/calendar/calendarservice.cpp index 512a48a..e90ad0f 100644 --- a/src/calendar/calendarservice.cpp +++ b/src/calendar/calendarservice.cpp @@ -1,1072 +1,1080 @@ /* * This file is part of LibKGAPI library * * Copyright (C) 2013 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "calendarservice.h" #include "calendar.h" #include "event.h" #include "reminder.h" #include "utils.h" #include "../debug.h" #include #include #include #include #include #include #include #include - +#include #include #include namespace KGAPI2 { namespace CalendarService { namespace Private { KCalCore::DateList parseRDate(const QString &rule); ObjectPtr JSONToCalendar(const QVariantMap &data); ObjectPtr JSONToEvent(const QVariantMap &data, const QString &timezone = QString()); /** * Checks whether TZID is in Olson format and converts it to it if neccessarry * * This is mainly to handle crazy Microsoft TZIDs like * "(GMT) Greenwich Mean Time/Dublin/Edinburgh/London", because Google only * accepts TZIDs in Olson format ("Europe/London"). * * It first tries to match the given \p tzid to all TZIDs in KTimeZones::zones(). * If it fails, it parses the \p event, looking for X-MICROSOFT-CDO-TZID * property and than matches it to Olson-formatted TZID using a table. * * When the method fails to process the TZID, it returns the original \p tzid * in hope, that Google will cope with it. */ QString checkAndConverCDOTZID(const QString &tzid, const EventPtr& event); static const QUrl GoogleApisUrl(QStringLiteral("https://www.googleapis.com")); static const QString CalendarListBasePath(QStringLiteral("/calendar/v3/users/me/calendarList")); static const QString CalendarBasePath(QStringLiteral("/calendar/v3/calendars")); } /************* URLS **************/ QUrl fetchCalendarsUrl() { QUrl url(Private::GoogleApisUrl); url.setPath(Private::CalendarListBasePath); return url; } QUrl fetchCalendarUrl(const QString& calendarID) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::CalendarListBasePath % QLatin1Char('/') % calendarID); return url; } QUrl updateCalendarUrl(const QString &calendarID) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::CalendarBasePath % QLatin1Char('/') % calendarID); return url; } QUrl createCalendarUrl() { QUrl url(Private::GoogleApisUrl); url.setPath(Private::CalendarBasePath); return url; } QUrl removeCalendarUrl(const QString& calendarID) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::CalendarBasePath % QLatin1Char('/') % calendarID); return url; } QUrl fetchEventsUrl(const QString& calendarID) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::CalendarBasePath % QLatin1Char('/') % calendarID % QLatin1String("/events")); - url.addQueryItem(QStringLiteral("maxResults"), QStringLiteral("20")); + QUrlQuery query(url); + query.addQueryItem(QStringLiteral("maxResults"), QStringLiteral("20")); + url.setQuery(query); return url; } QUrl fetchEventUrl(const QString& calendarID, const QString& eventID) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::CalendarBasePath % QLatin1Char('/') % calendarID % QLatin1String("/events/") % eventID); return url; } QUrl updateEventUrl(const QString& calendarID, const QString& eventID) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::CalendarBasePath % QLatin1Char('/') % calendarID % QLatin1String("/events/") % eventID); return url; } QUrl createEventUrl(const QString& calendarID) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::CalendarBasePath % QLatin1Char('/') % calendarID % QLatin1String("/events")); return url; } QUrl removeEventUrl(const QString& calendarID, const QString& eventID) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::CalendarBasePath % QLatin1Char('/') % calendarID % QLatin1String("/events/") % eventID); return url; } QUrl moveEventUrl(const QString& sourceCalendar, const QString& destCalendar, const QString& eventID) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::CalendarBasePath % QLatin1Char('/') % sourceCalendar % QLatin1String("/events/") % eventID); - url.addQueryItem(QStringLiteral("destination"), destCalendar); + QUrlQuery query(url); + query.addQueryItem(QStringLiteral("destination"), destCalendar); + url.setQuery(query); return url; } QUrl freeBusyQueryUrl() { QUrl url(Private::GoogleApisUrl); url.setPath(QStringLiteral("/calendar/v3/freeBusy")); return url; } QString APIVersion() { return QStringLiteral("3"); } CalendarPtr JSONToCalendar(const QByteArray& jsonData) { QJsonDocument document = QJsonDocument::fromJson(jsonData); QVariantMap calendar = document.toVariant().toMap(); if ((calendar.value(QStringLiteral("kind")) != QLatin1String("calendar#calendarListEntry")) && (calendar.value(QStringLiteral("kind")) != QLatin1String("calendar#calendar"))) { return CalendarPtr(); } return Private::JSONToCalendar(calendar).staticCast(); } ObjectPtr Private::JSONToCalendar(const QVariantMap& data) { CalendarPtr calendar(new Calendar); QString id = QUrl::fromPercentEncoding(data.value(QStringLiteral("id")).toByteArray()); calendar->setUid(id); calendar->setEtag(data.value(QStringLiteral("etag")).toString()); calendar->setTitle(data.value(QStringLiteral("summary")).toString()); calendar->setDetails(data.value(QStringLiteral("description")).toString()); calendar->setLocation(data.value(QStringLiteral("location")).toString()); calendar->setTimezone(data.value(QStringLiteral("timeZone")).toString()); calendar->setBackgroundColor(QColor(data.value(QStringLiteral("backgroundColor")).toString())); calendar->setForegroundColor(QColor(data.value(QStringLiteral("foregroundColor")).toString())); if ((data.value(QStringLiteral("accessRole")).toString() == QLatin1String("writer")) || (data.value(QStringLiteral("accessRole")).toString() == QLatin1String("owner"))) { calendar->setEditable(true); } else { calendar->setEditable(false); } const QVariantList reminders = data.value(QStringLiteral("defaultReminders")).toList(); for (const QVariant &r : reminders) { QVariantMap reminder = r.toMap(); ReminderPtr rem(new Reminder()); if (reminder.value(QStringLiteral("method")).toString() == QLatin1String("email")) { rem->setType(KCalCore::Alarm::Email); } else if (reminder.value(QStringLiteral("method")).toString() == QLatin1String("popup")) { rem->setType(KCalCore::Alarm::Display); } else { rem->setType(KCalCore::Alarm::Invalid); } rem->setStartOffset(KCalCore::Duration(reminder.value(QStringLiteral("minutes")).toInt() * (-60))); calendar->addDefaultReminer(rem); } return calendar.dynamicCast(); } QByteArray calendarToJSON(const CalendarPtr& calendar) { QVariantMap output, entry; if (!calendar->uid().isEmpty()) { entry.insert(QStringLiteral("id"), calendar->uid()); } entry.insert(QStringLiteral("summary"), calendar->title()); entry.insert(QStringLiteral("description"), calendar->details()); entry.insert(QStringLiteral("location"), calendar->location()); if (!calendar->timezone().isEmpty()) { entry.insert(QStringLiteral("timeZone"), calendar->timezone()); } QJsonDocument document = QJsonDocument::fromVariant(entry); return document.toJson(QJsonDocument::Compact); } ObjectsList parseCalendarJSONFeed(const QByteArray& jsonFeed, FeedData& feedData) { QJsonDocument document = QJsonDocument::fromJson(jsonFeed); QVariantMap data = document.toVariant().toMap(); ObjectsList list; if (data.value(QStringLiteral("kind")) == QLatin1String("calendar#calendarList")) { if (data.contains(QStringLiteral("nextPageToken"))) { feedData.nextPageUrl = fetchCalendarsUrl(); - feedData.nextPageUrl.addQueryItem(QStringLiteral("pageToken"), data.value(QStringLiteral("nextPageToken")).toString()); - if (feedData.nextPageUrl.queryItemValue(QStringLiteral("maxResults")).isEmpty()) { - feedData.nextPageUrl.addQueryItem(QStringLiteral("maxResults"),QStringLiteral("20")); + QUrlQuery query(feedData.nextPageUrl); + query.addQueryItem(QStringLiteral("pageToken"), data.value(QStringLiteral("nextPageToken")).toString()); + if (query.queryItemValue(QStringLiteral("maxResults")).isEmpty()) { + query.addQueryItem(QStringLiteral("maxResults"),QStringLiteral("20")); } + feedData.nextPageUrl.setQuery(query); } } else { return ObjectsList(); } const QVariantList items = data.value(QStringLiteral("items")).toList(); list.reserve(items.size()); for (const QVariant &i : items) { list.append(Private::JSONToCalendar(i.toMap())); } return list; } EventPtr JSONToEvent(const QByteArray& jsonData) { QJsonParseError error; QJsonDocument document = QJsonDocument::fromJson(jsonData, &error); if (error.error != QJsonParseError::NoError) { qCWarning(KGAPIDebug) << "Error parsing event JSON: " << error.errorString(); } QVariantMap data = document.toVariant().toMap(); if (data.value(QStringLiteral("kind")) != QLatin1String("calendar#event")) { return EventPtr(); } return Private::JSONToEvent(data).staticCast(); } ObjectPtr Private::JSONToEvent(const QVariantMap& data, const QString &timezone) { EventPtr event(new Event); /* ID */ event->setUid(QUrl::fromPercentEncoding(data.value(QStringLiteral("id")).toByteArray())); /* ETAG */ event->setEtag(data.value(QStringLiteral("etag")).toString()); /* Status */ if (data.value(QStringLiteral("status")).toString() == QLatin1String("confirmed")) { event->setStatus(KCalCore::Incidence::StatusConfirmed); } else if (data.value(QStringLiteral("status")).toString() == QLatin1String("cancelled")) { event->setStatus(KCalCore::Incidence::StatusCanceled); event->setDeleted(true); } else if (data.value(QStringLiteral("status")).toString() == QLatin1String("tentative")) { event->setStatus(KCalCore::Incidence::StatusTentative); } else { event->setStatus(KCalCore::Incidence::StatusNone); } /* Canceled instance of recurring event. Set ID of the instance to match ID of the event */ if (data.contains(QStringLiteral("recurringEventId"))) { event->setUid(QUrl::fromPercentEncoding(data.value(QStringLiteral("recurringEventId")).toByteArray())); } /* Created */ event->setCreated(Utils::rfc3339DateFromString(data.value(QStringLiteral("created")).toString())); /* Last updated */ event->setLastModified(Utils::rfc3339DateFromString(data.value(QStringLiteral("updated")).toString())); /* Summary */ event->setSummary(data.value(QStringLiteral("summary")).toString()); /* Description */ event->setDescription(data.value(QStringLiteral("description")).toString()); /* Location */ event->setLocation(data.value(QStringLiteral("location")).toString()); /* Start date */ QVariantMap startData = data.value(QStringLiteral("start")).toMap(); QDateTime dtStart; if (startData.contains(QStringLiteral("date"))) { dtStart = QDateTime::fromString(startData.value(QStringLiteral("date")).toString(), Qt::ISODate); event->setAllDay(true); } else if (startData.contains(QStringLiteral("dateTime"))) { dtStart = Utils::rfc3339DateFromString(startData.value(QStringLiteral("dateTime")).toString()); // If there's a timezone specified in the "start" entity, then use it if (startData.contains(QStringLiteral("timeZone"))) { const QTimeZone tz = QTimeZone(startData.value(QStringLiteral("timeZone")).toString().toUtf8()); if (tz.isValid()) { dtStart = dtStart.toTimeZone(tz); } else { qCWarning(KGAPIDebug) << "Invalid timezone" << startData.value(QStringLiteral("timeZone")).toString(); } // Otherwise try to fallback to calendar-wide timezone } else if (!timezone.isEmpty()) { const QTimeZone tz(timezone.toUtf8()); if (tz.isValid()) { dtStart.setTimeZone(tz); } else { qCWarning(KGAPIDebug) << "Invalid timezone" << timezone; } } } event->setDtStart(dtStart); /* End date */ QVariantMap endData = data.value(QStringLiteral("end")).toMap(); QDateTime dtEnd; if (endData.contains(QStringLiteral("date"))) { dtEnd = QDateTime::fromString(endData.value(QStringLiteral("date")).toString(), Qt::ISODate); /* For Google, all-day events starts on Monday and ends on Tuesday, * while in KDE, it both starts and ends on Monday. */ dtEnd = dtEnd.addDays(-1); event->setAllDay(true); } else if (endData.contains(QStringLiteral("dateTime"))) { dtEnd = Utils::rfc3339DateFromString(endData.value(QStringLiteral("dateTime")).toString()); if (endData.contains(QStringLiteral("timeZone"))) { const QTimeZone tz(endData.value(QStringLiteral("timeZone")).toString().toUtf8()); if (tz.isValid()) { dtEnd = dtEnd.toTimeZone(tz); } else { qCWarning(KGAPIDebug) << "Invalid timezone" << endData.value(QStringLiteral("timeZone")).toString(); } } else if (!timezone.isEmpty()) { const QTimeZone tz(timezone.toUtf8()); if (tz.isValid()) { dtEnd = dtEnd.toTimeZone(tz); } else { qCWarning(KGAPIDebug) << "Invalid timezone" << timezone; } } } event->setDtEnd(dtEnd); /* Transparency */ if (data.value(QStringLiteral("transparency")).toString() == QLatin1String("transparent")) { event->setTransparency(Event::Transparent); } else { /* Assume opaque as default transparency */ event->setTransparency(Event::Opaque); } /* Attendees */ const QVariantList attendees = data.value(QStringLiteral("attendees")).toList(); for (const QVariant & a : attendees) { QVariantMap att = a.toMap(); KCalCore::Attendee::Ptr attendee( new KCalCore::Attendee( att.value(QStringLiteral("displayName")).toString(), att.value(QStringLiteral("email")).toString())); if (att.value(QStringLiteral("responseStatus")).toString() == QLatin1String("accepted")) attendee->setStatus(KCalCore::Attendee::Accepted); else if (att.value(QStringLiteral("responseStatus")).toString() == QLatin1String("declined")) attendee->setStatus(KCalCore::Attendee::Declined); else if (att.value(QStringLiteral("responseStatus")).toString() == QLatin1String("tentative")) attendee->setStatus(KCalCore::Attendee::Tentative); else attendee->setStatus(KCalCore::Attendee::NeedsAction); if (att.value(QStringLiteral("optional")).toBool()) { attendee->setRole(KCalCore::Attendee::OptParticipant); } const auto uid = att.value(QStringLiteral("id")).toString(); if (!uid.isEmpty()) { attendee->setUid(uid); } else { // Set some UID, just so that the results are reproducible attendee->setUid(QString::number(qHash(attendee->email()))); } event->addAttendee(attendee, true); } /* According to RFC, only events with attendees can have an organizer. * Google seems to ignore it, so we must take care of it here */ if (event->attendeeCount() > 0) { KCalCore::Person::Ptr organizer(new KCalCore::Person); QVariantMap organizerData = data.value(QStringLiteral("organizer")).toMap(); organizer->setName(organizerData.value(QStringLiteral("displayName")).toString()); organizer->setEmail(organizerData.value(QStringLiteral("email")).toString()); event->setOrganizer(organizer); } /* Recurrence */ const QStringList recrs = data.value(QStringLiteral("recurrence")).toStringList(); for (const QString & rec : recrs) { KCalCore::ICalFormat format; if (rec.left(5) == QLatin1String("RRULE")) { KCalCore::RecurrenceRule *recurrenceRule = new KCalCore::RecurrenceRule(); format.fromString(recurrenceRule, rec.mid(6)); recurrenceRule->setRRule(rec); event->recurrence()->addRRule(recurrenceRule); } else if (rec.left(6) == QLatin1String("EXRULE")) { KCalCore::RecurrenceRule *recurrenceRule = new KCalCore::RecurrenceRule(); format.fromString(recurrenceRule, rec.mid(7)); recurrenceRule->setRRule(rec); event->recurrence()->addExRule(recurrenceRule); } else if (rec.left(6) == QLatin1String("EXDATE")) { KCalCore::DateList exdates = Private::parseRDate(rec); event->recurrence()->setExDates(exdates); } else if (rec.left(5) == QLatin1String("RDATE")) { KCalCore::DateList rdates = Private::parseRDate(rec); event->recurrence()->setRDates(rdates); } } QVariantMap reminders = data.value(QStringLiteral("reminders")).toMap(); if (reminders.contains(QStringLiteral("useDefault")) && reminders.value(QStringLiteral("useDefault")).toBool()) { event->setUseDefaultReminders(true); } else { event->setUseDefaultReminders(false); } const QVariantList overrides = reminders.value(QStringLiteral("overrides")).toList(); for (const QVariant & r : overrides) { QVariantMap override = r.toMap(); KCalCore::Alarm::Ptr alarm(new KCalCore::Alarm(static_cast(event.data()))); alarm->setTime(event->dtStart()); if (override.value(QStringLiteral("method")).toString() == QLatin1String("popup")) { alarm->setType(KCalCore::Alarm::Display); } else if (override.value(QStringLiteral("method")).toString() == QLatin1String("email")) { alarm->setType(KCalCore::Alarm::Email); } else { alarm->setType(KCalCore::Alarm::Invalid); continue; } alarm->setStartOffset(KCalCore::Duration(override.value(QStringLiteral("minutes")).toInt() * (-60))); alarm->setEnabled(true); event->addAlarm(alarm); } /* Extended properties */ QVariantMap extendedProperties = data.value(QStringLiteral("extendedProperties")).toMap(); QVariantMap privateProperties = extendedProperties.value(QStringLiteral("private")).toMap(); QMap< QString, QVariant >::const_iterator iter = privateProperties.constBegin(); while (iter != privateProperties.constEnd()) { if (iter.key() == QLatin1String("categories")) { event->setCategories(iter.value().toString()); } ++iter; } QVariantMap sharedProperties = extendedProperties.value(QStringLiteral("shared")).toMap(); iter = sharedProperties.constBegin(); while (iter != sharedProperties.constEnd()) { if (iter.key() == QLatin1String("categories")) { event->setCategories(iter.value().toString()); } ++iter; } return event.dynamicCast(); } QByteArray eventToJSON(const EventPtr& event) { QVariantMap data; /* Type */ data.insert(QStringLiteral("kind"), QStringLiteral("calendar#event")); /* ID */ if (!event->uid().isEmpty()) { data.insert(QStringLiteral("id"), event->uid()); } /* Status */ if (event->status() == KCalCore::Incidence::StatusConfirmed) { data.insert(QStringLiteral("status"), QStringLiteral("confirmed")); } else if (event->status() == KCalCore::Incidence::StatusCanceled) { data.insert(QStringLiteral("status"), QStringLiteral("canceled")); } else if (event->status() == KCalCore::Incidence::StatusTentative) { data.insert(QStringLiteral("status"), QStringLiteral("tentative")); } /* Summary */ data.insert(QStringLiteral("summary"), event->summary()); /* Description */ data.insert(QStringLiteral("description"), event->description()); /* Location */ data.insert(QStringLiteral("location"), event->location()); /* Recurrence */ QVariantList recurrence; KCalCore::ICalFormat format; const auto exRules = event->recurrence()->exRules(); const auto rRules = event->recurrence()->rRules(); recurrence.reserve(rRules.size() + rRules.size() + 2); for (KCalCore::RecurrenceRule *rRule : rRules) { recurrence << format.toString(rRule).remove(QStringLiteral("\r\n")); } for (KCalCore::RecurrenceRule *rRule : exRules) { recurrence << format.toString(rRule).remove(QStringLiteral("\r\n")); } QStringList dates; const auto rDates = event->recurrence()->rDates(); dates.reserve(rDates.size()); for (const QDate & rDate : rDates) { dates << rDate.toString(QStringLiteral("yyyyMMdd")); } if (!dates.isEmpty()) { recurrence << QString(QStringLiteral("RDATE;VALUE=DATA:") + dates.join(QStringLiteral(","))); } dates.clear(); const auto exDates = event->recurrence()->exDates(); dates.reserve(exDates.size()); for (const QDate & exDate : exDates) { dates << exDate.toString(QStringLiteral("yyyyMMdd")); } if (!dates.isEmpty()) { recurrence << QString(QStringLiteral("EXDATE;VALUE=DATE:") + dates.join(QStringLiteral(","))); } if (!recurrence.isEmpty()) { data.insert(QStringLiteral("recurrence"), recurrence); } /* Start */ QVariantMap start; if (event->allDay()) { start.insert(QStringLiteral("date"), event->dtStart().toString(QStringLiteral("yyyy-MM-dd"))); } else { start.insert(QStringLiteral("dateTime"), Utils::rfc3339DateToString(event->dtStart())); QString tzStart = QString::fromUtf8(event->dtStart().timeZone().id()); if (!recurrence.isEmpty() && tzStart.isEmpty()) { tzStart = QString::fromUtf8(QTimeZone::utc().id()); } if (!tzStart.isEmpty()) { start.insert(QStringLiteral("timeZone"), Private::checkAndConverCDOTZID(tzStart, event)); } } data.insert(QStringLiteral("start"), start); /* End */ QVariantMap end; if (event->allDay()) { /* For Google, all-day events starts on Monday and ends on Tuesday, * while in KDE, it both starts and ends on Monday. */ QDateTime dtEnd = event->dtEnd().addDays(1); end.insert(QStringLiteral("date"), dtEnd.toString(QStringLiteral("yyyy-MM-dd"))); } else { end.insert(QStringLiteral("dateTime"), Utils::rfc3339DateToString(event->dtEnd())); QString tzEnd = QString::fromUtf8(event->dtEnd().timeZone().id()); if (!recurrence.isEmpty() && tzEnd.isEmpty()) { tzEnd = QString::fromUtf8(QTimeZone::utc().id()); } if (!tzEnd.isEmpty()) { end.insert(QStringLiteral("timeZone"), Private::checkAndConverCDOTZID(tzEnd, event)); } } data.insert(QStringLiteral("end"), end); /* Transparency */ if (event->transparency() == Event::Transparent) { data.insert(QStringLiteral("transparency"), QStringLiteral("transparent")); } else { data.insert(QStringLiteral("transparency"), QStringLiteral("opaque")); } /* Attendees */ QVariantList atts; Q_FOREACH(const KCalCore::Attendee::Ptr& attee, event->attendees()) { QVariantMap att; att.insert(QStringLiteral("displayName"), attee->name()); att.insert(QStringLiteral("email"), attee->email()); if (attee->status() == KCalCore::Attendee::Accepted) { att.insert(QStringLiteral("responseStatus"), QStringLiteral("accepted")); } else if (attee->status() == KCalCore::Attendee::Declined) { att.insert(QStringLiteral("responseStatus"), QStringLiteral("declined")); } else if (attee->status() == KCalCore::Attendee::Tentative) { att.insert(QStringLiteral("responseStatus"), QStringLiteral("tentative")); } else { att.insert(QStringLiteral("responseStatus"), QStringLiteral("needsAction")); } if (attee->role() == KCalCore::Attendee::OptParticipant) { att.insert(QStringLiteral("optional"), true); } if (!attee->uid().isEmpty()) { att.insert(QStringLiteral("id"), attee->uid()); } atts.append(att); } if (!atts.isEmpty()) { data.insert(QStringLiteral("attendees"), atts); /* According to RFC, event without attendees should not have * any organizer. */ KCalCore::Person::Ptr organizer = event->organizer(); if (!organizer->isEmpty()) { QVariantMap org; org.insert(QStringLiteral("displayName"), organizer->fullName()); org.insert(QStringLiteral("email"), organizer->email()); data.insert(QStringLiteral("organizer"), org); } } /* Reminders */ QVariantList overrides; Q_FOREACH(const KCalCore::Alarm::Ptr &alarm, event->alarms()) { QVariantMap override; if (alarm->type() == KCalCore::Alarm::Display) { override.insert(QStringLiteral("method"), QLatin1String("popup")); } else if (alarm->type() == KCalCore::Alarm::Email) { override.insert(QStringLiteral("method"), QLatin1String("email")); } else { continue; } override.insert(QStringLiteral("minutes"), (int)(alarm->startOffset().asSeconds() / -60)); overrides << override; } QVariantMap reminders; reminders.insert(QStringLiteral("useDefault"), false); reminders.insert(QStringLiteral("overrides"), overrides); data.insert(QStringLiteral("reminders"), reminders); /* Store categories */ if (!event->categories().isEmpty()) { QVariantMap extendedProperties; QVariantMap sharedProperties; sharedProperties.insert(QStringLiteral("categories"), event->categoriesStr()); extendedProperties.insert(QStringLiteral("shared"), sharedProperties); data.insert(QStringLiteral("extendedProperties"), extendedProperties); } /* TODO: Implement support for additional features: * http://code.google.com/apis/gdata/docs/2.0/elements.html */ QJsonDocument document = QJsonDocument::fromVariant(data); return document.toJson(QJsonDocument::Compact); } ObjectsList parseEventJSONFeed(const QByteArray& jsonFeed, FeedData& feedData) { QJsonDocument document = QJsonDocument::fromJson(jsonFeed); QVariantMap data = document.toVariant().toMap(); QString timezone; if (data.value(QStringLiteral("kind")) == QLatin1String("calendar#events")) { if (data.contains(QStringLiteral("nextPageToken"))) { QString calendarId = feedData.requestUrl.toString().remove(QStringLiteral("https://www.googleapis.com/calendar/v3/calendars/")); calendarId = calendarId.left(calendarId.indexOf(QLatin1Char('/'))); feedData.nextPageUrl = feedData.requestUrl; // replace the old pageToken with a new one - feedData.nextPageUrl.removeQueryItem(QStringLiteral("pageToken")); - feedData.nextPageUrl.addQueryItem(QStringLiteral("pageToken"), data.value(QStringLiteral("nextPageToken")).toString()); - if (feedData.nextPageUrl.queryItemValue(QStringLiteral("maxResults")).isEmpty()) { - feedData.nextPageUrl.addQueryItem(QStringLiteral("maxResults"), QStringLiteral("20")); + QUrlQuery query(feedData.nextPageUrl); + query.removeQueryItem(QStringLiteral("pageToken")); + query.addQueryItem(QStringLiteral("pageToken"), data.value(QStringLiteral("nextPageToken")).toString()); + if (query.queryItemValue(QStringLiteral("maxResults")).isEmpty()) { + query.addQueryItem(QStringLiteral("maxResults"), QStringLiteral("20")); } + feedData.nextPageUrl.setQuery(query); } if (data.contains(QStringLiteral("timeZone"))) { // This should always be in Olson format timezone = data.value(QStringLiteral("timeZone")).toString(); } } else { return ObjectsList(); } ObjectsList list; const QVariantList items = data.value(QStringLiteral("items")).toList(); list.reserve(items.size()); for (const QVariant &i : items) { list.append(Private::JSONToEvent(i.toMap(), timezone)); } return list; } /******************************** PRIVATE ***************************************/ KCalCore::DateList Private::parseRDate(const QString& rule) { KCalCore::DateList list; QString value; QTimeZone tz; QString left = rule.left(rule.indexOf(QLatin1Char(':'))); const QStringList params = left.split(QLatin1Char(';')); for (const QString ¶m : params) { if (param.startsWith(QLatin1String("VALUE"))) { value = param.mid(param.indexOf(QLatin1Char('=')) + 1); } else if (param.startsWith(QLatin1String("TZID"))) { QString _name = param.mid(param.indexOf(QLatin1Char('=')) + 1); tz = QTimeZone(_name.toUtf8()); } } QString datesStr = rule.mid(rule.lastIndexOf(QLatin1Char(':')) + 1); const QStringList dates = datesStr.split(QLatin1Char(',')); for (const QString &date : dates) { QDate dt; if (value == QLatin1String("DATE")) { dt = QDate::fromString(date, QStringLiteral("yyyyMMdd")); } else if (value == QLatin1String("PERIOD")) { QString start = date.left(date.indexOf(QLatin1Char('/'))); QDateTime kdt = Utils::rfc3339DateFromString(start); if (tz.isValid()) { kdt.setTimeZone(tz); } dt = kdt.date(); } else { QDateTime kdt = Utils::rfc3339DateFromString(date); if (tz.isValid()) { kdt.setTimeZone(tz); } dt = kdt.date(); } list << dt; } return list; } static QMap initMSCDOTZIDTable() { QMap map; /* Based on "Time Zone to CdoTimeZoneId Map" * http://msdn.microsoft.com/en-us/library/aa563018%28loband%29.aspx * * The mapping is not exact, since the CdoTimeZoneId usually refers to a * region of multiple countries, so I always picked one of the countries * in the specified region and used it's TZID. */ map.insert(0, QStringLiteral("UTC")); map.insert(1, QStringLiteral("Europe/London")); /* GMT Greenwich Mean Time; Dublin, Edinburgh, London */ /* Seriously? *sigh* Let's handle these two in checkAndConvertCDOTZID() */ //map.insertMulti(2, QStringLiteral("Europe/Lisbon")); /* GMT Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London */ //map.insertMulti(2, QStringLiteral("Europe/Sarajevo")); /* GMT+01:00 Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb */ map.insert(3, QStringLiteral("Europe/Paris")); /* GMT+01:00 Paris, Madrid, Brussels, Copenhagen */ map.insert(4, QStringLiteral("Europe/Berlin")); /* GMT+01:00 Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna */ map.insert(5, QStringLiteral("Europe/Bucharest")); /* GMT+02:00 Bucharest */ map.insert(6, QStringLiteral("Europe/Prague")); /* GMT+01:00 Prague, Central Europe */ map.insert(7, QStringLiteral("Europe/Athens")); /* GMT+02:00 Athens, Istanbul, Minsk */ map.insert(8, QStringLiteral("America/Brazil")); /* GMT-03:00 Brasilia */ map.insert(9, QStringLiteral("America/Halifax")); /* GMT-04:00 Atlantic time (Canada) */ map.insert(10, QStringLiteral("America/New_York")); /* GMT-05:00 Eastern Time (US & Canada) */ map.insert(11, QStringLiteral("America/Chicago")); /* GMT-06:00 Central Time (US & Canada) */ map.insert(12, QStringLiteral("America/Denver")); /* GMT-07:00 Mountain Time (US & Canada) */ map.insert(13, QStringLiteral("America/Los_Angeles")); /* GMT-08:00 Pacific Time (US & Canada); Tijuana */ map.insert(14, QStringLiteral("America/Anchorage")); /* GMT-09:00 Alaska */ map.insert(15, QStringLiteral("Pacific/Honolulu")); /* GMT-10:00 Hawaii */ map.insert(16, QStringLiteral("Pacific/Apia")); /* GMT-11:00 Midway Island, Samoa */ map.insert(17, QStringLiteral("Pacific/Auckland")); /* GMT+12:00 Auckland, Wellington */ map.insert(18, QStringLiteral("Australia/Brisbane")); /* GMT+10:00 Brisbane, East Australia */ map.insert(19, QStringLiteral("Australia/Adelaide")); /* GMT+09:30 Adelaide, Central Australia */ map.insert(20, QStringLiteral("Asia/Tokyo")); /* GMT+09:00 Osaka, Sapporo, Tokyo */ map.insert(21, QStringLiteral("Asia/Singapore")); /* GMT+08:00 Kuala Lumpur, Singapore */ map.insert(22, QStringLiteral("Asia/Bangkok")); /* GMT+07:00 Bangkok, Hanoi, Jakarta */ map.insert(23, QStringLiteral("Asia/Calcutta")); /* GMT+05:30 Kolkata, Chennai, Mumbai, New Delhi, India Standard Time */ map.insert(24, QStringLiteral("Asia/Dubai")); /* GMT+04:00 Abu Dhabi, Muscat */ map.insert(25, QStringLiteral("Asia/Tehran")); /* GMT+03:30 Tehran */ map.insert(26, QStringLiteral("Asia/Baghdad")); /* GMT+03:00 Baghdad */ map.insert(27, QStringLiteral("Asia/Jerusalem")); /* GMT+02:00 Israel, Jerusalem Standard Time */ map.insert(28, QStringLiteral("America/St_Johns")); /* GMT-03:30 Newfoundland */ map.insert(29, QStringLiteral("Atlantic/Portugal")); /* GMT-01:00 Azores */ map.insert(30, QStringLiteral("America/Noronha")); /* GMT-02:00 Mid-Atlantic */ map.insert(31, QStringLiteral("Africa/Monrovia")); /* GMT Casablanca, Monrovia */ map.insert(32, QStringLiteral("America/Argentina/Buenos_Aires")); /* GMT-03:00 Buenos Aires, Georgetown */ map.insert(33, QStringLiteral("America/La_Paz")); /* GMT-04:00 Caracas, La Paz */ map.insert(34, QStringLiteral("America/New_York")); /* GMT-05:00 Indiana (East) */ map.insert(35, QStringLiteral("America/Bogota")); /* GMT-05:00 Bogota, Lima, Quito */ map.insert(36, QStringLiteral("America/Winnipeg")); /* GMT-06:00 Saskatchewan */ map.insert(37, QStringLiteral("America/Mexico_City")); /* GMT-06:00 Mexico City, Tegucigalpa */ map.insert(38, QStringLiteral("America/Phoenix")); /* GMT-07:00 Arizona */ map.insert(39, QStringLiteral("Pacific/Kwajalein")); /* GMT-12:00 Eniwetok, Kwajalein, Dateline Time */ map.insert(40, QStringLiteral("Pacific/Fiji")); /* GMT+12:00 Fušál, Kamchatka, Mashall Is. */ map.insert(41, QStringLiteral("Pacific/Noumea")); /* GMT+11:00 Magadan, Solomon Is., New Caledonia */ map.insert(42, QStringLiteral("Australia/Hobart")); /* GMT+10:00 Hobart, Tasmania */ map.insert(43, QStringLiteral("Pacific/Guam")); /* GMT+10:00 Guam, Port Moresby */ map.insert(44, QStringLiteral("Australia/Darwin")); /* GMT+09:30 Darwin */ map.insert(45, QStringLiteral("Asia/Shanghai")); /* GMT+08:00 Beijing, Chongqing, Hong Kong SAR, Urumqi */ map.insert(46, QStringLiteral("Asia/Omsk")); /* GMT+06:00 Almaty, Novosibirsk, North Central Asia */ map.insert(47, QStringLiteral("Asia/Karachi")); /* GMT+05:00 Islamabad, Karachi, Tashkent */ map.insert(48, QStringLiteral("Asia/Kabul")); /* GMT+04:30 Kabul */ map.insert(49, QStringLiteral("Africa/Cairo")); /* GMT+02:00 Cairo */ map.insert(50, QStringLiteral("Africa/Harare")); /* GMT+02:00 Harare, Pretoria */ map.insert(51, QStringLiteral("Europe/Moscow")); /* GMT+03:00 Moscow, St. Petersburg, Volgograd */ map.insert(53, QStringLiteral("Atlantic/Cape_Verde")); /* GMT-01:00 Cape Verde Is. */ map.insert(54, QStringLiteral("Asia/Tbilisi")); /* GMT+04:00 Baku, Tbilisi, Yerevan */ map.insert(55, QStringLiteral("America/Tegucigalpa")); /* GMT-06:00 Central America */ map.insert(56, QStringLiteral("Africa/Nairobi")); /* GMT+03:00 East Africa, Nairobi */ map.insert(58, QStringLiteral("Asia/Yekaterinburg")); /* GMT+05:00 Ekaterinburg */ map.insert(59, QStringLiteral("Europe/Helsinki")); /* GMT+02:00 Helsinki, Riga, Tallinn */ map.insert(60, QStringLiteral("America/Greenland")); /* GMT-03:00 Greenland */ map.insert(61, QStringLiteral("Asia/Rangoon")); /* GMT+06:30 Yangon (Rangoon) */ map.insert(62, QStringLiteral("Asia/Katmandu")); /* GMT+05:45 Kathmandu, Nepal */ map.insert(63, QStringLiteral("Asia/Irkutsk")); /* GMT+08:00 Irkutsk, Ulaan Bataar */ map.insert(64, QStringLiteral("Asia/Krasnoyarsk")); /* GMT+07:00 Krasnoyarsk */ map.insert(65, QStringLiteral("America/Santiago")); /* GMT-04:00 Santiago */ map.insert(66, QStringLiteral("Asia/Colombo")); /* GMT+06:00 Sri Jayawardenepura, Sri Lanka */ map.insert(67, QStringLiteral("Pacific/Tongatapu")); /* GMT+13:00 Nuku'alofa, Tonga */ map.insert(68, QStringLiteral("Asia/Vladivostok")); /* GMT+10:00 Vladivostok */ map.insert(69, QStringLiteral("Africa/Bangui")); /* GMT+01:00 West Central Africa */ map.insert(70, QStringLiteral("Asia/Yakutsk")); /* GMT+09:00 Yakutsk */ map.insert(71, QStringLiteral("Asia/Dhaka")); /* GMT+06:00 Astana, Dhaka */ map.insert(72, QStringLiteral("Asia/Seoul")); /* GMT+09:00 Seoul, Korea Standard time */ map.insert(73, QStringLiteral("Australia/Perth")); /* GMT+08:00 Perth, Western Australia */ map.insert(74, QStringLiteral("Asia/Kuwait")); /* GMT+03:00 Arab, Kuwait, Riyadh */ map.insert(75, QStringLiteral("Asia/Taipei")); /* GMT+08:00 Taipei */ map.insert(76, QStringLiteral("Australia/Sydney")); /* GMT+10:00 Canberra, Melbourne, Sydney */ return map; } static QMap initMSStandardTimeTZTable() { QMap map; /* Based on "Microsoft Time Zone Index Values" * http://support.microsoft.com/kb/973627 * * The mapping is not exact, since the TZID usually refers to a * region of multiple countries, so I always picked one of the countries * in the specified region and used it's TZID. * * The Olson timezones are taken from https://en.wikipedia.org/wiki/List_of_tz_database_time_zones */ map.insert(QStringLiteral("Dateline Standard Time"), QStringLiteral("Pacific/Kwajalein")); /* (GMT-12:00) International Date Line West */ map.insert(QStringLiteral("Samoa Standard Time"), QStringLiteral("Pacific/Apia")); /* (GMT-11:00) Midway Island, Samoa */ map.insert(QStringLiteral("Hawaiian Standard Time"), QStringLiteral("Pacific/Honolulu")); /* (GMT-10:00) Hawaii */ map.insert(QStringLiteral("Alaskan Standard Time"), QStringLiteral("America/Anchorage")); /* (GMT-09:00) Alaska */ map.insert(QStringLiteral("Pacific Standard Time"), QStringLiteral("America/Los_Angeles")); /* (GMT-08:00) Pacific Time (US and Canada); Tijuana */ map.insert(QStringLiteral("Mountain Standard Time"), QStringLiteral("America/Denver")); /* (GMT-07:00) Mountain Time (US and Canada) */ map.insert(QStringLiteral("Mexico Standard Time 2"), QStringLiteral("America/Chihuahua")); /* (GMT-07:00) Chihuahua, La Paz, Mazatlan */ map.insert(QStringLiteral("U.S. Mountain Standard Time"), QStringLiteral("America/Phoenix")); /* (GMT-07:00) Arizona */ map.insert(QStringLiteral("Central Standard Time"), QStringLiteral("America/Chicago")); /* (GMT-06:00) Central Time (US and Canada */ map.insert(QStringLiteral("Canada Central Standard Time"), QStringLiteral("America/Winnipeg")); /* (GMT-06:00) Saskatchewan */ map.insert(QStringLiteral("Mexico Standard Time"), QStringLiteral("America/Mexico_City")); /* (GMT-06:00) Guadalajara, Mexico City, Monterrey */ map.insert(QStringLiteral("Central America Standard Time"), QStringLiteral("America/Chicago")); /* (GMT-06:00) Central America */ map.insert(QStringLiteral("Eastern Standard Time"), QStringLiteral("America/New_York")); /* (GMT-05:00) Eastern Time (US and Canada) */ map.insert(QStringLiteral("U.S. Eastern Standard Time"), QStringLiteral("America/New_York")); /* (GMT-05:00) Indiana (East) */ map.insert(QStringLiteral("S.A. Pacific Standard Time"), QStringLiteral("America/Bogota")); /* (GMT-05:00) Bogota, Lima, Quito */ map.insert(QStringLiteral("Atlantic Standard Time"), QStringLiteral("America/Halifax")); /* (GMT-04:00) Atlantic Time (Canada) */ map.insert(QStringLiteral("S.A. Western Standard Time"), QStringLiteral("America/La_Paz")); /* (GMT-04:00) Caracas, La Paz */ map.insert(QStringLiteral("Pacific S.A. Standard Time"), QStringLiteral("America/Santiago")); /* (GMT-04:00) Santiago */ map.insert(QStringLiteral("Newfoundland and Labrador Standard Time"), QStringLiteral("America/St_Johns")); /* (GMT-03:30) Newfoundland and Labrador */ map.insert(QStringLiteral("E. South America Standard Time"), QStringLiteral("America/Brazil")); /* (GMT-03:00) Brasilia */ map.insert(QStringLiteral("S.A. Eastern Standard Time"), QStringLiteral("America/Argentina/Buenos_Aires")); /* (GMT-03:00) Buenos Aires, Georgetown */ map.insert(QStringLiteral("Greenland Standard Time"), QStringLiteral("America/Greenland")); /* (GMT-03:00) Greenland */ map.insert(QStringLiteral("Mid-Atlantic Standard Time"), QStringLiteral("America/Noronha")); /* (GMT-02:00) Mid-Atlantic */ map.insert(QStringLiteral("Azores Standard Time"), QStringLiteral("Atlantic/Portugal")); /* (GMT-01:00) Azores */ map.insert(QStringLiteral("Cape Verde Standard Time"), QStringLiteral("Atlantic/Cape_Verde")); /* (GMT-01:00) Cape Verde Islands */ map.insert(QStringLiteral("GMT Standard Time"), QStringLiteral("Europe/London")); /* (GMT) Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London */ map.insert(QStringLiteral("Greenwich Standard Time"), QStringLiteral("Africa/Casablanca")); /* (GMT) Casablanca, Monrovia */ map.insert(QStringLiteral("Central Europe Standard Time"), QStringLiteral("Europe/Prague")); /* (GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague */ map.insert(QStringLiteral("Central European Standard Time"), QStringLiteral("Europe/Sarajevo")); /* (GMT+01:00) Sarajevo, Skopje, Warsaw, Zagreb */ map.insert(QStringLiteral("Romance Standard Time"), QStringLiteral("Europe/Brussels")); /* (GMT+01:00) Brussels, Copenhagen, Madrid, Paris */ map.insert(QStringLiteral("W. Europe Standard Time"), QStringLiteral("Europe/Amsterdam")); /* (GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna */ map.insert(QStringLiteral("W. Central Africa Standard Time"), QStringLiteral("Africa/Bangui")); /* (GMT+01:00) West Central Africa */ map.insert(QStringLiteral("E. Europe Standard Time"), QStringLiteral("Europe/Bucharest")); /* (GMT+02:00) Bucharest */ map.insert(QStringLiteral("Egypt Standard Time"), QStringLiteral("Africa/Cairo")); /* (GMT+02:00) Cairo */ map.insert(QStringLiteral("FLE Standard Time"), QStringLiteral("Europe/Helsinki")); /* (GMT+02:00) Helsinki, Kiev, Riga, Sofia, Tallinn, Vilnius */ map.insert(QStringLiteral("GTB Standard Time"), QStringLiteral("Europe/Athens")); /* (GMT+02:00) Athens, Istanbul, Minsk */ map.insert(QStringLiteral("Israel Standard Time"), QStringLiteral("Europe/Athens")); /* (GMT+02:00) Jerusalem */ map.insert(QStringLiteral("South Africa Standard Time"), QStringLiteral("Africa/Harare")); /* (GMT+02:00) Harare, Pretoria */ map.insert(QStringLiteral("Russian Standard Time"), QStringLiteral("Europe/Moscow")); /* (GMT+03:00) Moscow, St. Petersburg, Volgograd */ map.insert(QStringLiteral("Arab Standard Time"), QStringLiteral("Asia/Kuwait")); /* (GMT+03:00) Kuwait, Riyadh */ map.insert(QStringLiteral("E. Africa Standard Time"), QStringLiteral("Africa/Nairobi")); /* (GMT+03:00) Nairobi */ map.insert(QStringLiteral("Arabic Standard Time"), QStringLiteral("Asia/Baghdad")); /* (GMT+03:00) Baghdad */ map.insert(QStringLiteral("Iran Standard Time"), QStringLiteral("Asia/Tehran")); /* (GMT+03:30) Tehran */ map.insert(QStringLiteral("Arabian Standard Time"), QStringLiteral("Asia/Dubai")); /* (GMT+04:00) Abu Dhabi, Muscat */ map.insert(QStringLiteral("Caucasus Standard Time"), QStringLiteral("Asia/Tbilisi")); /* (GMT+04:00) Baku, Tbilisi, Yerevan */ map.insert(QStringLiteral("Transitional Islamic State of Afghanistan Standard Time"), QStringLiteral("Asia/Kabul")); /* (GMT+04:30) Kabul */ map.insert(QStringLiteral("Ekaterinburg Standard Time"), QStringLiteral("Asia/Yekaterinburg")); /* (GMT+05:00) Ekaterinburg */ map.insert(QStringLiteral("West Asia Standard Time"), QStringLiteral("Asia/Karachi")); /* (GMT+05:00) Islamabad, Karachi, Tashkent */ map.insert(QStringLiteral("India Standard Time"), QStringLiteral("Asia/Calcutta")); /* (GMT+05:30) Chennai, Kolkata, Mumbai, New Delhi */ map.insert(QStringLiteral("Nepal Standard Time"), QStringLiteral("Asia/Calcutta")); /* (GMT+05:45) Kathmandu */ map.insert(QStringLiteral("Central Asia Standard Time"), QStringLiteral("Asia/Dhaka")); /* (GMT+06:00) Astana, Dhaka */ map.insert(QStringLiteral("Sri Lanka Standard Time"), QStringLiteral("Asia/Colombo")); /* (GMT+06:00) Sri Jayawardenepura */ map.insert(QStringLiteral("N. Central Asia Standard Time"), QStringLiteral("Asia/Omsk")); /* (GMT+06:00) Almaty, Novosibirsk */ map.insert(QStringLiteral("Myanmar Standard Time"), QStringLiteral("Asia/Rangoon")); /* (GMT+06:30) Yangon Rangoon */ map.insert(QStringLiteral("S.E. Asia Standard Time"), QStringLiteral("Asia/Bangkok")); /* (GMT+07:00) Bangkok, Hanoi, Jakarta */ map.insert(QStringLiteral("North Asia Standard Time"), QStringLiteral("Asia/Krasnoyarsk")); /* (GMT+07:00) Krasnoyarsk */ map.insert(QStringLiteral("China Standard Time"), QStringLiteral("Asia/Shanghai")); /* (GMT+08:00) Beijing, Chongqing, Hong Kong SAR, Urumqi */ map.insert(QStringLiteral("Singapore Standard Time"), QStringLiteral("Asia/Singapore")); /* (GMT+08:00) Kuala Lumpur, Singapore */ map.insert(QStringLiteral("Taipei Standard Time"), QStringLiteral("Asia/Taipei")); /* (GMT+08:00) Taipei */ map.insert(QStringLiteral("W. Australia Standard Time"), QStringLiteral("Australia/Perth")); /* (GMT+08:00) Perth */ map.insert(QStringLiteral("North Asia East Standard Time"), QStringLiteral("Asia/Irkutsk")); /* (GMT+08:00) Irkutsk, Ulaanbaatar */ map.insert(QStringLiteral("Korea Standard Time"), QStringLiteral("Asia/Seoul")); /* (GMT+09:00) Seoul */ map.insert(QStringLiteral("Tokyo Standard Time"), QStringLiteral("Asia/Tokyo")); /* (GMT+09:00) Osaka, Sapporo, Tokyo */ map.insert(QStringLiteral("Yakutsk Standard Time"), QStringLiteral("Asia/Yakutsk")); /* (GMT+09:00) Yakutsk */ map.insert(QStringLiteral("A.U.S. Central Standard Time"), QStringLiteral("Australia/Darwin")); /* (GMT+09:30) Darwin */ map.insert(QStringLiteral("Cen. Australia Standard Time"), QStringLiteral("Australia/Adelaide")); /* (GMT+09:30) Adelaide */ map.insert(QStringLiteral("A.U.S. Eastern Standard Time"), QStringLiteral("Australia/Sydney")); /* (GMT+10:00) Canberra, Melbourne, Sydney */ map.insert(QStringLiteral("E. Australia Standard Time"), QStringLiteral("Australia/Brisbane")); /* (GMT+10:00) Brisbane */ map.insert(QStringLiteral("Tasmania Standard Time"), QStringLiteral("Australia/Hobart")); /* (GMT+10:00) Hobart */ map.insert(QStringLiteral("Vladivostok Standard Time"), QStringLiteral("Asia/Vladivostok")); /* (GMT+10:00) Vladivostok */ map.insert(QStringLiteral("West Pacific Standard Time"), QStringLiteral("Pacific/Guam")); /* (GMT+10:00) Guam, Port Moresby */ map.insert(QStringLiteral("Central Pacific Standard Time"), QStringLiteral("Pacific/Noumea")); /* (GMT+11:00) Magadan, Solomon Islands, New Caledonia */ map.insert(QStringLiteral("Fiji Islands Standard Time"), QStringLiteral("Pacific/Fiji")); /* (GMT+12:00) Fiji Islands, Kamchatka, Marshall Islands */ map.insert(QStringLiteral("New Zealand Standard Time"), QStringLiteral("Pacific/Auckland")); /* (GMT+12:00) Auckland, Wellington */ map.insert(QStringLiteral("Tonga Standard Time"), QStringLiteral("Pacific/Tongatapu")); /* (GMT+13:00) Nuku'alofa */ map.insert(QStringLiteral("Azerbaijan Standard Time"), QStringLiteral("America/Argentina/Buenos_Aires")); /* (GMT-03:00) Buenos Aires */ map.insert(QStringLiteral("Middle East Standard Time"), QStringLiteral("Asia/Beirut")); /* (GMT+02:00) Beirut */ map.insert(QStringLiteral("Jordan Standard Time"), QStringLiteral("Asia/Amman")); /* (GMT+02:00) Amman */ map.insert(QStringLiteral("Central Standard Time (Mexico)"), QStringLiteral("America/Mexico_City")); /* (GMT-06:00) Guadalajara, Mexico City, Monterrey - New */ map.insert(QStringLiteral("Mountain Standard Time (Mexico)"), QStringLiteral("America/Ojinaga")); /* (GMT-07:00) Chihuahua, La Paz, Mazatlan - New */ map.insert(QStringLiteral("Pacific Standard Time (Mexico)"), QStringLiteral("America/Tijuana")); /* (GMT-08:00) Tijuana, Baja California */ map.insert(QStringLiteral("Namibia Standard Time"), QStringLiteral("Africa/Windhoek")); /* (GMT+02:00) Windhoek */ map.insert(QStringLiteral("Georgian Standard Time"), QStringLiteral("Asia/Tbilisi")); /* (GMT+03:00) Tbilisi */ map.insert(QStringLiteral("Central Brazilian Standard Time"), QStringLiteral("America/Manaus")); /*(GMT-04:00) Manaus */ map.insert(QStringLiteral("Montevideo Standard Time"), QStringLiteral("America/Montevideo")); /* (GMT-03:00) Montevideo */ map.insert(QStringLiteral("Armenian Standard Time"), QStringLiteral("Asia/Yerevan")); /* (GMT+04:00) Yerevan */ map.insert(QStringLiteral("Venezuela Standard Time"), QStringLiteral("America/Caracas")); /* (GMT-04:30) Caracas */ map.insert(QStringLiteral("Argentina Standard Time"), QStringLiteral("America/Argentina/Buenos_Aires")); /* (GMT-03:00) Buenos Aires */ map.insert(QStringLiteral("Morocco Standard Time"), QStringLiteral("Africa/Casablanca")); /* (GMT) Casablanca */ map.insert(QStringLiteral("Pakistan Standard Time"), QStringLiteral("Asia/Karachi")); /* (GMT+05:00) Islamabad, Karachi */ map.insert(QStringLiteral("Mauritius Standard Time"), QStringLiteral("Indian/Mauritius")); /* (GMT+04:00) Port Louis */ map.insert(QStringLiteral("UTC"), QStringLiteral("UTC")); /* (GMT) Coordinated Universal Time */ map.insert(QStringLiteral("Paraguay Standard Time"), QStringLiteral("America/Asuncion")); /* (GMT-04:00) Asuncion */ map.insert(QStringLiteral("Kamchatka Standard Time"), QStringLiteral("Asia/Kamchatka")); /* (GMT+12:00) Petropavlovsk-Kamchatsky */ return map; } static const QMap MSCDOTZIDTable = initMSCDOTZIDTable(); static const QMap MSSTTZTable = initMSStandardTimeTZTable(); QString Private::checkAndConverCDOTZID(const QString& tzid, const EventPtr& event) { /* Try to match the @tzid to any valid timezone we know. */ QTimeZone tz(tzid.toUtf8()); if (tz.isValid()) { /* Yay, @tzid is a valid TZID in Olson format */ return tzid; } /* Damn, no match. Parse the iCal and try to find X-MICROSOFT-CDO-TZID * property that we can match against the MSCDOTZIDTable */ KCalCore::ICalFormat format; /* Use a copy of @event, otherwise it would be deleted when ptr is destroyed */ KCalCore::Incidence::Ptr incidence = event.dynamicCast(); const QString vcard = format.toICalString(incidence); const QStringList properties = vcard.split(QLatin1Char('\n')); int CDOId = -1; for (const QString &property : properties) { if (property.startsWith(QLatin1String("X-MICROSOFT-CDO-TZID"))) { QStringList parsed = property.split(QLatin1Char(':')); if (parsed.length() != 2) { break; } CDOId = parsed.at(1).toInt(); break; } } /* Wheeee, we have X-MICROSOFT-CDO-TZID, try to map it to Olson format */ if (CDOId > -1) { /* *sigh* Some expert in MS assigned the same ID to two two different timezones... */ if (CDOId == 2) { /* GMT Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London */ if (tzid.contains(QStringLiteral("Dublin")) || tzid.contains(QStringLiteral("Edinburgh")) || tzid.contains(QStringLiteral("Lisbon")) || tzid.contains(QStringLiteral("London"))) { return QStringLiteral("Europe/London"); } /* GMT+01:00 Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb */ else if (tzid.contains(QStringLiteral("Sarajevo")) || tzid.contains(QStringLiteral("Skopje")) || tzid.contains(QStringLiteral("Sofija")) || tzid.contains(QStringLiteral("Vilnius")) || tzid.contains(QStringLiteral("Warsaw")) || tzid.contains(QStringLiteral("Zagreb"))) { return QStringLiteral("Europe/Sarajevo"); } } if (MSCDOTZIDTable.contains(CDOId)) { return MSCDOTZIDTable.value(CDOId); } } /* We failed to map to X-MICROSOFT-CDO-TZID. Let's try mapping the TZID * onto the Microsoft Standard Time Time Zones */ if (MSSTTZTable.contains(tzid)) { return MSSTTZTable.value(tzid); } /* Fail/ Just return the original TZID and hope Google will accept it * (though we know it won't) */ return tzid; } } // namespace CalendarService } // namespace KGAPI2 diff --git a/src/calendar/eventfetchjob.cpp b/src/calendar/eventfetchjob.cpp index bed5fa7..612ccec 100644 --- a/src/calendar/eventfetchjob.cpp +++ b/src/calendar/eventfetchjob.cpp @@ -1,229 +1,231 @@ /* * This file is part of LibKGAPI library * * Copyright (C) 2013 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "eventfetchjob.h" #include "calendarservice.h" #include "account.h" #include "../debug.h" #include "event.h" #include "utils.h" #include #include - +#include using namespace KGAPI2; class Q_DECL_HIDDEN EventFetchJob::Private { public: Private(EventFetchJob *parent); QNetworkRequest createRequest(const QUrl &url); QString calendarId; QString eventId; QString filter; bool fetchDeleted; quint64 updatedTimestamp; quint64 timeMin; quint64 timeMax; private: EventFetchJob * const q; }; EventFetchJob::Private::Private(EventFetchJob* parent): fetchDeleted(true), updatedTimestamp(0), timeMin(0), timeMax(0), q(parent) { } QNetworkRequest EventFetchJob::Private::createRequest(const QUrl& url) { QNetworkRequest request; request.setRawHeader("Authorization", "Bearer " + q->account()->accessToken().toLatin1()); request.setRawHeader("GData-Version", CalendarService::APIVersion().toLatin1()); request.setUrl(url); QStringList headers; auto rawHeaderList = request.rawHeaderList(); headers.reserve(rawHeaderList.size()); for (const QByteArray &str : qAsConst(rawHeaderList)) { headers << QLatin1String(str) + QLatin1String(": ") + QLatin1String(request.rawHeader(str)); } qCDebug(KGAPIRaw) << headers; return request; } EventFetchJob::EventFetchJob(const QString& calendarId, const AccountPtr& account, QObject* parent): FetchJob(account, parent), d(new Private(this)) { d->calendarId = calendarId; } EventFetchJob::EventFetchJob(const QString& eventId, const QString& calendarId, const AccountPtr& account, QObject* parent): FetchJob(account, parent), d(new Private(this)) { d->calendarId = calendarId; d->eventId = eventId; } EventFetchJob::~EventFetchJob() { delete d; } void EventFetchJob::setFetchDeleted(bool fetchDeleted) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify fetchDeleted property when job is running"; return; } d->fetchDeleted = fetchDeleted; } bool EventFetchJob::fetchDeleted() { return d->fetchDeleted; } void EventFetchJob::setFetchOnlyUpdated(quint64 timestamp) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify setFetchOnlyUpdated property when job is running"; return; } d->updatedTimestamp = timestamp; } quint64 EventFetchJob::fetchOnlyUpdated() { return d->updatedTimestamp; } void EventFetchJob::setTimeMax(quint64 timestamp) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify timeMax property when job is running"; return; } d->timeMax = timestamp; } quint64 EventFetchJob::timeMax() const { return d->timeMax; } void EventFetchJob::setTimeMin(quint64 timestamp) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify timeMin property when job is running"; return; } d->timeMin = timestamp; } quint64 EventFetchJob::timeMin() const { return d->timeMin; } void EventFetchJob::setFilter(const QString &query) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify filter property when job is running"; return; } d->filter = query; } QString EventFetchJob::filter() const { return d->filter; } void EventFetchJob::start() { QUrl url; if (d->eventId.isEmpty()) { url = CalendarService::fetchEventsUrl(d->calendarId); - url.addQueryItem(QStringLiteral("showDeleted"), Utils::bool2Str(d->fetchDeleted)); + QUrlQuery query(url); + query.addQueryItem(QStringLiteral("showDeleted"), Utils::bool2Str(d->fetchDeleted)); if (!d->filter.isEmpty()) { - url.addQueryItem(QStringLiteral("q"), d->filter); + query.addQueryItem(QStringLiteral("q"), d->filter); } if (d->updatedTimestamp > 0) { - url.addQueryItem(QStringLiteral("updatedMin"), Utils::ts2Str(d->updatedTimestamp)); + query.addQueryItem(QStringLiteral("updatedMin"), Utils::ts2Str(d->updatedTimestamp)); } if (d->timeMin > 0) { - url.addQueryItem(QStringLiteral("timeMin"), Utils::ts2Str(d->timeMin)); + query.addQueryItem(QStringLiteral("timeMin"), Utils::ts2Str(d->timeMin)); } if (d->timeMax > 0) { - url.addQueryItem(QStringLiteral("timeMax"), Utils::ts2Str(d->timeMax)); + query.addQueryItem(QStringLiteral("timeMax"), Utils::ts2Str(d->timeMax)); } + url.setQuery(query); } else { url = CalendarService::fetchEventUrl(d->calendarId, d->eventId); } const QNetworkRequest request = d->createRequest(url); enqueueRequest(request); } ObjectsList EventFetchJob::handleReplyWithItems(const QNetworkReply *reply, const QByteArray& rawData) { FeedData feedData; feedData.requestUrl = reply->url(); ObjectsList items; const QString contentType = reply->header(QNetworkRequest::ContentTypeHeader).toString(); ContentType ct = Utils::stringToContentType(contentType); if (ct == KGAPI2::JSON) { if (d->eventId.isEmpty()) { items = CalendarService::parseEventJSONFeed(rawData, feedData); } else { items << CalendarService::JSONToEvent(rawData).dynamicCast(); } } else { setError(KGAPI2::InvalidResponse); setErrorString(tr("Invalid response content type")); emitFinished(); return items; } if (feedData.nextPageUrl.isValid()) { const QNetworkRequest request = d->createRequest(feedData.nextPageUrl); enqueueRequest(request); } return items; } diff --git a/src/contacts/contactfetchjob.cpp b/src/contacts/contactfetchjob.cpp index c3f3cb0..3cc4d2e 100644 --- a/src/contacts/contactfetchjob.cpp +++ b/src/contacts/contactfetchjob.cpp @@ -1,189 +1,191 @@ /* * This file is part of LibKGAPI library * * Copyright (C) 2013 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "contactfetchjob.h" #include "contact.h" #include "contactsservice.h" #include "../debug.h" #include "utils.h" #include "account.h" #include #include - +#include using namespace KGAPI2; class Q_DECL_HIDDEN ContactFetchJob::Private { public: Private(ContactFetchJob *parent); QNetworkRequest createRequest(const QUrl &url); bool fetchDeleted; QString contactId; quint64 timestamp; QString filter; private: ContactFetchJob * const q; }; ContactFetchJob::Private::Private(ContactFetchJob *parent): fetchDeleted(true), timestamp(0), q(parent) { } QNetworkRequest ContactFetchJob::Private::createRequest(const QUrl& url) { QNetworkRequest request; request.setRawHeader("Authorization", "Bearer " + q->account()->accessToken().toLatin1()); request.setRawHeader("GData-Version", ContactsService::APIVersion().toLatin1()); request.setUrl(url); QStringList headers; auto rawHeaderList = request.rawHeaderList(); headers.reserve(rawHeaderList.size()); for (const QByteArray &str : qAsConst(rawHeaderList)) { headers << QLatin1String(str) + QLatin1String(": ") + QLatin1String(request.rawHeader(str)); } qCDebug(KGAPIRaw) << headers; return request; } ContactFetchJob::ContactFetchJob(const AccountPtr& account, QObject* parent): FetchJob(account, parent), d(new Private(this)) { } ContactFetchJob::ContactFetchJob(const QString& contactId, const AccountPtr& account, QObject* parent): FetchJob(account, parent), d(new Private(this)) { d->contactId = contactId; } ContactFetchJob::~ContactFetchJob() { delete d; } bool ContactFetchJob::fetchDeleted() const { return d->fetchDeleted; } void ContactFetchJob::setFetchDeleted(bool fetchDeleted) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify fetchDeleted property when job is running"; return; } d->fetchDeleted = fetchDeleted; } quint64 ContactFetchJob::fetchOnlyUpdated() { return d->timestamp; } void ContactFetchJob::setFetchOnlyUpdated(quint64 timestamp) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify fetchOnlyUpdated property when job is running"; return; } d->timestamp = timestamp; } QString ContactFetchJob::filter() const { return d->filter; } void ContactFetchJob::setFilter(const QString &query) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify filter property when job is running"; return; } d->filter = query; } void ContactFetchJob::start() { QUrl url; if (d->contactId.isEmpty()) { url = ContactsService::fetchAllContactsUrl(account()->accountName(), d->fetchDeleted); + QUrlQuery query(url); if (d->timestamp > 0) { - url.addQueryItem(QStringLiteral("updated-min"), Utils::ts2Str(d->timestamp)); + query.addQueryItem(QStringLiteral("updated-min"), Utils::ts2Str(d->timestamp)); } if (!d->filter.isEmpty()) { - url.addQueryItem(QStringLiteral("q"), d->filter); + query.addQueryItem(QStringLiteral("q"), d->filter); } + url.setQuery(query); } else { url = ContactsService::fetchContactUrl(account()->accountName(), d->contactId); } const QNetworkRequest request = d->createRequest(url); enqueueRequest(request); } ObjectsList ContactFetchJob::handleReplyWithItems(const QNetworkReply *reply, const QByteArray &rawData) { FeedData feedData; ObjectsList items; QString itemId; const QString contentType = reply->header(QNetworkRequest::ContentTypeHeader).toString(); ContentType ct = Utils::stringToContentType(contentType); if (ct == KGAPI2::JSON) { if (d->contactId.isEmpty()) { items = ContactsService::parseJSONFeed(rawData, feedData); } else { items << ContactsService::JSONToContact(rawData); } if (feedData.nextPageUrl.isValid()) { emitProgress(feedData.startIndex, feedData.totalResults); const QNetworkRequest request = d->createRequest(feedData.nextPageUrl); enqueueRequest(request); } else { emitFinished(); } return items; } return ObjectsList(); } diff --git a/src/contacts/contactsservice.cpp b/src/contacts/contactsservice.cpp index 9809533..6798c60 100644 --- a/src/contacts/contactsservice.cpp +++ b/src/contacts/contactsservice.cpp @@ -1,1199 +1,1207 @@ /* Copyright (C) 2012 - 2018 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "contactsservice.h" #include "contact.h" #include "contactsgroup.h" #include "../debug.h" #include #include #include +#include /* Qt::escape() */ #include namespace KGAPI2 { namespace ContactsService { namespace Private { QString stringFromXMLMap(const QVariantMap &map, const QString &key) { const QVariantMap t = map.value(key).toMap(); return t.value(QStringLiteral("$t")).toString(); } ObjectPtr JSONToContactsGroup(const QVariantMap &map); ObjectPtr JSONToContact(const QVariantMap& map); static const QUrl GoogleApisUrl(QStringLiteral("https://www.google.com")); static const QString ContactsBasePath(QStringLiteral("/m8/feeds/contacts")); static const QString ContactsGroupBasePath(QStringLiteral("/m8/feeds/groups")); static const QString PhotoBasePath(QStringLiteral("/m8/feeds/photos/media")); } ObjectsList parseJSONFeed(const QByteArray& jsonFeed, FeedData& feedData) { ObjectsList output; QJsonDocument document = QJsonDocument::fromJson(jsonFeed); const QVariantMap head = document.toVariant().toMap(); const QVariantMap feed = head.value(QStringLiteral("feed")).toMap(); const QVariantList categories = feed.value(QStringLiteral("category")).toList(); for (const QVariant &c : categories) { const QVariantMap category = c.toMap(); bool groups = false; if (category.value(QStringLiteral("term")).toString() == QLatin1String("http://schemas.google.com/contact/2008#group")) { groups = true; } const QVariantList entries = feed.value(QStringLiteral("entry")).toList(); for (const QVariant &e : entries) { if (groups) { output << Private::JSONToContactsGroup(e.toMap()); } else { output << Private::JSONToContact(e.toMap()); } } } const QVariantList links = feed.value(QStringLiteral("link")).toList(); for (const QVariant &l : links) { const QVariantMap link = l.toMap(); if (link.value(QStringLiteral("rel")).toString() == QLatin1String("next")) { feedData.nextPageUrl = QUrl(link.value(QStringLiteral("href")).toString()); break; } } feedData.totalResults = Private::stringFromXMLMap(feed, QStringLiteral("openSearch$totalResults")).toInt(); feedData.startIndex = Private::stringFromXMLMap(feed, QStringLiteral("openSearch$startIndex")).toInt(); feedData.itemsPerPage = Private::stringFromXMLMap(feed, QStringLiteral("openSearch$itemsPerPage")).toInt(); return output; } QUrl fetchAllContactsUrl(const QString& user, bool showDeleted) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::ContactsBasePath % QLatin1Char('/') % user % QLatin1String("/full")); - url.addQueryItem(QStringLiteral("alt"), QStringLiteral("json")); + QUrlQuery query(url); + query.addQueryItem(QStringLiteral("alt"), QStringLiteral("json")); if (showDeleted) { - url.addQueryItem(QStringLiteral("showdeleted"), QStringLiteral("true")); + query.addQueryItem(QStringLiteral("showdeleted"), QStringLiteral("true")); } + url.setQuery(query); return url; } QUrl fetchContactUrl(const QString& user, const QString& contactID) { QString id; if (contactID.contains(QLatin1Char('/'))) { id = contactID.mid(contactID.lastIndexOf(QLatin1Char('/')) + 1); } else { id = contactID; } QUrl url(Private::GoogleApisUrl); url.setPath(Private::ContactsBasePath % QLatin1Char('/') % user % QLatin1String("/full/") % id); - url.addQueryItem(QStringLiteral("alt"), QStringLiteral("json")); + QUrlQuery query(url); + query.addQueryItem(QStringLiteral("alt"), QStringLiteral("json")); + url.setQuery(query); return url; } QUrl createContactUrl(const QString& user) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::ContactsBasePath % QLatin1Char('/') % user % QLatin1String("/full")); return url; } QUrl updateContactUrl(const QString& user, const QString& contactID) { QString id; if (contactID.contains(QLatin1Char('/'))) { id = contactID.mid(contactID.lastIndexOf(QLatin1Char('/')) + 1); } else { id = contactID; } QUrl url(Private::GoogleApisUrl); url.setPath(Private::ContactsBasePath % QLatin1Char('/') % user % QLatin1String("/full/") % id); return url; } QUrl removeContactUrl(const QString& user, const QString& contactID) { QString id; if (contactID.contains(QLatin1Char('/'))) { id = contactID.mid(contactID.lastIndexOf(QLatin1Char('/')) + 1); } else { id = contactID; } QUrl url(Private::GoogleApisUrl); url.setPath(Private::ContactsBasePath % QLatin1Char('/') % user % QLatin1String("/full/") % id); return url; } QUrl fetchAllGroupsUrl(const QString &user) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::ContactsGroupBasePath % QLatin1Char('/') % user % QLatin1String("/full")); - url.addQueryItem(QStringLiteral("alt"), QStringLiteral("json")); + QUrlQuery query(url); + query.addQueryItem(QStringLiteral("alt"), QStringLiteral("json")); + url.setQuery(query); return url; } QUrl fetchGroupUrl(const QString &user, const QString &groupId) { QString id; if (groupId.contains(QLatin1Char('/'))) { id = groupId.mid(groupId.lastIndexOf(QLatin1Char('/')) + 1); } else { id = groupId; } QUrl url(Private::GoogleApisUrl); url.setPath(Private::ContactsGroupBasePath % QLatin1Char('/') % user % QLatin1String("/base/") % id); - url.addQueryItem(QStringLiteral("alt"), QStringLiteral("json")); - + QUrlQuery query(url); + query.addQueryItem(QStringLiteral("alt"), QStringLiteral("json")); + url.setQuery(query); return url; } QUrl createGroupUrl(const QString &user) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::ContactsGroupBasePath % QLatin1Char('/') % user % QLatin1String("/full")); return url; } QUrl updateGroupUrl(const QString &user, const QString &groupId) { QString id; if (groupId.contains(QLatin1Char('/'))) { id = groupId.mid(groupId.lastIndexOf(QLatin1Char('/')) + 1); } else { id = groupId; } QUrl url(Private::GoogleApisUrl); url.setPath(Private::ContactsGroupBasePath % QLatin1Char('/') % user % QLatin1String("/full/") % id); return url; } QUrl removeGroupUrl(const QString &user, const QString &groupId) { QString id; if (groupId.contains(QLatin1Char('/'))) { id = groupId.mid(groupId.lastIndexOf(QLatin1Char('/')) + 1); } else { id = groupId; } QUrl url(Private::GoogleApisUrl); url.setPath(Private::ContactsGroupBasePath % QLatin1Char('/') % user % QLatin1String("/full/") % id); return url; } QUrl photoUrl(const QString& user, const QString& contactID) { QString id; if (contactID.contains(QLatin1Char('/'))) { id = contactID.mid(contactID.lastIndexOf(QLatin1Char('/')) + 1); } else { id = contactID; } QUrl url(Private::GoogleApisUrl); url.setPath(Private::PhotoBasePath % QLatin1Char('/') % user % QLatin1Char('/') % id); return url; } QString APIVersion() { return QStringLiteral("3.0"); } /*********************************** PRIVATE *************************************/ ObjectPtr Private::JSONToContactsGroup(const QVariantMap& data) { ContactsGroupPtr group(new ContactsGroup); group->setId(Private::stringFromXMLMap(data, QStringLiteral("id"))); group->setEtag(data.value(QStringLiteral("gd$etag")).toString()); group->setTitle(Private::stringFromXMLMap(data, QStringLiteral("title"))); group->setContent(Private::stringFromXMLMap(data, QStringLiteral("content"))); group->setUpdated(QDateTime::fromString(Private::stringFromXMLMap(data, QStringLiteral("updated")), Qt::ISODate)); if (data.contains(QStringLiteral("gContact$systemGroup"))) { group->setIsSystemGroup(true); } else { group->setIsSystemGroup(false); } return group; } ContactsGroupPtr JSONToContactsGroup(const QByteArray& jsonData) { QJsonDocument document = QJsonDocument::fromJson(jsonData); const QVariantMap data = document.toVariant().toMap(); const QVariantMap entry = data.value(QStringLiteral("entry")).toMap(); const QVariantList categories = entry.value(QStringLiteral("category")).toList(); bool isGroup = false; for (const QVariant &c : categories) { const QVariantMap category = c.toMap(); if (category.value(QStringLiteral("term")).toString() == QLatin1String("http://schemas.google.com/contact/2008#group")) { isGroup = true; break; } } if (!isGroup) { return ContactsGroupPtr(); } return Private::JSONToContactsGroup(entry).staticCast(); } ObjectPtr Private::JSONToContact(const QVariantMap& data) { ContactPtr contact(new Contact); /* Google contact ID */ contact->setUid(Private::stringFromXMLMap(data, QStringLiteral("id"))); /* Google ETAG. This can be used to identify if the item was changed remotly */ contact->setEtag(data.value(QStringLiteral("gd$etag")).toString()); /* Date and time when contact was updated on the remote server */ contact->setUpdated(QDateTime::fromString(Private::stringFromXMLMap(data, QStringLiteral("updated")), Qt::ISODate)); /* If the contact was deleted, we don't need more info about it. * Just store our own flag, which will be then parsed by the resource * itself. */ contact->setDeleted(data.value(QStringLiteral("gd$deleted")).isValid()); /* Store URL of the picture. The URL will be used later by PhotoJob to fetch the picture * itself. */ const QVariantList links = data.value(QStringLiteral("link")).toList(); for (const QVariant &link : links) { const QVariantMap linkMap = link.toMap(); if (linkMap.value(QStringLiteral("rel")).toString() == QLatin1String("http://schemas.google.com/contacts/2008/rel#photo")) { contact->setPhotoUrl(linkMap.value(QStringLiteral("href")).toString()); } } /* Name */ if (data.contains(QStringLiteral("title"))) { contact->setName(Private::stringFromXMLMap(data, QStringLiteral("title"))); } /* Formatted name */ if (data.contains(QStringLiteral("gd$name"))) { const QVariantMap name = data.value(QStringLiteral("gd$name")).toMap(); if (name.contains(QStringLiteral("gd$fullName"))) { contact->setFormattedName(Private::stringFromXMLMap(name, QStringLiteral("gd$fullName"))); } if (name.contains(QStringLiteral("gd$givenName"))) { contact->setGivenName(Private::stringFromXMLMap(name, QStringLiteral("gd$givenName"))); } if (name.contains(QStringLiteral("gd$familyName"))) { contact->setFamilyName(Private::stringFromXMLMap(name, QStringLiteral("gd$familyName"))); } if (name.contains(QStringLiteral("gd$additionalName"))) { contact->setAdditionalName(Private::stringFromXMLMap(name, QStringLiteral("gd$additionalName"))); } if (name.contains(QStringLiteral("gd$namePrefix"))) { contact->setPrefix(Private::stringFromXMLMap(name, QStringLiteral("gd$namePrefix"))); } if (name.contains(QStringLiteral("gd$nameSuffix"))) { contact->setSuffix(Private::stringFromXMLMap(name, QStringLiteral("gd$nameSuffix"))); } } /* Note */ if (data.contains(QStringLiteral("content"))) { contact->setNote(Private::stringFromXMLMap(data, QStringLiteral("content"))); } /* Organization (work) - KABC supports only one organization */ if (data.contains(QStringLiteral("gd$organization"))) { const QVariantList organizations = data.value(QStringLiteral("gd$organization")).toList(); const QVariantMap organization = organizations.first().toMap(); if (organization.contains(QStringLiteral("gd$orgName"))) { contact->setOrganization(Private::stringFromXMLMap(organization, QStringLiteral("gd$orgName"))); } if (organization.contains(QStringLiteral("gd$orgDepartment"))) { contact->setDepartment(Private::stringFromXMLMap(organization, QStringLiteral("gd$orgDepartment"))); } if (organization.contains(QStringLiteral("gd$orgTitle"))) { contact->setTitle(Private::stringFromXMLMap(organization, QStringLiteral("gd$orgTitle"))); } if (organization.contains(QStringLiteral("gd$where"))) { contact->setOffice(Private::stringFromXMLMap(organization, QStringLiteral("gd$where"))); } } /* Nickname */ if (data.contains(QStringLiteral("gContact$nickname"))) { contact->setNickName(Private::stringFromXMLMap(data, QStringLiteral("gContact$nickname"))); } /* Occupation (= organization/title) */ if (data.contains(QStringLiteral("gContact$occupation"))) { contact->setProfession(Private::stringFromXMLMap(data, QStringLiteral("gContact$occupation"))); } /* Relationships */ if (data.contains(QStringLiteral("gContact$relation"))) { const QVariantList relations = data.value(QStringLiteral("gContact$relation")).toList(); for (const QVariant &r : relations) { const QVariantMap relation = r.toMap(); if (relation.value(QStringLiteral("rel")).toString() == QLatin1String("spouse")) { contact->setSpousesName(relation.value(QStringLiteral("$t")).toString()); continue; } if (relation.value(QStringLiteral("rel")).toString() == QLatin1String("manager")) { contact->setManagersName(relation.value(QStringLiteral("$t")).toString()); continue; } if (relation.value(QStringLiteral("rel")).toString() == QLatin1String("assistant")) { contact->setAssistantsName(relation.value(QStringLiteral("$t")).toString()); continue; } } } /* Anniversary */ if (data.contains(QStringLiteral("gContact$event"))) { const QVariantList events = data.value(QStringLiteral("gContact$event")).toList(); for (const QVariant &e : events) { const QVariantMap event = e.toMap(); if (event.value(QStringLiteral("rel")).toString() == QLatin1String("anniversary")) { QVariantMap when = event.value(QStringLiteral("gd$when")).toMap(); contact->setAnniversary(when.value(QStringLiteral("startTime")).toString()); } } } /* Websites */ if (data.contains(QStringLiteral("gContact$website"))) { const QVariantList websites = data.value(QStringLiteral("gContact$website")).toList(); for (const QVariant &w : websites) { const QVariantMap web = w.toMap(); const auto rel = web.value(QStringLiteral("rel")).toString(); const QUrl url(web.value(QStringLiteral("href")).toString()); if (rel == QLatin1String("home-page")) { KContacts::ResourceLocatorUrl locator; locator.setUrl(url); locator.setParameters({ { QStringLiteral("TYPE"), { QStringLiteral("HOME") } } }); contact->insertExtraUrl(locator); } else if (rel == QLatin1String("work")) { KContacts::ResourceLocatorUrl locator; locator.setUrl(url); locator.setParameters({ { QStringLiteral("TYPE"), { QStringLiteral("WORK") } } }); contact->insertExtraUrl(locator); } else if (rel == QLatin1String("profile")) { KContacts::ResourceLocatorUrl locator; locator.setUrl(url); locator.setParameters({ { QStringLiteral("TYPE"), { QStringLiteral("PROFILE") } } }); contact->insertExtraUrl(locator); } else if (rel == QLatin1String("blog")) { contact->setBlogFeed(url.toString(QUrl::PrettyDecoded)); } else { KContacts::ResourceLocatorUrl locator; locator.setUrl(url); locator.setParameters({ { QStringLiteral("TYPE"), { rel } } }); contact->insertExtraUrl(locator); } } } /* Emails */ const QVariantList emails = data.value(QStringLiteral("gd$email")).toList(); for (const QVariant & em : emails) { const QVariantMap email = em.toMap(); const QMap params({ { QStringLiteral("TYPE"), { email.value(QStringLiteral("rel")).toString() } } }); contact->insertEmail(email.value(QStringLiteral("address")).toString(), email.value(QStringLiteral("primary")).toBool(), params); } /* IMs */ const QVariantList ims = data.value(QStringLiteral("gd$im")).toList(); for (const QVariant & i : ims) { const QVariantMap im = i.toMap(); const QString protocol = Contact::IMSchemeToProtocolName(im.value(QStringLiteral("protocol")).toString()); contact->insertCustom(QLatin1String("messaging/") + protocol, QStringLiteral("All"), im.value(QStringLiteral("address")).toString()); } /* Phone numbers */ const QVariantList phones = data.value(QStringLiteral("gd$phoneNumber")).toList(); for (const QVariant & p : phones) { const QVariantMap phone = p.toMap(); KContacts::PhoneNumber phoneNumber( phone.value(QStringLiteral("$t")).toString(), Contact::phoneSchemeToType(phone.value(QStringLiteral("rel")).toString())); phoneNumber.setId(phoneNumber.number()); contact->insertPhoneNumber(phoneNumber); } /* Addresses */ const QVariantList addresses = data.value(QStringLiteral("gd$structuredPostalAddress")).toList(); for (const QVariant &a : addresses) { const QVariantMap address = a.toMap(); KContacts::Address addr; addr.setId(QString::number(contact->addresses().count())); if (!address.contains(QStringLiteral("gd$city")) && !address.contains(QStringLiteral("gd$country")) && !address.contains(QStringLiteral("gd$postcode")) && !address.contains(QStringLiteral("gd$region")) && !address.contains(QStringLiteral("gd$pobox"))) { addr.setExtended(Private::stringFromXMLMap(address, QStringLiteral("gd$street"))); } else { if (address.contains(QStringLiteral("gd$street"))) { addr.setStreet(Private::stringFromXMLMap(address, QStringLiteral("gd$street"))); } if (address.contains(QStringLiteral("gd$country"))) { addr.setCountry(Private::stringFromXMLMap(address, QStringLiteral("gd$country"))); } if (address.contains(QStringLiteral("gd$city"))) { addr.setLocality(Private::stringFromXMLMap(address, QStringLiteral("gd$city"))); } if (address.contains(QStringLiteral("gd$postcode"))) { addr.setPostalCode(Private::stringFromXMLMap(address, QStringLiteral("gd$postcode"))); } if (address.contains(QStringLiteral("gdregion"))) { addr.setRegion(Private::stringFromXMLMap(address, QStringLiteral("gd$region"))); } if (address.contains(QStringLiteral("gd$pobox"))) { addr.setPostOfficeBox(Private::stringFromXMLMap(address, QStringLiteral("gd$pobox"))); } } addr.setType(Contact::addressSchemeToType(address.value(QStringLiteral("rel")).toString())); contact->insertAddress(addr); } /* Birthday */ const QVariantMap bDay = data.value(QStringLiteral("gContact$birthday")).toMap(); if (!bDay.isEmpty()) { QString birthday = bDay.value(QStringLiteral("when")).toString(); /* Birthdays in format "--MM-DD" are valid and mean that no year has * been specified. Since KABC does not support birthdays without year, * we simulate that by specifying a fake year - 1900 */ if (birthday.startsWith(QLatin1String("--"))) { birthday = QLatin1String("1900") + birthday.mid(1); } contact->setBirthday(QDateTime::fromString(birthday, QStringLiteral("yyyy-MM-dd"))); } /* User-defined fields */ const QVariantList userDefined = data.value(QStringLiteral("gContact$userDefinedField")).toList(); for (const QVariant & u : userDefined) { const QVariantMap field = u.toMap(); contact->insertCustom(QStringLiteral("KADDRESSBOOK"), field.value(QStringLiteral("key")).toString(), field.value(QStringLiteral("value")).toString()); } /* Groups */ const QVariantList groups = data.value(QStringLiteral("gContact$groupMembershipInfo")).toList(); QStringList groupsList; for (const QVariant & g : groups) { const QVariantMap group = g.toMap(); if (group.value(QStringLiteral("deleted")).toBool() == false) { groupsList.append(group.value(QStringLiteral("href")).toString()); } } contact->insertCustom(QStringLiteral("GCALENDAR"), QStringLiteral("groupMembershipInfo"), groupsList.join(QStringLiteral(","))); return contact; } ContactPtr JSONToContact(const QByteArray& jsonData) { QJsonDocument document = QJsonDocument::fromJson(jsonData); const QVariantMap data = document.toVariant().toMap(); const QVariantMap entry = data.value(QStringLiteral("entry")).toMap(); const QVariantList categories = entry.value(QStringLiteral("category")).toList(); bool isContact = false; for (const QVariant &c : categories) { const QVariantMap category = c.toMap(); if (category.value(QStringLiteral("term")).toString() == QLatin1String("http://schemas.google.com/contact/2008#contact")) { isContact = true; break; } } if (!isContact) { return ContactPtr(); } return Private::JSONToContact(entry).staticCast(); } QByteArray contactToXML(const ContactPtr& contact) { QByteArray output; QStringList parsedCustoms; /* Name */ output.append(""); if (!contact->givenName().isEmpty()) { - output.append("").append(Qt::escape(contact->givenName()).toUtf8()).append(""); + output.append("").append(contact->givenName().toHtmlEscaped().toUtf8()).append(""); } if (!contact->familyName().isEmpty()) { - output.append("").append(Qt::escape(contact->familyName()).toUtf8()).append(""); + output.append("").append(contact->familyName().toHtmlEscaped().toUtf8()).append(""); } if (!contact->assembledName().isEmpty()) { - output.append("").append(Qt::escape(contact->assembledName()).toUtf8()).append(""); + output.append("").append(contact->assembledName().toHtmlEscaped().toUtf8()).append(""); } if (!contact->additionalName().isEmpty()) { - output.append("").append(Qt::escape(contact->additionalName()).toUtf8()).append(""); + output.append("").append(contact->additionalName().toHtmlEscaped().toUtf8()).append(""); } if (!contact->prefix().isEmpty()) { - output.append("").append(Qt::escape(contact->prefix()).toUtf8()).append(""); + output.append("").append(contact->prefix().toHtmlEscaped().toUtf8()).append(""); } if (!contact->suffix().isEmpty()) { - output.append("").append(Qt::escape(contact->suffix()).toUtf8()).append(""); + output.append("").append(contact->suffix().toHtmlEscaped().toUtf8()).append(""); } output.append(""); /* Notes */ if (!contact->note().isEmpty()) { - output.append("").append(Qt::escape(contact->note()).toUtf8()).append(""); + output.append("").append(contact->note().toHtmlEscaped().toUtf8()).append(""); } /* Organization (work) */ QByteArray org; const QString office = contact->office(); if (!contact->organization().isEmpty()) { - org.append("").append(Qt::escape(contact->organization()).toUtf8()).append(""); + org.append("").append(contact->organization().toHtmlEscaped().toUtf8()).append(""); } if (!contact->department().isEmpty()) { - org.append("").append(Qt::escape(contact->department()).toUtf8()).append(""); + org.append("").append(contact->department().toHtmlEscaped().toUtf8()).append(""); } if (!contact->title().isEmpty()) { - org.append("").append(Qt::escape(contact->title()).toUtf8()).append(""); + org.append("").append(contact->title().toHtmlEscaped().toUtf8()).append(""); } if (!office.isEmpty()) { - org.append("").append(Qt::escape(office).toUtf8()).append(""); + org.append("").append(office.toHtmlEscaped().toUtf8()).append(""); parsedCustoms << QStringLiteral("KADDRESSBOOK-X-Office"); } if (!org.isEmpty()) { output.append("").append(org).append(""); } /* Nickname */ if (!contact->nickName().isEmpty()) { - output.append("").append(Qt::escape(contact->nickName()).toUtf8()).append(""); + output.append("").append(contact->nickName().toHtmlEscaped().toUtf8()).append(""); } /* Occupation */ if (!contact->profession().isEmpty()) { - output.append("").append(Qt::escape(contact->profession()).toUtf8()).append(""); + output.append("").append(contact->profession().toHtmlEscaped().toUtf8()).append(""); parsedCustoms << QStringLiteral("KADDRESSBOOK-X-Profession"); } /* Spouse */ const QString spouse = contact->spousesName(); if (!spouse.isEmpty()) { - output.append("").append(Qt::escape(spouse).toUtf8()).append(""); + output.append("").append(spouse.toHtmlEscaped().toUtf8()).append(""); parsedCustoms << QStringLiteral("KADDRESSBOOK-X-SpousesName"); } /* Manager */ const QString manager = contact->managersName(); if (!manager.isEmpty()) { - output.append("").append(Qt::escape(manager).toUtf8()).append(""); + output.append("").append(manager.toHtmlEscaped().toUtf8()).append(""); parsedCustoms << QStringLiteral("KADDRESSBOOK-X-ManagersName"); } /* Assistant */ const QString assistant = contact->assistantsName(); if (!assistant.isEmpty()) { - output.append("").append(Qt::escape(assistant).toUtf8()).append(""); + output.append("").append(assistant.toHtmlEscaped().toUtf8()).append(""); parsedCustoms << QStringLiteral("KADDRESSBOOK-X-AssistantsName"); } /* Anniversary */ const QString anniversary = contact->anniversary(); if (!anniversary.isEmpty()) { - output.append(""); + output.append(""); parsedCustoms << QStringLiteral("KADDRESSBOOK-X-Anniversary"); } /* Blog */ const QString blog = contact->blogFeed(); if (!blog.isEmpty()) { - output.append(""); + output.append(""); parsedCustoms << QStringLiteral("KADDRESSBOOK-BlogFeed"); } /* URLs */ const auto extraUrls = contact->extraUrlList(); for (const auto &extraUrl : extraUrls) { const auto rels = extraUrl.parameters().value(QStringLiteral("TYPE")); auto rel = rels.isEmpty() ? "other" : rels.at(0).toLower().toUtf8(); if (rel == "home") { rel = "home-page"; } - output.append(""); + output.append(""); } /* Emails */ const auto preferredEmail = contact->preferredEmail(); Q_FOREACH(const auto &email, contact->emailList()) { const auto rels = email.parameters().value(QStringLiteral("TYPE"), { QStringLiteral("http://schemas.google.com/g/2005#home") }); auto rel = rels.isEmpty() ? "http://schemas.google.com/g/2005#home" : rels.at(0).toLower().toUtf8(); - output.append(""); } /* IMs */ const QString im_str = QStringLiteral(""); Q_FOREACH(const QString &im, contact->customs()) { if (im.startsWith(QLatin1String("messaging/"))) { QString key = im.left(im.indexOf(QLatin1Char(':'))); QString value = im.mid(im.indexOf(QLatin1Char(':')) + 1); QString proto = key.mid(10); proto.chop(4); bool primary = (contact->custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-IMAddress")) == value); output.append(im_str.arg(value, Contact::IMProtocolNameToScheme(proto), (primary ? QStringLiteral("true") : QStringLiteral("false"))).toUtf8()); parsedCustoms << key; /* X-messaging is probably a new key (?) used by KAddressbook when importing * contacts from vCard. */ } else if (im.startsWith(QLatin1String("X-messaging"))) { const QString key = im.left(im.indexOf(QLatin1Char(':'))); const QString value = im.mid(im.indexOf(QLatin1Char(':')) + 1); QString proto = key.mid(12); /* strlen("X-messaging/") */ if (proto.endsWith(QLatin1String("-All"))) { proto.chop(4); } output.append(im_str.arg(value, proto, QStringLiteral("false")).toUtf8()); parsedCustoms << key; } } parsedCustoms << QStringLiteral("KADDRESSBOOK-X-IMAddress"); /* Phone numbers */ const QString phone_str = QStringLiteral("%2"); Q_FOREACH(const KContacts::PhoneNumber &number, contact->phoneNumbers()) { output.append(phone_str.arg(Contact::phoneTypeToScheme(number.type()), number.number()).toUtf8()); } /* Address */ Q_FOREACH(const KContacts::Address &address, contact->addresses()) { output.append(""); if (!address.locality().isEmpty()) - output.append("").append(Qt::escape(address.locality()).toUtf8()).append(""); + output.append("").append(address.locality().toHtmlEscaped().toUtf8()).append(""); if (!address.street().isEmpty()) - output.append("").append(Qt::escape(address.street()).toUtf8()).append(""); + output.append("").append(address.street().toHtmlEscaped().toUtf8()).append(""); if (!address.region().isEmpty()) - output.append("").append(Qt::escape(address.region()).toUtf8()).append(""); + output.append("").append(address.region().toHtmlEscaped().toUtf8()).append(""); if (!address.postalCode().isEmpty()) - output.append("").append(Qt::escape(address.postalCode()).toUtf8()).append(""); + output.append("").append(address.postalCode().toHtmlEscaped().toUtf8()).append(""); if (!address.country().isEmpty()) - output.append("").append(Qt::escape(address.country()).toUtf8()).append(""); + output.append("").append(address.country().toHtmlEscaped().toUtf8()).append(""); if (!address.formattedAddress().isEmpty()) - output.append("").append(Qt::escape(address.formattedAddress()).toUtf8()).append(""); + output.append("").append(address.formattedAddress().toHtmlEscaped().toUtf8()).append(""); output.append(""); } /* Birthday */ const QDate birthday = contact->birthday().date(); if (birthday.isValid()) { QString birthdayStr; /* We use year 1900 as a fake year for birthdays without a year specified. * Here we assume that nobody actually has a contact born in 1900 and so * we replace 1900 by "-", so that we get "--MM-dd" date, which is a valid * birthday date according to RFC6350 */ if (birthday.year() == 1900) { birthdayStr = birthday.toString(QStringLiteral("--MM-dd")); } else { birthdayStr = birthday.toString(QStringLiteral("yyyy-MM-dd")); } output.append(""); } const QStringList groups = contact->custom(QStringLiteral("GCALENDAR"), QStringLiteral("groupMembershipInfo")).split(QLatin1Char(',')); qCDebug(KGAPIDebug) << groups; if ((!groups.isEmpty()) && !groups.at(0).isEmpty()) { for (const QString & group :groups) { bool removed = contact->groupIsDeleted(group); if (!removed) output.append(QStringLiteral("").arg(group).toUtf8()); } } parsedCustoms << QStringLiteral("GCALENDAR-groupMembershipInfo"); /* User-defined fields */ const QStringList customs = contact->customs(); const QString defined_str = QStringLiteral(""); for (const QString &customStr : customs) { QString key = customStr.left(customStr.indexOf(QLatin1Char(':'))); if (!parsedCustoms.contains(key)) { if (key.startsWith(QLatin1String("KADDRESSBOOK-"))) { key = key.remove(QStringLiteral("KADDRESSBOOK-")); } const QString value = customStr.mid(customStr.indexOf(QLatin1Char(':')) + 1); - output.append(defined_str.arg(Qt::escape(key), Qt::escape(value)).toUtf8()); + output.append(defined_str.arg(key.toHtmlEscaped(), value).toHtmlEscaped().toUtf8()); } } return output; } QByteArray contactsGroupToXML(const ContactsGroupPtr& group) { QByteArray output; - output.append("").append(Qt::escape(group->title()).toUtf8()).append(""); - output.append("").append(Qt::escape(group->content()).toUtf8()).append(""); + output.append("").append(group->title().toHtmlEscaped().toUtf8()).append(""); + output.append("").append(group->content().toHtmlEscaped().toUtf8()).append(""); return output; } ContactPtr XMLToContact(const QByteArray& xmlData) { QByteArray xmlDoc; /* Document without header is not valid and Qt won't parse it */ if (!xmlData.contains(""); } xmlDoc.append(xmlData); QDomDocument doc; doc.setContent(xmlDoc); const QDomNodeList entry = doc.elementsByTagName(QStringLiteral("entry")); QDomNodeList data; if (!entry.isEmpty()) { data = entry.at(0).childNodes(); } else { return ContactPtr(); } bool isGroup = false; for (int i = 0; i < data.count(); ++i) { const QDomNode n = data.at(i); const QDomElement e = n.toElement(); if (((e.tagName() == QLatin1String("category")) && (e.attribute(QStringLiteral("term")) == QLatin1String("http://schemas.google.com/contact/2008#group"))) || ((e.tagName() == QLatin1String("atom:category")) && (e.attribute(QStringLiteral("term")) == QLatin1String("http://schemas.google.com/g/2005#group")))) { isGroup = true; break; } } if (isGroup) { return ContactPtr(); } QStringList groups; ContactPtr contact(new Contact); contact->setEtag(entry.at(0).toElement().attribute(QLatin1String("gd:etag"))); for (int i = 0; i < data.count(); ++i) { const QDomNode n = data.at(i); const QDomElement e = n.toElement(); if (e.tagName() == QLatin1String("id")) { contact->setUid(e.text()); continue; } /* ETag */ if (e.tagName() == QLatin1String("etag")) { contact->setEtag(e.text()); continue; } if (e.tagName() == QLatin1String("gd:name")) { QDomNodeList l = e.childNodes(); for (int i = 0; i < l.length(); ++i) { const QDomElement el = l.at(i).toElement(); if (el.tagName() == QLatin1String("gd:fullName")) { contact->setFormattedName(el.text()); continue; } if (el.tagName() == QLatin1String("gd:givenName")) { contact->setGivenName(el.text()); continue; } if (el.tagName() == QLatin1String("gd:familyName")) { contact->setFamilyName(el.text()); continue; } if (el.tagName() == QLatin1String("gd:additionalName")) { contact->setAdditionalName(el.text()); continue; } if (el.tagName() == QLatin1String("gd:namePrefix")) { contact->setPrefix(el.text()); continue; } if (el.tagName() == QLatin1String("gd:nameSuffix")) { contact->setSuffix(el.text()); continue; } } continue; } /* If the contact was deleted, we don't need more info about it. * Just store our own flag, which will be then parsed by the resource * itself. */ contact->setDeleted(e.tagName() == QLatin1String("gd:deleted")); if (e.tagName() == QLatin1String("updated")) { contact->setUpdated(QDateTime::fromString(e.text(), Qt::ISODate)); } /* Store URL of the picture. The URL will be used later by PhotoJob to fetch the picture * itself. */ if ((e.tagName() == QLatin1String("link")) && (e.attribute(QStringLiteral("rel")) == QLatin1String("http://schemas.google.com/contacts/2008/rel#photo"))) { contact->setPhotoUrl(e.attribute(QStringLiteral("href"))); /* URL */ continue; } /* Name */ if (e.tagName() == QLatin1String("title")) { contact->setName(e.text()); continue; } /* Note */ if (e.tagName() == QLatin1String("content")) { contact->setNote(e.text()); continue; } /* Organization (work) - KABC supports only organization */ if (e.tagName() == QLatin1String("gd:organization")) { const QDomNodeList l = e.childNodes(); for (int i = 0; i < l.length(); ++i) { const QDomElement el = l.at(i).toElement(); if (el.tagName() == QLatin1String("gd:orgName")) { contact->setOrganization(el.text()); continue; } if (el.tagName() == QLatin1String("gd:orgDepartment")) { contact->setDepartment(el.text()); continue; } if (el.tagName() == QLatin1String("gd:orgTitle")) { contact->setTitle(el.text()); continue; } if (el.tagName() == QLatin1String("gd:where")) { contact->setOffice(el.text()); continue; } } continue; } /* Nickname */ if (e.tagName() == QLatin1String("gContact:nickname")) { contact->setNickName(e.text()); continue; } /* Occupation (= organization/title) */ if (e.tagName() == QLatin1String("gContact:occupation")) { contact->setProfession(e.text()); continue; } /* Relationships */ if (e.tagName() == QLatin1String("gContact:relation")) { if (e.attribute(QStringLiteral("rel"), QString()) == QLatin1String("spouse")) { contact->setSpousesName(e.text()); continue; } if (e.attribute(QStringLiteral("rel"), QString()) == QLatin1String("manager")) { contact->setManagersName(e.text()); continue; } if (e.attribute(QStringLiteral("rel"), QString()) == QLatin1String("assistant")) { contact->setAssistantsName(e.text()); continue; } continue; } /* Anniversary */ if (e.tagName() == QLatin1String("gContact:event")) { if (e.attribute(QStringLiteral("rel"), QString()) == QLatin1String("anniversary")) { QDomElement w = e.firstChildElement(QStringLiteral("gd:when")); contact->setAnniversary(w.attribute(QStringLiteral("startTime"), QString())); } continue; } /* Websites */ if (e.tagName() == QLatin1String("gContact:website")) { if (e.attribute(QStringLiteral("rel"), QString()) == QLatin1String("blog")) { contact->setBlogFeed(e.attribute(QStringLiteral("href"), QString())); continue; } KContacts::ResourceLocatorUrl url; QString rel = e.attribute(QStringLiteral("rel")).toUpper(); if (rel == QLatin1String("home-page")) { rel = QStringLiteral("HOME"); } url.setParameters({ { QStringLiteral("TYPE"), { rel } } }); url.setUrl(QUrl(e.attribute(QStringLiteral("href"), {}))); contact->insertExtraUrl(url); continue; } /* Emails */ if (e.tagName() == QLatin1String("gd:email")) { const QMap params({ { QStringLiteral("TYPE"), { e.attribute(QStringLiteral("rel"), {}) } } }); contact->insertEmail(e.attribute(QStringLiteral("address")), (e.attribute(QStringLiteral("primary")).toLower() == QLatin1String("true")), params); continue; } /* IMs */ if (e.tagName() == QLatin1String("gd:im")) { contact->insertCustom(QLatin1String("messaging/") + Contact::IMSchemeToProtocolName(e.attribute(QStringLiteral("protocol"))), QStringLiteral("All"), e.attribute(QStringLiteral("address"))); continue; } /* Phone numbers */ if (e.tagName() == QLatin1String("gd:phoneNumber")) { KContacts::PhoneNumber number(e.text(), Contact::phoneSchemeToType(e.attribute(QStringLiteral("rel")))); number.setId(e.text()); contact->insertPhoneNumber(number); continue; } /* Addresses */ if (e.tagName() == QLatin1String("gd:structuredPostalAddress")) { KContacts::Address address; address.setId(QString::number(contact->addresses().count())); const QDomNodeList l = e.childNodes(); for (int i = 0; i < l.length(); ++i) { const QDomElement el = l.at(i).toElement(); if (el.tagName() == QLatin1String("gd:street")) { address.setStreet(el.text()); continue; } if (el.tagName() == QLatin1String("gd:country")) { address.setCountry(el.text()); continue; } if (el.tagName() == QLatin1String("gd:city")) { address.setLocality(el.text()); continue; } if (el.tagName() == QLatin1String("gd:postcode")) { address.setPostalCode(el.text()); continue; } if (el.tagName() == QLatin1String("gd:region")) { address.setRegion(el.text()); continue; } if (el.tagName() == QLatin1String("gd:pobox")) { address.setPostOfficeBox(el.text()); continue; } } address.setType(Contact::addressSchemeToType(e.attribute(QStringLiteral("rel")), (e.attribute(QStringLiteral("primary")) == QLatin1String("true")))); contact->insertAddress(address); continue; } /* Birthday */ if (e.tagName() == QLatin1String("gContact:birthday")) { QString birthday = e.attribute(QStringLiteral("when")); /* Birthdays in format "--MM-DD" are valid and mean that no year has * been specified. Since KABC does not support birthdays without year, * we simulate that by specifying a fake year - 1900 */ if (birthday.startsWith(QLatin1String("--"))) { birthday = QLatin1String("1900") + birthday.mid(1); } contact->setBirthday(QDateTime::fromString(birthday, QStringLiteral("yyyy-MM-dd"))); continue; } /* User-defined tags */ if (e.tagName() == QLatin1String("gContact:userDefinedField")) { contact->insertCustom(QStringLiteral("KADDRESSBOOK"), e.attribute(QStringLiteral("key"), QString()), e.attribute(QStringLiteral("value"), QString())); continue; } if (e.tagName() == QLatin1String("gContact:groupMembershipInfo")) { if (e.hasAttribute(QStringLiteral("deleted")) || e.attribute(QStringLiteral("deleted")).toInt() == false) { groups.append(e.attribute(QStringLiteral("href"))); } } } contact->insertCustom(QStringLiteral("GCALENDAR"), QStringLiteral("groupMembershipInfo"), groups.join(QStringLiteral(","))); return contact; } ContactsGroupPtr XMLToContactsGroup(const QByteArray& xmlData) { QByteArray xmlDoc; /* Document without header is not valid and Qt won't parse it */ if (!xmlData.contains(""); } xmlDoc.append(xmlData); QDomDocument doc; doc.setContent(xmlDoc); const QDomNodeList entry = doc.elementsByTagName(QStringLiteral("entry")); QDomNodeList data; if (!entry.isEmpty()) { data = entry.at(0).childNodes(); } else { return ContactsGroupPtr(); } bool isGroup = false; for (int i = 0; i < data.count(); ++i) { const QDomNode n = data.at(i); const QDomElement e = n.toElement(); if (((e.tagName() == QLatin1String("category")) && (e.attribute(QStringLiteral("term")) == QLatin1String("http://schemas.google.com/contact/2008#group"))) || ((e.tagName() == QLatin1String("atom:category")) && (e.attribute(QStringLiteral("term")) == QLatin1String("http://schemas.google.com/g/2005#group")))) { isGroup = true; break; } } if (!isGroup) { return ContactsGroupPtr(); } ContactsGroupPtr group(new ContactsGroup); QStringList groups; for (int i = 0; i < data.count(); ++i) { const QDomNode n = data.at(i); const QDomElement e = n.toElement(); if (e.tagName() == QLatin1String("id")) { group->setId(e.text()); continue; } if (e.tagName() == QLatin1String("updated")) { group->setUpdated(QDateTime::fromString(e.text(), Qt::ISODate)); continue; } if ((e.tagName() == QLatin1String("title")) || (e.tagName() == QLatin1String("atom:title"))) { group->setTitle(e.text()); continue; } if ((e.tagName() == QLatin1String("content")) || (e.tagName() == QLatin1String("atom:content"))) { group->setContent(e.text()); continue; } if (e.tagName() == QLatin1String("gContact:systemGroup")) { group->setIsSystemGroup(true); continue; } } return group; } } // namespace ContactsService } // namespace KGAPI2 diff --git a/src/core/authjob.cpp b/src/core/authjob.cpp index 4b604d0..d388566 100644 --- a/src/core/authjob.cpp +++ b/src/core/authjob.cpp @@ -1,269 +1,271 @@ /* * This file is part of LibKGAPI library * * Copyright (C) 2013 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "authjob.h" #include "account.h" #include "../debug.h" #include "job_p.h" #include "ui/authwidget.h" #include "ui/authwidget_p.h" #include "private/newtokensfetchjob_p.h" #include #include #include #include #include #include #include #include +#include + #include using namespace KGAPI2; class Q_DECL_HIDDEN AuthJob::Private { public: Private(AuthJob *parent); QWidget* fullAuthentication(); void refreshTokens(); void _k_fullAuthenticationFinished(const KGAPI2::AccountPtr& account); void _k_fullAuthenticationFailed(KGAPI2::Error errorCode, const QString &errorMessage); void _k_destructDelayed(); AccountPtr account; QString apiKey; QString secretKey; QWidget* widget; QString username; QString password; QPointer dialog; private: AuthJob *q; }; AuthJob::Private::Private(AuthJob *parent): widget(nullptr), q(parent) { } QWidget* AuthJob::Private::fullAuthentication() { AuthWidget* authWidget = new AuthWidget(widget); // FIXME: Find a better way to pass the keys authWidget->d->apiKey = apiKey; authWidget->d->secretKey = secretKey; authWidget->setUsername(username); authWidget->setPassword(password); authWidget->setAccount(account); return authWidget; } void AuthJob::Private::refreshTokens() { static_cast(q)->d->accessManager->setCookieJar(new QNetworkCookieJar); QNetworkRequest request; request.setUrl(QUrl(QStringLiteral("https://accounts.google.com/o/oauth2/token"))); request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/x-www-form-urlencoded")); - QUrl params; + QUrlQuery params; params.addQueryItem(QStringLiteral("client_id"), apiKey); params.addQueryItem(QStringLiteral("client_secret"), secretKey); params.addQueryItem(QStringLiteral("refresh_token"), account->refreshToken()); params.addQueryItem(QStringLiteral("grant_type"), QStringLiteral("refresh_token")); - qCDebug(KGAPIRaw) << "Requesting token refresh: " << params.encodedQuery(); + qCDebug(KGAPIRaw) << "Requesting token refresh: " << params.toString(); - q->enqueueRequest(request, params.encodedQuery()); + q->enqueueRequest(request, params.toString(QUrl::FullyEncoded).toLatin1()); } void AuthJob::Private::_k_fullAuthenticationFailed(Error errorCode, const QString &errorMessage) { q->setError(errorCode); q->setErrorString(errorMessage); q->emitFinished(); } void AuthJob::Private::_k_fullAuthenticationFinished( const AccountPtr &account_ ) { account = account_; q->emitFinished(); } void AuthJob::Private::_k_destructDelayed() { if (!dialog) return; if (dialog->isVisible()) dialog->hide(); dialog->deleteLater(); dialog = nullptr; } AuthJob::AuthJob(const AccountPtr& account, const QString &apiKey, const QString &secretKey, QWidget* parent): Job(parent), d(new Private(this)) { d->account = account; d->apiKey = apiKey; d->secretKey = secretKey; d->widget = parent; } AuthJob::AuthJob(const AccountPtr& account, const QString &apiKey, const QString &secretKey, QObject* parent): Job(parent), d(new Private(this)) { d->account = account; d->apiKey = apiKey; d->secretKey = secretKey; } AuthJob::~AuthJob() { delete d; } AccountPtr AuthJob::account() const { return d->account; } void AuthJob::setUsername(const QString& username) { d->username = username; } void AuthJob::setPassword(const QString& password) { d->password = password; } void AuthJob::handleReply(const QNetworkReply *reply, const QByteArray& rawData) { Q_UNUSED(reply); QJsonDocument document = QJsonDocument::fromJson(rawData); if (document.isNull()) { setError(KGAPI2::InvalidResponse); setErrorString(tr("Failed to parse newly fetched tokens")); emitFinished(); return; } QVariantMap map = document.toVariant().toMap(); /* Expected structure: * { * "access_token": "the_access_token", * "token_type":"Bearer", * "expires_in":3600 * } */ const qlonglong expiresIn = map.value(QStringLiteral("expires_in")).toLongLong(); d->account->setExpireDateTime(QDateTime::currentDateTime().addSecs(expiresIn)); d->account->setAccessToken(map.value(QStringLiteral("access_token")).toString()); emitFinished(); } void AuthJob::dispatchRequest(QNetworkAccessManager* accessManager, const QNetworkRequest& request, const QByteArray& data, const QString& contentType) { Q_UNUSED(contentType); accessManager->post(request, data); } void AuthJob::start() { AuthWidget *widget = nullptr; if (d->account->refreshToken().isEmpty() || (d->account->m_scopesChanged == true)) { d->account->addScope(Account::accountInfoEmailScopeUrl()); /* Pre-fill the username in the dialog so that user knows what account * (s)he is re-authenticating for */ if (!d->account->accountName().isEmpty() && d->username.isEmpty()) { d->username = d->account->accountName(); } widget = qobject_cast(d->fullAuthentication()); } else { if (d->account->accountName().isEmpty()) { setError(KGAPI2::InvalidAccount); setErrorString(tr("Account name is empty")); emitFinished(); return; } d->refreshTokens(); } if (widget) { d->dialog = new QDialog(); d->dialog->setModal(true); KWindowSystem::setMainWindow(d->dialog, KWindowSystem::activeWindow()); QVBoxLayout *layout = new QVBoxLayout(d->dialog); layout->addWidget(widget, 2); QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Cancel, Qt::Horizontal, d->dialog); layout->addWidget(buttons, 0); connect(buttons, &QDialogButtonBox::rejected, this, [this]() { d->_k_destructDelayed(); d->_k_fullAuthenticationFailed(AuthCancelled, tr("Authentication canceled")); }); connect(widget, &AuthWidget::authenticated, this, [this](const KGAPI2::AccountPtr &account) { d->_k_destructDelayed(); d->_k_fullAuthenticationFinished(account); }); connect(widget, &AuthWidget::error, this, [this](KGAPI2::Error error, const QString &str) { d->_k_destructDelayed(); d->_k_fullAuthenticationFailed(error, str); }); d->dialog->show(); buttons->button(QDialogButtonBox::Cancel)->setDefault(false); // QTBUG-66109 widget->authenticate(); } } #include "moc_authjob.cpp" diff --git a/src/core/private/newtokensfetchjob.cpp b/src/core/private/newtokensfetchjob.cpp index 23643d2..1dfce79 100644 --- a/src/core/private/newtokensfetchjob.cpp +++ b/src/core/private/newtokensfetchjob.cpp @@ -1,143 +1,143 @@ /* * Copyright (C) 2013 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "newtokensfetchjob_p.h" #include "../debug.h" #include #include #include #include - +#include using namespace KGAPI2; class Q_DECL_HIDDEN NewTokensFetchJob::Private { public: Private() : expiresIn(0) { } QString tmpToken; QString apiKey; QString secretKey; int localPort; QString accessToken; QString refreshToken; qulonglong expiresIn; }; NewTokensFetchJob::NewTokensFetchJob(const QString &tmpToken, const QString &apiKey, const QString &secretKey, int localPort, QObject *parent): Job(parent), d(new Private) { d->tmpToken = tmpToken; d->apiKey = apiKey; d->secretKey = secretKey; d->localPort = localPort; } NewTokensFetchJob::~NewTokensFetchJob() { delete d; } QString NewTokensFetchJob::accessToken() const { if (isRunning()) { qCWarning(KGAPIDebug) << "Called accessToken() on running job!"; return QString(); } return d->accessToken; } QString NewTokensFetchJob::refreshToken() const { if (isRunning()) { qCWarning(KGAPIDebug) << "Called refreshToken() on running job!"; return QString(); } return d->refreshToken; } qulonglong NewTokensFetchJob::expiresIn() const { if (isRunning()) { qCWarning(KGAPIDebug) << "Called expiresIn() on running job!"; return 0; } return d->expiresIn; } void NewTokensFetchJob::start() { QNetworkRequest request; request.setUrl(QUrl(QStringLiteral("https://accounts.google.com/o/oauth2/token"))); request.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded")); - QUrl params; + QUrlQuery params; params.addQueryItem(QStringLiteral("client_id"), d->apiKey); params.addQueryItem(QStringLiteral("client_secret"), d->secretKey); params.addQueryItem(QStringLiteral("code"), d->tmpToken); params.addQueryItem(QStringLiteral("redirect_uri"), QStringLiteral("http://127.0.0.1:%1").arg(d->localPort)); // we need to use the same URL as in AuthWidget params.addQueryItem(QStringLiteral("grant_type"), QStringLiteral("authorization_code")); - enqueueRequest(request, params.encodedQuery()); + enqueueRequest(request, params.toString(QUrl::FullyEncoded).toLatin1()); } void NewTokensFetchJob::dispatchRequest(QNetworkAccessManager* accessManager, const QNetworkRequest& request, const QByteArray& data, const QString& contentType) { Q_UNUSED(contentType); accessManager->post(request, data); } void NewTokensFetchJob::handleReply(const QNetworkReply *reply, const QByteArray& rawData) { Q_UNUSED(reply); QJsonDocument document = QJsonDocument::fromJson(rawData); if (document.isNull()) { qCDebug(KGAPIDebug) << "Failed to parse server response."; qCDebug(KGAPIRaw) << rawData; setError(KGAPI2::AuthCancelled); setErrorString(tr("Failed to parse server response.")); return; } QVariantMap parsed_data = document.toVariant().toMap(); qCDebug(KGAPIRaw) << "Retrieved new tokens pair:" << parsed_data; d->accessToken = parsed_data.value(QStringLiteral("access_token")).toString(); d->refreshToken = parsed_data.value(QStringLiteral("refresh_token")).toString(); d->expiresIn = parsed_data.value(QStringLiteral("expires_in")).toULongLong(); } diff --git a/src/core/ui/authwidget.cpp b/src/core/ui/authwidget.cpp index ea245b4..bb49c26 100644 --- a/src/core/ui/authwidget.cpp +++ b/src/core/ui/authwidget.cpp @@ -1,138 +1,141 @@ /* Copyright (C) 2012 - 2018 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "authwidget.h" #include "authwidget_p.h" #include "../../debug.h" #include #include +#include using namespace KGAPI2; AuthWidget::AuthWidget(QWidget* parent): QWidget(parent), d(new AuthWidgetPrivate(this)) { } AuthWidget::~AuthWidget() { delete d; } void AuthWidget::setUsername(const QString& username) { d->username = username; } void AuthWidget::setPassword(const QString& password) { d->password = password; } void AuthWidget::clearCredentials() { d->username.clear(); d->password.clear(); } void AuthWidget::setAccount(const AccountPtr& account) { d->account = account; } void AuthWidget::setShowProgressBar(bool showProgressBar) { d->showProgressBar = showProgressBar; if (showProgressBar && d->progress == UserLogin) { d->progressbar->setVisible(true); } else { d->progressbar->setVisible(false); } } bool AuthWidget::getShowProgressBar() const { return d->showProgressBar; } AuthWidget::Progress AuthWidget::getProgress() const { return d->progress; } void AuthWidget::authenticate() { Q_ASSERT(!d->apiKey.isEmpty()); if (d->account.isNull()) { Q_EMIT error(InvalidAccount, tr("Invalid account")); return; } if (d->account->scopes().isEmpty()) { Q_EMIT error(InvalidAccount, tr("No scopes to authenticate for")); return; } QStringList scopes; scopes.reserve(d->account->scopes().size()); Q_FOREACH(const QUrl & scope, d->account->scopes()) { scopes << scope.toString(); } d->server = new QTcpServer(this); if (!d->server->listen(QHostAddress::LocalHost)) { Q_EMIT error(InvalidAccount, tr("Could not start oauth http server")); return; } connect(d->server, &QTcpServer::acceptError, d, &AuthWidgetPrivate::socketError); d->serverPort = d->server->serverPort(); connect(d->server, &QTcpServer::newConnection, [&]() { d->connection = d->server->nextPendingConnection(); d->connection->setParent(this); connect(d->connection, static_cast (&QAbstractSocket::error), d, &AuthWidgetPrivate::socketError); connect(d->connection, &QTcpSocket::readyRead, d, &AuthWidgetPrivate::socketReady); d->server->close(); d->server->deleteLater(); }); QUrl url(QStringLiteral("https://accounts.google.com/o/oauth2/auth")); - url.addQueryItem(QStringLiteral("client_id"), d->apiKey); - url.addQueryItem(QStringLiteral("redirect_uri"), QStringLiteral("http://127.0.0.1:%1").arg(d->serverPort)); - url.addQueryItem(QStringLiteral("scope"), scopes.join(QStringLiteral(" "))); - url.addQueryItem(QStringLiteral("response_type"), QStringLiteral("code")); + QUrlQuery query(url); + query.addQueryItem(QStringLiteral("client_id"), d->apiKey); + query.addQueryItem(QStringLiteral("redirect_uri"), QStringLiteral("http://127.0.0.1:%1").arg(d->serverPort)); + query.addQueryItem(QStringLiteral("scope"), scopes.join(QStringLiteral(" "))); + query.addQueryItem(QStringLiteral("response_type"), QStringLiteral("code")); + url.setQuery(query); qCDebug(KGAPIRaw) << "Requesting new token:" << url; d->sslIndicator->setVisible(true); d->urlEdit->setVisible(true); d->webview->setVisible(true); if (d->showProgressBar) { d->progressbar->setVisible(true); } d->webview->setUrl(url); d->setProgress(AuthWidget::UserLogin); d->webview->setFocus(); } diff --git a/src/core/ui/authwidget_p.cpp b/src/core/ui/authwidget_p.cpp index 2fff874..ecede7e 100644 --- a/src/core/ui/authwidget_p.cpp +++ b/src/core/ui/authwidget_p.cpp @@ -1,349 +1,349 @@ /* Copyright 2012, 2013 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "authwidget_p.h" #include "account.h" #include "accountinfo/accountinfo.h" #include "accountinfo/accountinfofetchjob.h" #include "private/newtokensfetchjob_p.h" #include "../../debug.h" #include #include #include #include #include #include #include #include - +#include #include using namespace KGAPI2; namespace { class WebView : public QWebEngineView { Q_OBJECT public: explicit WebView(QWidget *parent = nullptr) : QWebEngineView(parent) { // Don't store cookies, so that subsequent invocations of AuthJob won't remember // the previous accounts. QWebEngineProfile::defaultProfile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); } void contextMenuEvent(QContextMenuEvent *e) override { // No menu e->accept(); } }; class WebPage : public QWebEnginePage { Q_OBJECT public: explicit WebPage(QObject *parent = nullptr) : QWebEnginePage(parent) , mLastError(nullptr) { } QWebEngineCertificateError *lastCertificateError() const { return mLastError; } bool certificateError(const QWebEngineCertificateError &err) override { if (mLastError) { delete mLastError; } mLastError = new QWebEngineCertificateError(err.error(), err.url(), err.isOverridable(), err.errorDescription()); Q_EMIT sslError(); return false; // don't let it through } Q_SIGNALS: void sslError(); private: QWebEngineCertificateError *mLastError; }; } AuthWidgetPrivate::AuthWidgetPrivate(AuthWidget *parent): QObject(), showProgressBar(true), progress(AuthWidget::None), q(parent) { setupUi(); } AuthWidgetPrivate::~AuthWidgetPrivate() { } void AuthWidgetPrivate::setSslIcon(const QString &iconName) { // FIXME: workaround for silly Breeze icons: the small 22x22 icons are // monochromatic, which is absolutely useless since we are trying to security // information here, so instead we force use the bigger 48x48 icons which // have colors and downscale them sslIndicator->setIcon(QIcon::fromTheme(iconName).pixmap(48)); } void AuthWidgetPrivate::setupUi() { vbox = new QVBoxLayout(q); q->setLayout(vbox); label = new QLabel(q); label->setText(QLatin1String("") % tr("Authorizing token. This should take just a moment...") % QLatin1String("")); label->setWordWrap(true); label->setAlignment(Qt::AlignCenter); label->setVisible(false); vbox->addWidget(label); auto hbox = new QHBoxLayout; hbox->setSpacing(0); sslIndicator = new QToolButton(q); connect(sslIndicator, &QToolButton::clicked, this, [this]() { auto page = qobject_cast(webview->page()); if (auto err = page->lastCertificateError()) { QMessageBox msg; msg.setIconPixmap(QIcon::fromTheme(QStringLiteral("security-low")).pixmap(64)); msg.setText(err->errorDescription()); msg.addButton(QMessageBox::Ok); msg.exec(); } }); hbox->addWidget(sslIndicator); urlEdit = new QLineEdit(q); urlEdit->setReadOnly(true); hbox->addWidget(urlEdit); vbox->addLayout(hbox); progressbar = new QProgressBar(q); progressbar->setMinimum(0); progressbar->setMaximum(100); progressbar->setValue(0); vbox->addWidget(progressbar); webview = new WebView(q); auto webpage = new WebPage(webview); connect(webpage, &WebPage::sslError, this, [this]() { setSslIcon(QStringLiteral("security-low")); }); webview->setPage(webpage); vbox->addWidget(webview); connect(webview, &QWebEngineView::loadProgress, progressbar, &QProgressBar::setValue); connect(webview, &QWebEngineView::urlChanged, this, &AuthWidgetPrivate::webviewUrlChanged); connect(webview, &QWebEngineView::loadFinished, this, &AuthWidgetPrivate::webviewFinished); } void AuthWidgetPrivate::setProgress(AuthWidget::Progress progress) { qCDebug(KGAPIDebug) << progress; this->progress = progress; Q_EMIT q->progress(progress); } void AuthWidgetPrivate::emitError(const enum Error errCode, const QString& msg) { label->setVisible(true); sslIndicator->setVisible(false); urlEdit->setVisible(false); webview->setVisible(false); progressbar->setVisible(false); label->setText(QLatin1String("") % msg % QLatin1String("")); Q_EMIT q->error(errCode, msg); setProgress(AuthWidget::Error); } void AuthWidgetPrivate::webviewUrlChanged(const QUrl &url) { qCDebug(KGAPIDebug) << "URLChange:" << url; // Whoa! That should not happen! if (url.scheme() != QLatin1String("https")) { QTimer::singleShot(0, this, [this, url]() { QUrl sslUrl = url; sslUrl.setScheme(QStringLiteral("https")); webview->setUrl(sslUrl); }); return; } if (!isGoogleHost(url)) { // We handled SSL above, so we are secure. We are however outside of // accounts.google.com, which is a little suspicious in context of this class setSslIcon(QStringLiteral("security-medium")); return; } if (qobject_cast(webview->page())->lastCertificateError()) { setSslIcon(QStringLiteral("security-low")); } else { // We have no way of obtaining current SSL certifiace from QWebEngine, but we // handled SSL and accounts.google.com cases above and QWebEngine did not report // any SSL error to us, so we can assume we are safe. setSslIcon(QStringLiteral("security-high")); } // Username and password inputs are loaded dynamically, so we only get // urlChanged, but not urlFinished. if (isUsernameFrame(url)) { if (!username.isEmpty()) { webview->page()->runJavaScript(QStringLiteral("document.getElementById(\"identifierId\").value = \"%1\";").arg(username)); } } else if (isPasswordFrame(url)) { if (!password.isEmpty()) { webview->page()->runJavaScript(QStringLiteral("var elems = document.getElementsByTagName(\"input\");" "for (var i = 0; i < elems.length; i++) {" " if (elems[i].type == \"password\" && elems[i].name == \"password\") {" " elems[i].value = \"%1\";" " break;" " }" "}").arg(password)); } } } void AuthWidgetPrivate::webviewFinished(bool ok) { if (!ok) { qCWarning(KGAPIDebug) << "Failed to load" << webview->url(); } const QUrl url = webview->url(); urlEdit->setText(url.toDisplayString(QUrl::PrettyDecoded)); urlEdit->setCursorPosition(0); qCDebug(KGAPIDebug) << "URLFinished:" << url; } void AuthWidgetPrivate::socketError(QAbstractSocket::SocketError socketError) { if (connection) connection->deleteLater(); qCDebug(KGAPIDebug) << QStringLiteral("Socket error when receiving response: %1").arg(socketError); emitError(InvalidResponse, tr("Error receiving response: %1").arg(socketError)); } void AuthWidgetPrivate::socketReady() { Q_ASSERT(connection); const QByteArray data = connection->readLine(); connection->deleteLater(); qCDebug(KGAPIDebug) << QStringLiteral("Got connection on socket"); webview->stop(); sslIndicator->setVisible(false); urlEdit->setVisible(false); webview->setVisible(false); progressbar->setVisible(false); label->setVisible(true); const auto line = data.split(' '); if (line.size() != 3 || line.at(0) != QByteArray("GET") || !line.at(2).startsWith(QByteArray("HTTP/1.1"))) { qCDebug(KGAPIDebug) << QStringLiteral("Token response invalid"); emitError(InvalidResponse, tr("Token response invalid")); return; } //qCDebug(KGAPIRaw) << "Receiving data on socket: " << data; const QUrl url(QString::fromLatin1(line.at(1))); - const QUrlQuery query(url.query()); + const QUrlQuery query(url); const QString code = query.queryItemValue(QStringLiteral("code")); if (code.isEmpty()) { const QString error = query.queryItemValue(QStringLiteral("error")); if (!error.isEmpty()) { emitError(UnknownError, error); qCDebug(KGAPIDebug) << error; } else { qCDebug(KGAPIDebug) << QStringLiteral("Could not extract token from HTTP answer"); emitError(InvalidResponse, tr("Could not extract token from HTTP answer")); } return; } Q_ASSERT(serverPort != -1); auto fetch = new KGAPI2::NewTokensFetchJob(code, apiKey, secretKey, serverPort); connect(fetch, &Job::finished, this, &AuthWidgetPrivate::tokensReceived); } void AuthWidgetPrivate::tokensReceived(KGAPI2::Job* job) { KGAPI2::NewTokensFetchJob *tokensFetchJob = qobject_cast(job); account->setAccessToken(tokensFetchJob->accessToken()); account->setRefreshToken(tokensFetchJob->refreshToken()); account->setExpireDateTime(QDateTime::currentDateTime().addSecs(tokensFetchJob->expiresIn())); tokensFetchJob->deleteLater(); KGAPI2::AccountInfoFetchJob *fetchJob = new KGAPI2::AccountInfoFetchJob(account, this); connect(fetchJob, &Job::finished, this, &AuthWidgetPrivate::accountInfoReceived); qCDebug(KGAPIDebug) << "Requesting AccountInfo"; } void AuthWidgetPrivate::accountInfoReceived(KGAPI2::Job* job) { if (job->error()) { qCDebug(KGAPIDebug) << "Error when retrieving AccountInfo:" << job->errorString(); emitError((enum Error) job->error(), job->errorString()); return; } KGAPI2::ObjectsList objects = qobject_cast(job)->items(); Q_ASSERT(!objects.isEmpty()); KGAPI2::AccountInfoPtr accountInfo = objects.first().staticCast(); account->setAccountName(accountInfo->email()); job->deleteLater(); Q_EMIT q->authenticated(account); setProgress(AuthWidget::Finished); } #include "authwidget_p.moc" diff --git a/src/core/utils.cpp b/src/core/utils.cpp index 41db841..faa1fd6 100644 --- a/src/core/utils.cpp +++ b/src/core/utils.cpp @@ -1,59 +1,59 @@ /* * This file is part of LibKGAPI library * * Copyright (C) 2013 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "utils.h" #include KGAPI2::ContentType Utils::stringToContentType(const QString& contentType) { if (contentType.contains(QLatin1String("application/json")) || contentType.contains(QLatin1String("text/plain")) || contentType.contains(QLatin1String("text/javascript"))) { return KGAPI2::JSON; } else if (contentType.contains(QLatin1String("application/atom+xml")) || contentType.contains(QLatin1String("text/xml"))) { return KGAPI2::XML; } return KGAPI2::UnknownContentType; } QString Utils::bool2Str(bool val) { return (val ? QStringLiteral("true") : QStringLiteral("false")); } QString Utils::ts2Str(quint64 ts) { - return QDateTime::fromTime_t(ts).toUTC().toString(Qt::ISODate); + return QDateTime::fromSecsSinceEpoch(ts).toUTC().toString(Qt::ISODate); } QDateTime Utils::rfc3339DateFromString(const QString &string) { return QDateTime::fromString(string, Qt::ISODate); } QString Utils::rfc3339DateToString(const QDateTime& dt) { return dt.toUTC().toString(Qt::ISODate); } diff --git a/src/drive/changefetchjob.cpp b/src/drive/changefetchjob.cpp index ba1af4f..5c9590a 100644 --- a/src/drive/changefetchjob.cpp +++ b/src/drive/changefetchjob.cpp @@ -1,206 +1,208 @@ /* * This file is part of LibKGAPI library * * Copyright (C) 2013 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "changefetchjob.h" #include "account.h" #include "change.h" #include "../debug.h" #include "driveservice.h" #include "utils.h" #include #include - +#include using namespace KGAPI2; using namespace KGAPI2::Drive; class Q_DECL_HIDDEN ChangeFetchJob::Private { public: Private(ChangeFetchJob *parent); QNetworkRequest createRequest(const QUrl &url); QString changeId; bool includeDeleted; bool includeSubscribed; int maxResults; qlonglong startChangeId; private: ChangeFetchJob *q; }; ChangeFetchJob::Private::Private(ChangeFetchJob *parent): includeDeleted(true), includeSubscribed(true), maxResults(0), startChangeId(0), q(parent) { } QNetworkRequest ChangeFetchJob::Private::createRequest(const QUrl &url) { QNetworkRequest request; request.setRawHeader("Authorization", "Bearer " + q->account()->accessToken().toLatin1()); request.setUrl(url); return request; } ChangeFetchJob::ChangeFetchJob(const QString &changeId, const AccountPtr &account, QObject *parent): FetchJob(account, parent), d(new Private(this)) { d->changeId = changeId; } ChangeFetchJob::ChangeFetchJob(const AccountPtr &account, QObject *parent): FetchJob(account, parent), d(new Private(this)) { } ChangeFetchJob::~ChangeFetchJob() { delete d; } void ChangeFetchJob::setIncludeDeleted(bool includeDeleted) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify includeDeleted property when job is running"; return; } d->includeDeleted = includeDeleted; } bool ChangeFetchJob::includeDeleted() const { return d->includeDeleted; } void ChangeFetchJob::setIncludeSubscribed(bool includeSubscribed) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify includeSubscribed property when job is running"; return; } d->includeSubscribed = includeSubscribed; } bool ChangeFetchJob::includeSubscribed() const { return d->includeSubscribed; } void ChangeFetchJob::setMaxResults(int maxResults) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify maxResults property when job is running"; return; } d->maxResults = maxResults; } int ChangeFetchJob::maxResults() const { return d->maxResults; } void ChangeFetchJob::setStartChangeId(qlonglong startChangeId) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify startChangeId property when job is running"; } d->startChangeId = startChangeId; } qlonglong ChangeFetchJob::startChangeId() const { return d->startChangeId; } void ChangeFetchJob::start() { QUrl url; if (d->changeId.isEmpty()) { url = DriveService::fetchChangesUrl(); - url.addQueryItem(QStringLiteral("includeDeleted"), Utils::bool2Str(d->includeDeleted)); - url.addQueryItem(QStringLiteral("includeSubscribed"), Utils::bool2Str(d->includeSubscribed)); + QUrlQuery query(url); + query.addQueryItem(QStringLiteral("includeDeleted"), Utils::bool2Str(d->includeDeleted)); + query.addQueryItem(QStringLiteral("includeSubscribed"), Utils::bool2Str(d->includeSubscribed)); if (d->maxResults > 0) { - url.addQueryItem(QStringLiteral("maxResults"), QString::number(d->maxResults)); + query.addQueryItem(QStringLiteral("maxResults"), QString::number(d->maxResults)); } if (d->startChangeId > 0) { - url.addQueryItem(QStringLiteral("startChangeId"), QString::number(d->startChangeId)); + query.addQueryItem(QStringLiteral("startChangeId"), QString::number(d->startChangeId)); } + url.setQuery(query); } else { url = DriveService::fetchChangeUrl(d->changeId); } const QNetworkRequest request = d->createRequest(url); enqueueRequest(request); } ObjectsList ChangeFetchJob::handleReplyWithItems(const QNetworkReply *reply, const QByteArray &rawData) { FeedData feedData; feedData.requestUrl = reply->request().url(); ObjectsList items; QString itemId; const QString contentType = reply->header(QNetworkRequest::ContentTypeHeader).toString(); ContentType ct = Utils::stringToContentType(contentType); if (ct == KGAPI2::JSON) { if (d->changeId.isEmpty()) { items << Change::fromJSONFeed(rawData, feedData); } else { items << Change::fromJSON(rawData); } } else { setError(KGAPI2::InvalidResponse); setErrorString(tr("Invalid response content type")); emitFinished(); return items; } if (feedData.nextPageUrl.isValid()) { const QNetworkRequest request = d->createRequest(feedData.nextPageUrl); enqueueRequest(request); } return items; } diff --git a/src/drive/driveservice.cpp b/src/drive/driveservice.cpp index e672e77..45766a6 100644 --- a/src/drive/driveservice.cpp +++ b/src/drive/driveservice.cpp @@ -1,288 +1,292 @@ /* * This file is part of LibKGAPI library * * Copyright (C) 2013 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "driveservice.h" #include "utils.h" +#include + namespace KGAPI2 { namespace Private { static const QUrl GoogleApisUrl(QStringLiteral("https://www.googleapis.com")); static const QString AppsBasePath(QStringLiteral("/drive/v2/about")); static const QString FilesBasePath(QStringLiteral("/drive/v2/files")); static const QString ChangeBasePath(QStringLiteral("/drive/v2/changes")); } namespace DriveService { QUrl fetchAboutUrl(bool includeSubscribed, qlonglong maxChangeIdCount, qlonglong startChangeId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::AppsBasePath); - url.addQueryItem(QStringLiteral("includeSubscribed"), Utils::bool2Str(includeSubscribed)); + QUrlQuery query(url); + query.addQueryItem(QStringLiteral("includeSubscribed"), Utils::bool2Str(includeSubscribed)); if (maxChangeIdCount > 0) { - url.addQueryItem(QStringLiteral("maxChangeIdCount"), QString::number(maxChangeIdCount)); + query.addQueryItem(QStringLiteral("maxChangeIdCount"), QString::number(maxChangeIdCount)); } if (startChangeId > 0) { - url.addQueryItem(QStringLiteral("startChangeId"), QString::number(startChangeId)); + query.addQueryItem(QStringLiteral("startChangeId"), QString::number(startChangeId)); } + url.setQuery(query); return url; } QUrl fetchAppUrl(const QString &appId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::AppsBasePath % QLatin1Char('/') % appId); return url; } QUrl fetchAppsUrl() { QUrl url(Private::GoogleApisUrl); url.setPath(Private::AppsBasePath); return url; } QUrl fetchChildReference(const QString &folderId, const QString &referenceId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::FilesBasePath % QLatin1Char('/') % folderId % QLatin1String("/children/") % referenceId); return url; } QUrl fetchChildReferences(const QString &folderId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::FilesBasePath % QLatin1Char('/') % folderId % QLatin1String("/children")); return url; } QUrl createChildReference(const QString &folderId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::FilesBasePath % QLatin1Char('/') % folderId % QLatin1String("/children")); return url; } QUrl deleteChildReference(const QString &folderId, const QString &referenceId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::FilesBasePath % QLatin1Char('/') % folderId % QLatin1String("/children/") % referenceId); return url; } QUrl fetchChangeUrl(const QString &changeId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::ChangeBasePath % QLatin1Char('/') % changeId); return url; } QUrl copyFileUrl(const QString &fileId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::FilesBasePath % QLatin1Char('/') % fileId % QLatin1String("/copy")); return url; } QUrl deleteFileUrl(const QString &fileId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::FilesBasePath % QLatin1Char('/') % fileId); return url; } QUrl fetchFileUrl(const QString &fileId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::FilesBasePath % QLatin1Char('/') % fileId); return url; } QUrl fetchFilesUrl() { QUrl url(Private::GoogleApisUrl); url.setPath(Private::FilesBasePath); return url; } QUrl fetchChangesUrl() { QUrl url(Private::GoogleApisUrl); url.setPath(Private::ChangeBasePath); return url; } QUrl touchFileUrl(const QString &fileId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::FilesBasePath % QLatin1Char('/') % fileId % QLatin1String("/touch")); return url; } QUrl trashFileUrl(const QString &fileId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::FilesBasePath % QLatin1Char('/') % fileId % QLatin1String("/trash")); return url; } QUrl untrashFileUrl(const QString &fileId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::FilesBasePath % QLatin1Char('/') % fileId % QLatin1String("/untrash")); return url; } QUrl uploadMetadataFileUrl(const QString &fileId) { QUrl url(Private::GoogleApisUrl); if (!fileId.isEmpty()) { url.setPath(Private::FilesBasePath % QLatin1Char('/') % fileId); } else { url.setPath(Private::FilesBasePath); } return url; } QUrl uploadMediaFileUrl(const QString &fileId) { QUrl url(Private::GoogleApisUrl); if (!fileId.isEmpty()) { url.setPath(QLatin1String("/upload") % Private::FilesBasePath % QLatin1Char('/') % fileId); } else { url.setPath(QLatin1String("/upload") % Private::FilesBasePath); } return url; } QUrl uploadMultipartFileUrl(const QString &fileId) { QUrl url(Private::GoogleApisUrl); if (!fileId.isEmpty()) { url.setPath(QLatin1String("/upload") % Private::FilesBasePath % QLatin1Char('/') % fileId); } else { url.setPath(QLatin1String("/upload") % Private::FilesBasePath); } return url; } QUrl fetchParentReferenceUrl(const QString &fileId, const QString &referenceId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::FilesBasePath % QLatin1Char('/') % fileId % QLatin1String("/parents/") % referenceId); return url; } QUrl fetchParentReferencesUrl(const QString &fileId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::FilesBasePath % QLatin1Char('/') % fileId % QLatin1String("/parents")); return url; } QUrl createParentReferenceUrl(const QString &fileId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::FilesBasePath % QLatin1Char('/') % fileId % QLatin1String("/parents")); return url; } QUrl deleteParentReferenceUrl(const QString &fileId, const QString &referenceId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::FilesBasePath % QLatin1Char('/') % fileId % QLatin1String("/parents/") % referenceId); return url; } QUrl fetchPermissionsUrl(const QString &fileId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::FilesBasePath % QLatin1Char('/') % fileId % QLatin1String("/premissions")); return url; } QUrl fetchPermissionUrl(const QString &fileId, const QString &permissionId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::FilesBasePath % QLatin1Char('/') % fileId % QLatin1String("/permissions/") % permissionId); return url; } QUrl createPermissionUrl(const QString &fileId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::FilesBasePath % QLatin1Char('/') % fileId % QLatin1String("/permissions")); return url; } QUrl deletePermissionUrl(const QString &fileId, const QString &permissionId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::FilesBasePath % QLatin1Char('/') % fileId % QLatin1String("/permissions/") % permissionId); return url; } QUrl modifyPermissionUrl(const QString &fileId, const QString &permissionId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::FilesBasePath % QLatin1Char('/') % fileId % QLatin1String("/permissions/") % permissionId); return url; } QUrl fetchRevisionUrl(const QString &fileId, const QString &revisionId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::FilesBasePath % QLatin1Char('/') % fileId % QLatin1String("/revisions/") % revisionId); return url; } QUrl fetchRevisionsUrl(const QString &fileId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::FilesBasePath % QLatin1Char('/') % fileId % QLatin1String("/revisions")); return url; } QUrl deleteRevisionUrl(const QString &fileId, const QString &revisionId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::FilesBasePath % QLatin1Char('/') % fileId % QLatin1String("/revisions/") % revisionId); return url; } QUrl modifyRevisionUrl(const QString &fileId, const QString &revisionId) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::FilesBasePath % QLatin1Char('/') % fileId % QLatin1String("/revisions/") % revisionId); return url; } } // namespace DriveService } // namespace KGAPI2 diff --git a/src/drive/fileabstractdatajob.cpp b/src/drive/fileabstractdatajob.cpp index f0ad932..82c29da 100644 --- a/src/drive/fileabstractdatajob.cpp +++ b/src/drive/fileabstractdatajob.cpp @@ -1,182 +1,186 @@ /* * This file is part of LibKGAPI library * * Copyright (C) 2013 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "fileabstractdatajob.h" #include "../debug.h" #include "utils.h" +#include + using namespace KGAPI2; using namespace KGAPI2::Drive; class Q_DECL_HIDDEN FileAbstractDataJob::Private { public: Private(); bool convert; bool ocr; QString ocrLanguage; bool pinned; QString timedTextLanguage; QString timedTextTrackName; }; FileAbstractDataJob::Private::Private(): convert(false), ocr(false), pinned(false) { } FileAbstractDataJob::FileAbstractDataJob(const AccountPtr &account, QObject *parent): Job(account, parent), d(new Private) { } FileAbstractDataJob::~FileAbstractDataJob() { delete d; } bool FileAbstractDataJob::convert() const { return d->convert; } void FileAbstractDataJob::setConvert(bool convert) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify setConvert property when job is running"; return; } d->convert = convert; } bool FileAbstractDataJob::ocr() const { return d->ocr; } void FileAbstractDataJob::setOcr(bool ocr) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify ocr property when job is running"; return; } d->ocr = ocr; } QString FileAbstractDataJob::ocrLanguage() const { return d->ocrLanguage; } void FileAbstractDataJob::setOcrLanguage(const QString &ocrLanguage) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify ocrLanguage property when job is running"; return; } d->ocrLanguage = ocrLanguage; } bool FileAbstractDataJob::pinned() const { return d->pinned; } void FileAbstractDataJob::setPinned(bool pinned) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify pinned property when job is running"; return; } d->pinned = pinned; } QString FileAbstractDataJob::timedTextLanguage() const { return d->timedTextLanguage; } void FileAbstractDataJob::setTimedTextLanguage(const QString &timedTextLanguage) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify timedTextLanguage property when job is running"; return; } d->timedTextLanguage = timedTextLanguage; } QString FileAbstractDataJob::timedTextTrackName() const { return d->timedTextLanguage; } void FileAbstractDataJob::setTimedTextTrackName(const QString &timedTextTrackName) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify timedTextTrackName property when job is running"; return; } d->timedTextTrackName = timedTextTrackName; } QUrl FileAbstractDataJob::updateUrl(QUrl &url) { - url.removeQueryItem(QStringLiteral("convert")); - url.addQueryItem(QStringLiteral("convert"), Utils::bool2Str(d->convert)); + QUrlQuery query(url); + query.removeQueryItem(QStringLiteral("convert")); + query.addQueryItem(QStringLiteral("convert"), Utils::bool2Str(d->convert)); - url.removeQueryItem(QStringLiteral("ocr")); - url.removeQueryItem(QStringLiteral("ocrLanguage")); - url.addQueryItem(QStringLiteral("ocr"), Utils::bool2Str(d->ocr)); + query.removeQueryItem(QStringLiteral("ocr")); + query.removeQueryItem(QStringLiteral("ocrLanguage")); + query.addQueryItem(QStringLiteral("ocr"), Utils::bool2Str(d->ocr)); if (d->ocr && !d->ocrLanguage.isEmpty()) { - url.addQueryItem(QStringLiteral("ocrLanguage"), d->ocrLanguage); + query.addQueryItem(QStringLiteral("ocrLanguage"), d->ocrLanguage); } - url.removeQueryItem(QStringLiteral("pinned")); - url.addQueryItem(QStringLiteral("pinned"), Utils::bool2Str(d->pinned)); + query.removeQueryItem(QStringLiteral("pinned")); + query.addQueryItem(QStringLiteral("pinned"), Utils::bool2Str(d->pinned)); - url.removeQueryItem(QStringLiteral("timedTextLanguage")); + query.removeQueryItem(QStringLiteral("timedTextLanguage")); if (!d->timedTextLanguage.isEmpty()) { - url.addQueryItem(QStringLiteral("timedTextLanguage"), d->timedTextLanguage); + query.addQueryItem(QStringLiteral("timedTextLanguage"), d->timedTextLanguage); } - url.removeQueryItem(QStringLiteral("timedTextTrackName")); + query.removeQueryItem(QStringLiteral("timedTextTrackName")); if (!d->timedTextTrackName.isEmpty()) { - url.addQueryItem(QStringLiteral("timedTextTrackName"), d->timedTextTrackName); + query.addQueryItem(QStringLiteral("timedTextTrackName"), d->timedTextTrackName); } + url.setQuery(query); return url; } #include "moc_fileabstractdatajob.cpp" diff --git a/src/drive/fileabstractuploadjob.cpp b/src/drive/fileabstractuploadjob.cpp index 86a2d66..607f8ce 100644 --- a/src/drive/fileabstractuploadjob.cpp +++ b/src/drive/fileabstractuploadjob.cpp @@ -1,344 +1,347 @@ /* * This file is part of LibKGAPI library * * Copyright (C) 2013 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "fileabstractuploadjob.h" #include "account.h" #include "../debug.h" #include "driveservice.h" #include "file.h" #include "utils.h" #include #include #include #include #include #include +#include using namespace KGAPI2; using namespace KGAPI2::Drive; class Q_DECL_HIDDEN FileAbstractUploadJob::Private { public: Private(FileAbstractUploadJob *parent); void processNext(); QByteArray buildMultipart(const QString &filePath, const FilePtr &metaData, QString &boundary); QByteArray readFile(const QString &filePath, QString &contentType); void _k_uploadProgress(qint64 bytesSent, qint64 totalBytes); int originalFilesCount; QMap files; QMap uploadedFiles; bool useContentAsIndexableText; File::SerializationOptions serializationOptions = File::NoOptions; private: FileAbstractUploadJob *const q; }; FileAbstractUploadJob::Private::Private(FileAbstractUploadJob *parent): originalFilesCount(0), useContentAsIndexableText(false), q(parent) { } QByteArray FileAbstractUploadJob::Private::readFile(const QString &filePath, QString &contentType) { QFile file(filePath); if (!file.open(QIODevice::ReadOnly)) { qCWarning(KGAPIDebug) << "Failed to access" << filePath; return QByteArray(); } const QMimeDatabase db; const QMimeType mime = db.mimeTypeForFileNameAndData(filePath, &file); contentType = mime.name(); file.reset(); QByteArray output = file.readAll(); file.close(); return output; } QByteArray FileAbstractUploadJob::Private::buildMultipart(const QString &filePath, const FilePtr &metaData, QString &boundary) { QString fileContentType; QByteArray fileContent; fileContent = readFile(filePath, fileContentType); if (fileContent.isEmpty()) { return QByteArray(); } // Wannabe implementation of RFC2387, i.e. multipart/related QByteArray body; const QByteArray md5 = QCryptographicHash::hash(filePath.toLatin1(), QCryptographicHash::Md5); boundary = QString::fromLatin1(md5.toHex()); body += "--" + boundary.toLatin1() + '\n'; body += "Content-Type: application/json; charset=UTF-8\n"; body += '\n'; body += File::toJSON(metaData); body += '\n'; body += '\n'; body += "--" + boundary.toLatin1() + '\n'; body += "Content-Type: " + fileContentType.toLatin1() + '\n'; body += '\n'; body += fileContent; body += '\n'; body += "--" + boundary.toLatin1() + "--"; return body; } void FileAbstractUploadJob::Private::processNext() { if (files.isEmpty()) { q->emitFinished(); return; } const QString filePath = files.cbegin().key(); if (!filePath.startsWith(QLatin1String("?=")) && !QFile::exists(filePath)) { qCWarning(KGAPIDebug) << filePath << "is not a valid file path"; processNext(); return; } const FilePtr metaData = files.take(filePath); QUrl url; if (filePath.startsWith(QLatin1String("?="))) { url = q->createUrl(QString(), metaData); } else { url = q->createUrl(filePath, metaData); } q->updateUrl(url); - url.addQueryItem(QStringLiteral("useContentAsIndexableText"), Utils::bool2Str(useContentAsIndexableText)); + QUrlQuery query(url); + query.addQueryItem(QStringLiteral("useContentAsIndexableText"), Utils::bool2Str(useContentAsIndexableText)); QNetworkRequest request; QByteArray rawData; QString contentType; // just to be sure - url.removeQueryItem(QStringLiteral("uploadType")); + query.removeQueryItem(QStringLiteral("uploadType")); if (metaData.isNull()) { - url.addQueryItem(QStringLiteral("uploadType"), QStringLiteral("media")); + query.addQueryItem(QStringLiteral("uploadType"), QStringLiteral("media")); rawData = readFile(filePath, contentType); if (rawData.isEmpty()) { processNext(); return; } } else if (!filePath.startsWith(QLatin1String("?="))) { - url.addQueryItem(QStringLiteral("uploadType"), QStringLiteral("multipart")); + query.addQueryItem(QStringLiteral("uploadType"), QStringLiteral("multipart")); QString boundary; rawData = buildMultipart(filePath, metaData, boundary); contentType = QStringLiteral("multipart/related; boundary=%1").arg(boundary); if (rawData.isEmpty()) { processNext(); return; } } else { rawData = File::toJSON(metaData, q->serializationOptions()); contentType = QStringLiteral("application/json"); } + url.setQuery(query); request.setUrl(url); request.setRawHeader("Authorization", "Bearer " + q->account()->accessToken().toLatin1()); request.setHeader(QNetworkRequest::ContentLengthHeader, rawData.length()); request.setHeader(QNetworkRequest::ContentTypeHeader, contentType); request.setAttribute(QNetworkRequest::User, filePath); q->enqueueRequest(request, rawData, contentType); } void FileAbstractUploadJob::Private::_k_uploadProgress(qint64 bytesSent, qint64 totalBytes) { // Each file consists of 100 units, so if we have two files, one already // uploaded and the other one uploaded from 50%, the values are (150, 200) int processedParts = (originalFilesCount - files.count()) * 100; int currentFileParts = 100.0 * ((qreal) bytesSent / (qreal) totalBytes); q->emitProgress(processedParts + currentFileParts, originalFilesCount * 100); } FileAbstractUploadJob::FileAbstractUploadJob(const FilePtr &metadata, const AccountPtr &account, QObject *parent): FileAbstractDataJob(account, parent), d(new Private(this)) { d->files.insert(QStringLiteral("?=0"), metadata); d->originalFilesCount = 1; } FileAbstractUploadJob::FileAbstractUploadJob(const FilesList &metadata, const AccountPtr &account, QObject *parent): FileAbstractDataJob(account, parent), d(new Private(this)) { int i = 0; for (const FilePtr &file : metadata) { d->files.insert(QStringLiteral("?=%1").arg(i), file); ++i; } d->originalFilesCount = d->files.count(); } FileAbstractUploadJob::FileAbstractUploadJob(const QString &filePath, const AccountPtr &account, QObject *parent): FileAbstractDataJob(account, parent), d(new Private(this)) { d->files.insert(filePath, FilePtr()); d->originalFilesCount = 1; } FileAbstractUploadJob::FileAbstractUploadJob(const QString &filePath, const FilePtr &metaData, const AccountPtr &account, QObject *parent): FileAbstractDataJob(account, parent), d(new Private(this)) { d->files.insert(filePath, metaData); d->originalFilesCount = 1; } FileAbstractUploadJob::FileAbstractUploadJob(const QStringList &filePaths, const AccountPtr &account, QObject *parent): FileAbstractDataJob(account, parent), d(new Private(this)) { for (const QString & filePath : filePaths) { d->files.insert(filePath, FilePtr()); } d->originalFilesCount = d->files.count(); } FileAbstractUploadJob::FileAbstractUploadJob(const QMap< QString, FilePtr > &files, const AccountPtr &account, QObject *parent): FileAbstractDataJob(account, parent), d(new Private(this)) { d->files = files; d->originalFilesCount = d->files.count(); } FileAbstractUploadJob::~FileAbstractUploadJob() { delete d; } void FileAbstractUploadJob::setUseContentAsIndexableText(bool useContentAsIndexableText) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify useContentAsIndexableText property when job is running"; return; } d->useContentAsIndexableText = useContentAsIndexableText; } bool FileAbstractUploadJob::useContentAsIndexableText() const { return d->useContentAsIndexableText; } void FileAbstractUploadJob::start() { d->processNext(); } void FileAbstractUploadJob::dispatchRequest(QNetworkAccessManager *accessManager, const QNetworkRequest &request, const QByteArray &data, const QString &contentType) { Q_UNUSED(contentType) QNetworkReply *reply = dispatch(accessManager, request, data); connect(reply, &QNetworkReply::uploadProgress, this, [this](qint64 bytesSent, qint64 totalBytes) {d->_k_uploadProgress(bytesSent, totalBytes); }); } void FileAbstractUploadJob::handleReply(const QNetworkReply *reply, const QByteArray &rawData) { const QString contentType = reply->header(QNetworkRequest::ContentTypeHeader).toString(); ContentType ct = Utils::stringToContentType(contentType); if (ct == KGAPI2::JSON) { const QNetworkRequest request = reply->request(); const QString filePath = request.attribute(QNetworkRequest::User).toString(); FilePtr file = File::fromJSON(rawData); d->uploadedFiles.insert(filePath, file); } else { setError(KGAPI2::InvalidResponse); setErrorString(tr("Invalid response content type")); emitFinished(); return; } d->processNext(); } void FileAbstractUploadJob::setSerializationOptions(File::SerializationOptions options) { d->serializationOptions = options; } File::SerializationOptions FileAbstractUploadJob::serializationOptions() const { return d->serializationOptions; } #include "moc_fileabstractuploadjob.cpp" diff --git a/src/drive/filefetchjob.cpp b/src/drive/filefetchjob.cpp index e940e7e..2912531 100644 --- a/src/drive/filefetchjob.cpp +++ b/src/drive/filefetchjob.cpp @@ -1,359 +1,363 @@ /* * This file is part of LibKGAPI library * * Copyright (C) 2013 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "filefetchjob.h" #include "filesearchquery.h" #include "account.h" #include "../debug.h" #include "driveservice.h" #include "file.h" #include "utils.h" #include #include +#include using namespace KGAPI2; using namespace KGAPI2::Drive; class Q_DECL_HIDDEN FileFetchJob::Private { public: Private(FileFetchJob *parent); void processNext(); QNetworkRequest createRequest(const QUrl &url); QStringList fieldsToStrings(qulonglong fields); FileSearchQuery searchQuery; QStringList filesIDs; bool isFeed; bool updateViewedDate; qulonglong fields; private: FileFetchJob *const q; }; FileFetchJob::Private::Private(FileFetchJob *parent): isFeed(false), updateViewedDate(false), fields(FileFetchJob::AllFields), q(parent) { } QNetworkRequest FileFetchJob::Private::createRequest(const QUrl &url) { QNetworkRequest request; request.setUrl(url); request.setRawHeader("Authorization", "Bearer " + q->account()->accessToken().toLatin1()); return request; } QStringList FileFetchJob::Private::fieldsToStrings(qulonglong fields) { if (fields & AllFields) { return QStringList(); } QStringList fieldsStrings; // Always fetch kind fieldsStrings << QStringLiteral("kind"); // FIXME: Use QMetaEnum once it supports enums larger than int if (fields & Id) { fieldsStrings << QStringLiteral("id"); } if (fields & Title) { fieldsStrings << QStringLiteral("title"); } if (fields & MimeType) { fieldsStrings << QStringLiteral("mimeType"); } if (fields & Description) { fieldsStrings << QStringLiteral("description"); } if (fields & Labels) { fieldsStrings << QStringLiteral("labels"); } if (fields & CreatedDate) { fieldsStrings << QStringLiteral("createdDate"); } if (fields & ModifiedDate) { fieldsStrings << QStringLiteral("modifiedDate"); } if (fields & ModifiedByMeDate) { fieldsStrings << QStringLiteral("modifiedByMeDate"); } if (fields & DownloadUrl) { fieldsStrings << QStringLiteral("downloadUrl"); } if (fields & IndexableText) { fieldsStrings << QStringLiteral("indexableText"); } if (fields & UserPermission) { fieldsStrings << QStringLiteral("userPermission"); } if (fields & FileExtension) { fieldsStrings << QStringLiteral("fileExtension"); } if (fields & MD5Checksum) { fieldsStrings << QStringLiteral("md5Checksum"); } if (fields & FileSize) { fieldsStrings << QStringLiteral("fileSize"); } if (fields & AlternateLink) { fieldsStrings << QStringLiteral("alternateLink"); } if (fields & EmbedLink) { fieldsStrings << QStringLiteral("embedLink"); } if (fields & SharedWithMeDate) { fieldsStrings << QStringLiteral("sharedWithMeDate"); } if (fields & Parents) { fieldsStrings << QStringLiteral("parents"); } if (fields & ExportLinks) { fieldsStrings << QStringLiteral("exportLinks"); } if (fields & OriginalFilename) { fieldsStrings << QStringLiteral("originalFilename"); } if (fields & OwnerNames) { fieldsStrings << QStringLiteral("ownerNames"); } if (fields & LastModifiedByMeDate) { fieldsStrings << QStringLiteral("lastModifiedByMeDate"); } if (fields & Editable) { fieldsStrings << QStringLiteral("editable"); } if (fields & WritersCanShare) { fieldsStrings << QStringLiteral("writersCanShare"); } if (fields & ThumbnailLink) { fieldsStrings << QStringLiteral("thumbnailLink"); } if (fields & LastViewedByMeDate) { fieldsStrings << QStringLiteral("lastViewedByMeDate"); } if (fields & WebContentLink) { fieldsStrings << QStringLiteral("webContentLink"); } if (fields & ExplicitlyTrashed) { fieldsStrings << QStringLiteral("explicitlyTrashed"); } if (fields & ImageMediaMetadata) { fieldsStrings << QStringLiteral("imageMediaMetadata"); } if (fields & Thumbnail) { fieldsStrings << QStringLiteral("thumbnail"); } if (fields & WebViewLink) { fieldsStrings << QStringLiteral("webViewLink"); } if (fields & IconLink) { fieldsStrings << QStringLiteral("iconLink"); } if (fields & Shared) { fieldsStrings << QStringLiteral("shared"); } if (fields & Owners) { fieldsStrings << QStringLiteral("owners"); } if (fields & LastModifyingUser) { fieldsStrings << QStringLiteral("lastModifyingUser"); } if (fields & AppDataContents) { fieldsStrings << QStringLiteral("appDataContents"); } if (fields & OpenWithLinks) { fieldsStrings << QStringLiteral("openWithLinks"); } if (fields & DefaultOpenWithLink) { fieldsStrings << QStringLiteral("defaultOpenWithLink"); } if (fields & HeadRevisionId) { fieldsStrings << QStringLiteral("headRevisionId"); } if (fields & Copyable) { fieldsStrings << QStringLiteral("copyable"); } if (fields & Properties) { fieldsStrings << QStringLiteral("properties"); } if (fields & MarkedViewedByMeDate) { fieldsStrings << QStringLiteral("markedViewedByMeDate"); } if (fields & Version) { fieldsStrings << QStringLiteral("version"); } if (fields & SharingUser) { fieldsStrings << QStringLiteral("sharingUser"); } if (fields & Permissions) { fieldsStrings << QStringLiteral("permissions"); } return fieldsStrings; } void FileFetchJob::Private::processNext() { QUrl url; if (isFeed) { url = DriveService::fetchFilesUrl(); + QUrlQuery query(url); if (!searchQuery.isEmpty()) { - url.addQueryItem(QStringLiteral("q"), searchQuery.serialize()); + query.addQueryItem(QStringLiteral("q"), searchQuery.serialize()); } if (fields != FileFetchJob::AllFields) { const QStringList fieldsStrings = fieldsToStrings(fields); - url.addQueryItem(QStringLiteral("fields"), + query.addQueryItem(QStringLiteral("fields"), QStringLiteral("etag,kind,nextLink,nextPageToken,selfLink,items(%1)").arg(fieldsStrings.join(QStringLiteral(",")))); } + url.setQuery(query); } else { if (filesIDs.isEmpty()) { q->emitFinished(); return; } const QString fileId = filesIDs.takeFirst(); url = DriveService::fetchFileUrl(fileId); - if (fields != FileFetchJob::AllFields) { const QStringList fieldsStrings = fieldsToStrings(fields); - url.addQueryItem(QStringLiteral("fields"), fieldsStrings.join(QStringLiteral(","))); + QUrlQuery query(url); + query.addQueryItem(QStringLiteral("fields"), fieldsStrings.join(QStringLiteral(","))); + url.setQuery(query); } } q->enqueueRequest(createRequest(url)); } FileFetchJob::FileFetchJob(const QString &fileId, const AccountPtr &account, QObject *parent): FetchJob(account, parent), d(new Private(this)) { d->filesIDs << fileId; } FileFetchJob::FileFetchJob(const QStringList &filesIds, const AccountPtr &account, QObject *parent): FetchJob(account, parent), d(new Private(this)) { d->filesIDs << filesIds; } FileFetchJob::FileFetchJob(const AccountPtr &account, QObject *parent): FetchJob(account, parent), d(new Private(this)) { d->isFeed = true; } FileFetchJob::FileFetchJob(const FileSearchQuery &query, const AccountPtr &account, QObject *parent): FetchJob(account, parent), d(new Private(this)) { d->isFeed = true; d->searchQuery = query; } FileFetchJob::~FileFetchJob() { delete d; } bool FileFetchJob::updateViewedDate() const { return d->updateViewedDate; } void FileFetchJob::setUpdateViewedDate(bool updateViewedDate) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify updateViewedDate property when job is running."; return; } d->updateViewedDate = updateViewedDate; } void FileFetchJob::start() { d->processNext(); } void FileFetchJob::setFields(qulonglong fields) { d->fields = fields; } qulonglong FileFetchJob::fields() const { return d->fields; } ObjectsList FileFetchJob::handleReplyWithItems(const QNetworkReply *reply, const QByteArray &rawData) { ObjectsList items; const QString contentType = reply->header(QNetworkRequest::ContentTypeHeader).toString(); ContentType ct = Utils::stringToContentType(contentType); if (ct == KGAPI2::JSON) { if (d->isFeed) { FeedData feedData; items << File::fromJSONFeed(rawData, feedData); if (feedData.nextPageUrl.isValid()) { const QNetworkRequest request = d->createRequest(feedData.nextPageUrl); enqueueRequest(request); } } else { items << File::fromJSON(rawData); d->processNext(); } } else { setError(KGAPI2::InvalidResponse); setErrorString(tr("Invalid response content type")); emitFinished(); return items; } return items; } diff --git a/src/drive/filemodifyjob.cpp b/src/drive/filemodifyjob.cpp index d2d0e70..7507529 100644 --- a/src/drive/filemodifyjob.cpp +++ b/src/drive/filemodifyjob.cpp @@ -1,184 +1,187 @@ /* * This file is part of LibKGAPI library * * Copyright (C) 2013 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "filemodifyjob.h" #include "file.h" #include "../debug.h" #include "driveservice.h" #include "utils.h" #include #include +#include using namespace KGAPI2; using namespace KGAPI2::Drive; class Q_DECL_HIDDEN FileModifyJob::Private { public: Private(); QMap < QString /* filepath */, QString /* fileId */ > files; bool createNewRevision; bool changeModifiedDate; bool updateViewedDate; }; FileModifyJob::Private::Private(): createNewRevision(true), changeModifiedDate(false), updateViewedDate(true) { } FileModifyJob::FileModifyJob(const FilePtr &metadata, const AccountPtr &account, QObject *parent): FileAbstractUploadJob(metadata, account, parent), d(new Private) { d->files.insert(QStringLiteral("?=0"), metadata->id()); setSerializationOptions(File::ExcludeCreationDate); } FileModifyJob::FileModifyJob(const QString &filePath, const QString &fileId, const AccountPtr &account, QObject *parent): FileAbstractUploadJob(filePath, account, parent), d(new Private) { d->files.insert(filePath, fileId); } FileModifyJob::FileModifyJob(const QString &filePath, const FilePtr &metaData, const AccountPtr &account, QObject *parent): FileAbstractUploadJob(filePath, account, parent), d(new Private) { d->files.insert(filePath, metaData->id()); } FileModifyJob::FileModifyJob(const QMap< QString, QString > &files, const AccountPtr &account, QObject *parent): FileAbstractUploadJob(files.keys(), account, parent), d(new Private) { d->files = files; } FileModifyJob::FileModifyJob(const QMap< QString, FilePtr > &files, const AccountPtr &account, QObject *parent): FileAbstractUploadJob(files, account, parent), d(new Private) { QMap::ConstIterator iter = files.constBegin(); QMap::ConstIterator iterEnd = files.constEnd(); for (; iter != iterEnd; ++iter) { d->files.insert(iter.key(), iter.value()->id()); } } FileModifyJob::~FileModifyJob() { delete d; } bool FileModifyJob::createNewRevision() const { return d->createNewRevision; } void FileModifyJob::setCreateNewRevision(bool createNewRevision) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify createNewRevision property when the job is running"; return; } d->createNewRevision = createNewRevision; } bool FileModifyJob::updateModifiedDate() const { return d->updateViewedDate; } void FileModifyJob::setUpdateModifiedDate(bool updateModifiedDate) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify updateModifiedDate property when the job is running"; return; } d->updateViewedDate = updateModifiedDate; } bool FileModifyJob::updateViewedDate() const { return d->updateViewedDate; } void FileModifyJob::setUpdateViewedDate(bool updateViewedDate) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify updateViewedDate property when job is running"; return; } d->updateViewedDate = updateViewedDate; } QUrl FileModifyJob::createUrl(const QString &filePath, const FilePtr &metaData) { QUrl url; if (metaData.isNull()) { url = DriveService::uploadMediaFileUrl(d->files.value(filePath)); } else if (filePath.isEmpty()) { url = DriveService::uploadMetadataFileUrl(metaData->id()); } else { url = DriveService::uploadMultipartFileUrl(d->files.value(filePath)); } - url.addQueryItem(QStringLiteral("newRevision"), Utils::bool2Str(d->createNewRevision)); - url.addQueryItem(QStringLiteral("setModifiedDate"), Utils::bool2Str(d->changeModifiedDate)); - url.addQueryItem(QStringLiteral("updateViewedDate"), Utils::bool2Str(d->updateViewedDate)); + QUrlQuery query(url); + query.addQueryItem(QStringLiteral("newRevision"), Utils::bool2Str(d->createNewRevision)); + query.addQueryItem(QStringLiteral("setModifiedDate"), Utils::bool2Str(d->changeModifiedDate)); + query.addQueryItem(QStringLiteral("updateViewedDate"), Utils::bool2Str(d->updateViewedDate)); + url.setQuery(query); return url; } QNetworkReply *FileModifyJob::dispatch(QNetworkAccessManager *accessManager, const QNetworkRequest &request, const QByteArray &data) { return accessManager->put(request, data); } diff --git a/src/latitude/latitudeservice.cpp b/src/latitude/latitudeservice.cpp index 9591286..9ec4639 100644 --- a/src/latitude/latitudeservice.cpp +++ b/src/latitude/latitudeservice.cpp @@ -1,228 +1,233 @@ /* Copyright (C) 2012 Jan Grulich This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "latitudeservice.h" #include "location.h" #include +#include namespace KGAPI2 { namespace LatitudeService { namespace Private { LocationPtr parseLocation(const QVariantMap &map); static const QUrl GoogleApisUrl(QStringLiteral("https://www.googleapis.com")); static const QString LocationBasePath(QStringLiteral("/latitude/v1/location")); static const QString CurrentLocationBasePath(QStringLiteral("/latitude/v1/currentLocation")); } LocationPtr JSONToLocation(const QByteArray & jsonData) { QJsonDocument document = QJsonDocument::fromJson(jsonData); if (document.isNull()) { return LocationPtr(); } const QVariantMap data = document.toVariant().toMap(); const QVariantMap info = data.value(QStringLiteral("data")).toMap(); return Private::parseLocation(info); } QByteArray locationToJSON(const LocationPtr &location) { QVariantMap map, output; map.insert(QStringLiteral("kind"), QStringLiteral("latitude#location")); map.insert(QStringLiteral("latitude"), QString::number(location->latitude())); map.insert(QStringLiteral("longitude"), QString::number(location->longitude())); if (location->timestamp() != 0) { map.insert(QStringLiteral("timestampMs"), location->timestamp()); } if (location->accuracy() != -1) { map.insert(QStringLiteral("accuracy"), location->accuracy()); } if (location->speed() != -1) { map.insert(QStringLiteral("speed"), location->speed()); } if (location->heading() != -1) { map.insert(QStringLiteral("heading"), location->heading()); } map.insert(QStringLiteral("altitude"), location->altitude()); if (location->altitudeAccuracy() != 0) { map.insert(QStringLiteral("altitudeAccuracy"), location->altitudeAccuracy()); } output.insert(QStringLiteral("data"), map); QJsonDocument document = QJsonDocument::fromVariant(output); return document.toJson(QJsonDocument::Compact); } ObjectsList parseLocationJSONFeed(const QByteArray & jsonFeed, FeedData & feedData) { Q_UNUSED(feedData); ObjectsList output; QJsonDocument document = QJsonDocument::fromJson(jsonFeed); const QVariantMap map = document.toVariant().toMap(); const QVariantMap data = map.value(QStringLiteral("data")).toMap(); const QVariantList items = data.value(QStringLiteral("items")).toList(); output.reserve(items.size()); for (const QVariant &c : items) { QVariantMap location = c.toMap(); output << Private::parseLocation(location).dynamicCast(); } return output; } LocationPtr Private::parseLocation(const QVariantMap &map) { LocationPtr location(new Location); if (map.contains(QStringLiteral("timestampMs"))) { location->setTimestamp(map.value(QStringLiteral("timestampMs")).toULongLong()); } if (map.contains(QStringLiteral("latitude"))) { location->setLatitude(map.value(QStringLiteral("latitude")).toFloat()); } if (map.contains(QStringLiteral("longitude"))) { location->setLongitude(map.value(QStringLiteral("longitude")).toFloat()); } if (map.contains(QStringLiteral("accuracy"))) { location->setAccuracy(map.value(QStringLiteral("accuracy")).toInt()); } if (map.contains(QStringLiteral("speed"))) { location->setSpeed(map.value(QStringLiteral("speed")).toInt()); } if (map.contains(QStringLiteral("heading"))) { location->setHeading(map.value(QStringLiteral("heading")).toInt()); } if (map.contains(QStringLiteral("altitude"))) { location->setAltitude(map.value(QStringLiteral("altitude")).toInt()); } if (map.contains(QStringLiteral("altitudeAccuracy"))) { location->setAltitudeAccuracy(map.value(QStringLiteral("altitudeAccuracy")).toInt()); } return location; } QString APIVersion() { return QStringLiteral("1"); } QUrl retrieveCurrentLocationUrl(const Latitude::Granularity granularity) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::CurrentLocationBasePath); + QUrlQuery query(url); if (granularity == Latitude::City) { - url.addQueryItem(QStringLiteral("granularity"), QStringLiteral("city")); + query.addQueryItem(QStringLiteral("granularity"), QStringLiteral("city")); } else if (granularity == Latitude::Best) { - url.addQueryItem(QStringLiteral("granularity"), QStringLiteral("best")); + query.addQueryItem(QStringLiteral("granularity"), QStringLiteral("best")); } + url.setQuery(query); return url; } QUrl deleteCurrentLocationUrl() { QUrl url(Private::GoogleApisUrl); url.setPath(Private::CurrentLocationBasePath); return url; } QUrl insertCurrentLocationUrl() { QUrl url(Private::GoogleApisUrl); url.setPath(Private::CurrentLocationBasePath); return url; } QUrl locationHistoryUrl(const Latitude::Granularity granularity, const int maxResults, const qlonglong maxTime, const qlonglong minTime) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::LocationBasePath); - + QUrlQuery query(url); if (granularity == Latitude::City) { - url.addQueryItem(QStringLiteral("granularity"), QStringLiteral("city")); + query.addQueryItem(QStringLiteral("granularity"), QStringLiteral("city")); } else if (granularity == Latitude::Best) { - url.addQueryItem(QStringLiteral("granularity"), QStringLiteral("best")); + query.addQueryItem(QStringLiteral("granularity"), QStringLiteral("best")); } if (maxResults > 0) { - url.addQueryItem(QStringLiteral("max-results"), QString::number(maxResults)); + query.addQueryItem(QStringLiteral("max-results"), QString::number(maxResults)); } if ((maxTime > 0) && (maxTime >= minTime)) { - url.addQueryItem(QStringLiteral("max-time"), QString::number(maxTime)); + query.addQueryItem(QStringLiteral("max-time"), QString::number(maxTime)); } if ((minTime > 0) && (minTime <= maxTime)) { - url.addQueryItem(QStringLiteral("min-time"), QString::number(minTime)); + query.addQueryItem(QStringLiteral("min-time"), QString::number(minTime)); } + url.setQuery(query); return url; } QUrl retrieveLocationUrl(const qlonglong id, const Latitude::Granularity granularity) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::LocationBasePath % QLatin1Char('/') % QString::number(id)); - + QUrlQuery query(url); if (granularity == Latitude::City) { - url.addQueryItem(QStringLiteral("granularity"), QStringLiteral("city")); + query.addQueryItem(QStringLiteral("granularity"), QStringLiteral("city")); } else if (granularity == Latitude::Best) { - url.addQueryItem(QStringLiteral("granularity"), QStringLiteral("best")); + query.addQueryItem(QStringLiteral("granularity"), QStringLiteral("best")); } + url.setQuery(query); return url; } QUrl insertLocationUrl() { QUrl url(Private::GoogleApisUrl); url.setPath(Private::LocationBasePath); return url; } QUrl deleteLocationUrl(const qlonglong id) { Q_UNUSED(id); QUrl url(Private::GoogleApisUrl); url.setPath(Private::LocationBasePath); return url; } } // namespace LatitudeService } // namespace KGAPI2 diff --git a/src/staticmaps/staticmapurl.cpp b/src/staticmaps/staticmapurl.cpp index cf45964..de2b6d2 100644 --- a/src/staticmaps/staticmapurl.cpp +++ b/src/staticmaps/staticmapurl.cpp @@ -1,495 +1,499 @@ /* Copyright (C) 2012 Jan Grulich This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "staticmapurl.h" +#include + using namespace KGAPI2; class Q_DECL_HIDDEN StaticMapUrl::Private { public: Private(); Private(const Private& other); void init(const Private &other); StaticMapUrl::LocationType locationType; StaticMapUrl::ImageFormat format; QString locationString; KContacts::Address locationAddress; KContacts::Geo locationGeo; StaticMapUrl::MapType maptype; QList markers; QList paths; StaticMapUrl::Scale scale; bool sensor; QSize size; QString visibleString; KContacts::Address visibleAddress; KContacts::Geo visibleGeo; StaticMapUrl::LocationType visibleLocationType; qint32 zoom; }; StaticMapUrl::Private::Private(): locationType(StaticMapUrl::Undefined), format(StaticMapUrl::PNG), maptype(StaticMapUrl::Roadmap), scale(StaticMapUrl::Normal), sensor(false), visibleLocationType(StaticMapUrl::Undefined), zoom(-1) { } StaticMapUrl::Private::Private(const Private & other) { init(other); } void StaticMapUrl::Private::init(const StaticMapUrl::Private& other) { locationType = other.locationType; format = other.format; locationString = other.locationString; locationAddress = other.locationAddress; locationGeo = other.locationGeo; maptype = other.maptype; markers = other.markers; paths = other.paths; scale = other.scale; sensor = other.sensor; size = other.size; visibleString = other.visibleString; visibleAddress = other.visibleAddress; visibleGeo = other.visibleGeo; visibleLocationType = other.visibleLocationType; zoom = other.zoom; } StaticMapUrl::StaticMapUrl(): d(new Private) { } StaticMapUrl::StaticMapUrl(const StaticMapUrl& other): d(new Private(*(other.d))) { } StaticMapUrl::~StaticMapUrl() { delete d; } StaticMapUrl& StaticMapUrl::operator=(const StaticMapUrl& other) { if (&other == this) { return *this; } d->init(*(other.d)); return *this; } StaticMapUrl::StaticMapUrl(const QString &location, const QSize &size, quint32 zoom, bool sensor): d(new Private) { setLocation(location); setSize(size); setZoomLevel(zoom); setSensorUsed(sensor); } StaticMapUrl::StaticMapUrl(const KContacts::Address &address, const QSize &size, quint32 zoom, bool sensor): d(new Private) { setLocation(address); setSize(size); setZoomLevel(zoom); setSensorUsed(sensor); } StaticMapUrl::StaticMapUrl(const KContacts::Geo &geo, const QSize &size, quint32 zoom, bool sensor): d(new Private) { setLocation(geo); setSize(size); setZoomLevel(zoom); setSensorUsed(sensor); } StaticMapUrl::LocationType StaticMapUrl::locationType() const { return d->locationType; } StaticMapUrl::ImageFormat StaticMapUrl::format() const { return d->format; } void StaticMapUrl::setFormat(const StaticMapUrl::ImageFormat format) { d->format = format; } bool StaticMapUrl::isValid() const { bool maOrPa = true; if (d->markers.isEmpty()) { for (const StaticMapPath & path : qAsConst(d->paths)) { if (!path.isValid()) maOrPa = false; } } else { for (const StaticMapMarker & marker : qAsConst(d->markers)) { if (!marker.isValid()) maOrPa = false; } } if (maOrPa) { if ((d->locationType == Undefined || d->zoom == -1) && (d->visibleLocationType == Undefined)) return false; } return !(d->size.isEmpty()); } QString StaticMapUrl::locationString() const { return d->locationString; } void StaticMapUrl::setLocation(const QString& location) { d->locationString = location; d->locationType = String; d->locationAddress.clear(); d->locationGeo.setLatitude(91); d->locationGeo.setLongitude(181); } KContacts::Address StaticMapUrl::locationAddress() const { return d->locationAddress; } void StaticMapUrl::setLocation(const KContacts::Address& address) { d->locationAddress = address; d->locationType = KABCAddress; d->locationString.clear(); d->locationGeo.setLatitude(91); d->locationGeo.setLongitude(181); } KContacts::Geo StaticMapUrl::locationGeo() const { return d->locationGeo; } void StaticMapUrl::setLocation(const KContacts::Geo& geo) { d->locationGeo = geo; d->locationType = KABCGeo; d->locationString.clear(); d->locationAddress.clear(); } StaticMapUrl::MapType StaticMapUrl::mapType() const { return d->maptype; } void StaticMapUrl::setMapType(const StaticMapUrl::MapType type) { d->maptype = type; } QList< StaticMapMarker > StaticMapUrl::markers() const { return d->markers; } void StaticMapUrl::setMarker(const StaticMapMarker& marker) { QList markers; markers << marker; d->markers = markers; } void StaticMapUrl::setMarkers(const QList< StaticMapMarker >& markers) { d->markers = markers; } QList StaticMapUrl::paths() const { return d->paths; } void StaticMapUrl::setPath(const StaticMapPath & path) { QList paths; paths << path; d->paths = paths; } void StaticMapUrl::setPaths(const QList< StaticMapPath >& paths) { d->paths = paths; } QSize StaticMapUrl::size() const { return d->size; } void StaticMapUrl::setSize(const QSize& size) { d->size = size; } StaticMapUrl::Scale StaticMapUrl::scale() const { return d->scale; } void StaticMapUrl::setScale(const Scale scale) { d->scale = scale; } bool StaticMapUrl::sensorUsed() const { return d->sensor; } void StaticMapUrl::setSensorUsed(const bool sensor) { d->sensor = sensor; } QString StaticMapUrl::visibleLocationString() const { return d->visibleString; } void StaticMapUrl::setVisibleLocation(const QString & location) { d->visibleString = location; d->visibleLocationType = String; d->visibleAddress.clear(); d->visibleGeo.setLatitude(911); d->visibleGeo.setLongitude(181); } KContacts::Address StaticMapUrl::visibleLocationAddress() const { return d->locationAddress; } void StaticMapUrl::setVisibleLocation(const KContacts::Address & address) { d->visibleAddress = address; d->visibleLocationType = KABCAddress; d->visibleString.clear(); d->visibleGeo.setLatitude(911); d->visibleGeo.setLongitude(181); } KContacts::Geo StaticMapUrl::visibleLocationGeo() const { return d->locationGeo; } void StaticMapUrl::setVisibleLocation(const KContacts::Geo & geo) { d->visibleGeo = geo; d->visibleLocationType = KABCGeo; d->visibleString.clear(); d->visibleAddress.clear(); } StaticMapUrl::LocationType StaticMapUrl::visibleLocationType() const { return d->visibleLocationType; } qint8 StaticMapUrl::zoomLevel() const { return d->zoom; } void StaticMapUrl::setZoomLevel(const quint32 zoom) { d->zoom = zoom; } QUrl StaticMapUrl::url() const { QUrl url(QStringLiteral("http://maps.googleapis.com/maps/api/staticmap")); + QUrlQuery query(url); if (d->locationType != Undefined) { QString param; switch (d->locationType) { case Undefined: case String: param = d->locationString; param = param.replace(QLatin1String(", "), QLatin1String(",")); param = param.replace(QLatin1String(". "), QLatin1String(".")); param = param.replace(QLatin1Char(' '), QLatin1Char('+')); - url.addQueryItem(QStringLiteral("center"), param); + query.addQueryItem(QStringLiteral("center"), param); break; case KABCAddress: param = d->locationAddress.formattedAddress(); param = param.replace(QLatin1String(", "), QLatin1String(",")); param = param.replace(QLatin1String(". "), QLatin1String(".")); param = param.replace(QLatin1Char(' '), QLatin1Char('+')); param = param.replace(QLatin1Char('\n'), QLatin1Char(',')); - url.addQueryItem(QStringLiteral("center"), param); + query.addQueryItem(QStringLiteral("center"), param); break; case KABCGeo: param = QString::number(d->locationGeo.latitude()) + QLatin1String(",") + QString::number(d->locationGeo.longitude()); - url.addQueryItem(QStringLiteral("center"), param); + query.addQueryItem(QStringLiteral("center"), param); break; } } if (d->zoom != -1) - url.addQueryItem(QStringLiteral("zoom"), QString::number(d->zoom)); + query.addQueryItem(QStringLiteral("zoom"), QString::number(d->zoom)); if (!d->size.isEmpty()) { QString size = QString::number(d->size.width()) + QLatin1String("x") + QString::number(d->size.height()); - url.addQueryItem(QStringLiteral("size"), size); + query.addQueryItem(QStringLiteral("size"), size); } if (d->scale != Normal) - url.addQueryItem(QStringLiteral("scale"), QString::number(2)); + query.addQueryItem(QStringLiteral("scale"), QString::number(2)); if (d->format != PNG) { QString format; switch (d->format) { case PNG: case PNG32: format = QStringLiteral("png32"); break; case GIF: format = QStringLiteral("gif"); break; case JPG: format = QStringLiteral("jpg"); break; case JPGBaseline: format = QStringLiteral("jpg-baseline"); break; } - url.addQueryItem(QStringLiteral("format"), format); + query.addQueryItem(QStringLiteral("format"), format); } if (d->maptype != Roadmap) { QString maptype; switch (d->maptype) { case Roadmap: case Satellite: maptype = QStringLiteral("satellite"); break; case Terrain: maptype = QStringLiteral("terrain"); break; case Hybrid: maptype = QStringLiteral("hybrid"); break; } - url.addQueryItem(QStringLiteral("maptype"), maptype); + query.addQueryItem(QStringLiteral("maptype"), maptype); } for (const StaticMapMarker & marker : qAsConst(d->markers)) { if (marker.isValid()) - url.addQueryItem(QStringLiteral("markers"), marker.toString()); + query.addQueryItem(QStringLiteral("markers"), marker.toString()); } for (const StaticMapPath & path : qAsConst(d->paths)) { if (path.isValid()) - url.addQueryItem(QStringLiteral("path"), path.toString()); + query.addQueryItem(QStringLiteral("path"), path.toString()); } if (d->visibleLocationType != Undefined) { QString param; switch (d->visibleLocationType) { case Undefined: case String: param = d->visibleString; param = param.replace(QLatin1String(", "), QLatin1String(",")); param = param.replace(QLatin1String(". "), QLatin1String(".")); param = param.replace(QLatin1Char(' '), QLatin1Char('+')); - url.addQueryItem(QStringLiteral("visible"), param); + query.addQueryItem(QStringLiteral("visible"), param); break; case KABCAddress: param = d->visibleAddress.formattedAddress(); param = param.replace(QLatin1String(", "), QLatin1String(",")); param = param.replace(QLatin1String(". "), QLatin1String(".")); param = param.replace(QLatin1Char(' '), QLatin1Char('+')); param = param.replace(QLatin1Char('\n'), QLatin1Char(',')); - url.addQueryItem(QStringLiteral("visible"), param); + query.addQueryItem(QStringLiteral("visible"), param); break; case KABCGeo: param = QString::number(d->visibleGeo.latitude()) + QLatin1String(",") + QString::number(d->visibleGeo.longitude()); - url.addQueryItem(QStringLiteral("visible"), param); + query.addQueryItem(QStringLiteral("visible"), param); break; } } if (d->sensor) - url.addQueryItem(QStringLiteral("sensor"), QStringLiteral("true")); + query.addQueryItem(QStringLiteral("sensor"), QStringLiteral("true")); else - url.addQueryItem(QStringLiteral("sensor"), QStringLiteral("false")); + query.addQueryItem(QStringLiteral("sensor"), QStringLiteral("false")); + url.setQuery(query); return url; } diff --git a/src/tasks/taskcreatejob.cpp b/src/tasks/taskcreatejob.cpp index 42afe8e..d69b28c 100644 --- a/src/tasks/taskcreatejob.cpp +++ b/src/tasks/taskcreatejob.cpp @@ -1,134 +1,136 @@ /* * This file is part of LibKGAPI library * * Copyright (C) 2013 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "taskcreatejob.h" #include "tasksservice.h" #include "account.h" #include "../debug.h" #include "utils.h" #include "task.h" #include "private/queuehelper_p.h" #include #include - +#include using namespace KGAPI2; class Q_DECL_HIDDEN TaskCreateJob::Private { public: QueueHelper tasks; QString taskListId; QString parentId; }; TaskCreateJob::TaskCreateJob(const TaskPtr& task, const QString& taskListId, const AccountPtr& account, QObject* parent): CreateJob(account, parent), d(new Private) { d->tasks << task; d->taskListId = taskListId; } TaskCreateJob::TaskCreateJob(const TasksList& tasks, const QString& taskListId, const AccountPtr& account, QObject* parent): CreateJob(account, parent), d(new Private) { d->tasks = tasks; d->taskListId = taskListId; } TaskCreateJob::~TaskCreateJob() { delete d; } QString TaskCreateJob::parentItem() const { return d->parentId; } void TaskCreateJob::setParentItem(const QString &parentId) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify parentItem property when job is running!"; return; } d->parentId = parentId; } void TaskCreateJob::start() { if (d->tasks.atEnd()) { emitFinished(); return; } const TaskPtr task = d->tasks.current(); QUrl url = TasksService::createTaskUrl(d->taskListId); + QUrlQuery query(url); if (!d->parentId.isEmpty()) { - url.addQueryItem(QStringLiteral("parent"), d->parentId); + query.addQueryItem(QStringLiteral("parent"), d->parentId); } + url.setQuery(query); QNetworkRequest request; request.setRawHeader("Authorization", "Bearer " + account()->accessToken().toLatin1()); request.setUrl(url); const QByteArray rawData = TasksService::taskToJSON(task); QStringList headers; const auto rawHeaderList = request.rawHeaderList(); headers.reserve(rawHeaderList.size()); for (const QByteArray &str : qAsConst(rawHeaderList)) { headers << QLatin1String(str) + QLatin1String(": ") + QLatin1String(request.rawHeader(str)); } qCDebug(KGAPIRaw) << headers; enqueueRequest(request, rawData, QStringLiteral("application/json")); } ObjectsList TaskCreateJob::handleReplyWithItems(const QNetworkReply *reply, const QByteArray& rawData) { const QString contentType = reply->header(QNetworkRequest::ContentTypeHeader).toString(); ContentType ct = Utils::stringToContentType(contentType); ObjectsList items; if (ct != KGAPI2::JSON) { setError(KGAPI2::InvalidResponse); setErrorString(tr("Invalid response content type")); emitFinished(); return items; } items << TasksService::JSONToTask(rawData).dynamicCast(); d->tasks.currentProcessed(); // Enqueue next item or finish start(); return items; } diff --git a/src/tasks/taskfetchjob.cpp b/src/tasks/taskfetchjob.cpp index 59ff18f..3172c3b 100644 --- a/src/tasks/taskfetchjob.cpp +++ b/src/tasks/taskfetchjob.cpp @@ -1,264 +1,267 @@ /* * This file is part of LibKGAPI library * * Copyright (C) 2013 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "taskfetchjob.h" #include "tasksservice.h" #include "account.h" #include "../debug.h" #include "task.h" #include "utils.h" #include #include +#include using namespace KGAPI2; class Q_DECL_HIDDEN TaskFetchJob::Private { public: Private(TaskFetchJob *parent); QNetworkRequest createRequest(const QUrl &url); QString taskId; QString taskListId; bool fetchDeleted; bool fetchCompleted; quint64 updatedTimestamp; quint64 completedMin; quint64 completedMax; quint64 dueMin; quint64 dueMax; private: TaskFetchJob * const q; }; TaskFetchJob::Private::Private(TaskFetchJob* parent): fetchDeleted(true), fetchCompleted(true), updatedTimestamp(0), completedMin(0), completedMax(0), dueMin(0), dueMax(0), q(parent) { } QNetworkRequest TaskFetchJob::Private::createRequest(const QUrl& url) { QNetworkRequest request; request.setRawHeader("Authorization", "Bearer " + q->account()->accessToken().toLatin1()); request.setUrl(url); QStringList headers; const auto rawHeaderList = request.rawHeaderList(); headers.reserve(rawHeaderList.size()); for (const QByteArray &str : qAsConst(rawHeaderList)) { headers << QLatin1String(str) + QLatin1String(": ") + QLatin1String(request.rawHeader(str)); } qCDebug(KGAPIRaw) << headers; return request; } TaskFetchJob::TaskFetchJob(const QString& taskListId, const AccountPtr& account, QObject* parent): FetchJob(account, parent), d(new Private(this)) { d->taskListId = taskListId; } TaskFetchJob::TaskFetchJob(const QString& taskId, const QString& taskListId, const AccountPtr& account, QObject* parent): FetchJob(account, parent), d(new Private(this)) { d->taskId = taskId; d->taskListId = taskListId; } TaskFetchJob::~TaskFetchJob() { delete d; } void TaskFetchJob::setFetchOnlyUpdated(quint64 timestamp) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify fetchOnlyUpdated property when job is running"; return; } d->updatedTimestamp = timestamp; } quint64 TaskFetchJob::fetchOnlyUpdated() { return d->updatedTimestamp; } void TaskFetchJob::setFetchCompleted(bool fetchCompleted) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify fetchCompleted property when job is running"; return; } d->fetchCompleted = fetchCompleted; } bool TaskFetchJob::fetchCompleted() const { return d->fetchCompleted; } void TaskFetchJob::setFetchDeleted(bool fetchDeleted) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify fetchDeleted property when job is running"; return; } d->fetchDeleted = fetchDeleted; } bool TaskFetchJob::fetchDeleted() const { return d->fetchDeleted; } void TaskFetchJob::setCompletedMin(quint64 timestamp) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify completedMin property when job is running"; return; } d->completedMin = timestamp; } quint64 TaskFetchJob::completedMin() const { return d->completedMin; } void TaskFetchJob::setCompletedMax(quint64 timestamp) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify completedMax property when job is running"; return; } d->completedMax = timestamp; } quint64 TaskFetchJob::completedMax() const { return d->completedMax; } void TaskFetchJob::setDueMin(quint64 timestamp) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify dueMin property when job is running"; return; } d->dueMin = timestamp; } quint64 TaskFetchJob::dueMin() const { return d->dueMin; } void TaskFetchJob::setDueMax(quint64 timestamp) { if (isRunning()) { qCWarning(KGAPIDebug) << "Can't modify dueMax property when job is running"; return; } d->dueMax = timestamp; } quint64 TaskFetchJob::dueMax() const { return d->dueMax; } void TaskFetchJob::start() { QUrl url; if (d->taskId.isEmpty()) { url = TasksService::fetchAllTasksUrl(d->taskListId); - url.addQueryItem(QStringLiteral("showDeleted"), Utils::bool2Str(d->fetchDeleted)); - url.addQueryItem(QStringLiteral("showCompleted"), Utils::bool2Str(d->fetchCompleted)); + QUrlQuery query(url); + query.addQueryItem(QStringLiteral("showDeleted"), Utils::bool2Str(d->fetchDeleted)); + query.addQueryItem(QStringLiteral("showCompleted"), Utils::bool2Str(d->fetchCompleted)); if (d->updatedTimestamp > 0) { - url.addQueryItem(QStringLiteral("updatedMin"), Utils::ts2Str(d->updatedTimestamp)); + query.addQueryItem(QStringLiteral("updatedMin"), Utils::ts2Str(d->updatedTimestamp)); } if (d->completedMin > 0) { - url.addQueryItem(QStringLiteral("completedMin"), Utils::ts2Str(d->completedMin)); + query.addQueryItem(QStringLiteral("completedMin"), Utils::ts2Str(d->completedMin)); } if (d->completedMax > 0) { - url.addQueryItem(QStringLiteral("completedMax"), Utils::ts2Str(d->completedMax)); + query.addQueryItem(QStringLiteral("completedMax"), Utils::ts2Str(d->completedMax)); } if (d->dueMin > 0) { - url.addQueryItem(QStringLiteral("dueMin"), Utils::ts2Str(d->dueMin)); + query.addQueryItem(QStringLiteral("dueMin"), Utils::ts2Str(d->dueMin)); } if (d->dueMax > 0) { - url.addQueryItem(QStringLiteral("dueMax"), Utils::ts2Str(d->dueMax)); + query.addQueryItem(QStringLiteral("dueMax"), Utils::ts2Str(d->dueMax)); } + url.setQuery(query); } else { url = TasksService::fetchTaskUrl(d->taskListId, d->taskId); } const QNetworkRequest request = d->createRequest(url); enqueueRequest(request); } ObjectsList TaskFetchJob::handleReplyWithItems(const QNetworkReply *reply, const QByteArray& rawData) { FeedData feedData; feedData.requestUrl = reply->url(); ObjectsList items; QString itemId; const QString contentType = reply->header(QNetworkRequest::ContentTypeHeader).toString(); ContentType ct = Utils::stringToContentType(contentType); if (ct == KGAPI2::JSON) { if (d->taskId.isEmpty()) { items = TasksService::parseJSONFeed(rawData, feedData); } else { items << TasksService::JSONToTask(rawData); } } else { setError(KGAPI2::InvalidResponse); setErrorString(tr("Invalid response content type")); emitFinished(); return items; } if (feedData.nextPageUrl.isValid()) { const QNetworkRequest request = d->createRequest(feedData.nextPageUrl); enqueueRequest(request); } return items; } diff --git a/src/tasks/tasksservice.cpp b/src/tasks/tasksservice.cpp index 0da22e1..b09ec13 100644 --- a/src/tasks/tasksservice.cpp +++ b/src/tasks/tasksservice.cpp @@ -1,305 +1,311 @@ /* Copyright (C) 2012 - 2018 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "tasksservice.h" #include "object.h" #include "task.h" #include "tasklist.h" #include "utils.h" #include - +#include #include namespace KGAPI2 { namespace TasksService { namespace Private { ObjectsList parseTaskListJSONFeed(const QVariantList &items); ObjectsList parseTasksJSONFeed(const QVariantList &items); ObjectPtr JSONToTaskList(const QVariantMap &jsonData); ObjectPtr JSONToTask(const QVariantMap &jsonData); static const QUrl GoogleApisUrl(QStringLiteral("https://www.googleapis.com")); static const QString TasksBasePath(QStringLiteral("/tasks/v1/lists")); static const QString TasksListsBasePath(QStringLiteral("/tasks/v1/users/@me/lists")); } ObjectsList parseJSONFeed(const QByteArray& jsonFeed, FeedData& feedData) { QJsonDocument document = QJsonDocument::fromJson(jsonFeed); if (document.isNull()) { return ObjectsList(); } ObjectsList list; const QVariantMap feed = document.toVariant().toMap(); if (feed.value(QStringLiteral("kind")).toString() == QLatin1String("tasks#taskLists")) { list = Private::parseTaskListJSONFeed(feed.value(QStringLiteral("items")).toList()); if (feed.contains(QStringLiteral("nextPageToken"))) { feedData.nextPageUrl = fetchTaskListsUrl(); - feedData.nextPageUrl.addQueryItem(QStringLiteral("pageToken"), feed.value(QStringLiteral("nextPageToken")).toString()); - if (feedData.nextPageUrl.queryItemValue(QStringLiteral("maxResults")).isEmpty()) { - feedData.nextPageUrl.addQueryItem(QStringLiteral("maxResults"), QStringLiteral("20")); + QUrlQuery query(feedData.nextPageUrl); + query.addQueryItem(QStringLiteral("pageToken"), feed.value(QStringLiteral("nextPageToken")).toString()); + if (query.queryItemValue(QStringLiteral("maxResults")).isEmpty()) { + query.addQueryItem(QStringLiteral("maxResults"), QStringLiteral("20")); } + feedData.nextPageUrl.setQuery(query); } } else if (feed.value(QStringLiteral("kind")).toString() == QLatin1String("tasks#tasks")) { list = Private::parseTasksJSONFeed(feed.value(QStringLiteral("items")).toList()); if (feed.contains(QStringLiteral("nextPageToken"))) { QString taskListId = feedData.requestUrl.toString().remove(QStringLiteral("https://www.googleapis.com/tasks/v1/lists/")); taskListId = taskListId.left(taskListId.indexOf(QLatin1Char('/'))); feedData.nextPageUrl = fetchAllTasksUrl(taskListId); - feedData.nextPageUrl.addQueryItem(QStringLiteral("pageToken"), feed.value(QStringLiteral("nextPageToken")).toString()); - if (feedData.nextPageUrl.queryItemValue(QStringLiteral("maxResults")).isEmpty()) { - feedData.nextPageUrl.addQueryItem(QStringLiteral("maxResults"), QStringLiteral("20")); + QUrlQuery query(feedData.nextPageUrl); + query.addQueryItem(QStringLiteral("pageToken"), feed.value(QStringLiteral("nextPageToken")).toString()); + if (query.queryItemValue(QStringLiteral("maxResults")).isEmpty()) { + query.addQueryItem(QStringLiteral("maxResults"), QStringLiteral("20")); } + feedData.nextPageUrl.setQuery(query); } } return list; } QUrl fetchAllTasksUrl(const QString& tasklistID) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::TasksBasePath % QLatin1Char('/') % tasklistID % QLatin1String("/tasks")); return url; } QUrl fetchTaskUrl(const QString& tasklistID, const QString& taskID) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::TasksBasePath % QLatin1Char('/') % tasklistID % QLatin1String("/tasks/") % taskID); return url; } QUrl createTaskUrl(const QString& tasklistID) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::TasksBasePath % QLatin1Char('/') % tasklistID % QLatin1String("/tasks")); return url; } QUrl updateTaskUrl(const QString& tasklistID, const QString& taskID) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::TasksBasePath % QLatin1Char('/') % tasklistID % QLatin1String("/tasks/") % taskID); return url; } QUrl removeTaskUrl(const QString& tasklistID, const QString& taskID) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::TasksBasePath % QLatin1Char('/') % tasklistID % QLatin1String("/tasks/") % taskID); return url; } QUrl moveTaskUrl(const QString& tasklistID, const QString& taskID, const QString& newParent) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::TasksBasePath % QLatin1Char('/') % tasklistID % QLatin1String("/tasks/") % taskID % QLatin1String("/move")); if (!newParent.isEmpty()) { - url.addQueryItem(QStringLiteral("parent"), newParent); + QUrlQuery query(url); + query.addQueryItem(QStringLiteral("parent"), newParent); + url.setQuery(query); } return url; } QUrl fetchTaskListsUrl() { QUrl url(Private::GoogleApisUrl); url.setPath(Private::TasksListsBasePath); return url; } QUrl createTaskListUrl() { QUrl url(Private::GoogleApisUrl); url.setPath(Private::TasksListsBasePath); return url; } QUrl updateTaskListUrl(const QString& tasklistID) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::TasksListsBasePath % QLatin1Char('/') % tasklistID); return url; } QUrl removeTaskListUrl(const QString& tasklistID) { QUrl url(Private::GoogleApisUrl); url.setPath(Private::TasksListsBasePath % QLatin1Char('/') % tasklistID); return url; } /******************************* PRIVATE ******************************/ ObjectPtr Private::JSONToTaskList(const QVariantMap &jsonData) { TaskListPtr taskList(new TaskList()); taskList->setUid(jsonData.value(QStringLiteral("id")).toString()); taskList->setEtag(jsonData.value(QStringLiteral("etag")).toString()); taskList->setTitle(jsonData.value(QStringLiteral("title")).toString()); return taskList.dynamicCast(); } TaskListPtr JSONToTaskList(const QByteArray& jsonData) { QJsonDocument document = QJsonDocument::fromJson(jsonData); const QVariantMap data = document.toVariant().toMap(); if (data.value(QStringLiteral("kind")).toString() == QLatin1String("tasks#taskList")) { return Private::JSONToTaskList(data).staticCast(); } return TaskListPtr(); } ObjectPtr Private::JSONToTask(const QVariantMap &jsonData) { TaskPtr task(new Task()); task->setUid(jsonData.value(QStringLiteral("id")).toString()); task->setEtag(jsonData.value(QStringLiteral("etag")).toString()); task->setSummary(jsonData.value(QStringLiteral("title")).toString()); task->setLastModified(Utils::rfc3339DateFromString(jsonData.value(QStringLiteral("updated")).toString())); task->setDescription(jsonData.value(QStringLiteral("notes")).toString()); if (jsonData.value(QStringLiteral("status")).toString() == QStringLiteral("needsAction")) { task->setStatus(KCalCore::Incidence::StatusNeedsAction); } else if (jsonData.value(QStringLiteral("status")).toString() == QStringLiteral("completed")) { task->setStatus(KCalCore::Incidence::StatusCompleted); } else { task->setStatus(KCalCore::Incidence::StatusNone); } task->setDtDue(Utils::rfc3339DateFromString(jsonData.value(QStringLiteral("due")).toString())); if (task->status() == KCalCore::Incidence::StatusCompleted) { task->setCompleted(Utils::rfc3339DateFromString(jsonData.value(QStringLiteral("completed")).toString())); } task->setDeleted(jsonData.value(QStringLiteral("deleted")).toBool()); if (jsonData.contains(QStringLiteral("parent"))) { task->setRelatedTo(jsonData.value(QStringLiteral("parent")).toString(), KCalCore::Incidence::RelTypeParent); } return task.dynamicCast(); } TaskPtr JSONToTask(const QByteArray& jsonData) { QJsonDocument document = QJsonDocument::fromJson(jsonData); const QVariantMap data = document.toVariant().toMap(); if (data.value(QStringLiteral("kind")).toString() == QLatin1String("tasks#task")) { return Private::JSONToTask(data).staticCast(); } return TaskPtr(); } QByteArray taskListToJSON(const TaskListPtr &taskList) { QVariantMap output; output.insert(QStringLiteral("kind"), QStringLiteral("tasks#taskList")); if (!taskList->uid().isEmpty()) { output.insert(QStringLiteral("id"), taskList->uid()); } output.insert(QStringLiteral("title"), taskList->title()); QJsonDocument document = QJsonDocument::fromVariant(output); return document.toJson(QJsonDocument::Compact); } QByteArray taskToJSON(const TaskPtr &task) { QVariantMap output; output.insert(QStringLiteral("kind"), QStringLiteral("tasks#task")); if (!task->uid().isEmpty()) { output.insert(QStringLiteral("id"), task->uid()); } output.insert(QStringLiteral("title"), task->summary()); output.insert(QStringLiteral("notes"), task->description()); if (!task->relatedTo(KCalCore::Incidence::RelTypeParent).isEmpty()) { output.insert(QStringLiteral("parent"), task->relatedTo(KCalCore::Incidence::RelTypeParent)); } if (task->dtDue().isValid()) { /* Google accepts only UTC time strictly in this format :( */ output.insert(QStringLiteral("due"), task->dtDue().toUTC().toString(QStringLiteral("yyyy-MM-ddThh:mm:ss.zzzZ"))); } if ((task->status() == KCalCore::Incidence::StatusCompleted) && task->completed().isValid()) { /* Google accepts only UTC time strictly in this format :( */ output.insert(QStringLiteral("completed"), task->completed().toUTC().toString(QStringLiteral("yyyy-MM-ddThh:mm:ss.zzzZ"))); output.insert(QStringLiteral("status"), QStringLiteral("completed")); } else { output.insert(QStringLiteral("status"), QStringLiteral("needsAction")); } QJsonDocument document = QJsonDocument::fromVariant(output); return document.toJson(QJsonDocument::Compact); } ObjectsList Private::parseTaskListJSONFeed(const QVariantList &items) { ObjectsList list; list.reserve(items.size()); for (const QVariant &item : items) { list.append(Private::JSONToTaskList(item.toMap())); } return list; } ObjectsList Private::parseTasksJSONFeed(const QVariantList &items) { ObjectsList list; list.reserve(items.size()); for (const QVariant &item : items) { list.append(Private::JSONToTask(item.toMap())); } return list; } } // namespace TasksService } // namespace KGAPI2