Kgapi

tasksservice.cpp
1/*
2 SPDX-FileCopyrightText: 2012-2018 Daniel Vrátil <dvratil@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5*/
6
7#include "tasksservice.h"
8#include "object.h"
9#include "task.h"
10#include "tasklist.h"
11#include "utils.h"
12
13#include <QJsonDocument>
14#include <QUrlQuery>
15#include <QVariant>
16
17namespace KGAPI2
18{
19
20namespace TasksService
21{
22
23namespace
24{
25/* Google accepts only UTC time strictly in this format :( */
26static const auto DatetimeFormat = QStringLiteral("yyyy-MM-ddThh:mm:ss.zzzZ");
27
28static const auto TasksUrlPart = QLatin1StringView("/tasks");
29
30static const auto PageTokenParam = QStringLiteral("pageToken");
31static const auto MaxResultsParam = QStringLiteral("maxResults");
32static const auto MaxResultsParamValueDefault = QStringLiteral("20");
33
34static const auto KindAttr = QStringLiteral("kind");
35static const auto IdAttr = QStringLiteral("id");
36static const auto EtagAttr = QStringLiteral("etag");
37static const auto TitleAttr = QStringLiteral("title");
38static const auto NotesAttr = QStringLiteral("notes");
39static const auto ItemsAttr = QStringLiteral("items");
40static const auto NextPageTokenAttr = QStringLiteral("nextPageToken");
41static const auto ParentAttr = QStringLiteral("parent");
42static const auto SelfLinkAttr = QStringLiteral("selfLink");
43static const auto UpdatedAttr = QStringLiteral("updated");
44static const auto StatusAttr = QStringLiteral("status");
45static const auto DueAttr = QStringLiteral("due");
46static const auto DeletedAttr = QStringLiteral("deleted");
47
48static const auto CompletedAttrVal = QLatin1StringView("completed");
49static const auto NeedsActionAttrVal = QLatin1StringView("needsAction");
50}
51
52namespace Private
53{
54ObjectsList parseTaskListJSONFeed(const QVariantList &items);
55ObjectsList parseTasksJSONFeed(const QVariantList &items);
56
57ObjectPtr JSONToTaskList(const QVariantMap &jsonData);
58ObjectPtr JSONToTask(const QVariantMap &jsonData);
59
60static const QUrl GoogleApisUrl(QStringLiteral("https://www.googleapis.com"));
61static const QString TasksBasePath(QStringLiteral("/tasks/v1/lists"));
62static const QString TasksListsBasePath(QStringLiteral("/tasks/v1/users/@me/lists"));
63}
64
65ObjectsList parseJSONFeed(const QByteArray &jsonFeed, FeedData &feedData)
66{
67 QJsonDocument document = QJsonDocument::fromJson(jsonFeed);
68 if (document.isNull()) {
69 return ObjectsList();
70 }
71
72 ObjectsList list;
73 const QVariantMap feed = document.toVariant().toMap();
74
75 if (feed.value(KindAttr).toString() == QLatin1StringView("tasks#taskLists")) {
76 list = Private::parseTaskListJSONFeed(feed.value(ItemsAttr).toList());
77
78 if (feed.contains(NextPageTokenAttr)) {
79 feedData.nextPageUrl = fetchTaskListsUrl();
80 QUrlQuery query(feedData.nextPageUrl);
81 query.addQueryItem(PageTokenParam, feed.value(NextPageTokenAttr).toString());
82 if (query.queryItemValue(MaxResultsParam).isEmpty()) {
83 query.addQueryItem(MaxResultsParam, MaxResultsParamValueDefault);
84 }
85 feedData.nextPageUrl.setQuery(query);
86 }
87
88 } else if (feed.value(KindAttr).toString() == QLatin1StringView("tasks#tasks")) {
89 list = Private::parseTasksJSONFeed(feed.value(ItemsAttr).toList());
90
91 if (feed.contains(NextPageTokenAttr)) {
92 QString taskListId = feedData.requestUrl.toString().remove(QStringLiteral("https://www.googleapis.com/tasks/v1/lists/"));
93 taskListId = taskListId.left(taskListId.indexOf(QLatin1Char('/')));
94
95 feedData.nextPageUrl = fetchAllTasksUrl(taskListId);
96 QUrlQuery query(feedData.nextPageUrl);
97 query.addQueryItem(PageTokenParam, feed.value(NextPageTokenAttr).toString());
98 if (query.queryItemValue(MaxResultsParam).isEmpty()) {
99 query.addQueryItem(MaxResultsParam, MaxResultsParamValueDefault);
100 }
101 feedData.nextPageUrl.setQuery(query);
102 }
103 }
104
105 return list;
106}
107
108QUrl fetchAllTasksUrl(const QString &tasklistID)
109{
110 QUrl url(Private::GoogleApisUrl);
111 url.setPath(Private::TasksBasePath % QLatin1Char('/') % tasklistID % TasksUrlPart);
112 return url;
113}
114
115QUrl fetchTaskUrl(const QString &tasklistID, const QString &taskID)
116{
117 QUrl url(Private::GoogleApisUrl);
118 url.setPath(Private::TasksBasePath % QLatin1Char('/') % tasklistID % TasksUrlPart % QLatin1Char('/') % taskID);
119 return url;
120}
121
122QUrl createTaskUrl(const QString &tasklistID)
123{
124 QUrl url(Private::GoogleApisUrl);
125 url.setPath(Private::TasksBasePath % QLatin1Char('/') % tasklistID % TasksUrlPart);
126 return url;
127}
128
129QUrl updateTaskUrl(const QString &tasklistID, const QString &taskID)
130{
131 QUrl url(Private::GoogleApisUrl);
132 url.setPath(Private::TasksBasePath % QLatin1Char('/') % tasklistID % TasksUrlPart % QLatin1Char('/') % taskID);
133 return url;
134}
135
136QUrl removeTaskUrl(const QString &tasklistID, const QString &taskID)
137{
138 QUrl url(Private::GoogleApisUrl);
139 url.setPath(Private::TasksBasePath % QLatin1Char('/') % tasklistID % TasksUrlPart % QLatin1Char('/') % taskID);
140 return url;
141}
142
143QUrl moveTaskUrl(const QString &tasklistID, const QString &taskID, const QString &newParent)
144{
145 QUrl url(Private::GoogleApisUrl);
146 url.setPath(Private::TasksBasePath % QLatin1Char('/') % tasklistID % TasksUrlPart % QLatin1Char('/') % taskID % QLatin1StringView("/move"));
147 if (!newParent.isEmpty()) {
148 QUrlQuery query(url);
149 query.addQueryItem(ParentAttr, newParent);
150 url.setQuery(query);
151 }
152
153 return url;
154}
155
157{
158 QUrl url(Private::GoogleApisUrl);
159 url.setPath(Private::TasksListsBasePath);
160 return url;
161}
162
164{
165 QUrl url(Private::GoogleApisUrl);
166 url.setPath(Private::TasksListsBasePath);
167 return url;
168}
169
171{
172 QUrl url(Private::GoogleApisUrl);
173 url.setPath(Private::TasksListsBasePath % QLatin1Char('/') % tasklistID);
174 return url;
175}
176
178{
179 QUrl url(Private::GoogleApisUrl);
180 url.setPath(Private::TasksListsBasePath % QLatin1Char('/') % tasklistID);
181 return url;
182}
183
184/******************************* PRIVATE ******************************/
185
186ObjectPtr Private::JSONToTaskList(const QVariantMap &jsonData)
187{
188 TaskListPtr taskList(new TaskList());
189
190 taskList->setUid(jsonData.value(IdAttr).toString());
191 taskList->setEtag(jsonData.value(EtagAttr).toString());
192 taskList->setTitle(jsonData.value(TitleAttr).toString());
193 taskList->setSelfLink(jsonData.value(SelfLinkAttr).toString());
194 taskList->setUpdated(jsonData.value(UpdatedAttr).toString());
195
196 return taskList.dynamicCast<Object>();
197}
198
200{
201 QJsonDocument document = QJsonDocument::fromJson(jsonData);
202 const QVariantMap data = document.toVariant().toMap();
203
204 if (data.value(KindAttr).toString() == QLatin1StringView("tasks#taskList")) {
205 return Private::JSONToTaskList(data).staticCast<TaskList>();
206 }
207
208 return TaskListPtr();
209}
210
211ObjectPtr Private::JSONToTask(const QVariantMap &jsonData)
212{
213 TaskPtr task(new Task());
214
215 task->setUid(jsonData.value(IdAttr).toString());
216 task->setEtag(jsonData.value(EtagAttr).toString());
217 task->setSummary(jsonData.value(TitleAttr).toString());
218 task->setLastModified(Utils::rfc3339DateFromString(jsonData.value(UpdatedAttr).toString()));
219 task->setDescription(jsonData.value(NotesAttr).toString());
220
221 if (jsonData.value(StatusAttr).toString() == NeedsActionAttrVal) {
223 } else if (jsonData.value(StatusAttr).toString() == CompletedAttrVal) {
225 } else {
227 }
228
229 // "due" is date-only -- no time-of-day given.
230 task->setAllDay(true);
231 task->setDtDue(Utils::rfc3339DateFromString(jsonData.value(DueAttr).toString()));
232
234 task->setCompleted(Utils::rfc3339DateFromString(jsonData.value(CompletedAttrVal).toString()));
235 }
236
237 task->setDeleted(jsonData.value(DeletedAttr).toBool());
238
239 if (jsonData.contains(ParentAttr)) {
240 task->setRelatedTo(jsonData.value(ParentAttr).toString(), KCalendarCore::Incidence::RelTypeParent);
241 }
242
243 return task.dynamicCast<Object>();
244}
245
247{
248 QJsonDocument document = QJsonDocument::fromJson(jsonData);
249 const QVariantMap data = document.toVariant().toMap();
250
251 if (data.value(KindAttr).toString() == QLatin1StringView("tasks#task")) {
252 return Private::JSONToTask(data).staticCast<Task>();
253 }
254
255 return TaskPtr();
256}
257
259{
260 QVariantMap output;
261
262 output.insert(KindAttr, QStringLiteral("tasks#taskList"));
263 if (!taskList->uid().isEmpty()) {
264 output.insert(IdAttr, taskList->uid());
265 }
266 output.insert(TitleAttr, taskList->title());
267
268 QJsonDocument document = QJsonDocument::fromVariant(output);
269 return document.toJson(QJsonDocument::Compact);
270}
271
273{
274 QVariantMap output;
275
276 output.insert(KindAttr, QStringLiteral("tasks#task"));
277
278 if (!task->uid().isEmpty()) {
279 output.insert(IdAttr, task->uid());
280 }
281
282 output.insert(TitleAttr, task->summary());
283 output.insert(NotesAttr, task->description());
284
285 if (!task->relatedTo(KCalendarCore::Incidence::RelTypeParent).isEmpty()) {
286 output.insert(ParentAttr, task->relatedTo(KCalendarCore::Incidence::RelTypeParent));
287 }
288
289 if (task->dtDue().isValid()) {
290 output.insert(DueAttr, task->dtDue().toUTC().toString(DatetimeFormat));
291 }
292
293 if ((task->status() == KCalendarCore::Incidence::StatusCompleted) && task->completed().isValid()) {
294 output.insert(CompletedAttrVal, task->completed().toUTC().toString(DatetimeFormat));
295 output.insert(StatusAttr, CompletedAttrVal);
296 } else {
297 output.insert(StatusAttr, NeedsActionAttrVal);
298 }
299
300 QJsonDocument document = QJsonDocument::fromVariant(output);
301 return document.toJson(QJsonDocument::Compact);
302}
303
304ObjectsList Private::parseTaskListJSONFeed(const QVariantList &items)
305{
306 ObjectsList list;
307 list.reserve(items.size());
308 for (const QVariant &item : items) {
309 list.append(Private::JSONToTaskList(item.toMap()));
310 }
311
312 return list;
313}
314
315ObjectsList Private::parseTasksJSONFeed(const QVariantList &items)
316{
317 ObjectsList list;
318 list.reserve(items.size());
319 for (const QVariant &item : items) {
320 list.append(Private::JSONToTask(item.toMap()));
321 }
322
323 return list;
324}
325
326} // namespace TasksService
327
328} // namespace KGAPI2
Structure to store additional information about a feed.
Definition types.h:24
QUrl requestUrl
Original URL of the request.
Definition types.h:39
QUrl nextPageUrl
Link to next page of feed.
Definition types.h:38
Base class for all objects.
Definition object.h:31
Represents a tasklist for Google Tasks service.
Definition tasklist.h:27
Represents a single task (or todo)
Definition task.h:29
QUrl updateTaskListUrl(const QString &tasklistID)
Returns URL for modifying a tasklist.
QByteArray taskListToJSON(const TaskListPtr &taskList)
Serializes a TaskList into JSON data.
QByteArray taskToJSON(const TaskPtr &task)
Serializes a Task object into JSON.
QUrl createTaskUrl(const QString &tasklistID)
Returns URL to create a single task.
ObjectsList parseJSONFeed(const QByteArray &jsonFeed, FeedData &feedData)
Parses JSON feed into list of Tasks or TaskLists.
QUrl fetchAllTasksUrl(const QString &tasklistID)
Returns URL to fetch all tasks from a single tasklist.
QUrl removeTaskListUrl(const QString &tasklistID)
Returns URL for deleting a tasklist.
QUrl fetchTaskUrl(const QString &tasklistID, const QString &taskID)
Returns URL for fetching a single task.
TaskPtr JSONToTask(const QByteArray &jsonData)
Parses JSON data into a Task object.
TaskListPtr JSONToTaskList(const QByteArray &jsonData)
Parses JSON data into a TaskList object.
QUrl removeTaskUrl(const QString &tasklistID, const QString &taskID)
Returns URL for removing a task.
QUrl createTaskListUrl()
Returns URL for creating a new tasklist.
QUrl fetchTaskListsUrl()
Returns URL for fetching all tasklists.
QUrl moveTaskUrl(const QString &tasklistID, const QString &taskID, const QString &newParent)
Returns URL to reparent task.
QUrl updateTaskUrl(const QString &tasklistID, const QString &taskID)
Returns URL for updating a task.
A job to fetch a single map tile described by a StaticMapUrl.
Definition blog.h:16
KIOCORE_EXPORT QStringList list(const QString &fileClass)
QByteArray & insert(qsizetype i, QByteArrayView data)
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QJsonDocument fromVariant(const QVariant &variant)
bool isNull() const const
QByteArray toJson(JsonFormat format) const const
QVariant toVariant() const const
void append(QList< T > &&value)
bool isEmpty() const const
void reserve(qsizetype size)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString left(qsizetype n) const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QTaskBuilder< Task > task(Task &&task)
void setPath(const QString &path, ParsingMode mode)
void setQuery(const QString &query, ParsingMode mode)
QString toString(FormattingOptions options) const const
QMap< QString, QVariant > toMap() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:19:52 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.