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 &section : 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 }