File indexing completed on 2024-04-21 04:51:52
0001 /* 0002 SPDX-FileCopyrightText: 2021 Julius Künzel <jk.kdedev@smartlab.uber.space> 0003 SPDX-FileCopyrightText: 2011 Jean-Baptiste Mardelle <jb@kdenlive.org> 0004 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "providermodel.hpp" 0008 #include "kdenlive_debug.h" 0009 #include "kdenlivesettings.h" 0010 0011 #include <KLocalizedString> 0012 #include <KMessageBox> 0013 #include <QDateTime> 0014 #include <QDesktopServices> 0015 #include <QDir> 0016 #include <QFile> 0017 #include <QJsonArray> 0018 #include <QJsonValue> 0019 #include <QNetworkAccessManager> 0020 #include <QNetworkReply> 0021 #include <QNetworkRequest> 0022 #include <QUrlQuery> 0023 #include <kio/storedtransferjob.h> 0024 0025 ProviderModel::ProviderModel(const QString &path) 0026 : m_path(path) 0027 , m_invalid(false) 0028 { 0029 0030 QFile file(path); 0031 QJsonParseError jsonError; 0032 0033 m_networkManager = new QNetworkAccessManager(this); 0034 0035 if (!file.exists()) { 0036 qCWarning(KDENLIVE_LOG) << "WARNING, can not find provider configuration file at" << path << "."; 0037 m_invalid = true; 0038 } else { 0039 file.open(QFile::ReadOnly); 0040 m_doc = QJsonDocument::fromJson(file.readAll(), &jsonError); 0041 if (jsonError.error != QJsonParseError::NoError) { 0042 m_invalid = true; 0043 // There was an error parsing data 0044 KMessageBox::error(nullptr, jsonError.errorString(), i18nc("@title:window", "Error Loading Data")); 0045 return; 0046 } 0047 validate(); 0048 } 0049 0050 if (!m_invalid) { 0051 m_apiroot = m_doc["api"].toObject()["root"].toString(); 0052 m_search = m_doc["api"].toObject()["search"].toObject(); 0053 m_download = m_doc["api"].toObject()["downloadUrls"].toObject(); 0054 m_name = m_doc["name"].toString(); 0055 m_clientkey = m_doc["clientkey"].toString(); 0056 m_attribution = m_doc["attributionHtml"].toString(); 0057 m_homepage = m_doc["homepage"].toString(); 0058 0059 #ifndef DOXYGEN_SHOULD_SKIP_THIS // don't make this any more public than it is. 0060 if (!m_clientkey.isEmpty()) { 0061 // all these keys are registered with online-resources@kdenlive.org 0062 m_clientkey.replace("%freesound_apikey%", "aJuPDxHP7vQlmaPSmvqyca6YwNdP0tPaUnvmtjIn"); 0063 m_clientkey.replace("%pexels_apikey%", "563492ad6f91700001000001c2c34d4986e5421eb353e370ae5a89d0"); 0064 m_clientkey.replace("%pixabay_apikey%", "20228828-57acfa09b69e06ae394d206af"); 0065 } 0066 #endif 0067 0068 if (m_doc["type"].toString() == "music") { 0069 m_type = SERVICETYPE::AUDIO; 0070 } else if (m_doc["type"].toString() == "sound") { 0071 m_type = SERVICETYPE::AUDIO; 0072 } else if (m_doc["type"].toString() == "video") { 0073 m_type = SERVICETYPE::VIDEO; 0074 } else if (m_doc["type"].toString() == "image") { 0075 m_type = SERVICETYPE::IMAGE; 0076 } else { 0077 m_type = SERVICETYPE::UNKNOWN; 0078 } 0079 0080 if (downloadOAuth2() == true) { 0081 QJsonObject ouath2Info = m_doc["api"].toObject()["oauth2"].toObject(); 0082 auto replyHandler = new QOAuthHttpServerReplyHandler(1337, this); 0083 m_oauth2.setReplyHandler(replyHandler); 0084 m_oauth2.setAuthorizationUrl(QUrl(ouath2Info["authorizationUrl"].toString())); 0085 m_oauth2.setAccessTokenUrl(QUrl(ouath2Info["accessTokenUrl"].toString())); 0086 m_oauth2.setClientIdentifier(ouath2Info["clientId"].toString()); 0087 m_oauth2.setClientIdentifierSharedKey(m_clientkey); 0088 connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::refreshTokenChanged, this, [&](const QString &refreshToken){ 0089 KSharedConfigPtr config = KSharedConfig::openConfig(); 0090 KConfigGroup authGroup(config, "OAuth2Authentication" + m_name); 0091 authGroup.writeEntry(QStringLiteral("refresh_token"), refreshToken); 0092 }); 0093 0094 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0095 m_oauth2.setModifyParametersFunction([&](QAbstractOAuth::Stage stage, QVariantMap *parameters) { 0096 #else 0097 m_oauth2.setModifyParametersFunction([&](QAbstractOAuth::Stage stage, QMultiMap<QString, QVariant> *parameters) { 0098 #endif 0099 if (stage == QAbstractOAuth::Stage::RequestingAuthorization) { 0100 if (m_oauth2.scope().isEmpty()) { 0101 parameters->remove("scope"); 0102 } 0103 } 0104 if (stage == QAbstractOAuth::Stage::RefreshingAccessToken) { 0105 parameters->insert("client_id", m_oauth2.clientIdentifier()); 0106 parameters->insert("client_secret", m_oauth2.clientIdentifierSharedKey()); 0107 } 0108 }); 0109 0110 connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::statusChanged, this, [=](QAbstractOAuth::Status status) { 0111 if (status == QAbstractOAuth::Status::Granted) { 0112 Q_EMIT authenticated(m_oauth2.token()); 0113 } else if (status == QAbstractOAuth::Status::NotAuthenticated) { 0114 KMessageBox::error(nullptr, "DEBUG: NotAuthenticated"); 0115 } 0116 }); 0117 connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::error, this, [=](const QString &error, const QString &errorDescription) { 0118 qCWarning(KDENLIVE_LOG) << "Error in authorization flow. " << error << " " << errorDescription; 0119 Q_EMIT authenticated(QString()); 0120 }); 0121 connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, &QDesktopServices::openUrl); 0122 } 0123 } else { 0124 qCWarning(KDENLIVE_LOG) << "The provider config file at " << path << " is invalid. "; 0125 } 0126 } 0127 0128 void ProviderModel::authorize() 0129 { 0130 KSharedConfigPtr config = KSharedConfig::openConfig(); 0131 KConfigGroup authGroup(config, "OAuth2Authentication" + m_name); 0132 0133 QString strRefreshTokenFromSettings = authGroup.readEntry(QStringLiteral("refresh_token")); 0134 0135 if (m_oauth2.token().isEmpty()) { 0136 if (!strRefreshTokenFromSettings.isEmpty()) { 0137 m_oauth2.setRefreshToken(strRefreshTokenFromSettings); 0138 m_oauth2.refreshAccessToken(); 0139 } else { 0140 m_oauth2.grant(); 0141 } 0142 } else { 0143 if (m_oauth2.expirationAt() < QDateTime::currentDateTime()) { 0144 Q_EMIT authenticated(m_oauth2.token()); 0145 } else { 0146 m_oauth2.refreshAccessToken(); 0147 } 0148 } 0149 } 0150 0151 void ProviderModel::refreshAccessToken() 0152 { 0153 m_oauth2.refreshAccessToken(); 0154 } 0155 0156 /** 0157 * @brief ProviderModel::validate 0158 * Check if config has all required fields. Result can be gotten with is_valid() 0159 */ 0160 void ProviderModel::validate() 0161 { 0162 0163 m_invalid = true; 0164 if (m_doc.isNull() || m_doc.isEmpty() || !m_doc.isObject()) { 0165 qCWarning(KDENLIVE_LOG) << "Root object missing or invalid"; 0166 return; 0167 } 0168 0169 if (!m_doc["integration"].isString() || m_doc["integration"].toString() != "buildin") { 0170 qCWarning(KDENLIVE_LOG) << "Currently only integration type \"buildin\" is supported"; 0171 return; 0172 } 0173 0174 if (!m_doc["name"].isString()) { 0175 qCWarning(KDENLIVE_LOG) << "Missing key name of type string "; 0176 return; 0177 } 0178 0179 if (!m_doc["homepage"].isString()) { 0180 qCWarning(KDENLIVE_LOG) << "Missing key homepage of type string "; 0181 return; 0182 } 0183 0184 if (!m_doc["type"].isString()) { 0185 qCWarning(KDENLIVE_LOG) << "Missing key type of type string "; 0186 return; 0187 } 0188 0189 if (!m_doc["api"].isObject() || !m_doc["api"].toObject()["search"].isObject()) { 0190 qCWarning(KDENLIVE_LOG) << "Missing api of type object or key search of type object"; 0191 return; 0192 } 0193 if (downloadOAuth2()) { 0194 if (!m_doc["api"].toObject()["oauth2"].isObject()) { 0195 qCWarning(KDENLIVE_LOG) << "Missing OAuth2 configuration (required)"; 0196 return; 0197 } 0198 if (m_doc["api"].toObject()["oauth2"].toObject()["authorizationUrl"].toString().isEmpty()) { 0199 qCWarning(KDENLIVE_LOG) << "Missing authorizationUrl for OAuth2"; 0200 return; 0201 } 0202 if (m_doc["api"].toObject()["oauth2"].toObject()["accessTokenUrl"].toString().isEmpty()) { 0203 qCWarning(KDENLIVE_LOG) << "Missing accessTokenUrl for OAuth2"; 0204 return; 0205 } 0206 if (m_doc["api"].toObject()["oauth2"].toObject()["clientId"].toString().isEmpty()) { 0207 qCWarning(KDENLIVE_LOG) << "Missing clientId for OAuth2"; 0208 return; 0209 } 0210 } 0211 0212 m_invalid = false; 0213 } 0214 0215 bool ProviderModel::is_valid() const 0216 { 0217 return !m_invalid; 0218 } 0219 0220 QString ProviderModel::name() const 0221 { 0222 return m_name; 0223 } 0224 0225 QString ProviderModel::homepage() const 0226 { 0227 return m_homepage; 0228 } 0229 0230 ProviderModel::SERVICETYPE ProviderModel::type() const 0231 { 0232 return m_type; 0233 } 0234 0235 QString ProviderModel::attribution() const 0236 { 0237 return m_attribution; 0238 } 0239 0240 bool ProviderModel::downloadOAuth2() const 0241 { 0242 return m_doc["downloadOAuth2"].toBool(false); 0243 } 0244 0245 bool ProviderModel::requiresLogin() const 0246 { 0247 if (downloadOAuth2()) { 0248 KSharedConfigPtr config = KSharedConfig::openConfig(); 0249 KConfigGroup authGroup(config, "OAuth2Authentication" + m_name); 0250 authGroup.exists(); 0251 0252 return !authGroup.exists() || authGroup.readEntry(QStringLiteral("refresh_token")).isEmpty(); 0253 } 0254 return false; 0255 } 0256 0257 /** 0258 * @brief ProviderModel::objectGetValue 0259 * @param item Object containing the value 0260 * @param key General key of value to get 0261 * @return value 0262 * Gets a value of item identified by key. The key is translated to the key the provider uses (configured in the providers config file) 0263 * E.g. the provider uses "photographer" as key for the author and another provider uses "user". 0264 * With this function you can simply use "author" as key no matter of the providers specific key. 0265 * In addition this function takes care of modifiers like "$" for placeholders, etc. but does not parse them (use objectGetString for this purpose) 0266 */ 0267 0268 QJsonValue ProviderModel::objectGetValue(QJsonObject item, QString key) 0269 { 0270 QJsonObject tmpKeys = m_search["res"].toObject(); 0271 if (key.contains(".")) { 0272 QStringList subkeys = key.split("."); 0273 0274 for (const auto &subkey : qAsConst(subkeys)) { 0275 if (subkeys.indexOf(subkey) == subkeys.indexOf(subkeys.last())) { 0276 key = subkey; 0277 } else { 0278 tmpKeys = tmpKeys[subkey].toObject(); 0279 } 0280 } 0281 } 0282 0283 QString parseKey = tmpKeys[key].toString(); 0284 // "$" means template, store template string instead of using as key 0285 if (parseKey.startsWith("$")) { 0286 return tmpKeys[key]; 0287 } 0288 0289 // "." in key means value is in a subobject 0290 if (parseKey.contains(".")) { 0291 QStringList subkeys = tmpKeys[key].toString().split("."); 0292 0293 for (const auto &subkey : qAsConst(subkeys)) { 0294 if (subkeys.indexOf(subkey) == subkeys.indexOf(subkeys.last())) { 0295 parseKey = subkey; 0296 } else { 0297 item = item[subkey].toObject(); 0298 } 0299 } 0300 } 0301 0302 // "%" means placeholder, store placeholder instead of using as key 0303 if (parseKey.startsWith("%")) { 0304 return tmpKeys[key]; 0305 } 0306 0307 return item[parseKey]; 0308 } 0309 0310 /** 0311 * @brief ProviderModel::objectGetString 0312 * @param item Object containing the value 0313 * @param key General key of value to get 0314 * @param id The id is used for to replace the palceholder "%id%" (optional) 0315 * @param parentKey Key of the parent (json) object. Used for to replace the palceholder "&" (optional) 0316 * @return result string 0317 * Same as objectGetValue but more specific only for strings. In addition this function parses template strings and palceholders. 0318 */ 0319 0320 QString ProviderModel::objectGetString(QJsonObject item, const QString &key, const QString &id, const QString &parentKey) 0321 { 0322 QJsonValue val = objectGetValue(item, key); 0323 if (!val.isString()) { 0324 return QString(); 0325 } 0326 QString result = val.toString(); 0327 0328 if (result.startsWith("$")) { 0329 result = result.replace("%id%", id); 0330 QStringList sections = result.split("{"); 0331 for (auto §ion : sections) { 0332 section.remove("{"); 0333 section.remove(section.indexOf("}"), section.length()); 0334 0335 // "&" is a placeholder for the parent key 0336 if (section.startsWith("&")) { 0337 result.replace("{" + section + "}", parentKey); 0338 } else { 0339 result.replace("{" + section + "}", item[section].isDouble() ? QString::number(item[section].toDouble()) : item[section].toString()); 0340 } 0341 } 0342 result.remove("$"); 0343 } 0344 0345 return result; 0346 } 0347 0348 QString ProviderModel::replacePlaceholders(QString string, const QString &query, const int page, const QString &id) 0349 { 0350 string = string.replace("%query%", query); 0351 string = string.replace("%pagenum%", QString::number(page)); 0352 string = string.replace("%perpage%", QString::number(m_perPage)); 0353 string = string.replace("%shortlocale%", "en-US"); // TODO 0354 string = string.replace("%clientkey%", m_clientkey); 0355 string = string.replace("%id%", id); 0356 0357 return string; 0358 } 0359 0360 /** 0361 * @brief ProviderModel::getFilesUrl 0362 * @param searchText The search query 0363 * @param page The page to request 0364 * Get the url to search for items 0365 */ 0366 QUrl ProviderModel::getSearchUrl(const QString &searchText, const int page) 0367 { 0368 0369 QUrl url(m_apiroot); 0370 const QJsonObject req = m_search["req"].toObject(); 0371 QUrlQuery query; 0372 url.setPath(url.path().append(req["path"].toString())); 0373 0374 for (const auto param : req["params"].toArray()) { 0375 query.addQueryItem(param.toObject()["key"].toString(), replacePlaceholders(param.toObject()["value"].toString(), searchText, page)); 0376 } 0377 url.setQuery(query); 0378 0379 return url; 0380 } 0381 0382 /** 0383 * @brief ProviderModel::slotFetchFiles 0384 * @param searchText The search query 0385 * @param page The page to request 0386 * Fetch metadata about the available files, if they are not included in the search response (e.g. archive.org) 0387 */ 0388 void ProviderModel::slotStartSearch(const QString &searchText, const int page) 0389 { 0390 QUrl uri = getSearchUrl(searchText, page); 0391 0392 if (m_search["req"].toObject()["method"].toString() == "GET") { 0393 QNetworkRequest request(uri); 0394 0395 if (m_search["req"].toObject()["header"].isArray()) { 0396 for (const auto &header : m_search["req"].toObject()["header"].toArray()) { 0397 request.setRawHeader(header.toObject()["key"].toString().toUtf8(), 0398 replacePlaceholders(header.toObject()["value"].toString(), searchText, page).toUtf8()); 0399 } 0400 } 0401 QNetworkReply *reply = m_networkManager->get(request); 0402 0403 connect(reply, &QNetworkReply::finished, this, [=]() { 0404 if (reply->error() == QNetworkReply::NoError) { 0405 QByteArray response = reply->readAll(); 0406 std::pair<QList<ResourceItemInfo>, const int> result = parseSearchResponse(response); 0407 Q_EMIT searchDone(result.first, result.second); 0408 0409 } else { 0410 Q_EMIT searchError(QStringLiteral("HTTP ") + reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString()); 0411 qCDebug(KDENLIVE_LOG) << reply->errorString(); 0412 } 0413 reply->deleteLater(); 0414 }); 0415 0416 connect(reply, &QNetworkReply::sslErrors, this, [=]() { 0417 Q_EMIT searchError(QStringLiteral("HTTP ") + reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString()); 0418 qCDebug(KDENLIVE_LOG) << reply->errorString(); 0419 }); 0420 0421 } else { 0422 qCDebug(KDENLIVE_LOG) << "Only GET is implemented yet"; 0423 } 0424 } 0425 0426 /** 0427 * @brief ProviderModel::parseFilesResponse 0428 * @param data Response data of the api request 0429 * @return pair of of QList containing ResourceItemInfo and int reflecting the number of found pages 0430 * Parse the response data of a search request, usually after slotStartSearch 0431 */ 0432 std::pair<QList<ResourceItemInfo>, const int> ProviderModel::parseSearchResponse(const QByteArray &data) 0433 { 0434 QJsonObject keys = m_search["res"].toObject(); 0435 QList<ResourceItemInfo> list; 0436 int pageCount = 0; 0437 if (keys["format"].toString() == "json") { 0438 QJsonDocument res = QJsonDocument::fromJson(data); 0439 0440 QJsonArray items; 0441 if (keys["list"].toString("").isEmpty()) { 0442 items = res.array(); 0443 } else { 0444 items = objectGetValue(res.object(), "list").toArray(); 0445 } 0446 0447 pageCount = objectGetValue(res.object(), "resultCount").toInt() / m_perPage; 0448 0449 for (const auto &item : qAsConst(items)) { 0450 ResourceItemInfo onlineItem; 0451 onlineItem.author = objectGetString(item.toObject(), "author"); 0452 onlineItem.authorUrl = objectGetString(item.toObject(), "authorUrl"); 0453 onlineItem.name = objectGetString(item.toObject(), "name"); 0454 onlineItem.filetype = objectGetString(item.toObject(), "filetype"); 0455 onlineItem.description = objectGetString(item.toObject(), "description"); 0456 onlineItem.id = (objectGetValue(item.toObject(), "id").isString() ? objectGetString(item.toObject(), "id") 0457 : QString::number(objectGetValue(item.toObject(), "id").toInt())); 0458 onlineItem.infoUrl = objectGetString(item.toObject(), "url"); 0459 onlineItem.license = objectGetString(item.toObject(), "licenseUrl"); 0460 onlineItem.imageUrl = objectGetString(item.toObject(), "imageUrl"); 0461 onlineItem.previewUrl = objectGetString(item.toObject(), "previewUrl"); 0462 onlineItem.width = objectGetValue(item.toObject(), "width").toInt(); 0463 onlineItem.height = objectGetValue(item.toObject(), "height").toInt(); 0464 onlineItem.duration = objectGetValue(item.toObject(), "duration").isDouble() ? int(objectGetValue(item.toObject(), "duration").toDouble()) 0465 : objectGetValue(item.toObject(), "duration").toInt(); 0466 0467 if (keys["downloadUrls"].isObject()) { 0468 if (keys["downloadUrls"].toObject()["isObject"].toBool(false)) { 0469 QJsonObject list = objectGetValue(item.toObject(), "downloadUrls.key").toObject(); 0470 for (const auto &key : list.keys()) { 0471 QJsonObject urlItem = list[key].toObject(); 0472 onlineItem.downloadUrls << objectGetString(urlItem, "downloadUrls.url", QString(), key); 0473 onlineItem.downloadLabels << objectGetString(urlItem, "downloadUrls.name", QString(), key); 0474 } 0475 } else { 0476 for (const auto urlItem : objectGetValue(item.toObject(), "downloadUrls.key").toArray()) { 0477 onlineItem.downloadUrls << objectGetString(urlItem.toObject(), "downloadUrls.url"); 0478 onlineItem.downloadLabels << objectGetString(urlItem.toObject(), "downloadUrls.name"); 0479 } 0480 } 0481 if (onlineItem.previewUrl.isEmpty() && !onlineItem.downloadUrls.isEmpty()) { 0482 onlineItem.previewUrl = onlineItem.downloadUrls.first(); 0483 } 0484 } else if (keys["downloadUrl"].isString()) { 0485 onlineItem.downloadUrl = objectGetString(item.toObject(), "downloadUrl"); 0486 if (onlineItem.previewUrl.isEmpty()) { 0487 onlineItem.previewUrl = onlineItem.downloadUrl; 0488 } 0489 } 0490 0491 list << onlineItem; 0492 } 0493 } else { 0494 qCWarning(KDENLIVE_LOG) << "WARNING: unknown response format: " << keys["format"]; 0495 } 0496 return std::pair<QList<ResourceItemInfo>, const int>(list, pageCount); 0497 } 0498 0499 /** 0500 * @brief ProviderModel::getFilesUrl 0501 * @param id The providers id of the item the data should be fetched for 0502 * @return the url 0503 * Get the url to fetch metadata about the available files. 0504 */ 0505 QUrl ProviderModel::getFilesUrl(const QString &id) 0506 { 0507 0508 QUrl url(m_apiroot); 0509 if (!m_download["req"].isObject()) { 0510 return QUrl(); 0511 } 0512 const QJsonObject req = m_download["req"].toObject(); 0513 QUrlQuery query; 0514 url.setPath(url.path().append(replacePlaceholders(req["path"].toString(), QString(), 0, id))); 0515 0516 for (const auto param : req["params"].toArray()) { 0517 query.addQueryItem(param.toObject()["key"].toString(), replacePlaceholders(param.toObject()["value"].toString(), QString(), 0, id)); 0518 } 0519 0520 url.setQuery(query); 0521 0522 return url; 0523 } 0524 0525 /** 0526 * @brief ProviderModel::slotFetchFiles 0527 * @param id The providers id of the item the date should be fetched for 0528 * Fetch metadata about the available files, if they are not included in the search response (e.g. archive.org) 0529 */ 0530 void ProviderModel::slotFetchFiles(const QString &id) 0531 { 0532 0533 QUrl uri = getFilesUrl(id); 0534 0535 if (uri.isEmpty()) { 0536 return; 0537 } 0538 0539 if (m_download["req"].toObject()["method"].toString() == "GET") { 0540 0541 QNetworkRequest request(uri); 0542 0543 if (m_download["req"].toObject()["header"].isArray()) { 0544 for (const auto &header : m_search["req"].toObject()["header"].toArray()) { 0545 request.setRawHeader(header.toObject()["key"].toString().toUtf8(), replacePlaceholders(header.toObject()["value"].toString()).toUtf8()); 0546 } 0547 } 0548 QNetworkReply *reply = m_networkManager->get(request); 0549 0550 connect(reply, &QNetworkReply::finished, this, [=]() { 0551 if (reply->error() == QNetworkReply::NoError) { 0552 QByteArray response = reply->readAll(); 0553 std::pair<QStringList, QStringList> result = parseFilesResponse(response, id); 0554 Q_EMIT fetchedFiles(result.first, result.second); 0555 reply->deleteLater(); 0556 } else { 0557 Q_EMIT fetchedFiles(QStringList(), QStringList()); 0558 qCDebug(KDENLIVE_LOG) << reply->errorString(); 0559 } 0560 }); 0561 0562 connect(reply, &QNetworkReply::sslErrors, this, [=]() { 0563 Q_EMIT fetchedFiles(QStringList(), QStringList()); 0564 qCDebug(KDENLIVE_LOG) << reply->errorString(); 0565 }); 0566 0567 } else { 0568 qCDebug(KDENLIVE_LOG) << "Only GET is implemented yet"; 0569 } 0570 } 0571 0572 /** 0573 * @brief ProviderModel::parseFilesResponse 0574 * @param data Response data of the api request 0575 * @param id The providers id of the item the date should be fetched for 0576 * @return pair of two QStringList First list contains urls to files, second list contains labels describing the files 0577 * Parse the response data of a fetch files request, usually after slotFetchFiles 0578 */ 0579 std::pair<QStringList, QStringList> ProviderModel::parseFilesResponse(const QByteArray &data, const QString &id) 0580 { 0581 QJsonObject keys = m_download["res"].toObject(); 0582 QStringList urls; 0583 QStringList labels; 0584 0585 if (keys["format"].toString() == "json") { 0586 QJsonObject res = QJsonDocument::fromJson(data).object(); 0587 0588 if (keys["downloadUrls"].isObject()) { 0589 if (keys["downloadUrls"].toObject()["isObject"].toBool(false)) { 0590 QJsonObject list = objectGetValue(res, "downloadUrls.key").toObject(); 0591 for (const auto &key : list.keys()) { 0592 QJsonObject urlItem = list[key].toObject(); 0593 QString format = objectGetString(urlItem, "downloadUrls.format", id, key); 0594 // This ugly check is only for the complicated archive.org api to avoid a long file list for videos caused by thumbs for each frame and 0595 // metafiles 0596 if (m_type == ProviderModel::VIDEO && m_homepage == "https://archive.org" && format != QLatin1String("Animated GIF") && 0597 format != QLatin1String("Metadata") && format != QLatin1String("Archive BitTorrent") && format != QLatin1String("Thumbnail") && 0598 format != QLatin1String("JSON") && format != QLatin1String("JPEG") && format != QLatin1String("JPEG Thumb") && 0599 format != QLatin1String("PNG") && format != QLatin1String("Video Index")) { 0600 urls << objectGetString(urlItem, "downloadUrls.url", id, key); 0601 labels << objectGetString(urlItem, "downloadUrls.name", id, key); 0602 } 0603 } 0604 } else { 0605 for (const auto urlItem : objectGetValue(res, "downloadUrls.key").toArray()) { 0606 urls << objectGetString(urlItem.toObject(), "downloadUrls.url", id); 0607 labels << objectGetString(urlItem.toObject(), "downloadUrls.name", id); 0608 } 0609 } 0610 0611 } else if (keys["downloadUrl"].isString()) { 0612 urls << objectGetString(res, "downloadUrl", id); 0613 } 0614 0615 } else { 0616 qCWarning(KDENLIVE_LOG) << "WARNING fetch files: unknown response format: " << keys["format"]; 0617 } 0618 return std::pair<QStringList, QStringList>(urls, labels); 0619 }