Skip to content

Instantly share code, notes, and snippets.

@sloev
Last active November 6, 2025 10:05
Show Gist options
  • Select an option

  • Save sloev/539cc48838f41167b811027ee5ada7fa to your computer and use it in GitHub Desktop.

Select an option

Save sloev/539cc48838f41167b811027ee5ada7fa to your computer and use it in GitHub Desktop.
// 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
// 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
#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();
};
#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