init commit
This commit is contained in:
151
src/app/AppController.cpp
Normal file
151
src/app/AppController.cpp
Normal file
@@ -0,0 +1,151 @@
|
||||
#include "AppController.h"
|
||||
#include <QJsonArray>
|
||||
|
||||
AppController::AppController(QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_settings(new LocalSend::Settings(this))
|
||||
, m_security(new LocalSend::SecurityContext(this))
|
||||
, m_discovery(new LocalSend::DiscoveryManager(this))
|
||||
, m_server(new LocalSend::HttpServer(this))
|
||||
, m_sessions(new LocalSend::SessionManager(this))
|
||||
{
|
||||
}
|
||||
|
||||
AppController::~AppController()
|
||||
{
|
||||
stopDiscovery();
|
||||
}
|
||||
|
||||
void AppController::initialize()
|
||||
{
|
||||
m_security->initialize();
|
||||
|
||||
LocalSend::InfoDto info = buildInfoDto();
|
||||
m_server->setLocalInfo(info, m_security->fingerprint());
|
||||
m_discovery->setLocalInfo(info, m_security->fingerprint(), m_settings->port(),
|
||||
m_settings->https() ? LocalSend::ProtocolType::Https : LocalSend::ProtocolType::Http);
|
||||
|
||||
if (m_server->start(m_settings->port(), m_settings->https())) {
|
||||
emit serverRunningChanged();
|
||||
}
|
||||
|
||||
connect(m_discovery, &LocalSend::DiscoveryManager::deviceDiscovered,
|
||||
this, &AppController::onDeviceDiscovered);
|
||||
connect(m_discovery, &LocalSend::DiscoveryManager::deviceLost,
|
||||
this, &AppController::onDeviceLost);
|
||||
connect(m_server, &LocalSend::HttpServer::prepareUploadRequest,
|
||||
this, &AppController::onPrepareUploadRequest);
|
||||
|
||||
startDiscovery();
|
||||
}
|
||||
|
||||
LocalSend::InfoDto AppController::buildInfoDto() const
|
||||
{
|
||||
LocalSend::InfoDto info;
|
||||
info.alias = m_settings->alias();
|
||||
info.version = m_settings->version();
|
||||
info.deviceModel = m_settings->deviceModel();
|
||||
info.deviceType = m_settings->deviceType();
|
||||
info.download = false;
|
||||
return info;
|
||||
}
|
||||
|
||||
QString AppController::alias() const
|
||||
{
|
||||
return m_settings->alias();
|
||||
}
|
||||
|
||||
void AppController::setAlias(const QString& alias)
|
||||
{
|
||||
if (m_settings->alias() != alias) {
|
||||
m_settings->setAlias(alias);
|
||||
emit aliasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
quint16 AppController::port() const
|
||||
{
|
||||
return m_settings->port();
|
||||
}
|
||||
|
||||
void AppController::setPort(quint16 port)
|
||||
{
|
||||
if (m_settings->port() != port) {
|
||||
m_settings->setPort(port);
|
||||
emit portChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QVariantList AppController::devices() const
|
||||
{
|
||||
QVariantList result;
|
||||
for (const LocalSend::Device& device : m_devices) {
|
||||
QVariantMap map;
|
||||
map[QStringLiteral("ip")] = device.ip;
|
||||
map[QStringLiteral("port")] = device.port;
|
||||
map[QStringLiteral("alias")] = device.alias;
|
||||
map[QStringLiteral("fingerprint")] = device.fingerprint;
|
||||
map[QStringLiteral("deviceModel")] = device.deviceModel;
|
||||
map[QStringLiteral("deviceType")] = LocalSend::deviceTypeToString(device.deviceType);
|
||||
result.append(map);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool AppController::serverRunning() const
|
||||
{
|
||||
return m_server->isRunning();
|
||||
}
|
||||
|
||||
void AppController::startDiscovery()
|
||||
{
|
||||
m_discovery->start();
|
||||
}
|
||||
|
||||
void AppController::stopDiscovery()
|
||||
{
|
||||
m_discovery->stop();
|
||||
}
|
||||
|
||||
void AppController::refreshDevices()
|
||||
{
|
||||
m_discovery->startScan();
|
||||
}
|
||||
|
||||
void AppController::onDeviceDiscovered(const LocalSend::Device& device)
|
||||
{
|
||||
m_devices.insert(device.fingerprint, device);
|
||||
emit devicesChanged();
|
||||
}
|
||||
|
||||
void AppController::onDeviceLost(const QString& fingerprint)
|
||||
{
|
||||
m_devices.remove(fingerprint);
|
||||
emit devicesChanged();
|
||||
}
|
||||
|
||||
void AppController::onPrepareUploadRequest(const LocalSend::PrepareUploadRequestDto& dto,
|
||||
const QHostAddress& sender)
|
||||
{
|
||||
LocalSend::Device device;
|
||||
device.ip = sender.toString();
|
||||
device.alias = dto.info.alias;
|
||||
device.fingerprint = dto.info.fingerprint;
|
||||
device.deviceModel = dto.info.deviceModel;
|
||||
device.deviceType = dto.info.deviceType;
|
||||
device.version = dto.info.version;
|
||||
|
||||
QString sessionId = m_sessions->createReceiveSession(device, dto.files);
|
||||
|
||||
QVariantList filesList;
|
||||
for (auto it = dto.files.constBegin(); it != dto.files.constEnd(); ++it) {
|
||||
QVariantMap file;
|
||||
file[QStringLiteral("id")] = it.key();
|
||||
file[QStringLiteral("fileName")] = it.value().fileName;
|
||||
file[QStringLiteral("size")] = it.value().size;
|
||||
file[QStringLiteral("fileType")] = it.value().fileType;
|
||||
filesList.append(file);
|
||||
}
|
||||
|
||||
emit receiveRequest(sessionId, dto.info.alias, filesList);
|
||||
}
|
||||
63
src/app/AppController.h
Normal file
63
src/app/AppController.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariantList>
|
||||
#include "LocalSendCore/DiscoveryManager.h"
|
||||
#include "LocalSendCore/HttpServer.h"
|
||||
#include "LocalSendCore/HttpClient.h"
|
||||
#include "LocalSendCore/SessionManager.h"
|
||||
#include "LocalSendCore/SecurityContext.h"
|
||||
#include "LocalSendCore/Settings.h"
|
||||
|
||||
class AppController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString alias READ alias WRITE setAlias NOTIFY aliasChanged)
|
||||
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)
|
||||
|
||||
public:
|
||||
explicit AppController(QObject* parent = nullptr);
|
||||
~AppController() override;
|
||||
|
||||
void initialize();
|
||||
|
||||
QString alias() const;
|
||||
void setAlias(const QString& alias);
|
||||
|
||||
quint16 port() const;
|
||||
void setPort(quint16 port);
|
||||
|
||||
QVariantList devices() const;
|
||||
bool serverRunning() const;
|
||||
|
||||
Q_INVOKABLE void startDiscovery();
|
||||
Q_INVOKABLE void stopDiscovery();
|
||||
Q_INVOKABLE void refreshDevices();
|
||||
|
||||
signals:
|
||||
void aliasChanged();
|
||||
void portChanged();
|
||||
void devicesChanged();
|
||||
void serverRunningChanged();
|
||||
void receiveRequest(const QString& sessionId, const QString& senderAlias, const QVariantList& files);
|
||||
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);
|
||||
|
||||
private:
|
||||
LocalSend::Settings* m_settings = nullptr;
|
||||
LocalSend::SecurityContext* m_security = nullptr;
|
||||
LocalSend::DiscoveryManager* m_discovery = nullptr;
|
||||
LocalSend::HttpServer* m_server = nullptr;
|
||||
LocalSend::SessionManager* m_sessions = nullptr;
|
||||
|
||||
QMap<QString, LocalSend::Device> m_devices;
|
||||
|
||||
LocalSend::InfoDto buildInfoDto() const;
|
||||
};
|
||||
9
src/app/Application.cpp
Normal file
9
src/app/Application.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#include "Application.h"
|
||||
|
||||
Application::Application(int& argc, char** argv)
|
||||
: QGuiApplication(argc, argv)
|
||||
{
|
||||
setApplicationName(QStringLiteral("LocalSendQt"));
|
||||
setApplicationVersion(QStringLiteral("1.0.0"));
|
||||
setOrganizationName(QStringLiteral("LocalSend"));
|
||||
}
|
||||
12
src/app/Application.h
Normal file
12
src/app/Application.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QIcon>
|
||||
|
||||
class Application : public QGuiApplication
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Application(int& argc, char** argv);
|
||||
};
|
||||
23
src/app/CMakeLists.txt
Normal file
23
src/app/CMakeLists.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
qt_add_executable(LocalSendQt
|
||||
main.cpp
|
||||
Application.cpp
|
||||
AppController.cpp
|
||||
)
|
||||
|
||||
qt_add_qml_module(LocalSendQt
|
||||
URI LocalSend
|
||||
VERSION 1.0
|
||||
QML_FILES
|
||||
qml/main.qml
|
||||
)
|
||||
|
||||
target_link_libraries(LocalSendQt PRIVATE
|
||||
LocalSendCore
|
||||
Qt6::Quick
|
||||
Qt6::QuickControls2
|
||||
)
|
||||
|
||||
install(TARGETS LocalSendQt
|
||||
BUNDLE DESTINATION .
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
)
|
||||
33
src/app/main.cpp
Normal file
33
src/app/main.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#include <QGuiApplication>
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QQmlContext>
|
||||
#include <QQuickStyle>
|
||||
#include <QDir>
|
||||
#include "Application.h"
|
||||
#include "AppController.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
Application app(argc, argv);
|
||||
QQuickStyle::setStyle("Basic");
|
||||
|
||||
AppController controller;
|
||||
controller.initialize();
|
||||
|
||||
QQmlApplicationEngine engine;
|
||||
|
||||
engine.addImportPath(QStringLiteral("qrc:/"));
|
||||
|
||||
engine.rootContext()->setContextProperty(QStringLiteral("appController"), &controller);
|
||||
|
||||
const QUrl url(u"qrc:/LocalSend/qml/main.qml"_qs);
|
||||
|
||||
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
|
||||
&app, [url](QObject *obj, const QUrl &objUrl) {
|
||||
if (!obj && url == objUrl) QCoreApplication::exit(-1);
|
||||
}, Qt::QueuedConnection);
|
||||
|
||||
engine.load(url);
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
178
src/app/qml/main.qml
Normal file
178
src/app/qml/main.qml
Normal file
@@ -0,0 +1,178 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
ApplicationWindow {
|
||||
id: root
|
||||
visible: true
|
||||
width: 800
|
||||
height: 600
|
||||
title: qsTr("LocalSend")
|
||||
|
||||
StackView {
|
||||
id: stackView
|
||||
anchors.fill: parent
|
||||
|
||||
Component.onCompleted: {
|
||||
push(homePageComponent)
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: homePageComponent
|
||||
Page {
|
||||
id: homePage
|
||||
|
||||
signal openSettings()
|
||||
|
||||
header: ToolBar {
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
Label {
|
||||
text: qsTr("LocalSend")
|
||||
font.bold: true
|
||||
font.pixelSize: 20
|
||||
Layout.leftMargin: 16
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
ToolButton {
|
||||
text: qsTr("Settings")
|
||||
onClicked: homePage.openSettings()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 16
|
||||
|
||||
Label {
|
||||
text: qsTr("Nearby Devices")
|
||||
font.bold: true
|
||||
font.pixelSize: 16
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: deviceListView
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
property var devices: appController.devices
|
||||
model: devices
|
||||
spacing: 8
|
||||
|
||||
delegate: Pane {
|
||||
width: ListView.view.width
|
||||
padding: 12
|
||||
|
||||
background: Rectangle {
|
||||
color: Qt.lighter("gray", 1.8)
|
||||
radius: 8
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
Column {
|
||||
Layout.fillWidth: true
|
||||
Label {
|
||||
text: modelData.alias || modelData.ip
|
||||
font.bold: true
|
||||
}
|
||||
Label {
|
||||
text: "%1:%2".arg(modelData.ip).arg(modelData.port)
|
||||
color: "gray"
|
||||
font.pixelSize: 12
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Send")
|
||||
onClicked: {
|
||||
// send to this device
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Button {
|
||||
text: qsTr("Refresh")
|
||||
onClicked: appController.refreshDevices()
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
Label {
|
||||
text: qsTr("Alias: %1").arg(appController.alias)
|
||||
color: "gray"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onOpenSettings: stackView.push(settingsPageComponent)
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: settingsPageComponent
|
||||
Page {
|
||||
id: settingsPage
|
||||
signal back()
|
||||
|
||||
header: ToolBar {
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
ToolButton {
|
||||
text: qsTr("Back")
|
||||
onClicked: settingsPage.back()
|
||||
}
|
||||
Label {
|
||||
text: qsTr("Settings")
|
||||
font.bold: true
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 16
|
||||
|
||||
GridLayout {
|
||||
columns: 2
|
||||
Layout.fillWidth: true
|
||||
|
||||
Label { text: qsTr("Device Alias:") }
|
||||
TextField {
|
||||
id: aliasField
|
||||
text: appController.alias
|
||||
onEditingFinished: appController.alias = text
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Label { text: qsTr("Port:") }
|
||||
SpinBox {
|
||||
id: portField
|
||||
value: appController.port
|
||||
from: 1
|
||||
to: 65535
|
||||
onValueChanged: appController.port = value
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true }
|
||||
|
||||
Label {
|
||||
text: qsTr("Server Status: %1").arg(appController.serverRunning ? qsTr("Running") : qsTr("Stopped"))
|
||||
color: appController.serverRunning ? "green" : "red"
|
||||
}
|
||||
}
|
||||
|
||||
onBack: stackView.pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
52
src/core/CMakeLists.txt
Normal file
52
src/core/CMakeLists.txt
Normal file
@@ -0,0 +1,52 @@
|
||||
add_library(LocalSendCore SHARED
|
||||
src/Constants.cpp
|
||||
src/Types.cpp
|
||||
src/Device.cpp
|
||||
src/DtoTypes.cpp
|
||||
src/MulticastDiscovery.cpp
|
||||
src/DiscoveryManager.cpp
|
||||
src/HttpServer.cpp
|
||||
src/HttpClient.cpp
|
||||
src/SessionManager.cpp
|
||||
src/SecurityContext.cpp
|
||||
src/Settings.cpp
|
||||
|
||||
include/LocalSendCore/Constants.h
|
||||
include/LocalSendCore/Types.h
|
||||
include/LocalSendCore/Device.h
|
||||
include/LocalSendCore/DtoTypes.h
|
||||
include/LocalSendCore/MulticastDiscovery.h
|
||||
include/LocalSendCore/DiscoveryManager.h
|
||||
include/LocalSendCore/HttpServer.h
|
||||
include/LocalSendCore/HttpClient.h
|
||||
include/LocalSendCore/SessionManager.h
|
||||
include/LocalSendCore/SecurityContext.h
|
||||
include/LocalSendCore/Settings.h
|
||||
)
|
||||
|
||||
target_include_directories(LocalSendCore PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
)
|
||||
|
||||
target_link_libraries(LocalSendCore PUBLIC
|
||||
Qt6::Core
|
||||
Qt6::Network
|
||||
)
|
||||
|
||||
if(Qt6HttpServer_FOUND)
|
||||
target_link_libraries(LocalSendCore PUBLIC Qt6::HttpServer)
|
||||
target_compile_definitions(LocalSendCore PUBLIC HAS_QTHTTPSERVER)
|
||||
endif()
|
||||
|
||||
find_package(OpenSSL)
|
||||
if(OpenSSL_FOUND)
|
||||
target_link_libraries(LocalSendCore PUBLIC OpenSSL::SSL OpenSSL::Crypto)
|
||||
target_compile_definitions(LocalSendCore PUBLIC USE_OPENSSL)
|
||||
endif()
|
||||
|
||||
set_target_properties(LocalSendCore PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
SOVERSION 1
|
||||
OUTPUT_NAME localsend-core
|
||||
)
|
||||
27
src/core/include/LocalSendCore/Constants.h
Normal file
27
src/core/include/LocalSendCore/Constants.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
namespace LocalSend {
|
||||
|
||||
constexpr const char* PROTOCOL_VERSION = "2.1";
|
||||
constexpr const char* PEER_PROTOCOL_VERSION = "1.0";
|
||||
constexpr const char* FALLBACK_PROTOCOL_VERSION = "1.0";
|
||||
|
||||
constexpr quint16 DEFAULT_PORT = 53317;
|
||||
constexpr const char* DEFAULT_MULTICAST_GROUP = "224.0.0.167";
|
||||
constexpr int DEFAULT_DISCOVERY_TIMEOUT_MS = 500;
|
||||
|
||||
constexpr const char* API_BASE_PATH = "/api/localsend";
|
||||
constexpr const char* API_V2_PATH = "/api/localsend/v2";
|
||||
|
||||
namespace ApiRoute {
|
||||
constexpr const char* INFO = "/api/localsend/v2/info";
|
||||
constexpr const char* REGISTER = "/api/localsend/v2/register";
|
||||
constexpr const char* PREPARE_UPLOAD = "/api/localsend/v2/prepare-upload";
|
||||
constexpr const char* UPLOAD = "/api/localsend/v2/upload";
|
||||
constexpr const char* CANCEL = "/api/localsend/v2/cancel";
|
||||
constexpr const char* SHOW = "/api/localsend/v2/show";
|
||||
}
|
||||
|
||||
}
|
||||
41
src/core/include/LocalSendCore/Device.h
Normal file
41
src/core/include/LocalSendCore/Device.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QHostAddress>
|
||||
#include <QDateTime>
|
||||
#include "LocalSendCore/Types.h"
|
||||
|
||||
namespace LocalSend {
|
||||
|
||||
class Device
|
||||
{
|
||||
public:
|
||||
QString ip;
|
||||
quint16 port = 53317;
|
||||
ProtocolType protocol = ProtocolType::Http;
|
||||
|
||||
QString alias;
|
||||
QString fingerprint;
|
||||
QString deviceModel;
|
||||
DeviceType deviceType = DeviceType::Desktop;
|
||||
QString version;
|
||||
bool download = false;
|
||||
|
||||
DiscoveryMethod discoveryMethod = DiscoveryMethod::Multicast;
|
||||
QDateTime lastSeen;
|
||||
|
||||
Device() = default;
|
||||
Device(const QString& ip, quint16 port);
|
||||
|
||||
QString displayName() const;
|
||||
bool isHttps() const { return protocol == ProtocolType::Https; }
|
||||
|
||||
bool operator==(const Device& other) const {
|
||||
return fingerprint == other.fingerprint;
|
||||
}
|
||||
bool operator!=(const Device& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
48
src/core/include/LocalSendCore/DiscoveryManager.h
Normal file
48
src/core/include/LocalSendCore/DiscoveryManager.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QMap>
|
||||
#include <QTimer>
|
||||
#include "LocalSendCore/Device.h"
|
||||
#include "LocalSendCore/DtoTypes.h"
|
||||
#include "LocalSendCore/MulticastDiscovery.h"
|
||||
|
||||
namespace LocalSend {
|
||||
|
||||
class DiscoveryManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DiscoveryManager(QObject* parent = nullptr);
|
||||
~DiscoveryManager() override;
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
void startScan();
|
||||
|
||||
void setLocalInfo(const InfoDto& info, const QString& fingerprint,
|
||||
quint16 port, ProtocolType protocol);
|
||||
|
||||
QList<Device> discoveredDevices() const;
|
||||
Device deviceByFingerprint(const QString& fingerprint) const;
|
||||
|
||||
signals:
|
||||
void deviceDiscovered(const Device& device);
|
||||
void deviceUpdated(const Device& device);
|
||||
void deviceLost(const QString& fingerprint);
|
||||
void scanFinished();
|
||||
|
||||
private slots:
|
||||
void onDeviceDiscovered(const Device& device);
|
||||
void onDeviceTimeout();
|
||||
|
||||
private:
|
||||
MulticastDiscovery* m_multicastDiscovery = nullptr;
|
||||
QMap<QString, Device> m_devices;
|
||||
QTimer* m_cleanupTimer = nullptr;
|
||||
|
||||
static constexpr int DEVICE_TIMEOUT_MS = 30000;
|
||||
};
|
||||
|
||||
}
|
||||
102
src/core/include/LocalSendCore/DtoTypes.h
Normal file
102
src/core/include/LocalSendCore/DtoTypes.h
Normal file
@@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
#include <QMap>
|
||||
#include <QDateTime>
|
||||
#include <optional>
|
||||
#include "LocalSendCore/Types.h"
|
||||
|
||||
namespace LocalSend {
|
||||
|
||||
struct MulticastDto
|
||||
{
|
||||
QString alias;
|
||||
QString version;
|
||||
QString deviceModel;
|
||||
DeviceType deviceType = DeviceType::Desktop;
|
||||
QString fingerprint;
|
||||
quint16 port = 53317;
|
||||
ProtocolType protocol = ProtocolType::Http;
|
||||
bool download = false;
|
||||
bool announcement = false;
|
||||
bool announce = false;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static MulticastDto fromJson(const QJsonObject& json);
|
||||
};
|
||||
|
||||
struct InfoDto
|
||||
{
|
||||
QString alias;
|
||||
QString version;
|
||||
QString deviceModel;
|
||||
DeviceType deviceType = DeviceType::Desktop;
|
||||
QString fingerprint;
|
||||
bool download = false;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static InfoDto fromJson(const QJsonObject& json);
|
||||
};
|
||||
|
||||
struct RegisterDto
|
||||
{
|
||||
QString alias;
|
||||
QString version;
|
||||
QString deviceModel;
|
||||
DeviceType deviceType = DeviceType::Desktop;
|
||||
QString fingerprint;
|
||||
quint16 port = 53317;
|
||||
ProtocolType protocol = ProtocolType::Http;
|
||||
bool download = false;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static RegisterDto fromJson(const QJsonObject& json);
|
||||
};
|
||||
|
||||
struct FileDto
|
||||
{
|
||||
QString id;
|
||||
QString fileName;
|
||||
qint64 size = 0;
|
||||
QString fileType;
|
||||
QString hash;
|
||||
QString preview;
|
||||
|
||||
struct Metadata {
|
||||
QDateTime lastModified;
|
||||
QDateTime lastAccessed;
|
||||
};
|
||||
std::optional<Metadata> metadata;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static FileDto fromJson(const QJsonObject& json);
|
||||
};
|
||||
|
||||
struct PrepareUploadRequestDto
|
||||
{
|
||||
RegisterDto info;
|
||||
QMap<QString, FileDto> files;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static PrepareUploadRequestDto fromJson(const QJsonObject& json);
|
||||
};
|
||||
|
||||
struct PrepareUploadResponseDto
|
||||
{
|
||||
QString sessionId;
|
||||
QMap<QString, QString> files;
|
||||
|
||||
static PrepareUploadResponseDto fromJson(const QJsonObject& json);
|
||||
};
|
||||
|
||||
struct ReceiveRequestResponseDto
|
||||
{
|
||||
QString sessionId;
|
||||
QMap<QString, QString> destinationPaths;
|
||||
bool cancel = false;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
};
|
||||
|
||||
}
|
||||
51
src/core/include/LocalSendCore/HttpClient.h
Normal file
51
src/core/include/LocalSendCore/HttpClient.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QSslConfiguration>
|
||||
#include <functional>
|
||||
#include "LocalSendCore/Device.h"
|
||||
#include "LocalSendCore/DtoTypes.h"
|
||||
|
||||
namespace LocalSend {
|
||||
|
||||
class HttpClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit HttpClient(QObject* parent = nullptr);
|
||||
~HttpClient() override;
|
||||
|
||||
void setSslConfiguration(const QSslConfiguration& config);
|
||||
|
||||
void getInfo(const Device& device);
|
||||
void registerDevice(const Device& device, const RegisterDto& dto);
|
||||
void prepareUpload(const Device& device, const PrepareUploadRequestDto& dto);
|
||||
void uploadFile(const Device& device, const QString& sessionId, const QString& fileId,
|
||||
const QString& token, const QString& filePath);
|
||||
void cancel(const Device& device, const QString& sessionId);
|
||||
|
||||
signals:
|
||||
void infoReceived(const InfoDto& info);
|
||||
void infoError(const QString& error);
|
||||
void registerCompleted();
|
||||
void registerError(const QString& error);
|
||||
void prepareUploadResponse(const PrepareUploadResponseDto& response);
|
||||
void prepareUploadError(const QString& error);
|
||||
void uploadProgress(qint64 sent, qint64 total);
|
||||
void uploadCompleted();
|
||||
void uploadError(const QString& error);
|
||||
|
||||
private:
|
||||
QNetworkAccessManager* m_manager = nullptr;
|
||||
QSslConfiguration m_sslConfig;
|
||||
|
||||
QNetworkReply* sendGet(const QUrl& url);
|
||||
QNetworkReply* sendPost(const QUrl& url, const QByteArray& data);
|
||||
|
||||
QUrl buildUrl(const Device& device, const QString& path) const;
|
||||
};
|
||||
|
||||
}
|
||||
63
src/core/include/LocalSendCore/HttpServer.h
Normal file
63
src/core/include/LocalSendCore/HttpServer.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QHostAddress>
|
||||
#include "LocalSendCore/Device.h"
|
||||
#include "LocalSendCore/DtoTypes.h"
|
||||
|
||||
#ifdef HAS_QTHTTPSERVER
|
||||
#include <QHttpServer>
|
||||
#include <QSslConfiguration>
|
||||
#endif
|
||||
|
||||
namespace LocalSend {
|
||||
|
||||
class HttpServer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit HttpServer(QObject* parent = nullptr);
|
||||
~HttpServer() override;
|
||||
|
||||
bool start(quint16 port, bool https = false);
|
||||
void stop();
|
||||
|
||||
void setLocalInfo(const InfoDto& info, const QString& fingerprint);
|
||||
#ifdef HAS_QTHTTPSERVER
|
||||
void setSslConfiguration(const QSslConfiguration& config);
|
||||
#endif
|
||||
|
||||
quint16 serverPort() const;
|
||||
bool isRunning() const;
|
||||
|
||||
signals:
|
||||
void registerRequest(const RegisterDto& dto, const QHostAddress& sender);
|
||||
void prepareUploadRequest(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);
|
||||
|
||||
private:
|
||||
#ifdef HAS_QTHTTPSERVER
|
||||
QHttpServer* m_server = nullptr;
|
||||
QSslConfiguration m_sslConfig;
|
||||
#else
|
||||
QObject* m_server = nullptr;
|
||||
#endif
|
||||
|
||||
InfoDto m_localInfo;
|
||||
QString m_localFingerprint;
|
||||
quint16 m_port = 0;
|
||||
|
||||
#ifdef HAS_QTHTTPSERVER
|
||||
void setupRoutes();
|
||||
QHttpServerResponse handleInfoRequest();
|
||||
QHttpServerResponse handleRegisterRequest(const QHttpServerRequest& request, const QHostAddress& peer);
|
||||
QHttpServerResponse handlePrepareUploadRequest(const QHttpServerRequest& request);
|
||||
QHttpServerResponse handleUploadRequest(const QHttpServerRequest& request);
|
||||
QHttpServerResponse handleCancelRequest(const QHttpServerRequest& request);
|
||||
#endif
|
||||
};
|
||||
|
||||
}
|
||||
47
src/core/include/LocalSendCore/MulticastDiscovery.h
Normal file
47
src/core/include/LocalSendCore/MulticastDiscovery.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QUdpSocket>
|
||||
#include <QHostAddress>
|
||||
#include <QNetworkInterface>
|
||||
#include "LocalSendCore/Constants.h"
|
||||
#include "LocalSendCore/Device.h"
|
||||
#include "LocalSendCore/DtoTypes.h"
|
||||
|
||||
namespace LocalSend {
|
||||
|
||||
class MulticastDiscovery : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MulticastDiscovery(QObject* parent = nullptr);
|
||||
~MulticastDiscovery() override;
|
||||
|
||||
void start(const QString& multicastGroup = DEFAULT_MULTICAST_GROUP,
|
||||
quint16 port = DEFAULT_PORT);
|
||||
void stop();
|
||||
void sendAnnouncement();
|
||||
|
||||
void setLocalInfo(const InfoDto& info, const QString& fingerprint, quint16 port, ProtocolType protocol);
|
||||
|
||||
signals:
|
||||
void deviceDiscovered(const Device& device);
|
||||
|
||||
private slots:
|
||||
void onReadyRead();
|
||||
|
||||
private:
|
||||
QUdpSocket* m_socket = nullptr;
|
||||
QString m_multicastGroup;
|
||||
quint16 m_port = DEFAULT_PORT;
|
||||
|
||||
QString m_localFingerprint;
|
||||
InfoDto m_localInfo;
|
||||
quint16 m_localPort = DEFAULT_PORT;
|
||||
ProtocolType m_localProtocol = ProtocolType::Http;
|
||||
|
||||
void handleMulticastMessage(const QHostAddress& sender, const QByteArray& data);
|
||||
};
|
||||
|
||||
}
|
||||
41
src/core/include/LocalSendCore/SecurityContext.h
Normal file
41
src/core/include/LocalSendCore/SecurityContext.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QSslCertificate>
|
||||
#include <QSslKey>
|
||||
#include <QSslConfiguration>
|
||||
#include <QString>
|
||||
|
||||
namespace LocalSend {
|
||||
|
||||
class SecurityContext : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SecurityContext(QObject* parent = nullptr);
|
||||
|
||||
void initialize();
|
||||
void regenerate();
|
||||
|
||||
QString fingerprint() const { return m_fingerprint; }
|
||||
QSslCertificate certificate() const { return m_certificate; }
|
||||
QSslKey privateKey() const { return m_privateKey; }
|
||||
QSslConfiguration sslConfiguration() const;
|
||||
|
||||
bool isValid() const { return !m_fingerprint.isEmpty(); }
|
||||
|
||||
private:
|
||||
void loadFromStorage();
|
||||
void saveToStorage();
|
||||
void generateCertificate();
|
||||
QString calculateFingerprint(const QSslCertificate& cert) const;
|
||||
|
||||
QString m_fingerprint;
|
||||
QSslCertificate m_certificate;
|
||||
QSslKey m_privateKey;
|
||||
|
||||
QString storagePath() const;
|
||||
};
|
||||
|
||||
}
|
||||
86
src/core/include/LocalSendCore/SessionManager.h
Normal file
86
src/core/include/LocalSendCore/SessionManager.h
Normal file
@@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QMap>
|
||||
#include <QUuid>
|
||||
#include "LocalSendCore/Device.h"
|
||||
#include "LocalSendCore/DtoTypes.h"
|
||||
|
||||
namespace LocalSend {
|
||||
|
||||
struct FileTransfer
|
||||
{
|
||||
FileDto file;
|
||||
QString token;
|
||||
QString localPath;
|
||||
QString destinationPath;
|
||||
qint64 bytesTransferred = 0;
|
||||
FileStatus status = FileStatus::Queue;
|
||||
};
|
||||
|
||||
struct ReceiveSession
|
||||
{
|
||||
QString sessionId;
|
||||
Device sender;
|
||||
QMap<QString, FileTransfer> files;
|
||||
SessionStatus status = SessionStatus::Waiting;
|
||||
};
|
||||
|
||||
struct SendSession
|
||||
{
|
||||
QString sessionId;
|
||||
Device target;
|
||||
QMap<QString, FileTransfer> files;
|
||||
QString currentFileId;
|
||||
SessionStatus status = SessionStatus::Waiting;
|
||||
};
|
||||
|
||||
class SessionManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SessionManager(QObject* parent = nullptr);
|
||||
|
||||
QString createReceiveSession(const Device& sender, const QMap<QString, FileDto>& files);
|
||||
void acceptReceiveSession(const QString& sessionId, const QMap<QString, QString>& destinationPaths);
|
||||
void declineReceiveSession(const QString& sessionId);
|
||||
void updateReceiveProgress(const QString& sessionId, const QString& fileId, qint64 bytes);
|
||||
void completeReceiveFile(const QString& sessionId, const QString& fileId);
|
||||
void failReceiveFile(const QString& sessionId, const QString& fileId);
|
||||
void cancelReceiveSession(const QString& sessionId);
|
||||
|
||||
QString createSendSession(const Device& target, const QMap<QString, FileDto>& files,
|
||||
const QMap<QString, QString>& localPaths);
|
||||
void startSendSession(const QString& sessionId);
|
||||
void updateSendProgress(const QString& sessionId, const QString& fileId, qint64 bytes);
|
||||
void completeSendFile(const QString& sessionId, const QString& fileId);
|
||||
void failSendFile(const QString& sessionId, const QString& fileId);
|
||||
void cancelSendSession(const QString& sessionId);
|
||||
|
||||
ReceiveSession receiveSession(const QString& sessionId) const;
|
||||
SendSession sendSession(const QString& sessionId) const;
|
||||
bool hasReceiveSession(const QString& sessionId) const;
|
||||
bool hasSendSession(const QString& sessionId) const;
|
||||
QString generateToken();
|
||||
|
||||
signals:
|
||||
void receiveSessionCreated(const QString& sessionId);
|
||||
void receiveSessionAccepted(const QString& sessionId, const QMap<QString, QString>& tokens);
|
||||
void receiveSessionDeclined(const QString& sessionId);
|
||||
void receiveSessionCompleted(const QString& sessionId);
|
||||
void receiveSessionCanceled(const QString& sessionId);
|
||||
void receiveProgress(const QString& sessionId, const QString& fileId, double progress);
|
||||
|
||||
void sendSessionCreated(const QString& sessionId);
|
||||
void sendSessionStarted(const QString& sessionId);
|
||||
void sendSessionCompleted(const QString& sessionId);
|
||||
void sendSessionCanceled(const QString& sessionId);
|
||||
void sendProgress(const QString& sessionId, const QString& fileId, double progress);
|
||||
|
||||
private:
|
||||
QMap<QString, ReceiveSession> m_receiveSessions;
|
||||
QMap<QString, SendSession> m_sendSessions;
|
||||
};
|
||||
|
||||
}
|
||||
46
src/core/include/LocalSendCore/Settings.h
Normal file
46
src/core/include/LocalSendCore/Settings.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QSettings>
|
||||
#include <QString>
|
||||
#include "LocalSendCore/Types.h"
|
||||
|
||||
namespace LocalSend {
|
||||
|
||||
class Settings : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Settings(QObject* parent = nullptr);
|
||||
|
||||
QString alias() const;
|
||||
void setAlias(const QString& alias);
|
||||
|
||||
quint16 port() const;
|
||||
void setPort(quint16 port);
|
||||
|
||||
bool https() const;
|
||||
void setHttps(bool enabled);
|
||||
|
||||
QString downloadDir() const;
|
||||
void setDownloadDir(const QString& path);
|
||||
|
||||
bool quickSave() const;
|
||||
void setQuickSave(bool enabled);
|
||||
|
||||
QString deviceModel() const;
|
||||
void setDeviceModel(const QString& model);
|
||||
|
||||
DeviceType deviceType() const;
|
||||
void setDeviceType(DeviceType type);
|
||||
|
||||
QString version() const;
|
||||
|
||||
void sync();
|
||||
|
||||
private:
|
||||
QSettings m_settings;
|
||||
};
|
||||
|
||||
}
|
||||
57
src/core/include/LocalSendCore/Types.h
Normal file
57
src/core/include/LocalSendCore/Types.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QMetaType>
|
||||
|
||||
namespace LocalSend {
|
||||
|
||||
enum class DeviceType {
|
||||
Mobile,
|
||||
Desktop,
|
||||
Web,
|
||||
Headless,
|
||||
Server
|
||||
};
|
||||
|
||||
enum class ProtocolType {
|
||||
Http,
|
||||
Https
|
||||
};
|
||||
|
||||
enum class SessionStatus {
|
||||
Waiting,
|
||||
Sending,
|
||||
Finished,
|
||||
FinishedWithErrors,
|
||||
CanceledBySender,
|
||||
CanceledByReceiver,
|
||||
Declined
|
||||
};
|
||||
|
||||
enum class FileStatus {
|
||||
Queue,
|
||||
Sending,
|
||||
Finished,
|
||||
Skipped,
|
||||
Failed
|
||||
};
|
||||
|
||||
enum class DiscoveryMethod {
|
||||
Multicast,
|
||||
HttpScan,
|
||||
HttpTarget
|
||||
};
|
||||
|
||||
QString deviceTypeToString(DeviceType type);
|
||||
DeviceType deviceTypeFromString(const QString& str);
|
||||
|
||||
QString protocolTypeToString(ProtocolType type);
|
||||
ProtocolType protocolTypeFromString(const QString& str);
|
||||
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(LocalSend::DeviceType)
|
||||
Q_DECLARE_METATYPE(LocalSend::ProtocolType)
|
||||
Q_DECLARE_METATYPE(LocalSend::SessionStatus)
|
||||
Q_DECLARE_METATYPE(LocalSend::FileStatus)
|
||||
Q_DECLARE_METATYPE(LocalSend::DiscoveryMethod)
|
||||
1
src/core/src/Constants.cpp
Normal file
1
src/core/src/Constants.cpp
Normal file
@@ -0,0 +1 @@
|
||||
#include "LocalSendCore/Constants.h"
|
||||
16
src/core/src/Device.cpp
Normal file
16
src/core/src/Device.cpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#include "LocalSendCore/Device.h"
|
||||
|
||||
namespace LocalSend {
|
||||
|
||||
Device::Device(const QString& ip, quint16 port)
|
||||
: ip(ip)
|
||||
, port(port)
|
||||
{
|
||||
}
|
||||
|
||||
QString Device::displayName() const
|
||||
{
|
||||
return alias.isEmpty() ? ip : alias;
|
||||
}
|
||||
|
||||
}
|
||||
85
src/core/src/DiscoveryManager.cpp
Normal file
85
src/core/src/DiscoveryManager.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
#include "LocalSendCore/DiscoveryManager.h"
|
||||
#include "LocalSendCore/Constants.h"
|
||||
|
||||
namespace LocalSend {
|
||||
|
||||
DiscoveryManager::DiscoveryManager(QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_multicastDiscovery(new MulticastDiscovery(this))
|
||||
, m_cleanupTimer(new QTimer(this))
|
||||
{
|
||||
connect(m_multicastDiscovery, &MulticastDiscovery::deviceDiscovered,
|
||||
this, &DiscoveryManager::onDeviceDiscovered);
|
||||
connect(m_cleanupTimer, &QTimer::timeout, this, &DiscoveryManager::onDeviceTimeout);
|
||||
}
|
||||
|
||||
DiscoveryManager::~DiscoveryManager()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void DiscoveryManager::setLocalInfo(const InfoDto& info, const QString& fingerprint,
|
||||
quint16 port, ProtocolType protocol)
|
||||
{
|
||||
m_multicastDiscovery->setLocalInfo(info, fingerprint, port, protocol);
|
||||
}
|
||||
|
||||
void DiscoveryManager::start()
|
||||
{
|
||||
m_multicastDiscovery->start();
|
||||
m_cleanupTimer->start(5000);
|
||||
}
|
||||
|
||||
void DiscoveryManager::stop()
|
||||
{
|
||||
m_multicastDiscovery->stop();
|
||||
m_cleanupTimer->stop();
|
||||
m_devices.clear();
|
||||
}
|
||||
|
||||
void DiscoveryManager::startScan()
|
||||
{
|
||||
m_multicastDiscovery->sendAnnouncement();
|
||||
}
|
||||
|
||||
QList<Device> DiscoveryManager::discoveredDevices() const
|
||||
{
|
||||
return m_devices.values();
|
||||
}
|
||||
|
||||
Device DiscoveryManager::deviceByFingerprint(const QString& fingerprint) const
|
||||
{
|
||||
return m_devices.value(fingerprint);
|
||||
}
|
||||
|
||||
void DiscoveryManager::onDeviceDiscovered(const Device& device)
|
||||
{
|
||||
bool isNew = !m_devices.contains(device.fingerprint);
|
||||
m_devices.insert(device.fingerprint, device);
|
||||
|
||||
if (isNew) {
|
||||
emit deviceDiscovered(device);
|
||||
} else {
|
||||
emit deviceUpdated(device);
|
||||
}
|
||||
}
|
||||
|
||||
void DiscoveryManager::onDeviceTimeout()
|
||||
{
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
QStringList timedOut;
|
||||
|
||||
for (auto it = m_devices.constBegin(); it != m_devices.constEnd(); ++it) {
|
||||
if (it->lastSeen.msecsTo(now) > DEVICE_TIMEOUT_MS) {
|
||||
timedOut.append(it.key());
|
||||
}
|
||||
}
|
||||
|
||||
for (const QString& fingerprint : timedOut) {
|
||||
m_devices.remove(fingerprint);
|
||||
emit deviceLost(fingerprint);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
192
src/core/src/DtoTypes.cpp
Normal file
192
src/core/src/DtoTypes.cpp
Normal file
@@ -0,0 +1,192 @@
|
||||
#include "LocalSendCore/DtoTypes.h"
|
||||
#include "LocalSendCore/Constants.h"
|
||||
|
||||
namespace LocalSend {
|
||||
|
||||
QJsonObject MulticastDto::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj[QStringLiteral("alias")] = alias;
|
||||
obj[QStringLiteral("version")] = version.isEmpty() ? QString(PROTOCOL_VERSION) : version;
|
||||
obj[QStringLiteral("deviceModel")] = deviceModel;
|
||||
obj[QStringLiteral("deviceType")] = deviceTypeToString(deviceType);
|
||||
obj[QStringLiteral("fingerprint")] = fingerprint;
|
||||
obj[QStringLiteral("port")] = port;
|
||||
obj[QStringLiteral("protocol")] = protocolTypeToString(protocol);
|
||||
obj[QStringLiteral("download")] = download;
|
||||
obj[QStringLiteral("announcement")] = announcement;
|
||||
obj[QStringLiteral("announce")] = announce;
|
||||
return obj;
|
||||
}
|
||||
|
||||
MulticastDto MulticastDto::fromJson(const QJsonObject& json)
|
||||
{
|
||||
MulticastDto dto;
|
||||
dto.alias = json[QStringLiteral("alias")].toString();
|
||||
dto.version = json[QStringLiteral("version")].toString();
|
||||
dto.deviceModel = json[QStringLiteral("deviceModel")].toString();
|
||||
dto.deviceType = deviceTypeFromString(json[QStringLiteral("deviceType")].toString());
|
||||
dto.fingerprint = json[QStringLiteral("fingerprint")].toString();
|
||||
dto.port = static_cast<quint16>(json[QStringLiteral("port")].toInt(DEFAULT_PORT));
|
||||
dto.protocol = protocolTypeFromString(json[QStringLiteral("protocol")].toString());
|
||||
dto.download = json[QStringLiteral("download")].toBool();
|
||||
dto.announcement = json[QStringLiteral("announcement")].toBool();
|
||||
dto.announce = json[QStringLiteral("announce")].toBool();
|
||||
return dto;
|
||||
}
|
||||
|
||||
QJsonObject InfoDto::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj[QStringLiteral("alias")] = alias;
|
||||
obj[QStringLiteral("version")] = version.isEmpty() ? QString(PROTOCOL_VERSION) : version;
|
||||
obj[QStringLiteral("deviceModel")] = deviceModel;
|
||||
obj[QStringLiteral("deviceType")] = deviceTypeToString(deviceType);
|
||||
obj[QStringLiteral("fingerprint")] = fingerprint;
|
||||
obj[QStringLiteral("download")] = download;
|
||||
return obj;
|
||||
}
|
||||
|
||||
InfoDto InfoDto::fromJson(const QJsonObject& json)
|
||||
{
|
||||
InfoDto dto;
|
||||
dto.alias = json[QStringLiteral("alias")].toString();
|
||||
dto.version = json[QStringLiteral("version")].toString();
|
||||
dto.deviceModel = json[QStringLiteral("deviceModel")].toString();
|
||||
dto.deviceType = deviceTypeFromString(json[QStringLiteral("deviceType")].toString());
|
||||
dto.fingerprint = json[QStringLiteral("fingerprint")].toString();
|
||||
dto.download = json[QStringLiteral("download")].toBool();
|
||||
return dto;
|
||||
}
|
||||
|
||||
QJsonObject RegisterDto::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj[QStringLiteral("alias")] = alias;
|
||||
obj[QStringLiteral("version")] = version.isEmpty() ? QString(PROTOCOL_VERSION) : version;
|
||||
obj[QStringLiteral("deviceModel")] = deviceModel;
|
||||
obj[QStringLiteral("deviceType")] = deviceTypeToString(deviceType);
|
||||
obj[QStringLiteral("fingerprint")] = fingerprint;
|
||||
obj[QStringLiteral("port")] = port;
|
||||
obj[QStringLiteral("protocol")] = protocolTypeToString(protocol);
|
||||
obj[QStringLiteral("download")] = download;
|
||||
return obj;
|
||||
}
|
||||
|
||||
RegisterDto RegisterDto::fromJson(const QJsonObject& json)
|
||||
{
|
||||
RegisterDto dto;
|
||||
dto.alias = json[QStringLiteral("alias")].toString();
|
||||
dto.version = json[QStringLiteral("version")].toString();
|
||||
dto.deviceModel = json[QStringLiteral("deviceModel")].toString();
|
||||
dto.deviceType = deviceTypeFromString(json[QStringLiteral("deviceType")].toString());
|
||||
dto.fingerprint = json[QStringLiteral("fingerprint")].toString();
|
||||
dto.port = static_cast<quint16>(json[QStringLiteral("port")].toInt(DEFAULT_PORT));
|
||||
dto.protocol = protocolTypeFromString(json[QStringLiteral("protocol")].toString());
|
||||
dto.download = json[QStringLiteral("download")].toBool();
|
||||
return dto;
|
||||
}
|
||||
|
||||
QJsonObject FileDto::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj[QStringLiteral("id")] = id;
|
||||
obj[QStringLiteral("fileName")] = fileName;
|
||||
obj[QStringLiteral("size")] = size;
|
||||
obj[QStringLiteral("fileType")] = fileType;
|
||||
if (!hash.isEmpty()) {
|
||||
obj[QStringLiteral("hash")] = hash;
|
||||
}
|
||||
if (!preview.isEmpty()) {
|
||||
obj[QStringLiteral("preview")] = preview;
|
||||
}
|
||||
if (metadata) {
|
||||
QJsonObject metaObj;
|
||||
if (metadata->lastModified.isValid()) {
|
||||
metaObj[QStringLiteral("lastModified")] = metadata->lastModified.toMSecsSinceEpoch();
|
||||
}
|
||||
if (metadata->lastAccessed.isValid()) {
|
||||
metaObj[QStringLiteral("lastAccessed")] = metadata->lastAccessed.toMSecsSinceEpoch();
|
||||
}
|
||||
obj[QStringLiteral("metadata")] = metaObj;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
FileDto FileDto::fromJson(const QJsonObject& json)
|
||||
{
|
||||
FileDto dto;
|
||||
dto.id = json[QStringLiteral("id")].toString();
|
||||
dto.fileName = json[QStringLiteral("fileName")].toString();
|
||||
dto.size = json[QStringLiteral("size")].toVariant().toLongLong();
|
||||
dto.fileType = json[QStringLiteral("fileType")].toString();
|
||||
dto.hash = json[QStringLiteral("hash")].toString();
|
||||
dto.preview = json[QStringLiteral("preview")].toString();
|
||||
|
||||
if (json.contains(QStringLiteral("metadata"))) {
|
||||
QJsonObject metaObj = json[QStringLiteral("metadata")].toObject();
|
||||
FileDto::Metadata meta;
|
||||
if (metaObj.contains(QStringLiteral("lastModified"))) {
|
||||
meta.lastModified = QDateTime::fromMSecsSinceEpoch(
|
||||
metaObj[QStringLiteral("lastModified")].toVariant().toLongLong());
|
||||
}
|
||||
if (metaObj.contains(QStringLiteral("lastAccessed"))) {
|
||||
meta.lastAccessed = QDateTime::fromMSecsSinceEpoch(
|
||||
metaObj[QStringLiteral("lastAccessed")].toVariant().toLongLong());
|
||||
}
|
||||
dto.metadata = meta;
|
||||
}
|
||||
return dto;
|
||||
}
|
||||
|
||||
QJsonObject PrepareUploadRequestDto::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj[QStringLiteral("info")] = info.toJson();
|
||||
|
||||
QJsonObject filesObj;
|
||||
for (auto it = files.constBegin(); it != files.constEnd(); ++it) {
|
||||
filesObj[it.key()] = it.value().toJson();
|
||||
}
|
||||
obj[QStringLiteral("files")] = filesObj;
|
||||
return obj;
|
||||
}
|
||||
|
||||
PrepareUploadRequestDto PrepareUploadRequestDto::fromJson(const QJsonObject& json)
|
||||
{
|
||||
PrepareUploadRequestDto dto;
|
||||
dto.info = RegisterDto::fromJson(json[QStringLiteral("info")].toObject());
|
||||
|
||||
QJsonObject filesObj = json[QStringLiteral("files")].toObject();
|
||||
for (auto it = filesObj.constBegin(); it != filesObj.constEnd(); ++it) {
|
||||
dto.files.insert(it.key(), FileDto::fromJson(it.value().toObject()));
|
||||
}
|
||||
return dto;
|
||||
}
|
||||
|
||||
PrepareUploadResponseDto PrepareUploadResponseDto::fromJson(const QJsonObject& json)
|
||||
{
|
||||
PrepareUploadResponseDto dto;
|
||||
dto.sessionId = json[QStringLiteral("sessionId")].toString();
|
||||
|
||||
QJsonObject filesObj = json[QStringLiteral("files")].toObject();
|
||||
for (auto it = filesObj.constBegin(); it != filesObj.constEnd(); ++it) {
|
||||
dto.files.insert(it.key(), it.value().toString());
|
||||
}
|
||||
return dto;
|
||||
}
|
||||
|
||||
QJsonObject ReceiveRequestResponseDto::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj[QStringLiteral("sessionId")] = sessionId;
|
||||
|
||||
QJsonObject pathsObj;
|
||||
for (auto it = destinationPaths.constBegin(); it != destinationPaths.constEnd(); ++it) {
|
||||
pathsObj[it.key()] = it.value();
|
||||
}
|
||||
obj[QStringLiteral("destinationPaths")] = pathsObj;
|
||||
return obj;
|
||||
}
|
||||
|
||||
}
|
||||
182
src/core/src/HttpClient.cpp
Normal file
182
src/core/src/HttpClient.cpp
Normal file
@@ -0,0 +1,182 @@
|
||||
#include "LocalSendCore/HttpClient.h"
|
||||
#include "LocalSendCore/Constants.h"
|
||||
#include <QFile>
|
||||
#include <QUrlQuery>
|
||||
#include <QJsonDocument>
|
||||
|
||||
namespace LocalSend {
|
||||
|
||||
HttpClient::HttpClient(QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_manager(new QNetworkAccessManager(this))
|
||||
{
|
||||
}
|
||||
|
||||
HttpClient::~HttpClient()
|
||||
{
|
||||
}
|
||||
|
||||
void HttpClient::setSslConfiguration(const QSslConfiguration& config)
|
||||
{
|
||||
m_sslConfig = config;
|
||||
}
|
||||
|
||||
QUrl HttpClient::buildUrl(const Device& device, const QString& path) const
|
||||
{
|
||||
QUrl url;
|
||||
url.setScheme(device.isHttps() ? QStringLiteral("https") : QStringLiteral("http"));
|
||||
url.setHost(device.ip);
|
||||
url.setPort(device.port);
|
||||
url.setPath(path);
|
||||
return url;
|
||||
}
|
||||
|
||||
QNetworkReply* HttpClient::sendGet(const QUrl& url)
|
||||
{
|
||||
QNetworkRequest request(url);
|
||||
if (m_sslConfig.isNull() == false) {
|
||||
request.setSslConfiguration(m_sslConfig);
|
||||
}
|
||||
return m_manager->get(request);
|
||||
}
|
||||
|
||||
QNetworkReply* HttpClient::sendPost(const QUrl& url, const QByteArray& data)
|
||||
{
|
||||
QNetworkRequest request(url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
|
||||
if (m_sslConfig.isNull() == false) {
|
||||
request.setSslConfiguration(m_sslConfig);
|
||||
}
|
||||
return m_manager->post(request, data);
|
||||
}
|
||||
|
||||
void HttpClient::getInfo(const Device& device)
|
||||
{
|
||||
QUrl url = buildUrl(device, ApiRoute::INFO);
|
||||
QNetworkReply* reply = sendGet(url);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
emit infoError(reply->errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonParseError error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(reply->readAll(), &error);
|
||||
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
emit infoError(QStringLiteral("Invalid JSON response"));
|
||||
return;
|
||||
}
|
||||
|
||||
InfoDto info = InfoDto::fromJson(doc.object());
|
||||
emit infoReceived(info);
|
||||
});
|
||||
}
|
||||
|
||||
void HttpClient::registerDevice(const Device& device, const RegisterDto& dto)
|
||||
{
|
||||
QUrl url = buildUrl(device, ApiRoute::REGISTER);
|
||||
QByteArray data = QJsonDocument(dto.toJson()).toJson(QJsonDocument::Compact);
|
||||
QNetworkReply* reply = sendPost(url, data);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
emit registerError(reply->errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
emit registerCompleted();
|
||||
});
|
||||
}
|
||||
|
||||
void HttpClient::prepareUpload(const Device& device, const PrepareUploadRequestDto& dto)
|
||||
{
|
||||
QUrl url = buildUrl(device, ApiRoute::PREPARE_UPLOAD);
|
||||
QByteArray data = QJsonDocument(dto.toJson()).toJson(QJsonDocument::Compact);
|
||||
QNetworkReply* reply = sendPost(url, data);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
emit prepareUploadError(reply->errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonParseError error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(reply->readAll(), &error);
|
||||
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
emit prepareUploadError(QStringLiteral("Invalid JSON response"));
|
||||
return;
|
||||
}
|
||||
|
||||
PrepareUploadResponseDto response = PrepareUploadResponseDto::fromJson(doc.object());
|
||||
emit prepareUploadResponse(response);
|
||||
});
|
||||
}
|
||||
|
||||
void HttpClient::uploadFile(const Device& device, const QString& sessionId,
|
||||
const QString& fileId, const QString& token, const QString& filePath)
|
||||
{
|
||||
QUrl url = buildUrl(device, ApiRoute::UPLOAD);
|
||||
QUrlQuery query;
|
||||
query.addQueryItem(QStringLiteral("sessionId"), sessionId);
|
||||
query.addQueryItem(QStringLiteral("fileId"), fileId);
|
||||
query.addQueryItem(QStringLiteral("token"), token);
|
||||
url.setQuery(query);
|
||||
|
||||
QFile* file = new QFile(filePath);
|
||||
if (!file->open(QIODevice::ReadOnly)) {
|
||||
emit uploadError(QStringLiteral("Cannot open file: ") + file->errorString());
|
||||
delete file;
|
||||
return;
|
||||
}
|
||||
|
||||
qint64 fileSize = file->size();
|
||||
|
||||
QNetworkRequest request(url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/octet-stream"));
|
||||
request.setHeader(QNetworkRequest::ContentLengthHeader, fileSize);
|
||||
if (m_sslConfig.isNull() == false) {
|
||||
request.setSslConfiguration(m_sslConfig);
|
||||
}
|
||||
|
||||
QNetworkReply* reply = m_manager->post(request, file);
|
||||
file->setParent(reply);
|
||||
|
||||
connect(reply, &QNetworkReply::uploadProgress, this, [this, fileSize](qint64 sent, qint64) {
|
||||
emit uploadProgress(sent, fileSize);
|
||||
});
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
emit uploadError(reply->errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
emit uploadCompleted();
|
||||
});
|
||||
}
|
||||
|
||||
void HttpClient::cancel(const Device& device, const QString& sessionId)
|
||||
{
|
||||
QUrl url = buildUrl(device, ApiRoute::CANCEL);
|
||||
|
||||
QJsonObject body;
|
||||
body[QStringLiteral("sessionId")] = sessionId;
|
||||
QByteArray data = QJsonDocument(body).toJson(QJsonDocument::Compact);
|
||||
|
||||
QNetworkReply* reply = sendPost(url, data);
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
213
src/core/src/HttpServer.cpp
Normal file
213
src/core/src/HttpServer.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
#include "LocalSendCore/HttpServer.h"
|
||||
#include "LocalSendCore/Constants.h"
|
||||
|
||||
#ifdef HAS_QTHTTPSERVER
|
||||
#include <QTcpServer>
|
||||
#include <QJsonDocument>
|
||||
|
||||
namespace LocalSend {
|
||||
|
||||
HttpServer::HttpServer(QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_server(new QHttpServer(this))
|
||||
{
|
||||
setupRoutes();
|
||||
}
|
||||
|
||||
HttpServer::~HttpServer()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void HttpServer::setLocalInfo(const InfoDto& info, const QString& fingerprint)
|
||||
{
|
||||
m_localInfo = info;
|
||||
m_localFingerprint = fingerprint;
|
||||
}
|
||||
|
||||
void HttpServer::setSslConfiguration(const QSslConfiguration& config)
|
||||
{
|
||||
m_sslConfig = config;
|
||||
}
|
||||
|
||||
bool HttpServer::start(quint16 port, bool https)
|
||||
{
|
||||
Q_UNUSED(https)
|
||||
|
||||
if (m_server->isListening()) {
|
||||
stop();
|
||||
}
|
||||
|
||||
m_port = port;
|
||||
|
||||
auto tcpServer = new QTcpServer(this);
|
||||
if (!tcpServer->listen(QHostAddress::Any, port)) {
|
||||
delete tcpServer;
|
||||
return false;
|
||||
}
|
||||
m_server->bind(tcpServer);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void HttpServer::stop()
|
||||
{
|
||||
m_server->close();
|
||||
}
|
||||
|
||||
quint16 HttpServer::serverPort() const
|
||||
{
|
||||
return m_port;
|
||||
}
|
||||
|
||||
bool HttpServer::isRunning() const
|
||||
{
|
||||
return m_server->isListening();
|
||||
}
|
||||
|
||||
void HttpServer::setupRoutes()
|
||||
{
|
||||
m_server->route(ApiRoute::INFO, QHttpServerRequest::Method::Get,
|
||||
[this](const QHttpServerRequest&) {
|
||||
return handleInfoRequest();
|
||||
});
|
||||
|
||||
m_server->route(ApiRoute::REGISTER, QHttpServerRequest::Method::Post,
|
||||
[this](const QHttpServerRequest& request) {
|
||||
return handleRegisterRequest(request, request.remoteAddress());
|
||||
});
|
||||
|
||||
m_server->route(ApiRoute::PREPARE_UPLOAD, QHttpServerRequest::Method::Post,
|
||||
[this](const QHttpServerRequest& request) {
|
||||
return handlePrepareUploadRequest(request);
|
||||
});
|
||||
|
||||
m_server->route(ApiRoute::UPLOAD, QHttpServerRequest::Method::Post,
|
||||
[this](const QHttpServerRequest& request) {
|
||||
return handleUploadRequest(request);
|
||||
});
|
||||
|
||||
m_server->route(ApiRoute::CANCEL, QHttpServerRequest::Method::Post,
|
||||
[this](const QHttpServerRequest& request) {
|
||||
return handleCancelRequest(request);
|
||||
});
|
||||
}
|
||||
|
||||
QHttpServerResponse HttpServer::handleInfoRequest()
|
||||
{
|
||||
QJsonDocument doc(m_localInfo.toJson());
|
||||
return QHttpServerResponse(doc.toJson(QJsonDocument::Compact),
|
||||
QHttpServerResponse::StatusCode::Ok);
|
||||
}
|
||||
|
||||
QHttpServerResponse HttpServer::handleRegisterRequest(const QHttpServerRequest& request,
|
||||
const QHostAddress& peer)
|
||||
{
|
||||
QJsonParseError error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(request.body(), &error);
|
||||
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
return QHttpServerResponse(QHttpServerResponse::StatusCode::BadRequest);
|
||||
}
|
||||
|
||||
RegisterDto dto = RegisterDto::fromJson(doc.object());
|
||||
emit registerRequest(dto, peer);
|
||||
|
||||
QJsonDocument response(m_localInfo.toJson());
|
||||
return QHttpServerResponse(response.toJson(QJsonDocument::Compact),
|
||||
QHttpServerResponse::StatusCode::Ok);
|
||||
}
|
||||
|
||||
QHttpServerResponse HttpServer::handlePrepareUploadRequest(const QHttpServerRequest& request)
|
||||
{
|
||||
QJsonParseError error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(request.body(), &error);
|
||||
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
return QHttpServerResponse(QHttpServerResponse::StatusCode::BadRequest);
|
||||
}
|
||||
|
||||
PrepareUploadRequestDto dto = PrepareUploadRequestDto::fromJson(doc.object());
|
||||
emit prepareUploadRequest(dto, request.remoteAddress());
|
||||
|
||||
return QHttpServerResponse(QHttpServerResponse::StatusCode::Accepted);
|
||||
}
|
||||
|
||||
QHttpServerResponse HttpServer::handleUploadRequest(const QHttpServerRequest& request)
|
||||
{
|
||||
QUrlQuery query(request.url().query());
|
||||
QString sessionId = query.queryItemValue(QStringLiteral("sessionId"));
|
||||
QString fileId = query.queryItemValue(QStringLiteral("fileId"));
|
||||
QString token = query.queryItemValue(QStringLiteral("token"));
|
||||
|
||||
if (sessionId.isEmpty() || fileId.isEmpty() || token.isEmpty()) {
|
||||
return QHttpServerResponse(QHttpServerResponse::StatusCode::BadRequest);
|
||||
}
|
||||
|
||||
emit uploadRequest(sessionId, fileId, token, request.body());
|
||||
|
||||
return QHttpServerResponse(QHttpServerResponse::StatusCode::Ok);
|
||||
}
|
||||
|
||||
QHttpServerResponse HttpServer::handleCancelRequest(const QHttpServerRequest& request)
|
||||
{
|
||||
QJsonParseError error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(request.body(), &error);
|
||||
|
||||
QString sessionId;
|
||||
if (error.error == QJsonParseError::NoError) {
|
||||
sessionId = doc.object()[QStringLiteral("sessionId")].toString();
|
||||
}
|
||||
|
||||
emit cancelRequest(sessionId);
|
||||
|
||||
return QHttpServerResponse(QHttpServerResponse::StatusCode::Ok);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#else
|
||||
|
||||
namespace LocalSend {
|
||||
|
||||
HttpServer::HttpServer(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
HttpServer::~HttpServer()
|
||||
{
|
||||
}
|
||||
|
||||
void HttpServer::setLocalInfo(const InfoDto& info, const QString& fingerprint)
|
||||
{
|
||||
m_localInfo = info;
|
||||
m_localFingerprint = fingerprint;
|
||||
}
|
||||
|
||||
bool HttpServer::start(quint16 port, bool https)
|
||||
{
|
||||
Q_UNUSED(port)
|
||||
Q_UNUSED(https)
|
||||
return false;
|
||||
}
|
||||
|
||||
void HttpServer::stop()
|
||||
{
|
||||
}
|
||||
|
||||
quint16 HttpServer::serverPort() const
|
||||
{
|
||||
return m_port;
|
||||
}
|
||||
|
||||
bool HttpServer::isRunning() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
130
src/core/src/MulticastDiscovery.cpp
Normal file
130
src/core/src/MulticastDiscovery.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
#include "LocalSendCore/MulticastDiscovery.h"
|
||||
#include "LocalSendCore/Constants.h"
|
||||
#include <QJsonDocument>
|
||||
|
||||
namespace LocalSend {
|
||||
|
||||
MulticastDiscovery::MulticastDiscovery(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
MulticastDiscovery::~MulticastDiscovery()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void MulticastDiscovery::setLocalInfo(const InfoDto& info, const QString& fingerprint,
|
||||
quint16 port, ProtocolType protocol)
|
||||
{
|
||||
m_localInfo = info;
|
||||
m_localFingerprint = fingerprint;
|
||||
m_localPort = port;
|
||||
m_localProtocol = protocol;
|
||||
}
|
||||
|
||||
void MulticastDiscovery::start(const QString& multicastGroup, quint16 port)
|
||||
{
|
||||
if (m_socket) {
|
||||
stop();
|
||||
}
|
||||
|
||||
m_multicastGroup = multicastGroup;
|
||||
m_port = port;
|
||||
|
||||
m_socket = new QUdpSocket(this);
|
||||
|
||||
if (!m_socket->bind(QHostAddress::AnyIPv4, port,
|
||||
QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint)) {
|
||||
qWarning() << "Failed to bind UDP socket:" << m_socket->errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
for (const QNetworkInterface& iface : QNetworkInterface::allInterfaces()) {
|
||||
if (iface.flags() & QNetworkInterface::CanMulticast) {
|
||||
m_socket->joinMulticastGroup(QHostAddress(multicastGroup), iface);
|
||||
}
|
||||
}
|
||||
|
||||
connect(m_socket, &QUdpSocket::readyRead, this, &MulticastDiscovery::onReadyRead);
|
||||
|
||||
sendAnnouncement();
|
||||
}
|
||||
|
||||
void MulticastDiscovery::stop()
|
||||
{
|
||||
if (m_socket) {
|
||||
m_socket->close();
|
||||
m_socket->deleteLater();
|
||||
m_socket = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void MulticastDiscovery::sendAnnouncement()
|
||||
{
|
||||
if (!m_socket || m_localFingerprint.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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 = true;
|
||||
dto.announce = true;
|
||||
|
||||
QByteArray data = QJsonDocument(dto.toJson()).toJson(QJsonDocument::Compact);
|
||||
m_socket->writeDatagram(data, QHostAddress(m_multicastGroup), m_port);
|
||||
}
|
||||
|
||||
void MulticastDiscovery::onReadyRead()
|
||||
{
|
||||
while (m_socket && m_socket->hasPendingDatagrams()) {
|
||||
QByteArray datagram;
|
||||
datagram.resize(static_cast<int>(m_socket->pendingDatagramSize()));
|
||||
QHostAddress sender;
|
||||
quint16 senderPort;
|
||||
|
||||
m_socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
|
||||
handleMulticastMessage(sender, datagram);
|
||||
}
|
||||
}
|
||||
|
||||
void MulticastDiscovery::handleMulticastMessage(const QHostAddress& sender, const QByteArray& data)
|
||||
{
|
||||
QJsonParseError error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &error);
|
||||
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
return;
|
||||
}
|
||||
|
||||
MulticastDto dto = MulticastDto::fromJson(doc.object());
|
||||
|
||||
if (dto.fingerprint.isEmpty() || dto.fingerprint == m_localFingerprint) {
|
||||
return;
|
||||
}
|
||||
|
||||
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.discoveryMethod = DiscoveryMethod::Multicast;
|
||||
device.lastSeen = QDateTime::currentDateTime();
|
||||
|
||||
emit deviceDiscovered(device);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
141
src/core/src/SecurityContext.cpp
Normal file
141
src/core/src/SecurityContext.cpp
Normal file
@@ -0,0 +1,141 @@
|
||||
#include "LocalSendCore/SecurityContext.h"
|
||||
#include <QStandardPaths>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QCryptographicHash>
|
||||
#include <QProcess>
|
||||
|
||||
namespace LocalSend {
|
||||
|
||||
SecurityContext::SecurityContext(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
QString SecurityContext::storagePath() const
|
||||
{
|
||||
QString configPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
|
||||
QDir dir(configPath);
|
||||
if (!dir.exists()) {
|
||||
dir.mkpath(QStringLiteral("."));
|
||||
}
|
||||
return configPath;
|
||||
}
|
||||
|
||||
void SecurityContext::initialize()
|
||||
{
|
||||
loadFromStorage();
|
||||
|
||||
if (!isValid()) {
|
||||
generateCertificate();
|
||||
saveToStorage();
|
||||
}
|
||||
}
|
||||
|
||||
void SecurityContext::regenerate()
|
||||
{
|
||||
generateCertificate();
|
||||
saveToStorage();
|
||||
}
|
||||
|
||||
void SecurityContext::loadFromStorage()
|
||||
{
|
||||
QString basePath = storagePath();
|
||||
|
||||
QFile certFile(basePath + QStringLiteral("/cert.pem"));
|
||||
QFile keyFile(basePath + QStringLiteral("/key.pem"));
|
||||
|
||||
if (!certFile.exists() || !keyFile.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!certFile.open(QIODevice::ReadOnly) || !keyFile.open(QIODevice::ReadOnly)) {
|
||||
return;
|
||||
}
|
||||
|
||||
QSslCertificate cert(&certFile, QSsl::Pem);
|
||||
QSslKey key(&keyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
|
||||
|
||||
certFile.close();
|
||||
keyFile.close();
|
||||
|
||||
if (cert.isNull() || key.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_certificate = cert;
|
||||
m_privateKey = key;
|
||||
m_fingerprint = calculateFingerprint(cert);
|
||||
}
|
||||
|
||||
void SecurityContext::saveToStorage()
|
||||
{
|
||||
if (!isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString basePath = storagePath();
|
||||
|
||||
QFile certFile(basePath + QStringLiteral("/cert.pem"));
|
||||
QFile keyFile(basePath + QStringLiteral("/key.pem"));
|
||||
|
||||
if (!certFile.open(QIODevice::WriteOnly) || !keyFile.open(QIODevice::WriteOnly)) {
|
||||
return;
|
||||
}
|
||||
|
||||
certFile.write(m_certificate.toPem());
|
||||
keyFile.write(m_privateKey.toPem());
|
||||
|
||||
certFile.close();
|
||||
keyFile.close();
|
||||
|
||||
keyFile.setPermissions(QFile::ReadOwner | QFile::WriteOwner);
|
||||
}
|
||||
|
||||
void SecurityContext::generateCertificate()
|
||||
{
|
||||
#ifdef USE_OPENSSL
|
||||
QString basePath = storagePath();
|
||||
QString keyPath = basePath + QStringLiteral("/key.pem");
|
||||
QString certPath = basePath + QStringLiteral("/cert.pem");
|
||||
|
||||
QProcess openssl;
|
||||
|
||||
openssl.start(QStringLiteral("openssl"), {
|
||||
QStringLiteral("req"), QStringLiteral("-x509"),
|
||||
QStringLiteral("-newkey"), QStringLiteral("rsa:2048"),
|
||||
QStringLiteral("-keyout"), keyPath,
|
||||
QStringLiteral("-out"), certPath,
|
||||
QStringLiteral("-days"), QStringLiteral("3650"),
|
||||
QStringLiteral("-nodes"),
|
||||
QStringLiteral("-subj"), QStringLiteral("/CN=LocalSend")
|
||||
});
|
||||
|
||||
if (!openssl.waitForFinished(30000)) {
|
||||
qWarning() << "Failed to generate certificate";
|
||||
return;
|
||||
}
|
||||
|
||||
loadFromStorage();
|
||||
#else
|
||||
qWarning() << "Certificate generation requires OpenSSL support";
|
||||
#endif
|
||||
}
|
||||
|
||||
QString SecurityContext::calculateFingerprint(const QSslCertificate& cert) const
|
||||
{
|
||||
QByteArray digest = cert.digest(QCryptographicHash::Sha256);
|
||||
return QString::fromLatin1(digest.toHex());
|
||||
}
|
||||
|
||||
QSslConfiguration SecurityContext::sslConfiguration() const
|
||||
{
|
||||
QSslConfiguration config;
|
||||
config.setLocalCertificate(m_certificate);
|
||||
config.setPrivateKey(m_privateKey);
|
||||
config.setPeerVerifyMode(QSslSocket::VerifyNone);
|
||||
return config;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
263
src/core/src/SessionManager.cpp
Normal file
263
src/core/src/SessionManager.cpp
Normal file
@@ -0,0 +1,263 @@
|
||||
#include "LocalSendCore/SessionManager.h"
|
||||
#include <QUuid>
|
||||
|
||||
namespace LocalSend {
|
||||
|
||||
SessionManager::SessionManager(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
QString SessionManager::createReceiveSession(const Device& sender, const QMap<QString, FileDto>& files)
|
||||
{
|
||||
QString sessionId = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
|
||||
ReceiveSession session;
|
||||
session.sessionId = sessionId;
|
||||
session.sender = sender;
|
||||
session.status = SessionStatus::Waiting;
|
||||
|
||||
for (auto it = files.constBegin(); it != files.constEnd(); ++it) {
|
||||
FileTransfer transfer;
|
||||
transfer.file = it.value();
|
||||
transfer.token = generateToken();
|
||||
transfer.status = FileStatus::Queue;
|
||||
session.files.insert(it.key(), transfer);
|
||||
}
|
||||
|
||||
m_receiveSessions.insert(sessionId, session);
|
||||
emit receiveSessionCreated(sessionId);
|
||||
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
void SessionManager::acceptReceiveSession(const QString& sessionId,
|
||||
const QMap<QString, QString>& destinationPaths)
|
||||
{
|
||||
if (!m_receiveSessions.contains(sessionId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReceiveSession& session = m_receiveSessions[sessionId];
|
||||
session.status = SessionStatus::Sending;
|
||||
|
||||
QMap<QString, QString> tokens;
|
||||
for (auto it = session.files.begin(); it != session.files.end(); ++it) {
|
||||
it->destinationPath = destinationPaths.value(it.key());
|
||||
it->status = FileStatus::Queue;
|
||||
tokens.insert(it.key(), it->token);
|
||||
}
|
||||
|
||||
emit receiveSessionAccepted(sessionId, tokens);
|
||||
}
|
||||
|
||||
void SessionManager::declineReceiveSession(const QString& sessionId)
|
||||
{
|
||||
if (!m_receiveSessions.contains(sessionId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_receiveSessions[sessionId].status = SessionStatus::Declined;
|
||||
emit receiveSessionDeclined(sessionId);
|
||||
m_receiveSessions.remove(sessionId);
|
||||
}
|
||||
|
||||
void SessionManager::updateReceiveProgress(const QString& sessionId, const QString& fileId, qint64 bytes)
|
||||
{
|
||||
if (!m_receiveSessions.contains(sessionId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReceiveSession& session = m_receiveSessions[sessionId];
|
||||
if (!session.files.contains(fileId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
FileTransfer& transfer = session.files[fileId];
|
||||
transfer.bytesTransferred = bytes;
|
||||
transfer.status = FileStatus::Sending;
|
||||
|
||||
double progress = transfer.file.size > 0 ?
|
||||
static_cast<double>(bytes) / transfer.file.size : 0.0;
|
||||
emit receiveProgress(sessionId, fileId, progress);
|
||||
}
|
||||
|
||||
void SessionManager::completeReceiveFile(const QString& sessionId, const QString& fileId)
|
||||
{
|
||||
if (!m_receiveSessions.contains(sessionId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReceiveSession& session = m_receiveSessions[sessionId];
|
||||
if (!session.files.contains(fileId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
session.files[fileId].status = FileStatus::Finished;
|
||||
session.files[fileId].bytesTransferred = session.files[fileId].file.size;
|
||||
|
||||
bool allFinished = true;
|
||||
for (const FileTransfer& transfer : session.files) {
|
||||
if (transfer.status != FileStatus::Finished) {
|
||||
allFinished = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allFinished) {
|
||||
session.status = SessionStatus::Finished;
|
||||
emit receiveSessionCompleted(sessionId);
|
||||
m_receiveSessions.remove(sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
void SessionManager::failReceiveFile(const QString& sessionId, const QString& fileId)
|
||||
{
|
||||
if (!m_receiveSessions.contains(sessionId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_receiveSessions[sessionId].files[fileId].status = FileStatus::Failed;
|
||||
}
|
||||
|
||||
void SessionManager::cancelReceiveSession(const QString& sessionId)
|
||||
{
|
||||
if (!m_receiveSessions.contains(sessionId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_receiveSessions[sessionId].status = SessionStatus::CanceledByReceiver;
|
||||
emit receiveSessionCanceled(sessionId);
|
||||
m_receiveSessions.remove(sessionId);
|
||||
}
|
||||
|
||||
QString SessionManager::createSendSession(const Device& target, const QMap<QString, FileDto>& files,
|
||||
const QMap<QString, QString>& localPaths)
|
||||
{
|
||||
QString sessionId = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
|
||||
SendSession session;
|
||||
session.sessionId = sessionId;
|
||||
session.target = target;
|
||||
session.status = SessionStatus::Waiting;
|
||||
|
||||
for (auto it = files.constBegin(); it != files.constEnd(); ++it) {
|
||||
FileTransfer transfer;
|
||||
transfer.file = it.value();
|
||||
transfer.localPath = localPaths.value(it.key());
|
||||
transfer.status = FileStatus::Queue;
|
||||
session.files.insert(it.key(), transfer);
|
||||
}
|
||||
|
||||
m_sendSessions.insert(sessionId, session);
|
||||
emit sendSessionCreated(sessionId);
|
||||
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
void SessionManager::startSendSession(const QString& sessionId)
|
||||
{
|
||||
if (!m_sendSessions.contains(sessionId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_sendSessions[sessionId].status = SessionStatus::Sending;
|
||||
emit sendSessionStarted(sessionId);
|
||||
}
|
||||
|
||||
void SessionManager::updateSendProgress(const QString& sessionId, const QString& fileId, qint64 bytes)
|
||||
{
|
||||
if (!m_sendSessions.contains(sessionId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
SendSession& session = m_sendSessions[sessionId];
|
||||
if (!session.files.contains(fileId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
FileTransfer& transfer = session.files[fileId];
|
||||
transfer.bytesTransferred = bytes;
|
||||
transfer.status = FileStatus::Sending;
|
||||
session.currentFileId = fileId;
|
||||
|
||||
double progress = transfer.file.size > 0 ?
|
||||
static_cast<double>(bytes) / transfer.file.size : 0.0;
|
||||
emit sendProgress(sessionId, fileId, progress);
|
||||
}
|
||||
|
||||
void SessionManager::completeSendFile(const QString& sessionId, const QString& fileId)
|
||||
{
|
||||
if (!m_sendSessions.contains(sessionId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
SendSession& session = m_sendSessions[sessionId];
|
||||
if (!session.files.contains(fileId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
session.files[fileId].status = FileStatus::Finished;
|
||||
|
||||
bool allFinished = true;
|
||||
for (const FileTransfer& transfer : session.files) {
|
||||
if (transfer.status != FileStatus::Finished) {
|
||||
allFinished = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allFinished) {
|
||||
session.status = SessionStatus::Finished;
|
||||
emit sendSessionCompleted(sessionId);
|
||||
m_sendSessions.remove(sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
void SessionManager::failSendFile(const QString& sessionId, const QString& fileId)
|
||||
{
|
||||
if (!m_sendSessions.contains(sessionId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_sendSessions[sessionId].files[fileId].status = FileStatus::Failed;
|
||||
}
|
||||
|
||||
void SessionManager::cancelSendSession(const QString& sessionId)
|
||||
{
|
||||
if (!m_sendSessions.contains(sessionId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_sendSessions[sessionId].status = SessionStatus::CanceledBySender;
|
||||
emit sendSessionCanceled(sessionId);
|
||||
m_sendSessions.remove(sessionId);
|
||||
}
|
||||
|
||||
ReceiveSession SessionManager::receiveSession(const QString& sessionId) const
|
||||
{
|
||||
return m_receiveSessions.value(sessionId);
|
||||
}
|
||||
|
||||
SendSession SessionManager::sendSession(const QString& sessionId) const
|
||||
{
|
||||
return m_sendSessions.value(sessionId);
|
||||
}
|
||||
|
||||
bool SessionManager::hasReceiveSession(const QString& sessionId) const
|
||||
{
|
||||
return m_receiveSessions.contains(sessionId);
|
||||
}
|
||||
|
||||
bool SessionManager::hasSendSession(const QString& sessionId) const
|
||||
{
|
||||
return m_sendSessions.contains(sessionId);
|
||||
}
|
||||
|
||||
QString SessionManager::generateToken()
|
||||
{
|
||||
return QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
114
src/core/src/Settings.cpp
Normal file
114
src/core/src/Settings.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
#include "LocalSendCore/Settings.h"
|
||||
#include "LocalSendCore/Constants.h"
|
||||
#include <QStandardPaths>
|
||||
|
||||
namespace LocalSend {
|
||||
|
||||
Settings::Settings(QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_settings(QSettings::IniFormat, QSettings::UserScope,
|
||||
QStringLiteral("LocalSendQt"), QStringLiteral("settings"))
|
||||
{
|
||||
}
|
||||
|
||||
QString Settings::alias() const
|
||||
{
|
||||
return m_settings.value(QStringLiteral("alias"),
|
||||
QSysInfo::machineHostName()).toString();
|
||||
}
|
||||
|
||||
void Settings::setAlias(const QString& alias)
|
||||
{
|
||||
if (this->alias() != alias) {
|
||||
m_settings.setValue(QStringLiteral("alias"), alias);
|
||||
m_settings.sync();
|
||||
}
|
||||
}
|
||||
|
||||
quint16 Settings::port() const
|
||||
{
|
||||
return m_settings.value(QStringLiteral("port"), DEFAULT_PORT).value<quint16>();
|
||||
}
|
||||
|
||||
void Settings::setPort(quint16 port)
|
||||
{
|
||||
if (this->port() != port) {
|
||||
m_settings.setValue(QStringLiteral("port"), port);
|
||||
m_settings.sync();
|
||||
}
|
||||
}
|
||||
|
||||
bool Settings::https() const
|
||||
{
|
||||
return m_settings.value(QStringLiteral("https"), false).toBool();
|
||||
}
|
||||
|
||||
void Settings::setHttps(bool enabled)
|
||||
{
|
||||
if (https() != enabled) {
|
||||
m_settings.setValue(QStringLiteral("https"), enabled);
|
||||
m_settings.sync();
|
||||
}
|
||||
}
|
||||
|
||||
QString Settings::downloadDir() const
|
||||
{
|
||||
return m_settings.value(QStringLiteral("downloadDir"),
|
||||
QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)).toString();
|
||||
}
|
||||
|
||||
void Settings::setDownloadDir(const QString& path)
|
||||
{
|
||||
if (downloadDir() != path) {
|
||||
m_settings.setValue(QStringLiteral("downloadDir"), path);
|
||||
m_settings.sync();
|
||||
}
|
||||
}
|
||||
|
||||
bool Settings::quickSave() const
|
||||
{
|
||||
return m_settings.value(QStringLiteral("quickSave"), false).toBool();
|
||||
}
|
||||
|
||||
void Settings::setQuickSave(bool enabled)
|
||||
{
|
||||
if (quickSave() != enabled) {
|
||||
m_settings.setValue(QStringLiteral("quickSave"), enabled);
|
||||
m_settings.sync();
|
||||
}
|
||||
}
|
||||
|
||||
QString Settings::deviceModel() const
|
||||
{
|
||||
return m_settings.value(QStringLiteral("deviceModel"),
|
||||
QSysInfo::prettyProductName()).toString();
|
||||
}
|
||||
|
||||
void Settings::setDeviceModel(const QString& model)
|
||||
{
|
||||
m_settings.setValue(QStringLiteral("deviceModel"), model);
|
||||
}
|
||||
|
||||
DeviceType Settings::deviceType() const
|
||||
{
|
||||
return deviceTypeFromString(m_settings.value(QStringLiteral("deviceType"),
|
||||
QStringLiteral("desktop")).toString());
|
||||
}
|
||||
|
||||
void Settings::setDeviceType(DeviceType type)
|
||||
{
|
||||
m_settings.setValue(QStringLiteral("deviceType"), deviceTypeToString(type));
|
||||
}
|
||||
|
||||
QString Settings::version() const
|
||||
{
|
||||
return PROTOCOL_VERSION;
|
||||
}
|
||||
|
||||
void Settings::sync()
|
||||
{
|
||||
m_settings.sync();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
37
src/core/src/Types.cpp
Normal file
37
src/core/src/Types.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#include "LocalSendCore/Types.h"
|
||||
|
||||
namespace LocalSend {
|
||||
|
||||
QString deviceTypeToString(DeviceType type)
|
||||
{
|
||||
switch (type) {
|
||||
case DeviceType::Mobile: return QStringLiteral("mobile");
|
||||
case DeviceType::Desktop: return QStringLiteral("desktop");
|
||||
case DeviceType::Web: return QStringLiteral("web");
|
||||
case DeviceType::Headless: return QStringLiteral("headless");
|
||||
case DeviceType::Server: return QStringLiteral("server");
|
||||
}
|
||||
return QStringLiteral("desktop");
|
||||
}
|
||||
|
||||
DeviceType deviceTypeFromString(const QString& str)
|
||||
{
|
||||
if (str == QStringLiteral("mobile")) return DeviceType::Mobile;
|
||||
if (str == QStringLiteral("desktop")) return DeviceType::Desktop;
|
||||
if (str == QStringLiteral("web")) return DeviceType::Web;
|
||||
if (str == QStringLiteral("headless")) return DeviceType::Headless;
|
||||
if (str == QStringLiteral("server")) return DeviceType::Server;
|
||||
return DeviceType::Desktop;
|
||||
}
|
||||
|
||||
QString protocolTypeToString(ProtocolType type)
|
||||
{
|
||||
return type == ProtocolType::Https ? QStringLiteral("https") : QStringLiteral("http");
|
||||
}
|
||||
|
||||
ProtocolType protocolTypeFromString(const QString& str)
|
||||
{
|
||||
return str == QStringLiteral("https") ? ProtocolType::Https : ProtocolType::Http;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user