File indexing completed on 2024-05-12 04:58:05

0001 /* ============================================================
0002 * Falkon - Qt web browser
0003 * Copyright (C) 2010-2018 David Rosca <nowrep@gmail.com>
0004 *
0005 * This program is free software: you can redistribute it and/or modify
0006 * it under the terms of the GNU General Public License as published by
0007 * the Free Software Foundation, either version 3 of the License, or
0008 * (at your option) any later version.
0009 *
0010 * This program is distributed in the hope that it will be useful,
0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013 * GNU General Public License for more details.
0014 *
0015 * You should have received a copy of the GNU General Public License
0016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0017 * ============================================================ */
0018 #include "downloadmanager.h"
0019 #include "ui_downloadmanager.h"
0020 #include "browserwindow.h"
0021 #include "mainapplication.h"
0022 #include "downloadoptionsdialog.h"
0023 #include "downloaditem.h"
0024 #include "downloadmanagermodel.h"
0025 #include "networkmanager.h"
0026 #include "desktopnotificationsfactory.h"
0027 #include "qztools.h"
0028 #include "webpage.h"
0029 #include "webview.h"
0030 #include "settings.h"
0031 #include "datapaths.h"
0032 #include "tabwidget.h"
0033 #include "tabbedwebview.h"
0034 #include "tabbar.h"
0035 #include "locationbar.h"
0036 
0037 #include <QMessageBox>
0038 #include <QCloseEvent>
0039 #include <QDir>
0040 #include <QShortcut>
0041 #include <QStandardPaths>
0042 #include <QWebEngineHistory>
0043 #include <QWebEngineDownloadRequest>
0044 #include <QtWebEngineWidgetsVersion>
0045 
0046 #ifdef Q_OS_WIN
0047 #include <QtWin>
0048 #include <QWindow>
0049 #include <QWinTaskbarButton>
0050 #include <QWinTaskbarProgress>
0051 #endif
0052 
0053 DownloadManager::DownloadManager(QWidget* parent)
0054     : QWidget(parent)
0055     , ui(new Ui::DownloadManager)
0056     , m_model(new DownloadManagerModel(this))
0057     , m_isClosing(false)
0058     , m_lastDownloadOption(NoOption)
0059 {
0060     setWindowFlags(windowFlags() ^ Qt::WindowMaximizeButtonHint);
0061     ui->setupUi(this);
0062 #ifdef Q_OS_WIN
0063     if (QtWin::isCompositionEnabled()) {
0064         QtWin::extendFrameIntoClientArea(this, -1, -1, -1, -1);
0065     }
0066 #endif
0067     ui->clearButton->setIcon(QIcon::fromTheme(QSL("edit-clear")));
0068     QzTools::centerWidgetOnScreen(this);
0069 
0070     connect(ui->clearButton, &QAbstractButton::clicked, this, &DownloadManager::clearList);
0071 
0072     auto* clearShortcut = new QShortcut(QKeySequence(QSL("CTRL+L")), this);
0073     connect(clearShortcut, &QShortcut::activated, this, &DownloadManager::clearList);
0074 
0075     loadSettings();
0076 
0077     QzTools::setWmClass(QSL("Download Manager"), this);
0078 
0079     connect(m_model, &DownloadManagerModel::downloadAdded, this, &DownloadManager::downloadAdded);
0080 }
0081 
0082 void DownloadManager::loadSettings()
0083 {
0084     Settings settings;
0085     settings.beginGroup(QSL("DownloadManager"));
0086     m_downloadPath = settings.value(QSL("defaultDownloadPath"), QString()).toString();
0087     m_lastDownloadPath = settings.value(QSL("lastDownloadPath"), QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)).toString();
0088     m_closeOnFinish = settings.value(QSL("CloseManagerOnFinish"), false).toBool();
0089     m_useNativeDialog = settings.value(QSL("useNativeDialog"), DEFAULT_DOWNLOAD_USE_NATIVE_DIALOG).toBool();
0090 
0091     m_useExternalManager = settings.value(QSL("UseExternalManager"), false).toBool();
0092     m_externalExecutable = settings.value(QSL("ExternalManagerExecutable"), QString()).toString();
0093     m_externalArguments = settings.value(QSL("ExternalManagerArguments"), QString()).toString();
0094     settings.endGroup();
0095 
0096     if (!m_externalArguments.contains(QLatin1String("%d"))) {
0097         m_externalArguments.append(QLatin1String(" %d"));
0098     }
0099 }
0100 
0101 void DownloadManager::show()
0102 {
0103     m_timer.start(500, this);
0104 
0105     QWidget::show();
0106     raise();
0107     activateWindow();
0108 }
0109 
0110 void DownloadManager::resizeEvent(QResizeEvent* e)
0111 {
0112     QWidget::resizeEvent(e);
0113     Q_EMIT resized(size());
0114 }
0115 
0116 void DownloadManager::keyPressEvent(QKeyEvent* e)
0117 {
0118     if (e->key() == Qt::Key_Escape
0119         || (e->key() == Qt::Key_W && e->modifiers() == Qt::ControlModifier)) {
0120         close();
0121     }
0122 
0123     QWidget::keyPressEvent(e);
0124 }
0125 
0126 void DownloadManager::closeDownloadTab(QWebEngineDownloadRequest *item) const
0127 {
0128     // Attempt to close empty tab that was opened only for loading the download url
0129     auto testWebView = [](TabbedWebView *view, const QUrl &url) {
0130         if (!view->webTab()->isRestored()) {
0131             return false;
0132         }
0133         if (view->browserWindow()->tabWidget()->tabBar()->normalTabsCount() < 2) {
0134             return false;
0135         }
0136         WebPage *page = view->page();
0137         if (page->history()->count() != 0) {
0138             return false;
0139         }
0140         Q_UNUSED(url)
0141         return true;
0142     };
0143 
0144     if (!item->page()) {
0145         return;
0146     }
0147     auto *page = qobject_cast<WebPage*>(item->page());
0148     if (!page) {
0149         return;
0150     }
0151     auto *view = qobject_cast<TabbedWebView*>(page->view());
0152     if (!view) {
0153         return;
0154     }
0155     if (testWebView(view, QUrl())) {
0156         view->closeView();
0157     }
0158 }
0159 
0160 QWinTaskbarButton *DownloadManager::taskbarButton()
0161 {
0162 #ifdef Q_OS_WIN
0163     if (!m_taskbarButton) {
0164         BrowserWindow *window = mApp->getWindow();
0165         m_taskbarButton = new QWinTaskbarButton(window ? window->windowHandle() : windowHandle());
0166         m_taskbarButton->progress()->setRange(0, 100);
0167     }
0168     return m_taskbarButton;
0169 #else
0170     return nullptr;
0171 #endif
0172 }
0173 
0174 void DownloadManager::startExternalManager(const QUrl &url)
0175 {
0176     QString arguments = m_externalArguments;
0177     arguments.replace(QLatin1String("%d"), QString::fromUtf8(url.toEncoded()));
0178 
0179     QzTools::startExternalProcess(m_externalExecutable, arguments);
0180     m_lastDownloadOption = ExternalManager;
0181 }
0182 
0183 void DownloadManager::timerEvent(QTimerEvent* e)
0184 {
0185     QVector<QTime> remTimes;
0186     QVector<int> progresses;
0187     QVector<double> speeds;
0188 
0189     if (e->timerId() == m_timer.timerId()) {
0190         if (!ui->list->count()) {
0191             ui->speedLabel->clear();
0192             setWindowTitle(tr("Download Manager"));
0193 #ifdef Q_OS_WIN
0194             taskbarButton()->progress()->hide();
0195 #endif
0196             return;
0197         }
0198         for (int i = 0; i < ui->list->count(); i++) {
0199             auto* downItem = qobject_cast<DownloadItem*>(ui->list->itemWidget(ui->list->item(i)));
0200             if (!downItem || downItem->isCancelled() || !downItem->isDownloading()) {
0201                 continue;
0202             }
0203             progresses.append(downItem->progress());
0204             remTimes.append(downItem->remainingTime());
0205             speeds.append(downItem->currentSpeed());
0206         }
0207         if (remTimes.isEmpty()) {
0208             return;
0209         }
0210 
0211         QTime remaining;
0212         for (const QTime &time : std::as_const(remTimes)) {
0213             if (time > remaining) {
0214                 remaining = time;
0215             }
0216         }
0217 
0218         int progress = 0;
0219         for (int prog : std::as_const(progresses)) {
0220             progress += prog;
0221         }
0222         progress = progress / progresses.count();
0223 
0224         double speed = 0.00;
0225         for (double spee : std::as_const(speeds)) {
0226             speed += spee;
0227         }
0228 
0229 #ifndef Q_OS_WIN
0230         ui->speedLabel->setText(tr("%1% of %2 files (%3) %4 remaining").arg(QString::number(progress), QString::number(progresses.count()),
0231                                 DownloadItem::currentSpeedToString(speed),
0232                                 DownloadItem::remaingTimeToString(remaining)));
0233 #endif
0234         setWindowTitle(tr("%1% - Download Manager").arg(progress));
0235 #ifdef Q_OS_WIN
0236         taskbarButton()->progress()->show();
0237         taskbarButton()->progress()->setValue(progress);
0238 #endif
0239     }
0240 
0241     QWidget::timerEvent(e);
0242 }
0243 
0244 void DownloadManager::clearList()
0245 {
0246     QList<DownloadItem*> items;
0247     for (int i = 0; i < ui->list->count(); i++) {
0248         auto* downItem = qobject_cast<DownloadItem*>(ui->list->itemWidget(ui->list->item(i)));
0249         if (!downItem) {
0250             continue;
0251         }
0252         if (downItem->isDownloading()) {
0253             continue;
0254         }
0255         items.append(downItem);
0256     }
0257     qDeleteAll(items);
0258     Q_EMIT downloadsCountChanged();
0259 }
0260 
0261 void DownloadManager::download(QWebEngineDownloadRequest *downloadItem)
0262 {
0263     QElapsedTimer downloadTimer;
0264     downloadTimer.start();
0265 
0266     closeDownloadTab(downloadItem);
0267 
0268     QString downloadPath;
0269     bool openFile = false;
0270 
0271     const QString fileName = downloadItem->downloadFileName();
0272 
0273     const bool forceAsk = downloadItem->savePageFormat() != QWebEngineDownloadRequest::UnknownSaveFormat
0274             || downloadItem->isSavePageDownload();
0275 
0276     if (m_useExternalManager) {
0277         startExternalManager(downloadItem->url());
0278     } else if (forceAsk || m_downloadPath.isEmpty()) {
0279         enum Result { Open = 1, Save = 2, ExternalManager = 3, SavePage = 4, Unknown = 0 };
0280         Result result = Unknown;
0281 
0282         if (downloadItem->savePageFormat() != QWebEngineDownloadRequest::UnknownSaveFormat) {
0283             // Save Page requested
0284             result = SavePage;
0285         } else if (downloadItem->isSavePageDownload()) {
0286             // Save x as... requested
0287             result = Save;
0288         } else {
0289             // Ask what to do
0290             DownloadOptionsDialog optionsDialog(fileName, downloadItem, mApp->activeWindow());
0291             optionsDialog.showExternalManagerOption(m_useExternalManager);
0292             optionsDialog.setLastDownloadOption(m_lastDownloadOption);
0293             result = Result(optionsDialog.exec());
0294         }
0295 
0296         switch (result) {
0297         case Open:
0298             openFile = true;
0299             downloadPath = QzTools::ensureUniqueFilename(DataPaths::path(DataPaths::Temp) + QLatin1Char('/') + fileName);
0300             m_lastDownloadOption = OpenFile;
0301             break;
0302 
0303         case Save:
0304             downloadPath = QFileDialog::getSaveFileName(mApp->activeWindow(), tr("Save file as..."), m_lastDownloadPath + QLatin1Char('/') + fileName);
0305 
0306             if (!downloadPath.isEmpty()) {
0307                 m_lastDownloadPath = QFileInfo(downloadPath).absolutePath();
0308                 Settings().setValue(QSL("DownloadManager/lastDownloadPath"), m_lastDownloadPath);
0309                 m_lastDownloadOption = SaveFile;
0310             }
0311             break;
0312 
0313         case SavePage: {
0314             const QString mhtml = tr("MIME HTML Archive (*.mhtml)");
0315             const QString htmlSingle = tr("HTML Page, single (*.html)");
0316             const QString htmlComplete = tr("HTML Page, complete (*.html)");
0317             const QString filter = QStringLiteral("%1;;%2;;%3").arg(mhtml, htmlSingle, htmlComplete);
0318 
0319             QString selectedFilter;
0320             downloadPath = QFileDialog::getSaveFileName(mApp->activeWindow(), tr("Save page as..."),
0321                                                         m_lastDownloadPath + QLatin1Char('/') + fileName,
0322                                                         filter, &selectedFilter);
0323 
0324             if (!downloadPath.isEmpty()) {
0325                 m_lastDownloadPath = QFileInfo(downloadPath).absolutePath();
0326                 Settings().setValue(QSL("DownloadManager/lastDownloadPath"), m_lastDownloadPath);
0327                 m_lastDownloadOption = SaveFile;
0328 
0329                 QWebEngineDownloadRequest::SavePageFormat format = QWebEngineDownloadRequest::UnknownSaveFormat;
0330 
0331                 if (selectedFilter == mhtml) {
0332                     format = QWebEngineDownloadRequest::MimeHtmlSaveFormat;
0333                 } else if (selectedFilter == htmlSingle) {
0334                     format = QWebEngineDownloadRequest::SingleHtmlSaveFormat;
0335                 } else if (selectedFilter == htmlComplete) {
0336                     format = QWebEngineDownloadRequest::CompleteHtmlSaveFormat;
0337                 }
0338 
0339                 if (format != QWebEngineDownloadRequest::UnknownSaveFormat) {
0340                     downloadItem->setSavePageFormat(format);
0341                 }
0342             }
0343             break;
0344         }
0345 
0346         case ExternalManager:
0347             startExternalManager(downloadItem->url());
0348             // fallthrough
0349 
0350         default:
0351             downloadItem->cancel();
0352             return;
0353         }
0354     } else {
0355         downloadPath = QzTools::ensureUniqueFilename(m_downloadPath + QL1C('/') + fileName);
0356     }
0357 
0358     if (downloadPath.isEmpty()) {
0359         downloadItem->cancel();
0360         return;
0361     }
0362 
0363     // Set download path and accept
0364     downloadItem->setDownloadDirectory(QFileInfo(downloadPath).absoluteDir().absolutePath());
0365     downloadItem->setDownloadFileName(QFileInfo(downloadPath).fileName());
0366     downloadItem->accept();
0367 
0368     // Create download item
0369     auto* listItem = new QListWidgetItem(ui->list);
0370     auto* downItem = new DownloadItem(listItem, downloadItem, QFileInfo(downloadPath).absolutePath(), QFileInfo(downloadPath).fileName(), openFile, this);
0371     downItem->setDownTimer(downloadTimer);
0372     downItem->startDownloading();
0373     connect(downItem, &DownloadItem::deleteItem, m_model, &DownloadManagerModel::removeDownload);
0374     connect(downItem, &DownloadItem::downloadFinished, this, QOverload<bool>::of(&DownloadManager::downloadFinished));
0375     connect(downItem, &DownloadItem::downloadFinished, this, QOverload<>::of(&DownloadManager::downloadFinished));
0376     m_model->addDownload(downItem);
0377     ui->list->setItemWidget(listItem, downItem);
0378     listItem->setSizeHint(downItem->sizeHint());
0379     downItem->show();
0380 
0381     m_activeDownloadsCount++;
0382     Q_EMIT downloadsCountChanged();
0383 }
0384 
0385 int DownloadManager::downloadsCount() const
0386 {
0387     return m_model->count();
0388 }
0389 
0390 int DownloadManager::activeDownloadsCount() const
0391 {
0392     return m_activeDownloadsCount;
0393 }
0394 
0395 void DownloadManager::downloadFinished(bool success)
0396 {
0397     m_activeDownloadsCount = 0;
0398     bool downloadingAllFilesFinished = true;
0399     for (int i = 0; i < ui->list->count(); i++) {
0400         auto* downItem = qobject_cast<DownloadItem*>(ui->list->itemWidget(ui->list->item(i)));
0401         if (!downItem) {
0402             continue;
0403         }
0404         if (downItem->isDownloading()) {
0405             m_activeDownloadsCount++;
0406         }
0407         if (downItem->isCancelled() || !downItem->isDownloading()) {
0408             continue;
0409         }
0410         downloadingAllFilesFinished = false;
0411     }
0412 
0413     Q_EMIT downloadsCountChanged();
0414 
0415     if (downloadingAllFilesFinished) {
0416         if (success && qApp->activeWindow() != this) {
0417             mApp->desktopNotifications()->showNotification(QIcon::fromTheme(QSL("download"), QIcon(QSL(":icons/other/download.svg"))).pixmap(48), tr("Falkon: Download Finished"), tr("All files have been successfully downloaded."));
0418             if (!m_closeOnFinish) {
0419                 raise();
0420                 activateWindow();
0421             }
0422         }
0423         ui->speedLabel->clear();
0424         setWindowTitle(tr("Download Manager"));
0425 #ifdef Q_OS_WIN
0426         taskbarButton()->progress()->hide();
0427 #endif
0428         if (m_closeOnFinish) {
0429             close();
0430         }
0431     }
0432 }
0433 
0434 bool DownloadManager::canClose()
0435 {
0436     if (m_isClosing) {
0437         return true;
0438     }
0439 
0440     bool isDownloading = false;
0441     for (int i = 0; i < ui->list->count(); i++) {
0442         auto* downItem = qobject_cast<DownloadItem*>(ui->list->itemWidget(ui->list->item(i)));
0443         if (!downItem) {
0444             continue;
0445         }
0446         if (downItem->isDownloading()) {
0447             isDownloading = true;
0448             break;
0449         }
0450     }
0451 
0452     return !isDownloading;
0453 }
0454 
0455 bool DownloadManager::useExternalManager() const
0456 {
0457     return m_useExternalManager;
0458 }
0459 
0460 void DownloadManager::closeEvent(QCloseEvent* e)
0461 {
0462     if (mApp->windowCount() == 0) { // No main windows -> we are going to quit
0463         if (!canClose()) {
0464             QMessageBox::StandardButton button = QMessageBox::warning(this, tr("Warning"),
0465                                                  tr("Are you sure you want to quit? All uncompleted downloads will be cancelled!"), QMessageBox::Yes | QMessageBox::No);
0466             if (button != QMessageBox::Yes) {
0467                 e->ignore();
0468                 return;
0469             }
0470             m_isClosing = true;
0471         }
0472         mApp->quitApplication();
0473     }
0474     e->accept();
0475 }
0476 
0477 DownloadManager::~DownloadManager()
0478 {
0479     delete ui;
0480 }
0481