Last active
November 6, 2025 10:05
-
-
Save sloev/539cc48838f41167b811027ee5ada7fa to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // md5.hpp | |
| // --------------------------------------------------------------- | |
| // Header-only MD5 implementation (public domain) | |
| // Source: https://github.com/Chocobo1/Hash/blob/master/src/md5.h | |
| // --------------------------------------------------------------- | |
| #ifndef MD5_HPP | |
| #define MD5_HPP | |
| #include <array> | |
| #include <cstdint> | |
| #include <fstream> | |
| #include <iomanip> | |
| #include <sstream> | |
| #include <string> | |
| namespace md5_impl | |
| { | |
| constexpr std::array<uint32_t, 64> k = { { | |
| 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, | |
| 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, | |
| 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, | |
| 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, | |
| 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, | |
| 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, | |
| 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, | |
| 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, | |
| 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, | |
| 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, | |
| 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, | |
| 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, | |
| 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, | |
| 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, | |
| 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, | |
| 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 | |
| } }; | |
| constexpr std::array<uint32_t, 64> r = { { | |
| 7,12,17,22, 7,12,17,22, 7,12,17,22, 7,12,17,22, | |
| 5, 9,14,20, 5, 9,14,20, 5, 9,14,20, 5, 9,14,20, | |
| 4,11,16,23, 4,11,16,23, 4,11,16,23, 4,11,16,23, | |
| 6,10,15,21, 6,10,15,21, 6,10,15,21, 6,10,15,21 | |
| } }; | |
| inline uint32_t leftRotate(uint32_t v, int s) noexcept | |
| { | |
| return (v << s) | (v >> (32 - s)); | |
| } | |
| inline void transform(uint32_t state[4], const uint8_t block[64]) noexcept | |
| { | |
| uint32_t a = state[0], b = state[1], c = state[2], d = state[3]; | |
| for (std::size_t i = 0; i < 64; ++i) | |
| { | |
| uint32_t f, g; | |
| if (i < 16) { f = (b & c) | (~b & d); g = i; } | |
| else if (i < 32) { f = (d & b) | (~d & c); g = (5 * i + 1) % 16; } | |
| else if (i < 48) { f = b ^ c ^ d; g = (3 * i + 5) % 16; } | |
| else { f = c ^ (b | ~d); g = (7 * i) % 16; } | |
| uint32_t temp = d; | |
| d = c; | |
| c = b; | |
| b = b + leftRotate(a + f + k[i] + block[g], r[i]); | |
| a = temp; | |
| } | |
| state[0] += a; state[1] += b; state[2] += c; state[3] += d; | |
| } | |
| } // namespace md5_impl | |
| class MD5 | |
| { | |
| public: | |
| MD5() noexcept { init(); } | |
| void init() noexcept | |
| { | |
| m_state = { {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476} }; | |
| m_bitCount = 0; | |
| m_buffer.fill(0); | |
| } | |
| void accumulate(const void* data, std::size_t size) | |
| { | |
| const uint8_t* ptr = static_cast<const uint8_t*>(data); | |
| std::size_t idx = m_bitCount / 8 % 64; | |
| m_bitCount += static_cast<uint64_t>(size) * 8; | |
| std::size_t part = 64 - idx; | |
| std::size_t i = 0; | |
| if (size >= part) | |
| { | |
| std::copy(ptr, ptr + part, m_buffer.begin() + idx); | |
| md5_impl::transform(m_state.data(), m_buffer.data()); | |
| for (i = part; i + 63 < size; i += 64) | |
| md5_impl::transform(m_state.data(), ptr + i); | |
| idx = 0; | |
| } | |
| else { i = 0; } | |
| std::copy(ptr + i, ptr + size, m_buffer.begin() + idx); | |
| } | |
| void accumulate_from_file(const std::string& filename) | |
| { | |
| std::ifstream f(filename, std::ios::binary); | |
| if (!f) return; | |
| char buf[8192]; | |
| while (f.read(buf, sizeof(buf))) | |
| accumulate(buf, f.gcount()); | |
| if (f.gcount()) | |
| accumulate(buf, f.gcount()); | |
| } | |
| std::array<uint8_t, 16> finalise() | |
| { | |
| std::size_t idx = m_bitCount / 8 % 64; | |
| m_buffer[idx++] = 0x80; | |
| if (idx > 56) | |
| { | |
| std::fill(m_buffer.begin() + idx, m_buffer.end(), 0); | |
| md5_impl::transform(m_state.data(), m_buffer.data()); | |
| idx = 0; | |
| } | |
| std::fill(m_buffer.begin() + idx, m_buffer.end() - 8, 0); | |
| uint64_t bits = m_bitCount; | |
| for (int i = 0; i < 8; ++i) | |
| m_buffer[m_buffer.size() - 1 - i] = static_cast<uint8_t>(bits >> (i * 8)); | |
| md5_impl::transform(m_state.data(), m_buffer.data()); | |
| std::array<uint8_t, 16> digest{}; | |
| for (std::size_t i = 0; i < 4; ++i) | |
| for (std::size_t j = 0; j < 4; ++j) | |
| digest[i * 4 + j] = static_cast<uint8_t>((m_state[i] >> (j * 8)) & 0xFF); | |
| init(); // reset for next use | |
| return digest; | |
| } | |
| private: | |
| std::array<uint32_t, 4> m_state; | |
| std::array<uint8_t, 64> m_buffer{}; | |
| uint64_t m_bitCount = 0; | |
| }; | |
| // --------------------------------------------------------------------- | |
| // Helper – MD5 hex string for a file | |
| // --------------------------------------------------------------------- | |
| inline std::string compute_md5(const std::string& filepath) | |
| { | |
| if (!std::filesystem::is_regular_file(filepath)) | |
| return {}; | |
| MD5 md5; | |
| md5.accumulate_from_file(filepath); | |
| auto digest = md5.finalise(); | |
| std::ostringstream oss; | |
| oss << std::hex << std::setfill('0'); | |
| for (uint8_t b : digest) | |
| oss << std::setw(2) << static_cast<unsigned>(b); | |
| return oss.str(); | |
| } | |
| #endif // MD5_HPP |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // tcp_file_transfer.hpp | |
| // --------------------------------------------------------------- | |
| // Minimal single-header TCP file server + client (C++17) | |
| // --------------------------------------------------------------- | |
| // Features | |
| // • Server: list directory, send file, receive file | |
| // • Client: list remote dir, download file, upload file | |
| // • Cross-platform (Windows / POSIX) | |
| // • No external dependencies (uses only <winsock2.h> on Windows) | |
| // • Integrated with openFrameworks ofThread for multi-threading | |
| // --------------------------------------------------------------- | |
| #ifndef TCP_FILE_TRANSFER_HPP | |
| #define TCP_FILE_TRANSFER_HPP | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <cstdio> | |
| #include <cstring> | |
| #include <string> | |
| #include <vector> | |
| #include <array> | |
| #include <filesystem> | |
| #include <iostream> | |
| #include <sstream> | |
| #include <atomic> | |
| // openFrameworks integration (ofThread from ofThread.h/cpp) | |
| // ---------------------------------------------------------- | |
| // Paste the full ofThread.h content here if needed, but assuming OF is installed. | |
| // For standalone, include the ofThread implementation below. | |
| // Minimal ofThread implementation extracted/adapted from openFrameworks | |
| // (Based on https://github.com/openframeworks/openFrameworks/blob/master/libs/openFrameworks/utils/ofThread.cpp) | |
| // This is a simplified version for this header; full OF has more features. | |
| #include <ofThread.h> | |
| #ifdef _WIN32 | |
| #define WIN32_LEAN_AND_MEAN | |
| #include <windows.h> | |
| #include <winsock2.h> | |
| #include <ws2tcpip.h> | |
| #pragma comment(lib, "Ws2_32.lib") | |
| using socklen_t = int; | |
| #else | |
| #include <unistd.h> | |
| #include <sys/types.h> | |
| #include <sys/socket.h> | |
| #include <netinet/in.h> | |
| #include <arpa/inet.h> | |
| #include <netdb.h> | |
| #define SOCKET int | |
| #define INVALID_SOCKET -1 | |
| #define SOCKET_ERROR -1 | |
| #define closesocket close | |
| #endif | |
| namespace tcp_file | |
| { | |
| namespace fs = std::filesystem; | |
| // --------------------------------------------------------------------- | |
| // Helper: initialize Winsock (Windows only) | |
| inline bool init_winsock() | |
| { | |
| #ifdef _WIN32 | |
| WSADATA wsa; | |
| return WSAStartup(MAKEWORD(2, 2), &wsa) == 0; | |
| #else | |
| return true; | |
| #endif | |
| } | |
| // --------------------------------------------------------------------- | |
| // Helper: clean up Winsock | |
| inline void cleanup_winsock() | |
| { | |
| #ifdef _WIN32 | |
| WSACleanup(); | |
| #endif | |
| } | |
| // --------------------------------------------------------------------- | |
| // Helper: send exactly N bytes | |
| inline bool send_all(SOCKET s, const void *data, std::size_t len) | |
| { | |
| const char *ptr = static_cast<const char *>(data); | |
| while (len > 0) | |
| { | |
| int sent = ::send(s, ptr, static_cast<int>(len), 0); | |
| if (sent == SOCKET_ERROR) | |
| return false; | |
| ptr += sent; | |
| len -= sent; | |
| } | |
| return true; | |
| } | |
| // --------------------------------------------------------------------- | |
| // Helper: receive exactly N bytes | |
| inline bool recv_all(SOCKET s, void *data, std::size_t len) | |
| { | |
| char *ptr = static_cast<char *>(data); | |
| while (len > 0) | |
| { | |
| int rcvd = ::recv(s, ptr, static_cast<int>(len), 0); | |
| if (rcvd <= 0) | |
| return false; | |
| ptr += rcvd; | |
| len -= rcvd; | |
| } | |
| return true; | |
| } | |
| // --------------------------------------------------------------------- | |
| // Protocol constants | |
| constexpr uint8_t CMD_LIST = 1; | |
| constexpr uint8_t CMD_GET = 2; | |
| constexpr uint8_t CMD_PUT = 3; | |
| constexpr uint8_t CMD_DELETE = 4; // NEW | |
| constexpr uint8_t CMD_OK = 200; | |
| constexpr uint8_t CMD_ERR = 255; | |
| // ------------------------------------------------------------ | |
| // Add after Client class (inside namespace tcp_file) | |
| struct SyncStatus | |
| { | |
| enum State | |
| { | |
| Connecting, | |
| Listing, | |
| Uploading, | |
| Deleting, | |
| Done, | |
| Error | |
| }; | |
| State state; | |
| std::string host; | |
| uint16_t port; | |
| std::string filename; | |
| uint64_t bytes = 0; | |
| uint64_t total = 0; | |
| std::string message; | |
| float percent() const { return total > 0 ? (float)bytes / total : 0.0f; } | |
| }; | |
| // --------------------------------------------------------------------- | |
| // Server implementation using ofThread | |
| class Server : public ofThread | |
| { | |
| public: | |
| explicit Server(uint16_t port, const std::string &root_dir = ".") | |
| : m_port(port), m_root(fs::canonical(root_dir)) | |
| { | |
| if (!init_winsock()) | |
| { | |
| throw std::runtime_error("Failed to initialize Winsock"); | |
| } | |
| } | |
| ~Server() | |
| { | |
| stopThread(); | |
| cleanup_winsock(); | |
| } | |
| void threadedFunction() override | |
| { | |
| SOCKET listen_sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); | |
| if (listen_sock == INVALID_SOCKET) | |
| { | |
| ofLogError("tcp_file_server") << "socket() failed"; | |
| return; | |
| } | |
| sockaddr_in addr{}; | |
| addr.sin_family = AF_INET; | |
| addr.sin_port = htons(m_port); | |
| addr.sin_addr.s_addr = INADDR_ANY; | |
| if (::bind(listen_sock, (sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR) | |
| { | |
| closesocket(listen_sock); | |
| ofLogError("tcp_file_server") << "bind() failed"; | |
| return; | |
| } | |
| if (::listen(listen_sock, SOMAXCONN) == SOCKET_ERROR) | |
| { | |
| closesocket(listen_sock); | |
| ofLogError("tcp_file_server") << "listen() failed"; | |
| return; | |
| } | |
| ofLogNotice("tcp_file_server") << "Server listening on port " << m_port | |
| << " (root: " << m_root << ")"; | |
| while (isThreadRunning()) | |
| { | |
| sockaddr_in client_addr{}; | |
| socklen_t client_len = sizeof(client_addr); | |
| SOCKET client = ::accept(listen_sock, (sockaddr *)&client_addr, &client_len); | |
| if (client == INVALID_SOCKET) | |
| { | |
| if (isThreadRunning()) | |
| ofLogError("tcp_file_server") << "accept() error"; | |
| continue; | |
| } | |
| // Spawn new thread for each client using ofThread | |
| ClientHandler *handler = new ClientHandler(client, m_root); | |
| handler->startThread(); // no arguments | |
| } | |
| closesocket(listen_sock); | |
| } | |
| void start() | |
| { | |
| startThread(); | |
| } | |
| void stop() | |
| { | |
| stopThread(); | |
| } | |
| private: | |
| // Client handler as separate ofThread | |
| class ClientHandler : public ofThread | |
| { | |
| public: | |
| ClientHandler(SOCKET sock, const fs::path &root) : m_sock(sock), m_root(root) {} | |
| ~ClientHandler() | |
| { | |
| if (m_sock != INVALID_SOCKET) | |
| closesocket(m_sock); | |
| } | |
| void threadedFunction() override | |
| { | |
| while (true) | |
| { | |
| uint8_t cmd = 0; | |
| if (!recv_all(m_sock, &cmd, 1)) | |
| break; | |
| if (cmd == CMD_LIST) | |
| { | |
| list_directory(m_sock); | |
| } | |
| else if (cmd == CMD_GET) | |
| { | |
| std::string rel; | |
| if (!recv_string(m_sock, rel)) | |
| break; | |
| send_file(m_sock, rel, m_root); | |
| } | |
| else if (cmd == CMD_PUT) | |
| { | |
| std::string rel; | |
| if (!recv_string(m_sock, rel)) | |
| break; | |
| receive_file(m_sock, rel, m_root); | |
| } | |
| // Inside ClientHandler::threadedFunction() loop | |
| else if (cmd == CMD_DELETE) | |
| { | |
| std::string rel; | |
| if (!recv_string(m_sock, rel)) | |
| break; | |
| delete_file(m_sock, rel, m_root, m_parent); | |
| } | |
| else | |
| { | |
| break; | |
| } | |
| } | |
| delete this; | |
| } | |
| private: | |
| void list_directory(SOCKET sock) | |
| { | |
| std::ostringstream oss; | |
| for (const auto &entry : fs::directory_iterator(m_root)) | |
| { | |
| oss << (entry.is_directory() ? "d" : "-") | |
| << entry.path().filename().string() << '\n'; | |
| } | |
| std::string data = oss.str(); | |
| uint32_t sz = htonl(static_cast<uint32_t>(data.size())); | |
| send_all(sock, &CMD_OK, 1); | |
| send_all(sock, &sz, 4); | |
| send_all(sock, data.data(), data.size()); | |
| } | |
| void send_file(SOCKET sock, const std::string &rel_path, const fs::path &root) | |
| { | |
| fs::path full = safe_path(rel_path, root); | |
| if (!fs::exists(full) || !fs::is_regular_file(full)) | |
| { | |
| send_error(sock, "File not found"); | |
| return; | |
| } | |
| uint64_t fsize = fs::file_size(full); | |
| uint64_t net_size = hto64(fsize); | |
| send_all(sock, &CMD_OK, 1); | |
| send_all(sock, &net_size, 8); | |
| FILE *fp = fopen(full.string().c_str(), "rb"); | |
| if (!fp) | |
| { | |
| send_error(sock, "Cannot open file"); | |
| return; | |
| } | |
| std::array<char, 8192> buf{}; | |
| size_t read; | |
| while ((read = fread(buf.data(), 1, buf.size(), fp)) > 0) | |
| { | |
| if (!send_all(sock, buf.data(), read)) | |
| break; | |
| } | |
| fclose(fp); | |
| } | |
| // New method in ClientHandler | |
| void delete_file(SOCKET sock, const std::string &rel_path, const fs::path &root, Server *parent) | |
| { | |
| fs::path full = safe_path(rel_path, root); | |
| if (!fs::exists(full)) | |
| { | |
| send_error(sock, "File not found"); | |
| return; | |
| } | |
| if (!fs::is_regular_file(full)) | |
| { | |
| send_error(sock, "Not a regular file"); | |
| return; | |
| } | |
| std::error_code ec; | |
| fs::remove(full, ec); | |
| if (ec) | |
| { | |
| send_error(sock, "Delete failed: " + ec.message()); | |
| return; | |
| } | |
| send_all(sock, &CMD_OK, 1); | |
| } | |
| void receive_file(SOCKET sock, const std::string &rel_path, const fs::path &root) | |
| { | |
| fs::path full = safe_path(rel_path, root); | |
| if (full.has_parent_path() && !fs::exists(full.parent_path())) | |
| { | |
| fs::create_directories(full.parent_path()); | |
| } | |
| uint64_t fsize = 0; | |
| if (!recv_all(sock, &fsize, 8)) | |
| { | |
| send_error(sock, "recv size failed"); | |
| return; | |
| } | |
| fsize = ntoh64(fsize); | |
| FILE *fp = fopen(full.string().c_str(), "wb"); | |
| if (!fp) | |
| { | |
| send_error(sock, "Cannot create file"); | |
| return; | |
| } | |
| send_all(sock, &CMD_OK, 1); | |
| std::array<char, 8192> buf{}; | |
| uint64_t remaining = fsize; | |
| while (remaining > 0) | |
| { | |
| size_t to_read = static_cast<size_t>(std::min<uint64_t>(remaining, buf.size())); | |
| if (!recv_all(sock, buf.data(), to_read)) | |
| break; | |
| fwrite(buf.data(), 1, to_read, fp); | |
| remaining -= to_read; | |
| } | |
| fclose(fp); | |
| } | |
| fs::path safe_path(const std::string &rel, const fs::path &root) const | |
| { | |
| fs::path p = fs::path(rel).lexically_normal(); | |
| if (p.is_absolute()) | |
| p = p.lexically_relative("/"); | |
| return fs::canonical(root / p); | |
| } | |
| bool recv_string(SOCKET sock, std::string &out) | |
| { | |
| uint16_t len = 0; | |
| if (!recv_all(sock, &len, 2)) | |
| return false; | |
| len = ntohs(len); | |
| out.resize(len); | |
| return recv_all(sock, out.data(), len); | |
| } | |
| void send_error(SOCKET sock, const std::string &msg) | |
| { | |
| uint16_t len = htons(static_cast<uint16_t>(msg.size())); | |
| send_all(sock, &CMD_ERR, 1); | |
| send_all(sock, &len, 2); | |
| send_all(sock, msg.data(), msg.size()); | |
| } | |
| static uint64_t hto64(uint64_t v) | |
| { | |
| return ((v & 0xFFULL) << 56) | | |
| ((v & 0xFF00ULL) << 40) | | |
| ((v & 0xFF0000ULL) << 24) | | |
| ((v & 0xFF000000ULL) << 8) | | |
| ((v & 0xFF00000000ULL) >> 8) | | |
| ((v & 0xFF0000000000ULL) >> 24) | | |
| ((v & 0xFF000000000000ULL) >> 40) | | |
| ((v & 0xFF00000000000000ULL) >> 56); | |
| } | |
| static uint64_t ntoh64(uint64_t v) { return hto64(v); } | |
| SOCKET m_sock; | |
| fs::path m_root; | |
| }; | |
| uint16_t m_port = 0; | |
| fs::path m_root; | |
| }; | |
| // --------------------------------------------------------------------- | |
| // Client implementation (non-threaded, but can be used in OF threads) | |
| class Client | |
| { | |
| public: | |
| Client(const std::string &host, uint16_t port) : m_host(host), m_port(port) | |
| { | |
| if (!init_winsock()) | |
| throw std::runtime_error("Winsock init failed"); | |
| } | |
| ~Client() | |
| { | |
| disconnect(); | |
| cleanup_winsock(); | |
| } | |
| // In Client class (public) | |
| bool remove(const std::string &remote) | |
| { | |
| if (!send_cmd(CMD_DELETE, remote)) | |
| return false; | |
| uint8_t status = 0; | |
| if (!recv_all(m_sock, &status, 1)) | |
| return false; | |
| return status == CMD_OK; | |
| } | |
| bool connect() | |
| { | |
| disconnect(); | |
| m_sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); | |
| if (m_sock == INVALID_SOCKET) | |
| return false; | |
| addrinfo hints{}, *res = nullptr; | |
| hints.ai_family = AF_INET; | |
| hints.ai_socktype = SOCK_STREAM; | |
| if (getaddrinfo(m_host.c_str(), std::to_string(m_port).c_str(), &hints, &res) != 0) | |
| return false; | |
| bool ok = (::connect(m_sock, res->ai_addr, static_cast<int>(res->ai_addrlen)) != SOCKET_ERROR); | |
| freeaddrinfo(res); | |
| if (!ok) | |
| { | |
| closesocket(m_sock); | |
| m_sock = INVALID_SOCKET; | |
| } | |
| return ok; | |
| } | |
| void disconnect() | |
| { | |
| if (m_sock != INVALID_SOCKET) | |
| { | |
| closesocket(m_sock); | |
| m_sock = INVALID_SOCKET; | |
| } | |
| } | |
| // ----- LIST ----- | |
| std::string list() | |
| { | |
| if (!send_cmd(CMD_LIST)) | |
| return ""; | |
| uint8_t status = 0; | |
| if (!recv_all(m_sock, &status, 1) || status != CMD_OK) | |
| return ""; | |
| uint32_t sz = 0; | |
| if (!recv_all(m_sock, &sz, 4)) | |
| return ""; | |
| sz = ntohl(sz); | |
| std::string data(sz, '\0'); | |
| if (!recv_all(m_sock, data.data(), sz)) | |
| return ""; | |
| return data; | |
| } | |
| // ----- DOWNLOAD ----- | |
| bool download(const std::string &remote, const std::string &local) | |
| { | |
| if (!send_cmd(CMD_GET, remote)) | |
| return false; | |
| uint8_t status = 0; | |
| if (!recv_all(m_sock, &status, 1) || status != CMD_OK) | |
| return false; | |
| uint64_t fsize = 0; | |
| if (!recv_all(m_sock, &fsize, 8)) | |
| return false; | |
| fsize = ntoh64(fsize); | |
| FILE *fp = fopen(local.c_str(), "wb"); | |
| if (!fp) | |
| return false; | |
| std::array<char, 8192> buf{}; | |
| uint64_t remaining = fsize; | |
| while (remaining > 0) | |
| { | |
| size_t chunk = static_cast<size_t>(std::min<uint64_t>(remaining, buf.size())); | |
| if (!recv_all(m_sock, buf.data(), chunk)) | |
| { | |
| fclose(fp); | |
| return false; | |
| } | |
| fwrite(buf.data(), 1, chunk, fp); | |
| remaining -= chunk; | |
| } | |
| fclose(fp); | |
| return true; | |
| } | |
| // ----- UPLOAD ----- | |
| bool upload(const std::string &local, const std::string &remote) | |
| { | |
| if (!fs::exists(local) || !fs::is_regular_file(local)) | |
| return false; | |
| if (!send_cmd(CMD_PUT, remote)) | |
| return false; | |
| uint64_t fsize = fs::file_size(local); | |
| uint64_t net_size = hto64(fsize); | |
| if (!send_all(m_sock, &net_size, 8)) | |
| return false; | |
| uint8_t ok = 0; | |
| if (!recv_all(m_sock, &ok, 1) || ok != CMD_OK) | |
| return false; | |
| FILE *fp = fopen(local.c_str(), "rb"); | |
| if (!fp) | |
| return false; | |
| std::array<char, 8192> buf{}; | |
| size_t read; | |
| while ((read = fread(buf.data(), 1, buf.size(), fp)) > 0) | |
| { | |
| if (!send_all(m_sock, buf.data(), read)) | |
| { | |
| fclose(fp); | |
| return false; | |
| } | |
| } | |
| fclose(fp); | |
| return true; | |
| } | |
| private: | |
| bool send_cmd(uint8_t cmd, const std::string &arg = "") | |
| { | |
| if (!send_all(m_sock, &cmd, 1)) | |
| return false; | |
| if (!arg.empty()) | |
| { | |
| uint16_t len = htons(static_cast<uint16_t>(arg.size())); | |
| if (!send_all(m_sock, &len, 2)) | |
| return false; | |
| if (!send_all(m_sock, arg.data(), arg.size())) | |
| return false; | |
| } | |
| return true; | |
| } | |
| static uint64_t hto64(uint64_t v) | |
| { | |
| return ((v & 0xFFULL) << 56) | | |
| ((v & 0xFF00ULL) << 40) | | |
| ((v & 0xFF0000ULL) << 24) | | |
| ((v & 0xFF000000ULL) << 8) | | |
| ((v & 0xFF00000000ULL) >> 8) | | |
| ((v & 0xFF0000000000ULL) >> 24) | | |
| ((v & 0xFF000000000000ULL) >> 40) | | |
| ((v & 0xFF00000000000000ULL) >> 56); | |
| } | |
| static uint64_t ntoh64(uint64_t v) { return hto64(v); } | |
| std::string m_host; | |
| uint16_t m_port; | |
| SOCKET m_sock = INVALID_SOCKET; | |
| }; | |
| // Threaded Sync Client | |
| class SyncClient : public ofThread | |
| { | |
| public: | |
| struct Target | |
| { | |
| std::string host; | |
| uint16_t port; | |
| }; | |
| ofEvent<SyncStatus> syncEvent; | |
| SyncClient(const std::vector<Target> &targets, const std::string &local_root = ".") | |
| : m_targets(targets), m_local_root(fs::canonical(local_root)) {} | |
| void threadedFunction() override | |
| { | |
| std::vector<std::unique_ptr<SyncTask>> tasks; | |
| for (const auto &t : m_targets) | |
| { | |
| auto task = std::make_unique<SyncTask>(t.host, t.port, m_local_root, this); | |
| task->startThread(); | |
| tasks.push_back(std::move(task)); | |
| } | |
| // Wait for all | |
| for (auto &t : tasks) | |
| { | |
| t->waitForThread(); | |
| } | |
| SyncStatus done; | |
| done.state = SyncStatus::Done; | |
| done.message = "All servers synchronized"; | |
| ofNotifyEvent(syncEvent, done, this); | |
| } | |
| private: | |
| class SyncTask : public ofThread | |
| { | |
| public: | |
| SyncTask(const std::string &host, uint16_t port, const fs::path &local_root, SyncClient *parent) | |
| : m_host(host), m_port(port), m_local_root(local_root), m_parent(parent) {} | |
| void threadedFunction() override | |
| { | |
| Client client(m_host, m_port); | |
| notify(SyncStatus::Connecting, "Connecting..."); | |
| if (!client.connect()) | |
| { | |
| notify(SyncStatus::Error, "Failed to connect"); | |
| return; | |
| } | |
| notify(SyncStatus::Connecting, "Connected"); | |
| // Get local file set | |
| std::set<std::string> local_files; | |
| for (const auto &entry : fs::directory_iterator(m_local_root)) | |
| { | |
| if (entry.is_regular_file()) | |
| { | |
| local_files.insert(entry.path().filename().string()); | |
| } | |
| } | |
| bool changed = true; | |
| int attempts = 0; | |
| const int max_attempts = 10; | |
| while (changed && attempts++ < max_attempts && isThreadRunning()) | |
| { | |
| changed = false; | |
| // Get remote list | |
| std::string listing = client.list(); | |
| if (listing.empty()) | |
| { | |
| notify(SyncStatus::Error, "Failed to list remote"); | |
| return; | |
| } | |
| std::set<std::string> remote_files; | |
| std::istringstream iss(listing); | |
| std::string line; | |
| while (std::getline(iss, line)) | |
| { | |
| if (line.empty()) | |
| continue; | |
| char type = line[0]; | |
| std::string name = line.substr(1); | |
| if (type == '-') | |
| remote_files.insert(name); | |
| } | |
| // Upload missing | |
| for (const auto &f : local_files) | |
| { | |
| if (remote_files.count(f) == 0) | |
| { | |
| std::string local_path = (m_local_root / f).string(); | |
| notify(SyncStatus::Uploading, f, 0, 0); | |
| if (client.upload(local_path, f)) | |
| { | |
| changed = true; | |
| notify(SyncStatus::Uploading, f, 1, 1); | |
| } | |
| else | |
| { | |
| notify(SyncStatus::Error, "Upload failed: " + f); | |
| } | |
| } | |
| } | |
| // Delete extra | |
| for (const auto &f : remote_files) | |
| { | |
| if (local_files.count(f) == 0) | |
| { | |
| notify(SyncStatus::Deleting, f); | |
| if (client.remove(f)) | |
| { | |
| changed = true; | |
| } | |
| else | |
| { | |
| notify(SyncStatus::Error, "Delete failed: " + f); | |
| } | |
| } | |
| } | |
| if (changed) | |
| { | |
| std::this_thread::sleep_for(std::chrono::milliseconds(500)); | |
| } | |
| } | |
| notify(SyncStatus::Done, attempts <= max_attempts ? "Synced" : "Max attempts exceeded"); | |
| } | |
| private: | |
| void notify(SyncStatus::State state, const std::string &msg, | |
| uint64_t bytes = 0, uint64_t total = 0, const std::string &filename = "") | |
| { | |
| SyncStatus s; | |
| s.state = state; | |
| s.host = m_host; | |
| s.port = m_port; | |
| s.filename = filename.empty() ? msg : filename; | |
| s.message = msg; | |
| s.bytes = bytes; | |
| s.total = total; | |
| ofNotifyEvent(m_parent->syncEvent, s, m_parent); | |
| } | |
| std::string m_host; | |
| uint16_t m_port; | |
| fs::path m_local_root; | |
| SyncClient *m_parent; | |
| }; | |
| std::vector<Target> m_targets; | |
| fs::path m_local_root; | |
| }; | |
| } // namespace tcp_file | |
| // --------------------------------------------------------------- | |
| // Example usage with openFrameworks (uncomment to test) | |
| // Assume in an ofApp or similar: | |
| /* | |
| void ofApp::setup() { | |
| using namespace tcp_file; | |
| srv = std::make_unique<Server>(8080, "."); | |
| srv->startThread(); | |
| } | |
| void ofApp::update() { | |
| // Client usage in update or elsewhere | |
| using namespace tcp_file; | |
| static Client cli("127.0.0.1", 8080); | |
| static bool connected = false; | |
| if (!connected) { | |
| connected = cli.connect(); | |
| } | |
| if (connected) { | |
| std::cout << "Remote listing:\n" << cli.list() << "\n"; | |
| // etc. | |
| } | |
| } | |
| void ofApp::exit() { | |
| srv->stopThread(); | |
| srv->waitThread(); | |
| } | |
| */ | |
| #endif // TCP_FILE_TRANSFER_HPP |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include "textures.h" | |
| void TextureResource::start() | |
| {}; | |
| void TextureResource::stop() | |
| {}; | |
| void TextureResource::update() | |
| {}; | |
| void VideoTextureResource::setup(string path) | |
| { | |
| player.load(path); | |
| }; | |
| void VideoTextureResource::start() | |
| { | |
| if (!player.isPlaying()) | |
| { | |
| player.play(); | |
| } | |
| }; | |
| void VideoTextureResource::stop() | |
| { | |
| if (player.isPlaying()) | |
| { | |
| player.stop(); | |
| } | |
| }; | |
| void VideoTextureResource::update() | |
| { | |
| if (player.isPlaying()) | |
| { | |
| player.update(); | |
| } | |
| }; | |
| ofTexture VideoTextureResource::getTexture() | |
| { | |
| return player.getTexture(); | |
| }; | |
| void DefaultTextureResource::setup() | |
| { | |
| ofPixels pix; | |
| pix.allocate(256, 256, OF_PIXELS_RGB); | |
| for (int y = 0; y < 256; ++y) | |
| { | |
| for (int x = 0; x < 256; ++x) | |
| { | |
| // gradient + red grid every 32 px | |
| ofColor c = ofColor::fromHsb((x / 255.0f) * 255, 200, 255); | |
| if ((x % 32) < 2 || (y % 32) < 2) | |
| c = ofColor::red; | |
| pix.setColor(x, y, c); | |
| } | |
| } | |
| tex.loadData(pix); | |
| }; | |
| ofTexture DefaultTextureResource::getTexture() | |
| { | |
| return tex; | |
| }; | |
| void TextureManager::setup() | |
| { | |
| DefaultTextureResource dtr; | |
| dtr.setup(); | |
| textures[defaultTextureId] = dtr; | |
| }; | |
| bool TextureManager::registerTextureResource(string id, TextureResource res) | |
| { | |
| try | |
| { | |
| textures.at(id); | |
| return false; | |
| } | |
| catch (const std::out_of_range &) | |
| { | |
| } | |
| textures[id] = res; | |
| return true; | |
| }; | |
| ofTexture TextureManager::getTextureById(string id) | |
| { | |
| try | |
| { | |
| TextureResource tr = textures.at(id); | |
| tr.start(); | |
| texturesPlaying[id] = true; | |
| return tr.getTexture(); | |
| } | |
| catch (const std::out_of_range &) | |
| { | |
| std::cout << "Key \"" << id.c_str() << "\" not found" << std::endl; | |
| } | |
| return textures[defaultTextureId].getTexture(); | |
| }; | |
| void TextureManager::update() | |
| { | |
| for (const auto &item : textures) | |
| { | |
| item.second.update(); | |
| } | |
| }; | |
| void TextureManager::pauseNotUsedTextures() | |
| { | |
| for (const auto &item : textures) | |
| { | |
| if (item.first != defaultTextureId && texturesPlaying.count(item.first) == 0) | |
| { | |
| item.second.stop(); | |
| } | |
| } | |
| texturesPlaying.clear(); | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #pragma once | |
| #include "ofMain.h" | |
| class TextureResource | |
| { | |
| public: | |
| void start(); | |
| void stop(); | |
| void update(); | |
| ofTexture getTexture(); | |
| }; | |
| class DefaultTextureResource: public TextureResource | |
| { | |
| public: | |
| void setup(); | |
| void start(); | |
| void stop(); | |
| void update(); | |
| ofTexture getTexture(); | |
| ofTexture tex; | |
| }; | |
| class VideoTextureResource: public TextureResource | |
| { | |
| public: | |
| void setup(string path); | |
| void start(); | |
| void stop(); | |
| void update(); | |
| ofTexture getTexture(); | |
| ofVideoPlayer player; | |
| }; | |
| class TextureManager | |
| { | |
| public: | |
| void setup(); | |
| bool registerTextureResource(string id, TextureResource res); | |
| ofTexture getTextureById(string id); | |
| void update(); | |
| void pauseNotUsedTextures(); | |
| map<string, TextureResource> textures = {}; | |
| map<string, bool> texturesPlaying = {}; | |
| string defaultTextureId = "test"; | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment