1// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5#include "downloadutil.h"
6
7#include <QUrl>
8#include <QFileInfo>
9#include <QDir>
10#include <QDebug>
11#include <QNetworkAccessManager>
12#include <QNetworkReply>
13#include <QPointer>
14#include <memory>
15
16class DownloadUtilPrivate {
17 friend class DownloadUtil;
18 QString srcUrl;
19 QString dstPath;
20 QString fileName;
21 QString resultFileName;
22
23 QNetworkAccessManager accessManager;
24 QPointer<QNetworkReply> reply;
25
26 std::unique_ptr<QFile> dstFile;
27 bool requestAborted;
28 QUrl requestUrl;
29};
30
31DownloadUtil::DownloadUtil(const QString &srcUrl,
32 const QString &dstPath,
33 const QString &fileName,
34 QObject *parent)
35 : QObject(parent)
36 , d(new DownloadUtilPrivate())
37{
38 d->srcUrl = srcUrl;
39 d->dstPath = dstPath;
40 d->fileName = fileName + ".downloading";
41 d->resultFileName = fileName;
42
43}
44
45DownloadUtil::~DownloadUtil()
46{
47
48}
49
50bool DownloadUtil::start()
51{
52 const QUrl srcUrl = QUrl::fromUserInput(d->srcUrl);
53
54 if (!srcUrl.isValid() || d->dstPath.isEmpty() || d->fileName.isEmpty()) {
55 return false;
56 }
57
58 if (!QFileInfo(d->dstPath).isDir()) {
59 QDir dir;
60 dir.mkpath(d->dstPath);
61 }
62
63 d->fileName.prepend(d->dstPath + '/');
64 d->resultFileName.prepend(d->dstPath + '/');
65
66 if (QFile::exists(d->fileName)) {
67 QFile::remove(d->fileName);
68 }
69
70 d->dstFile = openFileForWrite(d->fileName);
71 if (!d->dstFile)
72 return false;
73
74 startRequest(srcUrl);
75
76 return true;
77}
78
79
80std::unique_ptr<QFile> DownloadUtil::openFileForWrite(const QString& fileName)
81{
82 std::unique_ptr<QFile> file(new QFile(fileName));
83 if (!file->open(QIODevice::WriteOnly)) {
84
85#ifdef DEBUG_INFO
86 qInfo() << QString("Unable to save the file %1: %2.")
87 .arg(QDir::toNativeSeparators(fileName), file->errorString());
88#endif
89
90 return nullptr;
91 }
92
93 return file;
94}
95
96
97void DownloadUtil::startRequest(const QUrl& url)
98{
99 d->requestUrl = url;
100 d->reply = d->accessManager.get(QNetworkRequest(url));
101
102 connect(d->reply, &QNetworkReply::finished, [this](){
103 QFileInfo file;
104 if (d->dstFile) {
105 file.setFile(d->dstFile->fileName());
106 d->dstFile->close();
107 d->dstFile->reset();
108 }
109
110 if (d->requestAborted) {
111 emit sigFailed();
112 return;
113 }
114
115 if (d->reply->error()) {
116 QFile::remove(file.absoluteFilePath());
117
118#ifdef DEBUG_INFO
119 qInfo() << QString("Download failed: %1.")
120 .arg(d->reply->errorString());
121#endif
122 emit sigFailed();
123 return;
124 }
125
126 const QVariant redirectionTarget = d->reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
127
128 if (!redirectionTarget.isNull()) {
129 const QUrl redirectedUrl = d->requestUrl.resolved(redirectionTarget.toUrl());
130 d->dstFile = openFileForWrite(file.absoluteFilePath());
131 if (!d->dstFile) {
132 emit sigFailed();
133 return;
134 }
135
136 startRequest(redirectedUrl);
137 return;
138 }
139
140 QFile::rename(d->fileName, d->resultFileName);
141 emit sigFinished();
142
143#ifdef DEBUG_INFO
144 qInfo() << QString(tr("Downloaded %1 bytes to %2 in %3") .arg(file.size()) .arg(file.fileName(),
145 QDir::toNativeSeparators(file.absolutePath())));
146 qInfo() << "Finished";
147#endif
148 });
149
150 connect(d->reply, &QIODevice::readyRead, [this](){
151 if (d->dstFile)
152 d->dstFile->write(d->reply->readAll());
153 });
154
155 connect(d->reply, &QNetworkReply::downloadProgress,
156 [this](qint64 bytesReceived, qint64 bytesTotal){
157 emit sigProgress(bytesReceived, bytesTotal);
158
159#ifdef DEBUG_INFO
160 qInfo() << QString::number(bytesReceived/bytesTotal * 100, 'f', 2) << "% "
161 << bytesReceived / (1024 * 1024) << "MB" << "/" << bytesTotal / (1024 * 1024) << "MB";
162#endif
163 });
164
165#ifdef DEBUG_INFO
166 qInfo() << QString(tr("Downloading %1...").arg(url.toString()));
167#endif
168}
169
170void DownloadUtil::cancel()
171{
172 d->requestAborted = true;
173 d->reply->abort();
174}
175