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