able to receive file
This commit is contained in:
@@ -19,7 +19,7 @@ option(WITH_HTTP_SERVER "Build with QHttpServer support" ON)
|
|||||||
find_package(Qt6 REQUIRED COMPONENTS Core Network Quick QuickControls2)
|
find_package(Qt6 REQUIRED COMPONENTS Core Network Quick QuickControls2)
|
||||||
|
|
||||||
if(WITH_HTTP_SERVER)
|
if(WITH_HTTP_SERVER)
|
||||||
find_package(Qt6 COMPONENTS HttpServer)
|
find_package(Qt6 6.8 COMPONENTS HttpServer)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_subdirectory(src/core)
|
add_subdirectory(src/core)
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
#include "AppController.h"
|
#include "AppController.h"
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
AppController::AppController(QObject* parent)
|
AppController::AppController(QObject* parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
@@ -33,8 +39,20 @@ void AppController::initialize()
|
|||||||
this, &AppController::onDeviceDiscovered);
|
this, &AppController::onDeviceDiscovered);
|
||||||
connect(m_discovery, &LocalSend::DiscoveryManager::deviceLost,
|
connect(m_discovery, &LocalSend::DiscoveryManager::deviceLost,
|
||||||
this, &AppController::onDeviceLost);
|
this, &AppController::onDeviceLost);
|
||||||
|
connect(m_server, &LocalSend::HttpServer::registerRequest,
|
||||||
|
this, &AppController::onRegisterRequest);
|
||||||
connect(m_server, &LocalSend::HttpServer::prepareUploadRequest,
|
connect(m_server, &LocalSend::HttpServer::prepareUploadRequest,
|
||||||
this, &AppController::onPrepareUploadRequest);
|
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();
|
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 AppController::devices() const
|
||||||
{
|
{
|
||||||
QVariantList result;
|
QVariantList result;
|
||||||
@@ -112,6 +156,64 @@ void AppController::refreshDevices()
|
|||||||
m_discovery->startScan();
|
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<QString, QString> 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)
|
void AppController::onDeviceDiscovered(const LocalSend::Device& device)
|
||||||
{
|
{
|
||||||
m_devices.insert(device.fingerprint, device);
|
m_devices.insert(device.fingerprint, device);
|
||||||
@@ -124,9 +226,33 @@ void AppController::onDeviceLost(const QString& fingerprint)
|
|||||||
emit devicesChanged();
|
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)
|
const QHostAddress& sender)
|
||||||
{
|
{
|
||||||
|
qDebug() << "[AppController] onPrepareUploadRequest received, httpSessionId:" << httpSessionId;
|
||||||
|
|
||||||
LocalSend::Device device;
|
LocalSend::Device device;
|
||||||
device.ip = sender.toString();
|
device.ip = sender.toString();
|
||||||
device.alias = dto.info.alias;
|
device.alias = dto.info.alias;
|
||||||
@@ -135,7 +261,8 @@ void AppController::onPrepareUploadRequest(const LocalSend::PrepareUploadRequest
|
|||||||
device.deviceType = dto.info.deviceType;
|
device.deviceType = dto.info.deviceType;
|
||||||
device.version = dto.info.version;
|
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;
|
QVariantList filesList;
|
||||||
for (auto it = dto.files.constBegin(); it != dto.files.constEnd(); ++it) {
|
for (auto it = dto.files.constBegin(); it != dto.files.constEnd(); ++it) {
|
||||||
@@ -147,5 +274,74 @@ void AppController::onPrepareUploadRequest(const LocalSend::PrepareUploadRequest
|
|||||||
filesList.append(file);
|
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<QString, QString>& 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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QVariantList>
|
#include <QVariantList>
|
||||||
|
#include <QVariantMap>
|
||||||
#include "LocalSendCore/DiscoveryManager.h"
|
#include "LocalSendCore/DiscoveryManager.h"
|
||||||
#include "LocalSendCore/HttpServer.h"
|
#include "LocalSendCore/HttpServer.h"
|
||||||
#include "LocalSendCore/HttpClient.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(quint16 port READ port WRITE setPort NOTIFY portChanged)
|
||||||
Q_PROPERTY(QVariantList devices READ devices NOTIFY devicesChanged)
|
Q_PROPERTY(QVariantList devices READ devices NOTIFY devicesChanged)
|
||||||
Q_PROPERTY(bool serverRunning READ serverRunning NOTIFY serverRunningChanged)
|
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:
|
public:
|
||||||
explicit AppController(QObject* parent = nullptr);
|
explicit AppController(QObject* parent = nullptr);
|
||||||
@@ -29,6 +32,12 @@ public:
|
|||||||
quint16 port() const;
|
quint16 port() const;
|
||||||
void setPort(quint16 port);
|
void setPort(quint16 port);
|
||||||
|
|
||||||
|
QString downloadPath() const;
|
||||||
|
void setDownloadPath(const QString& path);
|
||||||
|
|
||||||
|
bool quickSave() const;
|
||||||
|
void setQuickSave(bool enabled);
|
||||||
|
|
||||||
QVariantList devices() const;
|
QVariantList devices() const;
|
||||||
bool serverRunning() const;
|
bool serverRunning() const;
|
||||||
|
|
||||||
@@ -36,19 +45,37 @@ public:
|
|||||||
Q_INVOKABLE void stopDiscovery();
|
Q_INVOKABLE void stopDiscovery();
|
||||||
Q_INVOKABLE void refreshDevices();
|
Q_INVOKABLE void refreshDevices();
|
||||||
|
|
||||||
|
Q_INVOKABLE void acceptReceive(const QString& sessionId);
|
||||||
|
Q_INVOKABLE void declineReceive(const QString& sessionId);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void aliasChanged();
|
void aliasChanged();
|
||||||
void portChanged();
|
void portChanged();
|
||||||
|
void downloadPathChanged();
|
||||||
|
void quickSaveChanged();
|
||||||
void devicesChanged();
|
void devicesChanged();
|
||||||
void serverRunningChanged();
|
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 sendProgress(const QString& sessionId, double progress);
|
||||||
void sendCompleted(const QString& sessionId);
|
void sendCompleted(const QString& sessionId);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onDeviceDiscovered(const LocalSend::Device& device);
|
void onDeviceDiscovered(const LocalSend::Device& device);
|
||||||
void onDeviceLost(const QString& fingerprint);
|
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<QString, QString>& tokens);
|
||||||
|
void onSessionDeclined(const QString& sessionId);
|
||||||
|
void onReceiveProgress(const QString& sessionId, const QString& fileId, double progress);
|
||||||
|
void onReceiveCompleted(const QString& sessionId);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LocalSend::Settings* m_settings = nullptr;
|
LocalSend::Settings* m_settings = nullptr;
|
||||||
@@ -60,4 +87,5 @@ private:
|
|||||||
QMap<QString, LocalSend::Device> m_devices;
|
QMap<QString, LocalSend::Device> m_devices;
|
||||||
|
|
||||||
LocalSend::InfoDto buildInfoDto() const;
|
LocalSend::InfoDto buildInfoDto() const;
|
||||||
|
QString generateUniqueFilePath(const QString& baseDir, const QString& fileName) const;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import QtQuick.Dialogs
|
||||||
|
|
||||||
ApplicationWindow {
|
ApplicationWindow {
|
||||||
id: root
|
id: root
|
||||||
@@ -9,6 +10,12 @@ ApplicationWindow {
|
|||||||
height: 600
|
height: 600
|
||||||
title: qsTr("LocalSend")
|
title: qsTr("LocalSend")
|
||||||
|
|
||||||
|
property string currentSessionId: ""
|
||||||
|
property var currentFiles: []
|
||||||
|
property string currentSenderAlias: ""
|
||||||
|
property string currentSenderIp: ""
|
||||||
|
property var receiveProgress: ({})
|
||||||
|
|
||||||
StackView {
|
StackView {
|
||||||
id: stackView
|
id: stackView
|
||||||
anchors.fill: parent
|
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 {
|
Component {
|
||||||
id: homePageComponent
|
id: homePageComponent
|
||||||
Page {
|
Page {
|
||||||
@@ -90,7 +265,7 @@ ApplicationWindow {
|
|||||||
Button {
|
Button {
|
||||||
text: qsTr("Send")
|
text: qsTr("Send")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
// send to this device
|
// TODO: send to this device
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,7 +281,7 @@ ApplicationWindow {
|
|||||||
Item { Layout.fillWidth: true }
|
Item { Layout.fillWidth: true }
|
||||||
Label {
|
Label {
|
||||||
text: qsTr("Alias: %1").arg(appController.alias)
|
text: qsTr("Alias: %1").arg(appController.alias)
|
||||||
color: "gray"
|
color: palette.mid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,6 +337,36 @@ ApplicationWindow {
|
|||||||
to: 65535
|
to: 65535
|
||||||
onValueChanged: appController.port = value
|
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 }
|
Item { Layout.fillHeight: true }
|
||||||
|
|||||||
@@ -34,9 +34,12 @@ target_link_libraries(LocalSendCore PUBLIC
|
|||||||
Qt6::Network
|
Qt6::Network
|
||||||
)
|
)
|
||||||
|
|
||||||
if(Qt6HttpServer_FOUND)
|
if(TARGET Qt6::HttpServer)
|
||||||
target_link_libraries(LocalSendCore PUBLIC Qt6::HttpServer)
|
target_link_libraries(LocalSendCore PUBLIC Qt6::HttpServer)
|
||||||
target_compile_definitions(LocalSendCore PUBLIC HAS_QTHTTPSERVER)
|
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()
|
endif()
|
||||||
|
|
||||||
find_package(OpenSSL)
|
find_package(OpenSSL)
|
||||||
|
|||||||
@@ -2,12 +2,17 @@
|
|||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
|
#include <QFuture>
|
||||||
#include "LocalSendCore/Device.h"
|
#include "LocalSendCore/Device.h"
|
||||||
#include "LocalSendCore/DtoTypes.h"
|
#include "LocalSendCore/DtoTypes.h"
|
||||||
|
|
||||||
#ifdef HAS_QTHTTPSERVER
|
#ifdef HAS_QTHTTPSERVER
|
||||||
|
#include <QTcpServer>
|
||||||
#include <QHttpServer>
|
#include <QHttpServer>
|
||||||
|
#include <QHttpServerResponse>
|
||||||
#include <QSslConfiguration>
|
#include <QSslConfiguration>
|
||||||
|
#include <QPromise>
|
||||||
|
#include <memory>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace LocalSend {
|
namespace LocalSend {
|
||||||
@@ -31,9 +36,13 @@ public:
|
|||||||
quint16 serverPort() const;
|
quint16 serverPort() const;
|
||||||
bool isRunning() const;
|
bool isRunning() const;
|
||||||
|
|
||||||
|
void respondToPrepareUpload(const QString& sessionId, bool accepted,
|
||||||
|
const QMap<QString, QString>& tokens = {});
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void registerRequest(const RegisterDto& dto, const QHostAddress& sender);
|
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,
|
void uploadRequest(const QString& sessionId, const QString& fileId, const QString& token,
|
||||||
const QByteArray& data);
|
const QByteArray& data);
|
||||||
void cancelRequest(const QString& sessionId);
|
void cancelRequest(const QString& sessionId);
|
||||||
@@ -41,9 +50,14 @@ signals:
|
|||||||
private:
|
private:
|
||||||
#ifdef HAS_QTHTTPSERVER
|
#ifdef HAS_QTHTTPSERVER
|
||||||
QHttpServer* m_server = nullptr;
|
QHttpServer* m_server = nullptr;
|
||||||
|
QTcpServer* m_tcpServer = nullptr;
|
||||||
QSslConfiguration m_sslConfig;
|
QSslConfiguration m_sslConfig;
|
||||||
#else
|
|
||||||
QObject* m_server = nullptr;
|
struct PendingPrepareUpload {
|
||||||
|
QString sessionId;
|
||||||
|
std::shared_ptr<QPromise<QHttpServerResponse>> promise;
|
||||||
|
};
|
||||||
|
QMap<QString, PendingPrepareUpload> m_pendingPrepareUploads;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
InfoDto m_localInfo;
|
InfoDto m_localInfo;
|
||||||
@@ -54,7 +68,7 @@ private:
|
|||||||
void setupRoutes();
|
void setupRoutes();
|
||||||
QHttpServerResponse handleInfoRequest();
|
QHttpServerResponse handleInfoRequest();
|
||||||
QHttpServerResponse handleRegisterRequest(const QHttpServerRequest& request, const QHostAddress& peer);
|
QHttpServerResponse handleRegisterRequest(const QHttpServerRequest& request, const QHostAddress& peer);
|
||||||
QHttpServerResponse handlePrepareUploadRequest(const QHttpServerRequest& request);
|
QFuture<QHttpServerResponse> handlePrepareUploadRequest(const QHttpServerRequest& request);
|
||||||
QHttpServerResponse handleUploadRequest(const QHttpServerRequest& request);
|
QHttpServerResponse handleUploadRequest(const QHttpServerRequest& request);
|
||||||
QHttpServerResponse handleCancelRequest(const QHttpServerRequest& request);
|
QHttpServerResponse handleCancelRequest(const QHttpServerRequest& request);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ private:
|
|||||||
ProtocolType m_localProtocol = ProtocolType::Http;
|
ProtocolType m_localProtocol = ProtocolType::Http;
|
||||||
|
|
||||||
void handleMulticastMessage(const QHostAddress& sender, const QByteArray& data);
|
void handleMulticastMessage(const QHostAddress& sender, const QByteArray& data);
|
||||||
|
void sendResponse(const QHostAddress& target);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ class SessionManager : public QObject
|
|||||||
public:
|
public:
|
||||||
explicit SessionManager(QObject* parent = nullptr);
|
explicit SessionManager(QObject* parent = nullptr);
|
||||||
|
|
||||||
QString createReceiveSession(const Device& sender, const QMap<QString, FileDto>& files);
|
QString createReceiveSession(const Device& sender, const QMap<QString, FileDto>& files,
|
||||||
|
const QString& sessionId = QString());
|
||||||
void acceptReceiveSession(const QString& sessionId, const QMap<QString, QString>& destinationPaths);
|
void acceptReceiveSession(const QString& sessionId, const QMap<QString, QString>& destinationPaths);
|
||||||
void declineReceiveSession(const QString& sessionId);
|
void declineReceiveSession(const QString& sessionId);
|
||||||
void updateReceiveProgress(const QString& sessionId, const QString& fileId, qint64 bytes);
|
void updateReceiveProgress(const QString& sessionId, const QString& fileId, qint64 bytes);
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
#include "LocalSendCore/HttpServer.h"
|
#include "LocalSendCore/HttpServer.h"
|
||||||
#include "LocalSendCore/Constants.h"
|
#include "LocalSendCore/Constants.h"
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
#ifdef HAS_QTHTTPSERVER
|
#ifdef HAS_QTHTTPSERVER
|
||||||
#include <QTcpServer>
|
#include <QTcpServer>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
|
#include <QEventLoop>
|
||||||
|
#include <QUuid>
|
||||||
|
|
||||||
namespace LocalSend {
|
namespace LocalSend {
|
||||||
|
|
||||||
@@ -34,25 +37,36 @@ bool HttpServer::start(quint16 port, bool https)
|
|||||||
{
|
{
|
||||||
Q_UNUSED(https)
|
Q_UNUSED(https)
|
||||||
|
|
||||||
if (m_server->isListening()) {
|
if (m_tcpServer && m_tcpServer->isListening()) {
|
||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_port = port;
|
m_port = port;
|
||||||
|
|
||||||
auto tcpServer = new QTcpServer(this);
|
m_tcpServer = new QTcpServer(this);
|
||||||
if (!tcpServer->listen(QHostAddress::Any, port)) {
|
if (!m_tcpServer->listen(QHostAddress::Any, port)) {
|
||||||
delete tcpServer;
|
delete m_tcpServer;
|
||||||
|
m_tcpServer = nullptr;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
m_server->bind(tcpServer);
|
m_server->bind(m_tcpServer);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpServer::stop()
|
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
|
quint16 HttpServer::serverPort() const
|
||||||
@@ -62,7 +76,49 @@ quint16 HttpServer::serverPort() const
|
|||||||
|
|
||||||
bool HttpServer::isRunning() 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<QString, QString>& 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()
|
void HttpServer::setupRoutes()
|
||||||
@@ -103,14 +159,18 @@ QHttpServerResponse HttpServer::handleInfoRequest()
|
|||||||
QHttpServerResponse HttpServer::handleRegisterRequest(const QHttpServerRequest& request,
|
QHttpServerResponse HttpServer::handleRegisterRequest(const QHttpServerRequest& request,
|
||||||
const QHostAddress& peer)
|
const QHostAddress& peer)
|
||||||
{
|
{
|
||||||
|
qDebug() << "[HttpServer] handleRegisterRequest from:" << peer.toString();
|
||||||
|
|
||||||
QJsonParseError error;
|
QJsonParseError error;
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(request.body(), &error);
|
QJsonDocument doc = QJsonDocument::fromJson(request.body(), &error);
|
||||||
|
|
||||||
if (error.error != QJsonParseError::NoError) {
|
if (error.error != QJsonParseError::NoError) {
|
||||||
|
qWarning() << "[HttpServer] JSON parse error in register request:" << error.errorString();
|
||||||
return QHttpServerResponse(QHttpServerResponse::StatusCode::BadRequest);
|
return QHttpServerResponse(QHttpServerResponse::StatusCode::BadRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
RegisterDto dto = RegisterDto::fromJson(doc.object());
|
RegisterDto dto = RegisterDto::fromJson(doc.object());
|
||||||
|
qDebug() << "[HttpServer] Register from:" << dto.alias << "fingerprint:" << dto.fingerprint;
|
||||||
emit registerRequest(dto, peer);
|
emit registerRequest(dto, peer);
|
||||||
|
|
||||||
QJsonDocument response(m_localInfo.toJson());
|
QJsonDocument response(m_localInfo.toJson());
|
||||||
@@ -118,19 +178,37 @@ QHttpServerResponse HttpServer::handleRegisterRequest(const QHttpServerRequest&
|
|||||||
QHttpServerResponse::StatusCode::Ok);
|
QHttpServerResponse::StatusCode::Ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
QHttpServerResponse HttpServer::handlePrepareUploadRequest(const QHttpServerRequest& request)
|
QFuture<QHttpServerResponse> HttpServer::handlePrepareUploadRequest(const QHttpServerRequest& request)
|
||||||
{
|
{
|
||||||
QJsonParseError error;
|
QJsonParseError error;
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(request.body(), &error);
|
QJsonDocument doc = QJsonDocument::fromJson(request.body(), &error);
|
||||||
|
|
||||||
if (error.error != QJsonParseError::NoError) {
|
if (error.error != QJsonParseError::NoError) {
|
||||||
return QHttpServerResponse(QHttpServerResponse::StatusCode::BadRequest);
|
qWarning() << "[HttpServer] JSON parse error:" << error.errorString();
|
||||||
|
auto promise = std::make_shared<QPromise<QHttpServerResponse>>();
|
||||||
|
promise->start();
|
||||||
|
promise->addResult(QHttpServerResponse(QHttpServerResponse::StatusCode::BadRequest));
|
||||||
|
promise->finish();
|
||||||
|
return promise->future();
|
||||||
}
|
}
|
||||||
|
|
||||||
PrepareUploadRequestDto dto = PrepareUploadRequestDto::fromJson(doc.object());
|
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<QPromise<QHttpServerResponse>>();
|
||||||
|
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)
|
QHttpServerResponse HttpServer::handleUploadRequest(const QHttpServerRequest& request)
|
||||||
@@ -186,6 +264,14 @@ void HttpServer::setLocalInfo(const InfoDto& info, const QString& fingerprint)
|
|||||||
m_localFingerprint = fingerprint;
|
m_localFingerprint = fingerprint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HttpServer::respondToPrepareUpload(const QString& sessionId, bool accepted,
|
||||||
|
const QMap<QString, QString>& tokens)
|
||||||
|
{
|
||||||
|
Q_UNUSED(sessionId)
|
||||||
|
Q_UNUSED(accepted)
|
||||||
|
Q_UNUSED(tokens)
|
||||||
|
}
|
||||||
|
|
||||||
bool HttpServer::start(quint16 port, bool https)
|
bool HttpServer::start(quint16 port, bool https)
|
||||||
{
|
{
|
||||||
Q_UNUSED(port)
|
Q_UNUSED(port)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "LocalSendCore/MulticastDiscovery.h"
|
#include "LocalSendCore/MulticastDiscovery.h"
|
||||||
#include "LocalSendCore/Constants.h"
|
#include "LocalSendCore/Constants.h"
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
namespace LocalSend {
|
namespace LocalSend {
|
||||||
|
|
||||||
@@ -25,6 +26,8 @@ void MulticastDiscovery::setLocalInfo(const InfoDto& info, const QString& finger
|
|||||||
|
|
||||||
void MulticastDiscovery::start(const QString& multicastGroup, quint16 port)
|
void MulticastDiscovery::start(const QString& multicastGroup, quint16 port)
|
||||||
{
|
{
|
||||||
|
qDebug() << "[MulticastDiscovery] start() called";
|
||||||
|
|
||||||
if (m_socket) {
|
if (m_socket) {
|
||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
@@ -36,18 +39,22 @@ void MulticastDiscovery::start(const QString& multicastGroup, quint16 port)
|
|||||||
|
|
||||||
if (!m_socket->bind(QHostAddress::AnyIPv4, port,
|
if (!m_socket->bind(QHostAddress::AnyIPv4, port,
|
||||||
QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint)) {
|
QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint)) {
|
||||||
qWarning() << "Failed to bind UDP socket:" << m_socket->errorString();
|
qWarning() << "[MulticastDiscovery] Failed to bind UDP socket:" << m_socket->errorString();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qDebug() << "[MulticastDiscovery] Socket bound to port:" << port;
|
||||||
|
|
||||||
for (const QNetworkInterface& iface : QNetworkInterface::allInterfaces()) {
|
for (const QNetworkInterface& iface : QNetworkInterface::allInterfaces()) {
|
||||||
if (iface.flags() & QNetworkInterface::CanMulticast) {
|
if (iface.flags() & QNetworkInterface::CanMulticast) {
|
||||||
m_socket->joinMulticastGroup(QHostAddress(multicastGroup), iface);
|
m_socket->joinMulticastGroup(QHostAddress(multicastGroup), iface);
|
||||||
|
qDebug() << "[MulticastDiscovery] Joined multicast group on interface:" << iface.name();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(m_socket, &QUdpSocket::readyRead, this, &MulticastDiscovery::onReadyRead);
|
connect(m_socket, &QUdpSocket::readyRead, this, &MulticastDiscovery::onReadyRead);
|
||||||
|
|
||||||
|
qDebug() << "[MulticastDiscovery] Sending initial announcement";
|
||||||
sendAnnouncement();
|
sendAnnouncement();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,6 +70,8 @@ void MulticastDiscovery::stop()
|
|||||||
void MulticastDiscovery::sendAnnouncement()
|
void MulticastDiscovery::sendAnnouncement()
|
||||||
{
|
{
|
||||||
if (!m_socket || m_localFingerprint.isEmpty()) {
|
if (!m_socket || m_localFingerprint.isEmpty()) {
|
||||||
|
qWarning() << "[MulticastDiscovery] Cannot send announcement: socket=" << m_socket
|
||||||
|
<< "fingerprint empty=" << m_localFingerprint.isEmpty();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +88,7 @@ void MulticastDiscovery::sendAnnouncement()
|
|||||||
dto.announce = true;
|
dto.announce = true;
|
||||||
|
|
||||||
QByteArray data = QJsonDocument(dto.toJson()).toJson(QJsonDocument::Compact);
|
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);
|
m_socket->writeDatagram(data, QHostAddress(m_multicastGroup), m_port);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +120,11 @@ void MulticastDiscovery::handleMulticastMessage(const QHostAddress& sender, cons
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qDebug() << "[MulticastDiscovery] Received from:" << sender.toString()
|
||||||
|
<< "alias:" << dto.alias
|
||||||
|
<< "announce:" << dto.announce
|
||||||
|
<< "announcement:" << dto.announcement;
|
||||||
|
|
||||||
Device device;
|
Device device;
|
||||||
device.ip = sender.toString();
|
device.ip = sender.toString();
|
||||||
device.port = dto.port;
|
device.port = dto.port;
|
||||||
@@ -124,6 +139,29 @@ void MulticastDiscovery::handleMulticastMessage(const QHostAddress& sender, cons
|
|||||||
device.lastSeen = QDateTime::currentDateTime();
|
device.lastSeen = QDateTime::currentDateTime();
|
||||||
|
|
||||||
emit deviceDiscovered(device);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,13 @@ SessionManager::SessionManager(QObject* parent)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
QString SessionManager::createReceiveSession(const Device& sender, const QMap<QString, FileDto>& files)
|
QString SessionManager::createReceiveSession(const Device& sender, const QMap<QString, FileDto>& files,
|
||||||
|
const QString& sessionId)
|
||||||
{
|
{
|
||||||
QString sessionId = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
QString id = sessionId.isEmpty() ? QUuid::createUuid().toString(QUuid::WithoutBraces) : sessionId;
|
||||||
|
|
||||||
ReceiveSession session;
|
ReceiveSession session;
|
||||||
session.sessionId = sessionId;
|
session.sessionId = id;
|
||||||
session.sender = sender;
|
session.sender = sender;
|
||||||
session.status = SessionStatus::Waiting;
|
session.status = SessionStatus::Waiting;
|
||||||
|
|
||||||
@@ -25,10 +26,10 @@ QString SessionManager::createReceiveSession(const Device& sender, const QMap<QS
|
|||||||
session.files.insert(it.key(), transfer);
|
session.files.insert(it.key(), transfer);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_receiveSessions.insert(sessionId, session);
|
m_receiveSessions.insert(id, session);
|
||||||
emit receiveSessionCreated(sessionId);
|
emit receiveSessionCreated(id);
|
||||||
|
|
||||||
return sessionId;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionManager::acceptReceiveSession(const QString& sessionId,
|
void SessionManager::acceptReceiveSession(const QString& sessionId,
|
||||||
|
|||||||
Reference in New Issue
Block a user