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