phase 5: transfer service
This commit is contained in:
+17
-2
@@ -1,4 +1,5 @@
|
||||
.DS_Store
|
||||
.serena
|
||||
|
||||
# Prerequisites
|
||||
*.d
|
||||
@@ -32,5 +33,19 @@
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
server
|
||||
client
|
||||
|
||||
# Switch build artifacts
|
||||
*.nro
|
||||
*.nso
|
||||
*.pfs0
|
||||
*.nacp
|
||||
*.elf
|
||||
*.lst
|
||||
*.map
|
||||
|
||||
# CMake
|
||||
build/
|
||||
CMakeCache.txt
|
||||
CMakeFiles/
|
||||
cmake_install.cmake
|
||||
compile_commands.json
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(NXST
|
||||
LANGUAGES CXX
|
||||
VERSION 0.1.0
|
||||
)
|
||||
|
||||
# ── C++ standard and flags ────────────────────────────────────────────────────
|
||||
# Arch/linker/libnx flags are already injected by the Switch.cmake toolchain.
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS ON) # gnu++17
|
||||
|
||||
add_compile_options(
|
||||
-fno-rtti
|
||||
-fno-exceptions
|
||||
-O2
|
||||
-g
|
||||
-D_GNU_SOURCE=1
|
||||
)
|
||||
|
||||
# Export compilation database (enables clangd / clang-tidy on the host)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
# ── Sources ───────────────────────────────────────────────────────────────────
|
||||
file(GLOB_RECURSE NXST_SOURCES
|
||||
src/app/*.cpp
|
||||
src/domain/*.cpp
|
||||
src/infra/net/*.cpp
|
||||
src/infra/fs/*.cpp
|
||||
src/infra/sys/*.cpp
|
||||
src/service/*.cpp
|
||||
src/ui/*.cpp
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE PLUTONIUM_SOURCES
|
||||
lib/Plutonium/source/*.cpp
|
||||
)
|
||||
|
||||
add_executable(NXST ${NXST_SOURCES} ${PLUTONIUM_SOURCES})
|
||||
|
||||
# ── Include paths ─────────────────────────────────────────────────────────────
|
||||
target_include_directories(NXST PRIVATE
|
||||
include
|
||||
lib/Plutonium/include
|
||||
)
|
||||
|
||||
# ── pkg-config (uses aarch64-none-elf-pkg-config set by Switch.cmake) ─────────
|
||||
find_package(PkgConfig REQUIRED)
|
||||
|
||||
set(NXST_PKG_MODULES
|
||||
SDL2_ttf SDL2_gfx SDL2_image SDL2_mixer
|
||||
freetype2 harfbuzz minizip libpng libjpeg libwebp
|
||||
glesv2 egl glapi zlib
|
||||
)
|
||||
pkg_check_modules(PORTLIBS REQUIRED IMPORTED_TARGET ${NXST_PKG_MODULES})
|
||||
|
||||
target_include_directories(NXST PRIVATE ${PORTLIBS_INCLUDE_DIRS})
|
||||
|
||||
# ── Link libraries ────────────────────────────────────────────────────────────
|
||||
# Order matters for static linking: put most dependent libs first.
|
||||
# libpu.a first (contains C wrappers not in Plutonium source).
|
||||
# drm_nouveau, harfbuzz, freetype, z appended explicitly after pkg-config output
|
||||
# to fix the freetype→harfbuzz static link order (see build notes from libnx update).
|
||||
target_link_libraries(NXST PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/lib/Plutonium/lib/libpu.a
|
||||
PkgConfig::PORTLIBS
|
||||
drm_nouveau
|
||||
harfbuzz
|
||||
freetype
|
||||
z
|
||||
)
|
||||
|
||||
# ── NACP + NRO ────────────────────────────────────────────────────────────────
|
||||
set(NXST_NACP ${CMAKE_CURRENT_BINARY_DIR}/NXST.nacp)
|
||||
|
||||
nx_generate_nacp(
|
||||
OUTPUT ${NXST_NACP}
|
||||
NAME "NXST"
|
||||
AUTHOR "DragonSpirit"
|
||||
VERSION "04.26.2026"
|
||||
)
|
||||
|
||||
nx_create_nro(NXST
|
||||
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/NXST.nro
|
||||
ICON ${CMAKE_SOURCE_DIR}/icon.png
|
||||
NACP ${NXST_NACP}
|
||||
)
|
||||
|
||||
# ── Convenience targets ────────────────────────────────────────────────────────
|
||||
find_program(NXLINK_EXE nxlink HINTS ${DEVKITPRO}/tools/bin)
|
||||
|
||||
if(NXLINK_EXE)
|
||||
add_custom_target(send
|
||||
COMMAND ${NXLINK_EXE} ${CMAKE_CURRENT_BINARY_DIR}/NXST.nro
|
||||
DEPENDS NXST_nro
|
||||
COMMENT "Sending NXST.nro via nxlink"
|
||||
)
|
||||
|
||||
add_custom_target(debug
|
||||
COMMAND ${NXLINK_EXE} -s ${CMAKE_CURRENT_BINARY_DIR}/NXST.nro
|
||||
DEPENDS NXST_nro
|
||||
COMMENT "Sending NXST.nro with stdio bridge"
|
||||
)
|
||||
endif()
|
||||
@@ -32,8 +32,8 @@ include $(DEVKITPRO)/libnx/switch_rules
|
||||
#---------------------------------------------------------------------------------
|
||||
TARGET := NXST
|
||||
BUILD := build
|
||||
SOURCES := source lib/Plutonium/source
|
||||
INCLUDES := include include/net lib/Plutonium/include
|
||||
SOURCES := src/app src/domain src/infra/net src/infra/fs src/infra/sys src/service src/ui lib/Plutonium/source
|
||||
INCLUDES := include lib/Plutonium/include
|
||||
EXEFS_SRC := exefs_src
|
||||
APP_TITLE := NXST
|
||||
APP_AUTHOR := DragonSpirit
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
| 0 | Tooling & ground rules | ✅ Done | S (~2h) | `.clang-format`, `.clang-tidy`, `.editorconfig`, `.gitattributes` |
|
||||
| 1 | Bug fixes & dead code | ✅ Done | S (~3h) | logger rewrite, `mkdir 0777`, RU comment, dead code |
|
||||
| 2 | File renames + `#pragma once` | ✅ Done | S (~2h) | snake_case filenames, unify guards |
|
||||
| 3 | Directory restructure | ☐ Not started | M (~1d) | `src/` + `include/nxst/` layered tree |
|
||||
| 4 | Make → CMake migration | ☐ Not started | M (~1d) | devkitpro `Switch.cmake` toolchain |
|
||||
| 3 | Directory restructure | ✅ Done | M (~1d) | `src/` + `include/nxst/` layered tree |
|
||||
| 4 | Make → CMake migration | ✅ Done | M (~1d) | devkitpro `Switch.cmake` toolchain |
|
||||
| 5 | TransferService extraction | ☐ Not started | L (~2d) | kill globals, sever UI ↔ net coupling |
|
||||
| 6 | `Result<T>` + RAII | ☐ Not started | M (~1d) | tagged union, OS handle wrappers, split `restore()` |
|
||||
| 7 | Documentation + license | ☐ Not started | S (~half-day) | README, ARCHITECTURE, PROTOCOL, CHANGELOG, GPLv3 LICENSE |
|
||||
| 8 | CI | ☐ Not started | S (~2h) | GitHub Actions, `.nro` artifact, format check, layering check |
|
||||
|
||||
**Active phase:** Phase 3 — Directory restructure.
|
||||
**Active phase:** Phase 5 — TransferService extraction.
|
||||
**Last updated:** 2026-04-26.
|
||||
|
||||
Mark a phase `🟡 In progress` when starting and `✅ Done` when verified on hardware. Keep this table the source of truth.
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
#include <string>
|
||||
#include <switch.h>
|
||||
|
||||
int transfer_files(size_t index, AccountUid uid);
|
||||
bool isClientTransferDone();
|
||||
bool isClientTransferCancelled();
|
||||
bool isClientConnectionFailed();
|
||||
bool isClientProgressKnown();
|
||||
bool isClientWorkersIdle();
|
||||
void cancelClientTransfer();
|
||||
double getClientProgress();
|
||||
std::string getClientStatusText();
|
||||
std::string getClientFailReason();
|
||||
@@ -1,16 +1,15 @@
|
||||
#pragma once
|
||||
#include <const.h>
|
||||
#include "account.hpp"
|
||||
#include "title.hpp"
|
||||
#include "util.hpp"
|
||||
#include <nxst/ui/const.h>
|
||||
#include <nxst/domain/account.hpp>
|
||||
#include <nxst/domain/title.hpp>
|
||||
#include <nxst/domain/util.hpp>
|
||||
#include <memory>
|
||||
#include <switch.h>
|
||||
#include "logger.hpp"
|
||||
#include <nxst/infra/sys/logger.hpp>
|
||||
|
||||
typedef enum { SORT_ALPHA, SORT_LAST_PLAYED, SORT_PLAY_TIME, SORT_MODES_COUNT } sort_t;
|
||||
|
||||
inline float g_currentTime = 0;
|
||||
inline AccountUid g_currentUId;
|
||||
inline bool g_backupScrollEnabled = 0;
|
||||
inline bool g_notificationLedAvailable = false;
|
||||
inline bool g_shouldExitNetworkLoop = false;
|
||||
@@ -1,8 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <pu/Plutonium>
|
||||
#include <users_layout.hpp>
|
||||
#include <titles_layout.hpp>
|
||||
#include <nxst/service/transfer_service.hpp>
|
||||
#include <nxst/ui/users_layout.hpp>
|
||||
#include <nxst/ui/titles_layout.hpp>
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -14,8 +15,8 @@ namespace ui {
|
||||
|
||||
void OnLoad() override;
|
||||
|
||||
// Layout instance
|
||||
UsersLayout::Ref usersLayout;
|
||||
TitlesLayout::Ref titlesLayout;
|
||||
UsersLayout::Ref users_layout;
|
||||
TitlesLayout::Ref titles_layout;
|
||||
nxst::TransferService transfer;
|
||||
};
|
||||
}
|
||||
@@ -25,9 +25,9 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "account.hpp"
|
||||
#include "filesystem.hpp"
|
||||
#include "io.hpp"
|
||||
#include <nxst/domain/account.hpp>
|
||||
#include <nxst/infra/fs/filesystem.hpp>
|
||||
#include <nxst/infra/fs/io.hpp>
|
||||
#include <algorithm>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
@@ -25,9 +25,9 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "account.hpp"
|
||||
#include "common.hpp"
|
||||
#include "io.hpp"
|
||||
#include <nxst/domain/account.hpp>
|
||||
#include <nxst/domain/common.hpp>
|
||||
#include <nxst/infra/fs/io.hpp>
|
||||
#include <switch.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "account.hpp"
|
||||
#include <nxst/domain/account.hpp>
|
||||
#include <switch.h>
|
||||
|
||||
namespace FileSystem {
|
||||
@@ -25,10 +25,10 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "account.hpp"
|
||||
#include "directory.hpp"
|
||||
#include "title.hpp"
|
||||
#include "util.hpp"
|
||||
#include <nxst/domain/account.hpp>
|
||||
#include <nxst/infra/fs/directory.hpp>
|
||||
#include <nxst/domain/title.hpp>
|
||||
#include <nxst/domain/util.hpp>
|
||||
#include <dirent.h>
|
||||
#include <switch.h>
|
||||
#include <sys/stat.h>
|
||||
@@ -0,0 +1 @@
|
||||
#pragma once
|
||||
@@ -0,0 +1 @@
|
||||
#pragma once
|
||||
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
#include <atomic>
|
||||
#include <pthread.h>
|
||||
#include <string>
|
||||
#include <switch.h>
|
||||
#include <nxst/domain/transfer_state.hpp>
|
||||
|
||||
namespace nxst {
|
||||
|
||||
class TransferService {
|
||||
public:
|
||||
int startSend(size_t title_index, AccountUid uid);
|
||||
void cancelSend();
|
||||
|
||||
bool isSendDone() const { return sender_state.done.load(); }
|
||||
bool isSendCancelled() const { return sender_state.cancelled.load(); }
|
||||
bool isSendConnectionFailed() const { return sender_state.connection_failed.load(); }
|
||||
bool isSendProgressKnown() const { return sender_state.bytes_total.load() > 0; }
|
||||
bool isSendWorkersIdle() const { return !sender_active.load(); }
|
||||
double sendProgress() const { return sender_state.progress(); }
|
||||
std::string sendStatusText() const { return sender_state.getStatus(); }
|
||||
std::string sendFailReason() const { return sender_state.fail_reason; }
|
||||
|
||||
int startReceive(size_t title_index, AccountUid uid, std::string title_name);
|
||||
void cancelReceive();
|
||||
|
||||
bool isReceiveDone() const { return receiver_state.done.load(); }
|
||||
bool isReceiveCancelled() const { return receiver_state.cancelled.load(); }
|
||||
bool isReceiveWorkersIdle() const {
|
||||
return !receiver_accept_active.load() && !receiver_broadcast_active.load();
|
||||
}
|
||||
double receiveProgress() const { return receiver_state.progress(); }
|
||||
std::string receiveStatusText() const { return receiver_state.getStatus(); }
|
||||
bool restoreSucceeded() const { return restore_ok; }
|
||||
std::string restoreError() const { return restore_error; }
|
||||
|
||||
private:
|
||||
// Sender
|
||||
TransferState sender_state;
|
||||
std::atomic<int> sender_udp_sock{-1};
|
||||
std::atomic<int> sender_tcp_sock{-1};
|
||||
std::atomic<bool> sender_active{false};
|
||||
|
||||
// Receiver
|
||||
TransferState receiver_state;
|
||||
std::atomic<int> receiver_client_sock{-1};
|
||||
std::atomic<int> receiver_listen_sock{-1};
|
||||
std::atomic<int> receiver_bcast_sock{-1};
|
||||
std::atomic<bool> receiver_accept_active{false};
|
||||
std::atomic<bool> receiver_broadcast_active{false};
|
||||
pthread_t receiver_bcast_thread{};
|
||||
|
||||
// Stored at startReceive, read after network transfer completes
|
||||
size_t restore_title_index{0};
|
||||
AccountUid restore_uid{};
|
||||
std::string restore_title_name;
|
||||
bool restore_ok{false};
|
||||
std::string restore_error;
|
||||
|
||||
// Sender thread
|
||||
struct SenderArgs { TransferService* svc; size_t title_index; AccountUid uid; };
|
||||
static void* senderEntry(void* arg);
|
||||
void runSender(size_t title_index, AccountUid uid);
|
||||
void failSend(const std::string& reason);
|
||||
int findServer(char* out_ip);
|
||||
|
||||
// Receiver threads
|
||||
struct AcceptArgs { TransferService* svc; int server_fd; };
|
||||
static void* broadcastEntry(void* arg);
|
||||
static void* acceptEntry(void* arg);
|
||||
void runBroadcast();
|
||||
void runAccept(int server_fd);
|
||||
std::string replaceUsername(const std::string& file_path) const;
|
||||
};
|
||||
|
||||
} // namespace nxst
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
#include <pu/Plutonium>
|
||||
#include <theme.hpp>
|
||||
#include <nxst/ui/theme.hpp>
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <theme.hpp>
|
||||
#include <nxst/ui/theme.hpp>
|
||||
|
||||
#define COLOR(hex) pu::ui::Color::FromHex(hex)
|
||||
#define BACKGROUND_COLOR theme::color::BgBase
|
||||
@@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
#include <pu/Plutonium>
|
||||
#include <theme.hpp>
|
||||
#include <ui/ui_context.hpp>
|
||||
#include <account.hpp>
|
||||
#include <nxst/ui/theme.hpp>
|
||||
#include <nxst/ui/ui_context.hpp>
|
||||
#include <nxst/domain/account.hpp>
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
#include <pu/Plutonium>
|
||||
#include <theme.hpp>
|
||||
#include <nxst/ui/theme.hpp>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#pragma once
|
||||
#include <pu/Plutonium>
|
||||
#include <const.h>
|
||||
#include <title.hpp>
|
||||
#include <account.hpp>
|
||||
#include <nxst/ui/const.h>
|
||||
#include <nxst/domain/title.hpp>
|
||||
#include <nxst/domain/account.hpp>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <ui/header_bar.hpp>
|
||||
#include <ui/hint_bar.hpp>
|
||||
#include <nxst/ui/header_bar.hpp>
|
||||
#include <nxst/ui/hint_bar.hpp>
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -33,6 +34,7 @@ namespace ui {
|
||||
pu::ui::elm::TextBlock::Ref emptyText;
|
||||
pu::ui::elm::TextBlock::Ref emptySub;
|
||||
|
||||
AccountUid current_uid{};
|
||||
TitlesFocus focus = TitlesFocus::List;
|
||||
TitlesAction action = TitlesAction::Transfer;
|
||||
int lockedListIndex = 0;
|
||||
@@ -46,7 +48,7 @@ namespace ui {
|
||||
public:
|
||||
|
||||
TitlesLayout();
|
||||
void InitTitles();
|
||||
void InitTitles(AccountUid uid);
|
||||
void LockInput() { m_inputLocked = true; }
|
||||
void UnlockInput() { m_inputLocked = false; }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
#include <pu/Plutonium>
|
||||
#include <theme.hpp>
|
||||
#include <util.hpp>
|
||||
#include <nxst/ui/theme.hpp>
|
||||
#include <nxst/domain/util.hpp>
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <switch.h>
|
||||
#include <account.hpp>
|
||||
#include <nxst/domain/account.hpp>
|
||||
|
||||
namespace ui {
|
||||
struct UiContext {
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <pu/Plutonium>
|
||||
#include <const.h>
|
||||
#include <ui/header_bar.hpp>
|
||||
#include <ui/hint_bar.hpp>
|
||||
#include <nxst/ui/const.h>
|
||||
#include <nxst/ui/header_bar.hpp>
|
||||
#include <nxst/ui/hint_bar.hpp>
|
||||
#include <memory>
|
||||
|
||||
namespace ui {
|
||||
@@ -1,8 +0,0 @@
|
||||
#include <string>
|
||||
int startSendingThread();
|
||||
bool isServerTransferDone();
|
||||
bool isServerTransferCancelled();
|
||||
bool isServerWorkersIdle();
|
||||
void cancelServerTransfer();
|
||||
double getServerProgress();
|
||||
std::string getServerStatusText();
|
||||
@@ -1,249 +0,0 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <sys/select.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <netinet/in.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
#ifdef __SWITCH__
|
||||
#include <client.hpp>
|
||||
#include <io.hpp>
|
||||
#include <switch.h>
|
||||
#endif
|
||||
|
||||
#include <protocol.hpp>
|
||||
#include <transfer_state.hpp>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using path = fs::path;
|
||||
|
||||
static TransferState g_client_state;
|
||||
static std::atomic<int> g_client_udp_sock{-1};
|
||||
static std::atomic<int> g_client_tcp_sock{-1};
|
||||
static std::atomic<bool> g_client_thread_active{false};
|
||||
|
||||
bool isClientTransferDone() { return g_client_state.done.load(); }
|
||||
bool isClientTransferCancelled() { return g_client_state.cancelled.load(); }
|
||||
bool isClientConnectionFailed() { return g_client_state.connection_failed.load(); }
|
||||
bool isClientProgressKnown() { return g_client_state.bytes_total.load() > 0; }
|
||||
bool isClientWorkersIdle() { return !g_client_thread_active.load(); }
|
||||
double getClientProgress() { return g_client_state.progress(); }
|
||||
std::string getClientStatusText() { return g_client_state.getStatus(); }
|
||||
std::string getClientFailReason() { return g_client_state.fail_reason; }
|
||||
|
||||
void cancelClientTransfer() {
|
||||
g_client_state.cancelled.store(true);
|
||||
int udp = g_client_udp_sock.exchange(-1);
|
||||
if (udp >= 0) {
|
||||
shutdown(udp, SHUT_RDWR);
|
||||
close(udp);
|
||||
}
|
||||
int tcp = g_client_tcp_sock.exchange(-1);
|
||||
if (tcp >= 0) {
|
||||
shutdown(tcp, SHUT_RDWR);
|
||||
close(tcp);
|
||||
}
|
||||
}
|
||||
|
||||
static bool send_all(int sock, const void* buf, size_t len) {
|
||||
size_t sent = 0;
|
||||
while (sent < len) {
|
||||
ssize_t n = send(sock, static_cast<const char*>(buf) + sent, len - sent, 0);
|
||||
if (n <= 0) return false;
|
||||
sent += n;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sendFile(int sock, const fs::path& filepath) {
|
||||
std::ifstream infile(filepath, std::ios::binary | std::ios::ate);
|
||||
if (!infile.is_open()) {
|
||||
std::cerr << "File not found: " << filepath << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t filename_len = (uint32_t)filepath.string().size();
|
||||
uint64_t file_size = (uint64_t)infile.tellg();
|
||||
infile.seekg(0, std::ios::beg);
|
||||
|
||||
std::cout << "Sending: " << filepath << " (" << file_size << " bytes)" << std::endl;
|
||||
|
||||
if (!send_all(sock, &filename_len, sizeof(filename_len))) return false;
|
||||
if (!send_all(sock, filepath.c_str(), filename_len)) return false;
|
||||
if (!send_all(sock, &file_size, sizeof(file_size))) return false;
|
||||
|
||||
std::vector<char> buffer(proto::BUF_SIZE);
|
||||
uint64_t remaining = file_size;
|
||||
while (remaining > 0) {
|
||||
size_t to_read = (size_t)std::min(remaining, (uint64_t)proto::BUF_SIZE);
|
||||
infile.read(buffer.data(), (std::streamsize)to_read);
|
||||
std::streamsize count = infile.gcount();
|
||||
if (count <= 0) break;
|
||||
if (!send_all(sock, buffer.data(), (size_t)count)) {
|
||||
std::cerr << "Failed to send data for: " << filepath << std::endl;
|
||||
return false;
|
||||
}
|
||||
g_client_state.bytes_done.fetch_add((uint64_t)count);
|
||||
remaining -= (uint64_t)count;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct ThreadArgs { size_t index; AccountUid uid; };
|
||||
|
||||
static int find_server(char* server_ip);
|
||||
|
||||
static void fail_connect(const std::string& reason) {
|
||||
g_client_state.fail_reason = reason;
|
||||
g_client_state.connection_failed.store(true);
|
||||
g_client_state.done.store(true);
|
||||
}
|
||||
|
||||
static void* discovery_and_send_thread(void* arg) {
|
||||
g_client_thread_active.store(true);
|
||||
ThreadArgs* targs = static_cast<ThreadArgs*>(arg);
|
||||
size_t index = targs->index;
|
||||
AccountUid uid = targs->uid;
|
||||
delete targs;
|
||||
|
||||
auto finish = [](void*) {
|
||||
g_client_state.done.store(true);
|
||||
g_client_thread_active.store(false);
|
||||
return (void*)nullptr;
|
||||
};
|
||||
|
||||
char server_ip[INET_ADDRSTRLEN];
|
||||
if (find_server(server_ip) != 0) {
|
||||
if (!g_client_state.cancelled.load())
|
||||
fail_connect("No receiver found.\nMake sure the other Switch is in Receive mode.");
|
||||
return finish(nullptr);
|
||||
}
|
||||
if (g_client_state.cancelled.load()) return finish(nullptr);
|
||||
|
||||
g_client_state.setStatus("Creating backup...");
|
||||
auto backupResult = io::backup(index, uid);
|
||||
if (!std::get<0>(backupResult)) {
|
||||
fail_connect("Failed to create backup:\n" + std::get<2>(backupResult));
|
||||
return finish(nullptr);
|
||||
}
|
||||
fs::path directory = std::get<2>(backupResult);
|
||||
|
||||
if (g_client_state.cancelled.load()) return finish(nullptr);
|
||||
|
||||
g_client_state.setStatus("Connecting...");
|
||||
int tcp_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (tcp_fd < 0) { fail_connect("Failed to open socket."); return finish(nullptr); }
|
||||
g_client_tcp_sock.store(tcp_fd);
|
||||
|
||||
auto release_tcp = [&]() {
|
||||
int owned = g_client_tcp_sock.exchange(-1);
|
||||
if (owned == tcp_fd) close(tcp_fd);
|
||||
};
|
||||
|
||||
sockaddr_in serv{};
|
||||
serv.sin_family = AF_INET;
|
||||
serv.sin_port = htons(proto::TCP_PORT);
|
||||
if (inet_pton(AF_INET, server_ip, &serv.sin_addr) <= 0 ||
|
||||
connect(tcp_fd, (sockaddr*)&serv, sizeof(serv)) < 0) {
|
||||
if (!g_client_state.cancelled.load())
|
||||
fail_connect("Failed to connect to receiver.");
|
||||
release_tcp();
|
||||
return finish(nullptr);
|
||||
}
|
||||
|
||||
uint64_t total = 0;
|
||||
for (const auto& entry : fs::recursive_directory_iterator(directory))
|
||||
if (fs::is_regular_file(entry.path()))
|
||||
total += fs::file_size(entry.path());
|
||||
g_client_state.bytes_total.store(total);
|
||||
|
||||
for (const auto& entry : fs::recursive_directory_iterator(directory)) {
|
||||
if (g_client_state.cancelled.load()) break;
|
||||
const path& p = entry.path();
|
||||
if (fs::is_regular_file(p)) {
|
||||
g_client_state.setStatus(p.filename().string());
|
||||
if (!sendFile(tcp_fd, p)) break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t sentinel = proto::EOF_SENTINEL;
|
||||
send_all(tcp_fd, &sentinel, sizeof(sentinel));
|
||||
|
||||
release_tcp();
|
||||
g_client_state.setStatus("");
|
||||
return finish(nullptr);
|
||||
}
|
||||
|
||||
static int find_server(char* server_ip) {
|
||||
int udp_fd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (udp_fd < 0) return -1;
|
||||
g_client_udp_sock.store(udp_fd);
|
||||
|
||||
auto release_udp = [&]() {
|
||||
int owned = g_client_udp_sock.exchange(-1);
|
||||
if (owned == udp_fd) close(udp_fd);
|
||||
};
|
||||
|
||||
sockaddr_in addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(proto::MULTICAST_PORT);
|
||||
addr.sin_addr.s_addr = inet_addr(proto::MULTICAST_GROUP);
|
||||
|
||||
const char* msg = "DISCOVER_SERVER";
|
||||
if (sendto(udp_fd, msg, strlen(msg), 0, (sockaddr*)&addr, sizeof(addr)) < 0) {
|
||||
release_udp();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Poll in 100ms slices so we can react to cancel within 100ms
|
||||
auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(3);
|
||||
while (std::chrono::steady_clock::now() < deadline) {
|
||||
if (g_client_state.cancelled.load()) {
|
||||
release_udp();
|
||||
return -1;
|
||||
}
|
||||
struct timeval tv{0, 100000};
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(udp_fd, &fds);
|
||||
if (select(udp_fd + 1, &fds, nullptr, nullptr, &tv) > 0) {
|
||||
sockaddr_in from{};
|
||||
socklen_t fromlen = sizeof(from);
|
||||
char buf[256];
|
||||
ssize_t n = recvfrom(udp_fd, buf, sizeof(buf) - 1, 0, (sockaddr*)&from, &fromlen);
|
||||
if (n > 0) {
|
||||
buf[n] = '\0';
|
||||
if (strcmp(buf, "SERVER_HERE") == 0) {
|
||||
inet_ntop(AF_INET, &from.sin_addr, server_ip, INET_ADDRSTRLEN);
|
||||
release_udp();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
release_udp();
|
||||
return -1;
|
||||
}
|
||||
|
||||
int transfer_files(size_t index, AccountUid uid) {
|
||||
g_client_state.reset();
|
||||
g_client_state.setStatus("Searching for receiver...");
|
||||
|
||||
ThreadArgs* arg = new ThreadArgs{index, uid};
|
||||
pthread_t thread;
|
||||
if (pthread_create(&thread, nullptr, discovery_and_send_thread, arg) != 0) {
|
||||
delete arg;
|
||||
return -1;
|
||||
}
|
||||
pthread_detach(thread);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
#include <string>
|
||||
#include <switch.h>
|
||||
#include <switch/services/hid.h>
|
||||
#include <vector>
|
||||
#include <main_application.hpp>
|
||||
|
||||
namespace ui {
|
||||
MainApplication *mainApp;
|
||||
|
||||
void MainApplication::OnLoad() {
|
||||
mainApp = this;
|
||||
this->usersLayout = UsersLayout::New();
|
||||
this->titlesLayout = TitlesLayout::New();
|
||||
this->usersLayout->SetOnInput(
|
||||
std::bind(&UsersLayout::onInput, this->usersLayout, std::placeholders::_1, std::placeholders::_2,
|
||||
std::placeholders::_3, std::placeholders::_4));
|
||||
this->titlesLayout->SetOnInput(std::bind(&TitlesLayout::onInput, this->titlesLayout,std::placeholders::_1, std::placeholders::_2,
|
||||
std::placeholders::_3, std::placeholders::_4));
|
||||
this->LoadLayout(this->usersLayout);
|
||||
}
|
||||
}
|
||||
@@ -1,345 +0,0 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <netinet/in.h>
|
||||
#include <pthread.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
#ifdef __SWITCH__
|
||||
#include <server.hpp>
|
||||
#include <switch.h>
|
||||
#include <main.hpp>
|
||||
#endif
|
||||
|
||||
#include <protocol.hpp>
|
||||
#include <transfer_state.hpp>
|
||||
#include <net/socket.hpp>
|
||||
|
||||
static TransferState g_server_state;
|
||||
static std::atomic<int> g_server_client_sock{-1};
|
||||
static std::atomic<int> g_server_listen_sock{-1};
|
||||
static std::atomic<int> g_broadcast_sock{-1};
|
||||
static std::atomic<bool> g_accept_thread_active{false};
|
||||
static std::atomic<bool> g_broadcast_thread_active{false};
|
||||
static pthread_t g_broadcast_thread{};
|
||||
|
||||
bool isServerTransferDone() { return g_server_state.done.load(); }
|
||||
bool isServerTransferCancelled() { return g_server_state.cancelled.load(); }
|
||||
bool isServerWorkersIdle() { return !g_accept_thread_active.load() && !g_broadcast_thread_active.load(); }
|
||||
double getServerProgress() { return g_server_state.progress(); }
|
||||
std::string getServerStatusText() { return g_server_state.getStatus(); }
|
||||
|
||||
void cancelServerTransfer() {
|
||||
g_server_state.cancelled.store(true);
|
||||
int sock = g_server_client_sock.exchange(-1);
|
||||
if (sock >= 0) {
|
||||
shutdown(sock, SHUT_RDWR);
|
||||
close(sock);
|
||||
}
|
||||
int lsock = g_server_listen_sock.exchange(-1);
|
||||
if (lsock >= 0) {
|
||||
shutdown(lsock, SHUT_RDWR);
|
||||
close(lsock);
|
||||
}
|
||||
int bsock = g_broadcast_sock.exchange(-1);
|
||||
if (bsock >= 0) {
|
||||
shutdown(bsock, SHUT_RDWR);
|
||||
close(bsock);
|
||||
}
|
||||
if (g_broadcast_thread_active.load()) {
|
||||
pthread_cancel(g_broadcast_thread);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __SWITCH__
|
||||
static std::string replaceUsername(const std::string& path) {
|
||||
std::string username = StringUtils::removeNotAscii(
|
||||
StringUtils::removeAccents(Account::username(g_currentUId)));
|
||||
size_t lastSlash = path.rfind('/');
|
||||
if (lastSlash == std::string::npos) return path;
|
||||
size_t prevSlash = path.rfind('/', lastSlash - 1);
|
||||
if (prevSlash == std::string::npos)
|
||||
return username + path.substr(lastSlash);
|
||||
return path.substr(0, prevSlash + 1) + username + path.substr(lastSlash);
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool recv_all(int sock, void* buf, size_t len) {
|
||||
size_t received = 0;
|
||||
while (received < len) {
|
||||
ssize_t n = read(sock, static_cast<char*>(buf) + received, len - received);
|
||||
if (n <= 0) return false;
|
||||
received += n;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void mkdirs(const std::string& path) {
|
||||
for (size_t i = 1; i < path.size(); i++) {
|
||||
if (path[i] == '/') {
|
||||
std::string component = path.substr(0, i);
|
||||
mkdir(component.c_str(), 0777);
|
||||
}
|
||||
}
|
||||
mkdir(path.c_str(), 0777);
|
||||
}
|
||||
|
||||
static void receive_file(int sock, const std::string& relative_path, uint64_t file_size) {
|
||||
std::cout << "Receiving: " << relative_path << " (" << file_size << " bytes)" << std::endl;
|
||||
|
||||
size_t last_slash = relative_path.rfind('/');
|
||||
if (last_slash != std::string::npos) {
|
||||
std::string dir = relative_path.substr(0, last_slash);
|
||||
if (!dir.empty()) mkdirs(dir);
|
||||
}
|
||||
|
||||
FILE* outfile = fopen(relative_path.c_str(), "wb");
|
||||
if (!outfile) {
|
||||
std::cerr << "Failed to open for writing: " << relative_path
|
||||
<< " errno=" << errno << std::endl;
|
||||
// Drain so sender doesn't hang
|
||||
std::vector<char> drain(proto::BUF_SIZE);
|
||||
uint64_t remaining = file_size;
|
||||
while (remaining > 0) {
|
||||
size_t to_read = (size_t)std::min(remaining, (uint64_t)proto::BUF_SIZE);
|
||||
ssize_t n = read(sock, drain.data(), to_read);
|
||||
if (n <= 0) break;
|
||||
remaining -= (uint64_t)n;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
g_server_state.bytes_total.store(file_size);
|
||||
g_server_state.bytes_done.store(0);
|
||||
|
||||
std::vector<char> buffer(proto::BUF_SIZE);
|
||||
uint64_t total_received = 0;
|
||||
while (total_received < file_size) {
|
||||
size_t to_read = (size_t)std::min(file_size - total_received, (uint64_t)proto::BUF_SIZE);
|
||||
ssize_t n = read(sock, buffer.data(), to_read);
|
||||
if (n <= 0) {
|
||||
std::cerr << "Read error receiving: " << relative_path << std::endl;
|
||||
break;
|
||||
}
|
||||
fwrite(buffer.data(), 1, (size_t)n, outfile);
|
||||
total_received += (uint64_t)n;
|
||||
g_server_state.bytes_done.store(total_received);
|
||||
}
|
||||
|
||||
fclose(outfile);
|
||||
std::cout << "Received: " << relative_path << std::endl;
|
||||
}
|
||||
|
||||
static void* handle_client(void* socket_desc) {
|
||||
int client_socket = *(int*)socket_desc;
|
||||
delete static_cast<int*>(socket_desc);
|
||||
|
||||
while (true) {
|
||||
uint32_t filename_len = 0;
|
||||
if (!recv_all(client_socket, &filename_len, sizeof(filename_len)))
|
||||
break;
|
||||
|
||||
if (filename_len == proto::EOF_SENTINEL) {
|
||||
std::cout << "End of transfer." << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
if (filename_len > proto::MAX_FILENAME) {
|
||||
std::cerr << "filename_len=" << filename_len << " exceeds MAX_FILENAME, aborting." << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
std::vector<char> filename(filename_len + 1, '\0');
|
||||
if (!recv_all(client_socket, filename.data(), filename_len)) {
|
||||
std::cerr << "Short read on filename, aborting." << std::endl;
|
||||
break;
|
||||
}
|
||||
std::string filename_str(filename.data(), filename_len);
|
||||
|
||||
#ifdef __SWITCH__
|
||||
filename_str = replaceUsername(filename_str);
|
||||
#endif
|
||||
|
||||
{
|
||||
size_t sl = filename_str.rfind('/');
|
||||
g_server_state.setStatus(
|
||||
sl != std::string::npos ? filename_str.substr(sl + 1) : filename_str);
|
||||
}
|
||||
|
||||
uint64_t file_size = 0;
|
||||
if (!recv_all(client_socket, &file_size, sizeof(file_size))) {
|
||||
std::cerr << "Short read on file_size, aborting." << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
receive_file(client_socket, filename_str, file_size);
|
||||
}
|
||||
|
||||
int owned_client = g_server_client_sock.exchange(-1);
|
||||
if (owned_client == client_socket) {
|
||||
close(client_socket);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct AcceptArgs { int server_fd; };
|
||||
|
||||
static void* accept_and_handle(void* arg) {
|
||||
g_accept_thread_active.store(true);
|
||||
int server_fd = static_cast<AcceptArgs*>(arg)->server_fd;
|
||||
delete static_cast<AcceptArgs*>(arg);
|
||||
|
||||
g_server_listen_sock.store(server_fd);
|
||||
sockaddr_in client_addr{};
|
||||
socklen_t client_len = sizeof(client_addr);
|
||||
int client_socket = accept(server_fd, (sockaddr*)&client_addr, &client_len);
|
||||
int owned_listen = g_server_listen_sock.exchange(-1);
|
||||
if (owned_listen == server_fd) {
|
||||
close(server_fd);
|
||||
}
|
||||
|
||||
if (client_socket >= 0) {
|
||||
g_server_client_sock.store(client_socket);
|
||||
int* pclient = new (std::nothrow) int(client_socket);
|
||||
if (pclient) {
|
||||
handle_client(pclient);
|
||||
} else {
|
||||
close(client_socket);
|
||||
}
|
||||
}
|
||||
|
||||
g_server_state.done.store(true);
|
||||
g_accept_thread_active.store(false);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void* broadcast_listener(void* arg) {
|
||||
g_broadcast_thread_active.store(true);
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, nullptr);
|
||||
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, nullptr);
|
||||
int udp = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (udp < 0) {
|
||||
perror("broadcast_listener: socket");
|
||||
g_broadcast_thread_active.store(false);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
g_broadcast_sock.store(udp);
|
||||
|
||||
struct timeval tv{0, 20000}; // 20ms poll so cancel/exit wins race with socketExit
|
||||
setsockopt(udp, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
|
||||
sockaddr_in addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
addr.sin_port = htons(proto::MULTICAST_PORT);
|
||||
|
||||
if (bind(udp, (sockaddr*)&addr, sizeof(addr)) < 0) {
|
||||
perror("broadcast_listener: bind");
|
||||
int owned = g_broadcast_sock.exchange(-1);
|
||||
if (owned == udp) close(udp);
|
||||
g_broadcast_thread_active.store(false);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ip_mreq group{};
|
||||
group.imr_multiaddr.s_addr = inet_addr(proto::MULTICAST_GROUP);
|
||||
group.imr_interface.s_addr = htonl(INADDR_ANY);
|
||||
if (setsockopt(udp, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)) < 0) {
|
||||
perror("broadcast_listener: setsockopt");
|
||||
int owned = g_broadcast_sock.exchange(-1);
|
||||
if (owned == udp) close(udp);
|
||||
g_broadcast_thread_active.store(false);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::cout << "Broadcast listener started" << std::endl;
|
||||
char buf[256];
|
||||
sockaddr_in from{};
|
||||
socklen_t fromlen = sizeof(from);
|
||||
|
||||
while (true) {
|
||||
ssize_t n = recvfrom(udp, buf, sizeof(buf) - 1, 0, (sockaddr*)&from, &fromlen);
|
||||
if (n < 0) {
|
||||
if (g_server_state.cancelled.load()) break;
|
||||
continue;
|
||||
}
|
||||
buf[n] = '\0';
|
||||
if (strcmp(buf, "DISCOVER_SERVER") == 0) {
|
||||
const char* reply = "SERVER_HERE";
|
||||
sendto(udp, reply, strlen(reply), 0, (sockaddr*)&from, fromlen);
|
||||
std::cout << "Discovery replied." << std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int owned = g_broadcast_sock.exchange(-1);
|
||||
if (owned == udp) close(udp);
|
||||
g_broadcast_thread_active.store(false);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int startSendingThread() {
|
||||
g_server_state.reset();
|
||||
g_server_state.setStatus("Waiting for connection...");
|
||||
|
||||
pthread_t broadcast_thread;
|
||||
if (pthread_create(&broadcast_thread, nullptr, broadcast_listener, nullptr) != 0) {
|
||||
perror("startSendingThread: broadcast thread");
|
||||
return 1;
|
||||
}
|
||||
g_broadcast_thread = broadcast_thread;
|
||||
pthread_detach(broadcast_thread);
|
||||
|
||||
Socket server(socket(AF_INET, SOCK_STREAM, 0));
|
||||
if (!server.valid()) {
|
||||
perror("startSendingThread: socket");
|
||||
cancelServerTransfer();
|
||||
return 1;
|
||||
}
|
||||
|
||||
int yes = 1;
|
||||
setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
|
||||
|
||||
sockaddr_in addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = INADDR_ANY;
|
||||
addr.sin_port = htons(proto::TCP_PORT);
|
||||
|
||||
if (bind(server, (sockaddr*)&addr, sizeof(addr)) < 0) {
|
||||
perror("startSendingThread: bind");
|
||||
cancelServerTransfer();
|
||||
return 1;
|
||||
}
|
||||
if (listen(server, 3) < 0) {
|
||||
perror("startSendingThread: listen");
|
||||
cancelServerTransfer();
|
||||
return 1;
|
||||
}
|
||||
|
||||
AcceptArgs* acc_args = new AcceptArgs{server.fd};
|
||||
pthread_t accept_thread;
|
||||
if (pthread_create(&accept_thread, nullptr, accept_and_handle, acc_args) != 0) {
|
||||
delete acc_args;
|
||||
cancelServerTransfer();
|
||||
return 1;
|
||||
}
|
||||
pthread_detach(accept_thread);
|
||||
server.release(); // accepted by accept_and_handle
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifndef __SWITCH__
|
||||
int main() {
|
||||
if (startSendingThread() != 0) return 1;
|
||||
while (!isServerTransferDone()) usleep(16000);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -1,10 +1,10 @@
|
||||
#include <main_application.hpp>
|
||||
#include "util.hpp"
|
||||
#include "main.hpp"
|
||||
#include <server.hpp>
|
||||
#include <client.hpp>
|
||||
#include <nxst/app/main_application.hpp>
|
||||
#include <nxst/domain/util.hpp>
|
||||
#include <nxst/app/main.hpp>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace ui { extern MainApplication* mainApp; }
|
||||
|
||||
static int nxlink_sock = -1;
|
||||
|
||||
extern "C" void userAppInit() {
|
||||
@@ -22,10 +22,14 @@ extern "C" void userAppInit() {
|
||||
}
|
||||
|
||||
extern "C" void userAppExit() {
|
||||
cancelServerTransfer();
|
||||
cancelClientTransfer();
|
||||
for (int i = 0; i < 150 && (!isServerWorkersIdle() || !isClientWorkersIdle()); i++) {
|
||||
usleep(10000);
|
||||
if (ui::mainApp) {
|
||||
ui::mainApp->transfer.cancelReceive();
|
||||
ui::mainApp->transfer.cancelSend();
|
||||
for (int i = 0; i < 150 &&
|
||||
(!ui::mainApp->transfer.isReceiveWorkersIdle() ||
|
||||
!ui::mainApp->transfer.isSendWorkersIdle()); i++) {
|
||||
usleep(10000);
|
||||
}
|
||||
}
|
||||
if (nxlink_sock != -1) {
|
||||
close(nxlink_sock);
|
||||
@@ -0,0 +1,21 @@
|
||||
#include <string>
|
||||
#include <switch.h>
|
||||
#include <switch/services/hid.h>
|
||||
#include <vector>
|
||||
#include <nxst/app/main_application.hpp>
|
||||
|
||||
namespace ui {
|
||||
MainApplication *mainApp;
|
||||
|
||||
void MainApplication::OnLoad() {
|
||||
mainApp = this;
|
||||
this->users_layout = UsersLayout::New();
|
||||
this->titles_layout = TitlesLayout::New();
|
||||
this->users_layout->SetOnInput(
|
||||
std::bind(&UsersLayout::onInput, this->users_layout, std::placeholders::_1, std::placeholders::_2,
|
||||
std::placeholders::_3, std::placeholders::_4));
|
||||
this->titles_layout->SetOnInput(std::bind(&TitlesLayout::onInput, this->titles_layout, std::placeholders::_1, std::placeholders::_2,
|
||||
std::placeholders::_3, std::placeholders::_4));
|
||||
this->LoadLayout(this->users_layout);
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,7 @@
|
||||
* reasonable ways as different from the original version.
|
||||
*/
|
||||
|
||||
#include "account.hpp"
|
||||
#include <main.hpp>
|
||||
#include <nxst/domain/account.hpp>
|
||||
#include <sys/stat.h>
|
||||
#include <cstdio>
|
||||
|
||||
@@ -138,5 +137,5 @@ AccountUid Account::selectAccount(void)
|
||||
return uid;
|
||||
}
|
||||
|
||||
return g_currentUId;
|
||||
return AccountUid{};
|
||||
}
|
||||
@@ -24,7 +24,7 @@
|
||||
* reasonable ways as different from the original version.
|
||||
*/
|
||||
|
||||
#include "common.hpp"
|
||||
#include <nxst/domain/common.hpp>
|
||||
|
||||
std::string DateTime::timeStr(void)
|
||||
{
|
||||
@@ -24,8 +24,8 @@
|
||||
* reasonable ways as different from the original version.
|
||||
*/
|
||||
|
||||
#include "title.hpp"
|
||||
#include "main.hpp"
|
||||
#include <nxst/domain/title.hpp>
|
||||
#include <nxst/app/main.hpp>
|
||||
|
||||
static std::unordered_map<AccountUid, std::vector<Title>> titles;
|
||||
static bool s_titlesLoaded = false;
|
||||
@@ -24,10 +24,10 @@
|
||||
* reasonable ways as different from the original version.
|
||||
*/
|
||||
|
||||
#include "util.hpp"
|
||||
#include <logger.hpp>
|
||||
#include <main_application.hpp>
|
||||
#include "main.hpp"
|
||||
#include <nxst/domain/util.hpp>
|
||||
#include <nxst/infra/sys/logger.hpp>
|
||||
#include <nxst/app/main_application.hpp>
|
||||
#include <nxst/app/main.hpp>
|
||||
|
||||
void servicesExit(void)
|
||||
{
|
||||
@@ -24,7 +24,7 @@
|
||||
* reasonable ways as different from the original version.
|
||||
*/
|
||||
|
||||
#include "directory.hpp"
|
||||
#include <nxst/infra/fs/directory.hpp>
|
||||
|
||||
Directory::Directory(const std::string& root)
|
||||
{
|
||||
@@ -24,7 +24,7 @@
|
||||
* reasonable ways as different from the original version.
|
||||
*/
|
||||
|
||||
#include "filesystem.hpp"
|
||||
#include <nxst/infra/fs/filesystem.hpp>
|
||||
|
||||
Result FileSystem::mount(FsFileSystem* fileSystem, u64 titleID, AccountUid userID)
|
||||
{
|
||||
@@ -24,9 +24,9 @@
|
||||
* reasonable ways as different from the original version.
|
||||
*/
|
||||
|
||||
#include "io.hpp"
|
||||
#include "main.hpp"
|
||||
#include <logger.hpp>
|
||||
#include <nxst/infra/fs/io.hpp>
|
||||
#include <nxst/app/main.hpp>
|
||||
#include <nxst/infra/sys/logger.hpp>
|
||||
|
||||
bool io::fileExists(const std::string& path)
|
||||
{
|
||||
@@ -0,0 +1 @@
|
||||
// Logic moved to src/service/transfer_service.cpp
|
||||
@@ -0,0 +1 @@
|
||||
// Logic moved to src/service/transfer_service.cpp
|
||||
@@ -1,4 +1,4 @@
|
||||
#include <logger.hpp>
|
||||
#include <nxst/infra/sys/logger.hpp>
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
@@ -0,0 +1,483 @@
|
||||
#include <nxst/service/transfer_service.hpp>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
#ifdef __SWITCH__
|
||||
#include <switch.h>
|
||||
#include <nxst/infra/fs/io.hpp>
|
||||
#include <nxst/domain/util.hpp>
|
||||
#include <nxst/domain/account.hpp>
|
||||
#endif
|
||||
|
||||
#include <nxst/domain/protocol.hpp>
|
||||
#include <nxst/infra/net/socket.hpp>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace nxst {
|
||||
|
||||
// ─── File-transfer helpers ────────────────────────────────────────────────────
|
||||
|
||||
static bool sendAll(int sock, const void* buf, size_t len) {
|
||||
size_t sent = 0;
|
||||
while (sent < len) {
|
||||
ssize_t n = send(sock, static_cast<const char*>(buf) + sent, len - sent, 0);
|
||||
if (n <= 0) return false;
|
||||
sent += n;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool recvAll(int sock, void* buf, size_t len) {
|
||||
size_t got = 0;
|
||||
while (got < len) {
|
||||
ssize_t n = read(sock, static_cast<char*>(buf) + got, len - got);
|
||||
if (n <= 0) return false;
|
||||
got += n;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sendFile(int sock, const fs::path& filepath, TransferState& state) {
|
||||
std::ifstream infile(filepath, std::ios::binary | std::ios::ate);
|
||||
if (!infile.is_open()) return false;
|
||||
|
||||
uint32_t filename_len = (uint32_t)filepath.string().size();
|
||||
uint64_t file_size = (uint64_t)infile.tellg();
|
||||
infile.seekg(0, std::ios::beg);
|
||||
|
||||
if (!sendAll(sock, &filename_len, sizeof(filename_len))) return false;
|
||||
if (!sendAll(sock, filepath.c_str(), filename_len)) return false;
|
||||
if (!sendAll(sock, &file_size, sizeof(file_size))) return false;
|
||||
|
||||
std::vector<char> buffer(proto::BUF_SIZE);
|
||||
uint64_t remaining = file_size;
|
||||
while (remaining > 0) {
|
||||
size_t to_read = (size_t)std::min(remaining, (uint64_t)proto::BUF_SIZE);
|
||||
infile.read(buffer.data(), (std::streamsize)to_read);
|
||||
std::streamsize count = infile.gcount();
|
||||
if (count <= 0) break;
|
||||
if (!sendAll(sock, buffer.data(), (size_t)count)) return false;
|
||||
state.bytes_done.fetch_add((uint64_t)count);
|
||||
remaining -= (uint64_t)count;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void mkdirs(const std::string& path) {
|
||||
for (size_t i = 1; i < path.size(); i++) {
|
||||
if (path[i] == '/') {
|
||||
std::string component = path.substr(0, i);
|
||||
mkdir(component.c_str(), 0777);
|
||||
}
|
||||
}
|
||||
mkdir(path.c_str(), 0777);
|
||||
}
|
||||
|
||||
static void receiveFile(int sock, const std::string& rel_path, uint64_t file_size,
|
||||
TransferState& state) {
|
||||
size_t last_slash = rel_path.rfind('/');
|
||||
if (last_slash != std::string::npos) {
|
||||
std::string dir = rel_path.substr(0, last_slash);
|
||||
if (!dir.empty()) mkdirs(dir);
|
||||
}
|
||||
|
||||
FILE* outfile = fopen(rel_path.c_str(), "wb");
|
||||
if (!outfile) {
|
||||
std::vector<char> drain(proto::BUF_SIZE);
|
||||
uint64_t remaining = file_size;
|
||||
while (remaining > 0) {
|
||||
size_t to_read = (size_t)std::min(remaining, (uint64_t)proto::BUF_SIZE);
|
||||
ssize_t n = read(sock, drain.data(), to_read);
|
||||
if (n <= 0) break;
|
||||
remaining -= (uint64_t)n;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
state.bytes_total.store(file_size);
|
||||
state.bytes_done.store(0);
|
||||
|
||||
std::vector<char> buffer(proto::BUF_SIZE);
|
||||
uint64_t total = 0;
|
||||
while (total < file_size) {
|
||||
size_t to_read = (size_t)std::min(file_size - total, (uint64_t)proto::BUF_SIZE);
|
||||
ssize_t n = read(sock, buffer.data(), to_read);
|
||||
if (n <= 0) break;
|
||||
fwrite(buffer.data(), 1, (size_t)n, outfile);
|
||||
total += (uint64_t)n;
|
||||
state.bytes_done.store(total);
|
||||
}
|
||||
fclose(outfile);
|
||||
}
|
||||
|
||||
// ─── Sender ──────────────────────────────────────────────────────────────────
|
||||
|
||||
void TransferService::failSend(const std::string& reason) {
|
||||
sender_state.fail_reason = reason;
|
||||
sender_state.connection_failed.store(true);
|
||||
sender_state.done.store(true);
|
||||
}
|
||||
|
||||
int TransferService::findServer(char* out_ip) {
|
||||
int udp_fd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (udp_fd < 0) return -1;
|
||||
sender_udp_sock.store(udp_fd);
|
||||
|
||||
auto releaseUdp = [&]() {
|
||||
int owned = sender_udp_sock.exchange(-1);
|
||||
if (owned == udp_fd) close(udp_fd);
|
||||
};
|
||||
|
||||
sockaddr_in addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(proto::MULTICAST_PORT);
|
||||
addr.sin_addr.s_addr = inet_addr(proto::MULTICAST_GROUP);
|
||||
|
||||
if (sendto(udp_fd, "DISCOVER_SERVER", 15, 0, (sockaddr*)&addr, sizeof(addr)) < 0) {
|
||||
releaseUdp();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Poll in 100ms slices so cancel races within 100ms
|
||||
auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(3);
|
||||
while (std::chrono::steady_clock::now() < deadline) {
|
||||
if (sender_state.cancelled.load()) { releaseUdp(); return -1; }
|
||||
struct timeval tv{0, 100000};
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(udp_fd, &fds);
|
||||
if (select(udp_fd + 1, &fds, nullptr, nullptr, &tv) > 0) {
|
||||
sockaddr_in from{};
|
||||
socklen_t fromlen = sizeof(from);
|
||||
char buf[256];
|
||||
ssize_t n = recvfrom(udp_fd, buf, sizeof(buf) - 1, 0, (sockaddr*)&from, &fromlen);
|
||||
if (n > 0) {
|
||||
buf[n] = '\0';
|
||||
if (strcmp(buf, "SERVER_HERE") == 0) {
|
||||
inet_ntop(AF_INET, &from.sin_addr, out_ip, INET_ADDRSTRLEN);
|
||||
releaseUdp();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
releaseUdp();
|
||||
return -1;
|
||||
}
|
||||
|
||||
void* TransferService::senderEntry(void* arg) {
|
||||
auto* a = static_cast<SenderArgs*>(arg);
|
||||
TransferService* svc = a->svc;
|
||||
size_t idx = a->title_index;
|
||||
AccountUid uid = a->uid;
|
||||
delete a;
|
||||
svc->runSender(idx, uid);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void TransferService::runSender(size_t title_index, AccountUid uid) {
|
||||
sender_active.store(true);
|
||||
|
||||
auto finish = [this]() {
|
||||
sender_state.done.store(true);
|
||||
sender_active.store(false);
|
||||
};
|
||||
|
||||
char server_ip[INET_ADDRSTRLEN];
|
||||
if (findServer(server_ip) != 0) {
|
||||
if (!sender_state.cancelled.load())
|
||||
failSend("No receiver found.\nMake sure the other Switch is in Receive mode.");
|
||||
return finish();
|
||||
}
|
||||
if (sender_state.cancelled.load()) return finish();
|
||||
|
||||
sender_state.setStatus("Creating backup...");
|
||||
#ifdef __SWITCH__
|
||||
auto backup_result = io::backup(title_index, uid);
|
||||
if (!std::get<0>(backup_result)) {
|
||||
failSend("Failed to create backup:\n" + std::get<2>(backup_result));
|
||||
return finish();
|
||||
}
|
||||
fs::path directory = std::get<2>(backup_result);
|
||||
#else
|
||||
fs::path directory = ".";
|
||||
(void)title_index; (void)uid;
|
||||
#endif
|
||||
|
||||
if (sender_state.cancelled.load()) return finish();
|
||||
|
||||
sender_state.setStatus("Connecting...");
|
||||
int tcp_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (tcp_fd < 0) { failSend("Failed to open socket."); return finish(); }
|
||||
sender_tcp_sock.store(tcp_fd);
|
||||
|
||||
auto releaseTcp = [&]() {
|
||||
int owned = sender_tcp_sock.exchange(-1);
|
||||
if (owned == tcp_fd) close(tcp_fd);
|
||||
};
|
||||
|
||||
sockaddr_in serv{};
|
||||
serv.sin_family = AF_INET;
|
||||
serv.sin_port = htons(proto::TCP_PORT);
|
||||
if (inet_pton(AF_INET, server_ip, &serv.sin_addr) <= 0 ||
|
||||
connect(tcp_fd, (sockaddr*)&serv, sizeof(serv)) < 0) {
|
||||
if (!sender_state.cancelled.load())
|
||||
failSend("Failed to connect to receiver.");
|
||||
releaseTcp();
|
||||
return finish();
|
||||
}
|
||||
|
||||
uint64_t total = 0;
|
||||
for (const auto& entry : fs::recursive_directory_iterator(directory))
|
||||
if (fs::is_regular_file(entry.path()))
|
||||
total += fs::file_size(entry.path());
|
||||
sender_state.bytes_total.store(total);
|
||||
|
||||
for (const auto& entry : fs::recursive_directory_iterator(directory)) {
|
||||
if (sender_state.cancelled.load()) break;
|
||||
const fs::path& p = entry.path();
|
||||
if (fs::is_regular_file(p)) {
|
||||
sender_state.setStatus(p.filename().string());
|
||||
if (!sendFile(tcp_fd, p, sender_state)) break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t sentinel = proto::EOF_SENTINEL;
|
||||
sendAll(tcp_fd, &sentinel, sizeof(sentinel));
|
||||
|
||||
releaseTcp();
|
||||
sender_state.setStatus("");
|
||||
return finish();
|
||||
}
|
||||
|
||||
int TransferService::startSend(size_t title_index, AccountUid uid) {
|
||||
sender_state.reset();
|
||||
sender_state.setStatus("Searching for receiver...");
|
||||
|
||||
auto* arg = new SenderArgs{this, title_index, uid};
|
||||
pthread_t thread;
|
||||
if (pthread_create(&thread, nullptr, senderEntry, arg) != 0) {
|
||||
delete arg;
|
||||
return -1;
|
||||
}
|
||||
pthread_detach(thread);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void TransferService::cancelSend() {
|
||||
sender_state.cancelled.store(true);
|
||||
int udp = sender_udp_sock.exchange(-1);
|
||||
if (udp >= 0) { shutdown(udp, SHUT_RDWR); close(udp); }
|
||||
int tcp = sender_tcp_sock.exchange(-1);
|
||||
if (tcp >= 0) { shutdown(tcp, SHUT_RDWR); close(tcp); }
|
||||
}
|
||||
|
||||
// ─── Receiver ────────────────────────────────────────────────────────────────
|
||||
|
||||
std::string TransferService::replaceUsername(const std::string& file_path) const {
|
||||
#ifdef __SWITCH__
|
||||
std::string username = StringUtils::removeNotAscii(
|
||||
StringUtils::removeAccents(Account::username(restore_uid)));
|
||||
size_t last_slash = file_path.rfind('/');
|
||||
if (last_slash == std::string::npos) return file_path;
|
||||
size_t prev_slash = file_path.rfind('/', last_slash - 1);
|
||||
if (prev_slash == std::string::npos)
|
||||
return username + file_path.substr(last_slash);
|
||||
return file_path.substr(0, prev_slash + 1) + username + file_path.substr(last_slash);
|
||||
#else
|
||||
return file_path;
|
||||
#endif
|
||||
}
|
||||
|
||||
void* TransferService::broadcastEntry(void* arg) {
|
||||
static_cast<TransferService*>(arg)->runBroadcast();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void TransferService::runBroadcast() {
|
||||
receiver_broadcast_active.store(true);
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, nullptr);
|
||||
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, nullptr);
|
||||
|
||||
int udp = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (udp < 0) { receiver_broadcast_active.store(false); return; }
|
||||
receiver_bcast_sock.store(udp);
|
||||
|
||||
auto releaseUdp = [&]() {
|
||||
int owned = receiver_bcast_sock.exchange(-1);
|
||||
if (owned == udp) close(udp);
|
||||
};
|
||||
|
||||
struct timeval tv{0, 20000}; // 20ms poll so cancel/exit wins race with socketExit
|
||||
setsockopt(udp, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
|
||||
sockaddr_in addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
addr.sin_port = htons(proto::MULTICAST_PORT);
|
||||
|
||||
if (bind(udp, (sockaddr*)&addr, sizeof(addr)) < 0) {
|
||||
releaseUdp();
|
||||
receiver_broadcast_active.store(false);
|
||||
return;
|
||||
}
|
||||
|
||||
ip_mreq group{};
|
||||
group.imr_multiaddr.s_addr = inet_addr(proto::MULTICAST_GROUP);
|
||||
group.imr_interface.s_addr = htonl(INADDR_ANY);
|
||||
if (setsockopt(udp, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)) < 0) {
|
||||
releaseUdp();
|
||||
receiver_broadcast_active.store(false);
|
||||
return;
|
||||
}
|
||||
|
||||
char buf[256];
|
||||
sockaddr_in from{};
|
||||
socklen_t fromlen = sizeof(from);
|
||||
while (true) {
|
||||
ssize_t n = recvfrom(udp, buf, sizeof(buf) - 1, 0, (sockaddr*)&from, &fromlen);
|
||||
if (n < 0) {
|
||||
if (receiver_state.cancelled.load()) break;
|
||||
continue;
|
||||
}
|
||||
buf[n] = '\0';
|
||||
if (strcmp(buf, "DISCOVER_SERVER") == 0) {
|
||||
sendto(udp, "SERVER_HERE", 11, 0, (sockaddr*)&from, fromlen);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
releaseUdp();
|
||||
receiver_broadcast_active.store(false);
|
||||
}
|
||||
|
||||
void* TransferService::acceptEntry(void* arg) {
|
||||
auto* a = static_cast<AcceptArgs*>(arg);
|
||||
TransferService* svc = a->svc;
|
||||
int server_fd = a->server_fd;
|
||||
delete a;
|
||||
svc->runAccept(server_fd);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void TransferService::runAccept(int server_fd) {
|
||||
receiver_accept_active.store(true);
|
||||
receiver_listen_sock.store(server_fd);
|
||||
|
||||
sockaddr_in client_addr{};
|
||||
socklen_t client_len = sizeof(client_addr);
|
||||
int client_sock = accept(server_fd, (sockaddr*)&client_addr, &client_len);
|
||||
|
||||
int owned_listen = receiver_listen_sock.exchange(-1);
|
||||
if (owned_listen == server_fd) close(server_fd);
|
||||
|
||||
if (client_sock >= 0) {
|
||||
receiver_client_sock.store(client_sock);
|
||||
|
||||
while (true) {
|
||||
uint32_t filename_len = 0;
|
||||
if (!recvAll(client_sock, &filename_len, sizeof(filename_len))) break;
|
||||
if (filename_len == proto::EOF_SENTINEL) break;
|
||||
if (filename_len > proto::MAX_FILENAME) break;
|
||||
|
||||
std::vector<char> filename_buf(filename_len + 1, '\0');
|
||||
if (!recvAll(client_sock, filename_buf.data(), filename_len)) break;
|
||||
std::string filename_str(filename_buf.data(), filename_len);
|
||||
filename_str = replaceUsername(filename_str);
|
||||
|
||||
{
|
||||
size_t sl = filename_str.rfind('/');
|
||||
receiver_state.setStatus(
|
||||
sl != std::string::npos ? filename_str.substr(sl + 1) : filename_str);
|
||||
}
|
||||
|
||||
uint64_t file_size = 0;
|
||||
if (!recvAll(client_sock, &file_size, sizeof(file_size))) break;
|
||||
receiveFile(client_sock, filename_str, file_size, receiver_state);
|
||||
}
|
||||
|
||||
int owned = receiver_client_sock.exchange(-1);
|
||||
if (owned == client_sock) close(client_sock);
|
||||
|
||||
if (!receiver_state.cancelled.load()) {
|
||||
#ifdef __SWITCH__
|
||||
receiver_state.setStatus("Restoring...");
|
||||
auto result = io::restore(restore_title_index, restore_uid, 0, restore_title_name);
|
||||
restore_ok = std::get<0>(result);
|
||||
restore_error = restore_ok ? "" : std::get<2>(result);
|
||||
#else
|
||||
restore_ok = true;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
receiver_state.done.store(true);
|
||||
receiver_accept_active.store(false);
|
||||
}
|
||||
|
||||
int TransferService::startReceive(size_t title_index, AccountUid uid, std::string title_name) {
|
||||
receiver_state.reset();
|
||||
receiver_state.setStatus("Waiting for connection...");
|
||||
restore_title_index = title_index;
|
||||
restore_uid = uid;
|
||||
restore_title_name = std::move(title_name);
|
||||
restore_ok = false;
|
||||
restore_error.clear();
|
||||
|
||||
pthread_t bcast_thread;
|
||||
if (pthread_create(&bcast_thread, nullptr, broadcastEntry, this) != 0) return 1;
|
||||
receiver_bcast_thread = bcast_thread;
|
||||
pthread_detach(bcast_thread);
|
||||
|
||||
Socket server(socket(AF_INET, SOCK_STREAM, 0));
|
||||
if (!server.valid()) { cancelReceive(); return 1; }
|
||||
|
||||
int yes = 1;
|
||||
setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
|
||||
|
||||
sockaddr_in addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = INADDR_ANY;
|
||||
addr.sin_port = htons(proto::TCP_PORT);
|
||||
|
||||
if (bind(server, (sockaddr*)&addr, sizeof(addr)) < 0 ||
|
||||
listen(server, 3) < 0) {
|
||||
cancelReceive();
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto* acc_args = new AcceptArgs{this, server.fd};
|
||||
pthread_t accept_thread;
|
||||
if (pthread_create(&accept_thread, nullptr, acceptEntry, acc_args) != 0) {
|
||||
delete acc_args;
|
||||
cancelReceive();
|
||||
return 1;
|
||||
}
|
||||
pthread_detach(accept_thread);
|
||||
server.release();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void TransferService::cancelReceive() {
|
||||
receiver_state.cancelled.store(true);
|
||||
int sock = receiver_client_sock.exchange(-1);
|
||||
if (sock >= 0) { shutdown(sock, SHUT_RDWR); close(sock); }
|
||||
int lsock = receiver_listen_sock.exchange(-1);
|
||||
if (lsock >= 0) { shutdown(lsock, SHUT_RDWR); close(lsock); }
|
||||
int bsock = receiver_bcast_sock.exchange(-1);
|
||||
if (bsock >= 0) { shutdown(bsock, SHUT_RDWR); close(bsock); }
|
||||
if (receiver_broadcast_active.load()) pthread_cancel(receiver_bcast_thread);
|
||||
}
|
||||
|
||||
} // namespace nxst
|
||||
@@ -1,10 +1,7 @@
|
||||
#include <main_application.hpp>
|
||||
#include <stdio.h>
|
||||
#include <main.hpp>
|
||||
#include <const.h>
|
||||
#include <client.hpp>
|
||||
#include <server.hpp>
|
||||
#include <transfer_overlay.hpp>
|
||||
#include <nxst/app/main_application.hpp>
|
||||
#include <nxst/domain/util.hpp>
|
||||
#include <nxst/ui/transfer_overlay.hpp>
|
||||
#include <nxst/ui/const.h>
|
||||
|
||||
namespace ui {
|
||||
extern MainApplication *mainApp;
|
||||
@@ -98,24 +95,24 @@ namespace ui {
|
||||
this->updateHints();
|
||||
}
|
||||
|
||||
void TitlesLayout::InitTitles() {
|
||||
void TitlesLayout::InitTitles(AccountUid uid) {
|
||||
using namespace theme;
|
||||
Logger::getInstance().log(Logger::INFO, "InitTitles");
|
||||
this->current_uid = uid;
|
||||
|
||||
auto it = this->menuCache.find(g_currentUId);
|
||||
auto it = this->menuCache.find(uid);
|
||||
std::vector<pu::ui::elm::MenuItem::Ref>* items;
|
||||
if (it != this->menuCache.end()) {
|
||||
items = &it->second;
|
||||
} else {
|
||||
std::vector<pu::ui::elm::MenuItem::Ref> built;
|
||||
for (size_t i = 0; i < getTitleCount(g_currentUId); i++) {
|
||||
for (size_t i = 0; i < getTitleCount(uid); i++) {
|
||||
Title title;
|
||||
getTitle(title, g_currentUId, i);
|
||||
getTitle(title, uid, i);
|
||||
auto titleItem = pu::ui::elm::MenuItem::New(title.name().c_str());
|
||||
titleItem->SetColor(color::TextPrimary);
|
||||
built.push_back(titleItem);
|
||||
}
|
||||
auto inserted = this->menuCache.emplace(g_currentUId, std::move(built));
|
||||
auto inserted = this->menuCache.emplace(uid, std::move(built));
|
||||
items = &inserted.first->second;
|
||||
}
|
||||
|
||||
@@ -144,14 +141,14 @@ namespace ui {
|
||||
this->refreshButtons();
|
||||
this->updateHints();
|
||||
|
||||
this->header->SetUser(g_currentUId, Account::username(g_currentUId));
|
||||
this->header->SetUser(uid, Account::username(uid));
|
||||
}
|
||||
|
||||
void TitlesLayout::refreshPanel() {
|
||||
if (this->titlesMenu->GetItems().empty()) return;
|
||||
int idx = this->titlesMenu->GetSelectedIndex();
|
||||
Title title;
|
||||
getTitle(title, g_currentUId, idx);
|
||||
getTitle(title, this->current_uid, idx);
|
||||
this->panelTitle->SetText(StringUtils::elide(title.name(), 24));
|
||||
}
|
||||
|
||||
@@ -185,24 +182,25 @@ namespace ui {
|
||||
}
|
||||
|
||||
void TitlesLayout::runTransfer(int index, Title& title) {
|
||||
(void)title;
|
||||
auto ovl = TransferOverlay::New("Transferring save data...");
|
||||
this->titlesMenu->SetVisible(false);
|
||||
mainApp->StartOverlay(ovl);
|
||||
this->LockInput();
|
||||
if (transfer_files(index, g_currentUId) != 0) {
|
||||
if (mainApp->transfer.startSend((size_t)index, this->current_uid) != 0) {
|
||||
mainApp->EndOverlay();
|
||||
this->titlesMenu->SetVisible(true);
|
||||
this->UnlockInput();
|
||||
mainApp->CreateShowDialog("Transfer", "Failed to start transfer.", {"OK"}, true);
|
||||
return;
|
||||
}
|
||||
while (!isClientTransferDone()) {
|
||||
ovl->SetStatus(getClientStatusText());
|
||||
ovl->SetProgressVisible(isClientProgressKnown());
|
||||
ovl->SetProgress(getClientProgress());
|
||||
while (!mainApp->transfer.isSendDone()) {
|
||||
ovl->SetStatus(mainApp->transfer.sendStatusText());
|
||||
ovl->SetProgressVisible(mainApp->transfer.isSendProgressKnown());
|
||||
ovl->SetProgress(mainApp->transfer.sendProgress());
|
||||
mainApp->CallForRender();
|
||||
if (mainApp->GetButtonsDown() & HidNpadButton_B) {
|
||||
cancelClientTransfer();
|
||||
mainApp->transfer.cancelSend();
|
||||
}
|
||||
svcSleepThread(16666666LL);
|
||||
}
|
||||
@@ -210,9 +208,9 @@ namespace ui {
|
||||
this->titlesMenu->SetVisible(true);
|
||||
this->UnlockInput();
|
||||
|
||||
if (isClientConnectionFailed()) {
|
||||
mainApp->CreateShowDialog("Transfer", getClientFailReason(), {"OK"}, true);
|
||||
} else if (isClientTransferCancelled()) {
|
||||
if (mainApp->transfer.isSendConnectionFailed()) {
|
||||
mainApp->CreateShowDialog("Transfer", mainApp->transfer.sendFailReason(), {"OK"}, true);
|
||||
} else if (mainApp->transfer.isSendCancelled()) {
|
||||
mainApp->CreateShowDialog("Transfer", "Transfer cancelled.", {"OK"}, true);
|
||||
} else {
|
||||
mainApp->CreateShowDialog("Transfer", "Save data sent successfully!", {"OK"}, true);
|
||||
@@ -220,7 +218,7 @@ namespace ui {
|
||||
}
|
||||
|
||||
void TitlesLayout::runReceive(int index, Title& title) {
|
||||
if (startSendingThread() != 0) {
|
||||
if (mainApp->transfer.startReceive((size_t)index, this->current_uid, title.name()) != 0) {
|
||||
mainApp->CreateShowDialog("Receive", "Failed to start receiver.\nCheck network connection.", {"OK"}, true);
|
||||
return;
|
||||
}
|
||||
@@ -228,12 +226,12 @@ namespace ui {
|
||||
this->titlesMenu->SetVisible(false);
|
||||
mainApp->StartOverlay(ovl);
|
||||
this->LockInput();
|
||||
while (!isServerTransferDone()) {
|
||||
ovl->SetStatus(getServerStatusText());
|
||||
ovl->SetProgress(getServerProgress());
|
||||
while (!mainApp->transfer.isReceiveDone()) {
|
||||
ovl->SetStatus(mainApp->transfer.receiveStatusText());
|
||||
ovl->SetProgress(mainApp->transfer.receiveProgress());
|
||||
mainApp->CallForRender();
|
||||
if (mainApp->GetButtonsDown() & HidNpadButton_B) {
|
||||
cancelServerTransfer();
|
||||
mainApp->transfer.cancelReceive();
|
||||
}
|
||||
svcSleepThread(16666666LL);
|
||||
}
|
||||
@@ -241,24 +239,22 @@ namespace ui {
|
||||
this->titlesMenu->SetVisible(true);
|
||||
this->UnlockInput();
|
||||
|
||||
if (isServerTransferCancelled()) {
|
||||
if (mainApp->transfer.isReceiveCancelled()) {
|
||||
mainApp->CreateShowDialog("Receive", "Transfer cancelled.", {"OK"}, true);
|
||||
return;
|
||||
}
|
||||
auto restoreResult = io::restore(index, g_currentUId, 0, title.name());
|
||||
if (std::get<0>(restoreResult)) {
|
||||
} else if (mainApp->transfer.restoreSucceeded()) {
|
||||
mainApp->CreateShowDialog("Receive", "Save data received and restored successfully!", {"OK"}, true);
|
||||
} else {
|
||||
mainApp->CreateShowDialog("Receive", "Restore failed:\n" + std::get<2>(restoreResult), {"OK"}, true);
|
||||
mainApp->CreateShowDialog("Receive", "Restore failed:\n" + mainApp->transfer.restoreError(), {"OK"}, true);
|
||||
}
|
||||
}
|
||||
|
||||
void TitlesLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) {
|
||||
(void)Up; (void)Held; (void)Pos;
|
||||
if (m_inputLocked) return;
|
||||
|
||||
if (Down & HidNpadButton_Plus) {
|
||||
cancelClientTransfer();
|
||||
cancelServerTransfer();
|
||||
mainApp->transfer.cancelSend();
|
||||
mainApp->transfer.cancelReceive();
|
||||
mainApp->Close();
|
||||
return;
|
||||
}
|
||||
@@ -266,7 +262,7 @@ namespace ui {
|
||||
if (focus == TitlesFocus::List) {
|
||||
if (Down & HidNpadButton_B) {
|
||||
this->header->SetUser(std::nullopt, "");
|
||||
mainApp->LoadLayout(mainApp->usersLayout);
|
||||
mainApp->LoadLayout(mainApp->users_layout);
|
||||
return;
|
||||
}
|
||||
if (Down & HidNpadButton_A) {
|
||||
@@ -298,7 +294,7 @@ namespace ui {
|
||||
if (Down & HidNpadButton_A) {
|
||||
int idx = this->titlesMenu->GetSelectedIndex();
|
||||
Title title;
|
||||
getTitle(title, g_currentUId, idx);
|
||||
getTitle(title, this->current_uid, idx);
|
||||
TitlesAction chosen = action;
|
||||
this->focus = TitlesFocus::List;
|
||||
this->refreshButtons();
|
||||
@@ -1,6 +1,4 @@
|
||||
#include <cstdio>
|
||||
#include <main_application.hpp>
|
||||
#include "main.hpp"
|
||||
#include <nxst/app/main_application.hpp>
|
||||
|
||||
namespace ui {
|
||||
extern MainApplication *mainApp;
|
||||
@@ -50,11 +48,12 @@ namespace ui {
|
||||
|
||||
void UsersLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) {
|
||||
if (Down & HidNpadButton_Plus) {
|
||||
svcExitProcess();
|
||||
mainApp->Close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Down & HidNpadButton_A) {
|
||||
g_currentUId = Account::ids().at(this->usersMenu->GetSelectedIndex());
|
||||
AccountUid uid = Account::ids().at(this->usersMenu->GetSelectedIndex());
|
||||
|
||||
if (!areTitlesLoaded()) {
|
||||
this->usersMenu->SetVisible(false);
|
||||
@@ -69,8 +68,8 @@ namespace ui {
|
||||
this->usersMenu->SetVisible(true);
|
||||
}
|
||||
|
||||
mainApp->titlesLayout->InitTitles();
|
||||
mainApp->LoadLayout(mainApp->titlesLayout);
|
||||
mainApp->titles_layout->InitTitles(uid);
|
||||
mainApp->LoadLayout(mainApp->titles_layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user