Falkon Develop
Cross-platform Qt-based web browser
downloadmanager.cpp
Go to the documentation of this file.
1/* ============================================================
2* Falkon - Qt web browser
3* Copyright (C) 2010-2018 David Rosca <nowrep@gmail.com>
4*
5* This program is free software: you can redistribute it and/or modify
6* it under the terms of the GNU General Public License as published by
7* the Free Software Foundation, either version 3 of the License, or
8* (at your option) any later version.
9*
10* This program is distributed in the hope that it will be useful,
11* but WITHOUT ANY WARRANTY; without even the implied warranty of
12* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13* GNU General Public License for more details.
14*
15* You should have received a copy of the GNU General Public License
16* along with this program. If not, see <http://www.gnu.org/licenses/>.
17* ============================================================ */
18#include "downloadmanager.h"
19#include "ui_downloadmanager.h"
20#include "browserwindow.h"
21#include "mainapplication.h"
23#include "downloaditem.h"
25#include "networkmanager.h"
27#include "qztools.h"
28#include "webpage.h"
29#include "webview.h"
30#include "settings.h"
31#include "datapaths.h"
32#include "tabwidget.h"
33#include "tabbedwebview.h"
34#include "tabbar.h"
35#include "locationbar.h"
36
37#include <QMessageBox>
38#include <QCloseEvent>
39#include <QDir>
40#include <QShortcut>
41#include <QStandardPaths>
42#include <QWebEngineHistory>
43#include <QWebEngineDownloadRequest>
44#include <QtWebEngineWidgetsVersion>
45
46#ifdef Q_OS_WIN
47#include <QtWin>
48#include <QWindow>
49#include <QWinTaskbarButton>
50#include <QWinTaskbarProgress>
51#endif
52
54 : QWidget(parent)
55 , ui(new Ui::DownloadManager)
56 , m_model(new DownloadManagerModel(this))
57 , m_isClosing(false)
58 , m_lastDownloadOption(NoOption)
59{
60 setWindowFlags(windowFlags() ^ Qt::WindowMaximizeButtonHint);
61 ui->setupUi(this);
62#ifdef Q_OS_WIN
63 if (QtWin::isCompositionEnabled()) {
64 QtWin::extendFrameIntoClientArea(this, -1, -1, -1, -1);
65 }
66#endif
67 ui->clearButton->setIcon(QIcon::fromTheme(QSL("edit-clear")));
69
70 connect(ui->clearButton, &QAbstractButton::clicked, this, &DownloadManager::clearList);
71
72 auto* clearShortcut = new QShortcut(QKeySequence(QSL("CTRL+L")), this);
73 connect(clearShortcut, &QShortcut::activated, this, &DownloadManager::clearList);
74
76
77 QzTools::setWmClass(QSL("Download Manager"), this);
78
80}
81
83{
84 Settings settings;
85 settings.beginGroup(QSL("DownloadManager"));
86 m_downloadPath = settings.value(QSL("defaultDownloadPath"), QString()).toString();
87 m_lastDownloadPath = settings.value(QSL("lastDownloadPath"), QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)).toString();
88 m_closeOnFinish = settings.value(QSL("CloseManagerOnFinish"), false).toBool();
89 m_useNativeDialog = settings.value(QSL("useNativeDialog"), DEFAULT_DOWNLOAD_USE_NATIVE_DIALOG).toBool();
90
91 m_useExternalManager = settings.value(QSL("UseExternalManager"), false).toBool();
92 m_externalExecutable = settings.value(QSL("ExternalManagerExecutable"), QString()).toString();
93 m_externalArguments = settings.value(QSL("ExternalManagerArguments"), QString()).toString();
94 settings.endGroup();
95
96 if (!m_externalArguments.contains(QLatin1String("%d"))) {
97 m_externalArguments.append(QLatin1String(" %d"));
98 }
99}
100
102{
103 m_timer.start(500, this);
104
105 QWidget::show();
106 raise();
107 activateWindow();
108}
109
110void DownloadManager::resizeEvent(QResizeEvent* e)
111{
112 QWidget::resizeEvent(e);
113 Q_EMIT resized(size());
114}
115
116void DownloadManager::keyPressEvent(QKeyEvent* e)
117{
118 if (e->key() == Qt::Key_Escape
119 || (e->key() == Qt::Key_W && e->modifiers() == Qt::ControlModifier)) {
120 close();
121 }
122
123 QWidget::keyPressEvent(e);
124}
125
126void DownloadManager::closeDownloadTab(QWebEngineDownloadRequest *item) const
127{
128 // Attempt to close empty tab that was opened only for loading the download url
129 auto testWebView = [](TabbedWebView *view, const QUrl &url) {
130 if (!view->webTab()->isRestored()) {
131 return false;
132 }
133 if (view->browserWindow()->tabWidget()->tabBar()->normalTabsCount() < 2) {
134 return false;
135 }
136 WebPage *page = view->page();
137 if (page->history()->count() != 0) {
138 return false;
139 }
140 Q_UNUSED(url)
141 return true;
142 };
143
144 if (!item->page()) {
145 return;
146 }
147 auto *page = qobject_cast<WebPage*>(item->page());
148 if (!page) {
149 return;
150 }
151 auto *view = qobject_cast<TabbedWebView*>(page->view());
152 if (!view) {
153 return;
154 }
155 if (testWebView(view, QUrl())) {
156 view->closeView();
157 }
158}
159
160QWinTaskbarButton *DownloadManager::taskbarButton()
161{
162#ifdef Q_OS_WIN
163 if (!m_taskbarButton) {
164 BrowserWindow *window = mApp->getWindow();
165 m_taskbarButton = new QWinTaskbarButton(window ? window->windowHandle() : windowHandle());
166 m_taskbarButton->progress()->setRange(0, 100);
167 }
168 return m_taskbarButton;
169#else
170 return nullptr;
171#endif
172}
173
175{
176 QString arguments = m_externalArguments;
177 arguments.replace(QLatin1String("%d"), QString::fromUtf8(url.toEncoded()));
178
179 QzTools::startExternalProcess(m_externalExecutable, arguments);
180 m_lastDownloadOption = ExternalManager;
181}
182
183void DownloadManager::timerEvent(QTimerEvent* e)
184{
185 QVector<QTime> remTimes;
186 QVector<int> progresses;
187 QVector<double> speeds;
188
189 if (e->timerId() == m_timer.timerId()) {
190 if (!ui->list->count()) {
191 ui->speedLabel->clear();
192 setWindowTitle(tr("Download Manager"));
193#ifdef Q_OS_WIN
194 taskbarButton()->progress()->hide();
195#endif
196 return;
197 }
198 for (int i = 0; i < ui->list->count(); i++) {
199 auto* downItem = qobject_cast<DownloadItem*>(ui->list->itemWidget(ui->list->item(i)));
200 if (!downItem || downItem->isCancelled() || !downItem->isDownloading()) {
201 continue;
202 }
203 progresses.append(downItem->progress());
204 remTimes.append(downItem->remainingTime());
205 speeds.append(downItem->currentSpeed());
206 }
207 if (remTimes.isEmpty()) {
208 return;
209 }
210
211 QTime remaining;
212 for (const QTime &time : std::as_const(remTimes)) {
213 if (time > remaining) {
214 remaining = time;
215 }
216 }
217
218 int progress = 0;
219 for (int prog : std::as_const(progresses)) {
220 progress += prog;
221 }
222 progress = progress / progresses.count();
223
224 double speed = 0.00;
225 for (double spee : std::as_const(speeds)) {
226 speed += spee;
227 }
228
229#ifndef Q_OS_WIN
230 ui->speedLabel->setText(tr("%1% of %2 files (%3) %4 remaining").arg(QString::number(progress), QString::number(progresses.count()),
233#endif
234 setWindowTitle(tr("%1% - Download Manager").arg(progress));
235#ifdef Q_OS_WIN
236 taskbarButton()->progress()->show();
237 taskbarButton()->progress()->setValue(progress);
238#endif
239 }
240
241 QWidget::timerEvent(e);
242}
243
244void DownloadManager::clearList()
245{
246 QList<DownloadItem*> items;
247 for (int i = 0; i < ui->list->count(); i++) {
248 auto* downItem = qobject_cast<DownloadItem*>(ui->list->itemWidget(ui->list->item(i)));
249 if (!downItem) {
250 continue;
251 }
252 if (downItem->isDownloading()) {
253 continue;
254 }
255 items.append(downItem);
256 }
257 qDeleteAll(items);
258 Q_EMIT downloadsCountChanged();
259}
260
261void DownloadManager::download(QWebEngineDownloadRequest *downloadItem)
262{
263 QElapsedTimer downloadTimer;
264 downloadTimer.start();
265
266 closeDownloadTab(downloadItem);
267
268 QString downloadPath;
269 bool openFile = false;
270
271 const QString fileName = downloadItem->downloadFileName();
272
273 const bool forceAsk = downloadItem->savePageFormat() != QWebEngineDownloadRequest::UnknownSaveFormat
274 || downloadItem->isSavePageDownload();
275
276 if (m_useExternalManager) {
277 startExternalManager(downloadItem->url());
278 } else if (forceAsk || m_downloadPath.isEmpty()) {
279 enum Result { Open = 1, Save = 2, ExternalManager = 3, SavePage = 4, Unknown = 0 };
280 Result result = Unknown;
281
282 if (downloadItem->savePageFormat() != QWebEngineDownloadRequest::UnknownSaveFormat) {
283 // Save Page requested
284 result = SavePage;
285 } else if (downloadItem->isSavePageDownload()) {
286 // Save x as... requested
287 result = Save;
288 } else {
289 // Ask what to do
290 DownloadOptionsDialog optionsDialog(fileName, downloadItem, mApp->activeWindow());
291 optionsDialog.showExternalManagerOption(m_useExternalManager);
292 optionsDialog.setLastDownloadOption(m_lastDownloadOption);
293 result = Result(optionsDialog.exec());
294 }
295
296 switch (result) {
297 case Open:
298 openFile = true;
299 downloadPath = QzTools::ensureUniqueFilename(DataPaths::path(DataPaths::Temp) + QLatin1Char('/') + fileName);
300 m_lastDownloadOption = OpenFile;
301 break;
302
303 case Save:
304 downloadPath = QFileDialog::getSaveFileName(mApp->activeWindow(), tr("Save file as..."), m_lastDownloadPath + QLatin1Char('/') + fileName);
305
306 if (!downloadPath.isEmpty()) {
307 m_lastDownloadPath = QFileInfo(downloadPath).absolutePath();
308 Settings().setValue(QSL("DownloadManager/lastDownloadPath"), m_lastDownloadPath);
309 m_lastDownloadOption = SaveFile;
310 }
311 break;
312
313 case SavePage: {
314 const QString mhtml = tr("MIME HTML Archive (*.mhtml)");
315 const QString htmlSingle = tr("HTML Page, single (*.html)");
316 const QString htmlComplete = tr("HTML Page, complete (*.html)");
317 const QString filter = QStringLiteral("%1;;%2;;%3").arg(mhtml, htmlSingle, htmlComplete);
318
319 QString selectedFilter;
320 downloadPath = QFileDialog::getSaveFileName(mApp->activeWindow(), tr("Save page as..."),
321 m_lastDownloadPath + QLatin1Char('/') + fileName,
322 filter, &selectedFilter);
323
324 if (!downloadPath.isEmpty()) {
325 m_lastDownloadPath = QFileInfo(downloadPath).absolutePath();
326 Settings().setValue(QSL("DownloadManager/lastDownloadPath"), m_lastDownloadPath);
327 m_lastDownloadOption = SaveFile;
328
329 QWebEngineDownloadRequest::SavePageFormat format = QWebEngineDownloadRequest::UnknownSaveFormat;
330
331 if (selectedFilter == mhtml) {
332 format = QWebEngineDownloadRequest::MimeHtmlSaveFormat;
333 } else if (selectedFilter == htmlSingle) {
334 format = QWebEngineDownloadRequest::SingleHtmlSaveFormat;
335 } else if (selectedFilter == htmlComplete) {
336 format = QWebEngineDownloadRequest::CompleteHtmlSaveFormat;
337 }
338
339 if (format != QWebEngineDownloadRequest::UnknownSaveFormat) {
340 downloadItem->setSavePageFormat(format);
341 }
342 }
343 break;
344 }
345
346 case ExternalManager:
347 startExternalManager(downloadItem->url());
348 // fallthrough
349
350 default:
351 downloadItem->cancel();
352 return;
353 }
354 } else {
355 downloadPath = QzTools::ensureUniqueFilename(m_downloadPath + QL1C('/') + fileName);
356 }
357
358 if (downloadPath.isEmpty()) {
359 downloadItem->cancel();
360 return;
361 }
362
363 // Set download path and accept
364 downloadItem->setDownloadDirectory(QFileInfo(downloadPath).absoluteDir().absolutePath());
365 downloadItem->setDownloadFileName(QFileInfo(downloadPath).fileName());
366 downloadItem->accept();
367
368 // Create download item
369 auto* listItem = new QListWidgetItem(ui->list);
370 auto* downItem = new DownloadItem(listItem, downloadItem, QFileInfo(downloadPath).absolutePath(), QFileInfo(downloadPath).fileName(), openFile, this);
371 downItem->setDownTimer(downloadTimer);
372 downItem->startDownloading();
374 connect(downItem, &DownloadItem::downloadFinished, this, QOverload<bool>::of(&DownloadManager::downloadFinished));
375 connect(downItem, &DownloadItem::downloadFinished, this, QOverload<>::of(&DownloadManager::downloadFinished));
376 m_model->addDownload(downItem);
377 ui->list->setItemWidget(listItem, downItem);
378 listItem->setSizeHint(downItem->sizeHint());
379 downItem->show();
380
381 m_activeDownloadsCount++;
382 Q_EMIT downloadsCountChanged();
383}
384
386{
387 return m_model->count();
388}
389
391{
392 return m_activeDownloadsCount;
393}
394
395void DownloadManager::downloadFinished(bool success)
396{
397 m_activeDownloadsCount = 0;
398 bool downloadingAllFilesFinished = true;
399 for (int i = 0; i < ui->list->count(); i++) {
400 auto* downItem = qobject_cast<DownloadItem*>(ui->list->itemWidget(ui->list->item(i)));
401 if (!downItem) {
402 continue;
403 }
404 if (downItem->isDownloading()) {
405 m_activeDownloadsCount++;
406 }
407 if (downItem->isCancelled() || !downItem->isDownloading()) {
408 continue;
409 }
410 downloadingAllFilesFinished = false;
411 }
412
413 Q_EMIT downloadsCountChanged();
414
415 if (downloadingAllFilesFinished) {
416 if (success && qApp->activeWindow() != this) {
417 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."));
418 if (!m_closeOnFinish) {
419 raise();
420 activateWindow();
421 }
422 }
423 ui->speedLabel->clear();
424 setWindowTitle(tr("Download Manager"));
425#ifdef Q_OS_WIN
426 taskbarButton()->progress()->hide();
427#endif
428 if (m_closeOnFinish) {
429 close();
430 }
431 }
432}
433
435{
436 if (m_isClosing) {
437 return true;
438 }
439
440 bool isDownloading = false;
441 for (int i = 0; i < ui->list->count(); i++) {
442 auto* downItem = qobject_cast<DownloadItem*>(ui->list->itemWidget(ui->list->item(i)));
443 if (!downItem) {
444 continue;
445 }
446 if (downItem->isDownloading()) {
447 isDownloading = true;
448 break;
449 }
450 }
451
452 return !isDownloading;
453}
454
456{
457 return m_useExternalManager;
458}
459
460void DownloadManager::closeEvent(QCloseEvent* e)
461{
462 if (mApp->windowCount() == 0) { // No main windows -> we are going to quit
463 if (!canClose()) {
464 QMessageBox::StandardButton button = QMessageBox::warning(this, tr("Warning"),
465 tr("Are you sure you want to quit? All uncompleted downloads will be cancelled!"), QMessageBox::Yes | QMessageBox::No);
466 if (button != QMessageBox::Yes) {
467 e->ignore();
468 return;
469 }
470 m_isClosing = true;
471 }
472 mApp->quitApplication();
473 }
474 e->accept();
475}
476
478{
479 delete ui;
480}
481
TabWidget * tabWidget() const
int normalTabsCount() const
static QString path(Path type)
Definition: datapaths.cpp:66
static QString remaingTimeToString(QTime time)
static QString currentSpeedToString(double speed)
void downloadFinished(bool success)
void deleteItem(DownloadItem *)
void download(QWebEngineDownloadRequest *downloadItem)
void startExternalManager(const QUrl &url)
void downloadFinished()
void resized(QSize)
int activeDownloadsCount() const
bool useExternalManager() const
int downloadsCount() const
DownloadManager(QWidget *parent=nullptr)
void downloadAdded(DownloadItem *item)
void downloadsCountChanged()
~DownloadManager() override
void removeDownload(DownloadItem *item)
void downloadAdded(DownloadItem *item)
void addDownload(DownloadItem *item)
void showExternalManagerOption(bool show)
void setLastDownloadOption(const DownloadManager::DownloadOption &option)
static void setWmClass(const QString &name, const QWidget *widget)
Definition: qztools.cpp:874
static QString ensureUniqueFilename(const QString &name, const QString &appendFormat=QSL("(%1)"))
Definition: qztools.cpp:257
static bool startExternalProcess(const QString &executable, const QString &args)
Definition: qztools.cpp:846
static void centerWidgetOnScreen(QWidget *w)
Definition: qztools.cpp:116
void beginGroup(const QString &prefix)
Definition: settings.cpp:79
void endGroup()
Definition: settings.cpp:84
QVariant value(const QString &key, const QVariant &defaultValue=QVariant())
Definition: settings.cpp:74
void setValue(const QString &key, const QVariant &defaultValue=QVariant())
Definition: settings.cpp:69
TabBar * tabBar() const
Definition: tabwidget.cpp:580
void closeView() override
BrowserWindow * browserWindow() const
WebTab * webTab() const
WebView * view() const
Definition: webpage.cpp:140
bool isRestored() const
Definition: webtab.cpp:548
WebPage * page() const
Definition: webview.cpp:132
#define mApp
i
Definition: i18n.py:23
#define QL1C(x)
Definition: qzcommon.h:48
#define DEFAULT_DOWNLOAD_USE_NATIVE_DIALOG
Definition: qzcommon.h:137
#define QSL(x)
Definition: qzcommon.h:40