File indexing completed on 2024-05-12 05:22:33
0001 /* 0002 SPDX-FileCopyrightText: 2012-2018 Daniel Vrátil <dvratil@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0005 */ 0006 0007 #include "tasksservice.h" 0008 #include "object.h" 0009 #include "task.h" 0010 #include "tasklist.h" 0011 #include "utils.h" 0012 0013 #include <QJsonDocument> 0014 #include <QUrlQuery> 0015 #include <QVariant> 0016 0017 namespace KGAPI2 0018 { 0019 0020 namespace TasksService 0021 { 0022 0023 namespace 0024 { 0025 /* Google accepts only UTC time strictly in this format :( */ 0026 static const auto DatetimeFormat = QStringLiteral("yyyy-MM-ddThh:mm:ss.zzzZ"); 0027 0028 static const auto TasksUrlPart = QLatin1StringView("/tasks"); 0029 0030 static const auto PageTokenParam = QStringLiteral("pageToken"); 0031 static const auto MaxResultsParam = QStringLiteral("maxResults"); 0032 static const auto MaxResultsParamValueDefault = QStringLiteral("20"); 0033 0034 static const auto KindAttr = QStringLiteral("kind"); 0035 static const auto IdAttr = QStringLiteral("id"); 0036 static const auto EtagAttr = QStringLiteral("etag"); 0037 static const auto TitleAttr = QStringLiteral("title"); 0038 static const auto NotesAttr = QStringLiteral("notes"); 0039 static const auto ItemsAttr = QStringLiteral("items"); 0040 static const auto NextPageTokenAttr = QStringLiteral("nextPageToken"); 0041 static const auto ParentAttr = QStringLiteral("parent"); 0042 static const auto SelfLinkAttr = QStringLiteral("selfLink"); 0043 static const auto UpdatedAttr = QStringLiteral("updated"); 0044 static const auto StatusAttr = QStringLiteral("status"); 0045 static const auto DueAttr = QStringLiteral("due"); 0046 static const auto DeletedAttr = QStringLiteral("deleted"); 0047 0048 static const auto CompletedAttrVal = QLatin1StringView("completed"); 0049 static const auto NeedsActionAttrVal = QLatin1StringView("needsAction"); 0050 } 0051 0052 namespace Private 0053 { 0054 ObjectsList parseTaskListJSONFeed(const QVariantList &items); 0055 ObjectsList parseTasksJSONFeed(const QVariantList &items); 0056 0057 ObjectPtr JSONToTaskList(const QVariantMap &jsonData); 0058 ObjectPtr JSONToTask(const QVariantMap &jsonData); 0059 0060 static const QUrl GoogleApisUrl(QStringLiteral("https://www.googleapis.com")); 0061 static const QString TasksBasePath(QStringLiteral("/tasks/v1/lists")); 0062 static const QString TasksListsBasePath(QStringLiteral("/tasks/v1/users/@me/lists")); 0063 } 0064 0065 ObjectsList parseJSONFeed(const QByteArray &jsonFeed, FeedData &feedData) 0066 { 0067 QJsonDocument document = QJsonDocument::fromJson(jsonFeed); 0068 if (document.isNull()) { 0069 return ObjectsList(); 0070 } 0071 0072 ObjectsList list; 0073 const QVariantMap feed = document.toVariant().toMap(); 0074 0075 if (feed.value(KindAttr).toString() == QLatin1StringView("tasks#taskLists")) { 0076 list = Private::parseTaskListJSONFeed(feed.value(ItemsAttr).toList()); 0077 0078 if (feed.contains(NextPageTokenAttr)) { 0079 feedData.nextPageUrl = fetchTaskListsUrl(); 0080 QUrlQuery query(feedData.nextPageUrl); 0081 query.addQueryItem(PageTokenParam, feed.value(NextPageTokenAttr).toString()); 0082 if (query.queryItemValue(MaxResultsParam).isEmpty()) { 0083 query.addQueryItem(MaxResultsParam, MaxResultsParamValueDefault); 0084 } 0085 feedData.nextPageUrl.setQuery(query); 0086 } 0087 0088 } else if (feed.value(KindAttr).toString() == QLatin1StringView("tasks#tasks")) { 0089 list = Private::parseTasksJSONFeed(feed.value(ItemsAttr).toList()); 0090 0091 if (feed.contains(NextPageTokenAttr)) { 0092 QString taskListId = feedData.requestUrl.toString().remove(QStringLiteral("https://www.googleapis.com/tasks/v1/lists/")); 0093 taskListId = taskListId.left(taskListId.indexOf(QLatin1Char('/'))); 0094 0095 feedData.nextPageUrl = fetchAllTasksUrl(taskListId); 0096 QUrlQuery query(feedData.nextPageUrl); 0097 query.addQueryItem(PageTokenParam, feed.value(NextPageTokenAttr).toString()); 0098 if (query.queryItemValue(MaxResultsParam).isEmpty()) { 0099 query.addQueryItem(MaxResultsParam, MaxResultsParamValueDefault); 0100 } 0101 feedData.nextPageUrl.setQuery(query); 0102 } 0103 } 0104 0105 return list; 0106 } 0107 0108 QUrl fetchAllTasksUrl(const QString &tasklistID) 0109 { 0110 QUrl url(Private::GoogleApisUrl); 0111 url.setPath(Private::TasksBasePath % QLatin1Char('/') % tasklistID % TasksUrlPart); 0112 return url; 0113 } 0114 0115 QUrl fetchTaskUrl(const QString &tasklistID, const QString &taskID) 0116 { 0117 QUrl url(Private::GoogleApisUrl); 0118 url.setPath(Private::TasksBasePath % QLatin1Char('/') % tasklistID % TasksUrlPart % QLatin1Char('/') % taskID); 0119 return url; 0120 } 0121 0122 QUrl createTaskUrl(const QString &tasklistID) 0123 { 0124 QUrl url(Private::GoogleApisUrl); 0125 url.setPath(Private::TasksBasePath % QLatin1Char('/') % tasklistID % TasksUrlPart); 0126 return url; 0127 } 0128 0129 QUrl updateTaskUrl(const QString &tasklistID, const QString &taskID) 0130 { 0131 QUrl url(Private::GoogleApisUrl); 0132 url.setPath(Private::TasksBasePath % QLatin1Char('/') % tasklistID % TasksUrlPart % QLatin1Char('/') % taskID); 0133 return url; 0134 } 0135 0136 QUrl removeTaskUrl(const QString &tasklistID, const QString &taskID) 0137 { 0138 QUrl url(Private::GoogleApisUrl); 0139 url.setPath(Private::TasksBasePath % QLatin1Char('/') % tasklistID % TasksUrlPart % QLatin1Char('/') % taskID); 0140 return url; 0141 } 0142 0143 QUrl moveTaskUrl(const QString &tasklistID, const QString &taskID, const QString &newParent) 0144 { 0145 QUrl url(Private::GoogleApisUrl); 0146 url.setPath(Private::TasksBasePath % QLatin1Char('/') % tasklistID % TasksUrlPart % QLatin1Char('/') % taskID % QLatin1StringView("/move")); 0147 if (!newParent.isEmpty()) { 0148 QUrlQuery query(url); 0149 query.addQueryItem(ParentAttr, newParent); 0150 url.setQuery(query); 0151 } 0152 0153 return url; 0154 } 0155 0156 QUrl fetchTaskListsUrl() 0157 { 0158 QUrl url(Private::GoogleApisUrl); 0159 url.setPath(Private::TasksListsBasePath); 0160 return url; 0161 } 0162 0163 QUrl createTaskListUrl() 0164 { 0165 QUrl url(Private::GoogleApisUrl); 0166 url.setPath(Private::TasksListsBasePath); 0167 return url; 0168 } 0169 0170 QUrl updateTaskListUrl(const QString &tasklistID) 0171 { 0172 QUrl url(Private::GoogleApisUrl); 0173 url.setPath(Private::TasksListsBasePath % QLatin1Char('/') % tasklistID); 0174 return url; 0175 } 0176 0177 QUrl removeTaskListUrl(const QString &tasklistID) 0178 { 0179 QUrl url(Private::GoogleApisUrl); 0180 url.setPath(Private::TasksListsBasePath % QLatin1Char('/') % tasklistID); 0181 return url; 0182 } 0183 0184 /******************************* PRIVATE ******************************/ 0185 0186 ObjectPtr Private::JSONToTaskList(const QVariantMap &jsonData) 0187 { 0188 TaskListPtr taskList(new TaskList()); 0189 0190 taskList->setUid(jsonData.value(IdAttr).toString()); 0191 taskList->setEtag(jsonData.value(EtagAttr).toString()); 0192 taskList->setTitle(jsonData.value(TitleAttr).toString()); 0193 taskList->setSelfLink(jsonData.value(SelfLinkAttr).toString()); 0194 taskList->setUpdated(jsonData.value(UpdatedAttr).toString()); 0195 0196 return taskList.dynamicCast<Object>(); 0197 } 0198 0199 TaskListPtr JSONToTaskList(const QByteArray &jsonData) 0200 { 0201 QJsonDocument document = QJsonDocument::fromJson(jsonData); 0202 const QVariantMap data = document.toVariant().toMap(); 0203 0204 if (data.value(KindAttr).toString() == QLatin1StringView("tasks#taskList")) { 0205 return Private::JSONToTaskList(data).staticCast<TaskList>(); 0206 } 0207 0208 return TaskListPtr(); 0209 } 0210 0211 ObjectPtr Private::JSONToTask(const QVariantMap &jsonData) 0212 { 0213 TaskPtr task(new Task()); 0214 0215 task->setUid(jsonData.value(IdAttr).toString()); 0216 task->setEtag(jsonData.value(EtagAttr).toString()); 0217 task->setSummary(jsonData.value(TitleAttr).toString()); 0218 task->setLastModified(Utils::rfc3339DateFromString(jsonData.value(UpdatedAttr).toString())); 0219 task->setDescription(jsonData.value(NotesAttr).toString()); 0220 0221 if (jsonData.value(StatusAttr).toString() == NeedsActionAttrVal) { 0222 task->setStatus(KCalendarCore::Incidence::StatusNeedsAction); 0223 } else if (jsonData.value(StatusAttr).toString() == CompletedAttrVal) { 0224 task->setStatus(KCalendarCore::Incidence::StatusCompleted); 0225 } else { 0226 task->setStatus(KCalendarCore::Incidence::StatusNone); 0227 } 0228 0229 // "due" is date-only -- no time-of-day given. 0230 task->setAllDay(true); 0231 task->setDtDue(Utils::rfc3339DateFromString(jsonData.value(DueAttr).toString())); 0232 0233 if (task->status() == KCalendarCore::Incidence::StatusCompleted) { 0234 task->setCompleted(Utils::rfc3339DateFromString(jsonData.value(CompletedAttrVal).toString())); 0235 } 0236 0237 task->setDeleted(jsonData.value(DeletedAttr).toBool()); 0238 0239 if (jsonData.contains(ParentAttr)) { 0240 task->setRelatedTo(jsonData.value(ParentAttr).toString(), KCalendarCore::Incidence::RelTypeParent); 0241 } 0242 0243 return task.dynamicCast<Object>(); 0244 } 0245 0246 TaskPtr JSONToTask(const QByteArray &jsonData) 0247 { 0248 QJsonDocument document = QJsonDocument::fromJson(jsonData); 0249 const QVariantMap data = document.toVariant().toMap(); 0250 0251 if (data.value(KindAttr).toString() == QLatin1StringView("tasks#task")) { 0252 return Private::JSONToTask(data).staticCast<Task>(); 0253 } 0254 0255 return TaskPtr(); 0256 } 0257 0258 QByteArray taskListToJSON(const TaskListPtr &taskList) 0259 { 0260 QVariantMap output; 0261 0262 output.insert(KindAttr, QStringLiteral("tasks#taskList")); 0263 if (!taskList->uid().isEmpty()) { 0264 output.insert(IdAttr, taskList->uid()); 0265 } 0266 output.insert(TitleAttr, taskList->title()); 0267 0268 QJsonDocument document = QJsonDocument::fromVariant(output); 0269 return document.toJson(QJsonDocument::Compact); 0270 } 0271 0272 QByteArray taskToJSON(const TaskPtr &task) 0273 { 0274 QVariantMap output; 0275 0276 output.insert(KindAttr, QStringLiteral("tasks#task")); 0277 0278 if (!task->uid().isEmpty()) { 0279 output.insert(IdAttr, task->uid()); 0280 } 0281 0282 output.insert(TitleAttr, task->summary()); 0283 output.insert(NotesAttr, task->description()); 0284 0285 if (!task->relatedTo(KCalendarCore::Incidence::RelTypeParent).isEmpty()) { 0286 output.insert(ParentAttr, task->relatedTo(KCalendarCore::Incidence::RelTypeParent)); 0287 } 0288 0289 if (task->dtDue().isValid()) { 0290 output.insert(DueAttr, task->dtDue().toUTC().toString(DatetimeFormat)); 0291 } 0292 0293 if ((task->status() == KCalendarCore::Incidence::StatusCompleted) && task->completed().isValid()) { 0294 output.insert(CompletedAttrVal, task->completed().toUTC().toString(DatetimeFormat)); 0295 output.insert(StatusAttr, CompletedAttrVal); 0296 } else { 0297 output.insert(StatusAttr, NeedsActionAttrVal); 0298 } 0299 0300 QJsonDocument document = QJsonDocument::fromVariant(output); 0301 return document.toJson(QJsonDocument::Compact); 0302 } 0303 0304 ObjectsList Private::parseTaskListJSONFeed(const QVariantList &items) 0305 { 0306 ObjectsList list; 0307 list.reserve(items.size()); 0308 for (const QVariant &item : items) { 0309 list.append(Private::JSONToTaskList(item.toMap())); 0310 } 0311 0312 return list; 0313 } 0314 0315 ObjectsList Private::parseTasksJSONFeed(const QVariantList &items) 0316 { 0317 ObjectsList list; 0318 list.reserve(items.size()); 0319 for (const QVariant &item : items) { 0320 list.append(Private::JSONToTask(item.toMap())); 0321 } 0322 0323 return list; 0324 } 0325 0326 } // namespace TasksService 0327 0328 } // namespace KGAPI2