phase 5: transfer service
This commit is contained in:
+17
-2
@@ -1,4 +1,5 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
|
.serena
|
||||||
|
|
||||||
# Prerequisites
|
# Prerequisites
|
||||||
*.d
|
*.d
|
||||||
@@ -32,5 +33,19 @@
|
|||||||
*.exe
|
*.exe
|
||||||
*.out
|
*.out
|
||||||
*.app
|
*.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
|
TARGET := NXST
|
||||||
BUILD := build
|
BUILD := build
|
||||||
SOURCES := source lib/Plutonium/source
|
SOURCES := src/app src/domain src/infra/net src/infra/fs src/infra/sys src/service src/ui lib/Plutonium/source
|
||||||
INCLUDES := include include/net lib/Plutonium/include
|
INCLUDES := include lib/Plutonium/include
|
||||||
EXEFS_SRC := exefs_src
|
EXEFS_SRC := exefs_src
|
||||||
APP_TITLE := NXST
|
APP_TITLE := NXST
|
||||||
APP_AUTHOR := DragonSpirit
|
APP_AUTHOR := DragonSpirit
|
||||||
|
|||||||
@@ -7,14 +7,14 @@
|
|||||||
| 0 | Tooling & ground rules | ✅ Done | S (~2h) | `.clang-format`, `.clang-tidy`, `.editorconfig`, `.gitattributes` |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 3 | Directory restructure | ✅ Done | M (~1d) | `src/` + `include/nxst/` layered tree |
|
||||||
| 4 | Make → CMake migration | ☐ Not started | M (~1d) | devkitpro `Switch.cmake` toolchain |
|
| 4 | Make → CMake migration | ✅ Done | M (~1d) | devkitpro `Switch.cmake` toolchain |
|
||||||
| 5 | TransferService extraction | ☐ Not started | L (~2d) | kill globals, sever UI ↔ net coupling |
|
| 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()` |
|
| 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 |
|
| 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 |
|
| 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.
|
**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.
|
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
|
#pragma once
|
||||||
#include <const.h>
|
#include <nxst/ui/const.h>
|
||||||
#include "account.hpp"
|
#include <nxst/domain/account.hpp>
|
||||||
#include "title.hpp"
|
#include <nxst/domain/title.hpp>
|
||||||
#include "util.hpp"
|
#include <nxst/domain/util.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <switch.h>
|
#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;
|
typedef enum { SORT_ALPHA, SORT_LAST_PLAYED, SORT_PLAY_TIME, SORT_MODES_COUNT } sort_t;
|
||||||
|
|
||||||
inline float g_currentTime = 0;
|
inline float g_currentTime = 0;
|
||||||
inline AccountUid g_currentUId;
|
|
||||||
inline bool g_backupScrollEnabled = 0;
|
inline bool g_backupScrollEnabled = 0;
|
||||||
inline bool g_notificationLedAvailable = false;
|
inline bool g_notificationLedAvailable = false;
|
||||||
inline bool g_shouldExitNetworkLoop = false;
|
inline bool g_shouldExitNetworkLoop = false;
|
||||||
@@ -1,21 +1,22 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <pu/Plutonium>
|
#include <pu/Plutonium>
|
||||||
#include <users_layout.hpp>
|
#include <nxst/service/transfer_service.hpp>
|
||||||
#include <titles_layout.hpp>
|
#include <nxst/ui/users_layout.hpp>
|
||||||
|
#include <nxst/ui/titles_layout.hpp>
|
||||||
namespace ui {
|
|
||||||
|
namespace ui {
|
||||||
class MainApplication : public pu::ui::Application {
|
|
||||||
|
class MainApplication : public pu::ui::Application {
|
||||||
public:
|
|
||||||
using Application::Application;
|
public:
|
||||||
PU_SMART_CTOR(MainApplication)
|
using Application::Application;
|
||||||
|
PU_SMART_CTOR(MainApplication)
|
||||||
void OnLoad() override;
|
|
||||||
|
void OnLoad() override;
|
||||||
// Layout instance
|
|
||||||
UsersLayout::Ref usersLayout;
|
UsersLayout::Ref users_layout;
|
||||||
TitlesLayout::Ref titlesLayout;
|
TitlesLayout::Ref titles_layout;
|
||||||
};
|
nxst::TransferService transfer;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
@@ -25,9 +25,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "account.hpp"
|
#include <nxst/domain/account.hpp>
|
||||||
#include "filesystem.hpp"
|
#include <nxst/infra/fs/filesystem.hpp>
|
||||||
#include "io.hpp"
|
#include <nxst/infra/fs/io.hpp>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -25,9 +25,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "account.hpp"
|
#include <nxst/domain/account.hpp>
|
||||||
#include "common.hpp"
|
#include <nxst/domain/common.hpp>
|
||||||
#include "io.hpp"
|
#include <nxst/infra/fs/io.hpp>
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "account.hpp"
|
#include <nxst/domain/account.hpp>
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
|
|
||||||
namespace FileSystem {
|
namespace FileSystem {
|
||||||
@@ -25,10 +25,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "account.hpp"
|
#include <nxst/domain/account.hpp>
|
||||||
#include "directory.hpp"
|
#include <nxst/infra/fs/directory.hpp>
|
||||||
#include "title.hpp"
|
#include <nxst/domain/title.hpp>
|
||||||
#include "util.hpp"
|
#include <nxst/domain/util.hpp>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
#include <sys/stat.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
|
#pragma once
|
||||||
#include <pu/Plutonium>
|
#include <pu/Plutonium>
|
||||||
#include <theme.hpp>
|
#include <nxst/ui/theme.hpp>
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <theme.hpp>
|
#include <nxst/ui/theme.hpp>
|
||||||
|
|
||||||
#define COLOR(hex) pu::ui::Color::FromHex(hex)
|
#define COLOR(hex) pu::ui::Color::FromHex(hex)
|
||||||
#define BACKGROUND_COLOR theme::color::BgBase
|
#define BACKGROUND_COLOR theme::color::BgBase
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <pu/Plutonium>
|
#include <pu/Plutonium>
|
||||||
#include <theme.hpp>
|
#include <nxst/ui/theme.hpp>
|
||||||
#include <ui/ui_context.hpp>
|
#include <nxst/ui/ui_context.hpp>
|
||||||
#include <account.hpp>
|
#include <nxst/domain/account.hpp>
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <pu/Plutonium>
|
#include <pu/Plutonium>
|
||||||
#include <theme.hpp>
|
#include <nxst/ui/theme.hpp>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
#include <pu/Plutonium>
|
#include <pu/Plutonium>
|
||||||
#include <const.h>
|
#include <nxst/ui/const.h>
|
||||||
#include <title.hpp>
|
#include <nxst/domain/title.hpp>
|
||||||
#include <account.hpp>
|
#include <nxst/domain/account.hpp>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <ui/header_bar.hpp>
|
#include <nxst/ui/header_bar.hpp>
|
||||||
#include <ui/hint_bar.hpp>
|
#include <nxst/ui/hint_bar.hpp>
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ namespace ui {
|
|||||||
pu::ui::elm::TextBlock::Ref emptyText;
|
pu::ui::elm::TextBlock::Ref emptyText;
|
||||||
pu::ui::elm::TextBlock::Ref emptySub;
|
pu::ui::elm::TextBlock::Ref emptySub;
|
||||||
|
|
||||||
|
AccountUid current_uid{};
|
||||||
TitlesFocus focus = TitlesFocus::List;
|
TitlesFocus focus = TitlesFocus::List;
|
||||||
TitlesAction action = TitlesAction::Transfer;
|
TitlesAction action = TitlesAction::Transfer;
|
||||||
int lockedListIndex = 0;
|
int lockedListIndex = 0;
|
||||||
@@ -46,7 +48,7 @@ namespace ui {
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
TitlesLayout();
|
TitlesLayout();
|
||||||
void InitTitles();
|
void InitTitles(AccountUid uid);
|
||||||
void LockInput() { m_inputLocked = true; }
|
void LockInput() { m_inputLocked = true; }
|
||||||
void UnlockInput() { m_inputLocked = false; }
|
void UnlockInput() { m_inputLocked = false; }
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <pu/Plutonium>
|
#include <pu/Plutonium>
|
||||||
#include <theme.hpp>
|
#include <nxst/ui/theme.hpp>
|
||||||
#include <util.hpp>
|
#include <nxst/domain/util.hpp>
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
#include <account.hpp>
|
#include <nxst/domain/account.hpp>
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
struct UiContext {
|
struct UiContext {
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#include <pu/Plutonium>
|
#include <pu/Plutonium>
|
||||||
#include <const.h>
|
#include <nxst/ui/const.h>
|
||||||
#include <ui/header_bar.hpp>
|
#include <nxst/ui/header_bar.hpp>
|
||||||
#include <ui/hint_bar.hpp>
|
#include <nxst/ui/hint_bar.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace ui {
|
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,78 +1,82 @@
|
|||||||
#include <main_application.hpp>
|
#include <nxst/app/main_application.hpp>
|
||||||
#include "util.hpp"
|
#include <nxst/domain/util.hpp>
|
||||||
#include "main.hpp"
|
#include <nxst/app/main.hpp>
|
||||||
#include <server.hpp>
|
#include <unistd.h>
|
||||||
#include <client.hpp>
|
|
||||||
#include <unistd.h>
|
namespace ui { extern MainApplication* mainApp; }
|
||||||
|
|
||||||
static int nxlink_sock = -1;
|
static int nxlink_sock = -1;
|
||||||
|
|
||||||
extern "C" void userAppInit() {
|
extern "C" void userAppInit() {
|
||||||
appletInitialize();
|
appletInitialize();
|
||||||
hidInitialize();
|
hidInitialize();
|
||||||
nsInitialize();
|
nsInitialize();
|
||||||
setsysInitialize();
|
setsysInitialize();
|
||||||
setInitialize();
|
setInitialize();
|
||||||
accountInitialize(AccountServiceType_Administrator);
|
accountInitialize(AccountServiceType_Administrator);
|
||||||
pmshellInitialize();
|
pmshellInitialize();
|
||||||
socketInitializeDefault();
|
socketInitializeDefault();
|
||||||
pdmqryInitialize();
|
pdmqryInitialize();
|
||||||
nxlink_sock = nxlinkStdio();
|
nxlink_sock = nxlinkStdio();
|
||||||
printf("userAppInit\n");
|
printf("userAppInit\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void userAppExit() {
|
extern "C" void userAppExit() {
|
||||||
cancelServerTransfer();
|
if (ui::mainApp) {
|
||||||
cancelClientTransfer();
|
ui::mainApp->transfer.cancelReceive();
|
||||||
for (int i = 0; i < 150 && (!isServerWorkersIdle() || !isClientWorkersIdle()); i++) {
|
ui::mainApp->transfer.cancelSend();
|
||||||
usleep(10000);
|
for (int i = 0; i < 150 &&
|
||||||
}
|
(!ui::mainApp->transfer.isReceiveWorkersIdle() ||
|
||||||
if (nxlink_sock != -1) {
|
!ui::mainApp->transfer.isSendWorkersIdle()); i++) {
|
||||||
close(nxlink_sock);
|
usleep(10000);
|
||||||
}
|
}
|
||||||
appletExit();
|
}
|
||||||
hidExit();
|
if (nxlink_sock != -1) {
|
||||||
nsExit();
|
close(nxlink_sock);
|
||||||
setsysExit();
|
}
|
||||||
setExit();
|
appletExit();
|
||||||
accountExit();
|
hidExit();
|
||||||
pmshellExit();
|
nsExit();
|
||||||
socketExit();
|
setsysExit();
|
||||||
pdmqryExit();
|
setExit();
|
||||||
}
|
accountExit();
|
||||||
|
pmshellExit();
|
||||||
// Main entrypoint
|
socketExit();
|
||||||
int main() {
|
pdmqryExit();
|
||||||
Result res = servicesInit();
|
}
|
||||||
if (R_FAILED(res)) {
|
|
||||||
servicesExit();
|
// Main entrypoint
|
||||||
exit(res);
|
int main() {
|
||||||
}
|
Result res = servicesInit();
|
||||||
|
if (R_FAILED(res)) {
|
||||||
printf("main");
|
servicesExit();
|
||||||
|
exit(res);
|
||||||
// First create our renderer, where one can customize SDL or other stuff's
|
}
|
||||||
// initialization.
|
|
||||||
auto renderer_opts = pu::ui::render::RendererInitOptions(
|
printf("main");
|
||||||
SDL_INIT_EVERYTHING, pu::ui::render::RendererHardwareFlags);
|
|
||||||
renderer_opts.UseImage(pu::ui::render::IMGAllFlags);
|
// First create our renderer, where one can customize SDL or other stuff's
|
||||||
renderer_opts.UseAudio(pu::ui::render::MixerAllFlags);
|
// initialization.
|
||||||
renderer_opts.UseTTF();
|
auto renderer_opts = pu::ui::render::RendererInitOptions(
|
||||||
renderer_opts.SetExtraDefaultFontSize(theme::type::Caption);
|
SDL_INIT_EVERYTHING, pu::ui::render::RendererHardwareFlags);
|
||||||
renderer_opts.SetExtraDefaultFontSize(theme::type::Label);
|
renderer_opts.UseImage(pu::ui::render::IMGAllFlags);
|
||||||
renderer_opts.SetExtraDefaultFontSize(theme::type::Body);
|
renderer_opts.UseAudio(pu::ui::render::MixerAllFlags);
|
||||||
renderer_opts.SetExtraDefaultFontSize(theme::type::Title);
|
renderer_opts.UseTTF();
|
||||||
renderer_opts.SetExtraDefaultFontSize(theme::type::Display);
|
renderer_opts.SetExtraDefaultFontSize(theme::type::Caption);
|
||||||
|
renderer_opts.SetExtraDefaultFontSize(theme::type::Label);
|
||||||
auto renderer = pu::ui::render::Renderer::New(renderer_opts);
|
renderer_opts.SetExtraDefaultFontSize(theme::type::Body);
|
||||||
|
renderer_opts.SetExtraDefaultFontSize(theme::type::Title);
|
||||||
// Create our main application from the renderer
|
renderer_opts.SetExtraDefaultFontSize(theme::type::Display);
|
||||||
auto main = ui::MainApplication::New(renderer);
|
|
||||||
|
auto renderer = pu::ui::render::Renderer::New(renderer_opts);
|
||||||
main->Prepare();
|
|
||||||
|
// Create our main application from the renderer
|
||||||
main->Show();
|
auto main = ui::MainApplication::New(renderer);
|
||||||
|
|
||||||
servicesExit();
|
main->Prepare();
|
||||||
return 0;
|
|
||||||
}
|
main->Show();
|
||||||
|
|
||||||
|
servicesExit();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -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.
|
* reasonable ways as different from the original version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "account.hpp"
|
#include <nxst/domain/account.hpp>
|
||||||
#include <main.hpp>
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
|
||||||
@@ -138,5 +137,5 @@ AccountUid Account::selectAccount(void)
|
|||||||
return uid;
|
return uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
return g_currentUId;
|
return AccountUid{};
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
* reasonable ways as different from the original version.
|
* reasonable ways as different from the original version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "common.hpp"
|
#include <nxst/domain/common.hpp>
|
||||||
|
|
||||||
std::string DateTime::timeStr(void)
|
std::string DateTime::timeStr(void)
|
||||||
{
|
{
|
||||||
@@ -24,8 +24,8 @@
|
|||||||
* reasonable ways as different from the original version.
|
* reasonable ways as different from the original version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "title.hpp"
|
#include <nxst/domain/title.hpp>
|
||||||
#include "main.hpp"
|
#include <nxst/app/main.hpp>
|
||||||
|
|
||||||
static std::unordered_map<AccountUid, std::vector<Title>> titles;
|
static std::unordered_map<AccountUid, std::vector<Title>> titles;
|
||||||
static bool s_titlesLoaded = false;
|
static bool s_titlesLoaded = false;
|
||||||
@@ -24,10 +24,10 @@
|
|||||||
* reasonable ways as different from the original version.
|
* reasonable ways as different from the original version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "util.hpp"
|
#include <nxst/domain/util.hpp>
|
||||||
#include <logger.hpp>
|
#include <nxst/infra/sys/logger.hpp>
|
||||||
#include <main_application.hpp>
|
#include <nxst/app/main_application.hpp>
|
||||||
#include "main.hpp"
|
#include <nxst/app/main.hpp>
|
||||||
|
|
||||||
void servicesExit(void)
|
void servicesExit(void)
|
||||||
{
|
{
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
* reasonable ways as different from the original version.
|
* reasonable ways as different from the original version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "directory.hpp"
|
#include <nxst/infra/fs/directory.hpp>
|
||||||
|
|
||||||
Directory::Directory(const std::string& root)
|
Directory::Directory(const std::string& root)
|
||||||
{
|
{
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
* reasonable ways as different from the original version.
|
* 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)
|
Result FileSystem::mount(FsFileSystem* fileSystem, u64 titleID, AccountUid userID)
|
||||||
{
|
{
|
||||||
@@ -24,9 +24,9 @@
|
|||||||
* reasonable ways as different from the original version.
|
* reasonable ways as different from the original version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "io.hpp"
|
#include <nxst/infra/fs/io.hpp>
|
||||||
#include "main.hpp"
|
#include <nxst/app/main.hpp>
|
||||||
#include <logger.hpp>
|
#include <nxst/infra/sys/logger.hpp>
|
||||||
|
|
||||||
bool io::fileExists(const std::string& path)
|
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 <cstdarg>
|
||||||
#include <cstdio>
|
#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 <nxst/app/main_application.hpp>
|
||||||
#include <stdio.h>
|
#include <nxst/domain/util.hpp>
|
||||||
#include <main.hpp>
|
#include <nxst/ui/transfer_overlay.hpp>
|
||||||
#include <const.h>
|
#include <nxst/ui/const.h>
|
||||||
#include <client.hpp>
|
|
||||||
#include <server.hpp>
|
|
||||||
#include <transfer_overlay.hpp>
|
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
extern MainApplication *mainApp;
|
extern MainApplication *mainApp;
|
||||||
@@ -98,24 +95,24 @@ namespace ui {
|
|||||||
this->updateHints();
|
this->updateHints();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TitlesLayout::InitTitles() {
|
void TitlesLayout::InitTitles(AccountUid uid) {
|
||||||
using namespace theme;
|
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;
|
std::vector<pu::ui::elm::MenuItem::Ref>* items;
|
||||||
if (it != this->menuCache.end()) {
|
if (it != this->menuCache.end()) {
|
||||||
items = &it->second;
|
items = &it->second;
|
||||||
} else {
|
} else {
|
||||||
std::vector<pu::ui::elm::MenuItem::Ref> built;
|
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;
|
Title title;
|
||||||
getTitle(title, g_currentUId, i);
|
getTitle(title, uid, i);
|
||||||
auto titleItem = pu::ui::elm::MenuItem::New(title.name().c_str());
|
auto titleItem = pu::ui::elm::MenuItem::New(title.name().c_str());
|
||||||
titleItem->SetColor(color::TextPrimary);
|
titleItem->SetColor(color::TextPrimary);
|
||||||
built.push_back(titleItem);
|
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;
|
items = &inserted.first->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,14 +141,14 @@ namespace ui {
|
|||||||
this->refreshButtons();
|
this->refreshButtons();
|
||||||
this->updateHints();
|
this->updateHints();
|
||||||
|
|
||||||
this->header->SetUser(g_currentUId, Account::username(g_currentUId));
|
this->header->SetUser(uid, Account::username(uid));
|
||||||
}
|
}
|
||||||
|
|
||||||
void TitlesLayout::refreshPanel() {
|
void TitlesLayout::refreshPanel() {
|
||||||
if (this->titlesMenu->GetItems().empty()) return;
|
if (this->titlesMenu->GetItems().empty()) return;
|
||||||
int idx = this->titlesMenu->GetSelectedIndex();
|
int idx = this->titlesMenu->GetSelectedIndex();
|
||||||
Title title;
|
Title title;
|
||||||
getTitle(title, g_currentUId, idx);
|
getTitle(title, this->current_uid, idx);
|
||||||
this->panelTitle->SetText(StringUtils::elide(title.name(), 24));
|
this->panelTitle->SetText(StringUtils::elide(title.name(), 24));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,24 +182,25 @@ namespace ui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TitlesLayout::runTransfer(int index, Title& title) {
|
void TitlesLayout::runTransfer(int index, Title& title) {
|
||||||
|
(void)title;
|
||||||
auto ovl = TransferOverlay::New("Transferring save data...");
|
auto ovl = TransferOverlay::New("Transferring save data...");
|
||||||
this->titlesMenu->SetVisible(false);
|
this->titlesMenu->SetVisible(false);
|
||||||
mainApp->StartOverlay(ovl);
|
mainApp->StartOverlay(ovl);
|
||||||
this->LockInput();
|
this->LockInput();
|
||||||
if (transfer_files(index, g_currentUId) != 0) {
|
if (mainApp->transfer.startSend((size_t)index, this->current_uid) != 0) {
|
||||||
mainApp->EndOverlay();
|
mainApp->EndOverlay();
|
||||||
this->titlesMenu->SetVisible(true);
|
this->titlesMenu->SetVisible(true);
|
||||||
this->UnlockInput();
|
this->UnlockInput();
|
||||||
mainApp->CreateShowDialog("Transfer", "Failed to start transfer.", {"OK"}, true);
|
mainApp->CreateShowDialog("Transfer", "Failed to start transfer.", {"OK"}, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
while (!isClientTransferDone()) {
|
while (!mainApp->transfer.isSendDone()) {
|
||||||
ovl->SetStatus(getClientStatusText());
|
ovl->SetStatus(mainApp->transfer.sendStatusText());
|
||||||
ovl->SetProgressVisible(isClientProgressKnown());
|
ovl->SetProgressVisible(mainApp->transfer.isSendProgressKnown());
|
||||||
ovl->SetProgress(getClientProgress());
|
ovl->SetProgress(mainApp->transfer.sendProgress());
|
||||||
mainApp->CallForRender();
|
mainApp->CallForRender();
|
||||||
if (mainApp->GetButtonsDown() & HidNpadButton_B) {
|
if (mainApp->GetButtonsDown() & HidNpadButton_B) {
|
||||||
cancelClientTransfer();
|
mainApp->transfer.cancelSend();
|
||||||
}
|
}
|
||||||
svcSleepThread(16666666LL);
|
svcSleepThread(16666666LL);
|
||||||
}
|
}
|
||||||
@@ -210,9 +208,9 @@ namespace ui {
|
|||||||
this->titlesMenu->SetVisible(true);
|
this->titlesMenu->SetVisible(true);
|
||||||
this->UnlockInput();
|
this->UnlockInput();
|
||||||
|
|
||||||
if (isClientConnectionFailed()) {
|
if (mainApp->transfer.isSendConnectionFailed()) {
|
||||||
mainApp->CreateShowDialog("Transfer", getClientFailReason(), {"OK"}, true);
|
mainApp->CreateShowDialog("Transfer", mainApp->transfer.sendFailReason(), {"OK"}, true);
|
||||||
} else if (isClientTransferCancelled()) {
|
} else if (mainApp->transfer.isSendCancelled()) {
|
||||||
mainApp->CreateShowDialog("Transfer", "Transfer cancelled.", {"OK"}, true);
|
mainApp->CreateShowDialog("Transfer", "Transfer cancelled.", {"OK"}, true);
|
||||||
} else {
|
} else {
|
||||||
mainApp->CreateShowDialog("Transfer", "Save data sent successfully!", {"OK"}, true);
|
mainApp->CreateShowDialog("Transfer", "Save data sent successfully!", {"OK"}, true);
|
||||||
@@ -220,7 +218,7 @@ namespace ui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TitlesLayout::runReceive(int index, Title& title) {
|
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);
|
mainApp->CreateShowDialog("Receive", "Failed to start receiver.\nCheck network connection.", {"OK"}, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -228,12 +226,12 @@ namespace ui {
|
|||||||
this->titlesMenu->SetVisible(false);
|
this->titlesMenu->SetVisible(false);
|
||||||
mainApp->StartOverlay(ovl);
|
mainApp->StartOverlay(ovl);
|
||||||
this->LockInput();
|
this->LockInput();
|
||||||
while (!isServerTransferDone()) {
|
while (!mainApp->transfer.isReceiveDone()) {
|
||||||
ovl->SetStatus(getServerStatusText());
|
ovl->SetStatus(mainApp->transfer.receiveStatusText());
|
||||||
ovl->SetProgress(getServerProgress());
|
ovl->SetProgress(mainApp->transfer.receiveProgress());
|
||||||
mainApp->CallForRender();
|
mainApp->CallForRender();
|
||||||
if (mainApp->GetButtonsDown() & HidNpadButton_B) {
|
if (mainApp->GetButtonsDown() & HidNpadButton_B) {
|
||||||
cancelServerTransfer();
|
mainApp->transfer.cancelReceive();
|
||||||
}
|
}
|
||||||
svcSleepThread(16666666LL);
|
svcSleepThread(16666666LL);
|
||||||
}
|
}
|
||||||
@@ -241,24 +239,22 @@ namespace ui {
|
|||||||
this->titlesMenu->SetVisible(true);
|
this->titlesMenu->SetVisible(true);
|
||||||
this->UnlockInput();
|
this->UnlockInput();
|
||||||
|
|
||||||
if (isServerTransferCancelled()) {
|
if (mainApp->transfer.isReceiveCancelled()) {
|
||||||
mainApp->CreateShowDialog("Receive", "Transfer cancelled.", {"OK"}, true);
|
mainApp->CreateShowDialog("Receive", "Transfer cancelled.", {"OK"}, true);
|
||||||
return;
|
} else if (mainApp->transfer.restoreSucceeded()) {
|
||||||
}
|
|
||||||
auto restoreResult = io::restore(index, g_currentUId, 0, title.name());
|
|
||||||
if (std::get<0>(restoreResult)) {
|
|
||||||
mainApp->CreateShowDialog("Receive", "Save data received and restored successfully!", {"OK"}, true);
|
mainApp->CreateShowDialog("Receive", "Save data received and restored successfully!", {"OK"}, true);
|
||||||
} else {
|
} 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 TitlesLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) {
|
||||||
|
(void)Up; (void)Held; (void)Pos;
|
||||||
if (m_inputLocked) return;
|
if (m_inputLocked) return;
|
||||||
|
|
||||||
if (Down & HidNpadButton_Plus) {
|
if (Down & HidNpadButton_Plus) {
|
||||||
cancelClientTransfer();
|
mainApp->transfer.cancelSend();
|
||||||
cancelServerTransfer();
|
mainApp->transfer.cancelReceive();
|
||||||
mainApp->Close();
|
mainApp->Close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -266,7 +262,7 @@ namespace ui {
|
|||||||
if (focus == TitlesFocus::List) {
|
if (focus == TitlesFocus::List) {
|
||||||
if (Down & HidNpadButton_B) {
|
if (Down & HidNpadButton_B) {
|
||||||
this->header->SetUser(std::nullopt, "");
|
this->header->SetUser(std::nullopt, "");
|
||||||
mainApp->LoadLayout(mainApp->usersLayout);
|
mainApp->LoadLayout(mainApp->users_layout);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (Down & HidNpadButton_A) {
|
if (Down & HidNpadButton_A) {
|
||||||
@@ -298,7 +294,7 @@ namespace ui {
|
|||||||
if (Down & HidNpadButton_A) {
|
if (Down & HidNpadButton_A) {
|
||||||
int idx = this->titlesMenu->GetSelectedIndex();
|
int idx = this->titlesMenu->GetSelectedIndex();
|
||||||
Title title;
|
Title title;
|
||||||
getTitle(title, g_currentUId, idx);
|
getTitle(title, this->current_uid, idx);
|
||||||
TitlesAction chosen = action;
|
TitlesAction chosen = action;
|
||||||
this->focus = TitlesFocus::List;
|
this->focus = TitlesFocus::List;
|
||||||
this->refreshButtons();
|
this->refreshButtons();
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
#include <cstdio>
|
#include <nxst/app/main_application.hpp>
|
||||||
#include <main_application.hpp>
|
|
||||||
#include "main.hpp"
|
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
extern MainApplication *mainApp;
|
extern MainApplication *mainApp;
|
||||||
@@ -50,11 +48,12 @@ namespace ui {
|
|||||||
|
|
||||||
void UsersLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) {
|
void UsersLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) {
|
||||||
if (Down & HidNpadButton_Plus) {
|
if (Down & HidNpadButton_Plus) {
|
||||||
svcExitProcess();
|
mainApp->Close();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Down & HidNpadButton_A) {
|
if (Down & HidNpadButton_A) {
|
||||||
g_currentUId = Account::ids().at(this->usersMenu->GetSelectedIndex());
|
AccountUid uid = Account::ids().at(this->usersMenu->GetSelectedIndex());
|
||||||
|
|
||||||
if (!areTitlesLoaded()) {
|
if (!areTitlesLoaded()) {
|
||||||
this->usersMenu->SetVisible(false);
|
this->usersMenu->SetVisible(false);
|
||||||
@@ -69,8 +68,8 @@ namespace ui {
|
|||||||
this->usersMenu->SetVisible(true);
|
this->usersMenu->SetVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
mainApp->titlesLayout->InitTitles();
|
mainApp->titles_layout->InitTitles(uid);
|
||||||
mainApp->LoadLayout(mainApp->titlesLayout);
|
mainApp->LoadLayout(mainApp->titles_layout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user