able to send file
This commit is contained in:
@@ -6,6 +6,7 @@
|
|||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QMimeDatabase>
|
||||||
|
|
||||||
AppController::AppController(QObject* parent)
|
AppController::AppController(QObject* parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
@@ -14,6 +15,7 @@ AppController::AppController(QObject* parent)
|
|||||||
, m_discovery(new LocalSend::DiscoveryManager(this))
|
, m_discovery(new LocalSend::DiscoveryManager(this))
|
||||||
, m_server(new LocalSend::HttpServer(this))
|
, m_server(new LocalSend::HttpServer(this))
|
||||||
, m_sessions(new LocalSend::SessionManager(this))
|
, m_sessions(new LocalSend::SessionManager(this))
|
||||||
|
, m_httpClient(new LocalSend::HttpClient(this))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +56,17 @@ void AppController::initialize()
|
|||||||
connect(m_sessions, &LocalSend::SessionManager::receiveSessionCompleted,
|
connect(m_sessions, &LocalSend::SessionManager::receiveSessionCompleted,
|
||||||
this, &AppController::onReceiveCompleted);
|
this, &AppController::onReceiveCompleted);
|
||||||
|
|
||||||
|
connect(m_httpClient, &LocalSend::HttpClient::prepareUploadResponse,
|
||||||
|
this, &AppController::onPrepareUploadResponse);
|
||||||
|
connect(m_httpClient, &LocalSend::HttpClient::prepareUploadError,
|
||||||
|
this, &AppController::onPrepareUploadError);
|
||||||
|
connect(m_httpClient, &LocalSend::HttpClient::uploadProgress,
|
||||||
|
this, &AppController::onUploadProgress);
|
||||||
|
connect(m_httpClient, &LocalSend::HttpClient::uploadCompleted,
|
||||||
|
this, &AppController::onUploadCompleted);
|
||||||
|
connect(m_httpClient, &LocalSend::HttpClient::uploadError,
|
||||||
|
this, &AppController::onUploadError);
|
||||||
|
|
||||||
startDiscovery();
|
startDiscovery();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,3 +358,227 @@ void AppController::onReceiveCompleted(const QString& sessionId)
|
|||||||
{
|
{
|
||||||
emit receiveCompleted(sessionId);
|
emit receiveCompleted(sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AppController::sending() const
|
||||||
|
{
|
||||||
|
return !m_currentSendSessionId.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
double AppController::sendProgress() const
|
||||||
|
{
|
||||||
|
return m_sendProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppController::sendFiles(const QString& deviceFingerprint, const QStringList& filePaths)
|
||||||
|
{
|
||||||
|
if (!m_devices.contains(deviceFingerprint)) {
|
||||||
|
emit sendError(QStringLiteral("Device not found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filePaths.isEmpty()) {
|
||||||
|
emit sendError(QStringLiteral("No files selected"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sending()) {
|
||||||
|
emit sendError(QStringLiteral("Already sending files"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_currentSendDeviceFingerprint = deviceFingerprint;
|
||||||
|
m_pendingFiles = filePaths;
|
||||||
|
m_currentFileIndex = 0;
|
||||||
|
m_sendProgress = 0.0;
|
||||||
|
emit sendingChanged();
|
||||||
|
emit sendProgressChanged();
|
||||||
|
|
||||||
|
qDebug() << "[AppController] sendFiles: device=" << deviceFingerprint
|
||||||
|
<< "files=" << filePaths.size();
|
||||||
|
|
||||||
|
LocalSend::Device target = m_devices[deviceFingerprint];
|
||||||
|
|
||||||
|
QMap<QString, LocalSend::FileDto> files;
|
||||||
|
QMimeDatabase mimeDb;
|
||||||
|
|
||||||
|
for (int i = 0; i < filePaths.size(); ++i) {
|
||||||
|
QString filePath = filePaths[i];
|
||||||
|
QFileInfo info(filePath);
|
||||||
|
|
||||||
|
if (!info.exists()) {
|
||||||
|
emit sendError(QStringLiteral("File not found: ") + filePath);
|
||||||
|
m_currentSendSessionId.clear();
|
||||||
|
emit sendingChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalSend::FileDto fileDto;
|
||||||
|
fileDto.id = QString::number(i);
|
||||||
|
fileDto.fileName = info.fileName();
|
||||||
|
fileDto.size = info.size();
|
||||||
|
fileDto.fileType = mimeDb.mimeTypeForFile(filePath).name();
|
||||||
|
files.insert(fileDto.id, fileDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_currentSendSessionId = m_sessions->createSendSession(target, files,
|
||||||
|
[filePaths]() {
|
||||||
|
QMap<QString, QString> paths;
|
||||||
|
for (int i = 0; i < filePaths.size(); ++i) {
|
||||||
|
paths.insert(QString::number(i), filePaths[i]);
|
||||||
|
}
|
||||||
|
return paths;
|
||||||
|
}()
|
||||||
|
);
|
||||||
|
|
||||||
|
LocalSend::PrepareUploadRequestDto request;
|
||||||
|
request.info = buildRegisterDto();
|
||||||
|
request.files = files;
|
||||||
|
|
||||||
|
qDebug() << "[AppController] Sending prepare-upload request to" << target.ip;
|
||||||
|
m_httpClient->prepareUpload(target, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppController::cancelSend()
|
||||||
|
{
|
||||||
|
if (m_currentSendSessionId.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalSend::Device target = m_devices.value(m_currentSendDeviceFingerprint);
|
||||||
|
m_httpClient->cancel(target, m_currentSendSessionId);
|
||||||
|
|
||||||
|
m_sessions->cancelSendSession(m_currentSendSessionId);
|
||||||
|
m_currentSendSessionId.clear();
|
||||||
|
m_pendingFiles.clear();
|
||||||
|
m_sendProgress = 0.0;
|
||||||
|
emit sendingChanged();
|
||||||
|
emit sendProgressChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppController::onPrepareUploadResponse(const LocalSend::PrepareUploadResponseDto& response)
|
||||||
|
{
|
||||||
|
qDebug() << "[AppController] onPrepareUploadResponse: sessionId=" << response.sessionId
|
||||||
|
<< "files=" << response.files.size();
|
||||||
|
|
||||||
|
LocalSend::SendSession session = m_sessions->sendSession(m_currentSendSessionId);
|
||||||
|
if (session.sessionId.isEmpty()) {
|
||||||
|
emit sendError(QStringLiteral("Session not found"));
|
||||||
|
m_currentSendSessionId.clear();
|
||||||
|
emit sendingChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.files.isEmpty()) {
|
||||||
|
emit sendError(QStringLiteral("Receiver declined the transfer"));
|
||||||
|
m_currentSendSessionId.clear();
|
||||||
|
emit sendingChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_sessions->setSendSessionTokens(m_currentSendSessionId, response.sessionId, response.files);
|
||||||
|
|
||||||
|
m_currentSendSessionId = response.sessionId;
|
||||||
|
m_sessions->startSendSession(m_currentSendSessionId);
|
||||||
|
|
||||||
|
m_currentFileIndex = 0;
|
||||||
|
sendNextFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppController::onPrepareUploadError(const QString& error)
|
||||||
|
{
|
||||||
|
qWarning() << "[AppController] onPrepareUploadError:" << error;
|
||||||
|
emit sendError(error);
|
||||||
|
m_currentSendSessionId.clear();
|
||||||
|
emit sendingChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppController::onUploadProgress(qint64 sent, qint64 total)
|
||||||
|
{
|
||||||
|
if (total > 0) {
|
||||||
|
double fileProgress = static_cast<double>(sent) / total;
|
||||||
|
int totalFiles = m_pendingFiles.size();
|
||||||
|
double overallProgress = (m_currentFileIndex + fileProgress) / totalFiles;
|
||||||
|
m_sendProgress = overallProgress * 100.0;
|
||||||
|
emit sendProgressChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppController::onUploadCompleted()
|
||||||
|
{
|
||||||
|
qDebug() << "[AppController] onUploadCompleted, file index:" << m_currentFileIndex;
|
||||||
|
|
||||||
|
m_sessions->completeSendFile(m_currentSendSessionId, m_currentSendFileId);
|
||||||
|
|
||||||
|
m_currentFileIndex++;
|
||||||
|
|
||||||
|
if (m_currentFileIndex < m_pendingFiles.size()) {
|
||||||
|
sendNextFile();
|
||||||
|
} else {
|
||||||
|
qDebug() << "[AppController] All files sent successfully";
|
||||||
|
m_sendProgress = 100.0;
|
||||||
|
emit sendProgressChanged();
|
||||||
|
emit sendCompleted(m_currentSendSessionId);
|
||||||
|
m_currentSendSessionId.clear();
|
||||||
|
emit sendingChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppController::onUploadError(const QString& error)
|
||||||
|
{
|
||||||
|
qWarning() << "[AppController] onUploadError:" << error;
|
||||||
|
emit sendError(error);
|
||||||
|
m_sessions->failSendFile(m_currentSendSessionId, m_currentSendFileId);
|
||||||
|
m_currentSendSessionId.clear();
|
||||||
|
emit sendingChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppController::sendNextFile()
|
||||||
|
{
|
||||||
|
if (m_currentFileIndex >= m_pendingFiles.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalSend::SendSession session = m_sessions->sendSession(m_currentSendSessionId);
|
||||||
|
if (session.sessionId.isEmpty()) {
|
||||||
|
qWarning() << "[AppController] Session not found:" << m_currentSendSessionId;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString fileId = QString::number(m_currentFileIndex);
|
||||||
|
if (!session.files.contains(fileId)) {
|
||||||
|
qWarning() << "[AppController] File not found in session:" << fileId;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_currentSendFileId = fileId;
|
||||||
|
QString filePath = m_pendingFiles[m_currentFileIndex];
|
||||||
|
QString token = session.files[fileId].token;
|
||||||
|
|
||||||
|
LocalSend::Device target = m_devices.value(m_currentSendDeviceFingerprint);
|
||||||
|
|
||||||
|
qDebug() << "[AppController] Uploading file" << fileId << ":" << filePath
|
||||||
|
<< "token:" << token << "to" << target.ip;
|
||||||
|
|
||||||
|
if (token.isEmpty()) {
|
||||||
|
emit sendError(QStringLiteral("No token for file: ") + fileId);
|
||||||
|
m_currentSendSessionId.clear();
|
||||||
|
emit sendingChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_httpClient->uploadFile(target, m_currentSendSessionId, fileId, token, filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalSend::RegisterDto AppController::buildRegisterDto() const
|
||||||
|
{
|
||||||
|
LocalSend::RegisterDto dto;
|
||||||
|
dto.alias = m_settings->alias();
|
||||||
|
dto.version = m_settings->version();
|
||||||
|
dto.deviceModel = m_settings->deviceModel();
|
||||||
|
dto.deviceType = m_settings->deviceType();
|
||||||
|
dto.fingerprint = m_security->fingerprint();
|
||||||
|
dto.port = m_settings->port();
|
||||||
|
dto.protocol = m_settings->https() ? LocalSend::ProtocolType::Https : LocalSend::ProtocolType::Http;
|
||||||
|
dto.download = false;
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ class AppController : public QObject
|
|||||||
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(QString downloadPath READ downloadPath WRITE setDownloadPath NOTIFY downloadPathChanged)
|
||||||
Q_PROPERTY(bool quickSave READ quickSave WRITE setQuickSave NOTIFY quickSaveChanged)
|
Q_PROPERTY(bool quickSave READ quickSave WRITE setQuickSave NOTIFY quickSaveChanged)
|
||||||
|
Q_PROPERTY(bool sending READ sending NOTIFY sendingChanged)
|
||||||
|
Q_PROPERTY(double sendProgress READ sendProgress NOTIFY sendProgressChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit AppController(QObject* parent = nullptr);
|
explicit AppController(QObject* parent = nullptr);
|
||||||
@@ -41,12 +43,18 @@ public:
|
|||||||
QVariantList devices() const;
|
QVariantList devices() const;
|
||||||
bool serverRunning() const;
|
bool serverRunning() const;
|
||||||
|
|
||||||
|
bool sending() const;
|
||||||
|
double sendProgress() const;
|
||||||
|
|
||||||
Q_INVOKABLE void startDiscovery();
|
Q_INVOKABLE void startDiscovery();
|
||||||
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 acceptReceive(const QString& sessionId);
|
||||||
Q_INVOKABLE void declineReceive(const QString& sessionId);
|
Q_INVOKABLE void declineReceive(const QString& sessionId);
|
||||||
|
|
||||||
|
Q_INVOKABLE void sendFiles(const QString& deviceFingerprint, const QStringList& filePaths);
|
||||||
|
Q_INVOKABLE void cancelSend();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void aliasChanged();
|
void aliasChanged();
|
||||||
@@ -55,13 +63,15 @@ signals:
|
|||||||
void quickSaveChanged();
|
void quickSaveChanged();
|
||||||
void devicesChanged();
|
void devicesChanged();
|
||||||
void serverRunningChanged();
|
void serverRunningChanged();
|
||||||
|
void sendingChanged();
|
||||||
|
void sendProgressChanged();
|
||||||
void receiveRequest(const QString& sessionId, const QString& senderAlias,
|
void receiveRequest(const QString& sessionId, const QString& senderAlias,
|
||||||
const QString& senderIp, const QVariantList& files);
|
const QString& senderIp, const QVariantList& files);
|
||||||
void receiveProgress(const QString& sessionId, const QString& fileId, double progress);
|
void receiveProgress(const QString& sessionId, const QString& fileId, double progress);
|
||||||
void receiveCompleted(const QString& sessionId);
|
void receiveCompleted(const QString& sessionId);
|
||||||
void receiveError(const QString& sessionId, const QString& error);
|
void receiveError(const QString& sessionId, const QString& error);
|
||||||
void sendProgress(const QString& sessionId, double progress);
|
|
||||||
void sendCompleted(const QString& sessionId);
|
void sendCompleted(const QString& sessionId);
|
||||||
|
void sendError(const QString& error);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onDeviceDiscovered(const LocalSend::Device& device);
|
void onDeviceDiscovered(const LocalSend::Device& device);
|
||||||
@@ -76,6 +86,12 @@ private slots:
|
|||||||
void onSessionDeclined(const QString& sessionId);
|
void onSessionDeclined(const QString& sessionId);
|
||||||
void onReceiveProgress(const QString& sessionId, const QString& fileId, double progress);
|
void onReceiveProgress(const QString& sessionId, const QString& fileId, double progress);
|
||||||
void onReceiveCompleted(const QString& sessionId);
|
void onReceiveCompleted(const QString& sessionId);
|
||||||
|
|
||||||
|
void onPrepareUploadResponse(const LocalSend::PrepareUploadResponseDto& response);
|
||||||
|
void onPrepareUploadError(const QString& error);
|
||||||
|
void onUploadProgress(qint64 sent, qint64 total);
|
||||||
|
void onUploadCompleted();
|
||||||
|
void onUploadError(const QString& error);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LocalSend::Settings* m_settings = nullptr;
|
LocalSend::Settings* m_settings = nullptr;
|
||||||
@@ -83,9 +99,19 @@ private:
|
|||||||
LocalSend::DiscoveryManager* m_discovery = nullptr;
|
LocalSend::DiscoveryManager* m_discovery = nullptr;
|
||||||
LocalSend::HttpServer* m_server = nullptr;
|
LocalSend::HttpServer* m_server = nullptr;
|
||||||
LocalSend::SessionManager* m_sessions = nullptr;
|
LocalSend::SessionManager* m_sessions = nullptr;
|
||||||
|
LocalSend::HttpClient* m_httpClient = nullptr;
|
||||||
|
|
||||||
QMap<QString, LocalSend::Device> m_devices;
|
QMap<QString, LocalSend::Device> m_devices;
|
||||||
|
|
||||||
|
QString m_currentSendSessionId;
|
||||||
|
QString m_currentSendFileId;
|
||||||
|
QString m_currentSendDeviceFingerprint;
|
||||||
|
QStringList m_pendingFiles;
|
||||||
|
int m_currentFileIndex = 0;
|
||||||
|
double m_sendProgress = 0.0;
|
||||||
|
|
||||||
LocalSend::InfoDto buildInfoDto() const;
|
LocalSend::InfoDto buildInfoDto() const;
|
||||||
QString generateUniqueFilePath(const QString& baseDir, const QString& fileName) const;
|
QString generateUniqueFilePath(const QString& baseDir, const QString& fileName) const;
|
||||||
|
void sendNextFile();
|
||||||
|
LocalSend::RegisterDto buildRegisterDto() const;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ ApplicationWindow {
|
|||||||
property string currentSenderAlias: ""
|
property string currentSenderAlias: ""
|
||||||
property string currentSenderIp: ""
|
property string currentSenderIp: ""
|
||||||
property var receiveProgress: ({})
|
property var receiveProgress: ({})
|
||||||
|
property string selectedDeviceFingerprint: ""
|
||||||
|
|
||||||
StackView {
|
StackView {
|
||||||
id: stackView
|
id: stackView
|
||||||
@@ -25,6 +26,21 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FileDialog {
|
||||||
|
id: fileDialog
|
||||||
|
title: qsTr("Select Files to Send")
|
||||||
|
fileMode: FileDialog.OpenFiles
|
||||||
|
onAccepted: {
|
||||||
|
var paths = []
|
||||||
|
for (var i = 0; i < selectedFiles.length; i++) {
|
||||||
|
paths.push(selectedFiles[i].toString().replace("file://", ""))
|
||||||
|
}
|
||||||
|
if (paths.length > 0 && selectedDeviceFingerprint !== "") {
|
||||||
|
appController.sendFiles(selectedDeviceFingerprint, paths)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Dialog {
|
Dialog {
|
||||||
id: receiveDialog
|
id: receiveDialog
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
@@ -73,7 +89,7 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
text: formatSize(modelData.size)
|
text: formatSize(modelData.size)
|
||||||
color: "gray"
|
color: palette.mid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,7 +103,7 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Dialog {
|
Dialog {
|
||||||
id: progressDialog
|
id: receiveProgressDialog
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
modal: true
|
modal: true
|
||||||
closePolicy: Popup.NoAutoClose
|
closePolicy: Popup.NoAutoClose
|
||||||
@@ -97,12 +113,10 @@ ApplicationWindow {
|
|||||||
spacing: 12
|
spacing: 12
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: progressLabel
|
|
||||||
text: qsTr("Receiving from %1...").arg(currentSenderAlias)
|
text: qsTr("Receiving from %1...").arg(currentSenderAlias)
|
||||||
}
|
}
|
||||||
|
|
||||||
ProgressBar {
|
ProgressBar {
|
||||||
id: totalProgressBar
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
from: 0
|
from: 0
|
||||||
to: 100
|
to: 100
|
||||||
@@ -110,14 +124,53 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
text: qsTr("%1% complete").arg(Math.round(totalProgressBar.value))
|
text: qsTr("%1% complete").arg(Math.round(calculateTotalProgress()))
|
||||||
color: "gray"
|
color: palette.mid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
property var progressData: ({})
|
property var progressData: ({})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Dialog {
|
||||||
|
id: sendProgressDialog
|
||||||
|
anchors.centerIn: parent
|
||||||
|
modal: true
|
||||||
|
closePolicy: Popup.NoAutoClose
|
||||||
|
title: qsTr("Sending Files")
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: 12
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("Sending files...")
|
||||||
|
}
|
||||||
|
|
||||||
|
ProgressBar {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
from: 0
|
||||||
|
to: 100
|
||||||
|
value: appController.sendProgress
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("%1% complete").arg(Math.round(appController.sendProgress))
|
||||||
|
color: palette.mid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
footer: DialogButtonBox {
|
||||||
|
Button {
|
||||||
|
text: qsTr("Cancel")
|
||||||
|
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
|
||||||
|
onClicked: {
|
||||||
|
appController.cancelSend()
|
||||||
|
sendProgressDialog.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: appController
|
target: appController
|
||||||
|
|
||||||
@@ -129,6 +182,7 @@ ApplicationWindow {
|
|||||||
|
|
||||||
if (appController.quickSave) {
|
if (appController.quickSave) {
|
||||||
appController.acceptReceive(sessionId)
|
appController.acceptReceive(sessionId)
|
||||||
|
receiveProgressDialog.open()
|
||||||
} else {
|
} else {
|
||||||
receiveDialog.open()
|
receiveDialog.open()
|
||||||
}
|
}
|
||||||
@@ -138,13 +192,17 @@ ApplicationWindow {
|
|||||||
if (sessionId === currentSessionId) {
|
if (sessionId === currentSessionId) {
|
||||||
receiveProgress[fileId] = progress
|
receiveProgress[fileId] = progress
|
||||||
receiveProgress = Object.assign({}, receiveProgress)
|
receiveProgress = Object.assign({}, receiveProgress)
|
||||||
progressDialog.progressData = receiveProgress
|
receiveProgressDialog.progressData = receiveProgress
|
||||||
|
|
||||||
|
if (!receiveProgressDialog.visible) {
|
||||||
|
receiveProgressDialog.open()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onReceiveCompleted(sessionId) {
|
function onReceiveCompleted(sessionId) {
|
||||||
if (sessionId === currentSessionId) {
|
if (sessionId === currentSessionId) {
|
||||||
progressDialog.close()
|
receiveProgressDialog.close()
|
||||||
receiveProgress = {}
|
receiveProgress = {}
|
||||||
currentSessionId = ""
|
currentSessionId = ""
|
||||||
}
|
}
|
||||||
@@ -152,11 +210,29 @@ ApplicationWindow {
|
|||||||
|
|
||||||
function onReceiveError(sessionId, error) {
|
function onReceiveError(sessionId, error) {
|
||||||
if (sessionId === currentSessionId) {
|
if (sessionId === currentSessionId) {
|
||||||
progressDialog.close()
|
receiveProgressDialog.close()
|
||||||
errorDialog.text = error
|
errorDialog.text = error
|
||||||
errorDialog.open()
|
errorDialog.open()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onSendProgress(progress) {
|
||||||
|
if (!sendProgressDialog.visible) {
|
||||||
|
sendProgressDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSendCompleted(sessionId) {
|
||||||
|
sendProgressDialog.close()
|
||||||
|
successDialog.text = qsTr("Files sent successfully!")
|
||||||
|
successDialog.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSendError(error) {
|
||||||
|
sendProgressDialog.close()
|
||||||
|
errorDialog.text = error
|
||||||
|
errorDialog.open()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Dialog {
|
Dialog {
|
||||||
@@ -172,6 +248,19 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Dialog {
|
||||||
|
id: successDialog
|
||||||
|
anchors.centerIn: parent
|
||||||
|
modal: true
|
||||||
|
standardButtons: Dialog.Ok
|
||||||
|
title: qsTr("Success")
|
||||||
|
property alias text: successLabel.text
|
||||||
|
|
||||||
|
Label {
|
||||||
|
id: successLabel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function formatSize(bytes) {
|
function formatSize(bytes) {
|
||||||
if (bytes < 1024) return bytes + " B"
|
if (bytes < 1024) return bytes + " B"
|
||||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB"
|
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB"
|
||||||
@@ -242,8 +331,10 @@ ApplicationWindow {
|
|||||||
padding: 12
|
padding: 12
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: Qt.lighter("gray", 1.8)
|
color: "transparent"
|
||||||
radius: 8
|
radius: 8
|
||||||
|
border.color: palette.mid
|
||||||
|
border.width: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
@@ -257,19 +348,28 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
text: "%1:%2".arg(modelData.ip).arg(modelData.port)
|
text: "%1:%2".arg(modelData.ip).arg(modelData.port)
|
||||||
color: "gray"
|
color: palette.mid
|
||||||
font.pixelSize: 12
|
font.pixelSize: 12
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
text: qsTr("Send")
|
text: qsTr("Send")
|
||||||
|
enabled: !appController.sending
|
||||||
onClicked: {
|
onClicked: {
|
||||||
// TODO: send to this device
|
selectedDeviceFingerprint = modelData.fingerprint
|
||||||
|
fileDialog.open()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: qsTr("No devices found")
|
||||||
|
color: palette.mid
|
||||||
|
visible: deviceListView.count === 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ public:
|
|||||||
|
|
||||||
QString createSendSession(const Device& target, const QMap<QString, FileDto>& files,
|
QString createSendSession(const Device& target, const QMap<QString, FileDto>& files,
|
||||||
const QMap<QString, QString>& localPaths);
|
const QMap<QString, QString>& localPaths);
|
||||||
|
void setSendSessionTokens(const QString& sessionId, const QString& responseSessionId,
|
||||||
|
const QMap<QString, QString>& tokens);
|
||||||
void startSendSession(const QString& sessionId);
|
void startSendSession(const QString& sessionId);
|
||||||
void updateSendProgress(const QString& sessionId, const QString& fileId, qint64 bytes);
|
void updateSendProgress(const QString& sessionId, const QString& fileId, qint64 bytes);
|
||||||
void completeSendFile(const QString& sessionId, const QString& fileId);
|
void completeSendFile(const QString& sessionId, const QString& fileId);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QUrlQuery>
|
#include <QUrlQuery>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
namespace LocalSend {
|
namespace LocalSend {
|
||||||
|
|
||||||
@@ -10,6 +11,10 @@ HttpClient::HttpClient(QObject* parent)
|
|||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_manager(new QNetworkAccessManager(this))
|
, m_manager(new QNetworkAccessManager(this))
|
||||||
{
|
{
|
||||||
|
connect(m_manager, &QNetworkAccessManager::sslErrors,
|
||||||
|
this, [](QNetworkReply* reply, const QList<QSslError>&) {
|
||||||
|
reply->ignoreSslErrors();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpClient::~HttpClient()
|
HttpClient::~HttpClient()
|
||||||
@@ -98,20 +103,29 @@ void HttpClient::prepareUpload(const Device& device, const PrepareUploadRequestD
|
|||||||
{
|
{
|
||||||
QUrl url = buildUrl(device, ApiRoute::PREPARE_UPLOAD);
|
QUrl url = buildUrl(device, ApiRoute::PREPARE_UPLOAD);
|
||||||
QByteArray data = QJsonDocument(dto.toJson()).toJson(QJsonDocument::Compact);
|
QByteArray data = QJsonDocument(dto.toJson()).toJson(QJsonDocument::Compact);
|
||||||
|
|
||||||
|
qDebug() << "[HttpClient] prepareUpload to" << url.toString();
|
||||||
|
qDebug() << "[HttpClient] request:" << data;
|
||||||
|
|
||||||
QNetworkReply* reply = sendPost(url, data);
|
QNetworkReply* reply = sendPost(url, data);
|
||||||
|
|
||||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
|
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
qWarning() << "[HttpClient] prepareUpload error:" << reply->errorString();
|
||||||
emit prepareUploadError(reply->errorString());
|
emit prepareUploadError(reply->errorString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QByteArray responseData = reply->readAll();
|
||||||
|
qDebug() << "[HttpClient] prepareUpload response:" << responseData;
|
||||||
|
|
||||||
QJsonParseError error;
|
QJsonParseError error;
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(reply->readAll(), &error);
|
QJsonDocument doc = QJsonDocument::fromJson(responseData, &error);
|
||||||
|
|
||||||
if (error.error != QJsonParseError::NoError) {
|
if (error.error != QJsonParseError::NoError) {
|
||||||
|
qWarning() << "[HttpClient] Invalid JSON response";
|
||||||
emit prepareUploadError(QStringLiteral("Invalid JSON response"));
|
emit prepareUploadError(QStringLiteral("Invalid JSON response"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -131,14 +145,18 @@ void HttpClient::uploadFile(const Device& device, const QString& sessionId,
|
|||||||
query.addQueryItem(QStringLiteral("token"), token);
|
query.addQueryItem(QStringLiteral("token"), token);
|
||||||
url.setQuery(query);
|
url.setQuery(query);
|
||||||
|
|
||||||
|
qDebug() << "[HttpClient] uploadFile to" << url.toString();
|
||||||
|
|
||||||
QFile* file = new QFile(filePath);
|
QFile* file = new QFile(filePath);
|
||||||
if (!file->open(QIODevice::ReadOnly)) {
|
if (!file->open(QIODevice::ReadOnly)) {
|
||||||
|
qWarning() << "[HttpClient] Cannot open file:" << filePath << file->errorString();
|
||||||
emit uploadError(QStringLiteral("Cannot open file: ") + file->errorString());
|
emit uploadError(QStringLiteral("Cannot open file: ") + file->errorString());
|
||||||
delete file;
|
delete file;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
qint64 fileSize = file->size();
|
qint64 fileSize = file->size();
|
||||||
|
qDebug() << "[HttpClient] File size:" << fileSize;
|
||||||
|
|
||||||
QNetworkRequest request(url);
|
QNetworkRequest request(url);
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/octet-stream"));
|
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/octet-stream"));
|
||||||
@@ -154,14 +172,16 @@ void HttpClient::uploadFile(const Device& device, const QString& sessionId,
|
|||||||
emit uploadProgress(sent, fileSize);
|
emit uploadProgress(sent, fileSize);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
connect(reply, &QNetworkReply::finished, this, [this, reply, filePath]() {
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
|
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
qWarning() << "[HttpClient] uploadFile error:" << reply->errorString();
|
||||||
emit uploadError(reply->errorString());
|
emit uploadError(reply->errorString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qDebug() << "[HttpClient] uploadFile completed:" << filePath;
|
||||||
emit uploadCompleted();
|
emit uploadCompleted();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,6 +156,25 @@ QString SessionManager::createSendSession(const Device& target, const QMap<QStri
|
|||||||
return sessionId;
|
return sessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SessionManager::setSendSessionTokens(const QString& sessionId, const QString& responseSessionId,
|
||||||
|
const QMap<QString, QString>& tokens)
|
||||||
|
{
|
||||||
|
if (!m_sendSessions.contains(sessionId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SendSession session = m_sendSessions.take(sessionId);
|
||||||
|
session.sessionId = responseSessionId;
|
||||||
|
|
||||||
|
for (auto it = tokens.constBegin(); it != tokens.constEnd(); ++it) {
|
||||||
|
if (session.files.contains(it.key())) {
|
||||||
|
session.files[it.key()].token = it.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_sendSessions.insert(responseSessionId, session);
|
||||||
|
}
|
||||||
|
|
||||||
void SessionManager::startSendSession(const QString& sessionId)
|
void SessionManager::startSendSession(const QString& sessionId)
|
||||||
{
|
{
|
||||||
if (!m_sendSessions.contains(sessionId)) {
|
if (!m_sendSessions.contains(sessionId)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user