A lot of updates
This commit is contained in:
parent
fd256411d1
commit
48326bd102
@ -5,13 +5,22 @@ project(TeaSpeak-Files)
|
||||
#set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
add_library(TeaSpeak-FileServer STATIC
|
||||
local_server/LocalFileServer.cpp
|
||||
local_server/LocalFileProvider.cpp
|
||||
local_server/LocalFileSystem.cpp
|
||||
local_server/LocalFileTransfer.cpp
|
||||
local_server/LocalFileTransferClientWorker.cpp
|
||||
local_server/LocalFileTransferDisk.cpp
|
||||
local_server/LocalFileTransferNetwork.cpp
|
||||
local_server/clnpath.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(TeaSpeak-FileServer PUBLIC TeaSpeak ${StringVariable_LIBRARIES_STATIC} stdc++fs
|
||||
libevent::core libevent::pthreads)
|
||||
libevent::core libevent::pthreads
|
||||
DataPipes::core::static
|
||||
openssl::ssl::shared
|
||||
openssl::crypto::shared
|
||||
)
|
||||
|
||||
target_include_directories(TeaSpeak-FileServer PUBLIC include/)
|
||||
|
||||
add_executable(TeaSpeak-FileServerTest test/main.cpp)
|
||||
@ -20,6 +29,7 @@ target_link_libraries(TeaSpeak-FileServerTest PUBLIC TeaSpeak-FileServer
|
||||
CXXTerminal::static #Static
|
||||
stdc++fs
|
||||
)
|
||||
target_compile_options(TeaSpeak-FileServerTest PUBLIC -static-libgcc -static-libstdc++)
|
||||
|
||||
add_executable(FileServer-CLNText local_server/clnpath.cpp)
|
||||
target_compile_definitions(FileServer-CLNText PUBLIC -DCLN_EXEC)
|
@ -8,6 +8,8 @@
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
|
||||
#define TRANSFER_KEY_LENGTH (32)
|
||||
|
||||
namespace ts::server::file {
|
||||
enum struct ExecuteStatus {
|
||||
UNKNOWN,
|
||||
@ -70,6 +72,10 @@ namespace ts::server::file {
|
||||
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:
|
||||
@ -185,7 +191,7 @@ namespace ts::server::file {
|
||||
ClientId client_id{0};
|
||||
ChannelId channel_id{0};
|
||||
|
||||
std::string transfer_key{};
|
||||
char transfer_key[TRANSFER_KEY_LENGTH]{};
|
||||
std::chrono::system_clock::time_point initialized_timestamp{};
|
||||
enum Direction {
|
||||
DIRECTION_UNKNOWN,
|
||||
@ -193,8 +199,11 @@ namespace ts::server::file {
|
||||
DIRECTION_DOWNLOAD
|
||||
} direction{DIRECTION_UNKNOWN};
|
||||
|
||||
std::string transfer_hosts{}; /* comma separated list */
|
||||
uint16_t transfer_port{0};
|
||||
struct Address {
|
||||
std::string hostname{};
|
||||
uint16_t port{0};
|
||||
};
|
||||
std::vector<Address> server_addresses{};
|
||||
|
||||
enum TargetType {
|
||||
TARGET_TYPE_UNKNOWN,
|
||||
@ -205,19 +214,24 @@ namespace ts::server::file {
|
||||
std::string target_file_path{};
|
||||
|
||||
int64_t max_bandwidth{-1};
|
||||
size_t expected_file_size{0};
|
||||
size_t expected_file_size{0}; /* incl. the offset! */
|
||||
size_t file_offset{0};
|
||||
bool override_exiting{false};
|
||||
};
|
||||
|
||||
struct TransferStatistics {
|
||||
uint64_t bytes_send{0};
|
||||
uint64_t bytes_received{0};
|
||||
uint64_t network_bytes_send{0};
|
||||
uint64_t network_bytes_received{0};
|
||||
|
||||
uint64_t delta_bytes_send{0};
|
||||
uint64_t delta_bytes_received{0};
|
||||
uint64_t delta_network_bytes_send{0};
|
||||
uint64_t delta_network_bytes_received{0};
|
||||
|
||||
size_t current_offset{0};
|
||||
size_t total_size{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 {
|
||||
@ -239,10 +253,17 @@ namespace ts::server::file {
|
||||
struct TransferError {
|
||||
enum Type {
|
||||
UNKNOWN,
|
||||
|
||||
TRANSFER_TIMEOUT,
|
||||
|
||||
DISK_IO_ERROR,
|
||||
DISK_TIMEOUT,
|
||||
DISK_INITIALIZE_ERROR,
|
||||
|
||||
NETWORK_IO_ERROR,
|
||||
|
||||
UNEXPECTED_CLIENT_EOF
|
||||
UNEXPECTED_CLIENT_DISCONNECT,
|
||||
UNEXPECTED_DISK_EOF
|
||||
} error_type{UNKNOWN};
|
||||
std::string error_message{};
|
||||
};
|
||||
@ -262,20 +283,20 @@ namespace ts::server::file {
|
||||
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) = 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_timeout{}; /* client has not connected to that transfer */
|
||||
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 transfering the data */
|
||||
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{};
|
||||
private:
|
||||
};
|
||||
}
|
||||
|
||||
class AbstractFileServer {
|
||||
public:
|
||||
[[nodiscard]] virtual filesystem::AbstractProvider& file_system() = 0;
|
||||
[[nodiscard]] virtual transfer::AbstractProvider& file_transfer() = 0;
|
||||
private:
|
||||
};
|
||||
|
||||
|
73
file/local_server/LocalFileProvider.cpp
Normal file
73
file/local_server/LocalFileProvider.cpp
Normal file
@ -0,0 +1,73 @@
|
||||
//
|
||||
// Created by WolverinDEV on 28/04/2020.
|
||||
//
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include "LocalFileProvider.h"
|
||||
|
||||
using namespace ts::server;
|
||||
using LocalFileServer = file::LocalFileProvider;
|
||||
|
||||
std::shared_ptr<LocalFileServer> server_instance{};
|
||||
bool file::initialize(std::string &error) {
|
||||
server_instance = std::make_shared<LocalFileProvider>();
|
||||
if(!server_instance->initialize(error)) {
|
||||
server_instance = nullptr;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void file::finalize() {
|
||||
auto server = std::exchange(server_instance, nullptr);
|
||||
if(!server) return;
|
||||
|
||||
server->finalize();
|
||||
}
|
||||
|
||||
std::shared_ptr<file::AbstractFileServer> file::server() {
|
||||
return server_instance;
|
||||
}
|
||||
|
||||
LocalFileServer::LocalFileProvider() : file_system_{}, file_transfer_{this->file_system_} {}
|
||||
LocalFileServer::~LocalFileProvider() {}
|
||||
|
||||
bool LocalFileServer::initialize(std::string &error) {
|
||||
if(!this->file_system_.initialize(error, "file-root/"))
|
||||
return false;
|
||||
|
||||
|
||||
std::deque<std::shared_ptr<transfer::NetworkBinding>> bindings{};
|
||||
{
|
||||
auto binding = std::make_shared<transfer::NetworkBinding>();
|
||||
|
||||
binding->hostname = "localhost";
|
||||
|
||||
auto& iaddr = *(sockaddr_in*) &binding->address;
|
||||
iaddr.sin_family = AF_INET;
|
||||
iaddr.sin_port = htons(1112);
|
||||
iaddr.sin_addr.s_addr = INADDR_ANY;
|
||||
|
||||
bindings.push_back(std::move(binding));
|
||||
}
|
||||
|
||||
if(!this->file_transfer_.start(bindings)) {
|
||||
error = "transfer server startup failed";
|
||||
this->file_system_.finalize();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void LocalFileServer::finalize() {
|
||||
this->file_transfer_.stop();
|
||||
this->file_system_.finalize();
|
||||
}
|
||||
|
||||
file::filesystem::AbstractProvider &LocalFileServer::file_system() {
|
||||
return this->file_system_;
|
||||
}
|
||||
|
||||
file::transfer::AbstractProvider & LocalFileServer::file_transfer() {
|
||||
return this->file_transfer_;
|
||||
}
|
494
file/local_server/LocalFileProvider.h
Normal file
494
file/local_server/LocalFileProvider.h
Normal file
@ -0,0 +1,494 @@
|
||||
#pragma once
|
||||
|
||||
#include <files/FileServer.h>
|
||||
#include <deque>
|
||||
#include <utility>
|
||||
#include <thread>
|
||||
#include <shared_mutex>
|
||||
#include <sys/socket.h>
|
||||
#include <pipes/ws.h>
|
||||
#include <pipes/ssl.h>
|
||||
#include <misc/net.h>
|
||||
#include <random>
|
||||
|
||||
#define TRANSFER_MAX_CACHED_BYTES (1024 * 1024 * 1) // Buffer up to 1mb
|
||||
|
||||
namespace ts::server::file {
|
||||
namespace filesystem {
|
||||
#ifdef FS_INCLUDED
|
||||
namespace fs = std::experimental::filesystem;
|
||||
#endif
|
||||
|
||||
class LocalFileSystem : public filesystem::AbstractProvider {
|
||||
using FileModifyError = filesystem::FileModifyError;
|
||||
using DirectoryModifyError = filesystem::DirectoryModifyError;
|
||||
public:
|
||||
enum struct FileCategory {
|
||||
ICON,
|
||||
AVATAR,
|
||||
CHANNEL
|
||||
};
|
||||
|
||||
virtual ~LocalFileSystem();
|
||||
|
||||
bool initialize(std::string & /* error */, const std::string & /* root path */);
|
||||
void finalize();
|
||||
|
||||
void lock_file(const std::string& /* absolute path */);
|
||||
void unlock_file(const std::string& /* absolute path */);
|
||||
|
||||
[[nodiscard]] inline const auto &root_path() const { return this->root_path_; }
|
||||
|
||||
[[nodiscard]] std::string absolute_avatar_path(ServerId, const std::string&);
|
||||
[[nodiscard]] std::string absolute_icon_path(ServerId, const std::string&);
|
||||
[[nodiscard]] std::string absolute_channel_path(ServerId, ChannelId, const std::string&);
|
||||
|
||||
std::shared_ptr<ExecuteResponse<ServerCommandError>> initialize_server(ServerId /* server */) override;
|
||||
std::shared_ptr<ExecuteResponse<ServerCommandError>> delete_server(ServerId /* server */) override;
|
||||
|
||||
std::shared_ptr<directory_query_response_t>
|
||||
query_channel_directory(ServerId id, ChannelId channelId, const std::string &string) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<DirectoryModifyError>>
|
||||
create_channel_directory(ServerId id, ChannelId channelId, const std::string &string) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileModifyError>>
|
||||
delete_channel_file(ServerId id, ChannelId channelId, const std::string &string) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileModifyError>>
|
||||
rename_channel_file(ServerId id, ChannelId channelId, const std::string &, const std::string &) override;
|
||||
|
||||
std::shared_ptr<directory_query_response_t> query_icon_directory(ServerId id) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileModifyError>>
|
||||
delete_icon(ServerId id, const std::string &string) override;
|
||||
|
||||
std::shared_ptr<directory_query_response_t> query_avatar_directory(ServerId id) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileModifyError>>
|
||||
delete_avatar(ServerId id, const std::string &string) override;
|
||||
|
||||
private:
|
||||
#ifdef FS_INCLUDED
|
||||
[[nodiscard]] fs::path server_path(ServerId);
|
||||
[[nodiscard]] fs::path server_channel_path(ServerId, ChannelId);
|
||||
[[nodiscard]] static bool exceeds_base_path(const fs::path& /* base */, const fs::path& /* target */);
|
||||
[[nodiscard]] bool is_any_file_locked(const fs::path& /* base */, const std::string& /* path */, std::string& /* file (relative to the base) */);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<ExecuteResponse<FileModifyError>>
|
||||
delete_file(const fs::path& /* base */, const std::string &string);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<directory_query_response_t>
|
||||
query_directory(const fs::path& /* base */, const std::string &string, bool);
|
||||
#endif
|
||||
|
||||
template <typename error_t, typename result_t = EmptyExecuteResponse>
|
||||
std::shared_ptr<ExecuteResponse<error_t, result_t>> create_execute_response() {
|
||||
return std::make_shared<ExecuteResponse<error_t, result_t>>(this->result_notify_mutex, this->result_notify_cv);
|
||||
}
|
||||
std::string target_file_path(FileCategory type, ts::ServerId sid, ts::ChannelId cid, const std::string &path);
|
||||
|
||||
std::mutex result_notify_mutex{};
|
||||
std::condition_variable result_notify_cv{};
|
||||
|
||||
std::string root_path_{};
|
||||
|
||||
std::mutex locked_files_mutex{};
|
||||
std::deque<std::string> locked_files_{};
|
||||
};
|
||||
}
|
||||
|
||||
namespace transfer {
|
||||
class LocalFileTransfer;
|
||||
|
||||
struct Buffer {
|
||||
Buffer* next{nullptr};
|
||||
|
||||
size_t capacity{0};
|
||||
size_t length{0};
|
||||
size_t offset{0};
|
||||
|
||||
char data[1]{};
|
||||
};
|
||||
[[nodiscard]] extern Buffer* allocate_buffer(size_t);
|
||||
extern void free_buffer(Buffer*);
|
||||
|
||||
struct NetworkThrottle {
|
||||
constexpr static auto kThrottleTimespanMs{250};
|
||||
typedef uint8_t span_t;
|
||||
|
||||
ssize_t max_bytes{0};
|
||||
|
||||
span_t current_index{0};
|
||||
size_t bytes_send{0};
|
||||
|
||||
inline bool increase_bytes(size_t bytes) {
|
||||
auto current_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
auto current_span = (span_t) (current_ms / kThrottleTimespanMs);
|
||||
if(this->current_index != current_span) {
|
||||
this->current_index = current_span;
|
||||
this->bytes_send = bytes;
|
||||
} else {
|
||||
this->bytes_send += bytes;
|
||||
}
|
||||
return this->max_bytes > 0 && this->bytes_send >= this->max_bytes;
|
||||
}
|
||||
|
||||
inline void set_max_bandwidth(ssize_t bytes_per_second) {
|
||||
if(bytes_per_second <= 0)
|
||||
this->max_bytes = -1;
|
||||
else
|
||||
this->max_bytes = bytes_per_second * kThrottleTimespanMs / 1000;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool should_throttle(timeval& next_timestamp) {
|
||||
if(this->max_bytes <= 0) return false;
|
||||
|
||||
auto current_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
auto current_span = (span_t) (current_ms / kThrottleTimespanMs);
|
||||
if(this->current_index != current_span) return false;
|
||||
if(this->bytes_send < this->max_bytes) return false;
|
||||
|
||||
next_timestamp.tv_usec = (kThrottleTimespanMs - current_ms % kThrottleTimespanMs) * 1000;
|
||||
next_timestamp.tv_sec = next_timestamp.tv_usec / 1000000;
|
||||
next_timestamp.tv_usec -= next_timestamp.tv_sec * 1000000;
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline size_t bytes_left() const {
|
||||
if(this->max_bytes <= 0) return (size_t) -1;
|
||||
|
||||
auto current_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
auto current_span = (span_t) (current_ms / kThrottleTimespanMs);
|
||||
if(this->current_index != current_span) return this->max_bytes;
|
||||
if(this->bytes_send < this->max_bytes) return this->max_bytes - this->bytes_send;
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
/* all variables are locked via the state_mutex */
|
||||
struct FileClient : std::enable_shared_from_this<FileClient> {
|
||||
LocalFileTransfer* handle;
|
||||
std::shared_ptr<Transfer> transfer{nullptr};
|
||||
|
||||
std::shared_mutex state_mutex{};
|
||||
enum {
|
||||
STATE_AWAITING_KEY, /* includes SSL/HTTP init */
|
||||
STATE_TRANSFERRING,
|
||||
STATE_DISCONNECTING,
|
||||
STATE_DISCONNECTED
|
||||
} state{STATE_AWAITING_KEY};
|
||||
|
||||
enum NetworkingProtocol {
|
||||
PROTOCOL_UNKNOWN,
|
||||
PROTOCOL_HTTPS,
|
||||
PROTOCOL_TS_V1
|
||||
};
|
||||
|
||||
struct {
|
||||
bool file_locked{false};
|
||||
std::string absolute_path{};
|
||||
|
||||
#if 0
|
||||
struct event* io_process{nullptr}; /* either a read event or write event */
|
||||
#endif
|
||||
int file_descriptor{0};
|
||||
|
||||
bool currently_processing{false};
|
||||
FileClient* next_client{nullptr};
|
||||
} file{};
|
||||
|
||||
struct {
|
||||
size_t provided_bytes{0};
|
||||
char key[TRANSFER_KEY_LENGTH]{0};
|
||||
} transfer_key{};
|
||||
|
||||
/* will be used for both directions */
|
||||
struct {
|
||||
std::mutex mutex{};
|
||||
size_t bytes{0};
|
||||
|
||||
bool buffering_stopped{false};
|
||||
|
||||
Buffer* buffer_head{nullptr};
|
||||
Buffer** buffer_tail{&buffer_head};
|
||||
} buffer{};
|
||||
|
||||
struct {
|
||||
sockaddr_storage address{};
|
||||
int file_descriptor{0};
|
||||
|
||||
NetworkingProtocol protocol{PROTOCOL_UNKNOWN};
|
||||
|
||||
struct event* event_read{nullptr};
|
||||
struct event* event_write{nullptr};
|
||||
struct event* event_throttle{nullptr};
|
||||
|
||||
bool add_event_write{false}, add_event_read{false};
|
||||
|
||||
std::chrono::system_clock::time_point disconnect_timeout{};
|
||||
|
||||
NetworkThrottle throttle;
|
||||
|
||||
pipes::SSL pipe_ssl{};
|
||||
|
||||
/* Only read the transfer key length at the beginning. We than have the actual limit which will be set via throttle */
|
||||
size_t max_read_buffer_size{TRANSFER_KEY_LENGTH};
|
||||
} networking{};
|
||||
|
||||
struct {
|
||||
size_t network_bytes_send{0};
|
||||
size_t network_bytes_received{0};
|
||||
|
||||
size_t file_bytes_transferred{0};
|
||||
|
||||
/* used for delta statistics */
|
||||
size_t last_network_bytes_send{0};
|
||||
size_t last_network_bytes_received{0};
|
||||
|
||||
/* used for delta statistics */
|
||||
size_t last_file_bytes_transferred{0};
|
||||
|
||||
size_t disk_bytes_read{0};
|
||||
size_t disk_bytes_write{0};
|
||||
} statistics{};
|
||||
|
||||
struct {
|
||||
std::chrono::system_clock::time_point last_write{};
|
||||
std::chrono::system_clock::time_point last_read{};
|
||||
|
||||
std::chrono::system_clock::time_point connected{};
|
||||
std::chrono::system_clock::time_point key_received{};
|
||||
std::chrono::system_clock::time_point disconnecting{};
|
||||
} timings;
|
||||
|
||||
explicit FileClient(LocalFileTransfer* handle) : handle{handle} {}
|
||||
~FileClient();
|
||||
|
||||
void add_network_write_event(bool /* ignore bandwidth limits */);
|
||||
void add_network_write_event_nolock(bool /* ignore bandwidth limits */);
|
||||
|
||||
/* will check if we've enough space in out read buffer again */
|
||||
void add_network_read_event(bool /* ignore bandwidth limits */);
|
||||
|
||||
bool send_file_bytes(const void* /* buffer */, size_t /* length */);
|
||||
|
||||
[[nodiscard]] inline std::string log_prefix() const { return "[" + net::to_string(this->networking.address) + "]"; }
|
||||
};
|
||||
|
||||
enum struct DiskIOStartResult {
|
||||
SUCCESS,
|
||||
OUT_OF_MEMORY
|
||||
};
|
||||
|
||||
enum struct NetworkingStartResult {
|
||||
SUCCESS,
|
||||
OUT_OF_MEMORY,
|
||||
NO_BINDINGS
|
||||
};
|
||||
|
||||
enum struct ClientWorkerStartResult {
|
||||
SUCCESS
|
||||
};
|
||||
|
||||
enum struct NetworkInitializeResult {
|
||||
SUCCESS,
|
||||
OUT_OF_MEMORY
|
||||
};
|
||||
|
||||
enum struct FileInitializeResult {
|
||||
SUCCESS,
|
||||
|
||||
INVALID_TRANSFER_DIRECTION,
|
||||
OUT_OF_MEMORY,
|
||||
|
||||
PROCESS_FILE_LIMIT_REACHED,
|
||||
SYSTEM_FILE_LIMIT_REACHED,
|
||||
|
||||
FILE_IS_BUSY,
|
||||
FILE_DOES_NOT_EXISTS,
|
||||
FILE_SYSTEM_ERROR,
|
||||
FILE_IS_A_DIRECTORY,
|
||||
|
||||
FILE_TOO_LARGE,
|
||||
DISK_IS_READ_ONLY,
|
||||
|
||||
FILE_SIZE_MISMATCH,
|
||||
FILE_SEEK_FAILED,
|
||||
|
||||
FILE_IS_NOT_ACCESSIBLE,
|
||||
|
||||
MAX
|
||||
};
|
||||
|
||||
constexpr static std::array<std::string_view, (size_t) FileInitializeResult::MAX> kFileInitializeResultMessages{
|
||||
/* SUCCESS */ "success",
|
||||
|
||||
/* INVALID_TRANSFER_DIRECTION */ "invalid file transfer direction",
|
||||
/* OUT_OF_MEMORY */ "out of memory",
|
||||
|
||||
/* PROCESS_FILE_LIMIT_REACHED */ "process file limit reached",
|
||||
/* SYSTEM_FILE_LIMIT_REACHED */ "system file limit reached",
|
||||
|
||||
/* FILE_IS_BUSY */ "target file is busy",
|
||||
/* FILE_DOES_NOT_EXISTS */ "target file does not exists",
|
||||
/* FILE_SYSTEM_ERROR */ "internal file system error",
|
||||
/* FILE_IS_A_DIRECTORY */ "target file is a directory",
|
||||
|
||||
/* FILE_TOO_LARGE */ "file is too large",
|
||||
/* DISK_IS_READ_ONLY */ "disk is in read only mode",
|
||||
|
||||
/* FILE_SIZE_MISMATCH */ "file size mismatch",
|
||||
/* FILE_SEEK_FAILED */ "failed to seek to target file offset",
|
||||
|
||||
/* FILE_IS_NOT_ACCESSIBLE */ "file is not accessible"
|
||||
};
|
||||
|
||||
enum struct TransferKeyApplyResult {
|
||||
SUCCESS,
|
||||
FILE_ERROR,
|
||||
UNKNOWN_KEY
|
||||
};
|
||||
|
||||
struct NetworkBinding : std::enable_shared_from_this<NetworkBinding> {
|
||||
std::string hostname{};
|
||||
sockaddr_storage address{};
|
||||
|
||||
int file_descriptor{-1};
|
||||
struct event* accept_event{nullptr};
|
||||
|
||||
LocalFileTransfer* handle{nullptr};
|
||||
};
|
||||
|
||||
class LocalFileTransfer : public AbstractProvider {
|
||||
public:
|
||||
explicit LocalFileTransfer(filesystem::LocalFileSystem&);
|
||||
~LocalFileTransfer();
|
||||
|
||||
[[nodiscard]] bool start(const std::deque<std::shared_ptr<NetworkBinding>>& /* bindings */);
|
||||
void stop();
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_channel_transfer(Transfer::Direction direction, ServerId id, ChannelId channelId,
|
||||
const TransferInfo &info) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_icon_transfer(Transfer::Direction direction, ServerId id, const TransferInfo &info) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_avatar_transfer(Transfer::Direction direction, ServerId id, const TransferInfo &info) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferActionError>> stop_transfer(transfer_id id, bool) override;
|
||||
private:
|
||||
enum struct DiskIOLoopState {
|
||||
STOPPED,
|
||||
RUNNING,
|
||||
|
||||
STOPPING,
|
||||
FORCE_STOPPING
|
||||
};
|
||||
filesystem::LocalFileSystem& file_system_;
|
||||
|
||||
std::atomic<transfer_id> current_transfer_id{0};
|
||||
|
||||
std::mt19937 transfer_random_token_generator{std::random_device{}()};
|
||||
|
||||
std::mutex result_notify_mutex{};
|
||||
std::condition_variable result_notify_cv{};
|
||||
|
||||
std::mutex transfers_mutex{};
|
||||
std::deque<std::shared_ptr<FileClient>> transfers_{};
|
||||
std::deque<std::shared_ptr<Transfer>> pending_transfers{};
|
||||
|
||||
enum ServerState {
|
||||
STOPPED,
|
||||
RUNNING
|
||||
} state{ServerState::STOPPED};
|
||||
|
||||
struct {
|
||||
bool active{false};
|
||||
|
||||
std::thread dispatch_thread{};
|
||||
std::mutex mutex{};
|
||||
std::condition_variable notify_cv{};
|
||||
} disconnect;
|
||||
|
||||
struct {
|
||||
bool active{false};
|
||||
std::thread dispatch_thread{};
|
||||
struct event_base* event_base{nullptr};
|
||||
|
||||
std::deque<std::shared_ptr<NetworkBinding>> bindings{};
|
||||
} network{};
|
||||
|
||||
struct {
|
||||
DiskIOLoopState state{DiskIOLoopState::STOPPED};
|
||||
std::thread dispatch_thread{};
|
||||
std::mutex queue_lock{};
|
||||
std::condition_variable notify_work_awaiting{};
|
||||
std::condition_variable notify_client_processed{};
|
||||
|
||||
FileClient* queue_head{nullptr};
|
||||
FileClient** queue_tail{&queue_head};
|
||||
} disk_io{};
|
||||
|
||||
template <typename error_t, typename result_t = EmptyExecuteResponse>
|
||||
std::shared_ptr<ExecuteResponse<error_t, result_t>> create_execute_response() {
|
||||
return std::make_shared<ExecuteResponse<error_t, result_t>>(this->result_notify_mutex, this->result_notify_cv);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_transfer(Transfer::Direction, ServerId, ChannelId, Transfer::TargetType, const TransferInfo &info);
|
||||
|
||||
[[nodiscard]] NetworkingStartResult start_networking();
|
||||
[[nodiscard]] DiskIOStartResult start_disk_io();
|
||||
[[nodiscard]] ClientWorkerStartResult start_client_worker();
|
||||
|
||||
void shutdown_networking();
|
||||
void shutdown_disk_io();
|
||||
void shutdown_client_worker();
|
||||
|
||||
void disconnect_client(const std::shared_ptr<FileClient>& /* client */, std::unique_lock<std::shared_mutex>& /* state lock */, bool /* flush */);
|
||||
|
||||
[[nodiscard]] NetworkInitializeResult initialize_networking(const std::shared_ptr<FileClient>& /* client */, int /* file descriptor */);
|
||||
/* might block 'till all IO operations have been succeeded */
|
||||
void finalize_networking(const std::shared_ptr<FileClient>& /* client */, std::unique_lock<std::shared_mutex>& /* state lock */);
|
||||
|
||||
[[nodiscard]] FileInitializeResult initialize_file_io(const std::shared_ptr<FileClient>& /* client */);
|
||||
void finalize_file_io(const std::shared_ptr<FileClient>& /* client */, std::unique_lock<std::shared_mutex>& /* state lock */);
|
||||
|
||||
void enqueue_disk_io(const std::shared_ptr<FileClient>& /* client */);
|
||||
void execute_disk_io(const std::shared_ptr<FileClient>& /* client */);
|
||||
|
||||
void report_transfer_statistics(const std::shared_ptr<FileClient>& /* client */);
|
||||
|
||||
static void callback_transfer_network_write(int, short, void*);
|
||||
static void callback_transfer_network_read(int, short, void*);
|
||||
static void callback_transfer_network_throttle(int, short, void*);
|
||||
static void callback_transfer_network_accept(int, short, void*);
|
||||
|
||||
static void dispatch_loop_client_worker(void*);
|
||||
static void dispatch_loop_network(void*);
|
||||
static void dispatch_loop_disk_io(void*);
|
||||
|
||||
size_t handle_transfer_read(const std::shared_ptr<FileClient>& /* client */, const char* /* buffer */, size_t /* bytes */);
|
||||
[[nodiscard]] TransferKeyApplyResult handle_transfer_key_provided(const std::shared_ptr<FileClient>& /* client */);
|
||||
};
|
||||
}
|
||||
|
||||
class LocalFileProvider : public AbstractFileServer {
|
||||
public:
|
||||
LocalFileProvider();
|
||||
virtual ~LocalFileProvider();
|
||||
|
||||
[[nodiscard]] bool initialize(std::string& /* error */);
|
||||
void finalize();
|
||||
|
||||
filesystem::AbstractProvider &file_system() override;
|
||||
transfer::AbstractProvider &file_transfer() override;
|
||||
|
||||
private:
|
||||
filesystem::LocalFileSystem file_system_;
|
||||
transfer::LocalFileTransfer file_transfer_;
|
||||
};
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
//
|
||||
// Created by WolverinDEV on 28/04/2020.
|
||||
//
|
||||
|
||||
#include "./LocalFileServer.h"
|
||||
|
||||
using namespace ts::server;
|
||||
using LocalFileServer = file::LocalFileServer;
|
||||
|
||||
std::shared_ptr<LocalFileServer> server_instance{};
|
||||
bool file::initialize(std::string &error) {
|
||||
server_instance = std::make_shared<LocalFileServer>();
|
||||
if(!server_instance->initialize(error)) {
|
||||
server_instance = nullptr;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void file::finalize() {
|
||||
auto server = std::exchange(server_instance, nullptr);
|
||||
if(!server) return;
|
||||
|
||||
server->finalize();
|
||||
}
|
||||
|
||||
std::shared_ptr<file::AbstractFileServer> file::server() {
|
||||
return server_instance;
|
||||
}
|
||||
|
||||
LocalFileServer::~LocalFileServer() {}
|
||||
|
||||
bool LocalFileServer::initialize(std::string &error) {
|
||||
if(!this->file_system_.initialize(error, "file-root/"))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void LocalFileServer::finalize() {
|
||||
|
||||
}
|
||||
|
||||
file::filesystem::AbstractProvider &LocalFileServer::file_system() {
|
||||
return this->file_system_;
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <files/FileServer.h>
|
||||
#include <deque>
|
||||
|
||||
namespace ts::server::file {
|
||||
namespace filesystem {
|
||||
#ifdef FS_INCLUDED
|
||||
namespace fs = std::experimental::filesystem;
|
||||
#endif
|
||||
|
||||
class LocalFileSystem : public filesystem::AbstractProvider {
|
||||
using FileModifyError = filesystem::FileModifyError;
|
||||
using DirectoryModifyError = filesystem::DirectoryModifyError;
|
||||
public:
|
||||
virtual ~LocalFileSystem();
|
||||
|
||||
bool initialize(std::string & /* error */, const std::string & /* root path */);
|
||||
void finalize();
|
||||
|
||||
void lock_file(const std::string& /* path */);
|
||||
void unlock_file(const std::string& /* path */);
|
||||
|
||||
[[nodiscard]] inline const auto &root_path() const { return this->root_path_; }
|
||||
|
||||
std::shared_ptr<ExecuteResponse<ServerCommandError>> initialize_server(ServerId /* server */) override;
|
||||
std::shared_ptr<ExecuteResponse<ServerCommandError>> delete_server(ServerId /* server */) override;
|
||||
|
||||
std::shared_ptr<directory_query_response_t>
|
||||
query_channel_directory(ServerId id, ChannelId channelId, const std::string &string) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<DirectoryModifyError>>
|
||||
create_channel_directory(ServerId id, ChannelId channelId, const std::string &string) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileModifyError>>
|
||||
delete_channel_file(ServerId id, ChannelId channelId, const std::string &string) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileModifyError>>
|
||||
rename_channel_file(ServerId id, ChannelId channelId, const std::string &, const std::string &) override;
|
||||
|
||||
std::shared_ptr<directory_query_response_t> query_icon_directory(ServerId id) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileModifyError>>
|
||||
delete_icon(ServerId id, const std::string &string) override;
|
||||
|
||||
std::shared_ptr<directory_query_response_t> query_avatar_directory(ServerId id) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileModifyError>>
|
||||
delete_avatar(ServerId id, const std::string &string) override;
|
||||
|
||||
private:
|
||||
#ifdef FS_INCLUDED
|
||||
[[nodiscard]] fs::path server_path(ServerId);
|
||||
[[nodiscard]] fs::path server_channel_path(ServerId, ChannelId);
|
||||
[[nodiscard]] static bool exceeds_base_path(const fs::path& /* base */, const fs::path& /* target */);
|
||||
[[nodiscard]] bool is_any_file_locked(const fs::path& /* base */, const std::string& /* path */, std::string& /* file (relative to the base) */);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<ExecuteResponse<FileModifyError>>
|
||||
delete_file(const fs::path& /* base */, const std::string &string);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<directory_query_response_t>
|
||||
query_directory(const fs::path& /* base */, const std::string &string, bool);
|
||||
#endif
|
||||
|
||||
template <typename error_t, typename result_t = EmptyExecuteResponse>
|
||||
std::shared_ptr<ExecuteResponse<error_t, result_t>> create_execute_response() {
|
||||
return std::make_shared<ExecuteResponse<error_t, result_t>>(this->result_notify_mutex, this->result_notify_cv);
|
||||
}
|
||||
|
||||
std::mutex result_notify_mutex{};
|
||||
std::condition_variable result_notify_cv{};
|
||||
|
||||
std::string root_path_{};
|
||||
|
||||
std::mutex locked_files_mutex{};
|
||||
std::deque<std::string> locked_files_{};
|
||||
};
|
||||
}
|
||||
|
||||
class LocalFileServer : public AbstractFileServer {
|
||||
public:
|
||||
virtual ~LocalFileServer();
|
||||
|
||||
[[nodiscard]] bool initialize(std::string& /* error */);
|
||||
void finalize();
|
||||
|
||||
filesystem::AbstractProvider &file_system() override;
|
||||
|
||||
private:
|
||||
filesystem::LocalFileSystem file_system_{};
|
||||
};
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
#define FS_INCLUDED
|
||||
|
||||
#include <log/LogUtils.h>
|
||||
#include "./LocalFileServer.h"
|
||||
#include "LocalFileProvider.h"
|
||||
#include "clnpath.h"
|
||||
|
||||
using namespace ts::server::file;
|
||||
@ -21,7 +21,7 @@ bool LocalFileSystem::initialize(std::string &error_message, const std::string &
|
||||
|
||||
if(!fs::exists(root_path, error)) {
|
||||
if(error)
|
||||
logWarning(0, "Failed to check root path existence. Assuming it does not exist. ({}/{})", error.value(), error.message());
|
||||
logWarning(LOG_FT, "Failed to check root path existence. Assuming it does not exist. ({}/{})", error.value(), error.message());
|
||||
if(!fs::create_directories(root_path, error) || error) {
|
||||
error_message = "Failed to create root file system at " + root_path_string + ": " + std::to_string(error.value()) + "/" + error.message();
|
||||
return false;
|
||||
@ -29,7 +29,7 @@ bool LocalFileSystem::initialize(std::string &error_message, const std::string &
|
||||
}
|
||||
|
||||
auto croot = clnpath(fs::absolute(root_path).string());
|
||||
logMessage(0, "Started file system root at {}", croot);
|
||||
logMessage(LOG_FT, "Started file system root at {}", croot);
|
||||
this->root_path_ = croot;
|
||||
return true;
|
||||
}
|
||||
@ -56,6 +56,7 @@ bool LocalFileSystem::exceeds_base_path(const fs::path &base, const fs::path &ta
|
||||
bool LocalFileSystem::is_any_file_locked(const fs::path &base, const std::string &path, std::string &locked_file) {
|
||||
auto c_path = clnpath(fs::absolute(base / fs::u8path(path)).string());
|
||||
|
||||
std::lock_guard lock{this->locked_files_mutex};
|
||||
for(const auto& lfile : this->locked_files_) {
|
||||
if(lfile.starts_with(c_path)) {
|
||||
locked_file = lfile.substr(base.string().length());
|
||||
@ -66,6 +67,46 @@ bool LocalFileSystem::is_any_file_locked(const fs::path &base, const std::string
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string LocalFileSystem::target_file_path(FileCategory type, ts::ServerId sid, ts::ChannelId cid, const std::string &path) {
|
||||
fs::path target_path{};
|
||||
switch (type) {
|
||||
case FileCategory::CHANNEL:
|
||||
target_path = this->server_channel_path(sid, cid) / path;
|
||||
break;
|
||||
case FileCategory::ICON:
|
||||
target_path = this->server_path(sid) / "icons" / path;
|
||||
break;
|
||||
case FileCategory::AVATAR:
|
||||
target_path = this->server_path(sid) / "avatars" / path;
|
||||
break;
|
||||
}
|
||||
|
||||
return clnpath(fs::absolute(target_path).string());
|
||||
}
|
||||
|
||||
std::string LocalFileSystem::absolute_avatar_path(ServerId sid, const std::string &path) {
|
||||
return this->target_file_path(FileCategory::AVATAR, sid, 0, path);
|
||||
}
|
||||
|
||||
std::string LocalFileSystem::absolute_icon_path(ServerId sid, const std::string &path) {
|
||||
return this->target_file_path(FileCategory::ICON, sid, 0, path);
|
||||
}
|
||||
|
||||
std::string LocalFileSystem::absolute_channel_path(ServerId sid, ChannelId cid, const std::string &path) {
|
||||
return this->target_file_path(FileCategory::CHANNEL, sid, cid, path);
|
||||
}
|
||||
|
||||
void LocalFileSystem::lock_file(const std::string &c_path) {
|
||||
std::lock_guard lock{this->locked_files_mutex};
|
||||
this->locked_files_.push_back(c_path);
|
||||
}
|
||||
|
||||
void LocalFileSystem::unlock_file(const std::string &c_path) {
|
||||
std::lock_guard lock{this->locked_files_mutex};
|
||||
|
||||
this->locked_files_.erase(std::remove_if(this->locked_files_.begin(), this->locked_files_.end(), [&](const auto& p) { return p == c_path; }), this->locked_files_.end());
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<ServerCommandError>> LocalFileSystem::initialize_server(ServerId id) {
|
||||
auto path = this->server_path(id);
|
||||
std::error_code error{};
|
||||
@ -122,7 +163,7 @@ std::shared_ptr<directory_query_response_t> LocalFileSystem::query_directory(con
|
||||
response->emplace_fail(DirectoryQueryErrorType::PATH_DOES_NOT_EXISTS, "");
|
||||
return response;
|
||||
} else if(error) {
|
||||
logWarning(0, "Failed to check for file at {}: {}. Assuming it does not exists.", target_path.string(), error.value(), error.message());
|
||||
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does not exists.", target_path.string(), error.value(), error.message());
|
||||
response->emplace_fail(DirectoryQueryErrorType::PATH_DOES_NOT_EXISTS, "");
|
||||
return response;
|
||||
}
|
||||
@ -131,7 +172,7 @@ std::shared_ptr<directory_query_response_t> LocalFileSystem::query_directory(con
|
||||
response->emplace_fail(DirectoryQueryErrorType::PATH_IS_A_FILE, "");
|
||||
return response;
|
||||
} else if(error) {
|
||||
logWarning(0, "Failed to check for directory at {}: {}. Assuming its not a directory.", target_path.string(), error.value(), error.message());
|
||||
logWarning(LOG_FT, "Failed to check for directory at {}: {}. Assuming its not a directory.", target_path.string(), error.value(), error.message());
|
||||
response->emplace_fail(DirectoryQueryErrorType::PATH_IS_A_FILE, "");
|
||||
return response;
|
||||
}
|
||||
@ -140,7 +181,7 @@ std::shared_ptr<directory_query_response_t> LocalFileSystem::query_directory(con
|
||||
for(auto& entry : fs::directory_iterator(target_path, error)) {
|
||||
auto status = entry.status(error);
|
||||
if(error) {
|
||||
logWarning(0, "Failed to query file status for {} ({}/{}). Skipping entry for directory query.", entry.path().string(), error.value(), error.message());
|
||||
logWarning(LOG_FT, "Failed to query file status for {} ({}/{}). Skipping entry for directory query.", entry.path().string(), error.value(), error.message());
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -151,7 +192,7 @@ std::shared_ptr<directory_query_response_t> LocalFileSystem::query_directory(con
|
||||
|
||||
dentry.modified_at = fs::last_write_time(entry.path(), error);
|
||||
if(error)
|
||||
logWarning(0, "Failed to query last write time for directory {} ({}/{})", entry.path().string(), error.value(), error.message());
|
||||
logWarning(LOG_FT, "Failed to query last write time for directory {} ({}/{})", entry.path().string(), error.value(), error.message());
|
||||
dentry.size = 0;
|
||||
} else if(status.type() == fs::file_type::regular) {
|
||||
auto& dentry = entries.emplace_back();
|
||||
@ -160,12 +201,12 @@ std::shared_ptr<directory_query_response_t> LocalFileSystem::query_directory(con
|
||||
|
||||
dentry.modified_at = fs::last_write_time(entry.path(), error);
|
||||
if(error)
|
||||
logWarning(0, "Failed to query last write time for file {} ({}/{}).", entry.path().string(), error.value(), error.message());
|
||||
logWarning(LOG_FT, "Failed to query last write time for file {} ({}/{}).", entry.path().string(), error.value(), error.message());
|
||||
dentry.size = fs::file_size(entry.path(), error);
|
||||
if(error)
|
||||
logWarning(0, "Failed to query size for file {} ({}/{}).", entry.path().string(), error.value(), error.message());
|
||||
logWarning(LOG_FT, "Failed to query size for file {} ({}/{}).", entry.path().string(), error.value(), error.message());
|
||||
} else {
|
||||
logWarning(0, "Directory query listed an unknown file type for file {} ({}).", entry.path().string(), (int) status.type());
|
||||
logWarning(LOG_FT, "Directory query listed an unknown file type for file {} ({}).", entry.path().string(), (int) status.type());
|
||||
}
|
||||
}
|
||||
if(error && entries.empty()) {
|
||||
@ -203,7 +244,7 @@ std::shared_ptr<ExecuteResponse<DirectoryModifyError>> LocalFileSystem::create_c
|
||||
response->emplace_fail(DirectoryModifyErrorType::PATH_ALREADY_EXISTS, "");
|
||||
return response;
|
||||
} else if(error) {
|
||||
logWarning(0, "Failed to check for file at {}: {}. Assuming it does not exists.", target_path.string(), error.value(), error.message());
|
||||
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does not exists.", target_path.string(), error.value(), error.message());
|
||||
}
|
||||
|
||||
if(!fs::create_directories(target_path, error) || error) {
|
||||
@ -237,7 +278,7 @@ std::shared_ptr<ExecuteResponse<FileModifyError>> LocalFileSystem::rename_channe
|
||||
response->emplace_fail(FileModifyErrorType::PATH_DOES_NOT_EXISTS, "");
|
||||
return response;
|
||||
} else if(error) {
|
||||
logWarning(0, "Failed to check for file at {}: {}. Assuming it does not exists.", current_path.string(), error.value(), error.message());
|
||||
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does not exists.", current_path.string(), error.value(), error.message());
|
||||
response->emplace_fail(FileModifyErrorType::PATH_DOES_NOT_EXISTS, "");
|
||||
return response;
|
||||
}
|
||||
@ -246,7 +287,7 @@ std::shared_ptr<ExecuteResponse<FileModifyError>> LocalFileSystem::rename_channe
|
||||
response->emplace_fail(FileModifyErrorType::TARGET_PATH_ALREADY_EXISTS, "");
|
||||
return response;
|
||||
} else if(error) {
|
||||
logWarning(0, "Failed to check for file at {}: {}. Assuming it does exists.", current_path.string(), error.value(), error.message());
|
||||
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does exists.", current_path.string(), error.value(), error.message());
|
||||
response->emplace_fail(FileModifyErrorType::TARGET_PATH_ALREADY_EXISTS, "");
|
||||
return response;
|
||||
}
|
||||
@ -277,7 +318,7 @@ std::shared_ptr<ExecuteResponse<FileModifyError>> LocalFileSystem::delete_file(c
|
||||
response->emplace_fail(FileModifyErrorType::TARGET_PATH_ALREADY_EXISTS, "");
|
||||
return response;
|
||||
} else if(error) {
|
||||
logWarning(0, "Failed to check for file at {}: {}. Assuming it does exists.", target_path.string(), error.value(), error.message());
|
||||
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does exists.", target_path.string(), error.value(), error.message());
|
||||
response->emplace_fail(FileModifyErrorType::TARGET_PATH_ALREADY_EXISTS, "");
|
||||
return response;
|
||||
}
|
||||
|
208
file/local_server/LocalFileTransfer.cpp
Normal file
208
file/local_server/LocalFileTransfer.cpp
Normal file
@ -0,0 +1,208 @@
|
||||
//
|
||||
// Created by WolverinDEV on 04/05/2020.
|
||||
//
|
||||
|
||||
#include <cassert>
|
||||
#include <event2/event.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include <random>
|
||||
#include "./LocalFileProvider.h"
|
||||
#include "LocalFileProvider.h"
|
||||
|
||||
using namespace ts::server::file;
|
||||
using namespace ts::server::file::transfer;
|
||||
|
||||
Buffer* transfer::allocate_buffer(size_t size) {
|
||||
auto total_size = sizeof(Buffer) + size;
|
||||
auto buffer = (Buffer*) malloc(total_size);
|
||||
new (buffer) Buffer{};
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void transfer::free_buffer(Buffer* buffer) {
|
||||
buffer->~Buffer();
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
FileClient::~FileClient() {
|
||||
auto head = this->buffer.buffer_head;
|
||||
while (head) {
|
||||
auto next = head->next;
|
||||
free_buffer(head);
|
||||
head = next;
|
||||
}
|
||||
|
||||
assert(!this->file.file_descriptor);
|
||||
assert(!this->file.currently_processing);
|
||||
assert(!this->file.next_client);
|
||||
|
||||
assert(!this->networking.event_read);
|
||||
assert(!this->networking.event_write);
|
||||
|
||||
assert(this->state == STATE_DISCONNECTED);
|
||||
}
|
||||
|
||||
LocalFileTransfer::LocalFileTransfer(filesystem::LocalFileSystem &fs) : file_system_{fs} {}
|
||||
LocalFileTransfer::~LocalFileTransfer() = default;
|
||||
|
||||
bool LocalFileTransfer::start(const std::deque<std::shared_ptr<NetworkBinding>>& bindings) {
|
||||
(void) this->start_client_worker();
|
||||
|
||||
{
|
||||
auto start_result = this->start_disk_io();
|
||||
switch (start_result) {
|
||||
case DiskIOStartResult::SUCCESS:
|
||||
break;
|
||||
case DiskIOStartResult::OUT_OF_MEMORY:
|
||||
logError(LOG_FT, "Failed to start disk worker (Out of memory)");
|
||||
goto error_exit_disk;
|
||||
default:
|
||||
logError(LOG_FT, "Failed to start disk worker ({})", (int) start_result);
|
||||
goto error_exit_disk;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
this->network.bindings = bindings;
|
||||
auto start_result = this->start_networking();
|
||||
switch (start_result) {
|
||||
case NetworkingStartResult::SUCCESS:
|
||||
break;
|
||||
case NetworkingStartResult::OUT_OF_MEMORY:
|
||||
logError(LOG_FT, "Failed to start networking (Out of memory)");
|
||||
goto error_exit_network;
|
||||
case NetworkingStartResult::NO_BINDINGS:
|
||||
logError(LOG_FT, "Failed to start networking (No address could be bound)");
|
||||
goto error_exit_network;
|
||||
default:
|
||||
logError(LOG_FT, "Failed to start networking ({})", (int) start_result);
|
||||
goto error_exit_network;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
error_exit_network:
|
||||
this->shutdown_networking();
|
||||
|
||||
error_exit_disk:
|
||||
this->shutdown_disk_io();
|
||||
this->shutdown_client_worker();
|
||||
return false;
|
||||
}
|
||||
|
||||
void LocalFileTransfer::stop() {
|
||||
this->shutdown_networking();
|
||||
this->shutdown_disk_io();
|
||||
this->shutdown_client_worker();
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> LocalFileTransfer::initialize_icon_transfer(Transfer::Direction direction, ServerId sid, const TransferInfo &info) {
|
||||
return this->initialize_transfer(direction, sid, 0, Transfer::TARGET_TYPE_ICON, info);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> LocalFileTransfer::initialize_avatar_transfer(Transfer::Direction direction, ServerId sid, const TransferInfo &info) {
|
||||
return this->initialize_transfer(direction, sid, 0, Transfer::TARGET_TYPE_AVATAR, info);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> LocalFileTransfer::initialize_channel_transfer(Transfer::Direction direction, ServerId sid, ChannelId cid, const TransferInfo &info) {
|
||||
return this->initialize_transfer(direction, sid, cid, Transfer::TARGET_TYPE_CHANNEL_FILE, info);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> LocalFileTransfer::initialize_transfer(
|
||||
Transfer::Direction direction, ServerId sid, ChannelId cid,
|
||||
Transfer::TargetType ttype,
|
||||
const TransferInfo &info) {
|
||||
auto response = this->create_execute_response<TransferInitError, std::shared_ptr<Transfer>>();
|
||||
|
||||
/* TODO: test for a transfer limit */
|
||||
|
||||
auto transfer = std::make_shared<Transfer>();
|
||||
transfer->server_transfer_id = ++this->current_transfer_id;
|
||||
transfer->server_id = sid;
|
||||
transfer->channel_id = cid;
|
||||
transfer->target_type = ttype;
|
||||
transfer->direction = direction;
|
||||
|
||||
transfer->client_id = 0; /* must be provided externally */
|
||||
transfer->client_transfer_id = 0; /* must be provided externally */
|
||||
|
||||
transfer->server_addresses.reserve(this->network.bindings.size());
|
||||
for(auto& binding : this->network.bindings) {
|
||||
if(!binding->file_descriptor) continue;
|
||||
|
||||
transfer->server_addresses.emplace_back(Transfer::Address{binding->hostname, net::port(binding->address)});
|
||||
}
|
||||
|
||||
transfer->target_file_path = info.file_path;
|
||||
transfer->file_offset = info.file_offset;
|
||||
transfer->expected_file_size = info.expected_file_size;
|
||||
transfer->max_bandwidth = info.max_bandwidth;
|
||||
|
||||
constexpr static std::string_view kTokenCharacters{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"};
|
||||
for(auto& c : transfer->transfer_key)
|
||||
c = kTokenCharacters[transfer_random_token_generator() % kTokenCharacters.length()];
|
||||
transfer->transfer_key[0] = (char) 'r'; /* (114) */ /* a non valid SSL header type to indicate that we're using a file transfer key and not doing a SSL handshake */
|
||||
transfer->transfer_key[1] = (char) 'a'; /* ( 97) */
|
||||
transfer->transfer_key[2] = (char) 'w'; /* (119) */
|
||||
|
||||
transfer->initialized_timestamp = std::chrono::system_clock::now();
|
||||
|
||||
{
|
||||
std::lock_guard tlock{this->transfers_mutex};
|
||||
this->pending_transfers.push_back(transfer);
|
||||
}
|
||||
|
||||
if(auto callback{this->callback_transfer_registered}; callback)
|
||||
callback(transfer);
|
||||
|
||||
response->emplace_success(std::move(transfer));
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferActionError>> LocalFileTransfer::stop_transfer(transfer_id id, bool flush) {
|
||||
auto response = this->create_execute_response<TransferActionError>();
|
||||
|
||||
std::shared_ptr<Transfer> transfer{};
|
||||
std::shared_ptr<FileClient> connected_transfer{};
|
||||
|
||||
{
|
||||
std::lock_guard tlock{this->transfers_mutex};
|
||||
|
||||
auto ct_it = std::find_if(this->transfers_.begin(), this->transfers_.end(), [&](const std::shared_ptr<FileClient>& t) {
|
||||
return t->transfer && t->transfer->server_transfer_id == id;
|
||||
});
|
||||
if(ct_it != this->transfers_.end())
|
||||
connected_transfer = *ct_it;
|
||||
else {
|
||||
auto t_it = std::find_if(this->pending_transfers.begin(), this->pending_transfers.end(), [&](const std::shared_ptr<Transfer>& t) {
|
||||
return t->server_transfer_id == id;
|
||||
});
|
||||
if(t_it != this->pending_transfers.end()) {
|
||||
transfer = *t_it;
|
||||
this->pending_transfers.erase(t_it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!transfer) {
|
||||
if(connected_transfer)
|
||||
transfer = connected_transfer->transfer;
|
||||
else {
|
||||
response->emplace_fail(TransferActionError{TransferActionError::UNKNOWN_TRANSFER, ""});
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
if(connected_transfer) {
|
||||
logMessage(LOG_FT, "{} Stopping transfer due to an user request.", connected_transfer->log_prefix());
|
||||
|
||||
std::unique_lock slock{connected_transfer->state_mutex};
|
||||
this->disconnect_client(connected_transfer, slock, flush);
|
||||
} else {
|
||||
logMessage(LOG_FT, "Removing pending file transfer for id {}", id);
|
||||
}
|
||||
|
||||
response->emplace_success();
|
||||
return response;
|
||||
}
|
207
file/local_server/LocalFileTransferClientWorker.cpp
Normal file
207
file/local_server/LocalFileTransferClientWorker.cpp
Normal file
@ -0,0 +1,207 @@
|
||||
//
|
||||
// Created by WolverinDEV on 04/05/2020.
|
||||
//
|
||||
|
||||
#include <cassert>
|
||||
#include <event2/event.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include "./LocalFileProvider.h"
|
||||
|
||||
using namespace ts::server::file;
|
||||
using namespace ts::server::file::transfer;
|
||||
|
||||
ClientWorkerStartResult LocalFileTransfer::start_client_worker() {
|
||||
assert(!this->disconnect.active);
|
||||
this->disconnect.active = true;
|
||||
|
||||
this->disconnect.dispatch_thread = std::thread(&LocalFileTransfer::dispatch_loop_client_worker, this);
|
||||
return ClientWorkerStartResult::SUCCESS;
|
||||
}
|
||||
|
||||
void LocalFileTransfer::shutdown_client_worker() {
|
||||
if(!this->disconnect.active) return;
|
||||
this->disconnect.active = false;
|
||||
|
||||
this->disconnect.notify_cv.notify_all();
|
||||
if(this->disconnect.dispatch_thread.joinable())
|
||||
this->disconnect.dispatch_thread.join();
|
||||
|
||||
{
|
||||
std::unique_lock tlock{this->transfers_mutex};
|
||||
if(!this->transfers_.empty())
|
||||
logWarning(LOG_FT, "Shutting down disconnect worker even thou we still have some active clients. This could cause memory leaks.");
|
||||
}
|
||||
}
|
||||
|
||||
void LocalFileTransfer::disconnect_client(const std::shared_ptr<FileClient> &client, std::unique_lock<std::shared_mutex>& state_lock, bool flush) {
|
||||
assert(state_lock.owns_lock());
|
||||
|
||||
if(client->state == FileClient::STATE_DISCONNECTED || (client->state == FileClient::STATE_DISCONNECTING && flush)) {
|
||||
return; /* shall NOT happen */
|
||||
}
|
||||
|
||||
#define del_ev_noblock(event) if(event) event_del_noblock(event)
|
||||
|
||||
client->state = flush ? FileClient::STATE_DISCONNECTING : FileClient::STATE_DISCONNECTED;
|
||||
client->timings.disconnecting = std::chrono::system_clock::now();
|
||||
if(flush) {
|
||||
if(client->transfer->direction == Transfer::DIRECTION_UPLOAD) {
|
||||
del_ev_noblock(client->networking.event_read);
|
||||
del_ev_noblock(client->networking.event_write);
|
||||
del_ev_noblock(client->networking.event_throttle);
|
||||
|
||||
/* no direct timeout needed here, we're just flushing the disk */
|
||||
this->enqueue_disk_io(client);
|
||||
} else if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
del_ev_noblock(client->networking.event_read);
|
||||
client->add_network_write_event_nolock(false);
|
||||
|
||||
/* max flush 10 seconds */
|
||||
client->networking.disconnect_timeout = std::chrono::system_clock::now() + std::chrono::seconds{10};
|
||||
}
|
||||
} else {
|
||||
del_ev_noblock(client->networking.event_read);
|
||||
del_ev_noblock(client->networking.event_write);
|
||||
del_ev_noblock(client->networking.event_throttle);
|
||||
|
||||
this->disconnect.notify_cv.notify_one();
|
||||
}
|
||||
|
||||
#undef del_ev_noblock
|
||||
}
|
||||
|
||||
void LocalFileTransfer::dispatch_loop_client_worker(void *ptr_transfer) {
|
||||
auto provider = reinterpret_cast<LocalFileTransfer*>(ptr_transfer);
|
||||
|
||||
while(provider->disconnect.active) {
|
||||
{
|
||||
std::unique_lock dlock{provider->disconnect.mutex};
|
||||
provider->disconnect.notify_cv.wait_for(dlock, std::chrono::seconds{1});
|
||||
}
|
||||
/* run the disconnect worker at least once before exiting */
|
||||
|
||||
/* transfer statistics */
|
||||
{
|
||||
std::unique_lock tlock{provider->transfers_mutex};
|
||||
auto transfers = provider->transfers_;
|
||||
tlock.unlock();
|
||||
for(const auto& transfer : transfers) {
|
||||
switch(transfer->state) {
|
||||
case FileClient::STATE_TRANSFERRING:
|
||||
break;
|
||||
case FileClient::STATE_DISCONNECTING:
|
||||
if(transfer->transfer && transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD)
|
||||
break; /* we're still transferring (sending data) */
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
provider->report_transfer_statistics(transfer->shared_from_this());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::deque<std::shared_ptr<Transfer>> timeouted_transfers{};
|
||||
|
||||
{
|
||||
std::unique_lock tlock{provider->transfers_mutex};
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
std::copy_if(provider->pending_transfers.begin(), provider->pending_transfers.end(), std::back_inserter(timeouted_transfers), [&](const std::shared_ptr<Transfer>& t) {
|
||||
return t->initialized_timestamp + std::chrono::seconds{100} < now; //FIXME: Decrease to 10 again!
|
||||
});
|
||||
provider->pending_transfers.erase(std::remove_if(provider->pending_transfers.begin(), provider->pending_transfers.end(), [&](const auto& t) {
|
||||
return std::find(timeouted_transfers.begin(), timeouted_transfers.end(), t) != timeouted_transfers.end();
|
||||
}), provider->pending_transfers.end());
|
||||
}
|
||||
|
||||
for(const auto& pt : timeouted_transfers) {
|
||||
if(auto callback{provider->callback_transfer_aborted}; callback)
|
||||
callback(pt, { TransferError::TRANSFER_TIMEOUT, "" });
|
||||
}
|
||||
|
||||
if(!timeouted_transfers.empty())
|
||||
logMessage(LOG_FT, "Removed {} pending transfers because no request has been made for them.", timeouted_transfers.size());
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
std::deque<std::shared_ptr<FileClient>> disconnected_clients{};
|
||||
{
|
||||
std::unique_lock tlock{provider->transfers_mutex};
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
std::copy_if(provider->transfers_.begin(), provider->transfers_.end(), std::back_inserter(disconnected_clients), [&](const std::shared_ptr<FileClient>& t) {
|
||||
std::shared_lock slock{t->state_mutex};
|
||||
if(t->state == FileClient::STATE_DISCONNECTED) {
|
||||
return true;
|
||||
} else if(t->state == FileClient::STATE_AWAITING_KEY) {
|
||||
return t->timings.connected + std::chrono::seconds{10} < now;
|
||||
} else if(t->state == FileClient::STATE_TRANSFERRING) {
|
||||
assert(t->transfer);
|
||||
if(t->transfer->direction == Transfer::DIRECTION_UPLOAD) {
|
||||
return false; //FIXME: Due to debugging reasons
|
||||
return t->timings.last_read + std::chrono::seconds{5} < now;
|
||||
} else if(t->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
return t->timings.last_write + std::chrono::seconds{5} < now;
|
||||
}
|
||||
} else if(t->state == FileClient::STATE_DISCONNECTING) {
|
||||
return t->timings.disconnecting + std::chrono::seconds{30} < now;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
provider->transfers_.erase(std::remove_if(provider->transfers_.begin(), provider->transfers_.end(), [&](const auto& t) {
|
||||
return std::find(disconnected_clients.begin(), disconnected_clients.end(), t) != disconnected_clients.end();
|
||||
}), provider->transfers_.end());
|
||||
}
|
||||
|
||||
for(auto& client : disconnected_clients) {
|
||||
switch(client->state) {
|
||||
case FileClient::STATE_AWAITING_KEY:
|
||||
logMessage(LOG_FT, "{} Received no key. Dropping client.", client->log_prefix());
|
||||
break;
|
||||
case FileClient::STATE_TRANSFERRING:
|
||||
logMessage(LOG_FT, "{} Networking timeout. Dropping client", client->log_prefix());
|
||||
if(auto callback{provider->callback_transfer_aborted}; callback)
|
||||
callback(client->transfer, { TransferError::TRANSFER_TIMEOUT, "" });
|
||||
break;
|
||||
case FileClient::STATE_DISCONNECTING:
|
||||
logMessage(LOG_FT, "{} Failed to flush connection. Dropping client", client->log_prefix());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
{
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->state = FileClient::STATE_DISCONNECTED;
|
||||
provider->finalize_file_io(client, slock);
|
||||
provider->finalize_networking(client, slock);
|
||||
}
|
||||
|
||||
debugMessage(LOG_FT, "{} Destroying transfer.", client->log_prefix());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LocalFileTransfer::report_transfer_statistics(const std::shared_ptr<FileClient> &client) {
|
||||
auto callback{this->callback_transfer_statistics};
|
||||
if(!callback) return;
|
||||
|
||||
TransferStatistics stats{};
|
||||
|
||||
stats.network_bytes_send = client->statistics.network_bytes_send;
|
||||
stats.network_bytes_received = client->statistics.network_bytes_received;
|
||||
stats.file_bytes_transferred = client->statistics.file_bytes_transferred;
|
||||
|
||||
stats.delta_network_bytes_received = stats.network_bytes_received - std::exchange(client->statistics.last_network_bytes_received, stats.network_bytes_received);
|
||||
stats.delta_network_bytes_send = stats.network_bytes_send - std::exchange(client->statistics.last_network_bytes_send, stats.network_bytes_send);
|
||||
|
||||
stats.delta_file_bytes_transferred = stats.file_bytes_transferred - std::exchange(client->statistics.last_file_bytes_transferred, stats.file_bytes_transferred);
|
||||
|
||||
stats.file_start_offset = client->transfer->file_offset;
|
||||
stats.file_current_offset = client->statistics.file_bytes_transferred + client->transfer->file_offset;
|
||||
stats.file_total_size = client->transfer->expected_file_size;
|
||||
|
||||
callback(client->transfer, stats);
|
||||
}
|
473
file/local_server/LocalFileTransferDisk.cpp
Normal file
473
file/local_server/LocalFileTransferDisk.cpp
Normal file
@ -0,0 +1,473 @@
|
||||
//
|
||||
// Created by WolverinDEV on 04/05/2020.
|
||||
//
|
||||
|
||||
#include <cassert>
|
||||
#include <event.h>
|
||||
#include <experimental/filesystem>
|
||||
#include <log/LogUtils.h>
|
||||
#include "./LocalFileProvider.h"
|
||||
#include "./duration_utils.h"
|
||||
#include "LocalFileProvider.h"
|
||||
|
||||
using namespace ts::server::file;
|
||||
using namespace ts::server::file::transfer;
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
DiskIOStartResult LocalFileTransfer::start_disk_io() {
|
||||
assert(this->disk_io.state == DiskIOLoopState::STOPPED);
|
||||
|
||||
this->disk_io.state = DiskIOLoopState::RUNNING;
|
||||
this->disk_io.dispatch_thread = std::thread(&LocalFileTransfer::dispatch_loop_disk_io, this);
|
||||
return DiskIOStartResult::SUCCESS;
|
||||
}
|
||||
|
||||
void LocalFileTransfer::shutdown_disk_io() {
|
||||
if(this->disk_io.state == DiskIOLoopState::STOPPED) return;
|
||||
|
||||
this->disk_io.state = DiskIOLoopState::STOPPING;
|
||||
{
|
||||
std::unique_lock qlock{this->disk_io.queue_lock};
|
||||
this->disk_io.notify_work_awaiting.notify_all();
|
||||
while(this->disk_io.queue_head)
|
||||
this->disk_io.notify_client_processed.wait_for(qlock, std::chrono::seconds{10});
|
||||
|
||||
if(this->disk_io.queue_head) {
|
||||
logWarning(0, "Failed to flush disk IO. Force aborting.");
|
||||
this->disk_io.state = DiskIOLoopState::FORCE_STOPPING;
|
||||
this->disk_io.notify_work_awaiting.notify_all();
|
||||
this->disk_io.notify_client_processed.wait(qlock);
|
||||
}
|
||||
}
|
||||
|
||||
if(this->disk_io.dispatch_thread.joinable())
|
||||
this->disk_io.dispatch_thread.join();
|
||||
|
||||
this->disk_io.state = DiskIOLoopState::STOPPED;
|
||||
}
|
||||
|
||||
void LocalFileTransfer::dispatch_loop_disk_io(void *provider_ptr) {
|
||||
auto provider = reinterpret_cast<LocalFileTransfer*>(provider_ptr);
|
||||
|
||||
std::shared_ptr<FileClient> client{};
|
||||
while(true) {
|
||||
{
|
||||
std::unique_lock qlock{provider->disk_io.queue_lock};
|
||||
if(client) {
|
||||
client->file.currently_processing = false;
|
||||
provider->disk_io.notify_client_processed.notify_all();
|
||||
client = nullptr;
|
||||
}
|
||||
|
||||
provider->disk_io.notify_work_awaiting.wait(qlock, [&]{ return provider->disk_io.state != DiskIOLoopState::RUNNING || provider->disk_io.queue_head != nullptr; });
|
||||
|
||||
if(provider->disk_io.queue_head) {
|
||||
client = provider->disk_io.queue_head->shared_from_this();
|
||||
|
||||
provider->disk_io.queue_head = provider->disk_io.queue_head->file.next_client;
|
||||
if(!provider->disk_io.queue_head)
|
||||
provider->disk_io.queue_tail = &provider->disk_io.queue_head;
|
||||
}
|
||||
|
||||
if(provider->disk_io.state != DiskIOLoopState::RUNNING) {
|
||||
if(provider->disk_io.state == DiskIOLoopState::STOPPING) {
|
||||
if(!client)
|
||||
break;
|
||||
/* break only if all clients have been flushed */
|
||||
} else {
|
||||
/* force stopping without any flushing */
|
||||
auto fclient = &*client;
|
||||
while(fclient)
|
||||
fclient = std::exchange(fclient->file.next_client, nullptr);
|
||||
|
||||
provider->disk_io.queue_head = nullptr;
|
||||
provider->disk_io.queue_tail = &provider->disk_io.queue_head;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!client)
|
||||
continue;
|
||||
|
||||
client->file.currently_processing = true;
|
||||
client->file.next_client = nullptr;
|
||||
}
|
||||
|
||||
provider->execute_disk_io(client);
|
||||
}
|
||||
provider->disk_io.notify_client_processed.notify_all();
|
||||
}
|
||||
|
||||
FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr<FileClient> &transfer) {
|
||||
FileInitializeResult result{FileInitializeResult::SUCCESS};
|
||||
assert(transfer->transfer);
|
||||
|
||||
std::shared_lock slock{transfer->state_mutex};
|
||||
auto& file_data = transfer->file;
|
||||
assert(!file_data.file_descriptor);
|
||||
assert(!file_data.next_client);
|
||||
|
||||
{
|
||||
unsigned int open_flags{0};
|
||||
if(transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
open_flags = O_RDONLY;
|
||||
|
||||
std::error_code fs_error{};
|
||||
if(file_data.absolute_path.empty() || !fs::exists(file_data.absolute_path, fs_error)) {
|
||||
result = FileInitializeResult::FILE_DOES_NOT_EXISTS;
|
||||
goto error_exit;
|
||||
} else if(fs_error) {
|
||||
logWarning(LOG_FT, "{} Failed to check for file existence of {}: {}/{}", transfer->log_prefix(), file_data.absolute_path, fs_error.value(), fs_error.message());
|
||||
result = FileInitializeResult::FILE_SYSTEM_ERROR;
|
||||
goto error_exit;
|
||||
}
|
||||
} else if(transfer->transfer->direction == Transfer::DIRECTION_UPLOAD) {
|
||||
open_flags = (unsigned) O_WRONLY | (unsigned) O_CREAT;
|
||||
if(transfer->transfer->override_exiting)
|
||||
open_flags |= (unsigned) O_TRUNC;
|
||||
} else {
|
||||
return FileInitializeResult::INVALID_TRANSFER_DIRECTION;
|
||||
}
|
||||
|
||||
file_data.file_descriptor = open(file_data.absolute_path.c_str(), (int) open_flags, 0644);
|
||||
if(file_data.file_descriptor <= 0) {
|
||||
const auto errno_ = errno;
|
||||
switch (errno_) {
|
||||
case EACCES:
|
||||
result = FileInitializeResult::FILE_IS_NOT_ACCESSIBLE;
|
||||
break;
|
||||
case EDQUOT:
|
||||
logWarning(LOG_FT, "{} Disk inode limit has been reached. Failed to start file transfer for file {}", transfer->log_prefix(), file_data.absolute_path);
|
||||
result = FileInitializeResult::FILE_SYSTEM_ERROR;
|
||||
break;
|
||||
case EISDIR:
|
||||
result = FileInitializeResult::FILE_IS_A_DIRECTORY;
|
||||
break;
|
||||
case EMFILE:
|
||||
result = FileInitializeResult::PROCESS_FILE_LIMIT_REACHED;
|
||||
break;
|
||||
case ENFILE:
|
||||
result = FileInitializeResult::SYSTEM_FILE_LIMIT_REACHED;
|
||||
break;
|
||||
case ETXTBSY:
|
||||
result = FileInitializeResult::FILE_IS_BUSY;
|
||||
break;
|
||||
case EROFS:
|
||||
result = FileInitializeResult::DISK_IS_READ_ONLY;
|
||||
break;
|
||||
default:
|
||||
logWarning(LOG_FT, "{} Failed to start file transfer for file {}: {}/{}", transfer->log_prefix(), file_data.absolute_path, errno_, strerror(errno_));
|
||||
result = FileInitializeResult::FILE_SYSTEM_ERROR;
|
||||
break;
|
||||
}
|
||||
goto error_exit;
|
||||
}
|
||||
}
|
||||
|
||||
this->file_system_.lock_file(transfer->file.absolute_path);
|
||||
transfer->file.file_locked = true;
|
||||
|
||||
if(transfer->transfer->direction == Transfer::DIRECTION_UPLOAD) {
|
||||
if(ftruncate(file_data.file_descriptor, transfer->transfer->expected_file_size) != 0) {
|
||||
const auto errno_ = errno;
|
||||
switch (errno_) {
|
||||
case EACCES:
|
||||
logWarning(LOG_FT, "{} File {} got inaccessible on truncating, but not on opening.", transfer->log_prefix(), file_data.absolute_path);
|
||||
result = FileInitializeResult::FILE_IS_NOT_ACCESSIBLE;
|
||||
goto error_exit;
|
||||
|
||||
case EFBIG:
|
||||
result = FileInitializeResult::FILE_TOO_LARGE;
|
||||
goto error_exit;
|
||||
|
||||
case EIO:
|
||||
logWarning(LOG_FT, "{} A disk IO error occurred while resizing file {}.", transfer->log_prefix(), file_data.absolute_path);
|
||||
result = FileInitializeResult::FILE_IS_NOT_ACCESSIBLE;
|
||||
goto error_exit;
|
||||
|
||||
case EROFS:
|
||||
logWarning(LOG_FT, "{} Failed to resize file {} because disk is in read only mode.", transfer->log_prefix(), file_data.absolute_path);
|
||||
result = FileInitializeResult::FILE_IS_NOT_ACCESSIBLE;
|
||||
goto error_exit;
|
||||
default:
|
||||
debugMessage(LOG_FT, "{} Failed to truncate file {}: {}/{}. Trying to upload file anyways.", transfer->log_prefix(), file_data.absolute_path, errno_, strerror(errno_));
|
||||
}
|
||||
}
|
||||
} else if(transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
auto file_size = lseek(file_data.file_descriptor, 0, SEEK_END);
|
||||
if(file_size != transfer->transfer->expected_file_size) {
|
||||
logWarning(LOG_FT, "{} Expected target file to be of size {}, but file is actually of size {}", transfer->log_prefix(), transfer->transfer->expected_file_size, file_size);
|
||||
result = FileInitializeResult::FILE_SIZE_MISMATCH;
|
||||
goto error_exit;
|
||||
}
|
||||
}
|
||||
{
|
||||
auto new_pos = lseek(file_data.file_descriptor, transfer->transfer->file_offset, SEEK_SET);
|
||||
if(new_pos < 0) {
|
||||
logWarning(LOG_FT, "{} Failed to seek to target file offset ({}): {}/{}", transfer->log_prefix(), transfer->transfer->file_offset, errno, strerror(errno));
|
||||
result = FileInitializeResult::FILE_SEEK_FAILED;
|
||||
goto error_exit;
|
||||
} else if(new_pos != transfer->transfer->file_offset) {
|
||||
logWarning(LOG_FT, "{} File rw offset mismatch after seek. Expected {} but received {}", transfer->log_prefix(), transfer->transfer->file_offset, new_pos);
|
||||
result = FileInitializeResult::FILE_SEEK_FAILED;
|
||||
goto error_exit;
|
||||
}
|
||||
debugMessage(LOG_FT, "{} Seek to file offset {}. New actual offset is {}", transfer->log_prefix(), transfer->transfer->file_offset, new_pos);
|
||||
}
|
||||
|
||||
return FileInitializeResult::SUCCESS;
|
||||
error_exit:
|
||||
if(std::exchange(transfer->file.file_locked, false))
|
||||
this->file_system_.unlock_file(transfer->file.absolute_path);
|
||||
|
||||
if(file_data.file_descriptor > 0)
|
||||
::close(file_data.file_descriptor);
|
||||
file_data.file_descriptor = 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void LocalFileTransfer::finalize_file_io(const std::shared_ptr<FileClient> &transfer,
|
||||
std::unique_lock<std::shared_mutex> &state_lock) {
|
||||
assert(state_lock.owns_lock());
|
||||
|
||||
auto& file_data = transfer->file;
|
||||
|
||||
state_lock.unlock();
|
||||
{
|
||||
std::unique_lock dlock{this->disk_io.queue_lock};
|
||||
while(true) {
|
||||
if(file_data.currently_processing) {
|
||||
this->disk_io.notify_client_processed.wait(dlock);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(file_data.next_client) {
|
||||
if(this->disk_io.queue_head == &*transfer) {
|
||||
this->disk_io.queue_head = file_data.next_client;
|
||||
if(!this->disk_io.queue_head)
|
||||
this->disk_io.queue_tail = &this->disk_io.queue_head;
|
||||
} else {
|
||||
FileClient* head{this->disk_io.queue_head};
|
||||
while(head->file.next_client != &*transfer) {
|
||||
assert(head->file.next_client);
|
||||
head = head->file.next_client;
|
||||
}
|
||||
|
||||
head->file.next_client = file_data.next_client;
|
||||
if(!file_data.next_client)
|
||||
this->disk_io.queue_tail = &head->file.next_client;
|
||||
|
||||
}
|
||||
file_data.next_client = nullptr;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
state_lock.lock();
|
||||
|
||||
if(std::exchange(file_data.file_locked, false))
|
||||
this->file_system_.unlock_file(file_data.absolute_path);
|
||||
|
||||
if(file_data.file_descriptor > 0)
|
||||
::close(file_data.file_descriptor);
|
||||
file_data.file_descriptor = 0;
|
||||
}
|
||||
|
||||
void LocalFileTransfer::enqueue_disk_io(const std::shared_ptr<FileClient> &client) {
|
||||
if(!client->file.file_descriptor)
|
||||
return;
|
||||
|
||||
if(!client->transfer)
|
||||
return;
|
||||
|
||||
if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
if(client->state != FileClient::STATE_TRANSFERRING)
|
||||
return;
|
||||
|
||||
if(client->buffer.bytes > TRANSFER_MAX_CACHED_BYTES)
|
||||
return;
|
||||
} else if(client->transfer->direction == Transfer::DIRECTION_UPLOAD) {
|
||||
/* we don't do this check because this might be a flush instruction, where the buffer is actually zero bytes filled */
|
||||
/*
|
||||
if(client->buffer.bytes == 0)
|
||||
return;
|
||||
*/
|
||||
}
|
||||
|
||||
std::lock_guard dlock{this->disk_io.queue_lock};
|
||||
if(client->file.next_client || this->disk_io.queue_tail == &client->file.next_client)
|
||||
return;
|
||||
|
||||
*this->disk_io.queue_tail = &*client;
|
||||
this->disk_io.queue_tail = &client->file.next_client;
|
||||
|
||||
this->disk_io.notify_work_awaiting.notify_all();
|
||||
}
|
||||
|
||||
void LocalFileTransfer::execute_disk_io(const std::shared_ptr<FileClient> &client) {
|
||||
if(!client->transfer) return;
|
||||
|
||||
if(client->transfer->direction == Transfer::DIRECTION_UPLOAD) {
|
||||
Buffer* buffer{nullptr};
|
||||
size_t buffer_left_size{0};
|
||||
|
||||
while(true) {
|
||||
{
|
||||
std::lock_guard block{client->buffer.mutex};
|
||||
buffer = client->buffer.buffer_head;
|
||||
buffer_left_size = client->buffer.bytes;
|
||||
}
|
||||
if(!buffer) {
|
||||
assert(buffer_left_size == 0);
|
||||
break;
|
||||
}
|
||||
|
||||
assert(buffer->offset < buffer->length);
|
||||
auto written = ::write(client->file.file_descriptor, buffer->data + buffer->offset, buffer->length - buffer->offset);
|
||||
if(written <= 0) {
|
||||
if(written == 0) {
|
||||
/* EOF, how the hell is this event possible?! */
|
||||
auto offset_written = client->statistics.disk_bytes_write + client->transfer->file_offset;
|
||||
auto aoffset = lseek(client->file.file_descriptor, 0, SEEK_CUR);
|
||||
logError(LOG_FT, "{} Received unexpected file write EOF. EOF received at {} but expected {}. Actual file offset: {}. Closing transfer.",
|
||||
client->log_prefix(), offset_written, client->transfer->expected_file_size, aoffset);
|
||||
|
||||
this->report_transfer_statistics(client);
|
||||
if(auto callback{client->handle->callback_transfer_aborted}; callback)
|
||||
callback(client->transfer, { TransferError::UNEXPECTED_DISK_EOF, strerror(errno) });
|
||||
|
||||
{
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->handle->disconnect_client(client, slock, true);
|
||||
}
|
||||
} else {
|
||||
if(errno == EAGAIN) {
|
||||
//TODO: Timeout?
|
||||
this->enqueue_disk_io(client);
|
||||
break;
|
||||
}
|
||||
|
||||
auto offset_written = client->statistics.disk_bytes_write + client->transfer->file_offset;
|
||||
auto aoffset = lseek(client->file.file_descriptor, 0, SEEK_CUR);
|
||||
logError(LOG_FT, "{} Received write to disk IO error. Write pointer is at {} of {}. Actual file offset: {}. Closing transfer.",
|
||||
client->log_prefix(), offset_written, client->transfer->expected_file_size, aoffset);
|
||||
|
||||
this->report_transfer_statistics(client);
|
||||
if(auto callback{client->handle->callback_transfer_aborted}; callback)
|
||||
callback(client->transfer, { TransferError::DISK_IO_ERROR, strerror(errno) });
|
||||
|
||||
{
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->handle->disconnect_client(client, slock, true);
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
buffer->offset += written;
|
||||
assert(buffer->offset <= buffer->length);
|
||||
if(buffer->length == buffer->offset) {
|
||||
{
|
||||
std::lock_guard block{client->buffer.mutex};
|
||||
client->buffer.buffer_head = buffer->next;
|
||||
if(!buffer->next)
|
||||
client->buffer.buffer_tail = &client->buffer.buffer_head;
|
||||
|
||||
assert(client->buffer.bytes >= written);
|
||||
client->buffer.bytes -= written;
|
||||
buffer_left_size = client->buffer.bytes;
|
||||
(void) buffer_left_size; /* trick my IDE here a bit */
|
||||
}
|
||||
|
||||
free_buffer(buffer);
|
||||
} else {
|
||||
std::lock_guard block{client->buffer.mutex};
|
||||
assert(client->buffer.bytes >= written);
|
||||
client->buffer.bytes -= written;
|
||||
buffer_left_size = client->buffer.bytes;
|
||||
(void) buffer_left_size; /* trick my IDE here a bit */
|
||||
}
|
||||
|
||||
client->statistics.disk_bytes_write += written;
|
||||
}
|
||||
}
|
||||
|
||||
if(buffer_left_size > 0)
|
||||
this->enqueue_disk_io(client);
|
||||
else if(client->state == FileClient::STATE_DISCONNECTING) {
|
||||
debugMessage(LOG_FT, "{} Disk IO has been flushed.", client->log_prefix());
|
||||
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->handle->disconnect_client(client->shared_from_this(), slock, false);
|
||||
}
|
||||
|
||||
if(client->state == FileClient::STATE_TRANSFERRING && buffer_left_size < TRANSFER_MAX_CACHED_BYTES / 2) {
|
||||
if(client->buffer.buffering_stopped)
|
||||
logMessage(LOG_FT, "{} Starting network read, buffer is capable for reading again.", client->log_prefix());
|
||||
client->add_network_read_event(false);
|
||||
}
|
||||
} else if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
while(true) {
|
||||
constexpr auto buffer_capacity{4096};
|
||||
char buffer[buffer_capacity];
|
||||
|
||||
auto read = ::read(client->file.file_descriptor, buffer, buffer_capacity);
|
||||
if(read <= 0) {
|
||||
if(read == 0) {
|
||||
/* EOF */
|
||||
auto offset_send = client->statistics.disk_bytes_read + client->transfer->file_offset;
|
||||
if(client->transfer->expected_file_size == offset_send) {
|
||||
debugMessage(LOG_FT, "{} Finished file reading. Flushing and disconnecting transfer. Reading took {} seconds.",
|
||||
client->log_prefix(), duration_to_string(std::chrono::system_clock::now() - client->timings.key_received));
|
||||
} else {
|
||||
auto aoffset = lseek(client->file.file_descriptor, 0, SEEK_CUR);
|
||||
logError(LOG_FT, "{} Received unexpected read EOF. EOF received at {} but expected {}. Actual file offset: {}. Disconnecting client.",
|
||||
client->log_prefix(), offset_send, client->transfer->expected_file_size, aoffset);
|
||||
|
||||
this->report_transfer_statistics(client);
|
||||
if(auto callback{client->handle->callback_transfer_aborted}; callback)
|
||||
callback(client->transfer, { TransferError::UNEXPECTED_DISK_EOF, strerror(errno) });
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->handle->disconnect_client(client, slock, true);
|
||||
}
|
||||
} else {
|
||||
if(errno == EAGAIN) {
|
||||
this->enqueue_disk_io(client);
|
||||
return;
|
||||
}
|
||||
|
||||
logWarning(LOG_FT, "{} Failed to read from file {} ({}/{}). Aborting transfer.", client->log_prefix(), client->file.absolute_path, errno, strerror(errno));
|
||||
|
||||
this->report_transfer_statistics(client);
|
||||
if(auto callback{client->handle->callback_transfer_aborted}; callback)
|
||||
callback(client->transfer, { TransferError::DISK_IO_ERROR, strerror(errno) });
|
||||
|
||||
{
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->handle->disconnect_client(client, slock, true);
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
auto buffer_full = client->send_file_bytes(buffer, read);
|
||||
client->statistics.disk_bytes_read += read;
|
||||
client->statistics.file_bytes_transferred += read;
|
||||
|
||||
std::shared_lock slock{client->state_mutex};
|
||||
if(buffer_full) {
|
||||
logMessage(LOG_FT, "{} Stopping buffering from disk. Buffer full ({}bytes)", client->log_prefix(), client->buffer.bytes);
|
||||
break;
|
||||
}
|
||||
|
||||
/* we've stuff to write again, yeahr */
|
||||
client->add_network_write_event(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logError(LOG_FT, "{} Disk IO scheduled, but transfer direction is unknown.", client->log_prefix());
|
||||
}
|
||||
}
|
843
file/local_server/LocalFileTransferNetwork.cpp
Normal file
843
file/local_server/LocalFileTransferNetwork.cpp
Normal file
@ -0,0 +1,843 @@
|
||||
//
|
||||
// Created by WolverinDEV on 04/05/2020.
|
||||
//
|
||||
|
||||
#include <event.h>
|
||||
#include <cassert>
|
||||
#include <netinet/tcp.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include <misc/net.h>
|
||||
#include "./LocalFileProvider.h"
|
||||
#include "./duration_utils.h"
|
||||
#include "LocalFileProvider.h"
|
||||
|
||||
#if defined(TCP_CORK) && !defined(TCP_NOPUSH)
|
||||
#define TCP_NOPUSH TCP_CORK
|
||||
#endif
|
||||
|
||||
using namespace ts::server::file;
|
||||
using namespace ts::server::file::transfer;
|
||||
|
||||
inline void add_network_event(FileClient& transfer, event* ev, bool& ev_throttle_readd_flag, bool ignore_bandwidth) {
|
||||
timeval tv{0, 1}, *ptv{nullptr};
|
||||
{
|
||||
auto timeout = transfer.networking.disconnect_timeout;
|
||||
if(timeout.time_since_epoch().count() > 0) {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
if(now < timeout) {
|
||||
auto duration = timeout - now;
|
||||
|
||||
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(timeout - now);
|
||||
duration -= seconds;
|
||||
|
||||
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(timeout - now);
|
||||
|
||||
tv.tv_sec = seconds.count();
|
||||
tv.tv_usec = microseconds.count();
|
||||
}
|
||||
ptv = &tv;
|
||||
}
|
||||
}
|
||||
|
||||
if(!ignore_bandwidth) {
|
||||
if(ev_throttle_readd_flag) return; /* we're already throttled */
|
||||
|
||||
timeval ttv{};
|
||||
if(transfer.networking.throttle.should_throttle(ttv)) {
|
||||
if(transfer.networking.event_throttle)
|
||||
event_add(transfer.networking.event_throttle, &ttv);
|
||||
ev_throttle_readd_flag = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
event_add(ev, ptv);
|
||||
}
|
||||
|
||||
void FileClient::add_network_read_event(bool ignore_bandwidth) {
|
||||
std::shared_lock slock{this->state_mutex};
|
||||
|
||||
switch (this->state) {
|
||||
case STATE_DISCONNECTING:
|
||||
case STATE_DISCONNECTED:
|
||||
return;
|
||||
|
||||
case STATE_AWAITING_KEY:
|
||||
case STATE_TRANSFERRING:
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
if(this->state != STATE_AWAITING_KEY && this->state != STATE_TRANSFERRING)
|
||||
return;
|
||||
|
||||
add_network_event(*this, this->networking.event_read, this->networking.add_event_read, ignore_bandwidth);
|
||||
}
|
||||
|
||||
void FileClient::add_network_write_event(bool ignore_bandwidth) {
|
||||
std::shared_lock slock{this->state_mutex};
|
||||
this->add_network_write_event_nolock(ignore_bandwidth);
|
||||
}
|
||||
|
||||
void FileClient::add_network_write_event_nolock(bool ignore_bandwidth) {
|
||||
switch (this->state) {
|
||||
case STATE_DISCONNECTED:
|
||||
return;
|
||||
|
||||
case STATE_DISCONNECTING:
|
||||
if(this->transfer->direction == Transfer::DIRECTION_UPLOAD)
|
||||
return;
|
||||
/* flush our write buffer */
|
||||
break;
|
||||
|
||||
case STATE_TRANSFERRING:
|
||||
break;
|
||||
|
||||
//case STATE_PENDING:
|
||||
//case STATE_AWAITING_KEY:
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
add_network_event(*this, this->networking.event_write, this->networking.add_event_write, ignore_bandwidth);
|
||||
}
|
||||
|
||||
bool FileClient::send_file_bytes(const void *snd_buffer, size_t size) {
|
||||
if(this->networking.protocol == FileClient::PROTOCOL_TS_V1) {
|
||||
auto tbuffer = allocate_buffer(size);
|
||||
tbuffer->length = size;
|
||||
tbuffer->offset = 0;
|
||||
memcpy(tbuffer->data, snd_buffer, size);
|
||||
|
||||
size_t buffer_size;
|
||||
{
|
||||
std::lock_guard block{this->buffer.mutex};
|
||||
*this->buffer.buffer_tail = tbuffer;
|
||||
this->buffer.buffer_tail = &tbuffer->next;
|
||||
|
||||
buffer_size = (this->buffer.bytes += size);
|
||||
}
|
||||
|
||||
this->add_network_write_event(false);
|
||||
return buffer_size > TRANSFER_MAX_CACHED_BYTES;
|
||||
} else if(this->networking.protocol == FileClient::PROTOCOL_HTTPS) {
|
||||
this->networking.pipe_ssl.send(pipes::buffer_view{snd_buffer, size});
|
||||
return this->buffer.bytes > TRANSFER_MAX_CACHED_BYTES;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
NetworkingStartResult LocalFileTransfer::start_networking() {
|
||||
assert(!this->network.active);
|
||||
|
||||
this->network.active = true;
|
||||
this->network.event_base = event_base_new();
|
||||
if(!this->network.event_base) return NetworkingStartResult::OUT_OF_MEMORY;
|
||||
|
||||
bool bound{false};
|
||||
for(auto& binding : this->network.bindings) {
|
||||
binding->file_descriptor = socket(binding->address.ss_family, SOCK_STREAM | SOCK_NONBLOCK, 0);
|
||||
if(!binding->file_descriptor) {
|
||||
logWarning(LOG_FT, "Failed to allocate socket for {}: {}/{}", binding->hostname, errno, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
int enable = 1, disabled = 0;
|
||||
|
||||
if (setsockopt(binding->file_descriptor, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0)
|
||||
logWarning(LOG_FT, "Failed to activate SO_REUSEADDR for binding {} ({} | {})", binding->hostname, errno, strerror(errno));
|
||||
|
||||
if(setsockopt(binding->file_descriptor, IPPROTO_TCP, TCP_NOPUSH, &disabled, sizeof disabled) < 0)
|
||||
logWarning(LOG_FT, "Failed to deactivate TCP_NOPUSH for binding {} ({} | {})", binding->hostname, errno, strerror(errno));
|
||||
|
||||
if(binding->address.ss_family == AF_INET6) {
|
||||
if(setsockopt(binding->file_descriptor, IPPROTO_IPV6, IPV6_V6ONLY, &enable, sizeof(int)) < 0)
|
||||
logWarning(LOG_FT, "Failed to activate IPV6_V6ONLY for IPv6 binding {} ({} | {})", binding->hostname, errno, strerror(errno));
|
||||
}
|
||||
if(fcntl(binding->file_descriptor, F_SETFD, FD_CLOEXEC) < 0)
|
||||
logWarning(LOG_FT, "Failed to set flag FD_CLOEXEC for binding {} ({} | {})", binding->hostname, errno, strerror(errno));
|
||||
|
||||
|
||||
if (bind(binding->file_descriptor, (struct sockaddr *) &binding->address, sizeof(binding->address)) < 0) {
|
||||
logError(LOG_FT, "Failed to bind server to {}. (Failed to bind socket: {} | {})", binding->hostname, errno, strerror(errno));
|
||||
goto reset_binding;
|
||||
}
|
||||
|
||||
if (listen(binding->file_descriptor, 8) < 0) {
|
||||
logError(LOG_FT, "Failed to bind server to {}. (Failed to listen: {} | {})", binding->hostname, errno, strerror(errno));
|
||||
goto reset_binding;
|
||||
}
|
||||
|
||||
binding->handle = this;
|
||||
binding->accept_event = event_new(this->network.event_base, binding->file_descriptor, (unsigned) EV_READ | (unsigned) EV_PERSIST, &LocalFileTransfer::callback_transfer_network_accept, &*binding);
|
||||
if(!binding->accept_event)
|
||||
goto reset_binding;
|
||||
|
||||
event_add(binding->accept_event, nullptr);
|
||||
logMessage(LOG_FT, "Started to listen on {}:{}", binding->hostname, net::port(binding->address));
|
||||
|
||||
bound = true;
|
||||
continue;
|
||||
|
||||
reset_binding:
|
||||
if(binding->accept_event) {
|
||||
event_free(binding->accept_event);
|
||||
binding->accept_event = nullptr;
|
||||
}
|
||||
|
||||
if(binding->file_descriptor > 0)
|
||||
::close(binding->file_descriptor);
|
||||
binding->file_descriptor = 0;
|
||||
|
||||
binding->handle = nullptr;
|
||||
}
|
||||
if(!bound) {
|
||||
event_base_free(std::exchange(this->network.event_base, nullptr));
|
||||
return NetworkingStartResult::NO_BINDINGS;
|
||||
}
|
||||
|
||||
this->network.dispatch_thread = std::thread(&LocalFileTransfer::dispatch_loop_network, this);
|
||||
return NetworkingStartResult::SUCCESS;
|
||||
}
|
||||
|
||||
void LocalFileTransfer::shutdown_networking() {
|
||||
if(!this->network.active) return;
|
||||
this->network.active = false;
|
||||
|
||||
for(auto& binding : this->network.bindings) {
|
||||
if(binding->accept_event) {
|
||||
event_del_block(binding->accept_event);
|
||||
event_free(binding->accept_event);
|
||||
binding->accept_event = nullptr;
|
||||
}
|
||||
|
||||
if(binding->file_descriptor > 0)
|
||||
::close(binding->file_descriptor);
|
||||
binding->file_descriptor = 0;
|
||||
|
||||
binding->handle = nullptr;
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock tlock{this->transfers_mutex};
|
||||
auto transfers = this->transfers_;
|
||||
tlock.unlock();
|
||||
for(const auto& transfer : transfers) {
|
||||
std::unique_lock slock{transfer->state_mutex};
|
||||
this->disconnect_client(transfer, slock, false);
|
||||
}
|
||||
}
|
||||
|
||||
auto ev_base = std::exchange(this->network.event_base, nullptr);
|
||||
if(ev_base)
|
||||
event_base_loopbreak(ev_base);
|
||||
|
||||
if(this->network.dispatch_thread.joinable())
|
||||
this->network.dispatch_thread.join();
|
||||
|
||||
if(ev_base)
|
||||
event_base_free(ev_base);
|
||||
}
|
||||
|
||||
void LocalFileTransfer::dispatch_loop_network(void *provider_ptr) {
|
||||
auto provider = reinterpret_cast<LocalFileTransfer*>(provider_ptr);
|
||||
|
||||
while(provider->network.active) {
|
||||
assert(provider->network.event_base);
|
||||
event_base_loop(provider->network.event_base, EVLOOP_NO_EXIT_ON_EMPTY);
|
||||
}
|
||||
}
|
||||
|
||||
NetworkInitializeResult LocalFileTransfer::initialize_networking(const std::shared_ptr<FileClient> &client, int file_descriptor) {
|
||||
client->networking.file_descriptor = file_descriptor;
|
||||
|
||||
client->networking.event_read = event_new(this->network.event_base, file_descriptor, EV_READ, &LocalFileTransfer::callback_transfer_network_read, &*client);
|
||||
client->networking.event_write = event_new(this->network.event_base, file_descriptor, EV_WRITE, &LocalFileTransfer::callback_transfer_network_write, &*client);
|
||||
client->networking.event_throttle = evtimer_new(this->network.event_base, &LocalFileTransfer::callback_transfer_network_throttle, &*client);
|
||||
|
||||
if(!client->networking.event_read || !client->networking.event_write || !client->networking.event_throttle)
|
||||
goto oom_exit;
|
||||
|
||||
client->add_network_read_event(true);
|
||||
|
||||
client->timings.connected = std::chrono::system_clock::now();
|
||||
client->timings.last_write = client->timings.connected;
|
||||
client->timings.last_read = client->timings.connected;
|
||||
|
||||
return NetworkInitializeResult::SUCCESS;
|
||||
|
||||
oom_exit:
|
||||
if(auto event{std::exchange(client->networking.event_read, nullptr)}; event)
|
||||
event_free(event);
|
||||
if(auto event{std::exchange(client->networking.event_write, nullptr)}; event)
|
||||
event_free(event);
|
||||
if(auto event{std::exchange(client->networking.event_throttle, nullptr)}; event)
|
||||
event_free(event);
|
||||
|
||||
return NetworkInitializeResult::OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
void LocalFileTransfer::finalize_networking(const std::shared_ptr<FileClient> &client, std::unique_lock<std::shared_mutex>& state_lock) {
|
||||
assert(state_lock.owns_lock());
|
||||
|
||||
auto ev_read = std::exchange(client->networking.event_read, nullptr);
|
||||
auto ev_write = std::exchange(client->networking.event_write, nullptr);
|
||||
auto ev_throttle = std::exchange(client->networking.event_throttle, nullptr);
|
||||
|
||||
state_lock.unlock();
|
||||
if (ev_read) {
|
||||
event_del_block(ev_read);
|
||||
event_free(ev_read);
|
||||
}
|
||||
if (ev_write) {
|
||||
event_del_block(ev_write);
|
||||
event_free(ev_write);
|
||||
}
|
||||
if (ev_throttle) {
|
||||
event_del_block(ev_throttle);
|
||||
event_free(ev_throttle);
|
||||
}
|
||||
state_lock.lock();
|
||||
|
||||
if (client->networking.file_descriptor > 0) {
|
||||
::shutdown(client->networking.file_descriptor, SHUT_RDWR);
|
||||
::close(client->networking.file_descriptor);
|
||||
}
|
||||
client->networking.file_descriptor = 0;
|
||||
}
|
||||
|
||||
void LocalFileTransfer::callback_transfer_network_accept(int fd, short, void *ptr_binding) {
|
||||
auto binding = reinterpret_cast<NetworkBinding*>(ptr_binding);
|
||||
auto transfer = binding->handle;
|
||||
|
||||
sockaddr_storage address{};
|
||||
socklen_t address_length{sizeof(address)};
|
||||
auto client_fd = ::accept4(fd, reinterpret_cast<sockaddr*>(&address), &address_length, SOCK_NONBLOCK);
|
||||
if(client_fd <= 0) {
|
||||
/* TODO: Reserve one file descriptor in case of out of file descriptors (see current implementation) */
|
||||
logError(LOG_FT, "Failed to accept new client: {}/{}", errno, strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
auto client = std::make_shared<FileClient>(transfer);
|
||||
memcpy(&client->networking.address, &address, sizeof(sockaddr_storage));
|
||||
|
||||
logMessage(LOG_FT, "{} Connection received.", client->log_prefix());
|
||||
auto ninit = transfer->initialize_networking(client, client_fd);
|
||||
switch(ninit) {
|
||||
case NetworkInitializeResult::SUCCESS: {
|
||||
std::lock_guard tlock{transfer->transfers_mutex};
|
||||
transfer->transfers_.push_back(std::move(client));
|
||||
return;
|
||||
}
|
||||
case NetworkInitializeResult::OUT_OF_MEMORY:
|
||||
client->state = FileClient::STATE_DISCONNECTED; /* required else the deallocate assert will fail */
|
||||
logError(LOG_FT, "{} Failed to initialize transfer client because we ran out of memory. Closing connection.", client->log_prefix());
|
||||
::close(client_fd);
|
||||
return;
|
||||
|
||||
default:
|
||||
client->state = FileClient::STATE_DISCONNECTED; /* required else the deallocate assert will fail */
|
||||
logError(LOG_FT, "{} Failed to initialize transfer client. Closing connection.", client->log_prefix());
|
||||
::close(client_fd);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void LocalFileTransfer::callback_transfer_network_throttle(int, short, void *ptr_transfer) {
|
||||
auto transfer = reinterpret_cast<FileClient*>(ptr_transfer);
|
||||
|
||||
if(std::exchange(transfer->networking.add_event_write, false))
|
||||
transfer->add_network_write_event(true);
|
||||
|
||||
if(std::exchange(transfer->networking.add_event_read, false))
|
||||
transfer->add_network_read_event(true);
|
||||
}
|
||||
|
||||
void LocalFileTransfer::callback_transfer_network_read(int fd, short events, void *ptr_transfer) {
|
||||
auto transfer = reinterpret_cast<FileClient*>(ptr_transfer);
|
||||
|
||||
if((unsigned) events & (unsigned) EV_TIMEOUT) {
|
||||
/* should never happen, receive timeouts are done via the client tick */
|
||||
}
|
||||
|
||||
if((unsigned) events & (unsigned) EV_READ) {
|
||||
constexpr size_t buffer_size{4096};
|
||||
char buffer[buffer_size];
|
||||
|
||||
while(true) {
|
||||
const auto max_read_buffer = transfer->networking.throttle.bytes_left();
|
||||
if(!max_read_buffer) break; /* network throttle */
|
||||
|
||||
auto read = ::recv(fd, buffer, std::min(buffer_size, std::min(max_read_buffer, transfer->networking.max_read_buffer_size)), MSG_NOSIGNAL | MSG_DONTWAIT);
|
||||
//logTrace(0, "Read {}, max {} | {}", read, std::min(buffer_size, std::min(max_read_buffer, transfer->networking.max_read_buffer_size)), max_read_buffer);
|
||||
if(read <= 0) {
|
||||
if(read == 0) {
|
||||
std::unique_lock slock{transfer->state_mutex};
|
||||
auto original_state = transfer->state;
|
||||
transfer->handle->disconnect_client(transfer->shared_from_this(), slock, false);
|
||||
slock.unlock();
|
||||
|
||||
switch(original_state) {
|
||||
case FileClient::STATE_AWAITING_KEY:
|
||||
logMessage(LOG_FT, "{} Disconnected without sending any key or initializing a transfer.", transfer->log_prefix());
|
||||
break;
|
||||
case FileClient::STATE_TRANSFERRING: {
|
||||
assert(transfer->transfer);
|
||||
|
||||
/* since we're counting the bytes directly on read, a disconnect would mean that not all data has been transferred */
|
||||
if(transfer->transfer->direction == Transfer::DIRECTION_UPLOAD) {
|
||||
logMessage(LOG_FT, "{} Disconnected while receiving file. Received {} out of {} bytes",
|
||||
transfer->log_prefix(), transfer->statistics.file_bytes_transferred, transfer->transfer->expected_file_size - transfer->transfer->file_offset);
|
||||
} else if(transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
logMessage(LOG_FT, "{} Disconnected while sending file. Send {} out of {} bytes",
|
||||
transfer->log_prefix(), transfer->statistics.file_bytes_transferred, transfer->transfer->expected_file_size - transfer->transfer->file_offset);
|
||||
}
|
||||
|
||||
transfer->handle->report_transfer_statistics(transfer->shared_from_this());
|
||||
if(auto callback{transfer->handle->callback_transfer_aborted}; callback)
|
||||
callback(transfer->transfer, { TransferError::UNEXPECTED_CLIENT_DISCONNECT, "" });
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if(errno == EAGAIN) {
|
||||
transfer->add_network_read_event(false);
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock slock{transfer->state_mutex};
|
||||
auto original_state = transfer->state;
|
||||
transfer->handle->disconnect_client(transfer->shared_from_this(), slock, false);
|
||||
slock.unlock();
|
||||
|
||||
switch(original_state) {
|
||||
case FileClient::STATE_AWAITING_KEY:
|
||||
logMessage(LOG_FT, "{} Received a read error for an unauthenticated client: {}/{}. Closing connection.", transfer->log_prefix(), errno, strerror(errno));
|
||||
break;
|
||||
case FileClient::STATE_TRANSFERRING:
|
||||
assert(transfer->transfer);
|
||||
|
||||
/* since we're counting the bytes directly on read, a disconnect would mean that not all data has been transferred */
|
||||
if(transfer->transfer->direction == Transfer::DIRECTION_UPLOAD) {
|
||||
logMessage(LOG_FT, "{} Received read error while receiving file. Received {} out of {} bytes: {}/{}",
|
||||
transfer->log_prefix(), transfer->statistics.file_bytes_transferred, transfer->transfer->expected_file_size - transfer->transfer->file_offset, errno, strerror(errno));
|
||||
} else if(transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
logMessage(LOG_FT, "{} Received read error while sending file. Send {} out of {} bytes: {}/{}",
|
||||
transfer->log_prefix(), transfer->statistics.file_bytes_transferred, transfer->transfer->expected_file_size - transfer->transfer->file_offset, errno, strerror(errno));
|
||||
}
|
||||
|
||||
transfer->handle->report_transfer_statistics(transfer->shared_from_this());
|
||||
if(auto callback{transfer->handle->callback_transfer_aborted}; callback)
|
||||
callback(transfer->transfer, { TransferError::NETWORK_IO_ERROR, strerror(errno) });
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
transfer->timings.last_read = std::chrono::system_clock::now();
|
||||
transfer->statistics.network_bytes_received += read;
|
||||
bool throttle_read = transfer->networking.throttle.increase_bytes(read);
|
||||
|
||||
if(transfer->state != FileClient::STATE_AWAITING_KEY && !(transfer->state == FileClient::STATE_TRANSFERRING && transfer->transfer->direction == Transfer::DIRECTION_UPLOAD)) {
|
||||
debugMessage(LOG_FT, "{} Received {} bytes without any need. Dropping them.", transfer->log_prefix(), read);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t bytes_buffered{0};
|
||||
if(transfer->state == FileClient::STATE_AWAITING_KEY) {
|
||||
bytes_buffered = transfer->handle->handle_transfer_read(transfer->shared_from_this(), buffer, read);
|
||||
} else if(transfer->state == FileClient::STATE_TRANSFERRING) {
|
||||
if(transfer->transfer->direction == Transfer::DIRECTION_UPLOAD) {
|
||||
bytes_buffered = transfer->handle->handle_transfer_read(transfer->shared_from_this(), buffer, read);
|
||||
} else {
|
||||
debugMessage(LOG_FT, "{} Received {} bytes without any need. Dropping them.", transfer->log_prefix(), read);
|
||||
}
|
||||
}
|
||||
|
||||
if(transfer->state == FileClient::STATE_DISCONNECTING || transfer->state == FileClient::STATE_DISCONNECTED)
|
||||
break;
|
||||
|
||||
if(bytes_buffered > TRANSFER_MAX_CACHED_BYTES) {
|
||||
transfer->buffer.buffering_stopped = true;
|
||||
debugMessage(LOG_FT, "{} Stopping network read, temp buffer full.", transfer->log_prefix());
|
||||
return; /* no read event readd, buffer filled */
|
||||
}
|
||||
|
||||
if(throttle_read)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
transfer->add_network_read_event(false); /* read event is not persistent */
|
||||
}
|
||||
}
|
||||
|
||||
void LocalFileTransfer::callback_transfer_network_write(int fd, short events, void *ptr_transfer) {
|
||||
auto transfer = reinterpret_cast<FileClient*>(ptr_transfer);
|
||||
|
||||
if((unsigned) events & (unsigned) EV_TIMEOUT) {
|
||||
if(transfer->state == FileClient::STATE_DISCONNECTING) {
|
||||
{
|
||||
std::unique_lock slock{transfer->state_mutex};
|
||||
transfer->handle->disconnect_client(transfer->shared_from_this(), slock, false);
|
||||
}
|
||||
|
||||
logMessage(LOG_FT, "{} Flush timeout. Force closing connection.", transfer->log_prefix());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if((unsigned) events & (unsigned) EV_WRITE) {
|
||||
if(transfer->state != FileClient::STATE_DISCONNECTING && transfer->state != FileClient::STATE_TRANSFERRING) {
|
||||
debugMessage(LOG_FT, "{} Tried to write data to send only stream. Dropping buffers.", transfer->log_prefix());
|
||||
|
||||
std::unique_lock block{transfer->buffer.mutex};
|
||||
auto head = std::exchange(transfer->buffer.buffer_head, nullptr);
|
||||
transfer->buffer.buffer_tail = &transfer->buffer.buffer_head;
|
||||
|
||||
while(head) {
|
||||
auto next = head->next;
|
||||
free_buffer(head);
|
||||
head = next;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Buffer* buffer{nullptr};
|
||||
size_t buffer_left_size{0};
|
||||
|
||||
while(true) {
|
||||
{
|
||||
std::lock_guard block{transfer->buffer.mutex};
|
||||
buffer = transfer->buffer.buffer_head;
|
||||
buffer_left_size = transfer->buffer.bytes;
|
||||
}
|
||||
if(!buffer) {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto max_write_bytes = transfer->networking.throttle.bytes_left();
|
||||
if(!max_write_bytes) break; /* network throttle */
|
||||
|
||||
assert(buffer->offset < buffer->length);
|
||||
auto written = ::send(fd, buffer->data + buffer->offset, std::min(buffer->length - buffer->offset, max_write_bytes), MSG_DONTWAIT | MSG_NOSIGNAL);
|
||||
if(written <= 0) {
|
||||
if(written == 0) {
|
||||
/* EOF, how the hell is this event possible?! (Read should already catch it) */
|
||||
logError(LOG_FT, "{} Client disconnected unexpectedly on write. Send {} bytes out of {}.",
|
||||
transfer->log_prefix(), transfer->statistics.file_bytes_transferred, transfer->transfer->expected_file_size - transfer->transfer->file_offset);
|
||||
|
||||
transfer->handle->report_transfer_statistics(transfer->shared_from_this());
|
||||
if(auto callback{transfer->handle->callback_transfer_aborted}; callback)
|
||||
callback(transfer->transfer, { TransferError::UNEXPECTED_CLIENT_DISCONNECT, strerror(errno) });
|
||||
|
||||
{
|
||||
std::unique_lock slock{transfer->state_mutex};
|
||||
transfer->handle->disconnect_client(transfer->shared_from_this(), slock, true);
|
||||
}
|
||||
} else {
|
||||
if(errno == EAGAIN) {
|
||||
transfer->add_network_write_event(false);
|
||||
break;
|
||||
}
|
||||
|
||||
logError(LOG_FT, "{} Received network write error. Send {} bytes out of {}. Closing transfer.",
|
||||
transfer->log_prefix(), transfer->statistics.file_bytes_transferred, transfer->transfer->expected_file_size - transfer->transfer->file_offset);
|
||||
|
||||
transfer->handle->report_transfer_statistics(transfer->shared_from_this());
|
||||
if(auto callback{transfer->handle->callback_transfer_aborted}; callback)
|
||||
callback(transfer->transfer, { TransferError::NETWORK_IO_ERROR, strerror(errno) });
|
||||
|
||||
{
|
||||
std::unique_lock slock{transfer->state_mutex};
|
||||
transfer->handle->disconnect_client(transfer->shared_from_this(), slock, false);
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
buffer->offset += written;
|
||||
assert(buffer->offset <= buffer->length);
|
||||
if(buffer->length == buffer->offset) {
|
||||
{
|
||||
std::lock_guard block{transfer->buffer.mutex};
|
||||
transfer->buffer.buffer_head = buffer->next;
|
||||
if(!buffer->next)
|
||||
transfer->buffer.buffer_tail = &transfer->buffer.buffer_head;
|
||||
assert(transfer->buffer.bytes >= written);
|
||||
transfer->buffer.bytes -= written;
|
||||
buffer_left_size = transfer->buffer.bytes;
|
||||
}
|
||||
|
||||
free_buffer(buffer);
|
||||
} else {
|
||||
std::lock_guard block{transfer->buffer.mutex};
|
||||
assert(transfer->buffer.bytes >= written);
|
||||
transfer->buffer.bytes -= written;
|
||||
buffer_left_size = transfer->buffer.bytes;
|
||||
}
|
||||
|
||||
transfer->timings.last_write = std::chrono::system_clock::now();
|
||||
transfer->statistics.network_bytes_send += written;
|
||||
|
||||
if(transfer->networking.throttle.increase_bytes(written))
|
||||
break; /* we've to slow down */
|
||||
}
|
||||
}
|
||||
|
||||
if(buffer_left_size > 0)
|
||||
transfer->add_network_write_event(false);
|
||||
else if(transfer->state == FileClient::STATE_DISCONNECTING) {
|
||||
if(transfer->statistics.file_bytes_transferred + transfer->transfer->file_offset == transfer->transfer->expected_file_size) {
|
||||
logMessage(LOG_FT, "{} Finished file transfer within {}. Closing connection.", transfer->log_prefix(), duration_to_string(std::chrono::system_clock::now() - transfer->timings.key_received));
|
||||
transfer->handle->report_transfer_statistics(transfer->shared_from_this());
|
||||
if(auto callback{transfer->handle->callback_transfer_finished}; callback)
|
||||
callback(transfer->transfer);
|
||||
} else {
|
||||
debugMessage(LOG_FT, "{} Flushed output buffer.", transfer->log_prefix());
|
||||
}
|
||||
std::unique_lock slock{transfer->state_mutex};
|
||||
transfer->handle->disconnect_client(transfer->shared_from_this(), slock, false);
|
||||
return;
|
||||
}
|
||||
transfer->handle->enqueue_disk_io(transfer->shared_from_this());
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string transfer_key_to_string(char key[TRANSFER_KEY_LENGTH]) {
|
||||
std::string result{};
|
||||
result.resize(TRANSFER_KEY_LENGTH);
|
||||
|
||||
for(size_t index{0}; index < TRANSFER_KEY_LENGTH; index++)
|
||||
result[index] = isprint(key[index]) ? key[index] : '.';
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t LocalFileTransfer::handle_transfer_read(const std::shared_ptr<FileClient> &client, const char *buffer, size_t length) {
|
||||
if(client->state == FileClient::STATE_AWAITING_KEY) {
|
||||
if(client->networking.protocol == FileClient::PROTOCOL_UNKNOWN) {
|
||||
/* lets read the key bytes (16) and then decide */
|
||||
|
||||
if(client->transfer_key.provided_bytes < TRANSFER_KEY_LENGTH) {
|
||||
const auto bytes_write = std::min(TRANSFER_KEY_LENGTH - client->transfer_key.provided_bytes, length);
|
||||
memcpy(client->transfer_key.key + client->transfer_key.provided_bytes, buffer, bytes_write);
|
||||
length -= bytes_write;
|
||||
buffer += bytes_write;
|
||||
client->transfer_key.provided_bytes += bytes_write;
|
||||
}
|
||||
|
||||
if(client->transfer_key.provided_bytes < TRANSFER_KEY_LENGTH)
|
||||
return 0; /* we need more data */
|
||||
|
||||
if(pipes::SSL::is_ssl((uint8_t*) client->transfer_key.key, client->transfer_key.provided_bytes)) {
|
||||
client->networking.protocol = FileClient::PROTOCOL_HTTPS;
|
||||
client->networking.max_read_buffer_size = (size_t) 1024 * 2; /* HTTPS-Header are sometimes a bit bigger. Dont cap max bandwidth here. */
|
||||
debugMessage(LOG_FT, "{} Using protocol HTTPS for file transfer.", client->log_prefix());
|
||||
|
||||
char first_bytes[TRANSFER_KEY_LENGTH];
|
||||
memcpy(first_bytes, client->transfer_key.key, TRANSFER_KEY_LENGTH);
|
||||
client->transfer_key.provided_bytes = 0;
|
||||
|
||||
this->handle_transfer_read(client, first_bytes, TRANSFER_KEY_LENGTH);
|
||||
return this->handle_transfer_read(client, buffer, length);
|
||||
} else {
|
||||
client->networking.protocol = FileClient::PROTOCOL_TS_V1;
|
||||
debugMessage(LOG_FT, "{} Using protocol RAWv1 for file transfer.", client->log_prefix());
|
||||
|
||||
return this->handle_transfer_read(client, buffer, length);
|
||||
}
|
||||
} else if(client->networking.protocol == FileClient::PROTOCOL_TS_V1) {
|
||||
auto key_result = this->handle_transfer_key_provided(client);
|
||||
switch(key_result) {
|
||||
case TransferKeyApplyResult::SUCCESS:
|
||||
return length ? this->handle_transfer_read(client, buffer, length) : 0;
|
||||
|
||||
case TransferKeyApplyResult::FILE_ERROR:
|
||||
logMessage(LOG_FT, "{} Disconnecting client because we failed to open the target file.", client->log_prefix());
|
||||
return (size_t) -1;
|
||||
|
||||
case TransferKeyApplyResult::UNKNOWN_KEY:
|
||||
logMessage(LOG_FT, "{} Disconnecting client because we don't recognise his key ({}).", client->log_prefix(), transfer_key_to_string(client->transfer_key.key));
|
||||
return (size_t) -1;
|
||||
|
||||
default:
|
||||
logMessage(LOG_FT, "{} Disconnecting client because of an unknown key initialize error ({}).", client->log_prefix(), (int) key_result);
|
||||
return (size_t) -1;
|
||||
}
|
||||
} else if(client->networking.protocol == FileClient::PROTOCOL_HTTPS) {
|
||||
/* TODO! */
|
||||
/* 1. Await HTTP header
|
||||
* 2. Test for file transfer key & if its upload or download
|
||||
*/
|
||||
} else {
|
||||
logError(LOG_FT, "{} Protocol variable contains unknown protocol. Disconnecting client.", client->log_prefix());
|
||||
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->handle->disconnect_client(client->shared_from_this(), slock, true);
|
||||
return (size_t) -1;
|
||||
}
|
||||
} else if(client->state == FileClient::STATE_TRANSFERRING) {
|
||||
assert(client->transfer);
|
||||
if(client->transfer->direction != Transfer::DIRECTION_UPLOAD) {
|
||||
debugMessage(LOG_FT, "{} Read {} bytes from client even though we're only sending a file. Ignoring it.", client->log_prefix(), length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(client->networking.protocol == FileClient::PROTOCOL_HTTPS) {
|
||||
client->networking.pipe_ssl.process_incoming_data(pipes::buffer_view{buffer, length});
|
||||
return client->buffer.bytes; /* a bit unexact but the best we could get away with it */
|
||||
} else if(client->networking.protocol == FileClient::PROTOCOL_TS_V1) {
|
||||
client->statistics.file_bytes_transferred += length;
|
||||
|
||||
bool transfer_finished{false};
|
||||
auto writte_offset = client->statistics.file_bytes_transferred + client->transfer->file_offset;
|
||||
if(writte_offset > client->transfer->expected_file_size) {
|
||||
logMessage(LOG_FT, "{} Client send {} too many bytes (Transfer length was {}). Dropping them, flushing the disk IO and closing the transfer.", client->log_prefix(), writte_offset - client->transfer->expected_file_size, duration_to_string(std::chrono::system_clock::now() - client->timings.key_received));
|
||||
length -= writte_offset - client->transfer->expected_file_size;
|
||||
|
||||
transfer_finished = true;
|
||||
} else if(writte_offset == client->transfer->expected_file_size) {
|
||||
logMessage(LOG_FT, "{} File upload has been completed in {}. Flushing disk IO and closing the connection.", client->log_prefix(), duration_to_string(std::chrono::system_clock::now() - client->timings.key_received));
|
||||
transfer_finished = true;
|
||||
}
|
||||
|
||||
auto tbuffer = allocate_buffer(length);
|
||||
tbuffer->offset = 0;
|
||||
tbuffer->length = length;
|
||||
memcpy(tbuffer->data, buffer, length);
|
||||
|
||||
size_t buffered_bytes;
|
||||
{
|
||||
std::lock_guard block{client->buffer.mutex};
|
||||
*client->buffer.buffer_tail = tbuffer;
|
||||
client->buffer.buffer_tail = &tbuffer->next;
|
||||
buffered_bytes = (client->buffer.bytes += length);
|
||||
}
|
||||
|
||||
this->enqueue_disk_io(client);
|
||||
|
||||
if(transfer_finished) {
|
||||
this->report_transfer_statistics(client);
|
||||
if(auto callback{this->callback_transfer_finished}; callback)
|
||||
callback(client->transfer);
|
||||
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->handle->disconnect_client(client->shared_from_this(), slock, true);
|
||||
}
|
||||
return buffered_bytes;
|
||||
} else {
|
||||
logWarning(LOG_FT, "{} Read message for client with unknown protocol. Dropping {} bytes.", client->log_prefix(), length);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
logWarning(LOG_FT, "{} Read message at invalid client state ({}). Dropping message.", client->log_prefix(), client->state);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
TransferKeyApplyResult LocalFileTransfer::handle_transfer_key_provided(const std::shared_ptr<FileClient> &client) {
|
||||
{
|
||||
std::lock_guard tlock{this->transfers_mutex};
|
||||
for(auto it = this->pending_transfers.begin(); it != this->pending_transfers.end(); it++) {
|
||||
if(memcmp((*it)->transfer_key, client->transfer_key.key, TRANSFER_KEY_LENGTH) == 0) {
|
||||
client->transfer = *it;
|
||||
this->pending_transfers.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!client->transfer) {
|
||||
logMessage(LOG_FT, "{} Received invalid transfer key. Disconnecting client.", client->log_prefix());
|
||||
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->handle->disconnect_client(client->shared_from_this(), slock, false);
|
||||
|
||||
return TransferKeyApplyResult::UNKNOWN_KEY;
|
||||
}
|
||||
|
||||
{
|
||||
std::string absolute_path{};
|
||||
auto transfer = client->transfer;
|
||||
switch (transfer->target_type) {
|
||||
case Transfer::TARGET_TYPE_AVATAR:
|
||||
absolute_path = this->file_system_.absolute_avatar_path(transfer->server_id, transfer->target_file_path);
|
||||
logMessage(LOG_FT, "{} Initialized avatar transfer for avatar \"{}\" ({} bytes, transferring {} bytes).",
|
||||
client->log_prefix(), transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset);
|
||||
break;
|
||||
case Transfer::TARGET_TYPE_ICON:
|
||||
absolute_path = this->file_system_.absolute_icon_path(transfer->server_id, transfer->target_file_path);
|
||||
logMessage(LOG_FT, "{} Initialized icon transfer for icon \"{}\" ({} bytes, transferring {} bytes).",
|
||||
client->log_prefix(), transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset);
|
||||
break;
|
||||
case Transfer::TARGET_TYPE_CHANNEL_FILE:
|
||||
absolute_path = this->file_system_.absolute_channel_path(transfer->server_id, transfer->channel_id, transfer->target_file_path);
|
||||
logMessage(LOG_FT, "{} Initialized channel transfer for file \"{}/{}\" ({} bytes, transferring {} bytes).",
|
||||
client->log_prefix(), transfer->channel_id, transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset);
|
||||
break;
|
||||
default:
|
||||
logError(LOG_FT, "{} Tried to initialize client with unknown file target type ({}). Dropping transfer.", client->log_prefix(), (int) transfer->target_type);
|
||||
this->report_transfer_statistics(client);
|
||||
if(auto callback{this->callback_transfer_aborted}; callback)
|
||||
callback(client->transfer, { TransferError::UNKNOWN, "invalid transfer type" });
|
||||
|
||||
{
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->handle->disconnect_client(client->shared_from_this(), slock, false);
|
||||
}
|
||||
return TransferKeyApplyResult::FILE_ERROR;
|
||||
}
|
||||
debugMessage(LOG_FT, "{} Absolute file path: {}", client->log_prefix(), absolute_path);
|
||||
client->file.absolute_path = absolute_path;
|
||||
}
|
||||
|
||||
if(client->transfer->max_bandwidth > 0) {
|
||||
debugMessage(LOG_FT, "{} Limit network bandwidth to {} bytes/second", client->log_prefix(), client->transfer->max_bandwidth);
|
||||
client->networking.throttle.set_max_bandwidth(client->transfer->max_bandwidth);
|
||||
}
|
||||
client->networking.max_read_buffer_size = (size_t) -1; /* limit max bandwidth via throttle */
|
||||
|
||||
auto io_init_result = this->initialize_file_io(client);
|
||||
if(io_init_result != FileInitializeResult::SUCCESS) {
|
||||
logMessage(LOG_FT, "{} Failed to initialize file {}: {}/{}. Disconnecting client.",
|
||||
client->log_prefix(), client->transfer->direction == Transfer::DIRECTION_UPLOAD ? "writer" : "reader", (int) io_init_result, kFileInitializeResultMessages[(int) io_init_result]);
|
||||
|
||||
this->report_transfer_statistics(client);
|
||||
if(auto callback{this->callback_transfer_aborted}; callback)
|
||||
callback(client->transfer, { TransferError::DISK_INITIALIZE_ERROR, std::to_string((int) io_init_result) + "/" + std::string{kFileInitializeResultMessages[(int) io_init_result]} });
|
||||
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->handle->disconnect_client(client->shared_from_this(), slock, false);
|
||||
|
||||
return TransferKeyApplyResult::FILE_ERROR;
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
if(client->state != FileClient::STATE_AWAITING_KEY)
|
||||
return TransferKeyApplyResult::SUCCESS; /* something disconnected the client */
|
||||
|
||||
client->state = FileClient::STATE_TRANSFERRING;
|
||||
}
|
||||
|
||||
if(auto callback{this->callback_transfer_started}; callback)
|
||||
callback(client->transfer);
|
||||
|
||||
client->timings.key_received = std::chrono::system_clock::now();
|
||||
|
||||
if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD)
|
||||
this->enqueue_disk_io(client); /* we've to take initiative */
|
||||
|
||||
return TransferKeyApplyResult::SUCCESS;
|
||||
}
|
35
file/local_server/duration_utils.h
Normal file
35
file/local_server/duration_utils.h
Normal file
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
template <typename Rep, typename Per>
|
||||
inline std::string duration_to_string(std::chrono::duration<Rep, Per> ms) {
|
||||
std::string result{};
|
||||
|
||||
{
|
||||
auto hours = std::chrono::duration_cast<std::chrono::hours>(ms);
|
||||
if(hours.count())
|
||||
result += std::to_string(hours.count()) + " hours ";
|
||||
ms -= hours;
|
||||
}
|
||||
{
|
||||
auto minutes = std::chrono::duration_cast<std::chrono::minutes>(ms);
|
||||
if(minutes.count())
|
||||
result += std::to_string(minutes.count()) + " minutes ";
|
||||
ms -= minutes;
|
||||
}
|
||||
{
|
||||
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(ms);
|
||||
if(seconds.count())
|
||||
result += std::to_string(seconds.count()) + " seconds ";
|
||||
ms -= seconds;
|
||||
}
|
||||
if(result.empty()) {
|
||||
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(ms);
|
||||
if(milliseconds.count())
|
||||
result = std::to_string(milliseconds.count()) + " milliseconds ";
|
||||
}
|
||||
if(result.empty()) {
|
||||
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(ms);
|
||||
result = std::to_string(microseconds.count()) + " microseconds ";
|
||||
}
|
||||
return result.substr(0, result.length() - 1);
|
||||
}
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include <experimental/filesystem>
|
||||
#include <local_server/clnpath.h>
|
||||
#include <event2/thread.h>
|
||||
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
@ -15,49 +16,67 @@ using namespace ts::server;
|
||||
struct Nothing {};
|
||||
|
||||
template <typename ErrorType, typename ResponseType>
|
||||
inline void print_response(const std::string& message, const std::shared_ptr<file::ExecuteResponse<file::filesystem::DetailedError<ErrorType>, ResponseType>>& response) {
|
||||
inline void print_fs_response(const std::string& message, const std::shared_ptr<file::ExecuteResponse<file::filesystem::DetailedError<ErrorType>, ResponseType>>& response) {
|
||||
if(response->status == file::ExecuteStatus::ERROR)
|
||||
logError(0, "{}: {} => {}", message, (int) response->error().error_type, response->error().error_message);
|
||||
logError(LOG_FT, "{}: {} => {}", message, (int) response->error().error_type, response->error().error_message);
|
||||
else if(response->status == file::ExecuteStatus::SUCCESS)
|
||||
logMessage(0, "{}: success", message);
|
||||
logMessage(LOG_FT, "{}: success", message);
|
||||
else
|
||||
logWarning(0, "Unknown response state ({})!", (int) response->status);
|
||||
logWarning(LOG_FT, "Unknown response state ({})!", (int) response->status);
|
||||
}
|
||||
|
||||
template <typename ErrorType, typename ResponseType>
|
||||
inline void print_ft_response(const std::string& message, const std::shared_ptr<file::ExecuteResponse<ErrorType, ResponseType>>& response) {
|
||||
if(response->status == file::ExecuteStatus::ERROR)
|
||||
logError(LOG_FT, "{}: {} => {}", message, (int) response->error().error_type, response->error().error_message);
|
||||
else if(response->status == file::ExecuteStatus::SUCCESS)
|
||||
logMessage(LOG_FT, "{}: success", message);
|
||||
else
|
||||
logWarning(LOG_FT, "Unknown response state ({})!", (int) response->status);
|
||||
}
|
||||
|
||||
inline void print_query(const std::string& message, const file::filesystem::AbstractProvider::directory_query_response_t& response) {
|
||||
if(response.status == file::ExecuteStatus::ERROR)
|
||||
logError(0, "{}: {} => {}", message, (int) response.error().error_type, response.error().error_message);
|
||||
logError(LOG_FT, "{}: {} => {}", message, (int) response.error().error_type, response.error().error_message);
|
||||
else if(response.status == file::ExecuteStatus::SUCCESS) {
|
||||
const auto& entries = response.response();
|
||||
logMessage(0, "{}: Found {} entries", message, entries.size());
|
||||
logMessage(LOG_FT, "{}: Found {} entries", message, entries.size());
|
||||
for(auto& entry : entries) {
|
||||
if(entry.type == file::filesystem::DirectoryEntry::FILE)
|
||||
logMessage(0, " - File {}", entry.name);
|
||||
logMessage(LOG_FT, " - File {}", entry.name);
|
||||
else if(entry.type == file::filesystem::DirectoryEntry::DIRECTORY)
|
||||
logMessage(0, " - Directory {}", entry.name);
|
||||
logMessage(LOG_FT, " - Directory {}", entry.name);
|
||||
else
|
||||
logMessage(0, " - Unknown {}", entry.name);
|
||||
logMessage(0, " Write timestamp: {}", std::chrono::floor<std::chrono::seconds>(entry.modified_at.time_since_epoch()).count());
|
||||
logMessage(0, " Size: {}", entry.size);
|
||||
logMessage(LOG_FT, " - Unknown {}", entry.name);
|
||||
logMessage(LOG_FT, " Write timestamp: {}", std::chrono::floor<std::chrono::seconds>(entry.modified_at.time_since_epoch()).count());
|
||||
logMessage(LOG_FT, " Size: {}", entry.size);
|
||||
}
|
||||
} else
|
||||
logWarning(0, "{}: Unknown response state ({})!", message, (int) response.status);
|
||||
logWarning(LOG_FT, "{}: Unknown response state ({})!", message, (int) response.status);
|
||||
}
|
||||
|
||||
int main() {
|
||||
evthread_use_pthreads();
|
||||
|
||||
auto log_config = std::make_shared<logger::LoggerConfig>();
|
||||
log_config->terminalLevel = spdlog::level::trace;
|
||||
logger::setup(log_config);
|
||||
|
||||
std::string error{};
|
||||
if(!file::initialize(error)) {
|
||||
logError(0, "Failed to initialize file server: {}", error);
|
||||
logError(LOG_FT, "Failed to initialize file server: {}", error);
|
||||
return 0;
|
||||
}
|
||||
logMessage(0, "File server started");
|
||||
logMessage(LOG_FT, "File server started");
|
||||
auto instance = file::server();
|
||||
auto& fs = instance->file_system();
|
||||
|
||||
|
||||
#if 0
|
||||
auto& fs = instance->file_system();
|
||||
{
|
||||
auto response = fs.initialize_server(0);
|
||||
response->wait();
|
||||
print_response("Server init result", response);
|
||||
print_fs_response("Server init result", response);
|
||||
if(response->status != file::ExecuteStatus::SUCCESS)
|
||||
return 0;
|
||||
}
|
||||
@ -66,28 +85,28 @@ int main() {
|
||||
{
|
||||
auto response = fs.create_channel_directory(0, 2, "/");
|
||||
response->wait();
|
||||
print_response("Channel dir create A", response);
|
||||
print_fs_response("Channel dir create A", response);
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
auto response = fs.create_channel_directory(0, 2, "/test-folder/");
|
||||
response->wait();
|
||||
print_response("Channel dir create B", response);
|
||||
print_fs_response("Channel dir create B", response);
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
auto response = fs.create_channel_directory(0, 2, "../test-folder/");
|
||||
response->wait();
|
||||
print_response("Channel dir create C", response);
|
||||
print_fs_response("Channel dir create C", response);
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
auto response = fs.create_channel_directory(0, 2, "./test-folder/../test-folder-2");
|
||||
response->wait();
|
||||
print_response("Channel dir create D", response);
|
||||
print_fs_response("Channel dir create D", response);
|
||||
}
|
||||
|
||||
{
|
||||
@ -111,15 +130,52 @@ int main() {
|
||||
{
|
||||
auto response = fs.rename_channel_file(0, 2, "./test-folder/../test-folder-2", "./test-folder/../test-folder-3");
|
||||
response->wait();
|
||||
print_response("Folder rename A", response);
|
||||
print_fs_response("Folder rename A", response);
|
||||
}
|
||||
|
||||
{
|
||||
auto response = fs.rename_channel_file(0, 2, "./test-folder/../test-folder-3", "./test-folder/../test-folder-2");
|
||||
response->wait();
|
||||
print_response("Folder rename B", response);
|
||||
print_fs_response("Folder rename B", response);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 1
|
||||
auto& ft = instance->file_transfer();
|
||||
|
||||
ft.callback_transfer_finished = [](const std::shared_ptr<file::transfer::Transfer>& transfer) {
|
||||
logMessage(0, "Transfer finished");
|
||||
};
|
||||
|
||||
ft.callback_transfer_started = [](const std::shared_ptr<file::transfer::Transfer>& transfer) {
|
||||
logMessage(0, "Transfer started");
|
||||
};
|
||||
|
||||
ft.callback_transfer_aborted = [](const std::shared_ptr<file::transfer::Transfer>& transfer, const file::transfer::TransferError& error) {
|
||||
logMessage(0, "Transfer aborted ({}/{})", (int) error.error_type, error.error_message);
|
||||
};
|
||||
|
||||
ft.callback_transfer_statistics = [](const std::shared_ptr<file::transfer::Transfer>& transfer, const file::transfer::TransferStatistics& stats) {
|
||||
logMessage(0, "Transfer stats. New file bytes: {}, delta bytes send {}", stats.delta_file_bytes_transferred, stats.delta_network_bytes_send);
|
||||
};
|
||||
|
||||
{
|
||||
auto response = ft.initialize_channel_transfer(file::transfer::Transfer::DIRECTION_DOWNLOAD, 0, 2, {
|
||||
"test2.txt",
|
||||
false,
|
||||
4,
|
||||
120,
|
||||
16
|
||||
});
|
||||
response->wait();
|
||||
print_ft_response("Download test.txt", response);
|
||||
if(response->succeeded())
|
||||
logMessage(LOG_FT, "Download key: {}", std::string{response->response()->transfer_key, TRANSFER_KEY_LENGTH});
|
||||
}
|
||||
#endif
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds{120});
|
||||
//TODO: Test file locking
|
||||
file::finalize();
|
||||
return 0;
|
||||
}
|
@ -1 +1 @@
|
||||
Subproject commit ac7d6a0a1ee75aa6a9e77382c9a010f3827327ba
|
||||
Subproject commit 7261d531f3c10be1437a4572369260aeff075e6c
|
@ -62,6 +62,7 @@ set(SERVER_SOURCE_FILES
|
||||
src/client/command_handler/client.cpp
|
||||
src/client/command_handler/server.cpp
|
||||
src/client/command_handler/misc.cpp
|
||||
src/client/command_handler/bulk_parsers.cpp
|
||||
|
||||
src/client/ConnectedClientNotifyHandler.cpp
|
||||
src/VirtualServerManager.cpp
|
||||
|
@ -142,7 +142,7 @@ namespace ts {
|
||||
|
||||
void tick();
|
||||
|
||||
[[nodiscard]] inline const BandwidthEntry<uint32_t>& total_stats() const { return this->total_statistics; }
|
||||
[[nodiscard]] inline const BandwidthEntry<uint64_t>& total_stats() const { return this->total_statistics; }
|
||||
[[nodiscard]] inline BandwidthEntry<uint32_t> second_stats() const { return this->statistics_second; }
|
||||
[[nodiscard]] BandwidthEntry<uint32_t> minute_stats() const;
|
||||
|
||||
@ -151,7 +151,7 @@ namespace ts {
|
||||
private:
|
||||
std::shared_ptr<ConnectionStatistics> handle;
|
||||
|
||||
BandwidthEntry<uint32_t> total_statistics{};
|
||||
BandwidthEntry<uint64_t> total_statistics{};
|
||||
|
||||
BandwidthEntry<std::atomic<uint64_t>> statistics_second_current{};
|
||||
BandwidthEntry<uint32_t> statistics_second{}; /* will be updated every second by the stats from the "current_second" */
|
||||
|
@ -1216,4 +1216,41 @@ void VirtualServer::send_text_message(const std::shared_ptr<BasicChannel> &chann
|
||||
auto conversation = conversations->get_or_create(channel->channelId());
|
||||
conversation->register_message(sender->getClientDatabaseId(), sender->getUid(), sender->getDisplayName(), now, message);
|
||||
}
|
||||
}
|
||||
|
||||
void VirtualServer::update_channel_from_permissions(const std::shared_ptr<BasicChannel> &channel, const std::shared_ptr<ConnectedClient>& issuer) {
|
||||
bool require_view_update;
|
||||
auto property_updates = channel->update_properties_from_permissions(require_view_update);
|
||||
|
||||
if(!property_updates.empty()) {
|
||||
this->forEachClient([&](const std::shared_ptr<ConnectedClient>& cl) {
|
||||
shared_lock client_channel_lock(cl->channel_lock);
|
||||
cl->notifyChannelEdited(channel, property_updates, issuer, false);
|
||||
});
|
||||
}
|
||||
|
||||
if(require_view_update) {
|
||||
auto l_source = this->channelTree->findLinkedChannel(channel->channelId());
|
||||
this->forEachClient([&](const shared_ptr<ConnectedClient>& cl) {
|
||||
/* server tree read lock still active */
|
||||
auto l_target = !cl->currentChannel ? nullptr : cl->server->channelTree->findLinkedChannel(cl->currentChannel->channelId());
|
||||
sassert(l_source);
|
||||
if(cl->currentChannel) sassert(l_target);
|
||||
|
||||
{
|
||||
unique_lock client_channel_lock(cl->channel_lock);
|
||||
|
||||
deque<ChannelId> deleted;
|
||||
for(const auto& [flag_visible, channel] : cl->channels->update_channel(l_source, l_target)) {
|
||||
if(flag_visible) {
|
||||
cl->notifyChannelShow(channel->channel(), channel->previous_channel);
|
||||
} else {
|
||||
deleted.push_back(channel->channelId());
|
||||
}
|
||||
}
|
||||
if(!deleted.empty())
|
||||
cl->notifyChannelHide(deleted, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -278,6 +278,9 @@ namespace ts {
|
||||
|
||||
inline int voice_encryption_mode() { return this->_voice_encryption_mode; }
|
||||
inline std::shared_ptr<conversation::ConversationManager> conversation_manager() { return this->_conversation_manager; }
|
||||
|
||||
|
||||
void update_channel_from_permissions(const std::shared_ptr<BasicChannel>& /* channel */, const std::shared_ptr<ConnectedClient>& /* issuer */);
|
||||
protected:
|
||||
bool registerClient(std::shared_ptr<ConnectedClient>);
|
||||
bool unregisterClient(std::shared_ptr<ConnectedClient>, std::string, std::unique_lock<std::shared_mutex>& channel_tree_lock);
|
||||
|
@ -605,7 +605,7 @@ bool ConnectedClient::notifyError(const command_result& result, const std::strin
|
||||
auto bulks = result.bulks();
|
||||
command.reserve_bulks(bulks->size());
|
||||
for(size_t index{0}; index < bulks->size(); index++) {
|
||||
auto entry = bulks->at(index);
|
||||
auto& entry = bulks->at(index);
|
||||
switch (entry.type()) {
|
||||
case command_result_type::error:
|
||||
write_command_result_error(command.bulk(index), entry);
|
||||
@ -828,14 +828,14 @@ bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) {
|
||||
|
||||
command_result result;
|
||||
try {
|
||||
result = this->handleCommand(cmd);
|
||||
result.reset(this->handleCommand(cmd));
|
||||
} catch(invalid_argument& ex){
|
||||
logWarning(this->getServerId(), "{}[Command] Failed to handle command. Received invalid argument exception: {}", CLIENT_STR_LOG_PREFIX, ex.what());
|
||||
if(disconnectOnFail) {
|
||||
this->disconnect("Invalid argument (" + string(ex.what()) + ")");
|
||||
return false;
|
||||
} else {
|
||||
result = command_result{error::parameter_convert, ex.what()};
|
||||
result.reset(command_result{error::parameter_convert, ex.what()});
|
||||
}
|
||||
} catch (exception& ex) {
|
||||
logWarning(this->getServerId(), "{}[Command] Failed to handle command. Received exception with message: {}", CLIENT_STR_LOG_PREFIX, ex.what());
|
||||
@ -843,7 +843,7 @@ bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) {
|
||||
this->disconnect("Error while command handling (" + string(ex.what()) + ")!");
|
||||
return false;
|
||||
} else {
|
||||
result = command_result{error::vs_critical};
|
||||
result.reset(command_result{error::vs_critical});
|
||||
}
|
||||
} catch (...) {
|
||||
this->disconnect("Error while command handling! (unknown)");
|
||||
|
@ -867,11 +867,11 @@ command_result SpeakingClient::handleCommand(Command &command) {
|
||||
if(this->handshake.state == HandshakeState::BEGIN || this->handshake.state == HandshakeState::IDENTITY_PROOF) {
|
||||
command_result result;
|
||||
if(command.command() == "handshakebegin")
|
||||
result = this->handleCommandHandshakeBegin(command);
|
||||
result.reset(this->handleCommandHandshakeBegin(command));
|
||||
else if(command.command() == "handshakeindentityproof")
|
||||
result = this->handleCommandHandshakeIdentityProof(command);
|
||||
result.reset(this->handleCommandHandshakeIdentityProof(command));
|
||||
else
|
||||
result = command_result{error::client_not_logged_in};
|
||||
result.reset(command_result{error::client_not_logged_in});
|
||||
|
||||
if(result.has_error())
|
||||
this->postCommandHandler.push_back([&]{
|
||||
|
5
server/src/client/command_handler/bulk_parsers.cpp
Normal file
5
server/src/client/command_handler/bulk_parsers.cpp
Normal file
@ -0,0 +1,5 @@
|
||||
//
|
||||
// Created by WolverinDEV on 07/05/2020.
|
||||
//
|
||||
|
||||
#include "bulk_parsers.h"
|
240
server/src/client/command_handler/bulk_parsers.h
Normal file
240
server/src/client/command_handler/bulk_parsers.h
Normal file
@ -0,0 +1,240 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <query/Command.h>
|
||||
#include <Error.h>
|
||||
#include <PermissionManager.h>
|
||||
#include <src/client/ConnectedClient.h>
|
||||
#include "./helpers.h"
|
||||
|
||||
namespace ts::command::bulk_parser {
|
||||
template <bool kParseValue>
|
||||
class PermissionBulkParser {
|
||||
public:
|
||||
explicit PermissionBulkParser(ts::ParameterBulk& bulk) {
|
||||
if(bulk.has("permid")) {
|
||||
auto type = bulk["permid"].as<permission::PermissionType>();
|
||||
if ((type & PERM_ID_GRANT) != 0) {
|
||||
type &= ~PERM_ID_GRANT;
|
||||
}
|
||||
|
||||
this->permission_ = permission::resolvePermissionData(type);
|
||||
} else if(bulk.has("permsid")) {
|
||||
auto permission_name = bulk["permsid"].string();
|
||||
this->permission_ = permission::resolvePermissionData(permission_name);
|
||||
this->grant_ = this->permission_->grantName() == permission_name;;
|
||||
} else {
|
||||
this->error_.reset(ts::command_result{error::parameter_missing, "permid"});
|
||||
return;
|
||||
}
|
||||
|
||||
if(this->permission_->is_invalid()) {
|
||||
this->error_.reset(ts::command_result{error::parameter_invalid});
|
||||
return;
|
||||
}
|
||||
|
||||
if(kParseValue) {
|
||||
if(!bulk.has("permvalue")) {
|
||||
this->error_.reset(ts::command_result{error::parameter_missing, "permvalue"});
|
||||
return;
|
||||
}
|
||||
|
||||
this->value_ = bulk["permvalue"].as<permission::PermissionValue>();
|
||||
this->flag_skip_ = bulk.has("permskip") && bulk["permskip"].as<bool>();
|
||||
this->flag_negated_ = bulk.has("permnegated") && bulk["permnegated"].as<bool>();
|
||||
}
|
||||
}
|
||||
PermissionBulkParser(const PermissionBulkParser&) = delete;
|
||||
PermissionBulkParser(PermissionBulkParser&&) = default;
|
||||
|
||||
~PermissionBulkParser() {
|
||||
this->error_.release_data();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool has_error() const {
|
||||
assert(!this->error_released_);
|
||||
return this->error_.has_error();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool has_value() const { return this->value_ != permission::undefined; }
|
||||
[[nodiscard]] inline bool is_grant_permission() const { return this->grant_; }
|
||||
|
||||
[[nodiscard]] inline const auto& permission() const { return this->permission_; }
|
||||
[[nodiscard]] inline auto permission_type() const { return this->permission_->type; }
|
||||
|
||||
[[nodiscard]] inline auto value() const { return this->value_; }
|
||||
[[nodiscard]] inline auto flag_skip() const { return this->flag_skip_; }
|
||||
[[nodiscard]] inline auto flag_negated() const { return this->flag_negated_; }
|
||||
|
||||
[[nodiscard]] inline ts::command_result release_error() {
|
||||
assert(!std::exchange(this->error_released_, true));
|
||||
return std::move(this->error_);
|
||||
}
|
||||
|
||||
inline void emplace_custom_error(ts::command_result&& result) {
|
||||
assert(!this->error_released_);
|
||||
this->error_.reset(std::forward<ts::command_result>(result));
|
||||
}
|
||||
|
||||
inline void apply_to(const std::shared_ptr<permission::v2::PermissionManager>& manager, permission::v2::PermissionUpdateType mode) const {
|
||||
if(this->is_grant_permission()) {
|
||||
manager->set_permission(this->permission_type(), permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::do_nothing, mode);
|
||||
} else {
|
||||
manager->set_permission(
|
||||
this->permission_type(),
|
||||
{ this->value(), true },
|
||||
mode,
|
||||
permission::v2::PermissionUpdateType::do_nothing
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
inline void apply_to_channel(const std::shared_ptr<permission::v2::PermissionManager>& manager, permission::v2::PermissionUpdateType mode, ChannelId channel_id) const {
|
||||
if(this->is_grant_permission()) {
|
||||
manager->set_channel_permission(this->permission_type(), channel_id, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::do_nothing, mode);
|
||||
} else {
|
||||
manager->set_channel_permission(
|
||||
this->permission_type(),
|
||||
channel_id,
|
||||
{ this->value(), true },
|
||||
mode,
|
||||
permission::v2::PermissionUpdateType::do_nothing
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool is_group_property() const {
|
||||
return permission_is_group_property(this->permission_type());
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool is_client_view_property() const {
|
||||
return permission_is_client_property(this->permission_type());
|
||||
}
|
||||
private:
|
||||
std::shared_ptr<permission::PermissionTypeEntry> permission_{nullptr};
|
||||
bool grant_{false};
|
||||
|
||||
bool flag_skip_{false};
|
||||
bool flag_negated_{false};
|
||||
|
||||
permission::PermissionValue value_{0};
|
||||
ts::command_result error_{error::ok};
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool error_released_{false};
|
||||
#endif
|
||||
};
|
||||
|
||||
template <bool kParseValue>
|
||||
class PermissionBulksParser {
|
||||
public:
|
||||
PermissionBulksParser(const PermissionBulksParser&) = delete;
|
||||
PermissionBulksParser(PermissionBulksParser&&) = default;
|
||||
|
||||
template <typename base_iterator>
|
||||
struct FilteredPermissionIterator : public base_iterator {
|
||||
public:
|
||||
FilteredPermissionIterator() = default;
|
||||
explicit FilteredPermissionIterator(base_iterator position, base_iterator end = {}) : base_iterator{position}, end_{end} {
|
||||
if(*this != this->end_) {
|
||||
const auto& entry = **this;
|
||||
if(entry.has_error())
|
||||
this->operator++();
|
||||
}
|
||||
}
|
||||
|
||||
FilteredPermissionIterator& operator++() {
|
||||
while(true) {
|
||||
base_iterator::operator++();
|
||||
if(*this == this->end_) break;
|
||||
|
||||
const auto& entry = **this;
|
||||
if(!entry.has_error()) break;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[nodiscard]] FilteredPermissionIterator operator++(int) const {
|
||||
FilteredPermissionIterator copy = *this;
|
||||
++*this;
|
||||
return copy;
|
||||
}
|
||||
|
||||
private:
|
||||
base_iterator end_;
|
||||
};
|
||||
|
||||
struct FilteredPermissionListIterable {
|
||||
typedef typename std::vector<PermissionBulkParser<kParseValue>>::const_iterator const_iterator;
|
||||
public:
|
||||
FilteredPermissionListIterable(const_iterator begin, const_iterator end) noexcept : begin_{begin}, end_{end} {}
|
||||
|
||||
FilteredPermissionIterator<const_iterator> begin() const {
|
||||
return FilteredPermissionIterator{this->begin_, this->end_};
|
||||
}
|
||||
|
||||
FilteredPermissionIterator<const_iterator> end() const {
|
||||
return FilteredPermissionIterator{this->end_, this->end_};
|
||||
}
|
||||
private:
|
||||
const_iterator begin_;
|
||||
const_iterator end_;
|
||||
};
|
||||
|
||||
explicit PermissionBulksParser(ts::Command& command) {
|
||||
this->permissions_.reserve(command.bulkCount());
|
||||
for(size_t index{0}; index < command.bulkCount(); index++)
|
||||
this->permissions_.emplace_back(command[index]);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool validate(const std::shared_ptr<server::ConnectedClient>& issuer, ChannelId channel_id) {
|
||||
auto ignore_granted_values = permission::v2::permission_granted(1, issuer->calculate_permission(permission::b_permission_modify_power_ignore, channel_id));
|
||||
if(!ignore_granted_values) {
|
||||
auto max_value = issuer->calculate_permission(permission::i_permission_modify_power, channel_id, false);
|
||||
if(!max_value.has_value) {
|
||||
for(PermissionBulkParser<kParseValue>& permission : this->permissions_) {
|
||||
if(permission.has_error()) continue;
|
||||
|
||||
permission.emplace_custom_error(ts::command_result{permission::i_permission_modify_power});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
for(size_t index{0}; index < this->permissions_.size(); index++) {
|
||||
PermissionBulkParser<kParseValue>& permission = this->permissions_[index];
|
||||
if(permission.has_error()) continue;
|
||||
|
||||
if(kParseValue && permission_require_granted_value(permission.permission_type()) && !permission::v2::permission_granted(permission.value(), max_value)) {
|
||||
permission.emplace_custom_error(ts::command_result{permission::i_permission_modify_power});
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!permission::v2::permission_granted(1, issuer->calculate_permission(permission.permission_type(), channel_id, true))) {
|
||||
permission.emplace_custom_error(ts::command_result{permission::i_permission_modify_power});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline FilteredPermissionListIterable iterate_valid_permissions() const {
|
||||
return FilteredPermissionListIterable{this->permissions_.begin(), this->permissions_.end()};
|
||||
}
|
||||
|
||||
[[nodiscard]] inline ts::command_result build_command_result() {
|
||||
assert(!std::exchange(this->result_created_, true));
|
||||
ts::command_result_bulk result{};
|
||||
|
||||
for(auto& permission : this->permissions_)
|
||||
result.insert_result(std::forward<ts::command_result>(permission.release_error()));
|
||||
|
||||
return ts::command_result{std::move(result)};
|
||||
}
|
||||
private:
|
||||
std::vector<PermissionBulkParser<kParseValue>> permissions_{};
|
||||
#ifndef NDEBUG
|
||||
bool result_created_{false};
|
||||
#endif
|
||||
};
|
||||
}
|
@ -20,6 +20,7 @@
|
||||
#include <cstdint>
|
||||
|
||||
#include "helpers.h"
|
||||
#include "./bulk_parsers.h"
|
||||
|
||||
#include <Properties.h>
|
||||
#include <log/LogUtils.h>
|
||||
@ -407,46 +408,16 @@ command_result ConnectedClient::handleCommandChannelGroupAddPerm(Command &cmd) {
|
||||
if (!channelGroup || channelGroup->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "invalid channel group id"};
|
||||
ACTION_REQUIRES_GROUP_PERMISSION(channelGroup, permission::i_channel_group_needed_modify_power, permission::i_channel_group_modify_power, true);
|
||||
|
||||
auto max_value = this->calculate_permission(permission::i_permission_modify_power, 0, false);
|
||||
if(!max_value.has_value) return command_result{permission::i_permission_modify_power};
|
||||
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
|
||||
if(!pparser.validate(this->ref(), 0))
|
||||
return pparser.build_command_result();
|
||||
|
||||
auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0));
|
||||
|
||||
auto conOnError = cmd[0].has("continueonerror");
|
||||
bool updateList = false;
|
||||
|
||||
auto permission_manager = channelGroup->permissions();
|
||||
for (int index = 0; index < cmd.bulkCount(); index++) {
|
||||
PARSE_PERMISSION(cmd);
|
||||
|
||||
auto val = cmd[index]["permvalue"].as<permission::PermissionValue>();
|
||||
if(!ignore_granted_values && permission_require_granted_value(permType) && !permission::v2::permission_granted(val, max_value)) {
|
||||
if(conOnError) continue;
|
||||
return command_result{permission::i_permission_modify_power};
|
||||
}
|
||||
|
||||
if(!permission::v2::permission_granted(1, this->calculate_permission(permType, 0, true))) {
|
||||
if(conOnError) continue;
|
||||
return command_result{permission::i_permission_modify_power};
|
||||
}
|
||||
|
||||
if (grant) {
|
||||
permission_manager->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::set_value);
|
||||
} else {
|
||||
permission_manager->set_permission(
|
||||
permType,
|
||||
{cmd[index]["permvalue"], 0},
|
||||
permission::v2::PermissionUpdateType::set_value,
|
||||
permission::v2::PermissionUpdateType::do_nothing,
|
||||
|
||||
cmd[index]["permskip"].as<bool>() ? 1 : 0,
|
||||
cmd[index]["permnegated"].as<bool>() ? 1 : 0
|
||||
);
|
||||
updateList |= permission_is_group_property(permType);
|
||||
}
|
||||
bool updateList{false};
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
ppermission.apply_to(channelGroup->permissions(), permission::v2::PermissionUpdateType::set_value);
|
||||
updateList |= ppermission.is_group_property();
|
||||
}
|
||||
|
||||
|
||||
if(updateList)
|
||||
channelGroup->apply_properties_from_permissions();
|
||||
|
||||
@ -465,7 +436,8 @@ command_result ConnectedClient::handleCommandChannelGroupAddPerm(Command &cmd) {
|
||||
}
|
||||
});
|
||||
}
|
||||
return command_result{error::ok};
|
||||
|
||||
return pparser.build_command_result();
|
||||
}
|
||||
|
||||
command_result ConnectedClient::handleCommandChannelGroupDelPerm(Command &cmd) {
|
||||
@ -475,29 +447,14 @@ command_result ConnectedClient::handleCommandChannelGroupDelPerm(Command &cmd) {
|
||||
if (!channelGroup || channelGroup->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "invalid channel group id"};
|
||||
ACTION_REQUIRES_GROUP_PERMISSION(channelGroup, permission::i_channel_group_needed_modify_power, permission::i_channel_group_modify_power, true);
|
||||
|
||||
bool updateList = false;
|
||||
bool conOnError = cmd[0].has("continueonerror");
|
||||
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
|
||||
if(!pparser.validate(this->ref(), 0))
|
||||
return pparser.build_command_result();
|
||||
|
||||
auto permission_manager = channelGroup->permissions();
|
||||
for (int index = 0; index < cmd.bulkCount(); index++) {
|
||||
PARSE_PERMISSION(cmd)
|
||||
|
||||
if(!permission::v2::permission_granted(0, this->calculate_permission(permType, 0, true))) {
|
||||
if(conOnError) continue;
|
||||
return command_result{permission::i_permission_modify_power};
|
||||
}
|
||||
|
||||
if (grant) {
|
||||
permission_manager->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::delete_value);
|
||||
} else {
|
||||
permission_manager->set_permission(
|
||||
permType,
|
||||
permission::v2::empty_permission_values,
|
||||
permission::v2::PermissionUpdateType::delete_value,
|
||||
permission::v2::PermissionUpdateType::do_nothing
|
||||
);
|
||||
updateList |= permission_is_group_property(permType);
|
||||
}
|
||||
bool updateList{false};
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
ppermission.apply_to(channelGroup->permissions(), permission::v2::PermissionUpdateType::delete_value);
|
||||
updateList |= ppermission.is_group_property();
|
||||
}
|
||||
|
||||
if(updateList)
|
||||
@ -518,7 +475,8 @@ command_result ConnectedClient::handleCommandChannelGroupDelPerm(Command &cmd) {
|
||||
}
|
||||
});
|
||||
}
|
||||
return command_result{error::ok};
|
||||
|
||||
return pparser.build_command_result();
|
||||
}
|
||||
|
||||
//TODO: Test if parent or previous is deleted!
|
||||
@ -1479,101 +1437,33 @@ command_result ConnectedClient::handleCommandChannelAddPerm(Command &cmd) {
|
||||
|
||||
ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_channel_needed_permission_modify_power, permission::i_channel_permission_modify_power, true);
|
||||
|
||||
auto max_value = this->calculate_permission(permission::i_permission_modify_power, channel_id, false);
|
||||
if(!max_value.has_value) return command_result{permission::i_permission_modify_power};
|
||||
|
||||
auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, channel_id));
|
||||
auto updateClients = false, update_view = false, update_channel_properties = false;
|
||||
|
||||
bool conOnError = cmd[0].has("continueonerror");
|
||||
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
|
||||
if(!pparser.validate(this->ref(), channel->channelId()))
|
||||
return pparser.build_command_result();
|
||||
|
||||
auto permission_manager = channel->permissions();
|
||||
for (int index = 0; index < cmd.bulkCount(); index++) {
|
||||
PARSE_PERMISSION(cmd);
|
||||
auto updateClients = false, update_join_permissions = false, update_channel_properties = false;
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
ppermission.apply_to(permission_manager, permission::v2::PermissionUpdateType::set_value);
|
||||
|
||||
auto val = cmd[index]["permvalue"].as<permission::PermissionValue>();
|
||||
if(!ignore_granted_values && permission_require_granted_value(permType) && !permission::v2::permission_granted(val, max_value)) {
|
||||
if(conOnError) continue;
|
||||
return command_result{permission::i_permission_modify_power};
|
||||
}
|
||||
|
||||
if(!permission::v2::permission_granted(1, this->calculate_permission(permType, channel_id, true))) {
|
||||
if(conOnError) continue;
|
||||
return command_result{permission::i_permission_modify_power};
|
||||
}
|
||||
|
||||
|
||||
if (grant) {
|
||||
permission_manager->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::set_value);
|
||||
} else {
|
||||
permission_manager->set_permission(
|
||||
permType,
|
||||
{cmd[index]["permvalue"], 0},
|
||||
permission::v2::PermissionUpdateType::set_value,
|
||||
permission::v2::PermissionUpdateType::do_nothing,
|
||||
|
||||
cmd[index]["permskip"].as<bool>() ? 1 : 0,
|
||||
cmd[index]["permnegated"].as<bool>() ? 1 : 0
|
||||
);
|
||||
updateClients |= permission_is_client_property(permType);
|
||||
update_view |= permType == permission::i_channel_needed_view_power;
|
||||
update_channel_properties |= channel->permission_require_property_update(permType);
|
||||
|
||||
if (permType == permission::i_icon_id) {
|
||||
if(this->server) {
|
||||
auto self_ref = this->ref();
|
||||
this->server->forEachClient([&](std::shared_ptr<ConnectedClient> cl) {
|
||||
shared_lock client_channel_lock(cl->channel_lock);
|
||||
cl->notifyChannelEdited(channel, {property::CHANNEL_ICON_ID}, self_ref, false);
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
updateClients |= ppermission.is_client_view_property();
|
||||
update_join_permissions = ppermission.permission_type() == permission::i_channel_needed_join_power;
|
||||
update_channel_properties |= channel->permission_require_property_update(ppermission.permission_type());
|
||||
}
|
||||
|
||||
/* broadcast the updated channel properties */
|
||||
if(update_channel_properties) {
|
||||
auto updates = channel->update_properties_from_permissions();
|
||||
if(!updates.empty() && this->server){
|
||||
auto self_ref = this->ref();
|
||||
this->server->forEachClient([&](std::shared_ptr<ConnectedClient> cl) {
|
||||
shared_lock client_channel_lock(cl->channel_lock);
|
||||
cl->notifyChannelEdited(channel, updates, self_ref, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
if(update_channel_properties && this->server)
|
||||
this->server->update_channel_from_permissions(channel, this->ref());
|
||||
|
||||
if(updateClients && this->server)
|
||||
for(const auto& client : this->server->getClientsByChannel(channel)) {
|
||||
/* let them lock the server channel tree as well (read lock so does not matter) */
|
||||
client->updateChannelClientProperties(true, true);
|
||||
client->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */
|
||||
}
|
||||
|
||||
if(update_view && this->server) {
|
||||
auto l_source = this->server->channelTree->findLinkedChannel(channel->channelId());
|
||||
this->server->forEachClient([&](const shared_ptr<ConnectedClient>& cl) {
|
||||
/* server tree read lock still active */
|
||||
auto l_target = !cl->currentChannel ? nullptr : cl->server->channelTree->findLinkedChannel(cl->currentChannel->channelId());
|
||||
sassert(l_source);
|
||||
if(cl->currentChannel) sassert(l_target);
|
||||
|
||||
{
|
||||
unique_lock client_channel_lock(cl->channel_lock);
|
||||
|
||||
deque<ChannelId> deleted;
|
||||
for(const auto& update_entry : cl->channels->update_channel(l_source, l_target)) {
|
||||
if(update_entry.first)
|
||||
cl->notifyChannelShow(update_entry.second->channel(), update_entry.second->previous_channel);
|
||||
else deleted.push_back(update_entry.second->channelId());
|
||||
}
|
||||
if(!deleted.empty())
|
||||
cl->notifyChannelHide(deleted, false);
|
||||
}
|
||||
if((updateClients || update_join_permissions) && this->server) {
|
||||
this->server->forEachClient([&](std::shared_ptr<ConnectedClient> cl) {
|
||||
if(updateClients && cl->currentChannel == channel)
|
||||
cl->updateChannelClientProperties(true, true);
|
||||
if(update_join_permissions)
|
||||
cl->join_state_id++;
|
||||
});
|
||||
}
|
||||
return command_result{error::ok};;
|
||||
|
||||
return pparser.build_command_result();
|
||||
}
|
||||
|
||||
command_result ConnectedClient::handleCommandChannelDelPerm(Command &cmd) {
|
||||
@ -1585,71 +1475,33 @@ command_result ConnectedClient::handleCommandChannelDelPerm(Command &cmd) {
|
||||
|
||||
ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_channel_needed_permission_modify_power, permission::i_channel_permission_modify_power, true);
|
||||
|
||||
auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, channel_id));
|
||||
bool conOnError = cmd[0].has("continueonerror");
|
||||
auto updateClients = false, update_view = false, update_channel_properties = false;
|
||||
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
|
||||
if(!pparser.validate(this->ref(), channel->channelId()))
|
||||
return pparser.build_command_result();
|
||||
|
||||
auto permission_manager = channel->permissions();
|
||||
for (int index = 0; index < cmd.bulkCount(); index++) {
|
||||
PARSE_PERMISSION(cmd);
|
||||
auto updateClients = false, update_join_permissions = false, update_channel_properties = false;
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
ppermission.apply_to(permission_manager, permission::v2::PermissionUpdateType::delete_value);
|
||||
|
||||
if(!ignore_granted_values && !permission::v2::permission_granted(0, this->calculate_permission(permType, channel_id, true))) {
|
||||
if(conOnError) continue;
|
||||
return command_result{permission::i_permission_modify_power};
|
||||
}
|
||||
|
||||
if (grant) {
|
||||
permission_manager->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::delete_value);
|
||||
} else {
|
||||
permission_manager->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::delete_value, permission::v2::PermissionUpdateType::do_nothing);
|
||||
updateClients |= permission_is_client_property(permType);
|
||||
update_view |= permType == permission::i_channel_needed_view_power;
|
||||
update_channel_properties |= channel->permission_require_property_update(permType);
|
||||
}
|
||||
updateClients |= ppermission.is_client_view_property();
|
||||
update_join_permissions = ppermission.permission_type() == permission::i_channel_needed_join_power;
|
||||
update_channel_properties |= channel->permission_require_property_update(ppermission.permission_type());
|
||||
}
|
||||
|
||||
/* broadcast the updated channel properties */
|
||||
if(update_channel_properties) {
|
||||
auto updates = channel->update_properties_from_permissions();
|
||||
if(!updates.empty() && this->server){
|
||||
auto self_ref = this->ref();
|
||||
this->server->forEachClient([&](std::shared_ptr<ConnectedClient> cl) {
|
||||
shared_lock client_channel_lock(cl->channel_lock);
|
||||
cl->notifyChannelEdited(channel, updates, self_ref, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
if(update_channel_properties && this->server)
|
||||
this->server->update_channel_from_permissions(channel, this->ref());
|
||||
|
||||
if(updateClients && this->server)
|
||||
if((updateClients || update_join_permissions) && this->server) {
|
||||
this->server->forEachClient([&](std::shared_ptr<ConnectedClient> cl) {
|
||||
if(cl->currentChannel == channel) {
|
||||
if(updateClients && cl->currentChannel == channel)
|
||||
cl->updateChannelClientProperties(true, true);
|
||||
cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */
|
||||
}
|
||||
});
|
||||
if(update_view && this->server) {
|
||||
this->server->forEachClient([&](std::shared_ptr<ConnectedClient> cl) {
|
||||
/* server tree read lock still active */
|
||||
auto l_source = cl->server->channelTree->findLinkedChannel(channel->channelId());
|
||||
auto l_target = !cl->currentChannel ? nullptr : cl->server->channelTree->findLinkedChannel(cl->currentChannel->channelId());
|
||||
sassert(l_source);
|
||||
if(cl->currentChannel) sassert(l_target);
|
||||
|
||||
{
|
||||
unique_lock client_channel_lock(cl->channel_lock);
|
||||
|
||||
deque<ChannelId> deleted;
|
||||
for(const auto& update_entry : cl->channels->update_channel(l_source, l_target)) {
|
||||
if(update_entry.first)
|
||||
cl->notifyChannelShow(update_entry.second->channel(), update_entry.second->previous_channel);
|
||||
else deleted.push_back(update_entry.second->channelId());
|
||||
}
|
||||
if(!deleted.empty())
|
||||
cl->notifyChannelHide(deleted, false);
|
||||
}
|
||||
if(update_join_permissions)
|
||||
cl->join_state_id++;
|
||||
});
|
||||
}
|
||||
return command_result{error::ok};
|
||||
|
||||
return pparser.build_command_result();
|
||||
}
|
||||
|
||||
command_result ConnectedClient::handleCommandChannelClientPermList(Command &cmd) {
|
||||
@ -1728,27 +1580,21 @@ command_result ConnectedClient::handleCommandChannelClientDelPerm(Command &cmd)
|
||||
ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, required_permissions, channel_id);
|
||||
}
|
||||
|
||||
bool conOnError = cmd[0].has("continueonerror"), update_view = false;
|
||||
auto cll = this->server->findClientsByCldbId(cldbid);
|
||||
for (int index = 0; index < cmd.bulkCount(); index++) {
|
||||
PARSE_PERMISSION(cmd);
|
||||
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
|
||||
if(!pparser.validate(this->ref(), channel->channelId()))
|
||||
return pparser.build_command_result();
|
||||
|
||||
if(!permission::v2::permission_granted(0, this->calculate_permission(permType, channel_id, true))) {
|
||||
if(conOnError) continue;
|
||||
return command_result{permission::i_permission_modify_power};
|
||||
}
|
||||
|
||||
if (grant) {
|
||||
mgr->set_channel_permission(permType, channel->channelId(), permission::v2::empty_permission_values, permission::v2::do_nothing, permission::v2::delete_value);
|
||||
} else {
|
||||
mgr->set_channel_permission(permType, channel->channelId(), permission::v2::empty_permission_values, permission::v2::delete_value, permission::v2::do_nothing);
|
||||
update_view = permType == permission::b_channel_ignore_view_power || permType == permission::i_channel_view_power;
|
||||
}
|
||||
bool update_view{false};
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
ppermission.apply_to_channel(mgr, permission::v2::PermissionUpdateType::delete_value, channel->channelId());
|
||||
update_view |= ppermission.is_client_view_property();
|
||||
}
|
||||
|
||||
serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr);
|
||||
if (!cll.empty()) {
|
||||
for (const auto &elm : cll) {
|
||||
|
||||
auto onlineClients = this->server->findClientsByCldbId(cldbid);
|
||||
if (!onlineClients.empty()) {
|
||||
for (const auto &elm : onlineClients) {
|
||||
if(elm->update_cached_permissions()) /* update cached calculated permissions */
|
||||
elm->sendNeededPermissions(false); /* cached permissions had changed, notify the client */
|
||||
|
||||
@ -1774,7 +1620,7 @@ command_result ConnectedClient::handleCommandChannelClientDelPerm(Command &cmd)
|
||||
}
|
||||
}
|
||||
|
||||
return command_result{error::ok};
|
||||
return pparser.build_command_result();
|
||||
}
|
||||
|
||||
command_result ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd) {
|
||||
@ -1791,45 +1637,25 @@ command_result ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd)
|
||||
if(!channel) return command_result{error::vs_critical};
|
||||
|
||||
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid);
|
||||
{
|
||||
auto required_permissions = this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cmd["cldbid"], ClientType::CLIENT_TEAMSPEAK, channel_id);
|
||||
ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, required_permissions, channel_id);
|
||||
}
|
||||
auto required_permissions = this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cmd["cldbid"], ClientType::CLIENT_TEAMSPEAK, channel_id);
|
||||
ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, required_permissions, channel_id);
|
||||
|
||||
auto max_value = this->calculate_permission(permission::i_permission_modify_power, channel_id, false);
|
||||
if(!max_value.has_value) return command_result{permission::i_permission_modify_power};
|
||||
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
|
||||
if(!pparser.validate(this->ref(), channel->channelId()))
|
||||
return pparser.build_command_result();
|
||||
|
||||
auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, channel_id));
|
||||
auto update_view = false;
|
||||
|
||||
bool conOnError = cmd[0].has("continueonerror");
|
||||
auto onlineClientInstances = this->server->findClientsByCldbId(cldbid);
|
||||
|
||||
for (int index = 0; index < cmd.bulkCount(); index++) {
|
||||
PARSE_PERMISSION(cmd);
|
||||
|
||||
auto val = cmd[index]["permvalue"].as<permission::PermissionValue>();
|
||||
if(!ignore_granted_values && permission_require_granted_value(permType) && !permission::v2::permission_granted(val, max_value)) {
|
||||
if(conOnError) continue;
|
||||
return command_result{permission::i_permission_modify_power};
|
||||
}
|
||||
|
||||
if(!permission::v2::permission_granted(1, this->calculate_permission(permType, channel_id, true))) {
|
||||
if(conOnError) continue;
|
||||
return command_result{permission::i_permission_modify_power};
|
||||
}
|
||||
|
||||
if (grant) {
|
||||
mgr->set_channel_permission(permType, channel->channelId(), {0, cmd[index]["permvalue"]}, permission::v2::do_nothing, permission::v2::set_value);
|
||||
} else {
|
||||
mgr->set_channel_permission(permType, channel->channelId(), {cmd[index]["permvalue"], 0}, permission::v2::set_value, permission::v2::do_nothing, cmd[index]["permskip"] ? 1 : 0, cmd[index]["permnegated"] ? 1 : 0);
|
||||
update_view = permType == permission::b_channel_ignore_view_power || permType == permission::i_channel_view_power;
|
||||
}
|
||||
bool update_view{false};
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
ppermission.apply_to_channel(mgr, permission::v2::PermissionUpdateType::set_value, channel->channelId());
|
||||
update_view |= ppermission.is_client_view_property();
|
||||
}
|
||||
|
||||
serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr);
|
||||
if (!onlineClientInstances.empty())
|
||||
for (const auto &elm : onlineClientInstances) {
|
||||
|
||||
|
||||
auto onlineClients = this->server->findClientsByCldbId(cldbid);
|
||||
if (!onlineClients.empty())
|
||||
for (const auto &elm : onlineClients) {
|
||||
if (elm->update_cached_permissions()) /* update cached calculated permissions */
|
||||
elm->sendNeededPermissions(false); /* cached permissions had changed, notify the client */
|
||||
|
||||
@ -1853,7 +1679,7 @@ command_result ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd)
|
||||
elm->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */
|
||||
}
|
||||
|
||||
return command_result{error::ok};
|
||||
return pparser.build_command_result();
|
||||
}
|
||||
|
||||
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include <cstdint>
|
||||
|
||||
#include "helpers.h"
|
||||
#include "./bulk_parsers.h"
|
||||
|
||||
#include <Properties.h>
|
||||
#include <log/LogUtils.h>
|
||||
@ -241,6 +242,8 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) {
|
||||
|
||||
for(auto& client : clients) {
|
||||
auto oldChannel = client->getChannel();
|
||||
if(!oldChannel) continue;
|
||||
|
||||
this->server->client_move(
|
||||
client.client,
|
||||
channel,
|
||||
@ -964,33 +967,17 @@ command_result ConnectedClient::handleCommandClientAddPerm(Command &cmd) {
|
||||
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid);
|
||||
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_permission_modify_power, this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cldbid, ClientType::CLIENT_TEAMSPEAK, 0));
|
||||
auto max_value = this->calculate_permission(permission::i_permission_modify_power, 0, false);
|
||||
if(!max_value.has_value) return command_result{permission::i_permission_modify_power};
|
||||
|
||||
auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0));
|
||||
bool conOnError = cmd[0].has("continueonerror");
|
||||
auto update_channels = false;
|
||||
for (int index = 0; index < cmd.bulkCount(); index++) {
|
||||
PARSE_PERMISSION(cmd);
|
||||
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
|
||||
if(!pparser.validate(this->ref(), 0))
|
||||
return pparser.build_command_result();
|
||||
|
||||
auto val = cmd[index]["permvalue"].as<permission::PermissionValue>();
|
||||
if(!ignore_granted_values && permission_require_granted_value(permType) && !permission::v2::permission_granted(val, max_value)) {
|
||||
if(conOnError) continue;
|
||||
return command_result{permission::i_permission_modify_power};
|
||||
}
|
||||
|
||||
if(!permission::v2::permission_granted(1, this->calculate_permission(permType, 0, true))) {
|
||||
if(conOnError) continue;
|
||||
return command_result{permission::i_permission_modify_power};
|
||||
}
|
||||
|
||||
if (grant) {
|
||||
mgr->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::do_nothing, permission::v2::set_value);
|
||||
} else {
|
||||
mgr->set_permission(permType, {cmd[index]["permvalue"], 0}, permission::v2::set_value, permission::v2::do_nothing, cmd[index]["permskip"] ? 1 : 0, cmd[index]["permnegated"] ? 1 : 0);
|
||||
update_channels |= permission_is_client_property(permType);
|
||||
}
|
||||
bool update_channels{false};
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
ppermission.apply_to(mgr, permission::v2::PermissionUpdateType::set_value);
|
||||
update_channels |= ppermission.is_client_view_property();
|
||||
}
|
||||
|
||||
serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr);
|
||||
auto onlineClients = this->server->findClientsByCldbId(cldbid);
|
||||
if (!onlineClients.empty())
|
||||
@ -1002,7 +989,7 @@ command_result ConnectedClient::handleCommandClientAddPerm(Command &cmd) {
|
||||
elm->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */
|
||||
}
|
||||
|
||||
return command_result{error::ok};
|
||||
return pparser.build_command_result();
|
||||
}
|
||||
|
||||
command_result ConnectedClient::handleCommandClientDelPerm(Command &cmd) {
|
||||
@ -1016,36 +1003,28 @@ command_result ConnectedClient::handleCommandClientDelPerm(Command &cmd) {
|
||||
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid);
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_permission_modify_power, this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cldbid, ClientType::CLIENT_TEAMSPEAK, 0));
|
||||
|
||||
bool conOnError = cmd[0].has("continueonerror");
|
||||
auto onlineClients = this->server->findClientsByCldbId(cmd["cldbid"]);
|
||||
auto update_channel = false;
|
||||
for (int index = 0; index < cmd.bulkCount(); index++) {
|
||||
PARSE_PERMISSION(cmd)
|
||||
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
|
||||
if(!pparser.validate(this->ref(), 0))
|
||||
return pparser.build_command_result();
|
||||
|
||||
if(!permission::v2::permission_granted(0, this->calculate_permission(permType, 0, true))) {
|
||||
if(conOnError) continue;
|
||||
return command_result{permission::i_permission_modify_power};
|
||||
}
|
||||
|
||||
|
||||
if (grant) {
|
||||
mgr->set_permission(permType, permission::v2::empty_permission_values, permission::v2::do_nothing, permission::v2::delete_value);
|
||||
} else {
|
||||
mgr->set_permission(permType, permission::v2::empty_permission_values, permission::v2::delete_value, permission::v2::do_nothing);
|
||||
update_channel |= permission_is_client_property(permType);
|
||||
}
|
||||
bool update_channels{false};
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
ppermission.apply_to(mgr, permission::v2::PermissionUpdateType::delete_value);
|
||||
update_channels |= ppermission.is_client_view_property();
|
||||
}
|
||||
|
||||
serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr);
|
||||
auto onlineClients = this->server->findClientsByCldbId(cldbid);
|
||||
if (!onlineClients.empty())
|
||||
for (const auto &elm : onlineClients) {
|
||||
if(elm->update_cached_permissions()) /* update cached calculated permissions */
|
||||
elm->sendNeededPermissions(false); /* cached permissions had changed, notify the client */
|
||||
if(update_channel)
|
||||
if(update_channels)
|
||||
elm->updateChannelClientProperties(true, true);
|
||||
elm->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */
|
||||
}
|
||||
return command_result{error::ok};
|
||||
|
||||
return pparser.build_command_result();
|
||||
}
|
||||
|
||||
command_result ConnectedClient::handleCommandClientPermList(Command &cmd) {
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include <StringVariable.h>
|
||||
|
||||
#include "helpers.h"
|
||||
#include "./bulk_parsers.h"
|
||||
|
||||
#include <Properties.h>
|
||||
#include <log/LogUtils.h>
|
||||
@ -558,35 +559,14 @@ command_result ConnectedClient::handleCommandPlaylistAddPerm(ts::Command &cmd) {
|
||||
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr)
|
||||
return command_result{perr};
|
||||
|
||||
auto max_value = this->calculate_permission(permission::i_permission_modify_power, 0, true);
|
||||
if(!max_value.has_value) return command_result{permission::i_permission_modify_power};
|
||||
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
|
||||
if(!pparser.validate(this->ref(), 0))
|
||||
return pparser.build_command_result();
|
||||
|
||||
auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0));
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions())
|
||||
ppermission.apply_to(playlist->permission_manager(), permission::v2::PermissionUpdateType::set_value);
|
||||
|
||||
|
||||
bool conOnError = cmd[0].has("continueonerror");
|
||||
for (int index = 0; index < cmd.bulkCount(); index++) {
|
||||
PARSE_PERMISSION(cmd);
|
||||
|
||||
auto val = cmd[index]["permvalue"].as<permission::PermissionValue>();
|
||||
if(permission_require_granted_value(permType) && !permission::v2::permission_granted(val, max_value)) {
|
||||
if(conOnError) continue;
|
||||
return command_result{permission::i_permission_modify_power};
|
||||
}
|
||||
|
||||
if(!ignore_granted_values && !permission::v2::permission_granted(1, this->calculate_permission(permType, 0, true))) {
|
||||
if(conOnError) continue;
|
||||
return command_result{permission::i_permission_modify_power};
|
||||
}
|
||||
|
||||
if (grant) {
|
||||
playlist->permission_manager()->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::do_nothing, permission::v2::set_value);
|
||||
} else {
|
||||
playlist->permission_manager()->set_permission(permType, {cmd[index]["permvalue"],0}, permission::v2::set_value, permission::v2::do_nothing, cmd[index]["permskip"].as<bool>(), cmd[index]["permnegated"].as<bool>());
|
||||
}
|
||||
}
|
||||
|
||||
return command_result{error::ok};
|
||||
return pparser.build_command_result();
|
||||
}
|
||||
|
||||
command_result ConnectedClient::handleCommandPlaylistDelPerm(ts::Command &cmd) {
|
||||
@ -600,26 +580,14 @@ command_result ConnectedClient::handleCommandPlaylistDelPerm(ts::Command &cmd) {
|
||||
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr)
|
||||
return command_result{perr};
|
||||
|
||||
auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0));
|
||||
bool conOnError = cmd[0].has("continueonerror");
|
||||
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
|
||||
if(!pparser.validate(this->ref(), 0))
|
||||
return pparser.build_command_result();
|
||||
|
||||
for (int index = 0; index < cmd.bulkCount(); index++) {
|
||||
PARSE_PERMISSION(cmd);
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions())
|
||||
ppermission.apply_to(playlist->permission_manager(), permission::v2::PermissionUpdateType::delete_value);
|
||||
|
||||
if(!ignore_granted_values && !permission::v2::permission_granted(0, this->calculate_permission(permType, 0, true))) {
|
||||
if(conOnError) continue;
|
||||
return command_result{permission::i_permission_modify_power};
|
||||
}
|
||||
|
||||
|
||||
if (grant) {
|
||||
playlist->permission_manager()->set_permission(permType, {0, 0}, permission::v2::do_nothing, permission::v2::delete_value);
|
||||
} else {
|
||||
playlist->permission_manager()->set_permission(permType, {0, 0}, permission::v2::delete_value, permission::v2::do_nothing);
|
||||
}
|
||||
}
|
||||
|
||||
return command_result{error::ok};
|
||||
return pparser.build_command_result();
|
||||
}
|
||||
|
||||
command_result ConnectedClient::handleCommandPlaylistClientList(ts::Command &cmd) {
|
||||
@ -735,34 +703,14 @@ command_result ConnectedClient::handleCommandPlaylistClientAddPerm(ts::Command &
|
||||
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr)
|
||||
return command_result{perr};
|
||||
|
||||
auto max_value = this->calculate_permission(permission::i_permission_modify_power, 0, true);
|
||||
if(!max_value.has_value) return command_result{permission::i_permission_modify_power};
|
||||
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
|
||||
if(!pparser.validate(this->ref(), this->getClientDatabaseId()))
|
||||
return pparser.build_command_result();
|
||||
|
||||
auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0));
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions())
|
||||
ppermission.apply_to_channel(playlist->permission_manager(), permission::v2::PermissionUpdateType::set_value, client_id);
|
||||
|
||||
bool conOnError = cmd[0].has("continueonerror");
|
||||
for (int index = 0; index < cmd.bulkCount(); index++) {
|
||||
PARSE_PERMISSION(cmd);
|
||||
|
||||
auto val = cmd[index]["permvalue"].as<permission::PermissionValue>();
|
||||
if(permission_require_granted_value(permType) && !permission::v2::permission_granted(val, max_value)) {
|
||||
if(conOnError) continue;
|
||||
return command_result{permission::i_permission_modify_power};
|
||||
}
|
||||
|
||||
if(!ignore_granted_values && !permission::v2::permission_granted(1, this->calculate_permission(permType, 0, true))) {
|
||||
if(conOnError) continue;
|
||||
return command_result{permission::i_permission_modify_power};
|
||||
}
|
||||
|
||||
if (grant) {
|
||||
playlist->permission_manager()->set_channel_permission(permType, client_id, {0, cmd[index]["permvalue"]}, permission::v2::do_nothing, permission::v2::set_value);
|
||||
} else {
|
||||
playlist->permission_manager()->set_channel_permission(permType, client_id, {cmd[index]["permvalue"], 0}, permission::v2::set_value, permission::v2::do_nothing, cmd[index]["permskip"].as<bool>(), cmd[index]["permnegated"].as<bool>());
|
||||
}
|
||||
}
|
||||
|
||||
return command_result{error::ok};
|
||||
return pparser.build_command_result();
|
||||
}
|
||||
|
||||
command_result ConnectedClient::handleCommandPlaylistClientDelPerm(ts::Command &cmd) {
|
||||
@ -779,26 +727,15 @@ command_result ConnectedClient::handleCommandPlaylistClientDelPerm(ts::Command &
|
||||
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr)
|
||||
return command_result{perr};
|
||||
|
||||
auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0));
|
||||
bool conOnError = cmd[0].has("continueonerror");
|
||||
|
||||
for (int index = 0; index < cmd.bulkCount(); index++) {
|
||||
PARSE_PERMISSION(cmd);
|
||||
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
|
||||
if(!pparser.validate(this->ref(), this->getClientDatabaseId()))
|
||||
return pparser.build_command_result();
|
||||
|
||||
if(!ignore_granted_values && !permission::v2::permission_granted(0, this->calculate_permission(permType, 0, true))) {
|
||||
if(conOnError) continue;
|
||||
return command_result{permission::i_permission_modify_power};
|
||||
}
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions())
|
||||
ppermission.apply_to_channel(playlist->permission_manager(), permission::v2::PermissionUpdateType::delete_value, client_id);
|
||||
|
||||
|
||||
if (grant) {
|
||||
playlist->permission_manager()->set_channel_permission(permType, client_id, {0, 0}, permission::v2::do_nothing, permission::v2::delete_value);
|
||||
} else {
|
||||
playlist->permission_manager()->set_channel_permission(permType, client_id, {0, 0}, permission::v2::delete_value, permission::v2::do_nothing);
|
||||
}
|
||||
}
|
||||
|
||||
return command_result{error::ok};
|
||||
return pparser.build_command_result();
|
||||
}
|
||||
|
||||
constexpr auto max_song_meta_info = 1024 * 512;
|
||||
|
@ -4,9 +4,6 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <spdlog/sinks/rotating_file_sink.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <bitset>
|
||||
#include <algorithm>
|
||||
#include <openssl/sha.h>
|
||||
@ -16,7 +13,6 @@
|
||||
#include "src/server/file/LocalFileServer.h"
|
||||
#include "../../server/VoiceServer.h"
|
||||
#include "../voice/VoiceClient.h"
|
||||
#include "PermissionManager.h"
|
||||
#include "../../InstanceHandler.h"
|
||||
#include "../../server/QueryServer.h"
|
||||
#include "../file/FileClient.h"
|
||||
@ -25,25 +21,17 @@
|
||||
#include "../../weblist/WebListManager.h"
|
||||
#include "../../manager/ConversationManager.h"
|
||||
#include "../../manager/PermissionNameMapper.h"
|
||||
#include <experimental/filesystem>
|
||||
#include <cstdint>
|
||||
#include <StringVariable.h>
|
||||
|
||||
#include "helpers.h"
|
||||
#include "./bulk_parsers.h"
|
||||
|
||||
#include <Properties.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include <misc/sassert.h>
|
||||
#include <misc/base64.h>
|
||||
#include <misc/hex.h>
|
||||
#include <misc/digest.h>
|
||||
#include <misc/rnd.h>
|
||||
#include <misc/timer.h>
|
||||
#include <misc/strobf.h>
|
||||
#include <misc/scope_guard.h>
|
||||
#include <bbcode/bbcodes.h>
|
||||
|
||||
namespace fs = std::experimental::filesystem;
|
||||
using namespace std::chrono;
|
||||
using namespace std;
|
||||
using namespace ts;
|
||||
@ -805,73 +793,43 @@ command_result ConnectedClient::handleCommandServerGroupAddPerm(Command &cmd) {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1);
|
||||
}
|
||||
|
||||
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
|
||||
if(!pparser.validate(this->ref(), 0))
|
||||
return pparser.build_command_result();
|
||||
|
||||
auto max_value = this->calculate_permission(permission::i_permission_modify_power, 0, false);
|
||||
if(!max_value.has_value) return command_result{permission::i_permission_modify_power};
|
||||
|
||||
auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0));
|
||||
|
||||
bool conOnError = cmd[0].has("continueonerror");
|
||||
bool checkTp = false;
|
||||
bool sgroupUpdate = false;
|
||||
|
||||
bool update_talk_power{false}, update_server_group_list{false};
|
||||
auto permissions = serverGroup->permissions();
|
||||
for (int index = 0; index < cmd.bulkCount(); index++) {
|
||||
PARSE_PERMISSION(cmd);
|
||||
//permvalue='1' permnegated='0' permskip='0'
|
||||
|
||||
auto val = cmd[index]["permvalue"].as<permission::PermissionValue>();
|
||||
if(!ignore_granted_values && permission_require_granted_value(permType) && !permission::v2::permission_granted(val, max_value)) {
|
||||
if(conOnError) continue;
|
||||
return command_result{permission::i_permission_modify_power};
|
||||
}
|
||||
|
||||
if(!permission::v2::permission_granted(1, this->calculate_permission(permType, 0, true))) {
|
||||
if(conOnError) continue;
|
||||
return command_result{permission::i_permission_modify_power};
|
||||
}
|
||||
|
||||
if (grant) {
|
||||
permissions->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::set_value);
|
||||
} else {
|
||||
permissions->set_permission(
|
||||
permType,
|
||||
{cmd[index]["permvalue"], 0},
|
||||
permission::v2::PermissionUpdateType::set_value,
|
||||
permission::v2::PermissionUpdateType::do_nothing,
|
||||
|
||||
cmd[index]["permskip"].as<bool>() ? 1 : 0,
|
||||
cmd[index]["permnegated"].as<bool>() ? 1 : 0
|
||||
);
|
||||
sgroupUpdate |= permission_is_group_property(permType);
|
||||
checkTp |= permission_is_client_property(permType);
|
||||
}
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::set_value);
|
||||
update_server_group_list |= ppermission.is_group_property();
|
||||
update_talk_power |= ppermission.is_client_view_property();
|
||||
}
|
||||
|
||||
if(sgroupUpdate)
|
||||
if(update_server_group_list)
|
||||
serverGroup->apply_properties_from_permissions();
|
||||
|
||||
//TODO may update for every server?
|
||||
if(this->server) {
|
||||
auto lock = this->_this.lock();
|
||||
auto server = this->server;
|
||||
threads::Thread([checkTp, sgroupUpdate, serverGroup, lock, server]() {
|
||||
if(sgroupUpdate)
|
||||
threads::Thread([update_talk_power, update_server_group_list, serverGroup, lock, server]() {
|
||||
if(update_server_group_list)
|
||||
server->forEachClient([](shared_ptr<ConnectedClient> cl) {
|
||||
cl->notifyServerGroupList();
|
||||
});
|
||||
server->forEachClient([serverGroup, checkTp](shared_ptr<ConnectedClient> cl) {
|
||||
server->forEachClient([serverGroup, update_talk_power](shared_ptr<ConnectedClient> cl) {
|
||||
if (cl->serverGroupAssigned(serverGroup)) {
|
||||
if(cl->update_cached_permissions()) /* update cached calculated permissions */
|
||||
cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */
|
||||
if (checkTp)
|
||||
if (update_talk_power)
|
||||
cl->updateChannelClientProperties(true, true);
|
||||
cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */
|
||||
}
|
||||
});
|
||||
}).detach();
|
||||
}
|
||||
return command_result{error::ok};
|
||||
|
||||
return pparser.build_command_result();
|
||||
}
|
||||
|
||||
command_result ConnectedClient::handleCommandServerGroupDelPerm(Command &cmd) {
|
||||
@ -890,49 +848,34 @@ command_result ConnectedClient::handleCommandServerGroupDelPerm(Command &cmd) {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1);
|
||||
}
|
||||
|
||||
auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0));
|
||||
bool conOnError = cmd[0].has("continueonerror");
|
||||
bool checkTp = false;
|
||||
auto sgroupUpdate = false;
|
||||
for (int index = 0; index < cmd.bulkCount(); index++) {
|
||||
PARSE_PERMISSION(cmd);
|
||||
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
|
||||
if(!pparser.validate(this->ref(), 0))
|
||||
return pparser.build_command_result();
|
||||
|
||||
if(!ignore_granted_values && !permission::v2::permission_granted(0, this->calculate_permission(permType, 0, true))) {
|
||||
if(conOnError) continue;
|
||||
return command_result{permission::i_permission_modify_power};
|
||||
}
|
||||
|
||||
if (grant) {
|
||||
serverGroup->permissions()->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::delete_value);
|
||||
} else {
|
||||
serverGroup->permissions()->set_permission(
|
||||
permType,
|
||||
permission::v2::empty_permission_values,
|
||||
permission::v2::PermissionUpdateType::delete_value,
|
||||
permission::v2::PermissionUpdateType::do_nothing
|
||||
);
|
||||
sgroupUpdate |= permission_is_group_property(permType);
|
||||
checkTp |= permission_is_client_property(permType);
|
||||
}
|
||||
bool update_talk_power{false}, update_server_group_list{false};
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::delete_value);
|
||||
update_server_group_list |= ppermission.is_group_property();
|
||||
update_talk_power |= ppermission.is_client_view_property();
|
||||
}
|
||||
|
||||
if(sgroupUpdate)
|
||||
if(update_server_group_list)
|
||||
serverGroup->apply_properties_from_permissions();
|
||||
|
||||
if(this->server) {
|
||||
auto lock = this->_this.lock();
|
||||
auto server = this->server;
|
||||
threads::Thread([checkTp, sgroupUpdate, serverGroup, lock, server]() {
|
||||
if(sgroupUpdate)
|
||||
threads::Thread([update_talk_power, update_server_group_list, serverGroup, lock, server]() {
|
||||
if(update_server_group_list)
|
||||
server->forEachClient([](shared_ptr<ConnectedClient> cl) {
|
||||
cl->notifyServerGroupList();
|
||||
});
|
||||
server->forEachClient([serverGroup, checkTp](shared_ptr<ConnectedClient> cl) {
|
||||
server->forEachClient([serverGroup, update_talk_power](shared_ptr<ConnectedClient> cl) {
|
||||
if (cl->serverGroupAssigned(serverGroup)) {
|
||||
|
||||
if(cl->update_cached_permissions()) /* update cached calculated permissions */
|
||||
cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */
|
||||
if (checkTp)
|
||||
if (update_talk_power)
|
||||
cl->updateChannelClientProperties(true, true);
|
||||
cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */
|
||||
}
|
||||
@ -940,7 +883,7 @@ command_result ConnectedClient::handleCommandServerGroupDelPerm(Command &cmd) {
|
||||
}).detach();
|
||||
}
|
||||
|
||||
return command_result{error::ok};
|
||||
return pparser.build_command_result();
|
||||
}
|
||||
|
||||
command_result ConnectedClient::handleCommandServerGroupAutoAddPerm(ts::Command& cmd) {
|
||||
@ -970,67 +913,37 @@ command_result ConnectedClient::handleCommandServerGroupAutoAddPerm(ts::Command&
|
||||
if(groups.empty())
|
||||
return command_result{error::ok};
|
||||
|
||||
auto max_value = this->calculate_permission(permission::i_permission_modify_power, 0, false);
|
||||
if(!max_value.has_value) return command_result{permission::i_permission_modify_power};
|
||||
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
|
||||
if(!pparser.validate(this->ref(), 0))
|
||||
return pparser.build_command_result();
|
||||
|
||||
auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0));
|
||||
bool update_clients{false}, update_server_group_list{false};
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
for(const auto& serverGroup : groups)
|
||||
ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::set_value);
|
||||
|
||||
bool conOnError = cmd[0].has("continueonerror");
|
||||
bool checkTp = false;
|
||||
bool sgroupUpdate = false;
|
||||
|
||||
for (int index = 0; index < cmd.bulkCount(); index++) {
|
||||
PARSE_PERMISSION(cmd);
|
||||
//permvalue='1' permnegated='0' permskip='0'end
|
||||
|
||||
auto val = cmd[index]["permvalue"].as<permission::PermissionValue>();
|
||||
if(permission_require_granted_value(permType) && !permission::v2::permission_granted(val, max_value)) {
|
||||
if(conOnError) continue;
|
||||
return command_result{permission::i_permission_modify_power};
|
||||
}
|
||||
|
||||
if(!ignore_granted_values && !permission::v2::permission_granted(1, this->calculate_permission(permType, 0, true))) {
|
||||
if(conOnError) continue;
|
||||
return command_result{permission::i_permission_modify_power};
|
||||
}
|
||||
|
||||
for(const auto& serverGroup : groups) {
|
||||
if (grant) {
|
||||
serverGroup->permissions()->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::set_value);
|
||||
} else {
|
||||
serverGroup->permissions()->set_permission(
|
||||
permType,
|
||||
{cmd[index]["permvalue"], 0},
|
||||
permission::v2::PermissionUpdateType::set_value,
|
||||
permission::v2::PermissionUpdateType::do_nothing,
|
||||
|
||||
cmd[index]["permskip"].as<bool>() ? 1 : 0,
|
||||
cmd[index]["permnegated"].as<bool>() ? 1 : 0
|
||||
);
|
||||
}
|
||||
}
|
||||
sgroupUpdate |= permission_is_group_property(permType);
|
||||
checkTp |= permission_is_client_property(permType);
|
||||
update_server_group_list |= ppermission.is_group_property();
|
||||
update_clients |= ppermission.is_client_view_property();
|
||||
}
|
||||
|
||||
if(sgroupUpdate)
|
||||
if(update_server_group_list)
|
||||
for(auto& group : groups)
|
||||
group->apply_properties_from_permissions();
|
||||
|
||||
auto lock = this->_this.lock();
|
||||
if(ref_server) {
|
||||
threads::Thread([checkTp, sgroupUpdate, groups, lock, ref_server]() {
|
||||
if(sgroupUpdate)
|
||||
threads::Thread([update_clients, update_server_group_list, groups, lock, ref_server]() {
|
||||
if(update_server_group_list)
|
||||
ref_server->forEachClient([](shared_ptr<ConnectedClient> cl) {
|
||||
cl->notifyServerGroupList();
|
||||
});
|
||||
ref_server->forEachClient([groups, checkTp](shared_ptr<ConnectedClient> cl) {
|
||||
ref_server->forEachClient([groups, update_clients](shared_ptr<ConnectedClient> cl) {
|
||||
for(const auto& serverGroup : groups) {
|
||||
if (cl->serverGroupAssigned(serverGroup)) {
|
||||
if(cl->update_cached_permissions()) {/* update cached calculated permissions */
|
||||
cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */
|
||||
}
|
||||
if (checkTp) {
|
||||
if (update_clients) {
|
||||
cl->updateChannelClientProperties(true, true);
|
||||
}
|
||||
cl->join_state_id++; /* join permission may changed, all channels need to be recalculate if needed */
|
||||
@ -1040,7 +953,8 @@ command_result ConnectedClient::handleCommandServerGroupAutoAddPerm(ts::Command&
|
||||
});
|
||||
}).detach();
|
||||
}
|
||||
return command_result{error::ok};
|
||||
|
||||
return pparser.build_command_result();
|
||||
}
|
||||
|
||||
command_result ConnectedClient::handleCommandServerGroupAutoDelPerm(ts::Command& cmd) {
|
||||
@ -1069,54 +983,37 @@ command_result ConnectedClient::handleCommandServerGroupAutoDelPerm(ts::Command&
|
||||
|
||||
if(groups.empty()) return command_result{error::ok};
|
||||
|
||||
auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0));
|
||||
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
|
||||
if(!pparser.validate(this->ref(), 0))
|
||||
return pparser.build_command_result();
|
||||
|
||||
bool conOnError = cmd[0].has("continueonerror");
|
||||
bool checkTp = false;
|
||||
auto sgroupUpdate = false;
|
||||
for (int index = 0; index < cmd.bulkCount(); index++) {
|
||||
PARSE_PERMISSION(cmd);
|
||||
bool update_clients{false}, update_server_group_list{false};
|
||||
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
|
||||
for(const auto& serverGroup : groups)
|
||||
ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::delete_value);
|
||||
|
||||
if(!ignore_granted_values && !permission::v2::permission_granted(0, this->calculate_permission(permType, 0, true))) {
|
||||
if(conOnError) continue;
|
||||
return command_result{permission::i_permission_modify_power};
|
||||
}
|
||||
|
||||
for(const auto& serverGroup : groups) {
|
||||
if (grant) {
|
||||
serverGroup->permissions()->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::delete_value);
|
||||
} else {
|
||||
serverGroup->permissions()->set_permission(
|
||||
permType,
|
||||
permission::v2::empty_permission_values,
|
||||
permission::v2::PermissionUpdateType::delete_value,
|
||||
permission::v2::PermissionUpdateType::do_nothing
|
||||
);
|
||||
sgroupUpdate |= permission_is_group_property(permType);
|
||||
}
|
||||
}
|
||||
checkTp |= permission_is_client_property(permType);
|
||||
update_server_group_list |= ppermission.is_group_property();
|
||||
update_clients |= ppermission.is_client_view_property();
|
||||
}
|
||||
|
||||
|
||||
if(sgroupUpdate) {
|
||||
if(update_server_group_list) {
|
||||
for(auto& group : groups)
|
||||
group->apply_properties_from_permissions();
|
||||
}
|
||||
|
||||
if(ref_server) {
|
||||
auto lock = this->_this.lock();
|
||||
threads::Thread([checkTp, sgroupUpdate, groups, lock, ref_server]() {
|
||||
if(sgroupUpdate)
|
||||
threads::Thread([update_clients, update_server_group_list, groups, lock, ref_server]() {
|
||||
if(update_server_group_list)
|
||||
ref_server->forEachClient([](shared_ptr<ConnectedClient> cl) {
|
||||
cl->notifyServerGroupList();
|
||||
});
|
||||
ref_server->forEachClient([groups, checkTp](shared_ptr<ConnectedClient> cl) {
|
||||
ref_server->forEachClient([groups, update_clients](shared_ptr<ConnectedClient> cl) {
|
||||
for(const auto& serverGroup : groups) {
|
||||
if (cl->serverGroupAssigned(serverGroup)) {
|
||||
if(cl->update_cached_permissions()) /* update cached calculated permissions */
|
||||
cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */
|
||||
if (checkTp)
|
||||
if (update_clients)
|
||||
cl->updateChannelClientProperties(true, true);
|
||||
cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */
|
||||
break;
|
||||
@ -1126,7 +1023,7 @@ command_result ConnectedClient::handleCommandServerGroupAutoDelPerm(ts::Command&
|
||||
}).detach();
|
||||
}
|
||||
|
||||
return command_result{error::ok};
|
||||
return pparser.build_command_result();
|
||||
}
|
||||
|
||||
command_result ConnectedClient::handleCommandServerGroupsByClientId(Command &cmd) {
|
||||
|
@ -501,18 +501,18 @@ bool QueryClient::handleMessage(const pipes::buffer_view& message) {
|
||||
cmd = make_unique<Command>(Command::parse(pipes::buffer_view{(void*) command.data(), command.length()}, true, !ts::config::server::strict_ut8_mode));
|
||||
} catch(std::invalid_argument& ex) {
|
||||
logTrace(LOG_QUERY, "[{}:{}] Failed to parse command (invalid argument): {}", this->getLoggingPeerIp(), this->getPeerPort(), command);
|
||||
error = command_result{error::parameter_convert};
|
||||
error.reset(command_result{error::parameter_convert});
|
||||
goto handle_error;
|
||||
} catch(std::exception& ex) {
|
||||
logTrace(LOG_QUERY, "[{}:{}] Failed to parse command (exception: {}): {}", this->getLoggingPeerIp(), this->getPeerPort(), ex.what(), command);
|
||||
error = command_result{error::vs_critical, std::string{ex.what()}};
|
||||
error.reset(command_result{error::vs_critical, std::string{ex.what()}});
|
||||
goto handle_error;
|
||||
}
|
||||
|
||||
try {
|
||||
this->handleCommandFull(*cmd);
|
||||
} catch(std::exception& ex) {
|
||||
error = command_result{error::vs_critical, std::string{ex.what()}};
|
||||
error.reset(command_result{error::vs_critical, std::string{ex.what()}});
|
||||
goto handle_error;
|
||||
}
|
||||
return true;
|
||||
|
@ -21,18 +21,18 @@ void VoiceClient::handlePacketCommand(const pipes::buffer_view& command_string)
|
||||
try {
|
||||
command = make_unique<Command>(Command::parse(command_string, true, !ts::config::server::strict_ut8_mode));
|
||||
} catch(std::invalid_argument& ex) {
|
||||
result = command_result{error::parameter_convert, std::string{ex.what()}};
|
||||
result.reset(command_result{error::parameter_convert, std::string{ex.what()}});
|
||||
goto handle_error;
|
||||
} catch(std::exception& ex) {
|
||||
result = command_result{error::parameter_convert, std::string{ex.what()}};
|
||||
result.reset(command_result{error::parameter_convert, std::string{ex.what()}});
|
||||
goto handle_error;
|
||||
}
|
||||
|
||||
if(command->command() == "clientek") {
|
||||
result = this->handleCommandClientEk(*command);
|
||||
result.reset(this->handleCommandClientEk(*command));
|
||||
if(result.has_error()) goto handle_error;
|
||||
} else if(command->command() == "clientinitiv") {
|
||||
result = this->handleCommandClientInitIv(*command);
|
||||
result.reset(this->handleCommandClientInitIv(*command));
|
||||
if(result.has_error()) goto handle_error;
|
||||
} else this->handleCommandFull(*command, true);
|
||||
|
||||
|
@ -539,7 +539,7 @@ void LicenseService::execute_tick() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
if(this->request_state_ != request_state::empty) {
|
||||
if(this->timings.last_request + std::chrono::minutes{5} < now) {
|
||||
this->handle_check_fail(strobf("Scheduling next check at {}").string());
|
||||
this->handle_check_fail(strobf("check timeout").string());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ std::vector<std::shared_ptr<LetterHeader>> LetterManager::avariableLetters(Clien
|
||||
if(strcmp(columns[index], "sender") == 0)
|
||||
letter->sender = values[index];
|
||||
else if(strcmp(columns[index], "created") == 0)
|
||||
letter->created = system_clock::now() + milliseconds(stoull(values[index]));
|
||||
letter->created = std::chrono::system_clock::time_point{} + seconds{stoull(values[index])};
|
||||
else if(strcmp(columns[index], "letterId") == 0)
|
||||
letter->id = static_cast<LetterId>(stoull(values[index]));
|
||||
else if(strcmp(columns[index], "subject") == 0)
|
||||
|
2
shared
2
shared
@ -1 +1 @@
|
||||
Subproject commit ee4c7540f011d17cdac9c170e02c60296ae2487a
|
||||
Subproject commit b60608ff94b06145bc808426392871ebd95fe9d3
|
Loading…
Reference in New Issue
Block a user