modify:优化
This commit is contained in:
+193
-56
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user