1 /**************************************************************************
2 * Otter Browser: Web browser controlled by the user, not vice-versa.
3 * Copyright (C) 2013 - 2018 Michal Dutkiewicz aka Emdek <michal@emdek.pl>
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 **************************************************************************/
19 
20 #include "TransfersManager.h"
21 #include "Application.h"
22 #include "NetworkManager.h"
23 #include "NetworkManagerFactory.h"
24 #include "NotificationsManager.h"
25 #include "SessionsManager.h"
26 #include "Utils.h"
27 #include "../ui/MainWindow.h"
28 
29 #include <QtCore/QDir>
30 #include <QtCore/QMimeDatabase>
31 #include <QtCore/QRegularExpression>
32 #include <QtCore/QStandardPaths>
33 #include <QtCore/QTemporaryFile>
34 #include <QtCore/QTimer>
35 #include <QtNetwork/QAbstractNetworkCache>
36 #include <QtWidgets/QMessageBox>
37 
38 namespace Otter
39 {
40 
41 TransfersManager* TransfersManager::m_instance(nullptr);
42 QVector<Transfer*> TransfersManager::m_transfers;
43 QVector<Transfer*> TransfersManager::m_privateTransfers;
44 bool TransfersManager::m_isInitilized(false);
45 bool TransfersManager::m_hasRunningTransfers(false);
46 
Transfer(TransferOptions options,QObject * parent)47 Transfer::Transfer(TransferOptions options, QObject *parent) : QObject(parent ? parent : TransfersManager::getInstance()),
48 	m_reply(nullptr),
49 	m_device(nullptr),
50 	m_speed(0),
51 	m_bytesStart(0),
52 	m_bytesReceivedDifference(0),
53 	m_bytesReceived(0),
54 	m_bytesTotal(0),
55 	m_options(options),
56 	m_state(UnknownState),
57 	m_updateTimer(0),
58 	m_updateInterval(0),
59 	m_remainingTime(-1),
60 	m_isSelectingPath(false),
61 	m_isArchived(false)
62 {
63 }
64 
Transfer(const QSettings & settings,QObject * parent)65 Transfer::Transfer(const QSettings &settings, QObject *parent) : QObject(parent ? parent : TransfersManager::getInstance()),
66 	m_reply(nullptr),
67 	m_device(nullptr),
68 	m_source(settings.value(QLatin1String("source")).toUrl()),
69 	m_target(settings.value(QLatin1String("target")).toString()),
70 	m_timeStarted(settings.value(QLatin1String("timeStarted")).toDateTime()),
71 	m_timeFinished(settings.value(QLatin1String("timeFinished")).toDateTime()),
72 	m_mimeType(QMimeDatabase().mimeTypeForFile(m_target)),
73 	m_speed(0),
74 	m_bytesStart(0),
75 	m_bytesReceivedDifference(0),
76 	m_bytesReceived(settings.value(QLatin1String("bytesReceived")).toLongLong()),
77 	m_bytesTotal(settings.value(QLatin1String("bytesTotal")).toLongLong()),
78 	m_options(NoOption),
79 	m_state((m_bytesReceived > 0 && m_bytesTotal == m_bytesReceived && QFile::exists(settings.value(QLatin1String("target")).toString())) ? FinishedState : ErrorState),
80 	m_updateTimer(0),
81 	m_updateInterval(0),
82 	m_remainingTime(-1),
83 	m_isSelectingPath(false),
84 	m_isArchived(true)
85 {
86 	m_timeStarted.setTimeSpec(Qt::UTC);
87 	m_timeFinished.setTimeSpec(Qt::UTC);
88 }
89 
Transfer(const QUrl & source,const QString & target,TransferOptions options,QObject * parent)90 Transfer::Transfer(const QUrl &source, const QString &target, TransferOptions options, QObject *parent) : QObject(parent ? parent : TransfersManager::getInstance()),
91 	m_reply(nullptr),
92 	m_device(nullptr),
93 	m_source(source),
94 	m_target(target),
95 	m_speed(0),
96 	m_bytesStart(0),
97 	m_bytesReceivedDifference(0),
98 	m_bytesReceived(0),
99 	m_bytesTotal(0),
100 	m_options(options),
101 	m_state(UnknownState),
102 	m_updateTimer(0),
103 	m_updateInterval(0),
104 	m_remainingTime(-1),
105 	m_isSelectingPath(false),
106 	m_isArchived(false)
107 {
108 	QNetworkRequest request;
109 	request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
110 	request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
111 	request.setHeader(QNetworkRequest::UserAgentHeader, NetworkManagerFactory::getUserAgent());
112 	request.setUrl(QUrl(source));
113 
114 	start(NetworkManagerFactory::getNetworkManager(m_options.testFlag(IsPrivateOption))->get(request), target);
115 }
116 
Transfer(const QNetworkRequest & request,const QString & target,TransferOptions options,QObject * parent)117 Transfer::Transfer(const QNetworkRequest &request, const QString &target, TransferOptions options, QObject *parent) : QObject(parent ? parent : TransfersManager::getInstance()),
118 	m_reply(nullptr),
119 	m_device(nullptr),
120 	m_source(request.url()),
121 	m_target(target),
122 	m_speed(0),
123 	m_bytesStart(0),
124 	m_bytesReceivedDifference(0),
125 	m_bytesReceived(0),
126 	m_bytesTotal(0),
127 	m_options(options),
128 	m_state(UnknownState),
129 	m_updateTimer(0),
130 	m_updateInterval(0),
131 	m_remainingTime(-1),
132 	m_isSelectingPath(false),
133 	m_isArchived(false)
134 {
135 	start(NetworkManagerFactory::getNetworkManager(m_options.testFlag(IsPrivateOption))->get(request), target);
136 }
137 
Transfer(QNetworkReply * reply,const QString & target,TransferOptions options,QObject * parent)138 Transfer::Transfer(QNetworkReply *reply, const QString &target, TransferOptions options, QObject *parent) : QObject(parent ? parent : TransfersManager::getInstance()),
139 	m_reply(reply),
140 	m_source((m_reply->url().isValid() ? m_reply->url() : m_reply->request().url()).adjusted(QUrl::RemovePassword | QUrl::PreferLocalFile)),
141 	m_target(target),
142 	m_speed(0),
143 	m_bytesStart(0),
144 	m_bytesReceivedDifference(0),
145 	m_bytesReceived(0),
146 	m_bytesTotal(0),
147 	m_options(options),
148 	m_state(UnknownState),
149 	m_updateTimer(0),
150 	m_updateInterval(0),
151 	m_remainingTime(-1),
152 	m_isSelectingPath(false),
153 	m_isArchived(false)
154 {
155 	start(reply, target);
156 }
157 
~Transfer()158 Transfer::~Transfer()
159 {
160 	if (m_options.testFlag(HasToOpenAfterFinishOption) && QFile::exists(m_target))
161 	{
162 		QFile::remove(m_target);
163 	}
164 }
165 
timerEvent(QTimerEvent * event)166 void Transfer::timerEvent(QTimerEvent *event)
167 {
168 	if (event->timerId() == m_updateTimer)
169 	{
170 		const qint64 oldSpeed(m_speed);
171 
172 		m_speed = (m_bytesReceivedDifference * 2);
173 		m_bytesReceivedDifference = 0;
174 
175 		if (m_speed != oldSpeed)
176 		{
177 			m_speeds.enqueue(m_speed);
178 
179 			if (m_speeds.count() > 10)
180 			{
181 				m_speeds.dequeue();
182 			}
183 
184 			if (m_bytesTotal > 0)
185 			{
186 				qint64 speedSum(0);
187 
188 				for (int i = 0; i < m_speeds.count(); ++i)
189 				{
190 					speedSum += m_speeds.at(i);
191 				}
192 
193 				speedSum /= m_speeds.count();
194 
195 				m_remainingTime = qRound(static_cast<qreal>(m_bytesTotal - m_bytesReceived) / static_cast<qreal>(speedSum));
196 			}
197 
198 			emit changed();
199 		}
200 	}
201 }
202 
start(QNetworkReply * reply,const QString & target)203 void Transfer::start(QNetworkReply *reply, const QString &target)
204 {
205 	if (!reply)
206 	{
207 		m_state = ErrorState;
208 
209 		if (m_options.testFlag(CanAutoDeleteOption) && !m_isSelectingPath)
210 		{
211 			deleteLater();
212 		}
213 
214 		return;
215 	}
216 
217 	const QMimeDatabase mimeDatabase;
218 
219 	m_reply = reply;
220 	m_mimeType = mimeDatabase.mimeTypeForName(m_reply->header(QNetworkRequest::ContentTypeHeader).toString());
221 
222 	QString temporaryFileName(getSuggestedFileName());
223 
224 	if (temporaryFileName.isEmpty())
225 	{
226 		temporaryFileName = QLatin1String("otter-download-XXXXXX.") + m_mimeType.preferredSuffix();
227 	}
228 	else if (temporaryFileName.contains(QLatin1Char('.')))
229 	{
230 		const QString suffix(mimeDatabase.suffixForFileName(temporaryFileName.simplified().remove(QLatin1Char(' '))));
231 		int position(temporaryFileName.lastIndexOf(QLatin1Char('.')));
232 
233 		if (!suffix.isEmpty() && temporaryFileName.endsWith(suffix, Qt::CaseInsensitive))
234 		{
235 			position = (temporaryFileName.length() - suffix.length() - 1);
236 		}
237 
238 		temporaryFileName = temporaryFileName.insert(position, QLatin1String("-XXXXXX"));
239 	}
240 
241 	m_device = new QTemporaryFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QDir::separator() + temporaryFileName, this);
242 	m_timeStarted = QDateTime::currentDateTimeUtc();
243 	m_bytesTotal = m_reply->header(QNetworkRequest::ContentLengthHeader).toLongLong();
244 
245 	if (m_bytesTotal == 0 && reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool() && reply->manager()->cache())
246 	{
247 		QIODevice *device(reply->manager()->cache()->data(m_source));
248 
249 		if (device)
250 		{
251 			m_bytesTotal = device->size();
252 
253 			device->close();
254 			device->deleteLater();
255 		}
256 	}
257 
258 	if (!m_device->open(QIODevice::ReadWrite))
259 	{
260 		m_state = ErrorState;
261 
262 		if (m_options.testFlag(CanAutoDeleteOption) && !m_isSelectingPath)
263 		{
264 			deleteLater();
265 		}
266 
267 		return;
268 	}
269 
270 	m_target = m_device->fileName();
271 	m_state = (m_reply->isFinished() ? FinishedState : RunningState);
272 
273 	handleDataAvailable();
274 
275 	const bool isRunning(m_state == RunningState);
276 
277 	if (isRunning)
278 	{
279 		connect(m_reply, &QNetworkReply::downloadProgress, this, &Transfer::handleDownloadProgress);
280 		connect(m_reply, &QNetworkReply::finished, this, &Transfer::handleDownloadFinished);
281 		connect(m_reply, static_cast<void(QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error), this, &Transfer::handleDownloadError);
282 	}
283 	else
284 	{
285 		markAsFinished();
286 
287 		if (m_options.testFlag(CanAutoDeleteOption) && !m_isSelectingPath)
288 		{
289 			deleteLater();
290 		}
291 	}
292 
293 	m_device->reset();
294 
295 	m_mimeType = mimeDatabase.mimeTypeForData(m_device);
296 
297 	m_device->seek(m_device->size());
298 
299 	handleDataAvailable();
300 
301 	if (isRunning)
302 	{
303 		connect(m_reply, &QNetworkReply::readyRead, this, &Transfer::handleDataAvailable);
304 	}
305 
306 	QString finalTarget;
307 	bool canOverwriteExisting(false);
308 
309 	if (target.isEmpty())
310 	{
311 		if (!SettingsManager::getOption(SettingsManager::Browser_AlwaysAskWhereToSaveDownloadOption).toBool())
312 		{
313 			m_options |= IsQuickTransferOption;
314 		}
315 
316 		const QString directory(m_options.testFlag(IsQuickTransferOption) ? Utils::normalizePath(SettingsManager::getOption(SettingsManager::Paths_DownloadsOption).toString()) : QString());
317 		const QString fileName(getSuggestedFileName());
318 
319 		if (m_options.testFlag(IsQuickTransferOption))
320 		{
321 			finalTarget = directory + QDir::separator() + fileName;
322 
323 			if (QFile::exists(finalTarget))
324 			{
325 				m_options |= CanAskForPathOption;
326 			}
327 		}
328 
329 		if (m_options.testFlag(CanAskForPathOption))
330 		{
331 			m_isSelectingPath = true;
332 
333 			const SaveInformation information(Utils::getSavePath(fileName, directory));
334 
335 			m_isSelectingPath = false;
336 
337 			if (!information.canSave)
338 			{
339 				if (m_reply)
340 				{
341 					m_reply->abort();
342 				}
343 
344 				m_device = nullptr;
345 
346 				cancel();
347 
348 				return;
349 			}
350 
351 			finalTarget = information.path;
352 			canOverwriteExisting = true;
353 		}
354 
355 		finalTarget = QDir::toNativeSeparators(finalTarget);
356 	}
357 	else
358 	{
359 		finalTarget = QFileInfo(QDir::toNativeSeparators(target)).absoluteFilePath();
360 	}
361 
362 	if (!finalTarget.isEmpty())
363 	{
364 		setTarget(finalTarget, canOverwriteExisting);
365 	}
366 
367 	if (m_state == FinishedState)
368 	{
369 		if (m_bytesTotal <= 0 && m_bytesReceived > 0)
370 		{
371 			m_bytesTotal = m_bytesReceived;
372 		}
373 
374 		if (m_bytesReceived == 0 || m_bytesReceived < m_bytesTotal)
375 		{
376 			m_state = ErrorState;
377 
378 			if (m_options.testFlag(CanAutoDeleteOption) && !m_isSelectingPath)
379 			{
380 				deleteLater();
381 			}
382 		}
383 		else
384 		{
385 			m_mimeType = mimeDatabase.mimeTypeForFile(m_target);
386 		}
387 	}
388 }
389 
openTarget() const390 void Transfer::openTarget() const
391 {
392 	Utils::runApplication(m_openCommand, QUrl::fromLocalFile(getTarget()));
393 }
394 
cancel()395 void Transfer::cancel()
396 {
397 	m_state = CancelledState;
398 
399 	if (m_reply)
400 	{
401 		m_reply->abort();
402 
403 		QTimer::singleShot(250, m_reply, &QNetworkReply::deleteLater);
404 	}
405 
406 	if (m_device)
407 	{
408 		m_device->remove();
409 	}
410 
411 	stop();
412 
413 	if (m_options.testFlag(CanAutoDeleteOption) && !m_isSelectingPath)
414 	{
415 		deleteLater();
416 	}
417 }
418 
stop()419 void Transfer::stop()
420 {
421 	if (m_updateTimer != 0)
422 	{
423 		killTimer(m_updateTimer);
424 
425 		m_updateTimer = 0;
426 	}
427 
428 	if (m_reply)
429 	{
430 		m_reply->abort();
431 
432 		QTimer::singleShot(250, m_reply, &QNetworkReply::deleteLater);
433 	}
434 
435 	if (m_device && !m_device->inherits("QTemporaryFile"))
436 	{
437 		m_device->close();
438 		m_device->deleteLater();
439 		m_device = nullptr;
440 	}
441 
442 	if (m_state == RunningState)
443 	{
444 		m_state = ErrorState;
445 
446 		if (m_options.testFlag(CanAutoDeleteOption) && !m_isSelectingPath)
447 		{
448 			deleteLater();
449 		}
450 	}
451 
452 	m_speeds.clear();
453 
454 	emit stopped();
455 	emit changed();
456 }
457 
markAsStarted()458 void Transfer::markAsStarted()
459 {
460 	m_timeStarted = QDateTime::currentDateTimeUtc();
461 }
462 
markAsFinished()463 void Transfer::markAsFinished()
464 {
465 	m_timeFinished = QDateTime::currentDateTimeUtc();
466 }
467 
handleDownloadProgress(qint64 bytesReceived,qint64 bytesTotal)468 void Transfer::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
469 {
470 	m_bytesReceivedDifference += (bytesReceived - (m_bytesReceived - m_bytesStart));
471 	m_bytesReceived = (m_bytesStart + bytesReceived);
472 	m_bytesTotal = (m_bytesStart + bytesTotal);
473 
474 	emit progressChanged(bytesReceived, bytesTotal);
475 }
476 
handleDataAvailable()477 void Transfer::handleDataAvailable()
478 {
479 	if (!m_reply || !m_device)
480 	{
481 		return;
482 	}
483 
484 	if (m_state == ErrorState)
485 	{
486 		m_state = RunningState;
487 
488 		if (m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).isValid() && m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 206)
489 		{
490 			m_device->reset();
491 		}
492 	}
493 
494 	m_device->write(m_reply->readAll());
495 	m_device->seek(m_device->size());
496 
497 	if (m_state == RunningState && m_reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool() && m_bytesTotal >= 0 && m_device->size() == m_bytesTotal)
498 	{
499 		handleDownloadFinished();
500 	}
501 }
502 
handleDownloadFinished()503 void Transfer::handleDownloadFinished()
504 {
505 	if (!m_reply)
506 	{
507 		if (m_device && !m_device->inherits("QTemporaryFile"))
508 		{
509 			m_device->close();
510 			m_device->deleteLater();
511 			m_device = nullptr;
512 		}
513 
514 		if (m_options.testFlag(CanAutoDeleteOption) && !m_isSelectingPath)
515 		{
516 			deleteLater();
517 		}
518 
519 		return;
520 	}
521 
522 	if (!m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute).isNull())
523 	{
524 		const QUrl url(m_source.resolved(m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl()));
525 
526 		if (!url.isValid() || (m_source.scheme() == QLatin1String("https") && url.scheme() == QLatin1String("http")))
527 		{
528 			handleDownloadError(QNetworkReply::UnknownContentError);
529 		}
530 		else
531 		{
532 			m_source = url;
533 
534 			restart();
535 		}
536 
537 		return;
538 	}
539 
540 	if (m_updateTimer != 0)
541 	{
542 		killTimer(m_updateTimer);
543 
544 		m_updateTimer = 0;
545 	}
546 
547 	if (m_reply->size() > 0)
548 	{
549 		m_device->write(m_reply->readAll());
550 	}
551 
552 	disconnect(m_reply, &QNetworkReply::downloadProgress, this, &Transfer::handleDownloadProgress);
553 	disconnect(m_reply, &QNetworkReply::readyRead, this, &Transfer::handleDataAvailable);
554 	disconnect(m_reply, &QNetworkReply::finished, this, &Transfer::handleDownloadFinished);
555 
556 	m_bytesReceived = (m_device ? m_device->size() : -1);
557 
558 	if (m_bytesTotal <= 0 && m_bytesReceived > 0)
559 	{
560 		m_bytesTotal = m_bytesReceived;
561 	}
562 
563 	if (m_bytesReceived == 0 || m_bytesReceived < m_bytesTotal)
564 	{
565 		m_state = ErrorState;
566 	}
567 	else
568 	{
569 		markAsFinished();
570 
571 		m_state = FinishedState;
572 		m_mimeType = QMimeDatabase().mimeTypeForFile(m_target);
573 	}
574 
575 	emit finished();
576 	emit changed();
577 
578 	if (m_device && (m_options.testFlag(HasToOpenAfterFinishOption) || !m_device->inherits("QTemporaryFile")))
579 	{
580 		m_device->close();
581 		m_device->deleteLater();
582 		m_device = nullptr;
583 
584 		if (m_reply)
585 		{
586 			QTimer::singleShot(250, m_reply, &QNetworkReply::deleteLater);
587 		}
588 	}
589 
590 	if (m_state == FinishedState && m_options.testFlag(HasToOpenAfterFinishOption))
591 	{
592 		openTarget();
593 	}
594 
595 	if (m_options.testFlag(CanAutoDeleteOption) && !m_isSelectingPath)
596 	{
597 		deleteLater();
598 	}
599 }
600 
handleDownloadError(QNetworkReply::NetworkError error)601 void Transfer::handleDownloadError(QNetworkReply::NetworkError error)
602 {
603 	Q_UNUSED(error)
604 
605 	stop();
606 
607 	m_state = ErrorState;
608 
609 	if (m_options.testFlag(CanAutoDeleteOption) && !m_isSelectingPath)
610 	{
611 		deleteLater();
612 	}
613 }
614 
setOpenCommand(const QString & command)615 void Transfer::setOpenCommand(const QString &command)
616 {
617 	m_openCommand = command;
618 
619 	m_options |= HasToOpenAfterFinishOption;
620 
621 	QTemporaryFile *file(qobject_cast<QTemporaryFile*>(m_device));
622 
623 	if (file)
624 	{
625 		file->setAutoRemove(false);
626 	}
627 
628 	if (m_state == FinishedState)
629 	{
630 		if (file)
631 		{
632 			file->close();
633 			file->deleteLater();
634 
635 			m_device = nullptr;
636 		}
637 
638 		openTarget();
639 	}
640 }
641 
setUpdateInterval(int interval)642 void Transfer::setUpdateInterval(int interval)
643 {
644 	m_updateInterval = interval;
645 
646 	if (m_updateTimer != 0)
647 	{
648 		killTimer(m_updateTimer);
649 
650 		m_updateTimer = 0;
651 	}
652 
653 	if (interval > 0 && m_updateInterval > 0 && m_state == Transfer::RunningState)
654 	{
655 		m_updateTimer = startTimer(m_updateInterval);
656 	}
657 }
658 
getSource() const659 QUrl Transfer::getSource() const
660 {
661 	return m_source;
662 }
663 
getSuggestedFileName()664 QString Transfer::getSuggestedFileName()
665 {
666 	if (!m_reply)
667 	{
668 		return (m_suggestedFileName.isEmpty() ? tr("file") : m_suggestedFileName);
669 	}
670 
671 	QString fileName;
672 
673 	if (m_reply->hasRawHeader(QStringLiteral("Content-Disposition").toLatin1()))
674 	{
675 		const QString contenDispositionHeader(m_reply->rawHeader(QStringLiteral("Content-Disposition").toLatin1()));
676 
677 		if (contenDispositionHeader.contains(QLatin1String("filename*=")))
678 		{
679 			fileName = QRegularExpression(QLatin1String("[\\s;]filename\\*=[\"]?[a-zA-Z0-9\\-_]+\\'[a-zA-Z0-9\\-]?\\'([^\"]+)[\"]?[\\s;]?")).match(contenDispositionHeader).captured(1);
680 		}
681 		else
682 		{
683 			fileName = QRegularExpression(QLatin1String("[\\s;]filename=[\"]?([^\"]+)[\"]?[\\s;]?")).match(contenDispositionHeader).captured(1);
684 		}
685 
686 		if (fileName.contains(QLatin1String("; ")))
687 		{
688 			fileName = fileName.section(QLatin1String("; "), 0, 0);
689 		}
690 
691 		fileName = QUrl(fileName).fileName();
692 	}
693 
694 	if (fileName.isEmpty())
695 	{
696 		fileName = m_source.fileName();
697 	}
698 
699 	if (fileName.isEmpty())
700 	{
701 		fileName = tr("file");
702 	}
703 
704 	if (QFileInfo(fileName).suffix().isEmpty())
705 	{
706 		QString suffix;
707 
708 		if (m_reply->header(QNetworkRequest::ContentTypeHeader).isValid())
709 		{
710 			suffix = m_mimeType.preferredSuffix();
711 		}
712 
713 		if (!suffix.isEmpty())
714 		{
715 			fileName.append(QLatin1Char('.'));
716 			fileName.append(suffix);
717 		}
718 	}
719 
720 	m_suggestedFileName = fileName;
721 
722 	return fileName;
723 }
724 
getTarget() const725 QString Transfer::getTarget() const
726 {
727 	return m_target;
728 }
729 
getTimeStarted() const730 QDateTime Transfer::getTimeStarted() const
731 {
732 	return m_timeStarted;
733 }
734 
getTimeFinished() const735 QDateTime Transfer::getTimeFinished() const
736 {
737 	return m_timeFinished;
738 }
739 
getMimeType() const740 QMimeType Transfer::getMimeType() const
741 {
742 	return m_mimeType;
743 }
744 
getSpeed() const745 qint64 Transfer::getSpeed() const
746 {
747 	return m_speed;
748 }
749 
getBytesReceived() const750 qint64 Transfer::getBytesReceived() const
751 {
752 	return m_bytesReceived;
753 }
754 
getBytesTotal() const755 qint64 Transfer::getBytesTotal() const
756 {
757 	return m_bytesTotal;
758 }
759 
getOptions() const760 Transfer::TransferOptions Transfer::getOptions() const
761 {
762 	return m_options;
763 }
764 
getState() const765 Transfer::TransferState Transfer::getState() const
766 {
767 	return m_state;
768 }
769 
getRemainingTime() const770 int Transfer::getRemainingTime() const
771 {
772 	return m_remainingTime;
773 }
774 
isArchived() const775 bool Transfer::isArchived() const
776 {
777 	return m_isArchived;
778 }
779 
resume()780 bool Transfer::resume()
781 {
782 	if (m_state != ErrorState || !QFile::exists(m_target))
783 	{
784 		return false;
785 	}
786 
787 	m_isArchived = false;
788 
789 	if (m_bytesTotal == 0)
790 	{
791 		return restart();
792 	}
793 
794 	QFile *file(new QFile(m_target));
795 
796 	if (!file->open(QIODevice::WriteOnly | QIODevice::Append))
797 	{
798 		file->deleteLater();
799 
800 		return false;
801 	}
802 
803 	m_state = RunningState;
804 	m_device = file;
805 	m_timeStarted = QDateTime::currentDateTimeUtc();
806 	m_timeFinished = {};
807 	m_bytesStart = file->size();
808 
809 	QNetworkRequest request;
810 	request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
811 	request.setHeader(QNetworkRequest::UserAgentHeader, NetworkManagerFactory::getUserAgent());
812 	request.setRawHeader(QStringLiteral("Range").toLatin1(), QStringLiteral("bytes=%1-").arg(file->size()).toLatin1());
813 	request.setUrl(m_source);
814 
815 	m_reply = NetworkManagerFactory::getNetworkManager(m_options.testFlag(IsPrivateOption))->get(request);
816 
817 	handleDataAvailable();
818 
819 	connect(m_reply, &QNetworkReply::downloadProgress, this, &Transfer::handleDownloadProgress);
820 	connect(m_reply, &QNetworkReply::readyRead, this, &Transfer::handleDataAvailable);
821 	connect(m_reply, &QNetworkReply::finished, this, &Transfer::handleDownloadFinished);
822 	connect(m_reply, static_cast<void(QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error), this, &Transfer::handleDownloadError);
823 
824 	if (m_updateTimer == 0 && m_updateInterval > 0)
825 	{
826 		m_updateTimer = startTimer(m_updateInterval);
827 	}
828 
829 	return true;
830 }
831 
restart()832 bool Transfer::restart()
833 {
834 	stop();
835 
836 	m_isArchived = false;
837 
838 	QFile *file(new QFile(m_target));
839 
840 	if (!file->open(QIODevice::WriteOnly))
841 	{
842 		file->deleteLater();
843 
844 		return false;
845 	}
846 
847 	m_state = RunningState;
848 	m_device = file;
849 	m_timeStarted = QDateTime::currentDateTimeUtc();
850 	m_timeFinished = {};
851 	m_bytesStart = 0;
852 
853 	QNetworkRequest request;
854 	request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
855 	request.setHeader(QNetworkRequest::UserAgentHeader, NetworkManagerFactory::getUserAgent());
856 	request.setUrl(m_source);
857 
858 	m_reply = NetworkManagerFactory::getNetworkManager(m_options.testFlag(IsPrivateOption))->get(request);
859 
860 	handleDataAvailable();
861 
862 	connect(m_reply, &QNetworkReply::downloadProgress, this, &Transfer::handleDownloadProgress);
863 	connect(m_reply, &QNetworkReply::readyRead, this, &Transfer::handleDataAvailable);
864 	connect(m_reply, &QNetworkReply::finished, this, &Transfer::handleDownloadFinished);
865 	connect(m_reply, static_cast<void(QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error), this, &Transfer::handleDownloadError);
866 
867 	if (m_updateTimer == 0 && m_updateInterval > 0)
868 	{
869 		m_updateTimer = startTimer(m_updateInterval);
870 	}
871 
872 	return true;
873 }
874 
setTarget(const QString & target,bool canOverwriteExisting)875 bool Transfer::setTarget(const QString &target, bool canOverwriteExisting)
876 {
877 	if (m_target == target)
878 	{
879 		return false;
880 	}
881 
882 	QString mutableTarget(target);
883 
884 	if (!canOverwriteExisting && !m_options.testFlag(CanOverwriteOption) && QFile::exists(target))
885 	{
886 		const int result(QMessageBox::question(Application::getActiveWindow(), tr("Question"), tr("File with the same name already exists.\nDo you want to overwrite it?\n\n%1").arg(target), QMessageBox::Yes, QMessageBox::No, QMessageBox::Cancel));
887 
888 		if (result == QMessageBox::No)
889 		{
890 			const QFileInfo information(target);
891 			const QString path(Utils::getSavePath(information.fileName(), information.path(), {}, true).path);
892 
893 			if (path.isEmpty())
894 			{
895 				cancel();
896 
897 				return false;
898 			}
899 
900 			mutableTarget = path;
901 		}
902 		else if (result == QMessageBox::Cancel)
903 		{
904 			cancel();
905 
906 			return false;
907 		}
908 	}
909 
910 	if (!m_device)
911 	{
912 		if (m_state != FinishedState)
913 		{
914 			return false;
915 		}
916 
917 		const bool success(QFile::rename(m_target, mutableTarget));
918 
919 		if (success)
920 		{
921 			m_target = mutableTarget;
922 		}
923 
924 		return success;
925 	}
926 
927 	QFile *file(new QFile(mutableTarget, this));
928 
929 	if (!file->open(QIODevice::WriteOnly))
930 	{
931 		m_state = ErrorState;
932 
933 		file->deleteLater();
934 
935 		if (m_options.testFlag(CanAutoDeleteOption) && !m_isSelectingPath)
936 		{
937 			deleteLater();
938 		}
939 
940 		return false;
941 	}
942 
943 	m_target = mutableTarget;
944 
945 	if (m_device)
946 	{
947 		if (m_reply && m_state == RunningState)
948 		{
949 			disconnect(m_reply, &QNetworkReply::readyRead, this, &Transfer::handleDataAvailable);
950 		}
951 
952 		m_device->reset();
953 
954 		file->write(m_device->readAll());
955 
956 		m_device->close();
957 		m_device->deleteLater();
958 	}
959 
960 	m_device = file;
961 
962 	handleDataAvailable();
963 
964 	if (!m_reply || m_reply->isFinished())
965 	{
966 		handleDownloadFinished();
967 	}
968 	else
969 	{
970 		connect(m_reply, &QNetworkReply::readyRead, this, &Transfer::handleDataAvailable);
971 	}
972 
973 	return false;
974 }
975 
TransfersManager(QObject * parent)976 TransfersManager::TransfersManager(QObject *parent) : QObject(parent),
977 	m_saveTimer(0)
978 {
979 }
980 
createInstance()981 void TransfersManager::createInstance()
982 {
983 	if (!m_instance)
984 	{
985 		m_instance = new TransfersManager(QCoreApplication::instance());
986 	}
987 }
988 
timerEvent(QTimerEvent * event)989 void TransfersManager::timerEvent(QTimerEvent *event)
990 {
991 	if (event->timerId() == m_saveTimer)
992 	{
993 		killTimer(m_saveTimer);
994 
995 		m_saveTimer = 0;
996 
997 		save();
998 	}
999 }
1000 
scheduleSave()1001 void TransfersManager::scheduleSave()
1002 {
1003 	if (m_saveTimer == 0)
1004 	{
1005 		m_saveTimer = startTimer(1000);
1006 	}
1007 }
1008 
updateRunningTransfersState()1009 void TransfersManager::updateRunningTransfersState()
1010 {
1011 	bool hasRunningTransfers(false);
1012 
1013 	for (int i = 0; i < m_transfers.count(); ++i)
1014 	{
1015 		if (m_transfers.at(i)->getState() == Transfer::RunningState)
1016 		{
1017 			hasRunningTransfers = true;
1018 
1019 			break;
1020 		}
1021 	}
1022 
1023 	m_hasRunningTransfers = hasRunningTransfers;
1024 }
1025 
addTransfer(Transfer * transfer)1026 void TransfersManager::addTransfer(Transfer *transfer)
1027 {
1028 	m_transfers.append(transfer);
1029 
1030 	transfer->setUpdateInterval(500);
1031 
1032 	connect(transfer, &Transfer::started, m_instance, &TransfersManager::handleTransferStarted);
1033 	connect(transfer, &Transfer::finished, m_instance, &TransfersManager::handleTransferFinished);
1034 	connect(transfer, &Transfer::changed, m_instance, &TransfersManager::handleTransferChanged);
1035 	connect(transfer, &Transfer::stopped, m_instance, &TransfersManager::handleTransferStopped);
1036 
1037 	if (transfer->getOptions().testFlag(Transfer::CanNotifyOption) && transfer->getState() != Transfer::CancelledState)
1038 	{
1039 		emit m_instance->transferStarted(transfer);
1040 
1041 		if (transfer->getState() == Transfer::RunningState)
1042 		{
1043 			emit m_instance->transferFinished(transfer);
1044 		}
1045 	}
1046 
1047 	if (transfer->getOptions().testFlag(Transfer::IsPrivateOption))
1048 	{
1049 		m_privateTransfers.append(transfer);
1050 	}
1051 }
1052 
save()1053 void TransfersManager::save()
1054 {
1055 	if (SessionsManager::isReadOnly() || SettingsManager::getOption(SettingsManager::Browser_PrivateModeOption).toBool() || !SettingsManager::getOption(SettingsManager::History_RememberDownloadsOption).toBool())
1056 	{
1057 		return;
1058 	}
1059 
1060 	QSettings history(SessionsManager::getWritableDataPath(QLatin1String("transfers.ini")), QSettings::IniFormat);
1061 	history.clear();
1062 
1063 	const int limit(SettingsManager::getOption(SettingsManager::History_DownloadsLimitPeriodOption).toInt());
1064 	int entry(1);
1065 
1066 	for (int i = 0; i < m_transfers.count(); ++i)
1067 	{
1068 		if (m_privateTransfers.contains(m_transfers.at(i)) || (m_transfers.at(i)->getState() == Transfer::FinishedState && m_transfers.at(i)->getTimeFinished().isValid() && m_transfers.at(i)->getTimeFinished().daysTo(QDateTime::currentDateTimeUtc()) > limit))
1069 		{
1070 			continue;
1071 		}
1072 
1073 		history.setValue(QStringLiteral("%1/source").arg(entry), m_transfers.at(i)->getSource().toString());
1074 		history.setValue(QStringLiteral("%1/target").arg(entry), m_transfers.at(i)->getTarget());
1075 		history.setValue(QStringLiteral("%1/timeStarted").arg(entry), m_transfers.at(i)->getTimeStarted().toString(Qt::ISODate));
1076 		history.setValue(QStringLiteral("%1/timeFinished").arg(entry), ((m_transfers.at(i)->getTimeFinished().isValid() && m_transfers.at(i)->getState() != Transfer::RunningState) ? m_transfers.at(i)->getTimeFinished() : QDateTime::currentDateTimeUtc()).toString(Qt::ISODate));
1077 		history.setValue(QStringLiteral("%1/bytesTotal").arg(entry), m_transfers.at(i)->getBytesTotal());
1078 		history.setValue(QStringLiteral("%1/bytesReceived").arg(entry), m_transfers.at(i)->getBytesReceived());
1079 
1080 		++entry;
1081 	}
1082 
1083 	history.sync();
1084 }
1085 
clearTransfers(int period)1086 void TransfersManager::clearTransfers(int period)
1087 {
1088 	for (int i = (m_transfers.count() - 1); i >= 0; --i)
1089 	{
1090 		if (m_transfers.at(i)->getState() == Transfer::FinishedState && (period == 0 || (m_transfers.at(i)->getTimeFinished().isValid() && m_transfers.at(i)->getTimeFinished().secsTo(QDateTime::currentDateTimeUtc()) > (period * 3600))))
1091 		{
1092 			TransfersManager::removeTransfer(m_transfers.at(i));
1093 		}
1094 	}
1095 }
1096 
handleTransferStarted()1097 void TransfersManager::handleTransferStarted()
1098 {
1099 	Transfer *transfer(qobject_cast<Transfer*>(sender()));
1100 
1101 	if (transfer && transfer->getState() != Transfer::CancelledState)
1102 	{
1103 		if (transfer->getState() == Transfer::RunningState)
1104 		{
1105 			m_hasRunningTransfers = true;
1106 		}
1107 
1108 		emit transferStarted(transfer);
1109 
1110 		scheduleSave();
1111 	}
1112 }
1113 
handleTransferFinished()1114 void TransfersManager::handleTransferFinished()
1115 {
1116 	Transfer *transfer(qobject_cast<Transfer*>(sender()));
1117 
1118 	updateRunningTransfersState();
1119 
1120 	if (transfer)
1121 	{
1122 		if (transfer->getState() == Transfer::FinishedState)
1123 		{
1124 			connect(NotificationsManager::createNotification(NotificationsManager::TransferCompletedEvent, tr("Download completed:\n%1").arg(QFileInfo(transfer->getTarget()).fileName()), Notification::InformationLevel, this), &Notification::clicked, transfer, &Transfer::openTarget);
1125 		}
1126 
1127 		emit transferFinished(transfer);
1128 
1129 		if (!m_privateTransfers.contains(transfer))
1130 		{
1131 			scheduleSave();
1132 		}
1133 	}
1134 }
1135 
handleTransferChanged()1136 void TransfersManager::handleTransferChanged()
1137 {
1138 	Transfer *transfer(qobject_cast<Transfer*>(sender()));
1139 
1140 	if (transfer)
1141 	{
1142 		scheduleSave();
1143 		updateRunningTransfersState();
1144 
1145 		emit transferChanged(transfer);
1146 	}
1147 }
1148 
handleTransferStopped()1149 void TransfersManager::handleTransferStopped()
1150 {
1151 	Transfer *transfer(qobject_cast<Transfer*>(sender()));
1152 
1153 	updateRunningTransfersState();
1154 
1155 	if (transfer)
1156 	{
1157 		emit transferStopped(transfer);
1158 
1159 		scheduleSave();
1160 	}
1161 }
1162 
getInstance()1163 TransfersManager* TransfersManager::getInstance()
1164 {
1165 	return m_instance;
1166 }
1167 
startTransfer(const QUrl & source,const QString & target,Transfer::TransferOptions options)1168 Transfer* TransfersManager::startTransfer(const QUrl &source, const QString &target, Transfer::TransferOptions options)
1169 {
1170 	Transfer *transfer(new Transfer(source, target, options, m_instance));
1171 
1172 	if (transfer->getState() == Transfer::CancelledState)
1173 	{
1174 		transfer->deleteLater();
1175 
1176 		return nullptr;
1177 	}
1178 
1179 	addTransfer(transfer);
1180 
1181 	return transfer;
1182 }
1183 
startTransfer(const QNetworkRequest & request,const QString & target,Transfer::TransferOptions options)1184 Transfer* TransfersManager::startTransfer(const QNetworkRequest &request, const QString &target, Transfer::TransferOptions options)
1185 {
1186 	Transfer *transfer(new Transfer(request, target, options, m_instance));
1187 
1188 	if (transfer->getState() == Transfer::CancelledState)
1189 	{
1190 		transfer->deleteLater();
1191 
1192 		return nullptr;
1193 	}
1194 
1195 	addTransfer(transfer);
1196 
1197 	return transfer;
1198 }
1199 
startTransfer(QNetworkReply * reply,const QString & target,Transfer::TransferOptions options)1200 Transfer* TransfersManager::startTransfer(QNetworkReply *reply, const QString &target, Transfer::TransferOptions options)
1201 {
1202 	Transfer *transfer(new Transfer(reply, target, options, m_instance));
1203 
1204 	if (transfer->getState() == Transfer::CancelledState)
1205 	{
1206 		transfer->deleteLater();
1207 
1208 		return nullptr;
1209 	}
1210 
1211 	addTransfer(transfer);
1212 
1213 	return transfer;
1214 }
1215 
getTransfers()1216 QVector<Transfer*> TransfersManager::getTransfers()
1217 {
1218 	if (!m_isInitilized)
1219 	{
1220 		QSettings history(SessionsManager::getWritableDataPath(QLatin1String("transfers.ini")), QSettings::IniFormat);
1221 		const QStringList entries(history.childGroups());
1222 
1223 		m_transfers.reserve(entries.count());
1224 
1225 		for (int i = 0; i < entries.count(); ++i)
1226 		{
1227 			history.beginGroup(entries.at(i));
1228 
1229 			if (!history.value(QLatin1String("source")).toString().isEmpty() && !history.value(QLatin1String("target")).toString().isEmpty())
1230 			{
1231 				addTransfer(new Transfer(history, m_instance));
1232 			}
1233 
1234 			history.endGroup();
1235 		}
1236 
1237 		m_isInitilized = true;
1238 
1239 		connect(QCoreApplication::instance(), &Application::aboutToQuit, m_instance, &TransfersManager::save);
1240 	}
1241 
1242 	return m_transfers;
1243 }
1244 
removeTransfer(Transfer * transfer,bool keepFile)1245 bool TransfersManager::removeTransfer(Transfer *transfer, bool keepFile)
1246 {
1247 	if (!transfer || !m_transfers.contains(transfer))
1248 	{
1249 		return false;
1250 	}
1251 
1252 	m_transfers.removeAll(transfer);
1253 
1254 	m_privateTransfers.removeAll(transfer);
1255 
1256 	if (transfer->getState() == Transfer::RunningState)
1257 	{
1258 		transfer->stop();
1259 	}
1260 
1261 	if (!keepFile && !transfer->getTarget().isEmpty() && QFile::exists(transfer->getTarget()))
1262 	{
1263 		QFile::remove(transfer->getTarget());
1264 	}
1265 
1266 	emit m_instance->transferRemoved(transfer);
1267 
1268 	transfer->deleteLater();
1269 
1270 	return true;
1271 }
1272 
isDownloading(const QString & source,const QString & target)1273 bool TransfersManager::isDownloading(const QString &source, const QString &target)
1274 {
1275 	if (source.isEmpty() && target.isEmpty())
1276 	{
1277 		return false;
1278 	}
1279 
1280 	for (int i = 0; i < m_transfers.count(); ++i)
1281 	{
1282 		if (m_transfers.at(i)->getState() != Transfer::RunningState)
1283 		{
1284 			continue;
1285 		}
1286 
1287 		if (source.isEmpty() && m_transfers.at(i)->getTarget() == target)
1288 		{
1289 			return true;
1290 		}
1291 
1292 		if (target.isEmpty() && m_transfers.at(i)->getSource() == source)
1293 		{
1294 			return true;
1295 		}
1296 
1297 		if (!source.isEmpty() && !target.isEmpty() && m_transfers.at(i)->getSource() == source && m_transfers.at(i)->getTarget() == target)
1298 		{
1299 			return true;
1300 		}
1301 	}
1302 
1303 	return false;
1304 }
1305 
hasRunningTransfers()1306 bool TransfersManager::hasRunningTransfers()
1307 {
1308 	return m_hasRunningTransfers;
1309 }
1310 
1311 }
1312