modify:优化

This commit is contained in:
2026-04-29 23:16:10 +08:00
parent 21556d6b77
commit af1d3c37e8
6 changed files with 664 additions and 258 deletions
+193 -56
View File
@@ -1,9 +1,41 @@
#include "downloader.h"
#include "downloader.h"
#include <QCoreApplication>
#include <QDebug>
#include <QJsonDocument>
#include <QDateTime>
#include <QFileInfo>
#include <QRegularExpression>
namespace
{
/**
* @brief 从 Content-Disposition 响应头中提取服务器建议文件名。
* @param headerValue 响应头原始值,可能包含百分号编码和 filename 字段。
* @return 安全的纯文件名;解析失败返回空字符串。
*
* 设计意图:只接收文件名部分,丢弃目录,避免服务器返回带路径的名称覆盖应用目录外文件。
* 边界条件:响应头为空、格式不匹配、文件名为空时交给调用方继续使用本地预设文件名。
*/
QString fileNameFromContentDisposition(const QVariant &headerValue)
{
if(!headerValue.isValid())
{
return QString();
}
const QString contentDisposition =
QString::fromUtf8(QByteArray::fromPercentEncoding(headerValue.toByteArray()));
const QRegularExpression regExp(R"(filename\*?=(?:UTF-8''|")?([^";]+))",
QRegularExpression::CaseInsensitiveOption);
const QRegularExpressionMatch match = regExp.match(contentDisposition);
if(!match.hasMatch())
{
return QString();
}
return QFileInfo(match.captured(1).trimmed()).fileName();
}
}
Downloader::Downloader(QObject *parent)
: QObject{parent}
{
@@ -19,89 +51,153 @@ void Downloader::setUrl(const QString &_url)
void Downloader::startDownload(const QString &_url)
{
if(m_bIsDownloading)
{
emit doShowInfo(tr("download task is already running"));
return;
m_bIsDownloading = true;
}
if(!_url.isEmpty())
m_pUrl.setUrl(_url);
if(!dir.exists())
{
dir.mkpath(".");
}
/* Rename old downloads */
QString _path = dir.path() + "/" + m_fileName;
if(QFile::exists(_path))
{
QFile _file(_path);
_file.rename(_path + "_" + QDateTime::currentDateTime().toString("yyyy-MM-dd"));
m_pUrl = QUrl(_url);
}
QFile::remove(dir.filePath(m_fileName));
QFile::remove(dir.filePath(m_fileName + ".part"));
if(!m_pUrl.isValid() || m_pUrl.isEmpty())
{
emit doShowInfo(tr("download url is invalid"));
return;
}
if(m_fileName.trimmed().isEmpty())
{
const QString urlFileName = QFileInfo(m_pUrl.path()).fileName();
if(urlFileName.isEmpty())
{
emit doShowInfo(tr("download file name is empty"));
return;
}
m_fileName = urlFileName;
}
m_bIsDownloading = true;
if(!dir.exists() && !dir.mkpath("."))
{
m_bIsDownloading = false;
emit doShowInfo(tr("create download directory failed"));
return;
}
const QString targetPath = targetFilePath();
const QString partPath = partialFilePath();
// 保留旧版本文件,便于更新失败时人工回滚;同一天多次下载时追加时间避免重名。
if(QFile::exists(targetPath))
{
QFile oldFile(targetPath);
const QString backupPath = targetPath + "_" + QDateTime::currentDateTime().toString("yyyy-MM-dd_hhmmss");
if(!oldFile.rename(backupPath))
{
emit doShowInfo(tr("backup old file failed: %1").arg(oldFile.errorString()));
}
}
QFile::remove(partPath);
file.setFileName(partPath);
if(!file.open(QIODevice::WriteOnly | QIODevice::Truncate))
{
m_bIsDownloading = false;
emit doShowInfo(tr("open download file failed: %1").arg(file.errorString()));
return;
}
QNetworkRequest request;
request.setUrl(m_pUrl);
m_pReply = m_pNetWorkAccessManager->get(request);
connect(m_pReply, &QNetworkReply::downloadProgress, this, [&](qint64 received, qint64 total)
connect(m_pReply, &QNetworkReply::readyRead, this, [this]()
{
if(!file.isOpen() || !m_pReply)
{
return;
}
const QByteArray chunk = m_pReply->readAll();
if(file.write(chunk) != chunk.size())
{
emit doShowInfo(tr("write download file failed: %1").arg(file.errorString()));
m_pReply->abort();
}
});
connect(m_pReply, &QNetworkReply::downloadProgress, this, [this](qint64 received, qint64 total)
{
emit doProgress(received, total);
if (total > 0)
});
connect(m_pReply, &QNetworkReply::metaDataChanged, this, [this]()
{
const QString responseFileName =
fileNameFromContentDisposition(m_pReply->header(QNetworkRequest::ContentDispositionHeader));
if(!responseFileName.isEmpty() && m_fileName.isEmpty())
{
if(file.fileName() != (dir.filePath(m_fileName + ".part")))
file.setFileName(dir.filePath(m_fileName + ".part"));
if(!file.isOpen())
{
file.open(QIODevice::WriteOnly | QIODevice::Append);
}
file.write(m_pReply->readAll());
m_fileName = responseFileName;
}
});
connect(m_pReply, &QNetworkReply::metaDataChanged, this, [&]()
connect(m_pReply, &QNetworkReply::finished, this, [this]()
{
QString filename = "";
QVariant variant = m_pReply->header( QNetworkRequest::ContentDispositionHeader );
if ( variant.isValid() )
QNetworkReply *finishedReply = m_pReply;
const QNetworkReply::NetworkError errorCode = finishedReply->error();
// HTTP 状态码用于拦截 404/500 等服务器错误,避免把错误页面当作升级包落盘。
const int httpStatusCode =
finishedReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if(errorCode != QNetworkReply::NoError)
{
QString contentDisposition = QByteArray::fromPercentEncoding( variant.toByteArray() ).constData();
QRegularExpression regExp( "(.*)filename=\"(?<filename>.*)\"" );
QRegularExpressionMatch match = regExp.match( contentDisposition );
if ( match.hasMatch() )
{
filename = match.captured( "filename" );
}
m_fileName = filename;
auto localApplicationFilePath = QCoreApplication::applicationDirPath();
file.setFileName(localApplicationFilePath + "/" + m_fileName);
};
});
// connect(m_pReply, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
connect(m_pReply, &QNetworkReply::finished, this, [&]()
{
emit onError(errorCode);
emit doShowInfo(finishedReply->errorString());
cleanupCurrentReply(true);
return;
}
if(httpStatusCode >= 400)
{
emit doShowInfo(tr("http download failed, status code: %1").arg(httpStatusCode));
cleanupCurrentReply(true);
return;
}
if(file.isOpen())
{
file.close();
if(!file.exists())
{
emit doShowInfo("not exits");
}
if(file.isOpen())
const QString partPath = partialFilePath();
const QString targetPath = targetFilePath();
if(!QFile::exists(partPath))
{
emit doShowInfo("not close");
emit doShowInfo(tr("download temporary file not exists"));
cleanupCurrentReply(true);
return;
}
if(!file.rename(dir.path() + "/" + m_fileName))
QFile::remove(targetPath);
if(!QFile::rename(partPath, targetPath))
{
emit doShowInfo("rename file failed");
emit doShowInfo(tr("rename file failed"));
cleanupCurrentReply(true);
return;
}
file.setFileName(dir.filePath(m_fileName));
file.setFileName(targetPath);
m_bIsDownloading = false;
finishedReply->deleteLater();
m_pReply = nullptr;
emit doFinished();
});
connect(m_pReply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SIGNAL(onError(QNetworkReply::NetworkError)));
}
void Downloader::checkVersion()
{
QNetworkRequest request(CHECK_URL);
QNetworkRequest request{QUrl(CHECK_URL)};
m_pNetWorkAccessManager->get(request);
}
@@ -122,5 +218,46 @@ const QString &Downloader::fileName() const
void Downloader::setFileName(const QString &newFileName)
{
m_fileName = newFileName;
m_fileName = QFileInfo(newFileName).fileName();
}
void Downloader::cleanupCurrentReply(bool removePartial)
{
if(file.isOpen())
{
file.close();
}
if(removePartial)
{
QFile::remove(partialFilePath());
}
if(m_pReply)
{
m_pReply->deleteLater();
m_pReply = nullptr;
}
m_bIsDownloading = false;
}
QString Downloader::partialFilePath() const
{
if(m_fileName.isEmpty())
{
return QString();
}
return dir.filePath(m_fileName + ".part");
}
QString Downloader::targetFilePath() const
{
if(m_fileName.isEmpty())
{
return QString();
}
return dir.filePath(m_fileName);
}