A lot of file transfer updates

This commit is contained in:
WolverinDEV
2020-05-13 11:32:08 +02:00
parent 1a2dd4a008
commit 90b1646876
45 changed files with 1989 additions and 2806 deletions
+2
View File
@@ -1249,11 +1249,13 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
BIND_BOOL(config::server::delete_old_bans, true);
ADD_DESCRIPTION("Enable/disable the deletion of old bans within the database");
}
#if 0
{
CREATE_BINDING("delete_missing_icon_permissions", 0);
BIND_BOOL(config::server::delete_missing_icon_permissions, true);
ADD_DESCRIPTION("Enable/disable the deletion of invalid icon id permissions");
}
#endif
{
CREATE_BINDING("allow_weblist", 0);
BIND_BOOL(config::server::enable_teamspeak_weblist, true);
+50
View File
@@ -0,0 +1,50 @@
//
// Created by WolverinDEV on 12/05/2020.
//
#include <files/FileServer.h>
#include <log/LogUtils.h>
#include "FileServerHandler.h"
using namespace ts::server::file;
bool FileServerHandler::initialize(std::string &error) {
/*
* FIXME: Ports etc!
*
auto bindings_string = this->properties()[property::SERVERINSTANCE_FILETRANSFER_HOST].as<string>();
auto port = this->properties()[property::SERVERINSTANCE_FILETRANSFER_PORT].as<uint16_t>();
*/
/* TODO: Callback handler */
if(!file::initialize(error))
return false;
auto server = file::server();
assert(server);
auto& transfer = server->file_transfer();
transfer.callback_transfer_registered = std::bind(&FileServerHandler::callback_transfer_registered, this, std::placeholders::_1);
transfer.callback_transfer_started = std::bind(&FileServerHandler::callback_transfer_started, this, std::placeholders::_1);
transfer.callback_transfer_finished = std::bind(&FileServerHandler::callback_transfer_finished, this, std::placeholders::_1);
transfer.callback_transfer_aborted = std::bind(&FileServerHandler::callback_transfer_aborted, this, std::placeholders::_1, std::placeholders::_2);
transfer.callback_transfer_statistics = std::bind(&FileServerHandler::callback_transfer_statistics, this, std::placeholders::_1, std::placeholders::_2);
}
void FileServerHandler::callback_transfer_registered(const std::shared_ptr<transfer::Transfer> &transfer) {
auto server = this->instance_->getVoiceServerManager()->findServerById(transfer->server->server_id());
if(!server) return; /* well that's bad */
if(transfer->direction == transfer::Transfer::DIRECTION_UPLOAD) {
server->properties()[property::VIRTUALSERVER_TOTAL_BYTES_UPLOADED] += transfer->expected_file_size - transfer->file_offset;
server->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED] += transfer->expected_file_size - transfer->file_offset;
} else {
server->properties()[property::VIRTUALSERVER_TOTAL_BYTES_DOWNLOADED] += transfer->expected_file_size - transfer->file_offset;
server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED] += transfer->expected_file_size - transfer->file_offset;
}
}
void FileServerHandler::callback_transfer_aborted(const std::shared_ptr<transfer::Transfer> &transfer,
const ts::server::file::transfer::TransferError &error) {
/* TODO: Remove not used quota from server & client */
}
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include <string>
#include "./InstanceHandler.h"
namespace ts::server::file {
class FileServerHandler {
public:
explicit FileServerHandler(InstanceHandler*);
bool initialize(std::string& /* error */);
void finalize();
private:
InstanceHandler* instance_;
#if 0
std::function<void(const std::shared_ptr<Transfer>&)> callback_transfer_registered{}; /* transfer has been registered */
std::function<void(const std::shared_ptr<Transfer>&)> callback_transfer_started{}; /* transfer has been started */
std::function<void(const std::shared_ptr<Transfer>&)> callback_transfer_finished{}; /* transfer has been finished */
std::function<void(const std::shared_ptr<Transfer>&, const TransferError&)> callback_transfer_aborted{}; /* an error happened while transferring the data */
std::function<void(const std::shared_ptr<Transfer>&, const TransferStatistics&)> callback_transfer_statistics{};
#endif
void callback_transfer_registered(const std::shared_ptr<transfer::Transfer>&);
void callback_transfer_started(const std::shared_ptr<transfer::Transfer>&);
void callback_transfer_finished(const std::shared_ptr<transfer::Transfer>&);
void callback_transfer_aborted(const std::shared_ptr<transfer::Transfer>&, const transfer::TransferError&);
void callback_transfer_statistics(const std::shared_ptr<transfer::Transfer>&, const transfer::TransferStatistics&);
};
}
+2 -1
View File
@@ -6,7 +6,6 @@
#include "VirtualServer.h"
#include "src/client/ConnectedClient.h"
#include "InstanceHandler.h"
#include "src/server/file/LocalFileServer.h"
using namespace std;
using namespace std::chrono;
@@ -216,11 +215,13 @@ int GroupManager::insertGroupFromDb(int count, char **values, char **column) {
debugMessage(this->getServerId(), "Push back group -> " + to_string(group->groupId()) + " - " + group->name());
this->groups.push_back(group);
#if 0
auto iconId = (IconId) group->icon_id();
if(iconId != 0 && serverInstance && serverInstance->getFileServer() && !serverInstance->getFileServer()->iconExists(this->server.lock(), iconId)) {
logMessage(this->getServerId(), "[FILE] Missing group icon (" + to_string(iconId) + ").");
if(config::server::delete_missing_icon_permissions) group->permissions()->set_permission(permission::i_icon_id, {0, 0}, permission::v2::delete_value, permission::v2::do_nothing);
}
#endif
return 0;
}
+8 -35
View File
@@ -3,15 +3,13 @@
#define XFREE undefined_free
#define XREALLOC undefined_realloc
#include <netdb.h>
#include "src/weblist/WebListManager.h"
#include <log/LogUtils.h>
#include "InstanceHandler.h"
#include "src/client/InternalClient.h"
#include "src/server/QueryServer.h"
#include "src/server/file/LocalFileServer.h"
#include "SignalHandler.h"
#include "src/manager/PermissionNameMapper.h"
#include "./FileServerHandler.h"
#include <ThreadPool/Timer.h>
#include "ShutdownHelper.h"
#include <sys/utsname.h>
@@ -21,13 +19,14 @@
#include <misc/hex.h>
#include <misc/rnd.h>
#include <misc/strobf.h>
#include <jemalloc/jemalloc.h>
#include <protocol/buffers.h>
#ifndef _POSIX_SOURCE
#define _POSIX_SOURCE
#endif
#include <unistd.h>
#include <files/FileServer.h>
#undef _POSIX_SOURCE
using namespace std;
@@ -281,31 +280,11 @@ bool InstanceHandler::startInstance() {
}
this->loadWebCertificate();
fileServer = new ts::server::LocalFileServer();
{
auto bindings_string = this->properties()[property::SERVERINSTANCE_FILETRANSFER_HOST].as<string>();
auto port = this->properties()[property::SERVERINSTANCE_FILETRANSFER_PORT].as<uint16_t>();
auto ft_bindings = net::resolve_bindings(bindings_string, port);
deque<shared_ptr<LocalFileServer::Binding>> bindings;
for(auto& binding : ft_bindings) {
if(!get<2>(binding).empty()) {
logError(LOG_FT, "Failed to resolve binding for {}: {}", get<0>(binding), get<2>(binding));
continue;
}
auto entry = make_shared<LocalFileServer::Binding>();
memcpy(&entry->address, &get<1>(binding), sizeof(sockaddr_storage));
entry->file_descriptor = -1;
entry->event_accept = nullptr;
bindings.push_back(entry);
}
logMessage(LOG_FT, "Starting server on {}:{}", bindings_string, port);
if(!fileServer->start(bindings, errorMessage)) {
logCritical(LOG_FT, "Failed to start server: {}", errorMessage);
return false;
}
this->file_server_handler_ = new file::FileServerHandler{this};
if(!this->file_server_handler_->initialize(errorMessage)) {
logCritical(LOG_FT, "Failed to initialize server: {}", errorMessage);
return false;
}
if(config::query::sslMode > 0) {
@@ -443,9 +422,7 @@ void InstanceHandler::stopInstance() {
debugMessage(LOG_QUERY, "Query server stopped");
debugMessage(LOG_FT, "Stopping file server");
if (this->fileServer) this->fileServer->stop();
delete this->fileServer;
this->fileServer = nullptr;
file::finalize();
debugMessage(LOG_FT, "File server stopped");
this->save_channel_permissions();
@@ -519,10 +496,6 @@ void InstanceHandler::tickInstance() {
}
}
}
{
ALARM_TIMER(t, "InstanceHandler::tickInstance -> fileserver tick", milliseconds(5));
if(this->fileServer) this->fileServer->instanceTick();
}
{
ALARM_TIMER(t, "InstanceHandler::tickInstance -> sql_test tick", milliseconds(5));
if(this->sql && this->active) {
+7 -2
View File
@@ -23,6 +23,10 @@ namespace ts {
class LicenseService;
}
namespace file {
class FileServerHandler;
}
class InstanceHandler {
public:
explicit InstanceHandler(SqlDataManager*);
@@ -42,12 +46,12 @@ namespace ts {
std::shared_mutex& getChannelTreeLock() { return this->default_tree_lock; }
VirtualServerManager* getVoiceServerManager(){ return this->voiceServerManager; }
LocalFileServer* getFileServer(){ return fileServer; }
QueryServer* getQueryServer(){ return queryServer; }
DatabaseHelper* databaseHelper(){ return this->dbHelper; }
BanManager* banManager(){ return this->banMgr; }
ssl::SSLManager* sslManager(){ return this->sslMgr; }
sql::SqlManager* getSql(){ return sql->sql(); }
file::FileServerHandler* getFileServerHandler() { return this->file_server_handler_; }
std::chrono::time_point<std::chrono::system_clock> getStartTimestamp(){ return startTimestamp; }
@@ -110,12 +114,13 @@ namespace ts {
std::chrono::system_clock::time_point memcleanTimestamp;
SqlDataManager* sql;
LocalFileServer* fileServer = nullptr;
QueryServer* queryServer = nullptr;
VirtualServerManager* voiceServerManager = nullptr;
DatabaseHelper* dbHelper = nullptr;
BanManager* banMgr = nullptr;
ssl::SSLManager* sslMgr = nullptr;
file::FileServerHandler* file_server_handler_{nullptr};
ts::Properties* _properties = nullptr;
-1
View File
@@ -6,7 +6,6 @@
#include "InstanceHandler.h"
#include "src/client/InternalClient.h"
#include "src/server/QueryServer.h"
#include "src/server/file/LocalFileServer.h"
using namespace std;
using namespace std::chrono;
+40 -9
View File
@@ -10,6 +10,8 @@
#include <misc/digest.h>
#include <misc/base64.h>
#include <files/FileServer.h>
#include "weblist/WebListManager.h"
#include "./client/web/WebClient.h"
#include "./client/voice/VoiceClient.h"
@@ -18,7 +20,6 @@
#include "./client/query/QueryClient.h"
#include "music/MusicBotManager.h"
#include "server/VoiceServer.h"
#include "src/server/file/LocalFileServer.h"
#include "server/QueryServer.h"
#include "InstanceHandler.h"
#include "Configuration.h"
@@ -52,10 +53,17 @@ bool VirtualServer::initialize(bool test_properties) {
if(prop.type() == property::VIRTUALSERVER_DISABLE_IP_SAVING) {
this->_disable_ip_saving = prop.as<bool>();
return;
}
if(prop.type() == property::VIRTUALSERVER_CODEC_ENCRYPTION_MODE) {
} else if(prop.type() == property::VIRTUALSERVER_CODEC_ENCRYPTION_MODE) {
this->_voice_encryption_mode = prop.as<int>();
return;
} else if(prop.type() == property::VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH) {
auto file_vs = file::server()->find_virtual_server(this->getServerId());
if(!file_vs) return;
file_vs->max_networking_upload_bandwidth(prop.as_save<int64_t>([]{ return -1; }));
} else if(prop.type() == property::VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH) {
auto file_vs = file::server()->find_virtual_server(this->getServerId());
if(!file_vs) return;
file_vs->max_networking_download_bandwidth(prop.as_save<int64_t>([]{ return -1; }));
}
std::string sql{};
if(prop.type() == property::VIRTUALSERVER_HOST)
@@ -196,8 +204,32 @@ bool VirtualServer::initialize(bool test_properties) {
this->serverAdmin->server = nullptr;
this->registerInternalClient(this->serverAdmin); /* lets assign server id 0 */
if(serverInstance->getFileServer())
serverInstance->getFileServer()->setupServer(self.lock());
{
using ErrorType = file::filesystem::ServerCommandErrorType;
auto file_vs = file::server()->register_server(this->getServerId());
auto initialize_result = file::server()->file_system().initialize_server(file_vs);
if(!initialize_result->wait_for(std::chrono::seconds{5})) {
logError(this->getServerId(), "Failed to wait for file directory initialisation.");
} else if(!initialize_result->succeeded()) {
switch (initialize_result->error().error_type) {
case ErrorType::FAILED_TO_CREATE_DIRECTORIES:
logError(this->getServerId(), "Failed to create server file directories ({}).", initialize_result->error().error_message);
break;
case ErrorType::UNKNOWN:
case ErrorType::FAILED_TO_DELETE_DIRECTORIES:
logError(this->getServerId(), "Failed to initialize server file directory due to an unknown error: {}/{}",
(int) initialize_result->error().error_type, initialize_result->error().error_message);
break;
}
}
this->properties()[property::VIRTUALSERVER_FILEBASE] = file::server()->file_base_path();
file_vs->max_networking_download_bandwidth(this->properties()[property::VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH].as_save<int64_t>([]{ return -1; }));
file_vs->max_networking_upload_bandwidth(this->properties()[property::VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH].as_save<int64_t>([]{ return -1; }));
}
this->channelTree->printChannelTree([&](std::string msg){ debugMessage(this->serverId, msg); });
this->musicManager = make_shared<music::MusicBotManager>(self.lock());
@@ -205,11 +237,13 @@ bool VirtualServer::initialize(bool test_properties) {
this->musicManager->load_playlists();
this->musicManager->load_bots();
#if 0
if(this->properties()[property::VIRTUALSERVER_ICON_ID] != (IconId) 0)
if(!serverInstance->getFileServer()->iconExists(self.lock(), this->properties()[property::VIRTUALSERVER_ICON_ID])) {
debugMessage(this->getServerId(), "Removing invalid icon id of server");
this->properties()[property::VIRTUALSERVER_ICON_ID] = 0;
}
}
#endif
for(const auto& type : vector<property::VirtualServerProperties>{
property::VIRTUALSERVER_DOWNLOAD_QUOTA,
@@ -227,9 +261,6 @@ bool VirtualServer::initialize(bool test_properties) {
}
}
if(this->properties()[property::VIRTUALSERVER_FILEBASE].value().empty())
this->properties()[property::VIRTUALSERVER_FILEBASE] = serverInstance->getFileServer()->server_file_base(self.lock());
/* lets cleanup the conversations for not existent channels */
this->_conversation_manager->synchronize_channels();
return true;
+22 -2
View File
@@ -4,9 +4,9 @@
#include "src/server/VoiceServer.h"
#include "src/client/query/QueryClient.h"
#include "InstanceHandler.h"
#include "src/server/file/LocalFileServer.h"
#include "src/client/ConnectedClient.h"
#include <ThreadPool/ThreadHelper.h>
#include <files/FileServer.h>
using namespace std;
using namespace std::chrono;
@@ -419,7 +419,27 @@ bool VirtualServerManager::deleteServer(shared_ptr<VirtualServer> server) {
this->handle->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED] += server->properties()[property::VIRTUALSERVER_SPOKEN_TIME].as<uint64_t>();
this->delete_server_in_db(server->serverId);
this->handle->getFileServer()->deleteServer(server);
if(auto file_vs = file::server()->find_virtual_server(server->getServerId()); file_vs) {
using ErrorType = file::filesystem::ServerCommandErrorType;
auto delete_result = file::server()->file_system().delete_server(file_vs);
if(!delete_result->wait_for(std::chrono::seconds{5})) {
logError(LOG_INSTANCE, "Failed to wait for file directory initialisation.");
} else if(!delete_result->succeeded()) {
switch (delete_result->error().error_type) {
case ErrorType::FAILED_TO_DELETE_DIRECTORIES:
logError(LOG_INSTANCE, "Failed to delete server {} file directories ({}).", server->getServerId(), delete_result->error().error_message);
break;
case ErrorType::UNKNOWN:
case ErrorType::FAILED_TO_CREATE_DIRECTORIES:
logError(LOG_INSTANCE, "Failed to delete server {} file directory due to an unknown error: {}/{}",
server->getServerId(), (int) delete_result->error().error_type, delete_result->error().error_message);
break;
}
}
}
return true;
}
+2 -1
View File
@@ -5,7 +5,6 @@
#include "misc/rnd.h"
#include "src/VirtualServer.h"
#include "src/client/ConnectedClient.h"
#include "src/server/file/LocalFileServer.h"
#include "src/InstanceHandler.h"
#include "../manager/ConversationManager.h"
@@ -481,6 +480,7 @@ bool ServerChannelTree::validateChannelNames() {
bool ServerChannelTree::validateChannelIcons() {
for(const auto &channel : this->channels()) {
auto iconId = (IconId) channel->properties()[property::CHANNEL_ICON_ID];
#if 0
if(iconId != 0 && !serverInstance->getFileServer()->iconExists(this->server.lock(), iconId)) {
logMessage(this->getServerId(), "[FILE] Missing channel icon (" + to_string(iconId) + ").");
if(config::server::delete_missing_icon_permissions) {
@@ -488,6 +488,7 @@ bool ServerChannelTree::validateChannelIcons() {
channel->permissions()->set_permission(permission::i_icon_id, {0, 0}, permission::v2::PermissionUpdateType::set_value, permission::v2::PermissionUpdateType::do_nothing);
}
}
#endif
}
return true;
}
+3 -2
View File
@@ -11,7 +11,6 @@
#include "src/VirtualServer.h"
#include "voice/VoiceClient.h"
#include "../server/VoiceServer.h"
#include "src/server/file/LocalFileServer.h"
#include "../InstanceHandler.h"
#include "ConnectedClient.h"
@@ -143,7 +142,8 @@ void ConnectedClient::updateChannelClientProperties(bool lock_channel_tree, bool
iconId = value.value;
}
logTrace(this->getServerId(), "[CLIENT] Updating client icon from " + to_string(this->properties()[property::CLIENT_ICON_ID].as<IconId>()) + " to " + to_string(iconId));
if(this->properties()[property::CLIENT_ICON_ID].as<IconId>() != iconId){
if(this->properties()[property::CLIENT_ICON_ID].as<IconId>() != iconId) {
#if 0
if(server_ref && iconId != 0) {
auto dir = serverInstance->getFileServer()->iconDirectory(server_ref);
if(!serverInstance->getFileServer()->iconExists(server_ref, iconId)) {
@@ -151,6 +151,7 @@ void ConnectedClient::updateChannelClientProperties(bool lock_channel_tree, bool
iconId = 0;
}
}
#endif
if(this->properties()[property::CLIENT_ICON_ID].as<IconId>() != iconId) {
this->properties()[property::CLIENT_ICON_ID] = (IconId) iconId;
notifyList.emplace_back(property::CLIENT_ICON_ID);
+13 -6
View File
@@ -13,7 +13,11 @@
#define CLIENT_STR_LOG_PREFIX CLIENT_STR_LOG_PREFIX_(this)
#define CMD_REQ_SERVER \
if(!this->server) return command_result{error::server_invalid_id};
do { \
if(!this->server) { \
return command_result{error::server_invalid_id}; \
} \
} while(0)
/* TODO: Play lock the server here with read? So the client dosn't get kicked within that moment */
#define CMD_REF_SERVER(variable_name) \
@@ -33,8 +37,10 @@ if(!cmd[0].has(parm)) return command_result{error::parameter_not_found};
//the message here is show to the manager!
#define CMD_CHK_AND_INC_FLOOD_POINTS(num) \
this->increaseFloodPoints(num); \
if(this->shouldFloodBlock()) return command_result{error::ban_flooding};
do {\
this->increaseFloodPoints(num); \
if(this->shouldFloodBlock()) return command_result{error::ban_flooding}; \
} while(0)
#define CMD_CHK_PARM_COUNT(count) \
if(cmd.bulkCount() != count) return command_result{error::parameter_invalid_count};
@@ -453,10 +459,11 @@ namespace ts {
command_result handleCommandFTDeleteFile(Command&);
command_result handleCommandFTInitUpload(Command&);
command_result handleCommandFTInitDownload(Command&);
command_result handleCommandFTGetFileInfo(Command&);
//CMD_TODO handleCommandFTGetFileInfo -> 5 points
command_result handleCommandFTGetFileInfo(Command&);
command_result handleCommandFTRenameFile(Command&);
command_result handleCommandFTList(Command&);
command_result handleCommandFTStop(Command&);
//CMD_TODO handleCommandFTStop -> 5 points
//CMD_TODO handleCommandFTRenameFile -> 5 points
//CMD_TODO handleCommandFTList -> 5 points
command_result handleCommandBanList(Command&);
@@ -3,7 +3,6 @@
#include <algorithm>
#include "ConnectedClient.h"
#include "voice/VoiceClient.h"
#include "src/server/file/LocalFileServer.h"
#include "../server/VoiceServer.h"
#include "../InstanceHandler.h"
#include "../server/QueryServer.h"
+2 -1
View File
@@ -4,7 +4,6 @@
#include <misc/hex.h>
#include "DataClient.h"
#include "ConnectedClient.h"
#include "src/server/file/LocalFileServer.h"
#include "src/InstanceHandler.h"
#include "misc/base64.h"
@@ -115,6 +114,7 @@ bool DataClient::loadDataForCurrentServer() { //TODO for query
this->clientPermissions = serverInstance->databaseHelper()->loadClientPermissionManager(ref_server, this->getClientDatabaseId());
//Setup / fix stuff
#if 0
if(!this->properties()[property::CLIENT_FLAG_AVATAR].as<string>().empty()){
if(
!ref_server ||
@@ -123,6 +123,7 @@ bool DataClient::loadDataForCurrentServer() { //TODO for query
this->properties()[property::CLIENT_FLAG_AVATAR] = "";
}
}
#endif
if(ref_server)
this->properties()[property::CLIENT_UNREAD_MESSAGES] = ref_server->letters->unread_letter_count(this->getUid());
@@ -5,13 +5,11 @@
#include "../../build.h"
#include "../ConnectedClient.h"
#include "../InternalClient.h"
#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"
#include "../music/MusicClient.h"
#include "../query/QueryClient.h"
#include "../../weblist/WebListManager.h"
@@ -182,6 +180,7 @@ command_result ConnectedClient::handleCommandChannelGroupCopy(Command &cmd) {
return permission::b_serverinstance_modify_querygroup;
break;
case GroupType::GROUP_TYPE_NORMAL:
default:
break;
}
@@ -6,12 +6,10 @@
#include "../../build.h"
#include "../ConnectedClient.h"
#include "../InternalClient.h"
#include "src/server/file/LocalFileServer.h"
#include "../voice/VoiceClient.h"
#include "PermissionManager.h"
#include "../../InstanceHandler.h"
#include "../../server/QueryServer.h"
#include "../file/FileClient.h"
#include "../music/MusicClient.h"
#include "../query/QueryClient.h"
#include "../../weblist/WebListManager.h"
@@ -349,31 +347,6 @@ command_result ConnectedClient::handleCommandClientChatClosed(Command &cmd) {
return command_result{error::ok};
}
//ftgetfilelist cid=1 cpw path=\/ return_code=1:x
//Answer:
//1 .. n
// notifyfilelist cid=1 path=\/ return_code=1:x name=testFile size=35256 datetime=1509459767 type=1|name=testDir size=0 datetime=1509459741 type=0|name=testDir_2 size=0 datetime=1509459763 type=0
//notifyfilelistfinished cid=1 path=\/
inline void cmd_filelist_append_files(ServerId sid, Command &command, vector<std::shared_ptr<file::FileEntry>> files) {
int index = 0;
logTrace(sid, "Sending file list for path {}", command["path"].string());
for (const auto& fileEntry : files) {
logTrace(sid, " - {} ({})", fileEntry->name, fileEntry->type == file::FileType::FILE ? "file" : "directory");
command[index]["name"] = fileEntry->name;
command[index]["datetime"] = std::chrono::duration_cast<std::chrono::seconds>(fileEntry->lastChanged.time_since_epoch()).count();
command[index]["type"] = fileEntry->type;
if (fileEntry->type == file::FileType::FILE)
command[index]["size"] = static_pointer_cast<file::File>(fileEntry)->fileSize;
else
command[index]["size"] = 0;
index++;
}
}
#define CMD_REQ_FSERVER if(!serverInstance->getFileServer()) return command_result{error::vs_critical, "file server not started yet!"}
//start=0 duration=10
//pattern=%asd%
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -79,7 +79,8 @@ if (permission::resolvePermissionData(permType)->type == permission::PermissionT
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
inline bool permission_require_granted_value(ts::permission::PermissionType type) {
using namespace ts;
/*
@@ -192,6 +193,7 @@ inline bool permission_is_client_property(ts::permission::PermissionType type) {
return false;
}
}
#pragma GCC diagnostic pop
inline ssize_t count_characters(const std::string& in) {
+3 -2
View File
@@ -13,13 +13,11 @@
#include "../../build.h"
#include "../ConnectedClient.h"
#include "../InternalClient.h"
#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"
#include "../music/MusicClient.h"
#include "../query/QueryClient.h"
#include "../../weblist/WebListManager.h"
@@ -136,6 +134,9 @@ command_result ConnectedClient::handleCommand(Command &cmd) {
else if (command == "ftinitupload") return this->handleCommandFTInitUpload(cmd);
else if (command == "ftinitdownload") return this->handleCommandFTInitDownload(cmd);
else if (command == "ftgetfileinfo") return this->handleCommandFTGetFileInfo(cmd);
else if (command == "ftrenamefile") return this->handleCommandFTRenameFile(cmd);
else if (command == "ftlist") return this->handleCommandFTList(cmd);
else if (command == "ftstop") return this->handleCommandFTStop(cmd);
//Banlist
else if (command == "banlist") return this->handleCommandBanList(cmd);
else if (command == "banadd") return this->handleCommandBanAdd(cmd);
@@ -13,13 +13,11 @@
#include "../../build.h"
#include "../ConnectedClient.h"
#include "../InternalClient.h"
#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"
#include "../music/MusicClient.h"
#include "../query/QueryClient.h"
#include "../../weblist/WebListManager.h"
@@ -10,12 +10,10 @@
#include "../../build.h"
#include "../ConnectedClient.h"
#include "../InternalClient.h"
#include "src/server/file/LocalFileServer.h"
#include "../../server/VoiceServer.h"
#include "../voice/VoiceClient.h"
#include "../../InstanceHandler.h"
#include "../../server/QueryServer.h"
#include "../file/FileClient.h"
#include "../music/MusicClient.h"
#include "../query/QueryClient.h"
#include "../../weblist/WebListManager.h"
-719
View File
@@ -1,719 +0,0 @@
#include <algorithm>
#include <src/server/file/LocalFileServer.h>
#include <log/LogUtils.h>
#include "FileClient.h"
#include <src/InstanceHandler.h>
#include <experimental/filesystem>
#include <misc/memtracker.h>
#include <misc/base64.h>
#include "src/client/ConnectedClient.h"
#include <netinet/tcp.h>
using namespace std;
using namespace std::chrono;
using namespace ts::server;
namespace fs = std::experimental::filesystem;
#define BUFFER_SIZE (size_t) 2048
FileClient::FileClient(LocalFileServer* handle, int socketFd) : handle(handle), clientFd(socketFd) {
memtrack::allocated<FileClient>(this);
this->last_io_action = system_clock::now();
int enabled = 1;
if(setsockopt(socketFd, IPPROTO_TCP, TCP_NODELAY, &enabled, sizeof enabled) < 0)
logError(LOG_FT, "{} Cant enable TCP no delay for socket {}. Error: {}/{}", this->client_prefix(), socketFd, errno, strerror(errno));
this->readEvent = event_new(this->handle->ioLoop, socketFd, EV_READ|EV_PERSIST, [](int a, short b, void* c){ ((FileClient*) c)->handleMessageRead(a, b, c); }, this);
this->writeEvent = event_new(this->handle->ioLoop, socketFd, EV_TIMEOUT | EV_WRITE, [](int a, short b, void* c){ ((FileClient*) c)->handleMessageWrite(a, b, c); }, this);
this->ssl_handler.direct_process(pipes::PROCESS_DIRECTION_OUT, true);
this->ssl_handler.direct_process(pipes::PROCESS_DIRECTION_IN, true);
this->ssl_handler.callback_write(bind(&FileClient::sendRawMessage, this, placeholders::_1));
this->ssl_handler.callback_data(bind(&FileClient::handle_ssl_message, this, placeholders::_1));
/*
this->ssl_handler.callback_data([&](const pipes::buffer_view& msg) {
if(this->ftType == FTType::TeaWeb_HTTPS) {
this->handle_http_message(msg);
} else if(this->ftType == FTType::TeaWeb_SSL_WS) {
this->ws_handler.process_incoming_data(msg);
} else {
logError(LOG_FT, "{} Decoded SSL packet, but transfer type isn't SSL!", this->client_prefix());
}
});
*/
//FIXME init ssl error handler?
this->ws_handler.direct_process(pipes::PROCESS_DIRECTION_OUT, true);
this->ws_handler.direct_process(pipes::PROCESS_DIRECTION_IN, true);
/*
this->ws_handler.callback_data([&](const pipes::WSMessage& message) {
if(this->state_transfere == T_INITIALIZE) {
this->applyKey(message.data.string()); //Got the key :)
} else if(this->state_transfere == T_TRANSFER) {
if(this->pendingKey->upload)
this->uploadWriteBytes(message.data.string());
else {
logError(LOG_FT, "{} Invalid write (Just download)", this->client_prefix());
this->disconnect();
}
}
});
*/
this->ws_handler.callback_data(bind(&FileClient::handle_ws_message,this, placeholders::_1));
this->ws_handler.callback_write([&](const pipes::buffer_view& data) {
this->ssl_handler.send(data);
});
this->ws_handler.on_connect = [&]() {};
this->ws_handler.on_disconnect = [&](const std::string&) {};
}
FileClient::~FileClient() {
if(this->thread_flush.joinable()){
this->state_connection = C_DISCONNECTED;
if(this->thread_flush.get_id() != this_thread::get_id())
this->thread_flush.join();
else
this->thread_flush.detach();
}
memtrack::freed<FileClient>(this);
}
size_t FileClient::used_bandwidth() {
auto now = system_clock::now();
size_t tranfarred_bytes = 0;
auto timeout = now - seconds(1);
{
lock_guard<recursive_mutex> lock(this->bandwidth_lock);
for(auto it = this->bandwidth.end(); it != this->bandwidth.begin();) {
--it;
if((*it)->timestamp < timeout) {
this->bandwidth.erase(this->bandwidth.begin(), it);
break;
}
tranfarred_bytes += (*it)->length;
}
}
return tranfarred_bytes;
}
std::string FileClient::client_prefix() {
bool hide_ip = config::server::disable_ip_saving;
if(!hide_ip) {
auto client = this->client;
if(client) {
auto server = client->getServer();
if(server) {
hide_ip = server->disable_ip_saving();
}
}
}
std::string address = "";
if(hide_ip)
address = "X.X.X.X:" + to_string(net::port(this->remote_address));
else
address = net::to_string(this->remote_address);
if(this->client) return "[" + to_string(this->client->getServerId()) + "|" + address + "| " + this->client->getDisplayName() + "]";
return "[0|" + address + "|unconnected]";
}
size_t FileClient::transferred_bytes() {
return this->bytesHandled;
}
size_t FileClient::remaining_bytes() {
threads::MutexLock lock(this->tickLock);
if(!this->pendingKey) return 0;
return this->pendingKey->size - this->bytesHandled;
}
void FileClient::disconnect(std::chrono::milliseconds timeout) {
auto own_lock = _this.lock();
if(!own_lock) return;
bool apply_flush = timeout.count() > 0;
debugMessage(LOG_FT, "{} Disconnecting client. Connection state: {} Flush IO: {}", this->client_prefix(), (int) this->state_connection, apply_flush);
unique_lock<threads::Mutex> tick_lock(this->tickLock);
if(this->state_connection == C_DISCONNECTED) return; /* we're already disconnected */
if(apply_flush && this->state_connection == C_DISCONNECTING) return; /* we're already flushing the IO */
if(this->ftType == FTType::TeaWeb_SSL_WS) {
this->ws_handler.disconnect(1000, "disconnected");
};
this->state_connection = apply_flush ? C_DISCONNECTING : C_DISCONNECTED;
if(this->readEvent)
event_del_noblock(this->readEvent);
if(apply_flush){
lock_guard flush_lock(this->thread_flush_lock);
assert(!this->thread_flush.joinable());
this->thread_flush = std::thread([own_lock, timeout]{
auto beg = system_clock::now();
while(own_lock->state_connection == C_DISCONNECTING && beg + timeout > system_clock::now()){
{
lock_guard buffer_lock(own_lock->bufferLock);
if(own_lock->read_queue.empty() && own_lock->write_queue.empty()) break;
}
usleep(10 * 1000);
}
unique_lock<threads::Mutex> l(own_lock->tickLock);
if(own_lock->state_connection != C_DISCONNECTING) return;
own_lock->disconnectFinal(l, true);
});
{
auto native_handle = this->thread_flush.native_handle();
pthread_setname_np(native_handle, "FileClient flush");
}
} else {
unique_lock flush_lock(this->thread_flush_lock);
if(this->thread_flush.joinable()) {
flush_lock.unlock();
this->thread_flush.join();
flush_lock.lock();
}
disconnectFinal(tick_lock, false);
}
}
void FileClient::disconnectFinal(unique_lock<threads::Mutex>& l, bool lock_flush_thread) {
auto ownLock = _this.lock();
unique_lock l1(this->bufferLock);
this->state_connection = C_DISCONNECTED;
{
unique_lock flush_lock(this->thread_flush_lock, try_to_lock);
if(flush_lock.owns_lock() || !lock_flush_thread) {
if(this->thread_flush.joinable()) {
if(this->thread_flush.get_id() == this_thread::get_id())
this->thread_flush.detach();
else
this->thread_flush.join();
}
}
}
if(this->readEvent){
auto event = this->readEvent;
this->readEvent = nullptr;
l1.unlock();
l.unlock();
event_del_block(event);
event_free(event);
l.lock();
l1.lock();
}
if(this->writeEvent){
auto event = this->writeEvent;
this->writeEvent = nullptr;
l1.unlock();
l.unlock();
event_del_block(event);
event_free(event);
l.lock();
l1.lock();
}
if(this->clientFd > 0) {
shutdown(this->clientFd, SHUT_RDWR);
close(this->clientFd);
}
this->clientFd = -1;
{
threads::MutexLock l2(this->handle->clientLock);
auto& clList = this->handle->connectedClients;
auto elm = find(clList.begin(), clList.end(), _this.lock());
if(elm != clList.end()) clList.erase(elm);
else logError(LOG_FT, "{} Invalid ft client list!", this->client_prefix());
}
this->read_queue.clear();
this->write_queue.clear();
if(this->fstream){
this->fstream->flush();
this->fstream->close();
delete this->fstream;
this->fstream = nullptr;
}
this->pendingKey = nullptr;
}
bool FileClient::tick() {
lock_guard<threads::Mutex> l(this->tickLock);
if(this->state_connection == C_DISCONNECTED) return false;
if(this->state_connection != C_DISCONNECTING) {
if(last_io_action.time_since_epoch().count() == 0)
last_io_action = system_clock::now();
else if(last_io_action + minutes(1) < system_clock::now()) {
logMessage(LOG_FT, "{} Timed out after one minute of silence!", this->client_prefix());
this->disconnect();
return false;
}
}
/* decode incomming stuff */
bool flag_reexecute = false;
{
/* types which require an extra layer of decode */
if(this->ftType == FTType::TeaWeb_SSL || this->ftType == FTType::TeaWeb_SSL_HTTP || this->ftType == FTType::TeaWeb_SSL_WS || this->ftType == FTType::TeaWeb_HTTP) {
pipes::buffer buffer;
{
lock_guard buffer_lock(this->bufferLock);
if(this->read_queue.empty()) {
flag_reexecute = false;
} else {
flag_reexecute = true;
buffer = this->read_queue.front();
this->read_queue.pop_front();
}
}
if(flag_reexecute) {
if(this->ftType == FTType::TeaWeb_HTTP)
this->handle_http_message(buffer);
else
this->ssl_handler.process_incoming_data(buffer);
}
} else if(this->ftType == FTType::TeamSpeak) {
flag_reexecute |= this->handle_ts_message();
} else if(this->ftType == FTType::Unknown) {
/* we need at least 16 bytes to detect any type */
if(this->availableBytes() >= 16) {
auto header = this->peekBytes(16);
if(header.find("GET") != -1 || header.find("POST") != -1 || header.find("OPTIONS") != -1) {
debugMessage(LOG_FT, "{} Using HTTP only!", this->client_prefix());
this->ftType = FTType::TeaWeb_HTTP;
return true;
} else if(pipes::SSL::isSSLHeader(header) && serverInstance->sslManager()->web_ssl_options()) {
debugMessage(LOG_FT, "{} Encrypting pipe with SSL", this->client_prefix());
this->ftType = FTType::TeaWeb_SSL;
this->ssl_handler.initialize(serverInstance->sslManager()->web_ssl_options());
return true;
} else {
debugMessage(LOG_FT, "{} Transferring data with the TS protocol", this->client_prefix());
this->ftType = FTType::TeamSpeak;
return true;
}
}
} else {
logError(LOG_FT, "{} Ticked client with unknown protocol type. Closing connection.", this->client_prefix());
this->disconnect();
return false;
}
}
if(this->state_transfer == T_TRANSFER) {
if(this->pendingKey) {
/* test for download */
if(!this->pendingKey->upload) {
/* lets send some data :) */
if(!fstream) {
logError(LOG_FT, "{} Missing file stream! Disconnecting client...", this->client_prefix());
this->disconnect();
return false;
}
size_t count = 0;
{
lock_guard lock(this->bufferLock);
count = this->write_queue.size();
}
if(count <= (1024 * 512) / BUFFER_SIZE){ //Max buffer 500Kb
if(!fstream->good()){
logError(LOG_FT, "{} Cant finish file download. File isn't good anymore!", this->client_prefix());
this->disconnect();
return false;
}
pipes::buffer writeBuffer(BUFFER_SIZE);
auto read = fstream->readsome((char*) writeBuffer.data_ptr(), writeBuffer.length());
if(read < 0){
logError(LOG_FT, "{} Invalid file read. Read {} bytes of max {}. Index {}/{}", this->client_prefix(), read, writeBuffer.length(), this->bytesHandled, this->pendingKey->size);
this->disconnect();
return false;
} else if(read == 0){
if(this->bytesHandled != this->pendingKey->size){
logError(LOG_FT, "{} Invalid end of file. Expected {} bytes and attempted to read at {}", this->client_prefix(), this->pendingKey->size, this->bytesHandled);
this->disconnect();
return false;
}
}
this->bytesHandled += read;
this->client->getConnectionStatistics()->logFileTransferOut(read);
if(read > 0){
writeBuffer.resize(read);
this->sendMessage(writeBuffer);
flag_reexecute |= true;
}
}
}
/* lets test if we're done */
if(this->bytesHandled == this->pendingKey->size){
auto time = duration_cast<milliseconds>(system_clock::now() - this->connect_timestamp).count();
logMessage(LOG_FT, "{} File transfer completed. Transferred {} bytes in {} milliseconds. Waiting for disconnect.", this->client_prefix(), this->bytesHandled, time);
this->close_file_handle();
this->pendingKey.reset();
this->state_transfer = T_DONE;
this->finished_timestamp = system_clock::now();
/* we expect TS3 to hangup the connection */
if(this->ftType != FTType::TeamSpeak)
this->disconnect(seconds(5));
return false;
}
}
} else if(this->state_transfer == T_DONE) {
if(this->finished_timestamp + seconds(2) < system_clock::now() && this->state_connection == C_CONNECTED) {
debugMessage(LOG_FT, "{} Disconnecting client after 2 seconds after finish!", this->client_prefix());
this->disconnect(seconds(5));
}
}
return flag_reexecute;
}
bool FileClient::applyKey(const string &key) {
{
threads::MutexLock lock(this->handle->keylock);
for(const auto& pkey : this->handle->pendingKeys) //needs a copy
if(pkey && pkey->key == key){
this->pendingKey = pkey;
this->handle->pendingKeys.erase(std::find(this->handle->pendingKeys.begin(), this->handle->pendingKeys.end(), pkey));
break;
}
}
if(!this->pendingKey){
logError(LOG_FT, "{} Tried to apply an non existing key! (Key: {})", this->client_prefix(), key);
this->disconnect();
return false;
}
this->client = this->pendingKey->owner.lock();
if(!this->client) {
logError(LOG_FT, "{} Tried connect with an invalid key (client offline)! (Key: {})", this->client_prefix(), key);
this->disconnect();
return false;
}
debugMessage(LOG_FT, "{} Initialized file transfer for file '{}' (Type: {})", this->client_prefix(), this->pendingKey->targetFile, pendingKey->upload ? "upload" : "download");
if(pendingKey->offset == 0 && pendingKey->upload) {
try {
fs::remove(fs::u8path(pendingKey->targetFile));
} catch (std::exception& e) {}
}
fstream = new std::fstream();
fstream->open(pendingKey->targetFile, (pendingKey->upload ? ios::out : ios::in) | ios::binary | ios::app);
if(!*fstream) {
logError(LOG_FT, "{} Failed to open target file {} for {}", this->client_prefix(), this->pendingKey->targetFile, pendingKey->upload ? "upload" : "download");
delete fstream;
this->fstream = nullptr;
return false;
}
fstream->seekg(0, std::ios::beg);
auto fsize = fstream->tellg();
fstream->seekg(0, std::ios::end);
fsize = fstream->tellg() - fsize;
fstream->seekg(this->pendingKey->offset, std::ios::beg);
if(!*fstream) {
logError(LOG_FT, "{} Failed to seek within file {} for {}", this->client_prefix(), this->pendingKey->targetFile, pendingKey->upload ? "upload" : "download");
delete fstream;
this->fstream = nullptr;
return false;
}
debugMessage(LOG_FT, "{} Received local file size {}. Target size if {}", this->client_prefix(), fsize, pendingKey->size);
if(this->state_transfer == T_INITIALIZE)
this->state_transfer = T_TRANSFER;
this->connect_timestamp = system_clock::now();
return true;
}
bool FileClient::uploadWriteBytes(const pipes::buffer_view& message) {
this->client->getConnectionStatistics()->logFileTransferIn(message.length());
if(!fstream->good()){
logError(LOG_FT, "{} uploadWriteBytes(...) called with invalid fstream!", this->client_prefix());
return false;
}
fstream->write(message.data_ptr<char>(), message.length());
if(!fstream->good()){
logError(LOG_FT, "{} Invalid file write! ({})", this->client_prefix(), message.length());
this->disconnect();
return false;
}
this->bytesHandled += message.length();
return true;
}
void FileClient::close_file_handle() {
if(this->fstream) {
this->fstream->flush();
this->fstream->close();
delete this->fstream;
this->fstream = nullptr;
}
}
void FileClient::sendMessage(const pipes::buffer_view& message) {
switch(this->ftType) {
case FTType::TeamSpeak:
case FTType::TeaWeb_HTTP:
this->sendRawMessage(message);
break;
case FTType::TeaWeb_SSL:
case FTType::TeaWeb_SSL_HTTP:
this->ssl_handler.send(message);
break;
case FTType::TeaWeb_SSL_WS:
this->ws_handler.send({pipes::BINARY, message.own_buffer()});
break;
default:
/* Dont log an error because the timeout disconnect function uses this without knowing which proto */
__asm__("nop");
return;
}
}
void FileClient::handle_ssl_message(const pipes::buffer_view &buffer) {
if(this->ftType == FTType::TeaWeb_SSL_HTTP)
this->handle_http_message(buffer);
else if(this->ftType == FTType::TeaWeb_SSL_WS)
this->ws_handler.process_incoming_data(buffer);
else if(this->ftType == FTType::TeaWeb_SSL) { /* lets detect if we have HTTP or WebSocket */
this->read_buffer += buffer;
this->handle_http_header();
}
}
void FileClient::handle_http_header() {
auto header_end = this->read_buffer.find("\r\n\r\n");
if(header_end == string::npos) {
if(this->read_buffer.length() > 1024 * 1024 * 4) {
this->read_buffer = pipes::buffer{};
logMessage(LOG_FT, "{} Client tried to fillup server memory. Disconnecting client.", this->client_prefix());
this->disconnect();
return;
}
return;
}
auto raw_request = this->read_buffer.view(0, header_end).string();
http::HttpRequest request{};
if(!http::parse_request(raw_request, request)) {
logError(LOG_FT, "{} Failed to parse HTTP request. Disconnecting client.", this->client_prefix());
this->disconnect();
return;
}
auto header_upgrade = request.findHeader("Upgrade");
if(header_upgrade && header_upgrade.values[0] == "websocket") {
debugMessage(LOG_FT, "{} Received WebSocket upgrade. Upgrading connection.", this->client_prefix(), raw_request);
if(this->ftType == FTType::TeaWeb_SSL)
this->ftType = FTType::TeaWeb_SSL_WS;
else {
//TODO: WebSocket only!
}
this->ws_handler.initialize();
this->ws_handler.process_incoming_data(this->read_buffer);
this->read_buffer = pipes::buffer{};
this->handle->tickFileClient(_this.lock()); /* we require a manual reticking */
return;
} else {
/* process the http request header */
if(this->ftType == FTType::TeaWeb_SSL)
this->ftType = FTType::TeaWeb_SSL_HTTP;
http::HttpResponse response{};
response.setHeader("Connection", {"close"}); /* close the connection instance, we dont want multiple requests */
response.setHeader("Access-Control-Allow-Methods", {"GET, POST"});
response.setHeader("Access-Control-Allow-Origin", {"*"});
response.setHeader("Access-Control-Allow-Headers", request.findHeader("Access-Control-Request-Headers").values); //access-control-allow-headers
response.setHeader("Access-Control-Max-Age", {"86400"});
response.setHeader("Access-Control-Expose-Headers", {"*, Content-Length, X-media-bytes, Content-Disposition"});
/* test for a preflight request: https://developer.mozilla.org/en-US/docs/Glossary/preflight_request */
auto request_method = request.findHeader("Access-Control-Request-Method");
if(request_method) {
debugMessage(LOG_FT, "{} Received preflight request. Sending close response with allow and let the client reconnect.", this->client_prefix());
response.code = http::code::_200;
this->read_buffer = this->read_buffer.range(header_end + 4);
auto raw_response = response.build();
this->sendMessage(pipes::buffer_view{raw_response.data(), raw_response.length()});
this->disconnect(seconds(5)); /* write our response & flush */
return;
} else {
auto transfer_key = request.findHeader("transfer-key");
auto download_name = request.findHeader("download-name");
if(!transfer_key) {
response.code = http::code::code(400, "Bad Request");
debugMessage(LOG_FT, "{} Received invalid HTTP request (Missing transfer key).", this->client_prefix());
} else {
if(!this->applyKey(transfer_key.values[0]) || !this->pendingKey) {
response.code = http::code::code(404, "Not Found");
debugMessage(LOG_FT, "{} Received invalid HTTP request (Invalid transfer key: {}).", this->client_prefix(), transfer_key.values[0]);
} else {
response.code = http::code::code(200, "OK");
response.setHeader("Content-Length", {to_string(this->pendingKey->size)});
if(!this->pendingKey->upload) {
response.setHeader("Content-type", {"application/octet-stream; "});
response.setHeader("Content-Transfer-Encoding", {"binary"});
response.setHeader("Content-Disposition", {"attachment; filename=\"" + http::encode_url(download_name ? download_name.values[0] : "TeaWeb Download") + "\""});
if(this->pendingKey->size > 1) {
char header_buffer[64];
auto read = fstream->readsome(header_buffer, 64);
if(read > 0)
response.setHeader("X-media-bytes", {base64::encode(header_buffer, read)});
fstream->seekg(this->pendingKey->offset, std::ios::beg);
}
}
}
}
if(!this->pendingKey || !this->pendingKey->upload) {
auto raw_response = response.build();
this->sendMessage(pipes::buffer_view{raw_response.data(), raw_response.length()});
}
if(response.code->code != 200) {
this->disconnect(seconds(5)) /* write our response & flush */;
return;
}
this->http_init = true;
auto overhead = this->read_buffer.range(header_end + 4);
this->read_buffer = pipes::buffer{}; /* reset the read buffer */
if(overhead.length() > 0)
this->handle_http_message(overhead);
this->handle->tickFileClient(_this.lock()); /* we require a manual reticking */
}
}
}
bool FileClient::handle_ts_message() {
if(this->state_transfer == T_INITIALIZE) {
if(this->availableBytes() >= 16) {
return this->applyKey(this->getBytes(16));
} else
return false;
} else if(this->state_transfer == T_TRANSFER) {
if(!this->pendingKey)
return false;
if(!this->pendingKey->upload)
return false; /* should never happen! */
bool reexecute = false;
pipes::buffer buffer;
{
lock_guard buffer_lock(this->bufferLock);
if(this->read_queue.empty())
return false; /* nothing to upload */
buffer = this->read_queue.front();
this->read_queue.pop_front();
reexecute |= !this->read_queue.empty();
}
if(!this->uploadWriteBytes(buffer))
return false; /* error already handeled by uploadWriteBytes(...) */
return reexecute;
}
return false;
}
void FileClient::handle_ws_message(const pipes::WSMessage &message) {
if(this->state_transfer == T_INITIALIZE) {
debugMessage(LOG_FT, "{} Received transfer key: {}", this->client_prefix(), message.data.string());
if(this->applyKey(message.data.string()))
this->handle->tickFileClient(_this.lock()); /* we require a manual reticking */
return;
} else if(this->state_transfer == T_TRANSFER) {
if(this->pendingKey->upload)
this->uploadWriteBytes(message.data);
else {
logError(LOG_FT, "{} Invalid write (Just download)", this->client_prefix());
this->disconnect();
}
}
}
void FileClient::handle_http_message(const pipes::buffer_view &message) {
if(!this->http_init) {
this->read_buffer += message;
this->handle_http_header();
return;
}
if(!https_upload_init) {
//------WebKitFormBoundaryaWP8XAzMBnMOJznv\r\nContent-Disposition: form-data; name=\"file\"; filename=\"blob\"\r\nContent-Type: application/octet-stream\r\n\r\n
this->read_buffer += message;
auto header_end = this->read_buffer.find("\r\n\r\n");
if(header_end == string::npos) {
if(this->read_buffer.length() > 1024 * 1024 * 4) {
this->read_buffer = pipes::buffer{};
logMessage(LOG_FT, "{} Client tried to fillup server memory. Disconnecting client.", this->client_prefix());
this->disconnect();
return;
}
return;
}
https_upload_init = true;
auto overhead = this->read_buffer.view(header_end + 4);
this->read_buffer = pipes::buffer{};
if(!overhead.empty())
this->handle_http_message(overhead);
return;
}
if(!this->pendingKey || !this->pendingKey->upload) {
logError(LOG_FT, "{} HTTP Invalid request", this->client_prefix());
return;
}
auto bytes_to_write = this->pendingKey->size - this->bytesHandled; /* ignore boundaries */
if(bytes_to_write < message.length())
this->uploadWriteBytes(message.view(0, bytes_to_write));
else
this->uploadWriteBytes(message);
if(this->bytesHandled == this->pendingKey->size){
http::HttpResponse response{};
response.setHeader("Connection", {"close"}); /* close the connection instance, we dont want multiple requests */
response.setHeader("Access-Control-Allow-Methods", {"GET, POST"});
response.setHeader("Access-Control-Allow-Origin", {"*"});
response.setHeader("Access-Control-Allow-Headers", {"*"});
response.setHeader("Access-Control-Max-Age", {"86400"});
response.setHeader("Access-Control-Expose-Headers", {"*"});
auto raw_response = response.build();
this->sendMessage(pipes::buffer_view{raw_response.data(), raw_response.length()});
}
}
-133
View File
@@ -1,133 +0,0 @@
#pragma once
#include <protocol/buffers.h>
#include <poll.h>
#include <fstream>
#include <src/server/file/LocalFileServer.h>
#include <event.h>
#include <pipes/ws.h>
#include <pipes/ssl.h>
#include "src/VirtualServer.h"
namespace ts {
namespace server {
class ConnectedClient;
class ConnectedClient;
class LocalFileServer;
enum FTType {
Unknown,
TeamSpeak,
TeaWeb_SSL, /* not yet decided if the protocol is HTTP or WebSocket */
TeaWeb_SSL_WS,
TeaWeb_SSL_HTTP,
TeaWeb_HTTP
};
class FileClient {
friend class LocalFileServer;
public:
enum TransferState {
T_INITIALIZE,
T_TRANSFER,
T_DONE
};
enum ConnectionState {
C_CONNECTED,
C_DISCONNECTING,
C_DISCONNECTED
};
struct BandwidthEntry {
std::chrono::system_clock::time_point timestamp;
uint16_t length = 0;
};
FileClient(LocalFileServer* handle, int socketFd);
~FileClient();
void disconnect(std::chrono::milliseconds = std::chrono::milliseconds(5000));
FTType getFTType(){ return this->ftType; }
size_t used_bandwidth();
std::string client_prefix();
size_t transferred_bytes();
size_t remaining_bytes();
protected:
void disconnectFinal(std::unique_lock<threads::Mutex>& /* tick lock */, bool /* handle flush thread */);
bool tick();
bool uploadWriteBytes(const pipes::buffer_view&);
void close_file_handle();
bool applyKey(const std::string &);
void sendMessage(const pipes::buffer_view&);
//Direct methods & IO stuff
void sendRawMessage(const pipes::buffer_view&);
void handleMessageRead(int, short, void*);
void handleMessageWrite(int, short, void*);
void handle_ssl_message(const pipes::buffer_view&); /* handeles all decoded SSL messages */
/* http header parser. header must be stored with read buffer! */
void handle_http_header();
/* Final protocol handlers */
void handle_http_message(const pipes::buffer_view&);
void handle_ws_message(const pipes::WSMessage&);
bool handle_ts_message();
private:
LocalFileServer* handle;
std::weak_ptr<FileClient> _this;
std::recursive_mutex bandwidth_lock;
std::deque<std::unique_ptr<BandwidthEntry>> bandwidth;
sockaddr_storage remote_address;
int clientFd;
bool event_read_hold = false;
::event* readEvent = nullptr;
bool event_write_hold = false;
::event* writeEvent = nullptr;
threads::Mutex tickLock;
std::recursive_timed_mutex bufferLock;
std::deque<pipes::buffer> write_queue;
std::deque<pipes::buffer> read_queue;
pipes::buffer read_buffer; /* buffer which contains fragments of decoded data, e.g. HTTP request. Access only within tickLock locked */
FTType ftType = FTType::Unknown;
pipes::WebSocket ws_handler;
pipes::SSL ssl_handler;
bool https_upload_init = false;
bool http_init = false; /* general flag if the HTTP header has been parsed */
std::shared_ptr<ConnectedClient> client;
std::shared_ptr<file::FileTransfereKey> pendingKey = nullptr;
std::chrono::time_point<std::chrono::system_clock> last_io_action;
std::chrono::time_point<std::chrono::system_clock> connect_timestamp;
std::chrono::time_point<std::chrono::system_clock> finished_timestamp;
std::fstream* fstream = nullptr;
size_t bytesHandled = 0;
ConnectionState state_connection = ConnectionState::C_CONNECTED;
TransferState state_transfer = TransferState::T_INITIALIZE;
size_t availableBytes();
std::string getBytes(size_t);
std::string peekBytes(size_t);
std::mutex thread_flush_lock;
std::thread thread_flush;
};
}
}
-248
View File
@@ -1,248 +0,0 @@
#include <algorithm>
#include <memory>
#include <src/server/file/LocalFileServer.h>
#include <log/LogUtils.h>
#include <misc/std_unique_ptr.h>
#include <pipes/buffer.h>
#include "FileClient.h"
using namespace std;
using namespace std::chrono;
using namespace ts::server;
void FileClient::sendRawMessage(const pipes::buffer_view &message) {
lock_guard lock(this->bufferLock);
this->write_queue.push_back(message.own_buffer());
if(!this->event_write_hold)
event_add(this->writeEvent, nullptr);
else {
__asm__("nop");
}
}
void FileClient::handleMessageWrite(int fd, short, void *) {
auto self = this->_this.lock();
if(!self) return;
decltype(this->pendingKey) pending_key;
{
lock_guard<threads::Mutex> l(this->tickLock);
pending_key = this->pendingKey;
}
unique_lock lock(this->bufferLock, defer_lock);
if(!lock.try_lock_for(milliseconds(5))) {
logWarning(LOG_FT, "{} Buffer lock locked, could not write data!", this->client_prefix());
return; /* somebody else doing a action and will (hopefully) readd the event */
}
if(self->state_connection == C_DISCONNECTED) return;
auto used = this->used_bandwidth();
if(pending_key) { //Delay the write <3 :P
if(pending_key->max_bandwhidth >= 0 && used > pending_key->max_bandwhidth) {
event_write_hold = true;
logTrace(LOG_FT, "{} Exceeded bandwidth limit of {} bytes (Used {} bytes). Temporary skipping write event!", this->client_prefix(), pending_key->max_bandwhidth, this->used_bandwidth());
return;
}
}
event_write_hold = false;
pipes::buffer* buffer = nullptr;
while(true) {
if(this->write_queue.empty()) {
lock.unlock(); /* unlock write buffer because we're ticking */
if(pending_key && !pending_key->upload)
this->handle->tickFileClient(_this.lock()); //We have to fill up again
return;
}
buffer = &this->write_queue.front();
if(buffer->empty()) {
this->write_queue.pop_front();
buffer = nullptr;
continue;
}
break;
}
ssize_t length = buffer->length();
if(pending_key && pending_key->max_bandwhidth >= 0) {
if(pending_key->max_bandwhidth < length && pending_key->max_bandwhidth > used) length = pending_key->max_bandwhidth - used;
}
length = send(fd, buffer->data_ptr(), length, MSG_NOSIGNAL);
//logTrace(LOG_FT, "{} Wrote {} bytes", this->client_prefix(), length);
if(length == -1) {
if (errno == EINTR || errno == EAGAIN) {
event_add(this->writeEvent, nullptr);
return;
}
else {
logError(LOG_FT, "{} Failed to write some data! ({}/{})", this->client_prefix(), errno, strerror(errno));
lock.unlock();
self->disconnect();
return;
}
} else {
*buffer = buffer->range(length);
auto entry = make_unique<BandwidthEntry>();
entry->length = length ;
entry->timestamp = system_clock::now();
{
lock_guard<recursive_mutex> b_lock(this->bandwidth_lock);
this->bandwidth.push_back(move(entry));
}
}
last_io_action = system_clock::now();
if(buffer->empty()) {
this->write_queue.pop_front();
buffer = nullptr;
}
if(pending_key && this->bytesHandled != pending_key->size) {
if(this->write_queue.size() < 4) this->handle->tickFileClient(_this.lock());
}
if(!this->write_queue.empty()) {
event_add(this->writeEvent, nullptr);
}
self.reset();
}
void FileClient::handleMessageRead(int fd, short, void *) {
auto self = this->_this.lock();
if(self->state_connection != C_CONNECTED) return;
decltype(this->pendingKey) pending_key;
{
lock_guard<threads::Mutex> l(this->tickLock);
pending_key = this->pendingKey;
}
size_t buffer_length = 1024;
if(pending_key && pending_key->max_bandwhidth >= 0) {
auto used = this->used_bandwidth();
if(used < pending_key->max_bandwhidth) {
buffer_length = pending_key->max_bandwhidth - used;
if(buffer_length > 1024) buffer_length = 1024;
} else {
logTrace(LOG_FT, "{} Exceeded bandwidth limit of {} bytes (Used {} bytes). Temporary removing read event!", this->client_prefix(), pending_key->max_bandwhidth, used);
{
lock_guard lock(this->bufferLock);
if(this->readEvent)
event_del_noblock(this->readEvent);
}
this->event_read_hold = true;
return;
}
}
pipes::buffer buffer(buffer_length);
auto length = recv(fd, buffer.data_ptr(), buffer.length(), 0);
if(length < 0){
if(errno == EINTR || errno == EAGAIN)
;//event_add(this->readEvent, nullptr);
else {
{
lock_guard lock(this->bufferLock);
if(this->readEvent)
event_del_noblock(this->readEvent);
}
if(this->state_connection == C_CONNECTED) {
logError(LOG_FT, "{} Failed to read some data! ({}/{})", this->client_prefix(), errno, strerror(errno));
self->disconnect();
}
}
return;
} else if(length == 0){
{
lock_guard lock(this->bufferLock);
if(this->readEvent)
event_del_noblock(this->readEvent);
}
if(this->state_connection == C_CONNECTED) {
if(this->state_transfer == T_TRANSFER)
logWarning(LOG_FT, "{} Transfer hang up. Remote peer closed the connection.", this->client_prefix());
else
logMessage(LOG_FT, "{} Remote peer has closed the connection before initializing a transfer.", this->client_prefix());
self->disconnect(seconds(3));
}
return;
}
last_io_action = system_clock::now();
buffer.resize(length);
{
lock_guard lock(this->bufferLock);
if(self->state_connection != C_CONNECTED) return; /* drop the buffer because we're not connected anymore */
this->read_queue.push_back(std::move(buffer));
}
this->handle->tickFileClient(_this.lock());
auto entry = make_unique<BandwidthEntry>();
entry->length = length ;
entry->timestamp = system_clock::now();
//logTrace(LOG_FT, "{} Readed {} bytes", this->client_prefix(), length);
{
lock_guard<recursive_mutex> lock(this->bandwidth_lock);
this->bandwidth.push_back(move(entry));
}
}
size_t FileClient::availableBytes() {
lock_guard lock(this->bufferLock);
size_t available = 0;
for(const auto& buf : this->read_queue)
available += buf.length();
return available;
}
std::string FileClient::peekBytes(size_t size) {
ssize_t required = size;
lock_guard lock(this->bufferLock);
string result;
result.reserve(size);
for(pipes::buffer& buf : this->read_queue) {
if(required <= 0) break;
if(buf.length() > required) {
result += string(buf.data_ptr<const char>(), required);
required = 0;
} else {
result += string(buf.data_ptr<const char>(), buf.length());
required -= buf.length();
}
}
return result;
}
std::string FileClient::getBytes(size_t size) {
lock_guard lock(this->bufferLock);
string result;
result.reserve(size);
while(!this->read_queue.empty()) {
if(size <= 0) break;
auto& buf = this->read_queue.front();
if(buf.length() > size) {
result += string((char*) buf.pipes::buffer_view::data_ptr(), size);
buf = buf.range(size);
size = 0;
} else {
result += string((char*) buf.data_ptr(), buf.length());
size -= buf.length();
this->read_queue.pop_front();
}
}
return result;
}
@@ -1,7 +1,6 @@
#include "ChannelProvider.h"
#include "../../MusicClient.h"
#include "../../../../InstanceHandler.h"
#include "src/server/file/LocalFileServer.h"
#include "../../../../../../music/providers/ffmpeg/FFMpegProvider.h"
@@ -22,6 +21,7 @@ threads::Future<std::shared_ptr<::music::MusicPlayer>> ChannelProvider::createPl
auto server = ((VirtualServer*) ptr_server)->ref();
threads::Future<std::shared_ptr<::music::MusicPlayer>> future;
#if 0
if(server) {
std::thread([future, server, url, ptr_server]{
auto f_server = serverInstance->getFileServer();
@@ -116,7 +116,9 @@ threads::Future<std::shared_ptr<::music::MusicPlayer>> ChannelProvider::createPl
} else {
future.executionFailed("invalid bot");
}
#else
future.executionFailed("channel file playback is currently not supported");
#endif
return future;
}
@@ -165,6 +167,7 @@ threads::Future<shared_ptr<UrlInfo>> ChannelProvider::query_info(const std::stri
auto server = ((VirtualServer*) ptr_server)->ref();
threads::Future<shared_ptr<UrlInfo>> future;
#if 0
if(server) {
std::thread([future, server, url, ptr_server]{
auto f_server = serverInstance->getFileServer();
@@ -264,6 +267,9 @@ threads::Future<shared_ptr<UrlInfo>> ChannelProvider::query_info(const std::stri
future.executionFailed("invalid bot");
}
#else
future.executionFailed("channel file playback is currently not supported");
#endif
return future;
}
+3
View File
@@ -267,6 +267,8 @@ void LicenseService::handle_client_connected() {
this->send_license_validate_request();
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
void LicenseService::handle_message(::license::protocol::PacketType type, const void *buffer, size_t size) {
switch (type) {
case ::license::protocol::PACKET_SERVER_VALIDATION_RESPONSE:
@@ -286,6 +288,7 @@ void LicenseService::handle_message(::license::protocol::PacketType type, const
return;
}
}
#pragma GCC diagnostic pop
void LicenseService::handle_client_disconnected(const std::string& message) {
std::lock_guard rlock{this->request_lock};
-777
View File
@@ -1,777 +0,0 @@
#include "LocalFileServer.h"
#include "src/client/file/FileClient.h"
#include "src/client/ConnectedClient.h"
#include <netinet/tcp.h>
#include <experimental/filesystem>
#include <log/LogUtils.h>
#include <misc/std_unique_ptr.h>
#include "src/InstanceHandler.h"
using namespace std;
using namespace std::chrono;
using namespace ts;
using namespace ts::file;
using namespace ts::server;
namespace fs = std::experimental::filesystem;
#if defined(TCP_CORK) && !defined(TCP_NOPUSH)
#define TCP_NOPUSH TCP_CORK
#endif
extern InstanceHandler* serverInstance;
LocalFileServer::LocalFileServer() {}
LocalFileServer::~LocalFileServer() {
stop();
}
inline fs::path buildPath(std::string rootPath, std::shared_ptr<file::FileEntry> directory){
auto strPath = directory ? directory->path + "/" + directory->name : "";
if(strPath.find(rootPath) == 0) return fs::u8path(strPath);
return fs::u8path(rootPath + strPath);
}
std::shared_ptr<file::Directory> LocalFileServer::createDirectory(std::string name, std::shared_ptr<file::Directory> parent) {
auto path = buildPath(this->rootPath, parent);
path += name;
std::error_code code{};
if(!fs::exists(path, code))
if(!fs::create_directories(path, code)) return nullptr;
else ;
else if(!fs::is_directory(path, code)) return nullptr;
return static_pointer_cast<file::Directory>(this->findFile(path.string()));
}
bool LocalFileServer::fileExists(std::shared_ptr<file::Directory> dir) {
std::error_code code{};
return fs::exists(buildPath(this->rootPath, dir), code);
}
bool LocalFileServer::fileExists(std::shared_ptr<file::File> file) {
std::error_code code{};
return fs::exists(buildPath(this->rootPath, file), code);
}
std::shared_ptr<file::FileEntry> LocalFileServer::findFile(std::string path, std::shared_ptr<file::Directory> parent) {
if(path.find(this->rootPath) != 0) {
string strPath = (parent ? parent->path + "/" + parent->name : this->rootPath) + "/";
if(strPath.find(this->rootPath) != 0 && rootPath != strPath)
path = this->rootPath + strPath;
if(path.find(strPath) == 0 && !strPath.empty())
;
else path = strPath + path;
}
std::error_code code{};
fs::path absPath = fs::u8path(path);
if(!fs::is_regular_file(absPath, code) && !fs::is_directory(absPath, code)){
debugMessage(LOG_FT, "Could not find requested file. Abs path: {} | {}. (path={}, parent={})", absPath.string(), path, path, (parent ? parent->path + "/" + parent->name : "./"));
return nullptr;
}
std::shared_ptr<file::FileEntry> entry;
if(fs::is_directory(absPath, code))
entry = std::make_shared<file::Directory>();
else entry = std::make_shared<file::File>();
entry->name = absPath.filename();
entry->type = fs::is_directory(absPath, code) ? FileType::DIRECTORY : FileType::FILE;
entry->path = absPath.parent_path().string();
entry->lastChanged = fs::last_write_time(absPath, code);
if(entry->type == FileType::FILE)
static_pointer_cast<file::File>(entry)->fileSize = entry->type == FileType::FILE ? fs::file_size(absPath, code) : 0;
return entry;
}
std::vector<std::shared_ptr<file::FileEntry>> LocalFileServer::listFiles(std::shared_ptr<file::Directory> dir) {
if(!dir) return {};
auto directory = buildPath(this->rootPath, dir);
std::error_code code{};
if(!fs::exists(directory, code) || code) return {};
if(!fs::is_directory(directory, code) || code) return {};
std::vector<std::shared_ptr<file::FileEntry>> result;
deque<fs::directory_entry> files;
for(const auto& elm : fs::directory_iterator(directory, code)) {
files.push_back(elm);
}
if(code) {
logWarning(LOG_FT, "Failed to iterate over directory {}: {}", directory.string(), code.message());
return {};
}
std::stable_sort(files.begin(), files.end(), [&](const fs::directory_entry& a, const fs::directory_entry& b) {
return fs::last_write_time(a.path(), code) > fs::last_write_time(b.path(), code);
});
for(const auto& elm : files) {
if(fs::is_regular_file(elm.path(), code)){
auto entry = make_shared<file::File>();
entry->name = elm.path().filename();
entry->type = FileType::FILE;
entry->path = elm.path().parent_path().string();
entry->lastChanged = fs::last_write_time(elm.path(), code);
entry->fileSize = fs::file_size(elm.path(), code);
result.push_back(entry);
} else if(fs::is_directory(elm.path(), code)){
auto entry = make_shared<file::Directory>();
entry->name = elm.path().filename();
entry->type = FileType::DIRECTORY;
entry->path = elm.path().parent_path().string();
entry->lastChanged = fs::last_write_time(elm.path(), code);
result.push_back(entry);
} else {
logError(LOG_FT, "Invalid file in file tree. File path: " + elm.path().string());
}
}
return result;
}
bool LocalFileServer::deleteFile(std::shared_ptr<file::FileEntry> file) {
std::error_code code{};
return fs::remove_all(fs::u8path(file->path + "/" + file->name), code) > 0 && !code;
}
inline std::string randomString(uint length = 15, std::string charIndex = "abcdefghijklmnaoqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
{
std::string rs = "";
for (uint i = 0; i < length; ++i)
rs += charIndex[rand() % charIndex.length()];
return rs;
}
std::shared_ptr<file::FileTransfereKey> LocalFileServer::generateDownloadTransferKey(std::string &errorMessage, std::string targetFile, size_t offset, const shared_ptr<ConnectedClient>& client) {
auto file = this->findFile(targetFile, nullptr);
if(!file){
errorMessage = "file does not exists";
return nullptr;
}
if(file->type != FileType::FILE) {
errorMessage = "file is a directory";
return nullptr;
}
auto result = make_shared<file::FileTransfereKey>();
result->owner = client;
result->server = client->getServerId();
result->key_id = (uint16_t) (rand() & 0xFFFF);
result->createTimestamp = std::chrono::system_clock::now();
result->key = randomString(16);
result->offset = offset;
result->size = static_pointer_cast<file::File>(file)->fileSize;
result->upload = false;
result->targetFile = targetFile;
{ //Test for server?
auto bandwidth_server = client->getServer()->properties()[property::VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH].as<int64_t>();
auto bandwidth_instance = serverInstance->properties()[property::SERVERINSTANCE_MAX_UPLOAD_TOTAL_BANDWIDTH].as<int64_t>();
result->max_server_bandwidth = bandwidth_server;
result->max_bandwhidth = bandwidth_server;
if(bandwidth_instance != -1) {
if(bandwidth_instance < bandwidth_server)
result->max_bandwhidth = bandwidth_instance; //We want to limit that already until exact calculations was made
}
}
if(client->getServer())
result->max_server_bandwidth = client->getServer()->properties()[property::VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH];
{
threads::MutexLock lock(this->keylock);
pendingKeys.push_back(result);
}
return result;
}
std::shared_ptr<file::FileTransfereKey> LocalFileServer::generateUploadTransferKey(std::string &errorMessage, std::string targetFile, size_t size, size_t offset, const shared_ptr<ConnectedClient>& client) {
shared_ptr<file::FileTransfereKey> result = make_shared<file::FileTransfereKey>();
result->owner = client;
result->server = client->getServerId();
result->key_id = (uint16_t) (rand() & 0xFFFF);
result->createTimestamp = std::chrono::system_clock::now();
result->key = randomString(16);
result->offset = offset;
result->size = size;
result->upload = true;
result->targetFile = targetFile;
{ //Test for server?
auto bandwidth_server = client->getServer()->properties()[property::VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH].as<int64_t>();
auto bandwidth_instance = serverInstance->properties()[property::SERVERINSTANCE_MAX_DOWNLOAD_TOTAL_BANDWIDTH].as<int64_t>();
result->max_server_bandwidth = bandwidth_server;
result->max_bandwhidth = bandwidth_server;
if(bandwidth_instance != -1) {
if(bandwidth_instance < bandwidth_server)
result->max_bandwhidth = bandwidth_instance; //We want to limit that already until exact calculations was made
}
}
{
threads::MutexLock lock(this->keylock);
pendingKeys.push_back(result);
}
debugMessage(LOG_FT, "Created file upload key=" + result->key + " for " + targetFile + " (" + to_string(size) + " bytes)");
return result;
}
std::shared_ptr<file::Directory> LocalFileServer::resolveDirectory(const shared_ptr<VirtualServer> &server, std::shared_ptr<BasicChannel> channel, std::string subPath) {
fs::path path = fs::u8path("server_" + to_string(server ? server->getServerId() : 0) + "/channel_" + to_string(channel->channelId()));
if(!findFile(path))
this->createDirectory(path.string(), nullptr);
path += subPath;
auto ffile = findFile(path.string());
debugMessage(LOG_FT, "Resolve {} => {} -> {}", path.string(), (void*) ffile.get(), typeid(ffile).name());
return static_pointer_cast<file::Directory>(ffile);
}
std::shared_ptr<file::Directory> LocalFileServer::iconDirectory(const shared_ptr<VirtualServer> &server) {
fs::path root = fs::u8path(this->rootPath);
fs::path path = fs::u8path("server_" + to_string(server ? server->getServerId() : 0) + "/icons");
std::error_code code{};
if(!fs::exists(root / path, code)) {
if(!fs::create_directories(root / path, code) || code)
return nullptr;
}
return static_pointer_cast<file::Directory>(findFile(path.string()));
}
bool LocalFileServer::iconExists(const shared_ptr<VirtualServer> &server, IconId icon) {
if(icon == 0) return false;
if(icon < 1000) return true;
return this->findFile("icon_" + to_string(icon), this->iconDirectory(server)) != nullptr;
}
std::shared_ptr<file::Directory> LocalFileServer::avatarDirectory(const shared_ptr<VirtualServer> &server) {
fs::path path = fs::u8path("server_" + to_string(server ? server->getServerId() : 0) + "/avatars");
if(!findFile(path))
this->createDirectory(path.string(), nullptr);
return static_pointer_cast<file::Directory>(findFile(path.string()));
}
#define TS3_ICON_HASH "icon_1001"
void LocalFileServer::setupServer(const shared_ptr<VirtualServer> &server) {
auto dir = iconDirectory(server);
if(!dir) {
logError(LOG_FT,"Failed to find icon directory for server {}", server ? server->getServerId() : 0);
} else {
std::error_code code{};
if(!fs::exists(fs::u8path(dir->path + "/" + dir->name + "/" TS3_ICON_HASH), code)) {
try {
fs::copy(fs::u8path("resources/teaspeak16px.png"), fs::u8path(dir->path + "/" + dir->name + "/" + TS3_ICON_HASH), code);
} catch(std::exception& ex) {
logError(LOG_FT, "Failed to copy default path: {}", ex.what());
}
}
}
}
std::string LocalFileServer::server_file_base(const std::shared_ptr<ts::server::VirtualServer> &server) {
return rootPath + "/server_" + to_string(server->getServerId());
}
void LocalFileServer::deleteServer(const shared_ptr<VirtualServer> &server) {
fs::path path = fs::u8path(rootPath + "/server_" + to_string(server ? server->getServerId() : 0));
std::error_code code{};
if(fs::exists(path, code) && !code) {
if(fs::remove_all(path, code) == 0)
logError(LOG_FT, "Could not delete server directory {} ({} | {})", path.string(), code.value(), code.message());
} else {
logError(LOG_FT, "Could not delete missing server directory (" + path.string() + ")");
}
}
//The actual server!
bool LocalFileServer::start(const std::deque<std::shared_ptr<LocalFileServer::Binding>>& bindings, std::string& error) {
if(this->running()) {
error = "server already running";
return false;
}
this->active = true;
/* reserve backup file descriptor in case that the max file descriptors have been reached */
{
this->server_reserve_fd = dup(1);
if(this->server_reserve_fd < 0)
logWarning(LOG_FT, "Failed to reserve a backup accept file descriptor. ({} | {})", errno, strerror(errno));
}
/* setup event bases */
{
this->ioLoop = event_base_new();
this->ioThread = new threads::Thread(THREAD_SAVE_OPERATIONS | THREAD_EXECUTE_LATER, [&]{
while(this->active) {
debugMessage(LOG_FT, "Entering event loop ({})", (void*) this->ioLoop);
event_base_loop(this->ioLoop, EVLOOP_NO_EXIT_ON_EMPTY);
if(this->active) {
debugMessage(LOG_FT, "Event loop exited ({}). No active events. Sleeping 1 seconds", (void*) this->ioLoop);
this_thread::sleep_for(seconds(1));
} else {
debugMessage(LOG_FT, "Event loop exited ({})", (void*) this->ioLoop);
}
}
});
this->ioThread->name("File IO #1").execute();
}
{
for(auto& binding : bindings) {
binding->file_descriptor = socket(binding->address.ss_family, SOCK_STREAM | SOCK_NONBLOCK, 0);
if(binding->file_descriptor < 0) {
logError(LOG_FT, "Failed to bind server to {}. (Failed to create socket: {} | {})", binding->as_string(), 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->as_string(), 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->as_string(), 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->as_string(), 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->as_string(), 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->as_string(), errno, strerror(errno));
close(binding->file_descriptor);
continue;
}
if (listen(binding->file_descriptor, SOMAXCONN) < 0) {
logError(LOG_FT, "Failed to bind server to {}. (Failed to listen: {} | {})", binding->as_string(), errno, strerror(errno));
close(binding->file_descriptor);
continue;
}
binding->event_accept = event_new(this->ioLoop, binding->file_descriptor, EV_READ | EV_PERSIST, [](int a, short b, void* c){ ((LocalFileServer *) c)->on_client_accept(a, b, c); }, this);
event_add(binding->event_accept, nullptr);
this->bindings.push_back(binding);
}
if(this->bindings.empty()) {
this->stop();
error = "failed to bind to any address";
return false;
}
}
for(int index = 0; index < 2; index++){
auto th = new threads::Thread(THREAD_SAVE_OPERATIONS | THREAD_EXECUTE_LATER, &LocalFileServer::clientTickingExecutor, this);
th->name("Ticking FT #" + to_string(index)).execute();
this->tickingThreads.push_back(th);
}
return true;
}
void LocalFileServer::stop() {
if(!this->running()) return;
active = false;
{
lock_guard<mutex> lock(this->tickingLock);
this->tickingCon.notify_all();
}
for(auto& binding : this->bindings) {
if(binding->event_accept) {
event_del_block(binding->event_accept);
event_free(binding->event_accept);
binding->event_accept = nullptr;
}
if(binding->file_descriptor > 0) {
if(shutdown(binding->file_descriptor, SHUT_RDWR) < 0)
logWarning(LOG_FT, "Failed to shutdown socket for binding {} ({} | {}).", binding->as_string(), errno, strerror(errno));
if(close(binding->file_descriptor) < 0)
logError(LOG_FT, "Failed to close socket for binding {} ({} | {}).", binding->as_string(), errno, strerror(errno));
binding->file_descriptor = -1;
}
}
this->bindings.clear();
auto clClone = this->connectedClients;
for(const auto& cl : clClone) {
cl->disconnect(chrono::milliseconds(1));
}
if(this->ioLoop) {
event_base_loopbreak(this->ioLoop);
event_base_loopexit(this->ioLoop, nullptr);
}
for(const auto& thread : this->tickingThreads){
if(thread->join(chrono::seconds(1)) != 0) logCritical(LOG_FT, "Failed to terminal file server tick thread!");
delete thread;
}
this->tickingThreads.clear();
if(this->ioThread)
if(this->ioThread->join(chrono::system_clock::now() + chrono::seconds(3)) != 0) {
logCritical(LOG_FT, "Could not terminate the event base dispatch thread of the file server");
this->ioThread->detach();
}
delete this->ioThread;
this->ioThread = nullptr;
if(this->ioLoop) {
event_base_free(this->ioLoop);
this->ioLoop = nullptr;
}
if(this->server_reserve_fd > 0) {
if(close(this->server_reserve_fd) < 0)
logError(LOG_FT, "Failed to close backup file descriptor ({} | {})", errno, strerror(errno));
}
this->server_reserve_fd = -1;
}
inline std::string logging_address(const sockaddr_storage& address) {
if(config::server::disable_ip_saving)
return "[0|X.X.X.X" + to_string(net::port(address)) + "|unconnected]";
return "[0|X.X.X.X" + net::to_string(address, true) + "|unconnected]";
}
#define CLOSE_CONNECTION \
if(shutdown(file_descriptor, SHUT_RDWR) < 0) { \
debugMessage(LOG_FT, "[{}] Failed to shutdown socket ({} | {}).", logging_address(remote_address), errno, strerror(errno)); \
} \
if(close(file_descriptor) < 0) { \
debugMessage(LOG_FT, "[{}] Failed to close socket ({} | {}).", logging_address(remote_address), errno, strerror(errno)); \
}
void LocalFileServer::on_client_accept(int _server_file_descriptor, short ev, void *arg) {
sockaddr_storage remote_address{};
memset(&remote_address, 0, sizeof(remote_address));
socklen_t address_length = sizeof(remote_address);
int file_descriptor = accept(_server_file_descriptor, (struct sockaddr *) &remote_address, &address_length);
if (file_descriptor < 0) {
if(errno == EAGAIN)
return;
if(errno == EMFILE || errno == ENFILE) {
if(errno == EMFILE)
logError(LOG_FT, "Server ran out file descriptors. Please increase the process file descriptor limit or decrease the instance variable 'serverinstance_filetransfer_max_connections'");
else
logError(LOG_FT, "Server ran out file descriptors. Please increase the process and system-wide file descriptor limit or decrease the instance variable 'serverinstance_filetransfer_max_connections'");
bool tmp_close_success = false;
{
lock_guard reserve_fd_lock(server_reserve_fd_lock);
if(this->server_reserve_fd > 0) {
debugMessage(LOG_FT, "Trying to accept client with the reserved file descriptor to close the incomming connection.");
auto _ = [&]{
if(close(this->server_reserve_fd) < 0) {
debugMessage(LOG_FT, "Failed to close reserved file descriptor");
tmp_close_success = false;
return;
}
this->server_reserve_fd = 0;
errno = 0;
file_descriptor = accept(_server_file_descriptor, (struct sockaddr *) &remote_address, &address_length);
if(file_descriptor < 0) {
if(errno == EMFILE || errno == ENFILE)
debugMessage(LOG_FT, "[{}] Even with freeing the reserved descriptor accept failed. Attempting to reclaim reserved file descriptor", logging_address(remote_address));
else if(errno == EAGAIN);
else {
debugMessage(LOG_FT, "[{}] Failed to accept client with reserved file descriptor. ({} | {})", logging_address(remote_address), errno, strerror(errno));
}
this->server_reserve_fd = dup(1);
if(this->server_reserve_fd < 0)
debugMessage(LOG_FT, "[{}] Failed to reclaim reserved file descriptor. Future clients cant be accepted!", logging_address(remote_address));
else
tmp_close_success = true;
return;
}
debugMessage(LOG_FT, "[{}] Successfully accepted client via reserved descriptor (fd: {}). Disconnecting client.", logging_address(remote_address), file_descriptor);
CLOSE_CONNECTION
this->server_reserve_fd = dup(1);
if(this->server_reserve_fd < 0)
debugMessage(LOG_FT, "Failed to reclaim reserved file descriptor. Future clients cant be accepted!");
else
tmp_close_success = true;
logMessage(LOG_FT, "[{}] Dropping file transfer connection attempt because of too many open file descriptors.", logging_address(remote_address));
};
_();
}
}
if(!tmp_close_success) {
debugMessage(LOG_FT, "Sleeping two seconds because we're currently having no resources for this user. (Removing the accept event)");
for(auto& binding : this->bindings)
event_del_noblock(binding->event_accept);
accept_event_deleted = system_clock::now();
return;
}
return;
}
logMessage(LOG_FT, "Got an error while accepting a new client. (errno: {}, message: {})", errno, strerror(errno));
return;
}
{
unique_lock lock(this->clientLock);
auto max_connections = serverInstance->properties()[property::SERVERINSTANCE_FILETRANSFER_MAX_CONNECTIONS].as<size_t>();
if(max_connections > 0 && max_connections <= this->connectedClients.size()) {
lock.unlock();
logMessage(LOG_FT, "[{}] Dropping new connection attempt because of too many connected clients.", logging_address(remote_address));
CLOSE_CONNECTION
return;
}
auto max_ip_connections = serverInstance->properties()[property::SERVERINSTANCE_FILETRANSFER_MAX_CONNECTIONS_PER_IP].as<size_t>();
if(max_ip_connections > 0) {
size_t connection_count = 0;
for(auto& client : this->connectedClients) {
if(net::address_equal(client->remote_address, remote_address))
connection_count++;
}
if(connection_count >= max_ip_connections) {
lock.unlock();
logMessage(LOG_FT, "[{}] Dropping new connection attempt because of too many simultaneously connected session from this ip.", logging_address(remote_address));
CLOSE_CONNECTION
return;
}
}
}
shared_ptr<FileClient> client = std::make_shared<FileClient>(this, file_descriptor);
client->_this = client;
memcpy(&client->remote_address, &remote_address, sizeof(remote_address));
this->clientLock.lock();
this->connectedClients.push_back(client);
this->clientLock.unlock();
event_add(client->readEvent, nullptr);
logMessage(LOG_FT, "[{}] Remote peer connected. Initializing session.", logging_address(remote_address));
}
void LocalFileServer::clientTickingExecutor() {
while(this->running()){
shared_ptr<FileClient> client;
{
unique_lock<mutex> lock(this->tickingLock);
this->tickingCon.wait(lock, [&](){ return !this->running() || !this->tickQueue.empty(); });
if(!this->running()) return;
client = this->tickQueue.front();
this->tickQueue.pop_front();
}
if(client->tick()) this->tickFileClient(client); //Client needs more ticking
}
}
std::deque<std::shared_ptr<FileClient>> LocalFileServer::running_file_transfers(const std::shared_ptr<ts::server::ConnectedClient> &client) {
std::deque<std::shared_ptr<FileClient>> result;
{
threads::MutexLock lock(this->clientLock);
for(const auto& c : this->connectedClients) {
if(!client || c->client == client) result.push_back(c);
}
}
return result;
}
std::deque<std::shared_ptr<file::FileTransfereKey>> LocalFileServer::pending_file_transfers(const std::shared_ptr<ts::server::ConnectedClient> &client) {
std::deque<std::shared_ptr<file::FileTransfereKey>> result;
for(const auto& key : this->pending_keys())
if(!client || key->owner.lock() == client)
result.push_back(key);
return result;
}
void LocalFileServer::tickFileClient(std::shared_ptr<FileClient> cl) {
lock_guard<mutex> lock(this->tickingLock);
this->tickQueue.push_back(cl);
this->tickingCon.notify_one();
}
struct TransfareGroup {
shared_ptr<VirtualServer> server;
deque<shared_ptr<FileClient>> clients;
enum {
upload,
download
} direction = upload;
size_t used_bandwidth = 0;
ssize_t max_bandwidth = -1;
};
void LocalFileServer::instanceTick() {
{
//tickQueue
auto client = this->connected_clients();
lock_guard<mutex> tick_lock(this->tickingLock);
this->tickQueue.insert(this->tickQueue.end(), client.begin(), client.end()); //Tick all clients :)
this->tickingCon.notify_all();
}
if(this->accept_event_deleted.time_since_epoch().count() != 0 && accept_event_deleted + seconds(5) < system_clock::now()) {
debugMessage(LOG_FT, "Readding accept event and try again if we have enough resources again.");
for(auto& binding : this->bindings)
event_add(binding->event_accept, nullptr);
accept_event_deleted = system_clock::time_point{};
}
auto now = system_clock::now();
if(timestamp_bandwidth_update + seconds(1) < now) {
timestamp_bandwidth_update = now;
for(const auto& entry : this->pending_keys()) {
if(!entry) continue;
if(entry->createTimestamp + minutes(1) < now) {
logMessage(entry->server, "[FILE] Timeout file transfer for file " + entry->targetFile + "! Dropping key");
{
threads::MutexLock lock(this->keylock);
auto index = find(this->pendingKeys.begin(), this->pendingKeys.end(), entry);
if(index != this->pendingKeys.end())
this->pendingKeys.erase(index);
}
}
}
deque<shared_ptr<TransfareGroup>> groups;
{
for(const auto& client : this->connected_clients()) {
if(!client || !client->client) continue;
lock_guard<threads::Mutex> l(client->tickLock); //Instance hangup here
if(!client->pendingKey) continue;
auto used_band = client->used_bandwidth();
if(client->pendingKey && client->state_connection != FileClient::C_DISCONNECTED && (client->pendingKey->max_bandwhidth < 0 || used_band < client->pendingKey->max_bandwhidth)) {
if(client->event_read_hold) { //Reactivate read!
event_add(client->readEvent, nullptr);
client->event_read_hold = false;
debugMessage(LOG_FT, "{} Reattaching client read event! (Current bandwidth {})", client->client_prefix(), used_band);
}
if(client->event_write_hold) { //Reactivate read!
event_add(client->writeEvent, nullptr);
client->event_write_hold = false;
debugMessage(LOG_FT, "{} Reattaching client write event! (Current bandwidth {})", client->client_prefix(), used_band);
}
}
if(!client->client->getServer()) continue;
bool found = false;
for(auto& group : groups) {
if (group->server == client->client->getServer() && (group->direction == TransfareGroup::upload) == client->pendingKey->upload) {
group->clients.push_back(client);
group->used_bandwidth += client->used_bandwidth();
found = true;
break;
}
}
if(found) continue;
auto entry = make_shared<TransfareGroup>();
entry->direction = client->pendingKey->upload ? TransfareGroup::upload : TransfareGroup::download;
entry->used_bandwidth = client->used_bandwidth();
entry->clients.push_back(client);
entry->server = client->client->getServer();
groups.push_back(entry);
}
}
{ //Instance limit upload
auto limit_upload = serverInstance->properties()[property::SERVERINSTANCE_MAX_UPLOAD_TOTAL_BANDWIDTH].as<ssize_t>();
if(limit_upload >= 0) {
deque<shared_ptr<TransfareGroup>> elements;
size_t used_bandwidth = 0;
for(const auto& entry : groups) {
if (entry->direction == TransfareGroup::upload) {
elements.push_back(entry);
used_bandwidth += entry->used_bandwidth;
}
}
if(!elements.empty() && used_bandwidth > limit_upload) {
for(auto& entry : elements) {
entry->max_bandwidth = ssize_t(ceil(double(entry->used_bandwidth) * double(limit_upload) / double(used_bandwidth))); //Adjust the total
}
}
}
}
{ //Instance limit download
auto limit_download = serverInstance->properties()[property::SERVERINSTANCE_MAX_DOWNLOAD_TOTAL_BANDWIDTH].as<ssize_t>();
if(limit_download >= 0) {
deque<shared_ptr<TransfareGroup>> elements;
size_t used_bandwidth = 0;
for(const auto& entry : groups) {
if (entry->direction == TransfareGroup::download) {
elements.push_back(entry);
used_bandwidth += entry->used_bandwidth;
}
}
if(!elements.empty() && used_bandwidth > limit_download) {
for(auto& entry : elements) {
entry->max_bandwidth = ssize_t(ceil(double(entry->used_bandwidth) * double(limit_download) / double(used_bandwidth))); //Adjust the total
}
}
}
}
map<ServerId, deque<shared_ptr<TransfareGroup>>> servers;
for(const auto& entry : groups) {
servers[entry->server->getServerId()].push_back(entry);
}
for(const auto& server : servers) {
if(server.second.empty()) continue;
{ //Adjust server upload
auto max_bandwidth = server.second.front()->server->properties()[property::VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH].as<ssize_t>();
if(max_bandwidth >= 0) {
deque<shared_ptr<TransfareGroup>> tranfares;
size_t bandwidth = 0;
for(const auto& trans : server.second) {
if(trans->direction == TransfareGroup::upload) {
tranfares.push_back(trans);
bandwidth += trans->max_bandwidth;
}
}
if(bandwidth > max_bandwidth) {
for(auto& trans : tranfares)
trans->max_bandwidth = ssize_t(ceil(double(trans->max_bandwidth) * double(max_bandwidth) / double(bandwidth))); //Adjust the total
}
}
}
{ //Adjust server download
auto max_bandwidth = server.second.front()->server->properties()[property::VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH].as<ssize_t>();
if(max_bandwidth >= 0) {
deque<shared_ptr<TransfareGroup>> tranfares;
size_t bandwidth = 0;
for(const auto& trans : server.second)
if(trans->direction == TransfareGroup::download) {
tranfares.push_back(trans);
bandwidth += trans->max_bandwidth;
}
if(bandwidth > max_bandwidth) {
for(auto& trans : tranfares)
trans->max_bandwidth = ssize_t(ceil(double(trans->max_bandwidth) * double(max_bandwidth) / double(bandwidth))); //Adjust the total
}
}
}
}
}
}
-163
View File
@@ -1,163 +0,0 @@
#pragma once
#include <vector>
#include <string>
#include <chrono>
#include <memory>
#include <ThreadPool/Thread.h>
#include <deque>
#include <ThreadPool/Mutex.h>
#include <netinet/in.h>
#include <event.h>
#include <condition_variable>
#include "Variable.h"
#include <Definitions.h>
#include <misc/net.h>
namespace ts {
namespace file {
namespace FileType {
enum FileType {
DIRECTORY,
FILE
};
}
}
}
DEFINE_VARIABLE_TRANSFORM(ts::file::FileType::FileType, VARTYPE_INT, std::to_string((uint8_t) in), static_cast<ts::file::FileType::FileType>(in.as<uint8_t>()));
namespace ts {
class BasicChannel;
namespace server {
class ConnectedClient;
}
namespace file {
struct FileEntry {
FileType::FileType type;
std::string path;
std::string name;
std::chrono::time_point<std::chrono::system_clock> lastChanged;
};
struct File : public FileEntry {
int64_t fileSize;
};
struct Directory : public FileEntry { };
struct SymLink : public FileEntry {
std::string targetPath;
std::string targetName;
};
struct FileTransfereKey {
uint16_t key_id = 0;
ServerId server;
std::weak_ptr<server::ConnectedClient> owner;
std::chrono::time_point<std::chrono::system_clock> createTimestamp;
std::string key;
std::string targetFile; //Relative
int64_t size;
size_t offset;
bool upload = false;
int64_t max_bandwhidth = -1; //Get calculated each time
int64_t max_server_bandwidth = -1;
};
}
namespace server {
class VirtualServer;
class FileClient;
//FIXME Valid path
class LocalFileServer {
friend class FileClient;
public:
struct Binding {
sockaddr_storage address{};
int file_descriptor = 0;
::event* event_accept = nullptr;
inline std::string as_string() { return net::to_string(address, true); }
};
LocalFileServer();
~LocalFileServer();
bool start(const std::deque<std::shared_ptr<Binding>>& /* bindings */, std::string& /* error */);
void stop();
ts_always_inline bool running(){ return active; }
ts_always_inline std::deque<std::shared_ptr<Binding>> list_bindings() { return this->bindings; }
std::shared_ptr<file::Directory> createDirectory(std::string name, std::shared_ptr<file::Directory> parent);
bool fileExists(std::shared_ptr<file::Directory>);
bool fileExists(std::shared_ptr<file::File>);
std::shared_ptr<file::FileEntry> findFile(std::string, std::shared_ptr<file::Directory> = nullptr);
std::vector<std::shared_ptr<file::FileEntry>> listFiles(std::shared_ptr<file::Directory> directory = nullptr);
bool deleteFile(std::shared_ptr<file::FileEntry>);
std::shared_ptr<file::FileTransfereKey> generateDownloadTransferKey(std::string &errorMessage, std::string targetFile, size_t offset, const std::shared_ptr<ConnectedClient>&);
std::shared_ptr<file::FileTransfereKey> generateUploadTransferKey(std::string &errorMessage, std::string targetFile, size_t size, size_t offset, const std::shared_ptr<ConnectedClient>&);
std::shared_ptr<file::Directory> resolveDirectory(const std::shared_ptr<VirtualServer> &, std::shared_ptr<BasicChannel>, std::string = "");
std::shared_ptr<file::Directory> iconDirectory(const std::shared_ptr<VirtualServer> &);
bool iconExists(const std::shared_ptr<VirtualServer> &, IconId);
std::shared_ptr<file::Directory> avatarDirectory(const std::shared_ptr<VirtualServer> &);
std::string server_file_base(const std::shared_ptr<VirtualServer> &);
void setupServer(const std::shared_ptr<VirtualServer> &);
void deleteServer(const std::shared_ptr<VirtualServer> &);
void tickFileClient(std::shared_ptr<FileClient>);
void instanceTick();
std::deque<std::shared_ptr<FileClient>> running_file_transfers(const std::shared_ptr<ConnectedClient> & /* client */ = nullptr);
std::deque<std::shared_ptr<file::FileTransfereKey>> pending_file_transfers(const std::shared_ptr<ConnectedClient> & /* client */ = nullptr);
private:
bool active = false;
std::deque<std::shared_ptr<Binding>> bindings;
std::string rootPath = "./files/";
//IO stuff
event_base* ioLoop = nullptr;
std::chrono::system_clock::time_point accept_event_deleted;
std::mutex server_reserve_fd_lock;
int server_reserve_fd = -1; /* -1 = unset | 0 = in use | > 0 ready to use */
threads::Mutex clientLock;
std::deque<std::shared_ptr<FileClient>> connectedClients;
inline std::deque<std::shared_ptr<FileClient>> connected_clients(){
threads::MutexLock lock(clientLock);
return connectedClients;
}
threads::Thread* ioThread = nullptr;
void on_client_accept(int fd, short ev, void *arg);
std::deque<std::shared_ptr<FileClient>> tickQueue;
std::deque<threads::Thread*> tickingThreads;
std::mutex tickingLock;
std::condition_variable tickingCon;
std::chrono::system_clock::time_point timestamp_bandwidth_update;
//file management
threads::Mutex keylock;
std::deque<std::shared_ptr<file::FileTransfereKey>> pendingKeys;
inline std::deque<std::shared_ptr<file::FileTransfereKey>> pending_keys(){
threads::MutexLock lock(keylock);
return pendingKeys;
}
void clientTickingExecutor();
};
}
}