From 71ea3dbd0100651205b123ed5e514812607a3784 Mon Sep 17 00:00:00 2001 From: Gary Wang Date: Mon, 27 Apr 2026 15:06:59 +0800 Subject: [PATCH] able to receive file --- CMakeLists.txt | 2 +- src/app/AppController.cpp | 202 ++++++++++++++++- src/app/AppController.h | 32 ++- src/app/qml/main.qml | 209 +++++++++++++++++- src/core/CMakeLists.txt | 5 +- src/core/include/LocalSendCore/HttpServer.h | 22 +- .../LocalSendCore/MulticastDiscovery.h | 1 + .../include/LocalSendCore/SessionManager.h | 3 +- src/core/src/HttpServer.cpp | 108 ++++++++- src/core/src/MulticastDiscovery.cpp | 40 +++- src/core/src/SessionManager.cpp | 13 +- 11 files changed, 605 insertions(+), 32 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 89b983a..820216e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ option(WITH_HTTP_SERVER "Build with QHttpServer support" ON) find_package(Qt6 REQUIRED COMPONENTS Core Network Quick QuickControls2) if(WITH_HTTP_SERVER) - find_package(Qt6 COMPONENTS HttpServer) + find_package(Qt6 6.8 COMPONENTS HttpServer) endif() add_subdirectory(src/core) diff --git a/src/app/AppController.cpp b/src/app/AppController.cpp index 7382ca1..34216fd 100644 --- a/src/app/AppController.cpp +++ b/src/app/AppController.cpp @@ -1,5 +1,11 @@ #include "AppController.h" #include +#include +#include +#include +#include +#include +#include AppController::AppController(QObject* parent) : QObject(parent) @@ -33,8 +39,20 @@ void AppController::initialize() this, &AppController::onDeviceDiscovered); connect(m_discovery, &LocalSend::DiscoveryManager::deviceLost, this, &AppController::onDeviceLost); + connect(m_server, &LocalSend::HttpServer::registerRequest, + this, &AppController::onRegisterRequest); connect(m_server, &LocalSend::HttpServer::prepareUploadRequest, this, &AppController::onPrepareUploadRequest); + connect(m_server, &LocalSend::HttpServer::uploadRequest, + this, &AppController::onUploadRequest); + connect(m_sessions, &LocalSend::SessionManager::receiveSessionAccepted, + this, &AppController::onSessionAccepted); + connect(m_sessions, &LocalSend::SessionManager::receiveSessionDeclined, + this, &AppController::onSessionDeclined); + connect(m_sessions, &LocalSend::SessionManager::receiveProgress, + this, &AppController::onReceiveProgress); + connect(m_sessions, &LocalSend::SessionManager::receiveSessionCompleted, + this, &AppController::onReceiveCompleted); startDiscovery(); } @@ -76,6 +94,32 @@ void AppController::setPort(quint16 port) } } +QString AppController::downloadPath() const +{ + return m_settings->downloadDir(); +} + +void AppController::setDownloadPath(const QString& path) +{ + if (m_settings->downloadDir() != path) { + m_settings->setDownloadDir(path); + emit downloadPathChanged(); + } +} + +bool AppController::quickSave() const +{ + return m_settings->quickSave(); +} + +void AppController::setQuickSave(bool enabled) +{ + if (m_settings->quickSave() != enabled) { + m_settings->setQuickSave(enabled); + emit quickSaveChanged(); + } +} + QVariantList AppController::devices() const { QVariantList result; @@ -112,6 +156,64 @@ void AppController::refreshDevices() m_discovery->startScan(); } +void AppController::acceptReceive(const QString& sessionId) +{ + qDebug() << "[AppController] acceptReceive called with sessionId:" << sessionId; + + LocalSend::ReceiveSession session = m_sessions->receiveSession(sessionId); + if (session.sessionId.isEmpty()) { + qWarning() << "[AppController] Session not found for sessionId:" << sessionId; + return; + } + + QString baseDir = downloadPath(); + QDir dir(baseDir); + if (!dir.exists()) { + dir.mkpath(QStringLiteral(".")); + } + + QMap destinationPaths; + for (auto it = session.files.constBegin(); it != session.files.constEnd(); ++it) { + QString filePath = generateUniqueFilePath(baseDir, it->file.fileName); + destinationPaths.insert(it.key(), filePath); + qDebug() << "[AppController] File" << it.key() << "->" << filePath; + } + + m_sessions->acceptReceiveSession(sessionId, destinationPaths); +} + +void AppController::declineReceive(const QString& sessionId) +{ + qDebug() << "[AppController] declineReceive called with sessionId:" << sessionId; + m_sessions->declineReceiveSession(sessionId); +} + +QString AppController::generateUniqueFilePath(const QString& baseDir, const QString& fileName) const +{ + QString filePath = baseDir + QDir::separator() + fileName; + QFileInfo info(filePath); + + if (!info.exists()) { + return filePath; + } + + QString baseName = info.completeBaseName(); + QString suffix = info.suffix(); + + int counter = 1; + while (info.exists()) { + QString newName = QString(QStringLiteral("%1 (%2)")).arg(baseName).arg(counter); + if (!suffix.isEmpty()) { + newName += QStringLiteral(".") + suffix; + } + filePath = baseDir + QDir::separator() + newName; + info.setFile(filePath); + counter++; + } + + return filePath; +} + void AppController::onDeviceDiscovered(const LocalSend::Device& device) { m_devices.insert(device.fingerprint, device); @@ -124,9 +226,33 @@ void AppController::onDeviceLost(const QString& fingerprint) emit devicesChanged(); } -void AppController::onPrepareUploadRequest(const LocalSend::PrepareUploadRequestDto& dto, +void AppController::onRegisterRequest(const LocalSend::RegisterDto& dto, const QHostAddress& sender) +{ + qDebug() << "[AppController] onRegisterRequest from:" << sender.toString() + << "alias:" << dto.alias << "fingerprint:" << dto.fingerprint; + + LocalSend::Device device; + device.ip = sender.toString(); + device.port = dto.port; + device.protocol = dto.protocol; + device.alias = dto.alias; + device.fingerprint = dto.fingerprint; + device.deviceModel = dto.deviceModel; + device.deviceType = dto.deviceType; + device.version = dto.version; + device.download = dto.download; + device.lastSeen = QDateTime::currentDateTime(); + + m_devices.insert(device.fingerprint, device); + emit devicesChanged(); +} + +void AppController::onPrepareUploadRequest(const QString& httpSessionId, + const LocalSend::PrepareUploadRequestDto& dto, const QHostAddress& sender) { + qDebug() << "[AppController] onPrepareUploadRequest received, httpSessionId:" << httpSessionId; + LocalSend::Device device; device.ip = sender.toString(); device.alias = dto.info.alias; @@ -135,7 +261,8 @@ void AppController::onPrepareUploadRequest(const LocalSend::PrepareUploadRequest device.deviceType = dto.info.deviceType; device.version = dto.info.version; - QString sessionId = m_sessions->createReceiveSession(device, dto.files); + QString sessionId = m_sessions->createReceiveSession(device, dto.files, httpSessionId); + qDebug() << "[AppController] Created receive session, sessionId:" << sessionId; QVariantList filesList; for (auto it = dto.files.constBegin(); it != dto.files.constEnd(); ++it) { @@ -147,5 +274,74 @@ void AppController::onPrepareUploadRequest(const LocalSend::PrepareUploadRequest filesList.append(file); } - emit receiveRequest(sessionId, dto.info.alias, filesList); + emit receiveRequest(sessionId, dto.info.alias, sender.toString(), filesList); +} + +void AppController::onUploadRequest(const QString& sessionId, const QString& fileId, + const QString& token, const QByteArray& data) +{ + LocalSend::ReceiveSession session = m_sessions->receiveSession(sessionId); + if (session.sessionId.isEmpty()) { + return; + } + + if (!session.files.contains(fileId)) { + return; + } + + const LocalSend::FileTransfer& transfer = session.files[fileId]; + if (transfer.token != token) { + emit receiveError(sessionId, QStringLiteral("Invalid token")); + return; + } + + QString filePath = transfer.destinationPath; + if (filePath.isEmpty()) { + emit receiveError(sessionId, QStringLiteral("No destination path")); + return; + } + + QFile file(filePath); + if (!file.open(QIODevice::WriteOnly)) { + emit receiveError(sessionId, QStringLiteral("Cannot open file: ") + file.errorString()); + return; + } + + qint64 written = file.write(data); + file.close(); + + if (written != data.size()) { + m_sessions->failReceiveFile(sessionId, fileId); + emit receiveError(sessionId, QStringLiteral("Write error")); + return; + } + + m_sessions->updateReceiveProgress(sessionId, fileId, written); + + if (written >= transfer.file.size) { + m_sessions->completeReceiveFile(sessionId, fileId); + } +} + +void AppController::onSessionAccepted(const QString& sessionId, const QMap& tokens) +{ + qDebug() << "[AppController] onSessionAccepted, sessionId:" << sessionId; + qDebug() << "[AppController] Tokens:" << tokens; + m_server->respondToPrepareUpload(sessionId, true, tokens); +} + +void AppController::onSessionDeclined(const QString& sessionId) +{ + qDebug() << "[AppController] onSessionDeclined, sessionId:" << sessionId; + m_server->respondToPrepareUpload(sessionId, false); +} + +void AppController::onReceiveProgress(const QString& sessionId, const QString& fileId, double progress) +{ + emit receiveProgress(sessionId, fileId, progress); +} + +void AppController::onReceiveCompleted(const QString& sessionId) +{ + emit receiveCompleted(sessionId); } diff --git a/src/app/AppController.h b/src/app/AppController.h index 58bb1db..3380bc8 100644 --- a/src/app/AppController.h +++ b/src/app/AppController.h @@ -2,6 +2,7 @@ #include #include +#include #include "LocalSendCore/DiscoveryManager.h" #include "LocalSendCore/HttpServer.h" #include "LocalSendCore/HttpClient.h" @@ -16,6 +17,8 @@ class AppController : public QObject Q_PROPERTY(quint16 port READ port WRITE setPort NOTIFY portChanged) Q_PROPERTY(QVariantList devices READ devices NOTIFY devicesChanged) Q_PROPERTY(bool serverRunning READ serverRunning NOTIFY serverRunningChanged) + Q_PROPERTY(QString downloadPath READ downloadPath WRITE setDownloadPath NOTIFY downloadPathChanged) + Q_PROPERTY(bool quickSave READ quickSave WRITE setQuickSave NOTIFY quickSaveChanged) public: explicit AppController(QObject* parent = nullptr); @@ -29,26 +32,50 @@ public: quint16 port() const; void setPort(quint16 port); + QString downloadPath() const; + void setDownloadPath(const QString& path); + + bool quickSave() const; + void setQuickSave(bool enabled); + QVariantList devices() const; bool serverRunning() const; Q_INVOKABLE void startDiscovery(); Q_INVOKABLE void stopDiscovery(); Q_INVOKABLE void refreshDevices(); + + Q_INVOKABLE void acceptReceive(const QString& sessionId); + Q_INVOKABLE void declineReceive(const QString& sessionId); signals: void aliasChanged(); void portChanged(); + void downloadPathChanged(); + void quickSaveChanged(); void devicesChanged(); void serverRunningChanged(); - void receiveRequest(const QString& sessionId, const QString& senderAlias, const QVariantList& files); + void receiveRequest(const QString& sessionId, const QString& senderAlias, + const QString& senderIp, const QVariantList& files); + void receiveProgress(const QString& sessionId, const QString& fileId, double progress); + void receiveCompleted(const QString& sessionId); + void receiveError(const QString& sessionId, const QString& error); void sendProgress(const QString& sessionId, double progress); void sendCompleted(const QString& sessionId); private slots: void onDeviceDiscovered(const LocalSend::Device& device); void onDeviceLost(const QString& fingerprint); - void onPrepareUploadRequest(const LocalSend::PrepareUploadRequestDto& dto, const QHostAddress& sender); + void onRegisterRequest(const LocalSend::RegisterDto& dto, const QHostAddress& sender); + void onPrepareUploadRequest(const QString& httpSessionId, + const LocalSend::PrepareUploadRequestDto& dto, + const QHostAddress& sender); + void onUploadRequest(const QString& sessionId, const QString& fileId, + const QString& token, const QByteArray& data); + void onSessionAccepted(const QString& sessionId, const QMap& tokens); + void onSessionDeclined(const QString& sessionId); + void onReceiveProgress(const QString& sessionId, const QString& fileId, double progress); + void onReceiveCompleted(const QString& sessionId); private: LocalSend::Settings* m_settings = nullptr; @@ -60,4 +87,5 @@ private: QMap m_devices; LocalSend::InfoDto buildInfoDto() const; + QString generateUniqueFilePath(const QString& baseDir, const QString& fileName) const; }; diff --git a/src/app/qml/main.qml b/src/app/qml/main.qml index 63f0941..e8e4ddd 100644 --- a/src/app/qml/main.qml +++ b/src/app/qml/main.qml @@ -1,6 +1,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import QtQuick.Dialogs ApplicationWindow { id: root @@ -9,6 +10,12 @@ ApplicationWindow { height: 600 title: qsTr("LocalSend") + property string currentSessionId: "" + property var currentFiles: [] + property string currentSenderAlias: "" + property string currentSenderIp: "" + property var receiveProgress: ({}) + StackView { id: stackView anchors.fill: parent @@ -18,6 +25,174 @@ ApplicationWindow { } } + Dialog { + id: receiveDialog + anchors.centerIn: parent + modal: true + closePolicy: Popup.NoAutoClose + standardButtons: Dialog.Ok | Dialog.Cancel + title: qsTr("Receive Files") + + onAccepted: { + appController.acceptReceive(currentSessionId) + currentSessionId = "" + } + + onRejected: { + appController.declineReceive(currentSessionId) + currentSessionId = "" + } + + ColumnLayout { + spacing: 12 + + Label { + text: qsTr("From: %1 (%2)").arg(currentSenderAlias).arg(currentSenderIp) + font.bold: true + } + + Label { + text: qsTr("Files:") + } + + ListView { + Layout.fillWidth: true + Layout.preferredHeight: Math.min(200, contentHeight) + width: 400 + model: currentFiles + spacing: 4 + + delegate: RowLayout { + width: ListView.view.width + spacing: 8 + + Label { + text: modelData.fileName + Layout.fillWidth: true + elide: Text.ElideMiddle + } + Label { + text: formatSize(modelData.size) + color: "gray" + } + } + } + + Label { + text: qsTr("Save to: %1").arg(appController.downloadPath) + color: palette.mid + font.pixelSize: 12 + } + } + } + + Dialog { + id: progressDialog + anchors.centerIn: parent + modal: true + closePolicy: Popup.NoAutoClose + title: qsTr("Receiving Files") + + ColumnLayout { + spacing: 12 + + Label { + id: progressLabel + text: qsTr("Receiving from %1...").arg(currentSenderAlias) + } + + ProgressBar { + id: totalProgressBar + Layout.fillWidth: true + from: 0 + to: 100 + value: calculateTotalProgress() + } + + Label { + text: qsTr("%1% complete").arg(Math.round(totalProgressBar.value)) + color: "gray" + } + } + + property var progressData: ({}) + } + + Connections { + target: appController + + function onReceiveRequest(sessionId, senderAlias, senderIp, files) { + currentSessionId = sessionId + currentSenderAlias = senderAlias + currentSenderIp = senderIp + currentFiles = files + + if (appController.quickSave) { + appController.acceptReceive(sessionId) + } else { + receiveDialog.open() + } + } + + function onReceiveProgress(sessionId, fileId, progress) { + if (sessionId === currentSessionId) { + receiveProgress[fileId] = progress + receiveProgress = Object.assign({}, receiveProgress) + progressDialog.progressData = receiveProgress + } + } + + function onReceiveCompleted(sessionId) { + if (sessionId === currentSessionId) { + progressDialog.close() + receiveProgress = {} + currentSessionId = "" + } + } + + function onReceiveError(sessionId, error) { + if (sessionId === currentSessionId) { + progressDialog.close() + errorDialog.text = error + errorDialog.open() + } + } + } + + Dialog { + id: errorDialog + anchors.centerIn: parent + modal: true + standardButtons: Dialog.Ok + title: qsTr("Error") + property alias text: errorLabel.text + + Label { + id: errorLabel + } + } + + function formatSize(bytes) { + if (bytes < 1024) return bytes + " B" + if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB" + if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + " MB" + return (bytes / (1024 * 1024 * 1024)).toFixed(2) + " GB" + } + + function calculateTotalProgress() { + if (!currentFiles || currentFiles.length === 0) return 0 + var total = 0 + var count = 0 + for (var i = 0; i < currentFiles.length; i++) { + var fileId = currentFiles[i].id + if (receiveProgress[fileId] !== undefined) { + total += receiveProgress[fileId] * 100 + count++ + } + } + return count > 0 ? total / currentFiles.length : 0 + } + Component { id: homePageComponent Page { @@ -90,7 +265,7 @@ ApplicationWindow { Button { text: qsTr("Send") onClicked: { - // send to this device + // TODO: send to this device } } } @@ -106,7 +281,7 @@ ApplicationWindow { Item { Layout.fillWidth: true } Label { text: qsTr("Alias: %1").arg(appController.alias) - color: "gray" + color: palette.mid } } } @@ -162,6 +337,36 @@ ApplicationWindow { to: 65535 onValueChanged: appController.port = value } + + Label { text: qsTr("Download Path:") } + RowLayout { + Layout.fillWidth: true + TextField { + id: downloadPathField + text: appController.downloadPath + onEditingFinished: appController.downloadPath = text + Layout.fillWidth: true + } + Button { + text: qsTr("Browse") + onClicked: folderDialog.open() + } + } + + Label { text: qsTr("Quick Save:") } + CheckBox { + id: quickSaveCheck + checked: appController.quickSave + onCheckedChanged: appController.quickSave = checked + } + } + + FolderDialog { + id: folderDialog + onAccepted: { + downloadPathField.text = selectedFolder.toString().replace("file://", "") + appController.downloadPath = downloadPathField.text + } } Item { Layout.fillHeight: true } diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index d8672c4..9672488 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -34,9 +34,12 @@ target_link_libraries(LocalSendCore PUBLIC Qt6::Network ) -if(Qt6HttpServer_FOUND) +if(TARGET Qt6::HttpServer) target_link_libraries(LocalSendCore PUBLIC Qt6::HttpServer) target_compile_definitions(LocalSendCore PUBLIC HAS_QTHTTPSERVER) + message(STATUS "Qt6HttpServer found - HTTP server enabled") +else() + message(WARNING "Qt6HttpServer not found - HTTP server disabled, file receiving will not work") endif() find_package(OpenSSL) diff --git a/src/core/include/LocalSendCore/HttpServer.h b/src/core/include/LocalSendCore/HttpServer.h index 4be3890..be30526 100644 --- a/src/core/include/LocalSendCore/HttpServer.h +++ b/src/core/include/LocalSendCore/HttpServer.h @@ -2,12 +2,17 @@ #include #include +#include #include "LocalSendCore/Device.h" #include "LocalSendCore/DtoTypes.h" #ifdef HAS_QTHTTPSERVER +#include #include +#include #include +#include +#include #endif namespace LocalSend { @@ -30,10 +35,14 @@ public: quint16 serverPort() const; bool isRunning() const; + + void respondToPrepareUpload(const QString& sessionId, bool accepted, + const QMap& tokens = {}); signals: void registerRequest(const RegisterDto& dto, const QHostAddress& sender); - void prepareUploadRequest(const PrepareUploadRequestDto& dto, const QHostAddress& sender); + void prepareUploadRequest(const QString& sessionId, const PrepareUploadRequestDto& dto, + const QHostAddress& sender); void uploadRequest(const QString& sessionId, const QString& fileId, const QString& token, const QByteArray& data); void cancelRequest(const QString& sessionId); @@ -41,9 +50,14 @@ signals: private: #ifdef HAS_QTHTTPSERVER QHttpServer* m_server = nullptr; + QTcpServer* m_tcpServer = nullptr; QSslConfiguration m_sslConfig; -#else - QObject* m_server = nullptr; + + struct PendingPrepareUpload { + QString sessionId; + std::shared_ptr> promise; + }; + QMap m_pendingPrepareUploads; #endif InfoDto m_localInfo; @@ -54,7 +68,7 @@ private: void setupRoutes(); QHttpServerResponse handleInfoRequest(); QHttpServerResponse handleRegisterRequest(const QHttpServerRequest& request, const QHostAddress& peer); - QHttpServerResponse handlePrepareUploadRequest(const QHttpServerRequest& request); + QFuture handlePrepareUploadRequest(const QHttpServerRequest& request); QHttpServerResponse handleUploadRequest(const QHttpServerRequest& request); QHttpServerResponse handleCancelRequest(const QHttpServerRequest& request); #endif diff --git a/src/core/include/LocalSendCore/MulticastDiscovery.h b/src/core/include/LocalSendCore/MulticastDiscovery.h index d5d4a06..2052777 100644 --- a/src/core/include/LocalSendCore/MulticastDiscovery.h +++ b/src/core/include/LocalSendCore/MulticastDiscovery.h @@ -42,6 +42,7 @@ private: ProtocolType m_localProtocol = ProtocolType::Http; void handleMulticastMessage(const QHostAddress& sender, const QByteArray& data); + void sendResponse(const QHostAddress& target); }; } diff --git a/src/core/include/LocalSendCore/SessionManager.h b/src/core/include/LocalSendCore/SessionManager.h index 88e311c..d2d7917 100644 --- a/src/core/include/LocalSendCore/SessionManager.h +++ b/src/core/include/LocalSendCore/SessionManager.h @@ -42,7 +42,8 @@ class SessionManager : public QObject public: explicit SessionManager(QObject* parent = nullptr); - QString createReceiveSession(const Device& sender, const QMap& files); + QString createReceiveSession(const Device& sender, const QMap& files, + const QString& sessionId = QString()); void acceptReceiveSession(const QString& sessionId, const QMap& destinationPaths); void declineReceiveSession(const QString& sessionId); void updateReceiveProgress(const QString& sessionId, const QString& fileId, qint64 bytes); diff --git a/src/core/src/HttpServer.cpp b/src/core/src/HttpServer.cpp index 98b76d6..0d0d989 100644 --- a/src/core/src/HttpServer.cpp +++ b/src/core/src/HttpServer.cpp @@ -1,9 +1,12 @@ #include "LocalSendCore/HttpServer.h" #include "LocalSendCore/Constants.h" +#include #ifdef HAS_QTHTTPSERVER #include #include +#include +#include namespace LocalSend { @@ -34,25 +37,36 @@ bool HttpServer::start(quint16 port, bool https) { Q_UNUSED(https) - if (m_server->isListening()) { + if (m_tcpServer && m_tcpServer->isListening()) { stop(); } m_port = port; - auto tcpServer = new QTcpServer(this); - if (!tcpServer->listen(QHostAddress::Any, port)) { - delete tcpServer; + m_tcpServer = new QTcpServer(this); + if (!m_tcpServer->listen(QHostAddress::Any, port)) { + delete m_tcpServer; + m_tcpServer = nullptr; return false; } - m_server->bind(tcpServer); + m_server->bind(m_tcpServer); return true; } void HttpServer::stop() { - m_server->close(); + if (m_tcpServer) { + m_tcpServer->close(); + delete m_tcpServer; + m_tcpServer = nullptr; + } + for (auto it = m_pendingPrepareUploads.begin(); it != m_pendingPrepareUploads.end(); ++it) { + if (it->promise) { + it->promise->addResult(QHttpServerResponse(QHttpServerResponse::StatusCode::InternalServerError)); + } + } + m_pendingPrepareUploads.clear(); } quint16 HttpServer::serverPort() const @@ -62,7 +76,49 @@ quint16 HttpServer::serverPort() const bool HttpServer::isRunning() const { - return m_server->isListening(); + return m_tcpServer && m_tcpServer->isListening(); +} + +void HttpServer::respondToPrepareUpload(const QString& sessionId, bool accepted, + const QMap& tokens) +{ + qDebug() << "[HttpServer] respondToPrepareUpload called, sessionId:" << sessionId + << "accepted:" << accepted; + qDebug() << "[HttpServer] Pending sessions:" << m_pendingPrepareUploads.keys(); + + if (!m_pendingPrepareUploads.contains(sessionId)) { + qWarning() << "[HttpServer] sessionId not found in pending uploads:" << sessionId; + return; + } + + auto& pending = m_pendingPrepareUploads[sessionId]; + if (!pending.promise) { + qWarning() << "[HttpServer] Promise is null for sessionId:" << sessionId; + m_pendingPrepareUploads.remove(sessionId); + return; + } + + if (accepted) { + QJsonObject responseObj; + responseObj[QStringLiteral("sessionId")] = sessionId; + QJsonObject filesObj; + for (auto it = tokens.constBegin(); it != tokens.constEnd(); ++it) { + filesObj[it.key()] = it.value(); + } + responseObj[QStringLiteral("files")] = filesObj; + + QJsonDocument doc(responseObj); + qDebug() << "[HttpServer] Sending accept response:" << doc.toJson(QJsonDocument::Compact); + pending.promise->addResult(QHttpServerResponse(doc.toJson(QJsonDocument::Compact), + QHttpServerResponse::StatusCode::Ok)); + } else { + qDebug() << "[HttpServer] Sending decline response (403 Forbidden)"; + pending.promise->addResult(QHttpServerResponse(QHttpServerResponse::StatusCode::Forbidden)); + } + + pending.promise->finish(); + m_pendingPrepareUploads.remove(sessionId); + qDebug() << "[HttpServer] Response sent and session removed"; } void HttpServer::setupRoutes() @@ -103,14 +159,18 @@ QHttpServerResponse HttpServer::handleInfoRequest() QHttpServerResponse HttpServer::handleRegisterRequest(const QHttpServerRequest& request, const QHostAddress& peer) { + qDebug() << "[HttpServer] handleRegisterRequest from:" << peer.toString(); + QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(request.body(), &error); if (error.error != QJsonParseError::NoError) { + qWarning() << "[HttpServer] JSON parse error in register request:" << error.errorString(); return QHttpServerResponse(QHttpServerResponse::StatusCode::BadRequest); } RegisterDto dto = RegisterDto::fromJson(doc.object()); + qDebug() << "[HttpServer] Register from:" << dto.alias << "fingerprint:" << dto.fingerprint; emit registerRequest(dto, peer); QJsonDocument response(m_localInfo.toJson()); @@ -118,19 +178,37 @@ QHttpServerResponse HttpServer::handleRegisterRequest(const QHttpServerRequest& QHttpServerResponse::StatusCode::Ok); } -QHttpServerResponse HttpServer::handlePrepareUploadRequest(const QHttpServerRequest& request) +QFuture HttpServer::handlePrepareUploadRequest(const QHttpServerRequest& request) { QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(request.body(), &error); if (error.error != QJsonParseError::NoError) { - return QHttpServerResponse(QHttpServerResponse::StatusCode::BadRequest); + qWarning() << "[HttpServer] JSON parse error:" << error.errorString(); + auto promise = std::make_shared>(); + promise->start(); + promise->addResult(QHttpServerResponse(QHttpServerResponse::StatusCode::BadRequest)); + promise->finish(); + return promise->future(); } PrepareUploadRequestDto dto = PrepareUploadRequestDto::fromJson(doc.object()); - emit prepareUploadRequest(dto, request.remoteAddress()); + QString sessionId = QUuid::createUuid().toString(QUuid::WithoutBraces); - return QHttpServerResponse(QHttpServerResponse::StatusCode::Accepted); + qDebug() << "[HttpServer] handlePrepareUploadRequest: created sessionId:" << sessionId; + qDebug() << "[HttpServer] Request from:" << request.remoteAddress().toString(); + qDebug() << "[HttpServer] Files count:" << dto.files.size(); + + PendingPrepareUpload pending; + pending.sessionId = sessionId; + pending.promise = std::make_shared>(); + pending.promise->start(); + m_pendingPrepareUploads.insert(sessionId, pending); + + qDebug() << "[HttpServer] Emitting prepareUploadRequest signal"; + emit prepareUploadRequest(sessionId, dto, request.remoteAddress()); + + return pending.promise->future(); } QHttpServerResponse HttpServer::handleUploadRequest(const QHttpServerRequest& request) @@ -186,6 +264,14 @@ void HttpServer::setLocalInfo(const InfoDto& info, const QString& fingerprint) m_localFingerprint = fingerprint; } +void HttpServer::respondToPrepareUpload(const QString& sessionId, bool accepted, + const QMap& tokens) +{ + Q_UNUSED(sessionId) + Q_UNUSED(accepted) + Q_UNUSED(tokens) +} + bool HttpServer::start(quint16 port, bool https) { Q_UNUSED(port) diff --git a/src/core/src/MulticastDiscovery.cpp b/src/core/src/MulticastDiscovery.cpp index b1af244..bf8e239 100644 --- a/src/core/src/MulticastDiscovery.cpp +++ b/src/core/src/MulticastDiscovery.cpp @@ -1,6 +1,7 @@ #include "LocalSendCore/MulticastDiscovery.h" #include "LocalSendCore/Constants.h" #include +#include namespace LocalSend { @@ -25,6 +26,8 @@ void MulticastDiscovery::setLocalInfo(const InfoDto& info, const QString& finger void MulticastDiscovery::start(const QString& multicastGroup, quint16 port) { + qDebug() << "[MulticastDiscovery] start() called"; + if (m_socket) { stop(); } @@ -36,18 +39,22 @@ void MulticastDiscovery::start(const QString& multicastGroup, quint16 port) if (!m_socket->bind(QHostAddress::AnyIPv4, port, QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint)) { - qWarning() << "Failed to bind UDP socket:" << m_socket->errorString(); + qWarning() << "[MulticastDiscovery] Failed to bind UDP socket:" << m_socket->errorString(); return; } + qDebug() << "[MulticastDiscovery] Socket bound to port:" << port; + for (const QNetworkInterface& iface : QNetworkInterface::allInterfaces()) { if (iface.flags() & QNetworkInterface::CanMulticast) { m_socket->joinMulticastGroup(QHostAddress(multicastGroup), iface); + qDebug() << "[MulticastDiscovery] Joined multicast group on interface:" << iface.name(); } } connect(m_socket, &QUdpSocket::readyRead, this, &MulticastDiscovery::onReadyRead); + qDebug() << "[MulticastDiscovery] Sending initial announcement"; sendAnnouncement(); } @@ -63,6 +70,8 @@ void MulticastDiscovery::stop() void MulticastDiscovery::sendAnnouncement() { if (!m_socket || m_localFingerprint.isEmpty()) { + qWarning() << "[MulticastDiscovery] Cannot send announcement: socket=" << m_socket + << "fingerprint empty=" << m_localFingerprint.isEmpty(); return; } @@ -79,6 +88,7 @@ void MulticastDiscovery::sendAnnouncement() dto.announce = true; QByteArray data = QJsonDocument(dto.toJson()).toJson(QJsonDocument::Compact); + qDebug() << "[MulticastDiscovery] Sending announcement to" << m_multicastGroup << ":" << m_port; m_socket->writeDatagram(data, QHostAddress(m_multicastGroup), m_port); } @@ -110,6 +120,11 @@ void MulticastDiscovery::handleMulticastMessage(const QHostAddress& sender, cons return; } + qDebug() << "[MulticastDiscovery] Received from:" << sender.toString() + << "alias:" << dto.alias + << "announce:" << dto.announce + << "announcement:" << dto.announcement; + Device device; device.ip = sender.toString(); device.port = dto.port; @@ -124,6 +139,29 @@ void MulticastDiscovery::handleMulticastMessage(const QHostAddress& sender, cons device.lastSeen = QDateTime::currentDateTime(); emit deviceDiscovered(device); + + if ((dto.announcement || dto.announce) && m_socket && !m_localFingerprint.isEmpty()) { + qDebug() << "[MulticastDiscovery] Sending response to:" << sender.toString(); + sendResponse(sender); + } +} + +void MulticastDiscovery::sendResponse(const QHostAddress& target) +{ + MulticastDto dto; + dto.alias = m_localInfo.alias; + dto.version = m_localInfo.version.isEmpty() ? PROTOCOL_VERSION : m_localInfo.version; + dto.deviceModel = m_localInfo.deviceModel; + dto.deviceType = m_localInfo.deviceType; + dto.fingerprint = m_localFingerprint; + dto.port = m_localPort; + dto.protocol = m_localProtocol; + dto.download = m_localInfo.download; + dto.announcement = false; + dto.announce = false; + + QByteArray data = QJsonDocument(dto.toJson()).toJson(QJsonDocument::Compact); + m_socket->writeDatagram(data, target, m_port); } } diff --git a/src/core/src/SessionManager.cpp b/src/core/src/SessionManager.cpp index 20cab5f..8477055 100644 --- a/src/core/src/SessionManager.cpp +++ b/src/core/src/SessionManager.cpp @@ -8,12 +8,13 @@ SessionManager::SessionManager(QObject* parent) { } -QString SessionManager::createReceiveSession(const Device& sender, const QMap& files) +QString SessionManager::createReceiveSession(const Device& sender, const QMap& files, + const QString& sessionId) { - QString sessionId = QUuid::createUuid().toString(QUuid::WithoutBraces); + QString id = sessionId.isEmpty() ? QUuid::createUuid().toString(QUuid::WithoutBraces) : sessionId; ReceiveSession session; - session.sessionId = sessionId; + session.sessionId = id; session.sender = sender; session.status = SessionStatus::Waiting; @@ -25,10 +26,10 @@ QString SessionManager::createReceiveSession(const Device& sender, const QMap