File indexing completed on 2024-04-28 08:44:15

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