#include "AppController.h" #include #include #include #include #include #include #include #include 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)) , m_httpClient(new LocalSend::HttpClient(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::registerRequest, this, &AppController::onRegisterRequest); connect(m_server, &LocalSend::HttpServer::prepareUploadRequest, this, &AppController::onPrepareUploadRequest); connect(m_server, &LocalSend::HttpServer::uploadRequest, this, &AppController::onUploadRequest); connect(m_sessions, &LocalSend::SessionManager::receiveSessionAccepted, this, &AppController::onSessionAccepted); connect(m_sessions, &LocalSend::SessionManager::receiveSessionDeclined, this, &AppController::onSessionDeclined); connect(m_sessions, &LocalSend::SessionManager::receiveProgress, this, &AppController::onReceiveProgress); connect(m_sessions, &LocalSend::SessionManager::receiveSessionCompleted, this, &AppController::onReceiveCompleted); 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(); } 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(); } } QString AppController::downloadPath() const { return m_settings->downloadDir(); } void AppController::setDownloadPath(const QString& path) { if (m_settings->downloadDir() != path) { m_settings->setDownloadDir(path); emit downloadPathChanged(); } } bool AppController::quickSave() const { return m_settings->quickSave(); } void AppController::setQuickSave(bool enabled) { if (m_settings->quickSave() != enabled) { m_settings->setQuickSave(enabled); emit quickSaveChanged(); } } QVariantList AppController::devices() const { QVariantList result; 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::acceptReceive(const QString& sessionId) { qDebug() << "[AppController] acceptReceive called with sessionId:" << sessionId; LocalSend::ReceiveSession session = m_sessions->receiveSession(sessionId); if (session.sessionId.isEmpty()) { qWarning() << "[AppController] Session not found for sessionId:" << sessionId; return; } QString baseDir = downloadPath(); QDir dir(baseDir); if (!dir.exists()) { dir.mkpath(QStringLiteral(".")); } QMap destinationPaths; for (auto it = session.files.constBegin(); it != session.files.constEnd(); ++it) { QString filePath = generateUniqueFilePath(baseDir, it->file.fileName); destinationPaths.insert(it.key(), filePath); qDebug() << "[AppController] File" << it.key() << "->" << filePath; } m_sessions->acceptReceiveSession(sessionId, destinationPaths); } void AppController::declineReceive(const QString& sessionId) { qDebug() << "[AppController] declineReceive called with sessionId:" << sessionId; m_sessions->declineReceiveSession(sessionId); } QString AppController::generateUniqueFilePath(const QString& baseDir, const QString& fileName) const { QString filePath = baseDir + QDir::separator() + fileName; QFileInfo info(filePath); if (!info.exists()) { return filePath; } QString baseName = info.completeBaseName(); QString suffix = info.suffix(); int counter = 1; while (info.exists()) { QString newName = QString(QStringLiteral("%1 (%2)")).arg(baseName).arg(counter); if (!suffix.isEmpty()) { newName += QStringLiteral(".") + suffix; } filePath = baseDir + QDir::separator() + newName; info.setFile(filePath); counter++; } return filePath; } void AppController::onDeviceDiscovered(const LocalSend::Device& device) { m_devices.insert(device.fingerprint, device); emit devicesChanged(); } void AppController::onDeviceLost(const QString& fingerprint) { m_devices.remove(fingerprint); emit devicesChanged(); } void AppController::onRegisterRequest(const LocalSend::RegisterDto& dto, const QHostAddress& sender) { qDebug() << "[AppController] onRegisterRequest from:" << sender.toString() << "alias:" << dto.alias << "fingerprint:" << dto.fingerprint; LocalSend::Device device; device.ip = sender.toString(); device.port = dto.port; device.protocol = dto.protocol; device.alias = dto.alias; device.fingerprint = dto.fingerprint; device.deviceModel = dto.deviceModel; device.deviceType = dto.deviceType; device.version = dto.version; device.download = dto.download; device.lastSeen = QDateTime::currentDateTime(); m_devices.insert(device.fingerprint, device); emit devicesChanged(); } void AppController::onPrepareUploadRequest(const QString& httpSessionId, const LocalSend::PrepareUploadRequestDto& dto, const QHostAddress& sender) { qDebug() << "[AppController] onPrepareUploadRequest received, httpSessionId:" << httpSessionId; LocalSend::Device device; device.ip = sender.toString(); device.alias = dto.info.alias; 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, httpSessionId); qDebug() << "[AppController] Created receive session, sessionId:" << sessionId; 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, sender.toString(), filesList); } void AppController::onUploadRequest(const QString& sessionId, const QString& fileId, const QString& token, const QByteArray& data) { LocalSend::ReceiveSession session = m_sessions->receiveSession(sessionId); if (session.sessionId.isEmpty()) { return; } if (!session.files.contains(fileId)) { return; } const LocalSend::FileTransfer& transfer = session.files[fileId]; if (transfer.token != token) { emit receiveError(sessionId, QStringLiteral("Invalid token")); return; } QString filePath = transfer.destinationPath; if (filePath.isEmpty()) { emit receiveError(sessionId, QStringLiteral("No destination path")); return; } QFile file(filePath); if (!file.open(QIODevice::WriteOnly)) { emit receiveError(sessionId, QStringLiteral("Cannot open file: ") + file.errorString()); return; } qint64 written = file.write(data); file.close(); if (written != data.size()) { m_sessions->failReceiveFile(sessionId, fileId); emit receiveError(sessionId, QStringLiteral("Write error")); return; } m_sessions->updateReceiveProgress(sessionId, fileId, written); if (written >= transfer.file.size) { m_sessions->completeReceiveFile(sessionId, fileId); } } void AppController::onSessionAccepted(const QString& sessionId, const QMap& tokens) { qDebug() << "[AppController] onSessionAccepted, sessionId:" << sessionId; qDebug() << "[AppController] Tokens:" << tokens; m_server->respondToPrepareUpload(sessionId, true, tokens); } void AppController::onSessionDeclined(const QString& sessionId) { qDebug() << "[AppController] onSessionDeclined, sessionId:" << sessionId; m_server->respondToPrepareUpload(sessionId, false); } void AppController::onReceiveProgress(const QString& sessionId, const QString& fileId, double progress) { emit receiveProgress(sessionId, fileId, progress); } void AppController::onReceiveCompleted(const QString& sessionId) { emit receiveCompleted(sessionId); } bool AppController::sending() const { return !m_currentSendSessionId.isEmpty(); } double AppController::sendProgress() const { return m_sendProgress; } QVariantList AppController::pendingFiles() const { return m_pendingFilesList; } bool AppController::hasPendingFiles() const { return !m_pendingFilesList.isEmpty(); } void AppController::addFiles(const QStringList& filePaths) { QMimeDatabase mimeDb; for (const QString& rawPath : filePaths) { QString filePath = rawPath; if (filePath.startsWith(QStringLiteral("file://"))) { filePath = filePath.mid(7); } QFileInfo info(filePath); if (!info.exists() || info.isDir()) { continue; } QVariantMap file; file[QStringLiteral("path")] = info.absoluteFilePath(); file[QStringLiteral("fileName")] = info.fileName(); file[QStringLiteral("size")] = info.size(); file[QStringLiteral("fileType")] = mimeDb.mimeTypeForFile(info).name(); m_pendingFilesList.append(file); m_pendingSendPaths.append(info.absoluteFilePath()); } emit pendingFilesChanged(); } void AppController::removePendingFile(int index) { if (index < 0 || index >= m_pendingFilesList.size()) { return; } m_pendingFilesList.removeAt(index); m_pendingSendPaths.removeAt(index); emit pendingFilesChanged(); } void AppController::clearPendingFiles() { m_pendingFilesList.clear(); m_pendingSendPaths.clear(); emit pendingFilesChanged(); } void AppController::sendTo(const QString& deviceFingerprint) { if (m_pendingSendPaths.isEmpty()) { emit sendError(QStringLiteral("No files selected")); return; } sendFiles(deviceFingerprint, m_pendingSendPaths); } 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_pendingSendPaths = 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 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 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_pendingSendPaths.clear(); m_pendingFilesList.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(sent) / total; int totalFiles = m_pendingSendPaths.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_pendingSendPaths.size()) { sendNextFile(); } else { qDebug() << "[AppController] All files sent successfully"; m_sendProgress = 100.0; emit sendProgressChanged(); emit sendCompleted(m_currentSendSessionId); m_currentSendSessionId.clear(); m_pendingFilesList.clear(); m_pendingSendPaths.clear(); emit sendingChanged(); emit pendingFilesChanged(); } } 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_pendingSendPaths.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_pendingSendPaths[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; }