diff --git a/AicsKnowledgeBase/qml/component/FileList.qml b/AicsKnowledgeBase/qml/component/FileList.qml index e992e3e..5ea92ec 100644 --- a/AicsKnowledgeBase/qml/component/FileList.qml +++ b/AicsKnowledgeBase/qml/component/FileList.qml @@ -2,6 +2,7 @@ import QtQuick 2.15 import QtQuick.Layouts import FluentUI import QtQuick.Dialogs +import QtQuick.Controls import "qrc:///AicsKnowledgeBase/qml/global" import SignalFileOperation 1.0 import AicsKB.FileTransferManager @@ -44,43 +45,76 @@ Item { "uuid": uuid }]) } - FluButton { - Layout.alignment: Qt.AlignRight - text: "涓婁紶" - onClicked: function () { - console.log("click") - fileDialog.open() - } - FileDialog { - id: fileDialog - onAccepted: function () { - let name = FileTransferManager.getFileName(selectedFile) - const size = FileTransferManager.getFileSize( - selectedFile) - const md5 = FileTransferManager.getFileMd5(selectedFile) - if (size <= 0 || md5 === '') - return - var body = { - "name": name, - "brief": "brief", - "size": size, - "md5": md5, - "tags": [], - "parentId": header.items.length - !== 0 ? header.items[header.items.length - 1].uuid : null + RowLayout { + Layout.preferredWidth: parent.width + Item { + Layout.alignment: Qt.AlignRight + height: 28 + width: 28 + InputDialog { + id: dialog + title: "新建文件夹" + buttonFlags: FluContentDialog.PositiveButton + | FluContentDialog.NegativeButton + negativeText: "取消" + positiveText: "确定" + message: "请输入文件夹名称" + onPositiveClicked: text => { + console.log(text) + } + } + + Image { + source: "qrc:/AicsKnowledgeBase/res/createFolder.png" + anchors.fill: parent + } + MouseArea { + anchors.fill: parent + onClicked: { + dialog.open() + } + } + } + FluButton { + Layout.alignment: Qt.AlignRight + text: "上传" + onClicked: function () { + console.log("click") + fileDialog.open() + } + FileDialog { + id: fileDialog + onAccepted: function () { + let name = FileTransferManager.getFileName( + selectedFile) + const size = FileTransferManager.getFileSize( + selectedFile) + const md5 = FileTransferManager.getFileMd5( + selectedFile) + if (size <= 0 || md5 === '') + return + var body = { + "name": name, + "brief": "brief", + "size": size, + "md5": md5, + "tags": [], + "parentId": header.items.length !== 0 ? header.items[header.items.length - 1].uuid : null + } + console.log("begin") + console.log(JSON.stringify(body)) + Request.post("knowledge/file", + JSON.stringify(body), + function (res, data) { + console.log(res) + console.log(data) + FileTransferManager.upload( + selectedFile, data.id, + data.ticket, name) + }, function (res, data) { + console.log(res) + }) } - console.log("begin") - console.log(JSON.stringify(body)) - Request.post("knowledge/file", JSON.stringify(body), - function (res, data) { - console.log(res) - console.log(data) - FileTransferManager.upload( - selectedFile, data.id, - data.ticket, name) - }, function (res, data) { - console.log(res) - }) } } } @@ -151,6 +185,7 @@ Item { === 0 ? "null" : header.items[header.items.length - 1].uuid Request.get("/knowledge/" + uuid, function (response) { var data = JSON.parse(response) + console.log(response) console.log(data.knowledgeFileAttribute) fileListModel.clear() var files = data.children @@ -186,7 +221,7 @@ Item { } fileListModel.append(modelItem) } - console.log(fileListModel) + console.log(fileListModel.count) listView.currentIndex = -1 }) } diff --git a/AicsKnowledgeBase/qml/component/FileListItem.qml b/AicsKnowledgeBase/qml/component/FileListItem.qml index 7516c72..2eaab06 100644 --- a/AicsKnowledgeBase/qml/component/FileListItem.qml +++ b/AicsKnowledgeBase/qml/component/FileListItem.qml @@ -39,13 +39,13 @@ FluArea { Image { id: icon source: type ? "qrc:/AicsKnowledgeBase/res/" + type + ".png" : "" - Layout.preferredHeight: 32 - Layout.preferredWidth: 32 + Layout.preferredHeight: 24 + Layout.preferredWidth: 24 } FluText { id: title font.bold: true - font.pointSize: 15 + font.pointSize: 12 text: fileItem.title } } diff --git a/AicsKnowledgeBase/qml/component/InputDialog.qml b/AicsKnowledgeBase/qml/component/InputDialog.qml new file mode 100644 index 0000000..ef60cda --- /dev/null +++ b/AicsKnowledgeBase/qml/component/InputDialog.qml @@ -0,0 +1,122 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Window +import FluentUI + +FluPopup { + id: popup + property string title: "Title" + property string message: "Message" + property string neutralText: "Neutral" + property string negativeText: "Negative" + property string positiveText: "Positive" + signal neutralClicked + signal negativeClicked + signal positiveClicked(string text) + enum ButtonFlag { + NegativeButton = 1, + NeutralButton = 2, + PositiveButton = 4 + } + property int buttonFlags: FluContentDialog.NegativeButton | FluContentDialog.PositiveButton + property var minWidth: { + if (Window.window == null) + return 400 + return Math.min(Window.window.width, 400) + } + focus: true + Rectangle { + id: layout_content + anchors.fill: parent + implicitWidth: minWidth + implicitHeight: text_title.height + text_message.height + layout_actions.height + color: 'transparent' + radius: 5 + FluText { + id: text_title + font: FluTextStyle.TitleLarge + text: title + topPadding: 20 + leftPadding: 20 + rightPadding: 20 + wrapMode: Text.WrapAnywhere + anchors { + top: parent.top + left: parent.left + right: parent.right + } + } + FluTextBox { + id: text_message + font: FluTextStyle.Body + wrapMode: Text.WrapAnywhere + placeholderText: message + topPadding: 14 + leftPadding: 20 + rightPadding: 20 + bottomPadding: 14 + anchors { + top: text_title.bottom + left: parent.left + right: parent.right + } + } + Rectangle { + id: layout_actions + height: 68 + radius: 5 + color: FluTheme.dark ? Qt.rgba( + 32 / 255, 32 / 255, 32 / 255, + blurBackground ? blurOpacity - 0.4 : 1) : Qt.rgba( + 243 / 255, 243 / 255, 243 / 255, + blurBackground ? blurOpacity - 0.4 : 1) + anchors { + top: text_message.bottom + left: parent.left + right: parent.right + } + RowLayout { + anchors { + centerIn: parent + margins: spacing + fill: parent + } + spacing: 15 + FluButton { + id: neutral_btn + Layout.fillWidth: true + Layout.fillHeight: true + visible: popup.buttonFlags & FluContentDialog.NeutralButton + text: neutralText + onClicked: { + popup.close() + neutralClicked() + } + } + FluButton { + id: negative_btn + Layout.fillWidth: true + Layout.fillHeight: true + visible: popup.buttonFlags & FluContentDialog.NegativeButton + text: negativeText + onClicked: { + popup.close() + negativeClicked() + } + } + FluFilledButton { + id: positive_btn + Layout.fillWidth: true + Layout.fillHeight: true + visible: popup.buttonFlags & FluContentDialog.PositiveButton + text: positiveText + onClicked: { + popup.close() + positiveClicked(text_message.text) + } + } + } + } + } +} diff --git a/AicsKnowledgeBase/qml/component/UploadButton.qml b/AicsKnowledgeBase/qml/component/UploadButton.qml new file mode 100644 index 0000000..200c825 --- /dev/null +++ b/AicsKnowledgeBase/qml/component/UploadButton.qml @@ -0,0 +1,146 @@ +import QtQuick 2.15 +import QtQuick.Layouts +import FluentUI +import QtQuick.Dialogs +import "qrc:///AicsKnowledgeBase/qml/global" +import SignalFileOperation 1.0 +import AicsKB.FileTransferManager + +FluButton { + property var header + property var currentSelectedFile: null + + Layout.alignment: Qt.AlignRight + text: "涓婁紶" + onClicked: function () { + console.log("click") + fileDialog.open() + } + FileDialog { + id: fileDialog + onAccepted: function () { + currentSelectedFile = selectedFile + popup.open() + } + onRejected: currentSelectedFile = null + } + + FluPopup { + id: popup + focus: true + Rectangle { + id: layout_content + anchors.fill: parent + implicitWidth: Window.window == null ? 400 : Math.min( + Window.window.width, 400) + + implicitHeight: text_title.height + text_message.height + layout_actions.height + color: 'transparent' + radius: 5 + FluText { + id: text_title + font: FluTextStyle.TitleLarge + text: "涓婁紶鐭ヨ瘑鏂囦欢" + topPadding: 20 + leftPadding: 20 + rightPadding: 20 + wrapMode: Text.WrapAnywhere + anchors { + top: parent.top + left: parent.left + right: parent.right + } + } + FluText { + id: text_message + font: FluTextStyle.Body + wrapMode: Text.WrapAnywhere + text: "content" + topPadding: 14 + leftPadding: 20 + rightPadding: 20 + bottomPadding: 14 + anchors { + top: text_title.bottom + left: parent.left + right: parent.right + } + } + Rectangle { + id: layout_actions + height: 68 + radius: 5 + color: FluTheme.dark ? Qt.rgba( + 32 / 255, 32 / 255, 32 / 255, + blurBackground ? blurOpacity - 0.4 : 1) : Qt.rgba( + 243 / 255, 243 / 255, 243 / 255, + blurBackground ? blurOpacity - 0.4 : 1) + anchors { + top: text_message.bottom + left: parent.left + right: parent.right + } + RowLayout { + anchors { + centerIn: parent + margins: spacing + fill: parent + } + spacing: 15 + FluButton { + id: negative_btn + Layout.fillWidth: true + Layout.fillHeight: true + text: "鍙栨秷" + onClicked: { + popup.close() + } + } + FluFilledButton { + id: positive_btn + Layout.fillWidth: true + Layout.fillHeight: true + text: "涓婁紶" + onClicked: { + popup.close() + let name = FileTransferManager.getFileName( + currentSelectedFile) + const size = FileTransferManager.getFileSize( + currentSelectedFile) + const md5 = FileTransferManager.getFileMd5( + currentSelectedFile) + if (size <= 0 || md5 === '') + return + var body = { + "name": name, + "brief": "brief", + "size": size, + "md5": md5, + "tags": [], + "parentId": currentParentId() + } + console.log("begin") + console.log(JSON.stringify(body)) + Request.post("knowledge/file", + JSON.stringify(body), + function (res, data) { + console.log(res) + console.log(data) + FileTransferManager.upload( + currentSelectedFile, + data.id, data.ticket, + name) + }, function (res, data) { + console.log(res) + }) + } + } + } + } + } + } + + function currentParentId() { + return header.items.length !== 0 ? header.items[header.items.length - 1].uuid : null + } +} diff --git a/AicsKnowledgeBase/qml/page/ContentPage.qml b/AicsKnowledgeBase/qml/page/ContentPage.qml index 8fda1f2..be13e9b 100644 --- a/AicsKnowledgeBase/qml/page/ContentPage.qml +++ b/AicsKnowledgeBase/qml/page/ContentPage.qml @@ -208,9 +208,7 @@ FluArea { } onClicked: { emit: content_area.download(content_area.knowledgeFileId) - // FileTransferManager.download(content_area.knowledgeFileId) - FileTransferManager.download( - "4973c59e-3ba1-41f2-a57e-3b53c2b5e2a4") + FileTransferManager.download(content_area.knowledgeFileId) } } } diff --git a/AicsKnowledgeBase/res/createFolder.png b/AicsKnowledgeBase/res/createFolder.png new file mode 100644 index 0000000..1cac6e8 Binary files /dev/null and b/AicsKnowledgeBase/res/createFolder.png differ diff --git a/AicsKnowledgeBase/src/FileTransferManager.cpp b/AicsKnowledgeBase/src/FileTransferManager.cpp index 2f68638..35888fc 100644 --- a/AicsKnowledgeBase/src/FileTransferManager.cpp +++ b/AicsKnowledgeBase/src/FileTransferManager.cpp @@ -30,6 +30,24 @@ struct FileUploading : FileItem std::unordered_map uploadingMap; std::mutex uploadingMapMutex; +CURL *initCurlWithCommonOptions() +{ + CURL *curl = curl_easy_init(); + if (!curl) { + qCritical() << "CURL INIT FAILED!"; + return nullptr; + } + curl_easy_setopt(curl, CURLOPT_VERBOSE, true); + curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3000); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true); + curl_easy_setopt(curl, CURLOPT_DEFAULT_PROTOCOL, "https"); + //curl_easy_setopt(easy_handle, CURLOPT_SSL_VERIFYPEER, 0L); + //curl_easy_setopt(easy_handle, CURLOPT_SSL_VERIFYHOST, 0L);//涓嶉獙璇佽瘉涔﹀拰HOST + return curl; +} + + size_t writeDataFunc(const void *ptr, size_t size, size_t nmemb, void *obj) { auto buf = (FileDownloading *) obj; @@ -41,9 +59,8 @@ size_t writeDataFunc(const void *ptr, size_t size, size_t nmemb, void *obj) 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(ptr), readSize); - return readSize; + buf->file.read(reinterpret_cast(ptr), size * nmemb); + return buf->file.gcount(); } static int xferinfo(void *p, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) @@ -113,25 +130,18 @@ static size_t writeDataCallback(void *contents, size_t size, size_t nmemb, void } CURLcode -httpGet(const std::string &url, std::string &response, long httpVersion = CURL_HTTP_VERSION_3ONLY, int timeout = 3000) +httpGet(const std::string &url, std::string &response) { CURLcode res; // 鑾峰彇easy handle - CURL *easy_handle = curl_easy_init(); + CURL *easy_handle = initCurlWithCommonOptions(); if (!easy_handle) return CURLE_FAILED_INIT; - // 璁剧疆easy handle灞炴 - curl_easy_setopt(easy_handle, CURLOPT_VERBOSE, 1L); - curl_easy_setopt(easy_handle, CURLOPT_HTTP_VERSION, httpVersion); curl_easy_setopt(easy_handle, CURLOPT_URL, (baseUrl + url).c_str()); curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, writeDataCallback); - curl_easy_setopt(easy_handle, CURLOPT_TIMEOUT, timeout); curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, (void *) &response); - curl_easy_setopt(easy_handle, CURLOPT_FOLLOWLOCATION, 1L); - //curl_easy_setopt(easy_handle, CURLOPT_DEFAULT_PROTOCOL, "https"); - //curl_easy_setopt(easy_handle, CURLOPT_SSL_VERIFYPEER, 0L); - //curl_easy_setopt(easy_handle, CURLOPT_SSL_VERIFYHOST, 0L);//涓嶉獙璇佽瘉涔﹀拰HOST + // 鎵ц鏁版嵁璇锋眰 res = curl_easy_perform(easy_handle); curl_easy_cleanup(easy_handle); @@ -148,12 +158,40 @@ FileTransferManager::FileTransferManager(QObject *parent) qDebug() << "HTTP3 FAILED"; } +bool uploadComplete(const QString &fileId, const QString &ticket) +{ + CURLcode res; + auto curlHandle = initCurlWithCommonOptions(); + if (!curlHandle) return false; + + curl_httppost *formpost = nullptr; + curl_httppost *lastptr = nullptr; + curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "ticket", + CURLFORM_COPYCONTENTS, ticket.toStdString().c_str(), + CURLFORM_END); + + curl_easy_setopt(curlHandle, CURLOPT_URL, (baseUrl + "File/" + fileId.toStdString() + "/complete").c_str()); + curl_easy_setopt(curlHandle, CURLOPT_HTTPPOST, formpost); + + res = curl_easy_perform(curlHandle); + curl_easy_cleanup(curlHandle); + curl_formfree(formpost); + return res == CURLE_OK; +} + void FileTransferManager::upload(const QUrl &fileUrl, const QString &fileId, const QString &ticket, const QString &fileName) { qDebug() << fileUrl.fileName(); QtConcurrent::run([fileUrl, fileId, ticket, fileName] { + std::string resData; + if (CURLE_OK != httpGet("File/" + fileId.toStdString() + "/status", resData)) + return false; + + qDebug() << resData.c_str(); + auto resJson = QJsonDocument::fromJson(resData.c_str()); + //if(!resJson["isCompleted"].toBool()) auto fileSize = QFileInfo(fileUrl.toLocalFile()).size(); @@ -176,7 +214,7 @@ FileTransferManager::upload(const QUrl &fileUrl, const QString &fileId, const QS } CURLcode res; - fileUploading.curlHandle = curl_easy_init(); + fileUploading.curlHandle = initCurlWithCommonOptions(); if (!fileUploading.curlHandle) return false; @@ -194,27 +232,33 @@ FileTransferManager::upload(const QUrl &fileUrl, const QString &fileId, const QS CURLFORM_END); curl_formadd(&formpost, &lastptr, - CURLFORM_COPYNAME, "data", + CURLFORM_COPYNAME, "file", + CURLFORM_FILENAME, fileName.toLocal8Bit().toStdString().c_str(), CURLFORM_STREAM, &fileUploading, CURLFORM_CONTENTLEN, fileSize, CURLFORM_END); - curl_easy_setopt(fileUploading.curlHandle, CURLOPT_URL, (baseUrl + fileId.toStdString()).c_str()); - 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_URL, (baseUrl + "File/" + fileId.toStdString()).c_str()); 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); + curl_easy_setopt(fileUploading.curlHandle, CURLOPT_WRITEFUNCTION, writeDataCallback); + std::string response; + curl_easy_setopt(fileUploading.curlHandle, CURLOPT_WRITEDATA, (void *) &response); qDebug() << "Begin Upload"; + qDebug() << ticket.toStdString().c_str(); + qDebug() << std::to_string(fileSize).c_str(); + + res = curl_easy_perform(fileUploading.curlHandle); if (res == CURLE_OK) qDebug() << "Upload Success"; else qDebug() << "Upload Failed"; + qDebug() << response.c_str(); curl_easy_cleanup(fileUploading.curlHandle); curl_formfree(formpost); fileUploading.file.close(); @@ -223,6 +267,8 @@ FileTransferManager::upload(const QUrl &fileUrl, const QString &fileId, const QS uploadingMap.erase(fileId); uploadingMapMutex.unlock(); + if (res == CURLE_OK) + uploadComplete(fileId, ticket); }); } @@ -231,11 +277,10 @@ curl_off_t getHttpFileSize(const std::string &url) { curl_off_t fileLength = 0; - CURL *handle = curl_easy_init(); + CURL *handle = initCurlWithCommonOptions(); curl_easy_setopt(handle, CURLOPT_URL, url.c_str()); curl_easy_setopt(handle, CURLOPT_HEADER, 0); //鍙渶瑕乭eader澶 curl_easy_setopt(handle, CURLOPT_NOBODY, 1); //涓嶉渶瑕乥ody - curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L); if (curl_easy_perform(handle) == CURLE_OK) curl_easy_getinfo(handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &fileLength); @@ -261,7 +306,7 @@ bool httpDownload(const std::string &url, const FileItem &item) FileDownloading &obj = iter->second; - obj.curlHandle = curl_easy_init(); + obj.curlHandle = initCurlWithCommonOptions(); if (!obj.curlHandle) return false; obj.totalSize = fileSize;//鍘熷鏂囦欢澶у皬 @@ -273,10 +318,8 @@ bool httpDownload(const std::string &url, const FileItem &item) return false; } - curl_easy_setopt(obj.curlHandle, CURLOPT_VERBOSE, 0L); curl_easy_setopt(obj.curlHandle, CURLOPT_HEADER, 0); curl_easy_setopt(obj.curlHandle, CURLOPT_NOSIGNAL, 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_WRITEDATA, (void *) &obj); curl_easy_setopt(obj.curlHandle, CURLOPT_WRITEFUNCTION, writeDataFunc); @@ -296,11 +339,8 @@ bool httpDownload(const std::string &url, const FileItem &item) return res == CURLE_OK; } -void FileTransferManager::download(QString fileId) +void FileTransferManager::download(const QString& fileId) { - static int _fileId = 0; - //fileId = QString::number(_fileId++); - QtConcurrent::run([fileId, this] { qDebug() << "Start Get"; @@ -321,7 +361,7 @@ void FileTransferManager::download(QString fileId) FileTransferListModel::instance().insertItem(item); });*/ - auto fileUrl = "https://curl.se/download/curl-8.1.2.zip"; + auto fileUrl = "File/" + fileId.toStdString(); /* curl_slist *headers = nullptr; @@ -377,7 +417,7 @@ void FileTransferManager::resume(const QString &fileId) qDebug() << "Start Get"; std::string resData; - if (CURLE_OK != httpGet("File/" + fileId.toStdString() + "/status", resData, CURL_HTTP_VERSION_1_1)) + if (CURLE_OK != httpGet("File/" + fileId.toStdString() + "/status", resData)) return; qDebug() << resData.c_str(); diff --git a/AicsKnowledgeBase/src/FileTransferManager.h b/AicsKnowledgeBase/src/FileTransferManager.h index 256aec0..2f2406a 100644 --- a/AicsKnowledgeBase/src/FileTransferManager.h +++ b/AicsKnowledgeBase/src/FileTransferManager.h @@ -24,7 +24,7 @@ public: Q_INVOKABLE QString getFileMd5(const QUrl& fileUrl); 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(const QString& fileId); Q_INVOKABLE void pause(const QString& fileId); Q_INVOKABLE void resume(const QString& fileId);