实现上传进度显示

main
wuyize 2023-07-05 23:50:11 +08:00
parent 5c1b23421c
commit 3535f3ee03
6 changed files with 217 additions and 168 deletions

View File

@ -4,6 +4,7 @@ import FluentUI
import QtQuick.Dialogs import QtQuick.Dialogs
import "qrc:///AicsKnowledgeBase/qml/global" import "qrc:///AicsKnowledgeBase/qml/global"
import SignalFileOperation 1.0 import SignalFileOperation 1.0
import AicsKB.FileTransferManager
Item { Item {
anchors.fill: parent anchors.fill: parent
@ -53,13 +54,14 @@ Item {
FileDialog { FileDialog {
id: fileDialog id: fileDialog
onAccepted: function () { onAccepted: function () {
let name = FileTransferManager.getFileName(selectedFile)
const size = FileTransferManager.getFileSize( const size = FileTransferManager.getFileSize(
selectedFile) selectedFile)
const md5 = FileTransferManager.getFileMd5(selectedFile) const md5 = FileTransferManager.getFileMd5(selectedFile)
if (size <= 0 || md5 === '') if (size <= 0 || md5 === '')
return return
var body = { var body = {
"name": "test2", "name": name,
"brief": "brief", "brief": "brief",
"size": size, "size": size,
"md5": md5, "md5": md5,
@ -75,7 +77,7 @@ Item {
console.log(data) console.log(data)
FileTransferManager.upload( FileTransferManager.upload(
selectedFile, data.id, selectedFile, data.id,
data.ticket) data.ticket, name)
}, function (res, data) { }, function (res, data) {
console.log(res) console.log(res)
}) })

View File

@ -5,7 +5,6 @@ import QtQuick.Controls
import QtQuick.Controls.Basic import QtQuick.Controls.Basic
import QtQuick.Dialogs import QtQuick.Dialogs
import FluentUI import FluentUI
import AicsKB.FileTransferManager
import "qrc:///AicsKnowledgeBase/qml/component" import "qrc:///AicsKnowledgeBase/qml/component"
import "qrc:///AicsKnowledgeBase/qml/global" import "qrc:///AicsKnowledgeBase/qml/global"

View File

@ -3,20 +3,13 @@
FileTransferListModel::FileTransferListModel(QObject *parent) FileTransferListModel::FileTransferListModel(QObject *parent)
: QAbstractListModel(parent) : QAbstractListModel(parent)
{ {
m_roleName.insert(kDownloadRole, "download");
m_roleName.insert(kIdRole, "fileId"); m_roleName.insert(kIdRole, "fileId");
m_roleName.insert(kNameRole, "name"); m_roleName.insert(kNameRole, "name");
m_roleName.insert(kCompletedSizeRole, "completedSize"); m_roleName.insert(kCompletedSizeRole, "completedSize");
m_roleName.insert(kTotalSizeRole, "totalSize"); m_roleName.insert(kTotalSizeRole, "totalSize");
m_roleName.insert(kSpeedRole, "speed"); m_roleName.insert(kSpeedRole, "speed");
m_roleName.insert(kPausedRole, "paused"); m_roleName.insert(kPausedRole, "paused");
m_data = {
{"v", "vivo", 1200, 2000},
{"m", "meizu", 1300, 1500},
{"a", "apple", 1400, 1400},
};
} }
@ -36,6 +29,8 @@ QVariant FileTransferListModel::data(const QModelIndex &index, int role) const
return QVariant(); return QVariant();
switch (role) { switch (role) {
case kDownloadRole:
return m_data.value(index.row()).download;
case kIdRole: case kIdRole:
return m_data.value(index.row()).id; return m_data.value(index.row()).id;
case kNameRole: case kNameRole:

View File

@ -7,6 +7,7 @@
struct FileItem struct FileItem
{ {
bool download = true;
QString id; QString id;
QString name; QString name;
int64_t completedSize; int64_t completedSize;
@ -38,7 +39,8 @@ public:
enum FileItemRole enum FileItemRole
{ {
kIdRole = Qt::UserRole + 1, kDownloadRole = Qt::UserRole + 1,
kIdRole,
kNameRole, kNameRole,
kCompletedSizeRole, kCompletedSizeRole,
kTotalSizeRole, kTotalSizeRole,

View File

@ -13,6 +13,88 @@ static const std::string baseUrl = "https://api.hammer-hfut.tk:233/aics/file/";
//static const std::string baseUrl = "http://127.0.0.1:4523/m1/2914957-0-6e5f2db1/"; //static const std::string baseUrl = "http://127.0.0.1:4523/m1/2914957-0-6e5f2db1/";
//static const std::string baseUrl = "https://quic.nginx.org/"; //static const std::string baseUrl = "https://quic.nginx.org/";
struct FileDownloading : FileItem
{
CURL *curlHandle = nullptr;
std::ofstream file;
};
std::unordered_map<QString, FileDownloading> downloadingMap;
std::mutex downloadingMapMutex;
struct FileUploading : FileItem
{
CURL *curlHandle = nullptr;
std::ifstream file;
};
std::unordered_map<QString, FileUploading> uploadingMap;
std::mutex uploadingMapMutex;
size_t writeDataFunc(const void *ptr, size_t size, size_t nmemb, void *obj)
{
auto buf = (FileDownloading *) obj;
auto writeSize = size * nmemb;
buf->file.write(reinterpret_cast<const char *>(ptr), writeSize);
return writeSize;
}
size_t readDataFunc(void *ptr, size_t size, size_t nmemb, void *obj)
{
auto buf = (FileUploading *) obj;
auto readSize = size * nmemb;
buf->file.read(reinterpret_cast<char *>(ptr), readSize);
return readSize;
}
static int xferinfo(void *p, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
{
auto fileTrans = (FileDownloading *) p;
curl_off_t speed = 0;
curl_easy_getinfo(fileTrans->curlHandle, CURLINFO_SPEED_DOWNLOAD_T, &speed);
fileTrans->completedSize += dlnow;
fileTrans->speed = speed;
qDebug() << std::format("Downloading: {} / {}, Speed: {}", fileTrans->completedSize, fileTrans->totalSize,
speed).c_str();
auto item = static_cast<FileItem>(*fileTrans);
QTimer::singleShot(0, qApp, [item]() {
FileTransferListModel::instance().setItem(item);
});
if (fileTrans->paused) {
qDebug() << "Pause";
return 1;
}
fileTrans->completedSize -= dlnow;
return 0;
}
static int uploadInfo(void *p, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
{
auto fileTrans = (FileUploading *) p;
curl_off_t speed = 0;
curl_easy_getinfo(fileTrans->curlHandle, CURLINFO_SPEED_UPLOAD_T, &speed);
fileTrans->completedSize += ulnow;
fileTrans->speed = speed;
qDebug() << std::format("Uploading: {} / {}, Speed: {}", fileTrans->completedSize, fileTrans->totalSize,
speed).c_str();
auto item = static_cast<FileItem>(*fileTrans);
QTimer::singleShot(0, qApp, [item]() {
FileTransferListModel::instance().setItem(item);
});
if (fileTrans->paused) {
qDebug() << "Pause";
return 1;
}
fileTrans->completedSize -= ulnow;
return 0;
}
static size_t writeDataCallback(void *contents, size_t size, size_t nmemb, void *userp) static size_t writeDataCallback(void *contents, size_t size, size_t nmemb, void *userp)
{ {
try { try {
@ -60,44 +142,44 @@ FileTransferManager::FileTransferManager(QObject *parent)
: QObject(parent) : QObject(parent)
{ {
std::string resData; std::string resData;
if (CURLE_OK == httpGet("file/hello", resData)) if (CURLE_OK == httpGet("hello", resData))
qDebug() << "HTTP3 OK\n" << resData.c_str(); qDebug() << "HTTP3 OK\n" << resData.c_str();
else else
qDebug() << "HTTP3 FAILED"; qDebug() << "HTTP3 FAILED";
} }
int64_t completedSize = 0; void
FileTransferManager::upload(const QUrl &fileUrl, const QString &fileId, const QString &ticket, const QString &fileName)
size_t readDataFunc(void *ptr, size_t size, size_t nmemb, void *file)
{
auto s = reinterpret_cast<QFile *>(file)->read(reinterpret_cast<char *>(ptr),
size * nmemb);
completedSize += s;
qDebug() << std::format("Uploading: {} / {}", completedSize,
reinterpret_cast<QFile *>(file)->size()).c_str();
return s;
}
void FileTransferManager::upload(const QUrl &fileUrl, const QString &fileId, const QString &ticket)
{ {
qDebug() << fileUrl.fileName(); qDebug() << fileUrl.fileName();
QFile file = fileUrl.toLocalFile(); QtConcurrent::run([fileUrl, fileId, ticket, fileName] {
if (!file.open(QIODevice::ReadOnly))
return;
auto fileSize = file.size(); auto fileSize = QFileInfo(fileUrl.toLocalFile()).size();
FileItem item{false, fileId, fileName, 0, fileSize};
QTimer::singleShot(0, qApp, [item]() {
FileTransferListModel::instance().insertItem(item);
});
uploadingMapMutex.lock();
auto [iter, _] = uploadingMap.emplace(item.id, (FileUploading) item);
uploadingMapMutex.unlock();
FileUploading &fileUploading = iter->second;
fileUploading.file = std::ifstream(fileUrl.toLocalFile().toLocal8Bit().toStdString(), std::ios::binary);
if (!fileUploading.file.is_open()) {
qDebug() << "Open File Failed";
return false;
}
CURLcode res; CURLcode res;
CURL *curl = curl_easy_init(); fileUploading.curlHandle = curl_easy_init();
if (!curl) if (!fileUploading.curlHandle)
return; return false;
curl_easy_setopt(curl, CURLOPT_URL, (baseUrl + "file/" + fileId.toStdString()).c_str());
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_DEFAULT_PROTOCOL, "https");
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3ONLY);
curl_httppost *formpost = nullptr; curl_httppost *formpost = nullptr;
curl_httppost *lastptr = nullptr; curl_httppost *lastptr = nullptr;
curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "ticket", curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "ticket",
@ -113,21 +195,35 @@ void FileTransferManager::upload(const QUrl &fileUrl, const QString &fileId, con
curl_formadd(&formpost, &lastptr, curl_formadd(&formpost, &lastptr,
CURLFORM_COPYNAME, "data", CURLFORM_COPYNAME, "data",
CURLFORM_STREAM, &file, CURLFORM_STREAM, &fileUploading,
CURLFORM_CONTENTLEN, fileSize, CURLFORM_CONTENTLEN, fileSize,
CURLFORM_END); CURLFORM_END);
curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); curl_easy_setopt(fileUploading.curlHandle, CURLOPT_URL, (baseUrl + fileId.toStdString()).c_str());
curl_easy_setopt(curl, CURLOPT_READFUNCTION, readDataFunc); curl_easy_setopt(fileUploading.curlHandle, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(fileUploading.curlHandle, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(fileUploading.curlHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3ONLY);
curl_easy_setopt(fileUploading.curlHandle, CURLOPT_HTTPPOST, formpost);
curl_easy_setopt(fileUploading.curlHandle, CURLOPT_READFUNCTION, readDataFunc);
curl_easy_setopt(fileUploading.curlHandle, CURLOPT_NOPROGRESS, false);
curl_easy_setopt(fileUploading.curlHandle, CURLOPT_XFERINFOFUNCTION, uploadInfo);
curl_easy_setopt(fileUploading.curlHandle, CURLOPT_XFERINFODATA, &fileUploading);
qDebug() << "Begin Upload"; qDebug() << "Begin Upload";
completedSize = 0;
res = curl_easy_perform(curl); res = curl_easy_perform(fileUploading.curlHandle);
if (res == CURLE_OK) if (res == CURLE_OK)
qDebug() << "Upload Success"; qDebug() << "Upload Success";
else else
qDebug() << "Upload Failed"; qDebug() << "Upload Failed";
curl_easy_cleanup(curl); curl_easy_cleanup(fileUploading.curlHandle);
curl_formfree(formpost); curl_formfree(formpost);
file.close(); fileUploading.file.close();
uploadingMapMutex.lock();
uploadingMap.erase(fileId);
uploadingMapMutex.unlock();
});
} }
@ -151,66 +247,19 @@ curl_off_t getHttpFileSize(const std::string &url)
return fileLength; return fileLength;
} }
struct FileTransfering : FileItem
{
CURL *curlHandle = nullptr;
std::ofstream file;
};
size_t receiveDataFunc(const void *ptr, size_t size, size_t nmemb, void *obj)
{
auto buf = (FileTransfering *) obj;
auto writeSize = size * nmemb;
buf->file.write(reinterpret_cast<const char *>(ptr), writeSize);
return writeSize;
}
static int xferinfo(void *p, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
{
auto fileTrans = (FileTransfering *) p;
curl_off_t speed = 0;
curl_easy_getinfo(fileTrans->curlHandle, CURLINFO_SPEED_DOWNLOAD_T, &speed);
fileTrans->completedSize += dlnow;
fileTrans->speed = speed;
qDebug() << std::format("Downloading: {} / {}, Speed: {}", fileTrans->completedSize, fileTrans->totalSize,
speed).c_str();
auto item = static_cast<FileItem>(*fileTrans);
QTimer::singleShot(0, qApp, [item]() {
FileTransferListModel::instance().setItem(item);
});
if (fileTrans->paused) {
qDebug() << "Pause";
return 1;
}
fileTrans->completedSize -= dlnow;
return 0;
}
std::unordered_map<QString, FileTransfering> transferMap;
std::mutex transferMapMutex;
bool httpDownload(const std::string &url, const FileItem &item) bool httpDownload(const std::string &url, const FileItem &item)
{ {
auto fileSize = getHttpFileSize(url);//获得文件大小。 auto fileSize = getHttpFileSize(url);//获得文件大小。
if (fileSize != -1) { if (fileSize == -1)
return false;
qDebug() << "FileSize: " << fileSize; qDebug() << "FileSize: " << fileSize;
transferMapMutex.lock(); downloadingMapMutex.lock();
auto [iter, _] = transferMap.emplace(item.id, (FileTransfering) item); auto [iter, _] = downloadingMap.emplace(item.id, (FileDownloading) item);
transferMapMutex.unlock(); downloadingMapMutex.unlock();
FileTransfering &obj = iter->second; FileDownloading &obj = iter->second;
obj.curlHandle = curl_easy_init(); obj.curlHandle = curl_easy_init();
if (!obj.curlHandle) if (!obj.curlHandle)
@ -222,16 +271,15 @@ bool httpDownload(const std::string &url, const FileItem &item)
qDebug() << "Open File Failed"; qDebug() << "Open File Failed";
curl_easy_cleanup(obj.curlHandle); curl_easy_cleanup(obj.curlHandle);
return false; return false;
} else { }
curl_easy_setopt(obj.curlHandle, CURLOPT_VERBOSE, 0L); curl_easy_setopt(obj.curlHandle, CURLOPT_VERBOSE, 0L);
curl_easy_setopt(obj.curlHandle, CURLOPT_HEADER, 0); curl_easy_setopt(obj.curlHandle, CURLOPT_HEADER, 0);
curl_easy_setopt(obj.curlHandle, CURLOPT_NOPROGRESS, 1L);
curl_easy_setopt(obj.curlHandle, CURLOPT_NOSIGNAL, 1L); curl_easy_setopt(obj.curlHandle, CURLOPT_NOSIGNAL, 1L);
curl_easy_setopt(obj.curlHandle, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(obj.curlHandle, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(obj.curlHandle, CURLOPT_URL, url.c_str()); curl_easy_setopt(obj.curlHandle, CURLOPT_URL, url.c_str());
curl_easy_setopt(obj.curlHandle, CURLOPT_WRITEDATA, (void *) &obj); curl_easy_setopt(obj.curlHandle, CURLOPT_WRITEDATA, (void *) &obj);
curl_easy_setopt(obj.curlHandle, CURLOPT_WRITEFUNCTION, receiveDataFunc); curl_easy_setopt(obj.curlHandle, CURLOPT_WRITEFUNCTION, writeDataFunc);
curl_easy_setopt(obj.curlHandle, CURLOPT_NOPROGRESS, false);//设为false 下面才能设置进度响应函数 curl_easy_setopt(obj.curlHandle, CURLOPT_NOPROGRESS, false);//设为false 下面才能设置进度响应函数
curl_easy_setopt(obj.curlHandle, CURLOPT_XFERINFOFUNCTION, xferinfo); curl_easy_setopt(obj.curlHandle, CURLOPT_XFERINFOFUNCTION, xferinfo);
curl_easy_setopt(obj.curlHandle, CURLOPT_XFERINFODATA, &obj); curl_easy_setopt(obj.curlHandle, CURLOPT_XFERINFODATA, &obj);
@ -241,15 +289,12 @@ bool httpDownload(const std::string &url, const FileItem &item)
curl_easy_cleanup(obj.curlHandle); curl_easy_cleanup(obj.curlHandle);
obj.file.close(); obj.file.close();
transferMapMutex.lock(); downloadingMapMutex.lock();
transferMap.erase(obj.id); downloadingMap.erase(obj.id);
transferMapMutex.unlock(); downloadingMapMutex.unlock();
return res == CURLE_OK; return res == CURLE_OK;
} }
} else
return false;
}
void FileTransferManager::download(QString fileId) void FileTransferManager::download(QString fileId)
{ {
@ -285,7 +330,7 @@ void FileTransferManager::download(QString fileId)
int64_t size = getHttpFileSize(fileUrl); int64_t size = getHttpFileSize(fileUrl);
FileItem item{fileId, fileId, 0, size}; FileItem item{true, fileId, fileId, 0, size};
auto res = httpDownload(fileUrl, item); auto res = httpDownload(fileUrl, item);
@ -298,9 +343,9 @@ void FileTransferManager::download(QString fileId)
void FileTransferManager::pause(const QString &fileId) void FileTransferManager::pause(const QString &fileId)
{ {
transferMapMutex.lock(); downloadingMapMutex.lock();
transferMap[fileId].paused = true; downloadingMap[fileId].paused = true;
transferMapMutex.unlock(); downloadingMapMutex.unlock();
} }
QString FileTransferManager::getFileMd5(const QUrl &fileUrl) QString FileTransferManager::getFileMd5(const QUrl &fileUrl)
@ -358,7 +403,7 @@ void FileTransferManager::resume(const QString &fileId)
int64_t size = getHttpFileSize(fileUrl); int64_t size = getHttpFileSize(fileUrl);
FileItem item{fileId, fileId, 0, size}; FileItem item{true, fileId, fileId, 0, size};
QTimer::singleShot(0, qApp, [this, item]() { QTimer::singleShot(0, qApp, [this, item]() {
FileTransferListModel::instance().insertItem(item); FileTransferListModel::instance().insertItem(item);
}); });
@ -371,3 +416,8 @@ void FileTransferManager::resume(const QString &fileId)
} }
QString FileTransferManager::getFileName(const QUrl &fileUrl)
{
return fileUrl.fileName();
}

View File

@ -19,9 +19,10 @@ Q_OBJECT
public: public:
explicit FileTransferManager(QObject *parent = nullptr); explicit FileTransferManager(QObject *parent = nullptr);
Q_INVOKABLE QString getFileName(const QUrl& fileUrl);
Q_INVOKABLE int64_t getFileSize(const QUrl& fileUrl); Q_INVOKABLE int64_t getFileSize(const QUrl& fileUrl);
Q_INVOKABLE QString getFileMd5(const QUrl& fileUrl); Q_INVOKABLE QString getFileMd5(const QUrl& fileUrl);
Q_INVOKABLE void upload(const QUrl& fileUrl, const QString& fileId, const QString& ticket); Q_INVOKABLE void upload(const QUrl& fileUrl, const QString& fileId, const QString& ticket, const QString &fileName);
Q_INVOKABLE void download(QString fileId); Q_INVOKABLE void download(QString fileId);
Q_INVOKABLE void pause(const QString& fileId); Q_INVOKABLE void pause(const QString& fileId);