File indexing completed on 2024-04-21 04:51:53

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 "resourcewidget.hpp"
0008 #include "core.h"
0009 #include "kdenlivesettings.h"
0010 
0011 #include "utils/KMessageBox_KdenliveCompat.h"
0012 #include <KFileItem>
0013 #include <KIO/FileCopyJob>
0014 #include <KIO/Global>
0015 #include <KLocalizedString>
0016 #include <KMessageBox>
0017 #include <KRecentDirs>
0018 #include <KSelectAction>
0019 #include <KSqueezedTextLabel>
0020 #include <QComboBox>
0021 #include <QDesktopServices>
0022 #include <QFileDialog>
0023 #include <QFontDatabase>
0024 #include <QIcon>
0025 #include <QInputDialog>
0026 #include <QMenu>
0027 #include <QToolBar>
0028 #include <QtConcurrent>
0029 
0030 #include <kcompletion_version.h>
0031 
0032 ResourceWidget::ResourceWidget(QWidget *parent)
0033     : QWidget(parent)
0034     , m_showloadingWarning(true)
0035 {
0036     setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
0037     setupUi(this);
0038     m_tmpThumbFile = new QTemporaryFile(this);
0039 
0040     int iconHeight = int(QFontInfo(font()).pixelSize() * 3.5);
0041     m_iconSize = QSize(int(iconHeight * pCore->getCurrentDar()), iconHeight);
0042 
0043     slider_zoom->setRange(0, 15);
0044     connect(slider_zoom, &QAbstractSlider::valueChanged, this, &ResourceWidget::slotSetIconSize);
0045     connect(button_zoomin, &QToolButton::clicked, this, [&]() { slider_zoom->setValue(qMin(slider_zoom->value() + 1, slider_zoom->maximum())); });
0046     connect(button_zoomout, &QToolButton::clicked, this, [&]() { slider_zoom->setValue(qMax(slider_zoom->value() - 1, slider_zoom->minimum())); });
0047 
0048     message_line->hide();
0049 
0050     for (const QPair<QString, QString> &provider : ProvidersRepository::get()->getAllProviers()) {
0051         QIcon icon;
0052         switch (ProvidersRepository::get()->getProvider(provider.second)->type()) {
0053         case ProviderModel::AUDIO:
0054             icon = QIcon::fromTheme(QStringLiteral("player-volume"));
0055             break;
0056         case ProviderModel::VIDEO:
0057             icon = QIcon::fromTheme(QStringLiteral("camera-video"));
0058             break;
0059         case ProviderModel::IMAGE:
0060             icon = QIcon::fromTheme(QStringLiteral("camera-photo"));
0061             break;
0062         default:
0063             icon = QIcon();
0064         }
0065         service_list->addItem(icon, provider.first, provider.second);
0066     }
0067     connect(service_list, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &ResourceWidget::slotChangeProvider);
0068     loadConfig();
0069     connect(provider_info, &KUrlLabel::leftClickedUrl, this, [&]() { slotOpenUrl(provider_info->url()); });
0070     connect(label_license, &KUrlLabel::leftClickedUrl, this, [&]() { slotOpenUrl(label_license->url()); });
0071     connect(search_text, &KLineEdit::returnKeyPressed, this, &ResourceWidget::slotStartSearch);
0072     connect(search_results, &QListWidget::currentRowChanged, this, &ResourceWidget::slotUpdateCurrentItem);
0073     connect(this, &ResourceWidget::gotPixmap, this, &ResourceWidget::slotShowPixmap);
0074     connect(button_preview, &QAbstractButton::clicked, this, [&]() {
0075         if (!m_currentProvider) {
0076             return;
0077         }
0078 
0079         slotPreviewItem();
0080     });
0081 
0082     connect(button_import, &QAbstractButton::clicked, this, [&]() {
0083         if (m_currentProvider->get()->downloadOAuth2()) {
0084             if (m_currentProvider->get()->requiresLogin()) {
0085                 KMessageBox::information(this, i18n("Login is required to download this item.\nYou will be redirected to the login page now."));
0086             }
0087             m_currentProvider->get()->authorize();
0088         } else {
0089             if (m_currentItem->data(singleDownloadRole).toBool()) {
0090                 if (m_currentItem->data(downloadRole).toString().isEmpty()) {
0091                     m_currentProvider->get()->slotFetchFiles(m_currentItem->data(idRole).toString());
0092                     return;
0093                 } else {
0094                     slotSaveItem();
0095                 }
0096             } else {
0097                 slotChooseVersion(m_currentItem->data(downloadRole).toStringList(), m_currentItem->data(downloadLabelRole).toStringList());
0098             }
0099         }
0100     });
0101 
0102     page_number->setEnabled(false);
0103 
0104     connect(page_number, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &ResourceWidget::slotStartSearch);
0105     adjustSize();
0106 }
0107 
0108 ResourceWidget::~ResourceWidget()
0109 {
0110     saveConfig();
0111     delete m_tmpThumbFile;
0112 }
0113 
0114 /**
0115  * @brief ResourceWidget::saveConfig
0116  * Load current provider and zoom value from config file
0117  */
0118 void ResourceWidget::loadConfig()
0119 {
0120     KSharedConfigPtr config = KSharedConfig::openConfig();
0121     KConfigGroup resourceConfig(config, "OnlineResources");
0122     slider_zoom->setValue(resourceConfig.readEntry("zoom", 7));
0123     if (resourceConfig.readEntry("provider", service_list->itemText(0)).isEmpty()) {
0124         service_list->setCurrentIndex(0);
0125     } else {
0126         service_list->setCurrentItem(resourceConfig.readEntry("provider", service_list->itemText(0)));
0127     }
0128     slotChangeProvider();
0129 }
0130 
0131 /**
0132  * @brief ResourceWidget::saveConfig
0133  * Save current provider and zoom value to config file
0134  */
0135 void ResourceWidget::saveConfig()
0136 {
0137     KSharedConfigPtr config = KSharedConfig::openConfig();
0138     KConfigGroup resourceConfig(config, "OnlineResources");
0139     resourceConfig.writeEntry(QStringLiteral("provider"), service_list->currentText());
0140     resourceConfig.writeEntry(QStringLiteral("zoom"), slider_zoom->value());
0141     config->sync();
0142 }
0143 
0144 /**
0145  * @brief ResourceWidget::blockUI
0146  * @param block
0147  * Block or unblock the online resource ui
0148  */
0149 void ResourceWidget::blockUI(bool block)
0150 {
0151     buildin_box->setEnabled(!block);
0152     search_text->setEnabled(!block);
0153     service_list->setEnabled(!block);
0154     setCursor(block ? Qt::WaitCursor : Qt::ArrowCursor);
0155 }
0156 
0157 /**
0158  * @brief ResourceWidget::slotChangeProvider
0159  * Set m_currentProvider to the current selected provider of the service_list and update ui
0160  */
0161 void ResourceWidget::slotChangeProvider()
0162 {
0163     if (m_currentProvider != nullptr) {
0164         m_currentProvider->get()->disconnect(this);
0165     }
0166 
0167     details_box->setEnabled(false);
0168     button_import->setEnabled(false);
0169     button_preview->setEnabled(false);
0170     info_browser->clear();
0171     search_results->clear();
0172     page_number->blockSignals(true);
0173     page_number->setValue(1);
0174     page_number->setMaximum(1);
0175     page_number->blockSignals(false);
0176 
0177     if (service_list->currentData().toString().isEmpty()) {
0178         provider_info->clear();
0179         buildin_box->setEnabled(false);
0180         return;
0181     } else {
0182         buildin_box->setEnabled(true);
0183         message_line->hide();
0184     }
0185 
0186     m_currentProvider = &ProvidersRepository::get()->getProvider(service_list->currentData().toString());
0187 
0188     provider_info->setText(i18n("Media provided by %1", m_currentProvider->get()->name()));
0189     provider_info->setUrl(m_currentProvider->get()->homepage());
0190     connect(m_currentProvider->get(), &ProviderModel::searchDone, this, &ResourceWidget::slotSearchFinished);
0191     connect(m_currentProvider->get(), &ProviderModel::searchError, this, &ResourceWidget::slotDisplayError);
0192 
0193     connect(m_currentProvider->get(), &ProviderModel::fetchedFiles, this, &ResourceWidget::slotChooseVersion);
0194     connect(m_currentProvider->get(), &ProviderModel::authenticated, this, &ResourceWidget::slotAccessTokenReceived);
0195 
0196     // automatically kick of a search if we have search text and we switch services.
0197     if (!search_text->text().isEmpty()) {
0198         slotStartSearch();
0199     }
0200 }
0201 
0202 /**
0203  * @brief ResourceWidget::slotOpenUrl
0204  * @param url link to open in external browser
0205  * Open a url in a external browser
0206  */
0207 void ResourceWidget::slotOpenUrl(const QString &url)
0208 {
0209     QDesktopServices::openUrl(QUrl(url));
0210 }
0211 
0212 /**
0213  * @brief ResourceWidget::slotStartSearch
0214  * Calls slotStartSearch on the object for the currently selected service.
0215  */
0216 void ResourceWidget::slotStartSearch()
0217 {
0218     message_line->setText(i18nc("@info:status", "Search pending…"));
0219     message_line->setMessageType(KMessageWidget::Information);
0220     message_line->show();
0221 
0222     blockUI(true);
0223     details_box->setEnabled(false);
0224     button_import->setEnabled(false);
0225     button_preview->setEnabled(false);
0226     info_browser->clear();
0227     search_results->clear();
0228     m_currentProvider->get()->slotStartSearch(search_text->text(), page_number->value());
0229 }
0230 
0231 /**
0232  * @brief ResourceWidget::slotDisplayError
0233  * @param message the error text
0234  * Displays the items of list in the search_results ListView
0235  */
0236 void ResourceWidget::slotDisplayError(const QString &message)
0237 {
0238     message_line->setText(i18n("Search failed! %1", message));
0239     message_line->setMessageType(KMessageWidget::Error);
0240     message_line->show();
0241     page_number->setEnabled(false);
0242     service_list->setEnabled(true);
0243     buildin_box->setEnabled(true);
0244     setCursor(Qt::ArrowCursor);
0245 }
0246 
0247 /**
0248  * @brief ResourceWidget::slotSearchFinished
0249  * @param list list of the found items
0250  * @param pageCount number of found pages
0251  * Displays the items of list in the search_results ListView
0252  */
0253 void ResourceWidget::slotSearchFinished(const QList<ResourceItemInfo> &list, int pageCount)
0254 {
0255     QMutexLocker lock(&m_imageLock);
0256     m_imagesUrl.clear();
0257     if (list.isEmpty()) {
0258         message_line->setText(i18nc("@info", "No items found."));
0259         message_line->setMessageType(KMessageWidget::Error);
0260         message_line->show();
0261         blockUI(false);
0262         return;
0263     }
0264 
0265     message_line->setMessageType(KMessageWidget::Information);
0266     message_line->show();
0267     int count = 0;
0268     for (const ResourceItemInfo &item : qAsConst(list)) {
0269         message_line->setText(i18nc("@info:progress", "Parsing item %1 of %2…", count, list.count()));
0270         // if item has no name use "Created by Author", if item even has no author use "Unnamed"
0271         QListWidgetItem *listItem = new QListWidgetItem(
0272             item.name.isEmpty() ? (item.author.isEmpty() ? i18n("Unnamed") : i18nc("Created by author name", "Created by %1", item.author)) : item.name);
0273         if (!item.imageUrl.isEmpty()) {
0274             m_imagesUrl << item.imageUrl;
0275         }
0276 
0277         listItem->setData(idRole, item.id);
0278         listItem->setData(nameRole, item.name);
0279         listItem->setData(filetypeRole, item.filetype);
0280         listItem->setData(descriptionRole, item.description);
0281         listItem->setData(imageRole, item.imageUrl);
0282         listItem->setData(previewRole, item.previewUrl);
0283         listItem->setData(authorUrl, item.authorUrl);
0284         listItem->setData(authorRole, item.author);
0285         listItem->setData(widthRole, item.width);
0286         listItem->setData(heightRole, item.height);
0287         listItem->setData(durationRole, item.duration);
0288         listItem->setData(urlRole, item.infoUrl);
0289         listItem->setData(licenseRole, item.license);
0290         if (item.downloadUrl.isEmpty() && item.downloadUrls.length() > 0) {
0291             listItem->setData(singleDownloadRole, false);
0292             listItem->setData(downloadRole, item.downloadUrls);
0293             listItem->setData(downloadLabelRole, item.downloadLabels);
0294         } else {
0295             listItem->setData(singleDownloadRole, true);
0296             listItem->setData(downloadRole, item.downloadUrl);
0297         }
0298         search_results->addItem(listItem);
0299         count++;
0300     }
0301     m_imagesUrl.removeDuplicates();
0302     message_line->hide();
0303     page_number->setMaximum(pageCount);
0304     page_number->setEnabled(true);
0305     blockUI(false);
0306     lock.unlock();
0307 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0308     QtConcurrent::run(this, &ResourceWidget::slotLoadImages);
0309 #else
0310     QtConcurrent::run(&ResourceWidget::slotLoadImages, this);
0311 #endif
0312 }
0313 
0314 void ResourceWidget::slotShowPixmap(const QString &url, const QPixmap &pixmap)
0315 {
0316     for (int i = 0; i < search_results->count(); i++) {
0317         auto item = search_results->item(i);
0318         if (item->data(imageRole).toString() == url) {
0319             item->setIcon(pixmap);
0320             break;
0321         }
0322     }
0323 }
0324 
0325 void ResourceWidget::slotLoadImages()
0326 {
0327     if (!m_imageLock.tryLock()) {
0328         // Another request is loading, wait
0329         return;
0330     }
0331     if (m_imagesUrl.isEmpty()) {
0332         // No images to load
0333         m_imageLock.unlock();
0334         return;
0335     }
0336     while (!m_imagesUrl.isEmpty()) {
0337         const QString url = m_imagesUrl.takeFirst();
0338         m_imageLock.unlock();
0339         QUrl img(url);
0340         m_tmpThumbFile->close();
0341         if (m_tmpThumbFile->open()) {
0342             KIO::FileCopyJob *copyjob = KIO::file_copy(img, QUrl::fromLocalFile(m_tmpThumbFile->fileName()), -1, KIO::HideProgressInfo | KIO::Overwrite);
0343             if (copyjob->exec()) {
0344                 QPixmap pic(m_tmpThumbFile->fileName());
0345                 Q_EMIT gotPixmap(url, pic);
0346             }
0347         }
0348         if (m_imagesUrl.isEmpty()) {
0349             break;
0350         }
0351         if (!m_imageLock.tryLock()) {
0352             break;
0353         }
0354     }
0355 }
0356 
0357 /**
0358  * @brief ResourceWidget::slotUpdateCurrentItem
0359  * Set m_currentItem to the current selected item of the search_results ListView and
0360  * show its details within the details_box
0361  */
0362 void ResourceWidget::slotUpdateCurrentItem()
0363 {
0364     details_box->setEnabled(false);
0365     button_import->setEnabled(false);
0366     button_preview->setEnabled(false);
0367 
0368     // get the item the user selected
0369     m_currentItem = search_results->currentItem();
0370     if (!m_currentItem) {
0371         return;
0372     }
0373 
0374     if (m_currentProvider->get()->type() != ProviderModel::IMAGE && !m_currentItem->data(previewRole).toString().isEmpty()) {
0375         button_preview->show();
0376     } else {
0377         button_preview->hide();
0378     }
0379 
0380     QString details = "<h3>" + m_currentItem->text();
0381 
0382     if (!m_currentItem->data(urlRole).toString().isEmpty()) {
0383         details += QStringLiteral(" <a href=\"%1\">%2</a>").arg(m_currentItem->data(urlRole).toString(), i18nc("the url link pointing to a web page", "link"));
0384     }
0385 
0386     details.append(QStringLiteral("</h3>"));
0387 
0388     if (!m_currentItem->data(authorUrl).toString().isEmpty()) {
0389         details += i18n("Created by <a href=\"%1\">", m_currentItem->data(authorUrl).toString());
0390         if (!m_currentItem->data(authorRole).toString().isEmpty()) {
0391             details.append(m_currentItem->data(authorRole).toString());
0392         } else {
0393             details.append(i18n("Author"));
0394         }
0395         details.append(QStringLiteral("</a><br />"));
0396     } else if (!m_currentItem->data(authorRole).toString().isEmpty()) {
0397         details.append(i18n("Created by %1", m_currentItem->data(authorRole).toString()) + QStringLiteral("<br />"));
0398     } else {
0399         details.append(QStringLiteral("<br />"));
0400     }
0401 
0402     if (m_currentProvider->get()->type() != ProviderModel::AUDIO && m_currentItem->data(widthRole).toInt() != 0) {
0403         details.append(i18n("Size: %1 x %2", m_currentItem->data(widthRole).toInt(), m_currentItem->data(heightRole).toInt()) + QStringLiteral("<br />"));
0404     }
0405     if (m_currentItem->data(durationRole).toInt() != 0) {
0406         details.append(i18n("Duration: %1 sec", m_currentItem->data(durationRole).toInt()) + QStringLiteral("<br />"));
0407     }
0408     details.append(m_currentItem->data(descriptionRole).toString());
0409     info_browser->setHtml(details);
0410 
0411     label_license->setText(licenseNameFromUrl(m_currentItem->data(licenseRole).toString(), true));
0412     label_license->setTipText(licenseNameFromUrl(m_currentItem->data(licenseRole).toString(), false));
0413     label_license->setUseTips(true);
0414     label_license->setUrl(m_currentItem->data(licenseRole).toString());
0415 
0416     details_box->setEnabled(true);
0417     button_import->setEnabled(true);
0418     button_preview->setEnabled(true);
0419 }
0420 
0421 /**
0422  * @brief ResourceWidget::licenseNameFromUrl
0423  * @param licenseUrl
0424  * @param shortName Whether the long name like "Attribution-NonCommercial-ShareAlike 3.0" or the short name like "CC BY-NC-SA 3.0" should be returned
0425  * @return the license name "Unnamed License" if url is not known.
0426  */
0427 QString ResourceWidget::licenseNameFromUrl(const QString &licenseUrl, const bool shortName)
0428 {
0429     QString licenseName;
0430     QString licenseShortName;
0431 
0432     if (licenseUrl.contains("creativecommons.org")) {
0433         if (licenseUrl.contains(QStringLiteral("/sampling+/"))) {
0434             licenseName = i18nc("Creative Commons License", "CC Sampling+");
0435         } else if (licenseUrl.contains(QStringLiteral("/by/"))) {
0436             licenseName = i18nc("Creative Commons License", "Creative Commons Attribution");
0437             licenseShortName = i18nc("Creative Commons License (short)", "CC BY");
0438         } else if (licenseUrl.contains(QStringLiteral("/by-nd/"))) {
0439             licenseName = i18nc("Creative Commons License", "Creative Commons Attribution-NoDerivs");
0440             licenseShortName = i18nc("Creative Commons License (short)", "CC BY-ND");
0441         } else if (licenseUrl.contains(QStringLiteral("/by-nc-sa/"))) {
0442             licenseName = i18nc("Creative Commons License", "Creative Commons Attribution-NonCommercial-ShareAlike");
0443             licenseShortName = i18nc("Creative Commons License (short)", "CC BY-NC-SA");
0444         } else if (licenseUrl.contains(QStringLiteral("/by-sa/"))) {
0445             licenseName = i18nc("Creative Commons License", "Creative Commons Attribution-ShareAlike");
0446             licenseShortName = i18nc("Creative Commons License (short)", "CC BY-SA");
0447         } else if (licenseUrl.contains(QStringLiteral("/by-nc/"))) {
0448             licenseName = i18nc("Creative Commons License", "Creative Commons Attribution-NonCommercial");
0449             licenseShortName = i18nc("Creative Commons License (short)", "CC BY-NC");
0450         } else if (licenseUrl.contains(QStringLiteral("/by-nc-nd/"))) {
0451             licenseName = i18nc("Creative Commons License", "Creative Commons Attribution-NonCommercial-NoDerivs");
0452             licenseShortName = i18nc("Creative Commons License (short)", "CC BY-NC-ND");
0453         } else if (licenseUrl.contains(QLatin1String("/publicdomain/zero/"))) {
0454             licenseName = i18nc("Creative Commons License", "Creative Commons 0");
0455             licenseShortName = i18nc("Creative Commons License (short)", "CC 0");
0456         } else if (licenseUrl.endsWith(QLatin1String("/publicdomain")) || licenseUrl.contains(QLatin1String("openclipart.org/share"))) {
0457             licenseName = i18nc("License", "Public Domain");
0458         } else {
0459             licenseShortName = i18nc("Short for: Unknown Creative Commons License", "Unknown CC License");
0460             licenseName = i18n("Unknown Creative Commons License");
0461         }
0462 
0463         if (licenseUrl.contains(QStringLiteral("/1.0"))) {
0464             licenseName.append(QStringLiteral(" 1.0"));
0465             licenseShortName.append(QStringLiteral(" 1.0"));
0466         } else if (licenseUrl.contains(QStringLiteral("/2.0"))) {
0467             licenseName.append(QStringLiteral(" 2.0"));
0468             licenseShortName.append(QStringLiteral(" 2.0"));
0469         } else if (licenseUrl.contains(QStringLiteral("/2.5"))) {
0470             licenseName.append(QStringLiteral(" 2.5"));
0471             licenseShortName.append(QStringLiteral(" 2.5"));
0472         } else if (licenseUrl.contains(QStringLiteral("/3.0"))) {
0473             licenseName.append(QStringLiteral(" 3.0"));
0474             licenseShortName.append(QStringLiteral(" 3.0"));
0475         } else if (licenseUrl.contains(QStringLiteral("/4.0"))) {
0476             licenseName.append(QStringLiteral(" 4.0"));
0477             licenseShortName.append(QStringLiteral(" 4.0"));
0478         }
0479     } else if (licenseUrl.contains("pexels.com/license/")) {
0480         licenseName = i18n("Pexels License");
0481     } else if (licenseUrl.contains("pixabay.com/service/license/")) {
0482         licenseName = i18n("Pixabay License");
0483         ;
0484     } else {
0485         licenseName = i18n("Unknown License");
0486     }
0487 
0488     if (shortName && !licenseShortName.isEmpty()) {
0489         return licenseShortName;
0490     } else {
0491         return licenseName;
0492     }
0493 }
0494 
0495 /**
0496  * @brief ResourceWidget::slotSetIconSize
0497  * @param size
0498  * Set the icon size for the search_results ListView
0499  */
0500 void ResourceWidget::slotSetIconSize(int size)
0501 {
0502     if (!search_results) {
0503         return;
0504     }
0505     QSize zoom = m_iconSize;
0506     zoom = zoom * (size / 4.0);
0507     search_results->setIconSize(zoom);
0508 }
0509 
0510 /**
0511  * @brief ResourceWidget::slotPreviewItem
0512  * Emits the previewClip signal if m_currentItem is valid to display a preview in the Clip Monitor
0513  */
0514 void ResourceWidget::slotPreviewItem()
0515 {
0516     if (!m_currentItem) {
0517         return;
0518     }
0519     blockUI(true);
0520     const QString path = m_currentItem->data(previewRole).toString();
0521     if (m_showloadingWarning && !QUrl::fromUserInput(path).isLocalFile()) {
0522         message_line->setText(i18n("It maybe takes a while until the preview is loaded"));
0523         message_line->setMessageType(KMessageWidget::Warning);
0524         message_line->show();
0525         QTimer::singleShot(6000, message_line, &KMessageWidget::animatedHide);
0526         repaint();
0527         // Only show this warning once
0528         m_showloadingWarning = false;
0529     }
0530     Q_EMIT previewClip(path, i18n("Online Resources Preview"));
0531     blockUI(false);
0532 }
0533 
0534 /**
0535  * @brief ResourceWidget::slotChooseVersion
0536  * @param urls list of download urls pointing to the certain file version
0537  * @param labels list of labels for the certain file version (needs to have the same order than urls)
0538  * @param accessToken access token to pass through to slotSaveItem
0539  * Displays a dialog to let the user choose a file version (e.g. filetype, quality) if there are multiple versions
0540  * available
0541  */
0542 void ResourceWidget::slotChooseVersion(const QStringList &urls, const QStringList &labels, const QString &accessToken)
0543 {
0544     if (urls.isEmpty() || labels.isEmpty()) {
0545         return;
0546     }
0547     if (urls.length() == 1) {
0548         slotSaveItem(urls.first(), accessToken);
0549         return;
0550     }
0551     bool ok;
0552     QString name = QInputDialog::getItem(this, i18nc("@title:window", "Choose File Version"), i18n("Please choose the version you want to download"), labels, 0,
0553                                          false, &ok);
0554     if (!ok || name.isEmpty()) {
0555         return;
0556     }
0557     slotSaveItem(urls.at(labels.indexOf(name)), accessToken);
0558 }
0559 
0560 /**
0561  * @brief ResourceWidget::slotSaveItem
0562  * @param originalUrl url pointing to the download file
0563  * @param accessToken the access token (optional)
0564  * Opens a dialog for user to choose a save location and start the download of the file
0565  */
0566 void ResourceWidget::slotSaveItem(const QString &originalUrl, const QString &accessToken)
0567 {
0568     QUrl saveUrl;
0569 
0570     if (!m_currentItem) {
0571         return;
0572     }
0573 
0574     QUrl srcUrl(originalUrl.isEmpty() ? m_currentItem->data(downloadRole).toString() : originalUrl);
0575     if (srcUrl.isEmpty()) {
0576         return;
0577     }
0578 
0579     QString path = KRecentDirs::dir(QStringLiteral(":KdenliveOnlineResourceFolder"));
0580     QString ext;
0581 
0582     if (path.isEmpty()) {
0583         path = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
0584     }
0585     if (!path.endsWith(QLatin1Char('/'))) {
0586         path.append(QLatin1Char('/'));
0587     }
0588     if (!srcUrl.fileName().isEmpty()) {
0589         path.append(srcUrl.fileName());
0590         ext = "*." + srcUrl.fileName().section(QLatin1Char('.'), -1);
0591     } else if (!m_currentItem->data(filetypeRole).toString().isEmpty()) {
0592         ext = "*." + m_currentItem->data(filetypeRole).toString();
0593     } else {
0594         if (m_currentProvider->get()->type() == ProviderModel::AUDIO) {
0595             ext = i18n("Audio") + QStringLiteral(" (*.wav *.mp3 *.ogg *.aif *.aiff *.m4a *.flac)") + QStringLiteral(";;");
0596         } else if (m_currentProvider->get()->type() == ProviderModel::VIDEO) {
0597             ext = i18n("Video") + QStringLiteral(" (*.mp4 *.webm *.mpg *.mov *.avi *.mkv)") + QStringLiteral(";;");
0598         } else if (m_currentProvider->get()->type() == ProviderModel::ProviderModel::IMAGE) {
0599             ext = i18n("Images") + QStringLiteral(" (*.png *.jpg *.jpeg *.svg") + QStringLiteral(";;");
0600         }
0601         ext.append(i18n("All Files") + QStringLiteral(" (*)"));
0602     }
0603     if (path.endsWith(QLatin1Char('/'))) {
0604         if (m_currentItem->data(nameRole).toString().isEmpty()) {
0605             path.append(i18n("Unnamed"));
0606         } else {
0607             path.append(m_currentItem->data(nameRole).toString());
0608         }
0609         path.append(srcUrl.fileName().section(QLatin1Char('.'), -1));
0610     }
0611 
0612     QString attribution;
0613     if (KMessageBox::questionTwoActions(this,
0614                                         i18n("Be aware that the usage of the resource is maybe restricted by license terms or law!\n"
0615                                              "Do you want to add license attribution to your Project Notes?"),
0616                                         QString(), KStandardGuiItem::add(), KGuiItem(i18nc("@action:button", "Continue without")),
0617                                         i18n("Remember this decision")) == KMessageBox::PrimaryAction) {
0618         attribution = i18nc("item name, item url, author name, license name, license url",
0619                             "This video uses \"%1\" (%2) by \"%3\" licensed under %4. To view a copy of this license, visit %5",
0620                             m_currentItem->data(nameRole).toString().isEmpty() ? i18n("Unnamed") : m_currentItem->data(nameRole).toString(),
0621                             m_currentItem->data(urlRole).toString(), m_currentItem->data(authorRole).toString(),
0622                             ResourceWidget::licenseNameFromUrl(m_currentItem->data(licenseRole).toString(), true), m_currentItem->data(licenseRole).toString());
0623         attribution.append(QStringLiteral("<br/> "));
0624     }
0625 
0626     QString saveUrlstring = QFileDialog::getSaveFileName(this, QString(), path, ext);
0627 
0628     // if user cancels save
0629     if (saveUrlstring.isEmpty()) {
0630         return;
0631     }
0632 
0633     saveUrl = QUrl::fromLocalFile(saveUrlstring);
0634 
0635     KIO::FileCopyJob *getJob = KIO::file_copy(srcUrl, saveUrl, -1, KIO::Overwrite);
0636     if (!accessToken.isEmpty()) {
0637         getJob->addMetaData("customHTTPHeader", QStringLiteral("Authorization: Bearer %1").arg(accessToken));
0638     }
0639     KFileItem info(srcUrl);
0640     getJob->setSourceSize(info.size());
0641     getJob->setProperty("attribution", attribution);
0642     getJob->setProperty("usedOAuth2", !accessToken.isEmpty());
0643 
0644     connect(getJob, &KJob::result, this, &ResourceWidget::slotGotFile);
0645     getJob->start();
0646 }
0647 
0648 /**
0649  * @brief ResourceWidget::slotGotFile
0650  * @param job
0651  * Finish the download by emitting addClip and if necessary addLicenseInfo
0652  * Enables the import button
0653  */
0654 void ResourceWidget::slotGotFile(KJob *job)
0655 
0656 {
0657     if (job->error() != 0) {
0658         const QString errTxt = job->errorString();
0659         if (job->property("usedOAuth2").toBool()) {
0660             KMessageBox::error(this, i18n("%1 Try again.", errTxt), i18n("Error Loading Data"));
0661         } else {
0662             KMessageBox::error(this, errTxt, i18n("Error Loading Data"));
0663         }
0664         qCDebug(KDENLIVE_LOG) << "//file import job errored: " << errTxt;
0665         return;
0666     }
0667     auto *copyJob = static_cast<KIO::FileCopyJob *>(job);
0668     const QUrl filePath = copyJob->destUrl();
0669     KRecentDirs::add(QStringLiteral(":KdenliveOnlineResourceFolder"), filePath.adjusted(QUrl::RemoveFilename).toLocalFile());
0670 
0671     KMessageBox::information(this, i18n("Resource saved to %1", filePath.toLocalFile()), i18n("Data Imported"));
0672     Q_EMIT addClip(filePath, QString());
0673 
0674     if (!copyJob->property("attribution").toString().isEmpty()) {
0675         Q_EMIT addLicenseInfo(copyJob->property("attribution").toString());
0676     }
0677 }
0678 
0679 /**
0680  * @brief ResourceWidget::slotAccessTokenReceived
0681  * @param accessToken - the access token obtained from the provider
0682  * Calls slotSaveItem or slotChooseVersion for auth protected files
0683  */
0684 void ResourceWidget::slotAccessTokenReceived(const QString &accessToken)
0685 {
0686     if (!accessToken.isEmpty()) {
0687         if (m_currentItem->data(singleDownloadRole).toBool()) {
0688             if (m_currentItem->data(downloadRole).toString().isEmpty()) {
0689                 m_currentProvider->get()->slotFetchFiles(m_currentItem->data(idRole).toString());
0690                 return;
0691             } else {
0692                 slotSaveItem(QString(), accessToken);
0693             }
0694         } else {
0695             slotChooseVersion(m_currentItem->data(downloadRole).toStringList(), m_currentItem->data(downloadLabelRole).toStringList(), accessToken);
0696         }
0697 
0698     } else {
0699         KMessageBox::error(this, i18n("Try importing again to obtain a new connection"),
0700                            i18n("Error Getting Access Token from %1.", m_currentProvider->get()->name()));
0701     }
0702 }