#include #include #include #include #include #include #include #include #include #include #include #ifdef __SWITCH__ #include #include #endif #include #include #include namespace fs = std::filesystem; using path = fs::path; static TransferState g_client_state; bool isClientTransferDone() { return g_client_state.done.load(); } void cancelClientTransfer() { g_client_state.cancelled.store(true); } double getClientProgress() { return g_client_state.progress(); } std::string getClientStatusText() { return g_client_state.getStatus(); } struct ThreadArgs { int sock; fs::path directory; }; 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(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 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; } static void* send_files_thread(void* arg) { ThreadArgs* targs = static_cast(arg); int sock = targs->sock; fs::path cwd = targs->directory; delete targs; for (const auto& entry : fs::recursive_directory_iterator(cwd)) { 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(sock, p)) break; } } // EOF sentinel — server reads this and exits its receive loop cleanly uint32_t sentinel = proto::EOF_SENTINEL; send_all(sock, &sentinel, sizeof(sentinel)); g_client_state.setStatus(""); close(sock); g_client_state.done.store(true); return nullptr; } static int find_server(char* server_ip) { Socket udp(socket(AF_INET, SOCK_DGRAM, 0)); if (!udp.valid()) { std::cerr << "find_server: socket failed" << std::endl; return -1; } 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, msg, strlen(msg), 0, (sockaddr*)&addr, sizeof(addr)) < 0) { std::cerr << "find_server: sendto failed" << std::endl; return -1; } sockaddr_in from{}; socklen_t fromlen = sizeof(from); char buf[256]; ssize_t n = recvfrom(udp, buf, sizeof(buf) - 1, 0, (sockaddr*)&from, &fromlen); if (n < 0) { std::cerr << "find_server: recvfrom failed" << std::endl; return -1; } buf[n] = '\0'; if (strcmp(buf, "SERVER_HERE") != 0) { std::cerr << "find_server: unexpected response: " << buf << std::endl; return -1; } inet_ntop(AF_INET, &from.sin_addr, server_ip, INET_ADDRSTRLEN); return 0; } int transfer_files(fs::path directory) { char server_ip[INET_ADDRSTRLEN]; if (find_server(server_ip) != 0) return -1; Socket tcp(socket(AF_INET, SOCK_STREAM, 0)); if (!tcp.valid()) return -1; 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) return -1; if (connect(tcp, (sockaddr*)&serv, sizeof(serv)) < 0) return -1; 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.reset(); g_client_state.bytes_total.store(total); g_client_state.setStatus("Connecting..."); ThreadArgs* args = new ThreadArgs{tcp.fd, directory}; tcp.release(); // ownership transferred to thread pthread_t thread; if (pthread_create(&thread, nullptr, send_files_thread, args) != 0) { delete args; return -1; } pthread_detach(thread); return 0; } #ifndef __SWITCH__ int main(int argc, char* argv[]) { if (argc < 2) { std::cerr << "Usage: " << argv[0] << " " << std::endl; return 1; } if (transfer_files(fs::path(argv[1])) != 0) return 1; while (!isClientTransferDone()) usleep(16000); return 0; } #endif