2020-10-25 20:03:47 +08:00
|
|
|
#include "widget.h"
|
|
|
|
#include "ui_widget.h"
|
|
|
|
#include "utils.h"
|
|
|
|
#include <QUrl>
|
|
|
|
#include <QNetworkReply>
|
|
|
|
#include <QNetworkRequest>
|
|
|
|
#include <QMessageBox>
|
|
|
|
#include <QRegExp>
|
|
|
|
#include <QRegExpValidator>
|
|
|
|
#include <QFileDialog>
|
|
|
|
#include <QDir>
|
|
|
|
#include <QThread>
|
|
|
|
#include <QEventLoop>
|
|
|
|
#include <QFuture>
|
|
|
|
#include <QtConcurrent/QtConcurrent>
|
2020-10-25 22:19:26 +08:00
|
|
|
#include <QMutex>
|
2021-02-16 00:25:54 +08:00
|
|
|
#include <downloadworker.h>
|
2020-10-25 20:03:47 +08:00
|
|
|
|
|
|
|
Widget::Widget(QWidget *parent)
|
|
|
|
: QWidget(parent)
|
|
|
|
, ui(new Ui::Widget)
|
|
|
|
{
|
|
|
|
ui->setupUi(this);
|
|
|
|
|
|
|
|
// 设置URL链接验证器
|
2020-12-27 23:05:57 +08:00
|
|
|
QRegExp regex(R"(https?:\/\/.+)");
|
2020-10-25 20:03:47 +08:00
|
|
|
auto *validator = new QRegExpValidator(regex, nullptr);
|
|
|
|
ui->urlInput->setValidator(validator);
|
|
|
|
|
|
|
|
// 设置最大的线程并发数
|
|
|
|
int processer_num = QThread::idealThreadCount();
|
|
|
|
processer_num = processer_num > maxThreadNum ? maxThreadNum : processer_num;
|
|
|
|
ui->threadCountSpinbox->setMaximum(processer_num);
|
|
|
|
ui->threadCountSpinbox->setValue(processer_num);
|
|
|
|
|
|
|
|
// 设置保存路径为当前程序运行路径
|
|
|
|
ui->savePathInput->setText(QDir::currentPath());
|
|
|
|
|
|
|
|
requestManager = new QNetworkAccessManager(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
Widget::~Widget()
|
|
|
|
{
|
|
|
|
delete ui;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief 展示错误信息
|
|
|
|
* @param msg
|
|
|
|
*/
|
|
|
|
void Widget::showError(const QString& msg)
|
|
|
|
{
|
|
|
|
auto msgBox = new QMessageBox(this);
|
|
|
|
msgBox->setWindowTitle(tr("请求出错"));
|
|
|
|
msgBox->setIcon(QMessageBox::Critical);
|
|
|
|
msgBox->setText(msg);
|
|
|
|
msgBox->exec();
|
|
|
|
ui->downloadBtn->setEnabled(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief 获取要下载的文件大小
|
|
|
|
* @param url
|
|
|
|
*/
|
|
|
|
qint64 Widget::getFileSize(const QString& url)
|
|
|
|
{
|
|
|
|
QEventLoop event;
|
|
|
|
QNetworkRequest request;
|
|
|
|
request.setUrl(QUrl(url));
|
|
|
|
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
|
|
|
QNetworkReply *reply = requestManager->head(request);
|
|
|
|
connect(reply, &QNetworkReply::errorOccurred, [this, reply](QNetworkReply::NetworkError error){
|
|
|
|
if (error != QNetworkReply::NoError) {
|
|
|
|
return showError(reply->errorString());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
connect(reply, &QNetworkReply::finished, &event, &QEventLoop::quit);
|
|
|
|
event.exec();
|
|
|
|
qint64 fileSize = 0;
|
|
|
|
if (reply->rawHeader("Accept-Ranges") == QByteArrayLiteral("bytes")
|
|
|
|
&& reply->hasRawHeader(QString("Content-Length").toLocal8Bit())) {
|
|
|
|
fileSize = reply->header(QNetworkRequest::ContentLengthHeader).toUInt();
|
|
|
|
}
|
2020-12-27 23:05:57 +08:00
|
|
|
qDebug() << "文件大小为:" << fileSize;
|
2020-10-25 20:03:47 +08:00
|
|
|
reply->deleteLater();
|
|
|
|
return fileSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief 开始下载文件
|
|
|
|
* @param url
|
|
|
|
* @param filename
|
|
|
|
*/
|
|
|
|
void Widget::singleDownload(const QString& url, const QString& filename)
|
|
|
|
{
|
|
|
|
QFile file(filename);
|
|
|
|
if (file.exists())
|
|
|
|
file.remove();
|
|
|
|
if (!file.open(QIODevice::WriteOnly)) {
|
|
|
|
return showError(file.errorString());
|
|
|
|
}
|
|
|
|
|
|
|
|
QEventLoop event;
|
|
|
|
QNetworkAccessManager mgr;
|
|
|
|
QNetworkRequest request;
|
|
|
|
request.setUrl(QUrl(url));
|
|
|
|
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
|
|
|
QNetworkReply *reply = mgr.get(request);
|
|
|
|
connect(reply, &QNetworkReply::readyRead, this, [&file, reply](){
|
|
|
|
file.write(reply->readAll());
|
|
|
|
});
|
|
|
|
connect(reply, &QNetworkReply::finished, &event, &QEventLoop::quit);
|
2020-10-25 20:17:37 +08:00
|
|
|
connect(reply, &QNetworkReply::downloadProgress, this, &Widget::download_progress_change);
|
2020-10-25 20:03:47 +08:00
|
|
|
connect(reply, &QNetworkReply::errorOccurred, [this, reply](QNetworkReply::NetworkError error){
|
|
|
|
if (error != QNetworkReply::NoError) showError(reply->errorString());
|
|
|
|
});
|
|
|
|
event.exec();
|
|
|
|
file.flush();
|
|
|
|
file.close();
|
|
|
|
ui->downloadBtn->setEnabled(true);
|
|
|
|
}
|
|
|
|
|
2021-02-16 00:25:54 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief 点击开始下载
|
|
|
|
*/
|
|
|
|
void Widget::on_downloadBtn_clicked()
|
|
|
|
{
|
|
|
|
QString url = ui->urlInput->text().trimmed();
|
|
|
|
if (url.isEmpty()) {
|
|
|
|
return showError(tr("请求链接不允许为空!"));
|
|
|
|
return ;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 选择保存路径
|
|
|
|
QString savePath = ui->savePathInput->text().trimmed();
|
|
|
|
if (savePath.isEmpty()) {
|
|
|
|
savePath = QFileDialog::getExistingDirectory();
|
|
|
|
if (savePath.isEmpty()) return;
|
|
|
|
} else {
|
|
|
|
if(!QDir(savePath).exists()) {
|
|
|
|
return showError(tr("路径不存在!"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ui->downloadBtn->setEnabled(false);
|
|
|
|
ui->downProgressBar->setValue(0);
|
|
|
|
qint64 fileSize = getFileSize(url);
|
|
|
|
QString sizeText = fileSize == 0 ? "未知大小" : Utils::sizeFormat(fileSize);
|
|
|
|
ui->filesizeLabel->setText(sizeText);
|
|
|
|
|
|
|
|
int process_num = ui->threadCountSpinbox->text().toInt();
|
|
|
|
|
|
|
|
QDir::setCurrent(savePath);
|
|
|
|
QString filename = QFileInfo(url).fileName();
|
|
|
|
if (fileSize == 0 || process_num == 1) {
|
|
|
|
singleDownload(url, filename);
|
|
|
|
} else {
|
|
|
|
multiDownloadWithQThread(url, fileSize, filename, process_num);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief 设置保存下载文件的路径
|
|
|
|
*/
|
|
|
|
void Widget::on_brwoserPathBtn_clicked()
|
|
|
|
{
|
|
|
|
QString savePath = QFileDialog::getExistingDirectory();
|
|
|
|
if (!savePath.isEmpty()) {
|
|
|
|
ui->savePathInput->setText(savePath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Widget::download_progress_change(qint64 bytesReceived, qint64 bytesTotal)
|
|
|
|
{
|
|
|
|
if (bytesTotal <= 0)
|
|
|
|
return;
|
|
|
|
ui->downProgressBar->setMaximum(10000); // 最大值
|
|
|
|
ui->downProgressBar->setValue((bytesReceived * 10000) / bytesTotal); // 当前值
|
|
|
|
ui->downProgressBar->setTextVisible(true);
|
|
|
|
}
|
|
|
|
|
2020-10-25 20:03:47 +08:00
|
|
|
/**
|
|
|
|
* @brief 多线程下载
|
|
|
|
* @param url
|
|
|
|
* @param fileSize
|
|
|
|
* @param filename
|
|
|
|
* @param threadCount
|
|
|
|
*/
|
2021-02-16 00:25:54 +08:00
|
|
|
void Widget::multiDownloadWithQtConcurrent(const QString &url, qint64 fileSize, const QString &filename, int threadCount)
|
2020-10-25 20:03:47 +08:00
|
|
|
{
|
|
|
|
QFile file(filename);
|
|
|
|
if (file.exists())
|
|
|
|
file.remove();
|
|
|
|
if (!file.open(QIODevice::WriteOnly)) {
|
|
|
|
return showError(file.errorString());
|
|
|
|
}
|
|
|
|
file.resize(fileSize);
|
|
|
|
|
2020-12-27 23:05:57 +08:00
|
|
|
qDebug() << "开始下载文件";
|
2020-10-25 20:03:47 +08:00
|
|
|
// 任务等分
|
|
|
|
qint64 segmentSize = fileSize / threadCount;
|
|
|
|
QVector<QPair<qint64, qint64>> vec(threadCount);
|
|
|
|
for (int i = 0; i < threadCount; i++) {
|
|
|
|
vec[i].first = i * segmentSize;
|
|
|
|
vec[i].second = i * segmentSize + segmentSize - 1;
|
|
|
|
}
|
|
|
|
vec[threadCount-1].second = fileSize; // 余数部分加入最后一个
|
|
|
|
|
|
|
|
qint64 bytesReceived = 0; // 下载接收的总字节数
|
2021-01-10 23:51:20 +08:00
|
|
|
QMutex lock;
|
2020-10-25 20:03:47 +08:00
|
|
|
// 任务队列
|
|
|
|
auto mapCaller = [&, this](const QPair<qint64, qint64>& pair) -> qint64 {
|
|
|
|
QEventLoop event;
|
|
|
|
QNetworkAccessManager mgr;
|
|
|
|
QNetworkRequest request;
|
|
|
|
request.setUrl(url);
|
|
|
|
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
|
|
|
request.setRawHeader("Range", QString("bytes=%1-%2").arg(pair.first).arg(pair.second).toLocal8Bit());
|
|
|
|
QNetworkReply *reply = mgr.get(request);
|
|
|
|
qint64 writePos = pair.first;
|
2020-12-27 23:05:57 +08:00
|
|
|
qDebug() << "开始下载数据:" << QString(" %1~%2 -> writePos Start %3")
|
|
|
|
.arg(pair.first).arg(pair.second).arg(writePos);
|
2020-10-25 22:19:26 +08:00
|
|
|
connect(reply, &QNetworkReply::readyRead, [&lock, &writePos, &file, &bytesReceived, reply](){
|
2020-10-25 20:03:47 +08:00
|
|
|
QByteArray data = reply->readAll();
|
2021-01-03 21:26:49 +08:00
|
|
|
{
|
|
|
|
QMutexLocker mLock(&lock);
|
2021-01-10 23:51:20 +08:00
|
|
|
// qDebug() << "调整文件的数据指针:" << writePos;
|
|
|
|
file.seek(writePos);
|
|
|
|
file.write(data);
|
2021-01-03 21:26:49 +08:00
|
|
|
bytesReceived += data.size();
|
|
|
|
qDebug() << QString("%1, %2, %3")
|
|
|
|
.arg(writePos).arg(bytesReceived).arg(data.size());
|
|
|
|
}
|
2020-10-25 20:03:47 +08:00
|
|
|
writePos += data.size();
|
|
|
|
});
|
|
|
|
connect(reply, &QNetworkReply::finished, &event, &QEventLoop::quit);
|
|
|
|
event.exec();
|
|
|
|
return writePos - pair.first;
|
|
|
|
};
|
|
|
|
QFuture<void> future = QtConcurrent::map(vec, mapCaller);
|
|
|
|
QFutureWatcher<void> futureWatcher;
|
|
|
|
QEventLoop loop;
|
|
|
|
connect(&futureWatcher, &QFutureWatcher<void>::finished, &loop, &QEventLoop::quit, Qt::QueuedConnection);
|
|
|
|
futureWatcher.setFuture(future);
|
|
|
|
if (!future.isFinished()) {
|
|
|
|
loop.exec();
|
|
|
|
}
|
|
|
|
file.close();
|
|
|
|
qDebug() << "下载完毕!!";
|
|
|
|
ui->downloadBtn->setEnabled(true);
|
|
|
|
}
|
|
|
|
|
2021-02-16 00:25:54 +08:00
|
|
|
void Widget::multiDownloadWithQThread(const QString& url, qint64 fileSize, const QString& filename, int threadCount)
|
2020-10-25 20:03:47 +08:00
|
|
|
{
|
2021-02-16 20:18:33 +08:00
|
|
|
DownloadController *download = new DownloadController(this);
|
|
|
|
download->setFilename(filename);
|
|
|
|
download->startDownload(url, fileSize);
|
|
|
|
connect(download, &DownloadController::downloadProcess, this, &Widget::download_progress_change);
|
2020-10-25 20:03:47 +08:00
|
|
|
}
|