2020-05-07 15:28:15 -04:00
//
// Created by WolverinDEV on 04/05/2020.
//
# include <cassert>
# include <event2/event.h>
# include <log/LogUtils.h>
# include <random>
# include "./LocalFileProvider.h"
2020-05-13 05:32:08 -04:00
# include <experimental/filesystem>
2020-05-07 15:28:15 -04:00
2020-05-13 05:32:08 -04:00
namespace fs = std : : experimental : : filesystem ;
2020-05-07 15:28:15 -04:00
using namespace ts : : server : : file ;
using namespace ts : : server : : file : : transfer ;
Buffer * transfer : : allocate_buffer ( size_t size ) {
auto total_size = sizeof ( Buffer ) + size ;
auto buffer = ( Buffer * ) malloc ( total_size ) ;
new ( buffer ) Buffer { } ;
2020-05-10 10:23:02 -04:00
buffer - > capacity = size ;
2020-05-07 15:28:15 -04:00
return buffer ;
}
void transfer : : free_buffer ( Buffer * buffer ) {
buffer - > ~ Buffer ( ) ;
free ( buffer ) ;
}
FileClient : : ~ FileClient ( ) {
2020-06-11 07:08:45 -04:00
this - > flush_network_buffer ( ) ;
this - > flush_disk_buffer ( ) ;
assert ( ! this - > disk_buffer . buffer_head ) ;
assert ( ! this - > network_buffer . buffer_head ) ;
2020-05-07 15:28:15 -04:00
assert ( ! this - > file . file_descriptor ) ;
assert ( ! this - > file . currently_processing ) ;
assert ( ! this - > file . next_client ) ;
assert ( ! this - > networking . event_read ) ;
assert ( ! this - > networking . event_write ) ;
assert ( this - > state = = STATE_DISCONNECTED ) ;
2020-06-12 19:08:49 -04:00
memtrack : : freed < FileClient > ( this ) ;
2020-05-07 15:28:15 -04:00
}
LocalFileTransfer : : LocalFileTransfer ( filesystem : : LocalFileSystem & fs ) : file_system_ { fs } { }
LocalFileTransfer : : ~ LocalFileTransfer ( ) = default ;
2020-06-12 19:08:49 -04:00
bool LocalFileTransfer : : start ( ) {
2020-05-07 15:28:15 -04:00
( void ) this - > start_client_worker ( ) ;
{
auto start_result = this - > start_disk_io ( ) ;
switch ( start_result ) {
case DiskIOStartResult : : SUCCESS :
break ;
case DiskIOStartResult : : OUT_OF_MEMORY :
logError ( LOG_FT , " Failed to start disk worker (Out of memory) " ) ;
goto error_exit_disk ;
default :
logError ( LOG_FT , " Failed to start disk worker ({}) " , ( int ) start_result ) ;
goto error_exit_disk ;
}
}
{
auto start_result = this - > start_networking ( ) ;
switch ( start_result ) {
case NetworkingStartResult : : SUCCESS :
break ;
2020-06-12 19:08:49 -04:00
2020-05-07 15:28:15 -04:00
case NetworkingStartResult : : OUT_OF_MEMORY :
logError ( LOG_FT , " Failed to start networking (Out of memory) " ) ;
goto error_exit_network ;
2020-06-12 19:08:49 -04:00
2020-05-07 15:28:15 -04:00
default :
logError ( LOG_FT , " Failed to start networking ({}) " , ( int ) start_result ) ;
goto error_exit_network ;
}
}
return true ;
error_exit_network :
this - > shutdown_networking ( ) ;
error_exit_disk :
this - > shutdown_disk_io ( ) ;
this - > shutdown_client_worker ( ) ;
return false ;
}
void LocalFileTransfer : : stop ( ) {
this - > shutdown_networking ( ) ;
this - > shutdown_disk_io ( ) ;
this - > shutdown_client_worker ( ) ;
}
2020-05-13 05:32:08 -04:00
std : : shared_ptr < ExecuteResponse < TransferInitError , std : : shared_ptr < Transfer > > >
LocalFileTransfer : : initialize_icon_transfer ( Transfer : : Direction direction , const std : : shared_ptr < VirtualFileServer > & server , const TransferInfo & info ) {
return this - > initialize_transfer ( direction , server , 0 , Transfer : : TARGET_TYPE_ICON , info ) ;
2020-05-07 15:28:15 -04:00
}
2020-05-13 05:32:08 -04:00
std : : shared_ptr < ExecuteResponse < TransferInitError , std : : shared_ptr < Transfer > > >
LocalFileTransfer : : initialize_avatar_transfer ( Transfer : : Direction direction , const std : : shared_ptr < VirtualFileServer > & server , const TransferInfo & info ) {
return this - > initialize_transfer ( direction , server , 0 , Transfer : : TARGET_TYPE_AVATAR , info ) ;
2020-05-07 15:28:15 -04:00
}
2020-05-13 05:32:08 -04:00
std : : shared_ptr < ExecuteResponse < TransferInitError , std : : shared_ptr < Transfer > > >
LocalFileTransfer : : initialize_channel_transfer ( Transfer : : Direction direction , const std : : shared_ptr < VirtualFileServer > & server , ChannelId cid , const TransferInfo & info ) {
return this - > initialize_transfer ( direction , server , cid , Transfer : : TARGET_TYPE_CHANNEL_FILE , info ) ;
2020-05-07 15:28:15 -04:00
}
std : : shared_ptr < ExecuteResponse < TransferInitError , std : : shared_ptr < Transfer > > > LocalFileTransfer : : initialize_transfer (
2020-05-13 05:32:08 -04:00
Transfer : : Direction direction , const std : : shared_ptr < VirtualFileServer > & server , ChannelId cid ,
2020-05-07 15:28:15 -04:00
Transfer : : TargetType ttype ,
const TransferInfo & info ) {
auto response = this - > create_execute_response < TransferInitError , std : : shared_ptr < Transfer > > ( ) ;
2020-05-13 05:32:08 -04:00
std : : lock_guard clock { this - > transfer_create_mutex } ;
if ( info . max_concurrent_transfers > 0 ) {
std : : unique_lock tlock { this - > transfers_mutex } ;
{
auto transfers = std : : count_if ( this - > transfers_ . begin ( ) , this - > transfers_ . end ( ) , [ & ] ( const std : : shared_ptr < FileClient > & client ) {
2020-06-10 12:13:14 -04:00
return client - > transfer & & client - > transfer - > client_unique_id = = info . client_unique_id & & client - > state < FileClient : : STATE_DISCONNECTING ;
2020-05-13 05:32:08 -04:00
} ) ;
transfers + = std : : count_if ( this - > pending_transfers . begin ( ) , this - > pending_transfers . end ( ) , [ & ] ( const std : : shared_ptr < Transfer > & transfer ) {
return transfer - > client_unique_id = = info . client_unique_id ;
} ) ;
if ( transfers > = info . max_concurrent_transfers ) {
response - > emplace_fail ( TransferInitError : : CLIENT_TOO_MANY_TRANSFERS , std : : to_string ( transfers ) ) ;
return response ;
}
}
{
auto server_transfers = this - > pending_transfers . size ( ) ;
server_transfers + = std : : count_if ( this - > transfers_ . begin ( ) , this - > transfers_ . end ( ) , [ & ] ( const std : : shared_ptr < FileClient > & client ) {
return client - > transfer ;
} ) ;
if ( server_transfers > = this - > max_concurrent_transfers ) {
response - > emplace_fail ( TransferInitError : : SERVER_TOO_MANY_TRANSFERS , std : : to_string ( server_transfers ) ) ;
return response ;
}
}
}
2020-05-07 15:28:15 -04:00
auto transfer = std : : make_shared < Transfer > ( ) ;
2020-05-13 05:32:08 -04:00
transfer - > server_transfer_id = server - > generate_transfer_id ( ) ;
transfer - > server = server ;
2020-05-07 15:28:15 -04:00
transfer - > channel_id = cid ;
transfer - > target_type = ttype ;
transfer - > direction = direction ;
transfer - > client_id = 0 ; /* must be provided externally */
transfer - > client_transfer_id = 0 ; /* must be provided externally */
transfer - > server_addresses . reserve ( this - > network . bindings . size ( ) ) ;
for ( auto & binding : this - > network . bindings ) {
if ( ! binding - > file_descriptor ) continue ;
transfer - > server_addresses . emplace_back ( Transfer : : Address { binding - > hostname , net : : port ( binding - > address ) } ) ;
}
transfer - > target_file_path = info . file_path ;
transfer - > file_offset = info . file_offset ;
transfer - > expected_file_size = info . expected_file_size ;
transfer - > max_bandwidth = info . max_bandwidth ;
2020-05-13 05:32:08 -04:00
transfer - > client_unique_id = info . client_unique_id ;
transfer - > client_id = info . client_id ;
2020-05-07 15:28:15 -04:00
constexpr static std : : string_view kTokenCharacters { " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 " } ;
for ( auto & c : transfer - > transfer_key )
c = kTokenCharacters [ transfer_random_token_generator ( ) % kTokenCharacters . length ( ) ] ;
transfer - > transfer_key [ 0 ] = ( char ) ' r ' ; /* (114) */ /* a non valid SSL header type to indicate that we're using a file transfer key and not doing a SSL handshake */
transfer - > transfer_key [ 1 ] = ( char ) ' a ' ; /* ( 97) */
transfer - > transfer_key [ 2 ] = ( char ) ' w ' ; /* (119) */
transfer - > initialized_timestamp = std : : chrono : : system_clock : : now ( ) ;
2020-05-13 05:32:08 -04:00
{
std : : string absolute_path { } ;
switch ( transfer - > target_type ) {
case Transfer : : TARGET_TYPE_AVATAR :
absolute_path = this - > file_system_ . absolute_avatar_path ( transfer - > server , transfer - > target_file_path ) ;
break ;
case Transfer : : TARGET_TYPE_ICON :
absolute_path = this - > file_system_ . absolute_icon_path ( transfer - > server , transfer - > target_file_path ) ;
break ;
case Transfer : : TARGET_TYPE_CHANNEL_FILE :
absolute_path = this - > file_system_ . absolute_channel_path ( transfer - > server , transfer - > channel_id , transfer - > target_file_path ) ;
break ;
case Transfer : : TARGET_TYPE_UNKNOWN :
default :
response - > emplace_fail ( TransferInitError : : INVALID_FILE_TYPE , " " ) ;
return response ;
}
transfer - > absolute_file_path = absolute_path ;
const auto root_path_length = this - > file_system_ . root_path ( ) . size ( ) ;
if ( root_path_length < absolute_path . size ( ) )
transfer - > relative_file_path = absolute_path . substr ( root_path_length ) ;
else
transfer - > relative_file_path = " error " ;
transfer - > file_name = fs : : u8path ( absolute_path ) . filename ( ) ;
}
if ( direction = = Transfer : : DIRECTION_DOWNLOAD ) {
auto path = fs : : u8path ( transfer - > absolute_file_path ) ;
std : : error_code error { } ;
if ( ! fs : : exists ( path , error ) ) {
response - > emplace_fail ( TransferInitError : : FILE_DOES_NOT_EXISTS , " " ) ;
return response ;
} else if ( error ) {
logWarning ( LOG_FT , " Failed to check for file at {}: {}. Assuming it does not exists. " , transfer - > absolute_file_path , error . value ( ) , error . message ( ) ) ;
response - > emplace_fail ( TransferInitError : : FILE_DOES_NOT_EXISTS , " " ) ;
return response ;
}
auto status = fs : : status ( path , error ) ;
if ( error ) {
logWarning ( LOG_FT , " Failed to status for file at {}: {}. Ignoring file transfer. " , transfer - > absolute_file_path , error . value ( ) , error . message ( ) ) ;
response - > emplace_fail ( TransferInitError : : IO_ERROR , " stat " ) ;
return response ;
}
if ( status . type ( ) ! = fs : : file_type : : regular ) {
response - > emplace_fail ( TransferInitError : : FILE_IS_NOT_A_FILE , " " ) ;
return response ;
}
transfer - > expected_file_size = fs : : file_size ( path , error ) ;
if ( error ) {
logWarning ( LOG_FT , " Failed to get file size for file at {}: {}. Ignoring file transfer. " , transfer - > absolute_file_path , error . value ( ) , error . message ( ) ) ;
response - > emplace_fail ( TransferInitError : : IO_ERROR , " file_size " ) ;
return response ;
}
2020-05-14 09:08:28 -04:00
if ( info . download_client_quota_limit > 0 & & info . download_client_quota_limit < = transfer - > expected_file_size ) {
response - > emplace_fail ( TransferInitError : : CLIENT_QUOTA_EXCEEDED , " " ) ;
return response ;
}
if ( info . download_server_quota_limit > 0 & & info . download_server_quota_limit < = transfer - > expected_file_size ) {
response - > emplace_fail ( TransferInitError : : SERVER_QUOTA_EXCEEDED , " " ) ;
return response ;
}
2020-05-13 05:32:08 -04:00
}
2020-05-07 15:28:15 -04:00
{
std : : lock_guard tlock { this - > transfers_mutex } ;
this - > pending_transfers . push_back ( transfer ) ;
}
2020-06-10 12:13:14 -04:00
switch ( transfer - > target_type ) {
case Transfer : : TARGET_TYPE_AVATAR :
logMessage ( LOG_FT , " Initialized avatar transfer for avatar \" {} \" ({} bytes, transferring {} bytes). " , transfer - > target_file_path , transfer - > expected_file_size , transfer - > expected_file_size - transfer - > file_offset ) ;
break ;
case Transfer : : TARGET_TYPE_ICON :
logMessage ( LOG_FT , " Initialized icon transfer for icon \" {} \" ({} bytes, transferring {} bytes). " ,
transfer - > target_file_path , transfer - > expected_file_size , transfer - > expected_file_size - transfer - > file_offset ) ;
break ;
case Transfer : : TARGET_TYPE_CHANNEL_FILE :
logMessage ( LOG_FT , " Initialized channel transfer for file \" {}/{} \" ({} bytes, transferring {} bytes). " ,
transfer - > channel_id , transfer - > target_file_path , transfer - > expected_file_size , transfer - > expected_file_size - transfer - > file_offset ) ;
break ;
case Transfer : : TARGET_TYPE_UNKNOWN :
default :
response - > emplace_fail ( TransferInitError : : INVALID_FILE_TYPE , " " ) ;
return response ;
}
2020-05-07 15:28:15 -04:00
if ( auto callback { this - > callback_transfer_registered } ; callback )
callback ( transfer ) ;
response - > emplace_success ( std : : move ( transfer ) ) ;
return response ;
}
2020-05-13 05:32:08 -04:00
std : : shared_ptr < ExecuteResponse < TransferActionError > > LocalFileTransfer : : stop_transfer ( const std : : shared_ptr < VirtualFileServer > & server , transfer_id id , bool flush ) {
2020-05-07 15:28:15 -04:00
auto response = this - > create_execute_response < TransferActionError > ( ) ;
std : : shared_ptr < Transfer > transfer { } ;
std : : shared_ptr < FileClient > connected_transfer { } ;
{
std : : lock_guard tlock { this - > transfers_mutex } ;
auto ct_it = std : : find_if ( this - > transfers_ . begin ( ) , this - > transfers_ . end ( ) , [ & ] ( const std : : shared_ptr < FileClient > & t ) {
2020-05-13 05:32:08 -04:00
return t - > transfer & & t - > transfer - > server_transfer_id = = id & & t - > transfer - > server = = server ;
2020-05-07 15:28:15 -04:00
} ) ;
if ( ct_it ! = this - > transfers_ . end ( ) )
connected_transfer = * ct_it ;
else {
auto t_it = std : : find_if ( this - > pending_transfers . begin ( ) , this - > pending_transfers . end ( ) , [ & ] ( const std : : shared_ptr < Transfer > & t ) {
2020-05-13 05:32:08 -04:00
return t - > server_transfer_id = = id & & t - > server = = server ;
2020-05-07 15:28:15 -04:00
} ) ;
if ( t_it ! = this - > pending_transfers . end ( ) ) {
transfer = * t_it ;
this - > pending_transfers . erase ( t_it ) ;
}
}
}
if ( ! transfer ) {
if ( connected_transfer )
transfer = connected_transfer - > transfer ;
else {
response - > emplace_fail ( TransferActionError { TransferActionError : : UNKNOWN_TRANSFER , " " } ) ;
return response ;
}
}
if ( connected_transfer ) {
2020-05-14 09:08:28 -04:00
this - > invoke_aborted_callback ( connected_transfer , { TransferError : : USER_REQUEST , " " } ) ;
2020-05-07 15:28:15 -04:00
logMessage ( LOG_FT , " {} Stopping transfer due to an user request. " , connected_transfer - > log_prefix ( ) ) ;
std : : unique_lock slock { connected_transfer - > state_mutex } ;
this - > disconnect_client ( connected_transfer , slock , flush ) ;
} else {
2020-05-14 09:08:28 -04:00
this - > invoke_aborted_callback ( transfer , { TransferError : : USER_REQUEST , " " } ) ;
2020-05-07 15:28:15 -04:00
logMessage ( LOG_FT , " Removing pending file transfer for id {} " , id ) ;
}
response - > emplace_success ( ) ;
return response ;
2020-05-13 05:32:08 -04:00
}
inline void apply_transfer_info ( const std : : shared_ptr < Transfer > & transfer , ActiveFileTransfer & info ) {
info . server_transfer_id = transfer - > server_transfer_id ;
info . client_transfer_id = transfer - > client_transfer_id ;
info . direction = transfer - > direction ;
info . client_id = transfer - > client_id ;
info . client_unique_id = transfer - > client_unique_id ;
info . file_path = transfer - > relative_file_path ;
info . file_name = transfer - > file_name ;
info . expected_size = transfer - > expected_file_size ;
}
std : : shared_ptr < ExecuteResponse < TransferListError , std : : vector < ActiveFileTransfer > > > LocalFileTransfer : : list_transfer ( ) {
std : : vector < ActiveFileTransfer > transfer_infos { } ;
auto response = this - > create_execute_response < TransferListError , std : : vector < ActiveFileTransfer > > ( ) ;
std : : unique_lock tlock { this - > transfers_mutex } ;
auto awaiting_transfers = this - > pending_transfers ;
auto running_transfers = this - > transfers_ ;
tlock . unlock ( ) ;
transfer_infos . reserve ( awaiting_transfers . size ( ) + running_transfers . size ( ) ) ;
for ( const auto & transfer : awaiting_transfers ) {
ActiveFileTransfer info { } ;
apply_transfer_info ( transfer , info ) ;
info . size_done = transfer - > file_offset ;
info . status = ActiveFileTransfer : : NOT_STARTED ;
info . runtime = std : : chrono : : milliseconds { 0 } ;
info . average_speed = 0 ;
info . current_speed = 0 ;
transfer_infos . push_back ( info ) ;
}
for ( const auto & client : running_transfers ) {
auto transfer = client - > transfer ;
if ( ! transfer ) continue ;
ActiveFileTransfer info { } ;
apply_transfer_info ( transfer , info ) ;
info . size_done = transfer - > file_offset + client - > statistics . file_transferred . total_bytes ;
info . status = ActiveFileTransfer : : RUNNING ;
info . runtime = std : : chrono : : floor < std : : chrono : : milliseconds > ( std : : chrono : : system_clock : : now ( ) - client - > timings . key_received ) ;
info . average_speed = client - > statistics . file_transferred . average_bandwidth ( ) ;
info . current_speed = client - > statistics . file_transferred . current_bandwidth ( ) ;
transfer_infos . push_back ( info ) ;
}
response - > emplace_success ( std : : move ( transfer_infos ) ) ;
return response ;
2020-05-07 15:28:15 -04:00
}