#pragma once #include <string> #include <chrono> #include <Definitions.h> #include <condition_variable> #include <variant> #include <deque> #include <functional> #define TRANSFER_KEY_LENGTH (32) namespace ts::server::file { enum struct ExecuteStatus { UNKNOWN, WAITING, SUCCESS, ERROR }; template<typename VariantType, typename T, std::size_t index = 0> constexpr std::size_t variant_index() { if constexpr (index == std::variant_size_v<VariantType>) { return index; } else if constexpr (std::is_same_v<std::variant_alternative_t<index, VariantType>, T>) { return index; } else { return variant_index<VariantType, T, index + 1>(); } } struct EmptyExecuteResponse { }; template <class error_t, class response_t = EmptyExecuteResponse> class ExecuteResponse { typedef std::variant<EmptyExecuteResponse, error_t, response_t> variant_t; public: ExecuteStatus status{ExecuteStatus::WAITING}; [[nodiscard]] inline const auto& response() const { return std::get<response_t>(this->response_); } template <typename = std::enable_if_t<!std::is_void<error_t>::value>> [[nodiscard]] inline const error_t& error() const { return std::get<error_t>(this->response_); } inline void wait() const { std::unique_lock nlock{this->notify_mutex}; this->notify_cv.wait(nlock, [&]{ return this->status != ExecuteStatus::WAITING; }); } template<typename _Rep, typename _Period> [[nodiscard]] inline bool wait_for(const std::chrono::duration<_Rep, _Period>& time) const { std::unique_lock nlock{this->notify_mutex}; return this->notify_cv.wait_for(nlock, time, [&]{ return this->status != ExecuteStatus::WAITING; }); } template <typename... Args> inline void emplace_success(Args&&... args) { constexpr auto success_index = variant_index<variant_t, response_t>(); std::lock_guard rlock{this->notify_mutex}; this->response_.template emplace<success_index, Args...>(std::forward<Args>(args)...); this->status = ExecuteStatus::SUCCESS; this->notify_cv.notify_all(); } template <typename... Args> inline void emplace_fail(Args&&... args) { constexpr auto error_index = variant_index<variant_t, error_t>(); std::lock_guard rlock{this->notify_mutex}; this->response_.template emplace<error_index, Args...>(std::forward<Args>(args)...); this->status = ExecuteStatus::ERROR; this->notify_cv.notify_all(); } [[nodiscard]] inline bool succeeded() const { return this->status == ExecuteStatus::SUCCESS; } ExecuteResponse(std::mutex& notify_mutex, std::condition_variable& notify_cv) : notify_mutex{notify_mutex}, notify_cv{notify_cv} {} private: variant_t response_{}; /* void* as default value so we don't initialize error_t or response_t */ std::mutex& notify_mutex; std::condition_variable& notify_cv; }; namespace filesystem { template <typename ErrorCodes> struct DetailedError { ErrorCodes error_type{ErrorCodes::UNKNOWN}; std::string error_message{}; DetailedError(ErrorCodes type, std::string extraMessage) : error_type{type}, error_message{std::move(extraMessage)} {} }; enum struct DirectoryQueryErrorType { UNKNOWN, PATH_EXCEEDS_ROOT_PATH, PATH_IS_A_FILE, PATH_DOES_NOT_EXISTS, FAILED_TO_LIST_FILES, MAX }; constexpr std::array<std::string_view, (int) DirectoryQueryErrorType::MAX> directory_query_error_messages = { "unknown error", "path exceeds base path", "path is a file", "path does not exists", "failed to list files" }; typedef DetailedError<DirectoryQueryErrorType> DirectoryQueryError; struct DirectoryEntry { enum Type { UNKNOWN, DIRECTORY, FILE }; Type type{Type::UNKNOWN}; std::string name{}; std::chrono::system_clock::time_point modified_at{}; size_t size{0}; }; enum struct DirectoryModifyErrorType { UNKNOWN, PATH_EXCEEDS_ROOT_PATH, PATH_ALREADY_EXISTS, FAILED_TO_CREATE_DIRECTORIES }; typedef DetailedError<DirectoryModifyErrorType> DirectoryModifyError; enum struct FileModifyErrorType { UNKNOWN, PATH_EXCEEDS_ROOT_PATH, TARGET_PATH_EXCEEDS_ROOT_PATH, PATH_DOES_NOT_EXISTS, TARGET_PATH_ALREADY_EXISTS, FAILED_TO_DELETE_FILES, FAILED_TO_RENAME_FILE, SOME_FILES_ARE_LOCKED }; typedef DetailedError<FileModifyErrorType> FileModifyError; enum struct ServerCommandErrorType { UNKNOWN, FAILED_TO_CREATE_DIRECTORIES, FAILED_TO_DELETE_DIRECTORIES }; typedef DetailedError<ServerCommandErrorType> ServerCommandError; class AbstractProvider { public: typedef ExecuteResponse<DirectoryQueryError, std::deque<DirectoryEntry>> directory_query_response_t; /* server */ [[nodiscard]] virtual std::shared_ptr<ExecuteResponse<ServerCommandError>> initialize_server(ServerId /* server */) = 0; [[nodiscard]] virtual std::shared_ptr<ExecuteResponse<ServerCommandError>> delete_server(ServerId /* server */) = 0; /* channels */ [[nodiscard]] virtual std::shared_ptr<directory_query_response_t> query_channel_directory(ServerId /* server */, ChannelId /* channel */, const std::string& /* path */) = 0; [[nodiscard]] virtual std::shared_ptr<ExecuteResponse<DirectoryModifyError>> create_channel_directory(ServerId /* server */, ChannelId /* channel */, const std::string& /* path */) = 0; [[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileModifyError>> delete_channel_file(ServerId /* server */, ChannelId /* channel */, const std::string& /* path */) = 0; [[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileModifyError>> rename_channel_file(ServerId /* server */, ChannelId /* channel */, const std::string& /* path */, const std::string& /* target */) = 0; /* icons */ [[nodiscard]] virtual std::shared_ptr<directory_query_response_t> query_icon_directory(ServerId /* server */) = 0; [[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileModifyError>> delete_icon(ServerId /* server */, const std::string& /* name */) = 0; /* avatars */ [[nodiscard]] virtual std::shared_ptr<directory_query_response_t> query_avatar_directory(ServerId /* server */) = 0; [[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileModifyError>> delete_avatar(ServerId /* server */, const std::string& /* name */) = 0; private: }; } namespace transfer { typedef uint32_t transfer_id; struct Transfer { transfer_id server_transfer_id{0}; transfer_id client_transfer_id{0}; ServerId server_id{0}; ClientId client_id{0}; ChannelId channel_id{0}; char transfer_key[TRANSFER_KEY_LENGTH]{}; std::chrono::system_clock::time_point initialized_timestamp{}; enum Direction { DIRECTION_UNKNOWN, DIRECTION_UPLOAD, DIRECTION_DOWNLOAD } direction{DIRECTION_UNKNOWN}; struct Address { std::string hostname{}; uint16_t port{0}; }; std::vector<Address> server_addresses{}; enum TargetType { TARGET_TYPE_UNKNOWN, TARGET_TYPE_CHANNEL_FILE, TARGET_TYPE_ICON, TARGET_TYPE_AVATAR } target_type{TARGET_TYPE_UNKNOWN}; std::string target_file_path{}; int64_t max_bandwidth{-1}; size_t expected_file_size{0}; /* incl. the offset! */ size_t file_offset{0}; bool override_exiting{false}; }; struct TransferStatistics { uint64_t network_bytes_send{0}; uint64_t network_bytes_received{0}; uint64_t delta_network_bytes_send{0}; uint64_t delta_network_bytes_received{0}; uint64_t file_bytes_transferred{0}; uint64_t delta_file_bytes_transferred{0}; size_t file_start_offset{0}; size_t file_current_offset{0}; size_t file_total_size{0}; }; struct TransferInitError { enum Type { UNKNOWN } error_type{UNKNOWN}; std::string error_message{}; }; struct TransferActionError { enum Type { UNKNOWN, UNKNOWN_TRANSFER } error_type{UNKNOWN}; std::string error_message{}; }; struct TransferError { enum Type { UNKNOWN, TRANSFER_TIMEOUT, DISK_IO_ERROR, DISK_TIMEOUT, DISK_INITIALIZE_ERROR, NETWORK_IO_ERROR, UNEXPECTED_CLIENT_DISCONNECT, UNEXPECTED_DISK_EOF } error_type{UNKNOWN}; std::string error_message{}; }; class AbstractProvider { public: struct TransferInfo { std::string file_path{}; bool override_exiting{false}; /* only for upload valid */ size_t file_offset{0}; size_t expected_file_size{0}; int64_t max_bandwidth{-1}; }; virtual std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> initialize_channel_transfer(Transfer::Direction /* direction */, ServerId /* server */, ChannelId /* channel */, const TransferInfo& /* info */) = 0; virtual std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> initialize_icon_transfer(Transfer::Direction /* direction */, ServerId /* server */, const TransferInfo& /* info */) = 0; virtual std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> initialize_avatar_transfer(Transfer::Direction /* direction */, ServerId /* server */, const TransferInfo& /* info */) = 0; virtual std::shared_ptr<ExecuteResponse<TransferActionError>> stop_transfer(transfer_id /* id */, bool /* flush */) = 0; std::function<void(const std::shared_ptr<Transfer>&)> callback_transfer_registered{}; /* transfer has been registered */ std::function<void(const std::shared_ptr<Transfer>&)> callback_transfer_started{}; /* transfer has been started */ std::function<void(const std::shared_ptr<Transfer>&)> callback_transfer_finished{}; /* transfer has been finished */ std::function<void(const std::shared_ptr<Transfer>&, const TransferError&)> callback_transfer_aborted{}; /* an error happened while transferring the data */ std::function<void(const std::shared_ptr<Transfer>&, const TransferStatistics&)> callback_transfer_statistics{}; }; } class AbstractFileServer { public: [[nodiscard]] virtual filesystem::AbstractProvider& file_system() = 0; [[nodiscard]] virtual transfer::AbstractProvider& file_transfer() = 0; private: }; extern bool initialize(std::string& /* error */); extern void finalize(); extern std::shared_ptr<AbstractFileServer> server(); }