From 5922ff4ca591f4506f5a47efda4b2b17715f27a3 Mon Sep 17 00:00:00 2001 From: Gary Wang Date: Tue, 26 May 2026 16:33:57 +0800 Subject: [PATCH] manual send (to ip) --- src/app/AppController.cpp | 83 +++++++++++++++++++++ src/app/AppController.h | 6 ++ src/app/qml/main.qml | 50 +++++++++++++ src/core/include/LocalSendCore/HttpClient.h | 2 +- src/core/src/HttpClient.cpp | 12 ++- 5 files changed, 151 insertions(+), 2 deletions(-) diff --git a/src/app/AppController.cpp b/src/app/AppController.cpp index 303e093..bce6160 100644 --- a/src/app/AppController.cpp +++ b/src/app/AppController.cpp @@ -74,6 +74,10 @@ void AppController::initialize() this, &AppController::onUploadCompleted); connect(m_httpClient, &LocalSend::HttpClient::uploadError, this, &AppController::onUploadError); + connect(m_httpClient, &LocalSend::HttpClient::registerCompleted, + this, &AppController::onManualRegisterCompleted); + connect(m_httpClient, &LocalSend::HttpClient::registerError, + this, &AppController::onManualRegisterError); startDiscovery(); } @@ -556,6 +560,46 @@ void AppController::sendTo(const QString& deviceFingerprint) sendFiles(deviceFingerprint, m_pendingSendPaths); } +void AppController::sendToAddress(const QString& address) +{ + if (m_pendingSendPaths.isEmpty()) { + emit sendError(QStringLiteral("No files selected")); + return; + } + + if (sending()) { + emit sendError(QStringLiteral("Already sending files")); + return; + } + + QString ip = address; + quint16 port = m_settings->port(); + + if (address.contains(':')) { + int colonPos = address.lastIndexOf(':'); + ip = address.left(colonPos); + bool ok = false; + int parsedPort = address.mid(colonPos + 1).toInt(&ok); + if (ok && parsedPort > 0 && parsedPort <= 65535) { + port = static_cast(parsedPort); + } + } + + QHostAddress hostAddr(ip); + if (hostAddr.isNull()) { + emit sendError(QStringLiteral("Invalid IP address: %1").arg(ip)); + return; + } + + qDebug() << "[AppController] sendToAddress:" << ip << "port:" << port; + + m_manualSendDevice = LocalSend::Device(ip, port); + m_manualSendDevice.discoveryMethod = LocalSend::DiscoveryMethod::HttpTarget; + m_manualSendPending = true; + + m_httpClient->registerDevice(m_manualSendDevice, buildRegisterDto()); +} + void AppController::sendFiles(const QString& deviceFingerprint, const QStringList& filePaths) { if (!m_devices.contains(deviceFingerprint)) { @@ -898,3 +942,42 @@ void AppController::resetReceiveState() emit receivingChanged(); emit receiveProgressChanged(); } + +void AppController::onManualRegisterCompleted(const LocalSend::InfoDto& peerInfo) +{ + if (!m_manualSendPending) return; + m_manualSendPending = false; + + m_manualSendDevice.alias = peerInfo.alias; + m_manualSendDevice.deviceModel = peerInfo.deviceModel; + m_manualSendDevice.deviceType = peerInfo.deviceType; + m_manualSendDevice.version = peerInfo.version; + m_manualSendDevice.fingerprint = peerInfo.fingerprint; + + qDebug() << "[AppController] Manual register completed:" << m_manualSendDevice.alias + << "fingerprint:" << m_manualSendDevice.fingerprint; + + if (!m_devices.contains(m_manualSendDevice.fingerprint)) { + m_devices.insert(m_manualSendDevice.fingerprint, m_manualSendDevice); + emit devicesChanged(); + } + + sendFiles(m_manualSendDevice.fingerprint, m_pendingSendPaths); +} + +void AppController::onManualRegisterError(const QString& error) +{ + if (!m_manualSendPending) return; + + if (m_manualSendDevice.protocol == LocalSend::ProtocolType::Http) { + qWarning() << "[AppController] Manual register failed with HTTP, retrying with HTTPS:" << error; + m_manualSendDevice.protocol = LocalSend::ProtocolType::Https; + m_httpClient->registerDevice(m_manualSendDevice, buildRegisterDto()); + return; + } + + m_manualSendPending = false; + + qWarning() << "[AppController] Manual register error:" << error; + emit sendError(QStringLiteral("Cannot reach device at %1: %2").arg(m_manualSendDevice.ip).arg(error)); +} diff --git a/src/app/AppController.h b/src/app/AppController.h index a085d3f..6f9a6f6 100644 --- a/src/app/AppController.h +++ b/src/app/AppController.h @@ -86,6 +86,7 @@ public: Q_INVOKABLE void sendFiles(const QString& deviceFingerprint, const QStringList& filePaths); Q_INVOKABLE void sendTo(const QString& deviceFingerprint); + Q_INVOKABLE void sendToAddress(const QString& address); Q_INVOKABLE void cancelSend(); Q_INVOKABLE void addFiles(const QStringList& filePaths); Q_INVOKABLE void removePendingFile(int index); @@ -137,6 +138,8 @@ private slots: void onUploadProgress(qint64 sent, qint64 total); void onUploadCompleted(); void onUploadError(const QString& error); + void onManualRegisterCompleted(const LocalSend::InfoDto& peerInfo); + void onManualRegisterError(const QString& error); void onCancelRequest(const QString& sessionId); @@ -175,4 +178,7 @@ private: LocalSend::RegisterDto buildRegisterDto() const; void resetSendState(); void resetReceiveState(); + + bool m_manualSendPending = false; + LocalSend::Device m_manualSendDevice; }; diff --git a/src/app/qml/main.qml b/src/app/qml/main.qml index 93ecce8..a800ecc 100644 --- a/src/app/qml/main.qml +++ b/src/app/qml/main.qml @@ -410,6 +410,51 @@ ApplicationWindow { } } + Dialog { + id: manualSendDialog + anchors.centerIn: parent + modal: true + title: qsTr("Manual Sending") + + ColumnLayout { + spacing: 12 + + Label { + text: qsTr("Enter the IP address of the target device.") + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + TextField { + id: manualAddressInput + Layout.fillWidth: true + placeholderText: qsTr("IP address (e.g., 192.168.1.100:53317)") + onAccepted: manualSendDialog.accepted() + } + } + + footer: DialogButtonBox { + Button { + text: qsTr("Cancel") + DialogButtonBox.buttonRole: DialogButtonBox.RejectRole + } + Button { + text: qsTr("Send") + DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + enabled: manualAddressInput.text.trim().length > 0 + } + } + + onAccepted: { + appController.sendToAddress(manualAddressInput.text.trim()) + } + + onOpened: { + manualAddressInput.text = "" + manualAddressInput.forceActiveFocus() + } + } + Dialog { id: setPinDialog anchors.centerIn: parent @@ -646,6 +691,11 @@ ApplicationWindow { text: qsTr("Refresh") onClicked: appController.refreshDevices() } + Button { + text: qsTr("Manual Send") + enabled: appController.hasPendingFiles && !appController.sending + onClicked: manualSendDialog.open() + } Item { Layout.fillWidth: true } Label { text: qsTr("Alias: %1").arg(appController.alias) diff --git a/src/core/include/LocalSendCore/HttpClient.h b/src/core/include/LocalSendCore/HttpClient.h index 8308966..4953f1e 100644 --- a/src/core/include/LocalSendCore/HttpClient.h +++ b/src/core/include/LocalSendCore/HttpClient.h @@ -31,7 +31,7 @@ public: signals: void infoReceived(const InfoDto& info); void infoError(const QString& error); - void registerCompleted(); + void registerCompleted(const InfoDto& peerInfo); void registerError(const QString& error); void prepareUploadResponse(const PrepareUploadResponseDto& response); void prepareUploadError(const QString& error); diff --git a/src/core/src/HttpClient.cpp b/src/core/src/HttpClient.cpp index 6ccc0ed..4b3963a 100644 --- a/src/core/src/HttpClient.cpp +++ b/src/core/src/HttpClient.cpp @@ -95,7 +95,17 @@ void HttpClient::registerDevice(const Device& device, const RegisterDto& dto) return; } - emit registerCompleted(); + QByteArray responseData = reply->readAll(); + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(responseData, &error); + + if (error.error != QJsonParseError::NoError) { + emit registerError(QStringLiteral("Invalid JSON response")); + return; + } + + InfoDto info = InfoDto::fromJson(doc.object()); + emit registerCompleted(info); }); }