2019-10-26 01:51:40 +02:00
# include "FileTransferManager.h"
# include "FileTransferObject.h"
2020-06-11 13:50:37 +02:00
# include <ThreadPool/ThreadHelper.h>
2019-10-29 18:11:47 +01:00
# include <misc/net.h>
2019-10-26 01:51:40 +02:00
# include <algorithm>
2020-06-11 13:50:37 +02:00
# include <utility>
2019-10-26 01:51:40 +02:00
# ifndef WIN32
# include <unistd.h>
# include <misc/net.h>
2019-11-24 18:38:50 +01:00
2019-11-09 22:16:08 +01:00
# ifndef IPPROTO_TCP
# define IPPROTO_TCP (0)
# endif
2019-10-26 01:51:40 +02:00
# else
# include <ws2tcpip.h>
2020-06-11 13:50:37 +02:00
2019-10-26 01:51:40 +02:00
# define SOCK_NONBLOCK (0)
# define MSG_DONTWAIT (0)
# endif
using namespace tc ;
using namespace tc : : ft ;
using namespace std ;
using namespace std : : chrono ;
tc : : ft : : FileTransferManager * transfer_manager = nullptr ;
Transfer : : ~ Transfer ( ) {
log_free ( " Transfer " , this ) ;
}
bool Transfer : : initialize ( std : : string & error ) {
if ( this - > _state ! = state : : UNINITIALIZED ) {
error = tr ( " invalid state " ) ;
return false ;
}
if ( ! this - > _transfer_object - > initialize ( error ) ) {
error = tr ( " failed to initialize transfer object: " ) + error ;
return false ;
}
this - > _state = state : : CONNECTING ;
/* resolve address */
{
addrinfo hints { } , * result ;
memset ( & hints , 0 , sizeof ( hints ) ) ;
hints . ai_family = AF_UNSPEC ;
if ( getaddrinfo ( this - > _options - > remote_address . data ( ) , nullptr , & hints , & result ) ! = 0 | | ! result ) {
error = tr ( " failed to resolve hostname " ) ;
this - > _state = state : : UNINITIALIZED ;
return false ;
}
memcpy ( & this - > remote_address , result - > ai_addr , result - > ai_addrlen ) ;
freeaddrinfo ( result ) ;
}
switch ( this - > remote_address . ss_family ) {
case AF_INET :
( ( sockaddr_in * ) & this - > remote_address ) - > sin_port = htons ( this - > _options - > remote_port ) ;
2019-10-29 18:11:47 +01:00
break ;
2019-10-26 01:51:40 +02:00
case AF_INET6 :
( ( sockaddr_in6 * ) & this - > remote_address ) - > sin6_port = htons ( this - > _options - > remote_port ) ;
2019-10-29 18:11:47 +01:00
break ;
2019-10-26 01:51:40 +02:00
default :
2019-10-29 18:11:47 +01:00
log_warn ( category : : file_transfer , tr ( " getaddrinfo() returns unknown address family ({}) " ) , this - > remote_address . ss_family ) ;
2019-10-26 01:51:40 +02:00
break ;
}
2019-10-29 18:11:47 +01:00
log_info ( category : : file_transfer , tr ( " Setting remote port to {} " ) , net : : to_string ( this - > remote_address ) ) ;
2020-06-11 13:50:37 +02:00
this - > _socket = ( int ) : : socket ( this - > remote_address . ss_family , ( unsigned ) SOCK_STREAM | ( unsigned ) SOCK_NONBLOCK , IPPROTO_TCP ) ;
2019-10-26 01:51:40 +02:00
if ( this - > _socket < 0 ) {
2020-06-11 13:50:37 +02:00
this - > finalize ( true , true ) ;
2019-10-26 01:51:40 +02:00
error = tr ( " failed to spawn socket " ) ;
return false ;
}
# ifdef WIN32
u_long enabled = 0 ;
2020-06-11 13:50:37 +02:00
auto non_block_rs = ioctlsocket ( this - > _socket , ( long ) FIONBIO , & enabled ) ;
2019-10-26 01:51:40 +02:00
if ( non_block_rs ! = NO_ERROR ) {
2020-06-11 13:50:37 +02:00
this - > finalize ( true , true ) ;
2019-10-26 01:51:40 +02:00
error = " failed to enable non blocking more " ;
return false ;
}
# endif
{
lock_guard lock ( this - > event_lock ) ;
2020-06-11 13:50:37 +02:00
this - > event_read = event_new ( this - > event_io , this - > _socket , ( unsigned ) EV_READ | ( unsigned ) EV_PERSIST , & Transfer : : _callback_read , this ) ;
2019-10-26 01:51:40 +02:00
this - > event_write = event_new ( this - > event_io , this - > _socket , EV_WRITE , & Transfer : : _callback_write , this ) ;
}
return true ;
}
bool Transfer : : connect ( ) {
int result = : : connect ( this - > _socket , reinterpret_cast < sockaddr * > ( & this - > remote_address ) , sizeof ( this - > remote_address ) ) ;
if ( result < 0 ) {
# ifdef WIN32
auto error = WSAGetLastError ( ) ;
if ( error ! = WSAEWOULDBLOCK ) {
wchar_t * s = nullptr ;
FormatMessageW (
2020-06-11 13:50:37 +02:00
( DWORD ) FORMAT_MESSAGE_ALLOCATE_BUFFER | ( DWORD ) FORMAT_MESSAGE_FROM_SYSTEM | ( DWORD ) FORMAT_MESSAGE_IGNORE_INSERTS ,
2019-10-26 01:51:40 +02:00
nullptr ,
error ,
MAKELANGID ( LANG_NEUTRAL , SUBLANG_DEFAULT ) ,
( LPWSTR ) & s ,
0 ,
nullptr
) ;
2019-10-29 18:11:47 +01:00
auto r = wcschr ( s , L ' \r ' ) ;
if ( r ) * r = L ' \0 ' ;
2020-03-28 15:04:55 +01:00
this - > call_callback_failed ( std : : to_string ( error ) + " / " + std : : string { s , s + wcslen ( s ) } ) ;
2019-10-29 18:11:47 +01:00
log_trace ( category : : file_transfer , tr ( " Failed to connect with code: {} => {}/{} " ) , result , error , std : : string { s , s + wcslen ( s ) } . c_str ( ) ) ;
2019-10-26 01:51:40 +02:00
LocalFree ( s ) ;
2019-10-29 18:11:47 +01:00
2020-06-11 13:50:37 +02:00
this - > finalize ( true , true ) ;
2019-10-26 01:51:40 +02:00
return false ;
}
# else
if ( errno ! = EINPROGRESS ) {
log_error ( category : : file_transfer , tr ( " Failed to connect with code: {} => {}/{} " ) , result , errno , strerror ( errno ) ) ;
2019-10-29 18:11:47 +01:00
this - > call_callback_failed ( to_string ( errno ) + " / " + strerror ( errno ) ) ;
2019-10-26 01:51:40 +02:00
this - > finalize ( ) ;
return false ;
}
# endif
} else {
this - > _state = state : : CONNECTED ; /* we're connected */
}
log_debug ( category : : file_transfer , tr ( " Connect result: {} - {} " ) , result , errno ) ;
timeval connect_timeout { 5 , 0 } ;
event_add ( this - > event_write , & connect_timeout ) ; /* enabled if socket is connected */
////event_add(this->event_read, &connect_timeout); /* enabled if socket is connected */
if ( this - > _state = = state : : CONNECTED )
this - > handle_connected ( ) ;
return true ;
}
2020-06-11 13:50:37 +02:00
void Transfer : : finalize ( bool blocking , bool aborted ) {
2019-10-26 01:51:40 +02:00
if ( this - > _state = = state : : UNINITIALIZED )
return ;
this - > _state = state : : UNINITIALIZED ;
{
unique_lock lock ( this - > event_lock ) ;
2020-06-11 13:50:37 +02:00
auto ev_read = std : : exchange ( this - > event_read , nullptr ) ;
auto ev_write = std : : exchange ( this - > event_write , nullptr ) ;
2019-10-26 01:51:40 +02:00
lock . unlock ( ) ;
2020-06-11 13:50:37 +02:00
if ( ev_read ) {
2019-10-26 01:51:40 +02:00
if ( blocking )
2020-06-11 13:50:37 +02:00
event_del_block ( ev_read ) ;
2019-10-26 01:51:40 +02:00
else
2020-06-11 13:50:37 +02:00
event_del_noblock ( ev_read ) ;
event_free ( ev_read ) ;
2019-10-26 01:51:40 +02:00
}
2020-06-11 13:50:37 +02:00
if ( ev_write ) {
2019-10-26 01:51:40 +02:00
if ( blocking )
2020-06-11 13:50:37 +02:00
event_del_block ( ev_write ) ;
2019-10-26 01:51:40 +02:00
else
2020-06-11 13:50:37 +02:00
event_del_noblock ( ev_write ) ;
event_free ( ev_write ) ;
2019-10-26 01:51:40 +02:00
}
}
if ( this - > _socket > 0 ) {
# ifdef WIN32
closesocket ( this - > _socket ) ;
# else
shutdown ( this - > _socket , SHUT_RDWR ) ;
close ( this - > _socket ) ;
# endif
this - > _socket = 0 ;
}
2020-06-11 13:50:37 +02:00
this - > _transfer_object - > finalize ( aborted ) ;
2019-10-26 01:51:40 +02:00
this - > _handle - > remove_transfer ( this ) ;
}
void Transfer : : _callback_write ( evutil_socket_t , short flags , void * _ptr_transfer ) {
reinterpret_cast < Transfer * > ( _ptr_transfer ) - > callback_write ( flags ) ;
}
void Transfer : : _callback_read ( evutil_socket_t , short flags , void * _ptr_transfer ) {
reinterpret_cast < Transfer * > ( _ptr_transfer ) - > callback_read ( flags ) ;
}
void Transfer : : callback_read ( short flags ) {
if ( this - > _state < state : : CONNECTING & & this - > _state > state : : DISCONNECTING )
return ;
2020-06-11 13:50:37 +02:00
if ( ( unsigned ) flags & ( unsigned ) EV_TIMEOUT ) {
2019-10-26 01:51:40 +02:00
auto target = dynamic_pointer_cast < TransferTarget > ( this - > _transfer_object ) ;
if ( target ) {
2020-06-11 13:50:37 +02:00
if ( this - > last_target_write . time_since_epoch ( ) . count ( ) = = 0 ) {
2019-10-26 01:51:40 +02:00
this - > last_target_write = system_clock : : now ( ) ;
2020-06-11 13:50:37 +02:00
} else if ( system_clock : : now ( ) - this - > last_target_write > seconds ( 5 ) ) {
2019-10-26 01:51:40 +02:00
this - > call_callback_failed ( " timeout (write) " ) ;
2020-06-11 13:50:37 +02:00
this - > finalize ( false , true ) ;
2019-10-26 01:51:40 +02:00
return ;
}
} else {
if ( this - > last_source_read . time_since_epoch ( ) . count ( ) = = 0 )
this - > last_source_read = system_clock : : now ( ) ;
else if ( system_clock : : now ( ) - this - > last_source_read > seconds ( 5 ) ) {
this - > call_callback_failed ( " timeout (read) " ) ;
2020-06-11 13:50:37 +02:00
this - > finalize ( false , true ) ;
2019-10-26 01:51:40 +02:00
return ;
}
}
{
lock_guard lock ( this - > event_lock ) ;
if ( this - > event_read ) {
event_add ( this - > event_read , & this - > alive_check_timeout ) ;
}
}
}
2020-06-11 13:50:37 +02:00
if ( ( unsigned ) flags & ( unsigned ) EV_READ ) {
2019-10-26 01:51:40 +02:00
if ( this - > _state = = state : : CONNECTING ) {
log_debug ( category : : file_transfer , tr ( " Connected (read event) " ) ) ;
this - > handle_connected ( ) ;
}
int64_t buffer_length = 1024 ;
char buffer [ 1024 ] ;
buffer_length = recv ( this - > _socket , buffer , ( int ) buffer_length , MSG_DONTWAIT ) ;
if ( buffer_length < 0 ) {
# ifdef WIN32
auto error = WSAGetLastError ( ) ;
if ( error ! = WSAEWOULDBLOCK )
return ;
# else
if ( errno = = EAGAIN )
return ;
# endif
2020-06-11 13:50:37 +02:00
log_error ( category : : file_transfer , tr ( " Received an error while receiving data: {}/{} " ) , errno , strerror ( errno ) ) ;
2019-10-26 01:51:40 +02:00
//TODO may handle this error message?
2020-06-11 13:50:37 +02:00
this - > handle_disconnect ( false ) ;
2019-10-26 01:51:40 +02:00
return ;
} else if ( buffer_length = = 0 ) {
log_info ( category : : file_transfer , tr ( " Received an disconnect " ) ) ;
2020-06-11 13:50:37 +02:00
this - > handle_disconnect ( false ) ;
2019-10-26 01:51:40 +02:00
return ;
}
auto target = dynamic_pointer_cast < TransferTarget > ( this - > _transfer_object ) ;
if ( target ) {
string error ;
auto state = target - > write_bytes ( error , ( uint8_t * ) buffer , buffer_length ) ;
this - > last_target_write = system_clock : : now ( ) ;
if ( state = = error : : out_of_space ) {
log_error ( category : : file_transfer , tr ( " Failed to write read data (out of space) " ) ) ;
this - > call_callback_failed ( tr ( " out of local space " ) ) ;
2020-06-11 13:50:37 +02:00
this - > finalize ( true , true ) ;
2019-10-26 01:51:40 +02:00
return ;
} else if ( state = = error : : custom ) {
log_error ( category : : file_transfer , tr ( " Failed to write read data ({}) " ) , error ) ;
this - > call_callback_failed ( error ) ;
2020-06-11 13:50:37 +02:00
this - > finalize ( true , true ) ;
2019-10-26 01:51:40 +02:00
return ;
} else if ( state = = error : : custom_recoverable ) {
log_error ( category : : file_transfer , tr ( " Failed to write read data ({}) " ) , error ) ;
} else if ( state ! = error : : success ) {
log_error ( category : : file_transfer , tr ( " invalid local write return code! ({}) " ) , state ) ;
}
auto stream_index = target - > stream_index ( ) ;
auto expected_bytes = target - > expected_length ( ) ;
if ( stream_index > = expected_bytes ) {
this - > call_callback_finished ( false ) ;
2020-06-11 13:50:37 +02:00
this - > finalize ( false , false ) ;
2019-10-26 01:51:40 +02:00
}
this - > call_callback_process ( stream_index , expected_bytes ) ;
} else {
log_warn ( category : : file_transfer , tr ( " Read {} bytes, but we're not in download mode " ) , buffer_length ) ;
}
}
}
void Transfer : : callback_write ( short flags ) {
if ( this - > _state < state : : CONNECTING & & this - > _state > state : : DISCONNECTING )
return ;
2020-06-11 13:50:37 +02:00
if ( ( unsigned ) flags & ( unsigned ) EV_TIMEOUT ) {
2019-10-26 01:51:40 +02:00
//we received a timeout! (May just for creating buffers)
if ( this - > _state = = state : : CONNECTING ) {
this - > call_callback_failed ( tr ( " failed to connect " ) ) ;
2020-06-11 13:50:37 +02:00
this - > finalize ( false , true ) ;
2019-10-26 01:51:40 +02:00
return ;
}
}
bool readd_write = false , readd_write_for_read = false ;
2020-06-11 13:50:37 +02:00
if ( ( unsigned ) flags & ( unsigned ) EV_WRITE ) {
2019-10-26 01:51:40 +02:00
if ( this - > _state = = state : : CONNECTING )
this - > handle_connected ( ) ;
pipes : : buffer buffer ;
while ( true ) {
{
lock_guard lock ( this - > queue_lock ) ;
auto size = this - > write_queue . size ( ) ;
if ( size = = 0 )
break ;
buffer = this - > write_queue . front ( ) ;
this - > write_queue . pop_front ( ) ;
readd_write = size > 1 ;
}
2020-03-28 15:04:55 +01:00
auto written = send ( this - > _socket , buffer . data_ptr < char > ( ) , ( int ) buffer . length ( ) , MSG_DONTWAIT ) ;
2019-10-26 01:51:40 +02:00
if ( written < = 0 ) {
{
lock_guard lock ( this - > queue_lock ) ;
this - > write_queue . push_front ( buffer ) ;
readd_write = true ;
}
# ifdef WIN32
auto _error = WSAGetLastError ( ) ;
# else
auto _error = errno ;
# define WSAEWOULDBLOCK (0)
# define WSAECONNREFUSED (0)
# define WSAECONNRESET (0)
# define WSAENOTCONN (0)
# endif
if ( _error = = EAGAIN | | _error = = WSAEWOULDBLOCK ) {
break ; /* event will be added with e.g. a timeout */
} else if ( _error = = ECONNREFUSED | | _error = = WSAECONNREFUSED ) {
2020-06-11 13:50:37 +02:00
this - > call_callback_failed ( tr ( " connection refused " ) ) ;
this - > finalize ( false , true ) ;
2019-10-26 01:51:40 +02:00
} else if ( _error = = ECONNRESET | | _error = = WSAECONNRESET ) {
2020-06-11 13:50:37 +02:00
this - > call_callback_failed ( tr ( " connection reset " ) ) ;
this - > finalize ( false , true ) ;
2019-10-26 01:51:40 +02:00
} else if ( _error = = ENOTCONN | | _error = = WSAENOTCONN ) {
2020-06-11 13:50:37 +02:00
this - > call_callback_failed ( tr ( " not connected " ) ) ;
this - > finalize ( false , true ) ;
2019-10-26 01:51:40 +02:00
} else if ( written = = 0 ) {
2020-06-11 13:50:37 +02:00
this - > handle_disconnect ( true ) ;
2019-10-26 01:51:40 +02:00
} else {
2020-06-11 13:50:37 +02:00
log_error ( category : : file_transfer , tr ( " Encountered write error: {}/{} " ) , _error , strerror ( _error ) ) ;
this - > handle_disconnect ( true ) ;
2019-10-26 01:51:40 +02:00
}
return ;
}
if ( written < buffer . length ( ) ) {
lock_guard lock ( this - > queue_lock ) ;
this - > write_queue . push_front ( buffer . range ( written ) ) ;
readd_write = true ;
}
}
}
if ( this - > _state = = state : : CONNECTED ) {
auto source = dynamic_pointer_cast < TransferSource > ( this - > _transfer_object ) ;
if ( source ) {
size_t queue_length = 0 ;
{
lock_guard lock ( this - > queue_lock ) ;
queue_length = this - > write_queue . size ( ) ;
}
string error ;
auto total_bytes = source - > byte_length ( ) ;
auto bytes_to_write = total_bytes - source - > stream_index ( ) ;
while ( queue_length < 8 & & bytes_to_write > 0 ) {
uint64_t buffer_size = 1400 ; /* best TCP packet size (equal to the MTU) */
pipes : : buffer buffer { buffer_size } ;
auto read_status = source - > read_bytes ( error , buffer . data_ptr < uint8_t > ( ) , buffer_size ) ;
if ( read_status ! = error : : success ) {
if ( read_status = = error : : would_block ) {
readd_write_for_read = true ;
break ;
} else if ( read_status = = error : : custom ) {
this - > call_callback_failed ( tr ( " failed to read from source: " ) + error ) ;
2020-06-11 13:50:37 +02:00
this - > finalize ( false , true ) ;
2019-10-26 01:51:40 +02:00
return ;
} else if ( read_status = = error : : custom_recoverable ) {
log_warn ( category : : file_transfer , tr ( " Failed to read from source (but its recoverable): {} " ) , error ) ;
break ;
} else {
log_error ( category : : file_transfer , tr ( " invalid source read status ({}) " ) , read_status ) ;
2020-06-11 13:50:37 +02:00
this - > finalize ( false , true ) ;
2019-10-26 01:51:40 +02:00
return ;
}
} else if ( buffer_size = = 0 ) {
log_warn ( category : : file_transfer , tr ( " Invalid source read size! ({}) " ) , buffer_size ) ;
break ;
}
2019-10-26 12:34:37 +01:00
this - > last_source_read = system_clock : : now ( ) ;
2019-10-26 01:51:40 +02:00
{
lock_guard lock ( this - > queue_lock ) ;
this - > write_queue . push_back ( buffer . range ( 0 , buffer_size ) ) ;
queue_length = this - > write_queue . size ( ) ;
}
bytes_to_write - = buffer_size ;
}
this - > call_callback_process ( total_bytes - bytes_to_write , total_bytes ) ;
if ( queue_length = = 0 ) {
if ( source - > stream_index ( ) = = source - > byte_length ( ) ) {
this - > call_callback_finished ( false ) ;
2020-06-11 13:50:37 +02:00
this - > finalize ( false , false ) ;
2019-10-26 01:51:40 +02:00
return ;
}
}
readd_write = queue_length > 0 ;
}
}
/* we only need write for connect */
if ( readd_write | | readd_write_for_read ) {
lock_guard lock ( this - > event_lock ) ;
if ( this - > event_write ) {
timeval timeout { } ;
if ( readd_write ) {
/* we should be writeable within the next second or we do a keep alive circle */
timeout . tv_sec = 1 ;
timeout . tv_usec = 0 ;
} else if ( readd_write_for_read ) {
/* Schedule a next read attempt of our source */
timeout . tv_sec = 0 ;
timeout . tv_usec = 50000 ;
}
event_add ( this - > event_write , & timeout ) ;
}
}
}
void Transfer : : _write_message ( const pipes : : buffer_view & buffer ) {
{
lock_guard lock ( this - > queue_lock ) ;
this - > write_queue . push_back ( buffer . own_buffer ( ) ) ;
}
if ( this - > _state > = state : : CONNECTED ) {
lock_guard lock ( this - > event_lock ) ;
if ( this - > event_write ) {
event_add ( this - > event_write , nullptr ) ;
}
}
}
2020-06-11 13:50:37 +02:00
void Transfer : : handle_disconnect ( bool write_disconnect ) {
2019-10-26 01:51:40 +02:00
if ( this - > _state ! = state : : DISCONNECTING ) {
auto source = dynamic_pointer_cast < TransferSource > ( this - > _transfer_object ) ;
auto target = dynamic_pointer_cast < TransferTarget > ( this - > _transfer_object ) ;
2020-06-11 13:50:37 +02:00
auto mode = std : : string { write_disconnect ? " write " : " read " } ;
2019-10-26 01:51:40 +02:00
if ( source & & source - > stream_index ( ) ! = source - > byte_length ( ) ) {
2020-06-11 13:50:37 +02:00
this - > call_callback_failed ( " received " + mode + " disconnect while transmitting data ( " + to_string ( target - > stream_index ( ) ) + " / " + to_string ( target - > expected_length ( ) ) + " ) " ) ;
2019-10-26 01:51:40 +02:00
} else if ( target & & target - > stream_index ( ) ! = target - > expected_length ( ) ) {
2020-06-11 13:50:37 +02:00
this - > call_callback_failed ( " received " + mode + " disconnect while receiving data ( " + to_string ( target - > stream_index ( ) ) + " / " + to_string ( target - > expected_length ( ) ) + " ) " ) ;
2019-10-26 01:51:40 +02:00
} else
this - > call_callback_finished ( false ) ;
}
2020-06-11 13:50:37 +02:00
this - > finalize ( false , false ) ;
2019-10-26 01:51:40 +02:00
}
void Transfer : : handle_connected ( ) {
log_info ( category : : file_transfer , tr ( " Transfer connected. Sending key {} " ) , this - > _options - > transfer_key ) ;
this - > _state = state : : CONNECTED ;
event_add ( this - > event_read , & this - > alive_check_timeout ) ;
this - > _write_message ( pipes : : buffer_view { this - > _options - > transfer_key . data ( ) , this - > _options - > transfer_key . length ( ) } ) ;
this - > call_callback_connected ( ) ;
//We dont have to add a timeout to write for prebuffering because its already done by writing this message
}
void Transfer : : call_callback_connected ( ) {
if ( this - > callback_start )
this - > callback_start ( ) ;
}
void Transfer : : call_callback_failed ( const std : : string & error ) {
if ( this - > callback_failed )
this - > callback_failed ( error ) ;
}
void Transfer : : call_callback_finished ( bool aborted ) {
if ( this - > callback_finished )
this - > callback_finished ( aborted ) ;
}
void Transfer : : call_callback_process ( size_t current , size_t max ) {
auto now = system_clock : : now ( ) ;
if ( now - milliseconds { 500 } > this - > last_process_call )
this - > last_process_call = now ;
else
return ;
if ( this - > callback_process )
this - > callback_process ( current , max ) ;
}
2020-06-11 13:50:37 +02:00
FileTransferManager : : FileTransferManager ( ) = default ;
FileTransferManager : : ~ FileTransferManager ( ) = default ;
2019-10-26 01:51:40 +02:00
void FileTransferManager : : initialize ( ) {
this - > event_io_canceled = false ;
this - > event_io = event_base_new ( ) ;
this - > event_io_thread = std : : thread ( & FileTransferManager : : _execute_event_loop , this ) ;
}
void FileTransferManager : : finalize ( ) {
this - > event_io_canceled = true ;
2020-06-11 13:50:37 +02:00
event_base_loopbreak ( this - > event_io ) ;
threads : : save_join ( this - > event_io_thread , false ) ;
2019-10-26 01:51:40 +02:00
//TODO drop all file transfers!
event_base_free ( this - > event_io ) ;
this - > event_io = nullptr ;
}
void FileTransferManager : : _execute_event_loop ( ) {
2020-06-11 13:50:37 +02:00
while ( ! this - > event_io_canceled )
event_base_loop ( this - > event_io , EVLOOP_NO_EXIT_ON_EMPTY ) ;
2019-10-26 01:51:40 +02:00
}
std : : shared_ptr < Transfer > FileTransferManager : : register_transfer ( std : : string & error , const std : : shared_ptr < tc : : ft : : TransferObject > & object , std : : unique_ptr < tc : : ft : : TransferOptions > options ) {
auto transfer = make_shared < Transfer > ( this , object , move ( options ) ) ;
transfer - > event_io = this - > event_io ;
if ( ! transfer - > initialize ( error ) ) {
//error = "failed to initialize transfer: " + error;
return nullptr ;
}
{
lock_guard lock ( this - > _transfer_lock ) ;
this - > _running_transfers . push_back ( transfer ) ;
}
return transfer ;
}
void FileTransferManager : : drop_transfer ( const std : : shared_ptr < Transfer > & transfer ) {
2020-06-11 13:50:37 +02:00
transfer - > finalize ( true , true ) ;
2019-10-26 01:51:40 +02:00
{
lock_guard lock ( this - > _transfer_lock ) ;
auto it = find ( this - > _running_transfers . begin ( ) , this - > _running_transfers . end ( ) , transfer ) ;
if ( it ! = this - > _running_transfers . end ( ) )
this - > _running_transfers . erase ( it ) ;
}
}
void FileTransferManager : : remove_transfer ( tc : : ft : : Transfer * transfer ) {
lock_guard lock ( this - > _transfer_lock ) ;
this - > _running_transfers . erase ( remove_if ( this - > _running_transfers . begin ( ) , this - > _running_transfers . end ( ) , [ & ] ( const shared_ptr < Transfer > & _t ) {
return & * _t = = transfer ;
} ) , this - > _running_transfers . end ( ) ) ;
}
# ifdef NODEJS_API
2020-02-08 16:50:48 +01:00
# include <NanGet.h>
2019-10-26 01:51:40 +02:00
NAN_MODULE_INIT ( JSTransfer : : Init ) {
auto klass = Nan : : New < v8 : : FunctionTemplate > ( JSTransfer : : NewInstance ) ;
klass - > SetClassName ( Nan : : New ( " JSTransfer " ) . ToLocalChecked ( ) ) ;
klass - > InstanceTemplate ( ) - > SetInternalFieldCount ( 1 ) ;
Nan : : SetPrototypeMethod ( klass , " start " , JSTransfer : : _start ) ;
constructor ( ) . Reset ( Nan : : GetFunction ( klass ) . ToLocalChecked ( ) ) ;
}
NAN_METHOD ( JSTransfer : : NewInstance ) {
if ( info . IsConstructCall ( ) ) {
if ( info . Length ( ) ! = 1 | | ! info [ 0 ] - > IsObject ( ) ) {
Nan : : ThrowError ( " invalid arguments " ) ;
return ;
}
/*
* transfer_key : string ;
* client_transfer_id : number ;
* server_transfer_id : number ;
* object : HandledTransferObject ;
*/
auto options = info [ 0 ] - > ToObject ( Nan : : GetCurrentContext ( ) ) . ToLocalChecked ( ) ;
2019-11-24 18:38:50 +01:00
auto key = Nan : : GetStringLocal ( options , " transfer_key " ) ;
v8 : : Local < v8 : : Number > client_transfer_id = Nan : : GetLocal < v8 : : Number > ( options , " client_transfer_id " ) ;
v8 : : Local < v8 : : Number > server_transfer_id = Nan : : GetLocal < v8 : : Number > ( options , " server_transfer_id " ) ;
v8 : : Local < v8 : : String > remote_address = Nan : : GetStringLocal ( options , " remote_address " ) ;
v8 : : Local < v8 : : Number > remote_port = Nan : : GetLocal < v8 : : Number > ( options , " remote_port " ) ;
2019-10-26 01:51:40 +02:00
if (
key . IsEmpty ( ) | | ! key - > IsString ( ) | |
remote_address . IsEmpty ( ) | | ! remote_address - > IsString ( ) | |
remote_port . IsEmpty ( ) | | ! remote_port - > IsInt32 ( ) | |
client_transfer_id . IsEmpty ( ) | | ! client_transfer_id - > IsInt32 ( ) | |
server_transfer_id . IsEmpty ( ) | | ! server_transfer_id - > IsInt32 ( )
) {
Nan : : ThrowError ( " invalid argument types " ) ;
return ;
}
2019-11-24 18:38:50 +01:00
auto wrapped_options = Nan : : GetLocal < v8 : : Object > ( options , " object " ) ;
2019-10-26 01:51:40 +02:00
if ( ! TransferObjectWrap : : is_wrap ( wrapped_options ) ) {
Nan : : ThrowError ( " invalid handle " ) ;
return ;
}
auto transfer_object = ObjectWrap : : Unwrap < TransferObjectWrap > ( wrapped_options ) - > target ( ) ;
assert ( transfer_object ) ;
auto t_options = make_unique < TransferOptions > ( ) ;
t_options - > transfer_key = * Nan : : Utf8String ( key ) ;
t_options - > client_transfer_id = client_transfer_id - > Int32Value ( Nan : : GetCurrentContext ( ) ) . FromMaybe ( 0 ) ;
t_options - > server_transfer_id = server_transfer_id - > Int32Value ( Nan : : GetCurrentContext ( ) ) . FromMaybe ( 0 ) ;
t_options - > remote_address = * Nan : : Utf8String ( remote_address ) ;
t_options - > remote_port = remote_port - > Int32Value ( Nan : : GetCurrentContext ( ) ) . FromMaybe ( 0 ) ;
string error ;
auto transfer = transfer_manager - > register_transfer ( error , transfer_object , move ( t_options ) ) ;
if ( ! transfer ) {
Nan : : ThrowError ( Nan : : New < v8 : : String > ( " failed to create transfer: " + error ) . ToLocalChecked ( ) ) ;
return ;
}
auto js_instance = new JSTransfer ( transfer ) ;
js_instance - > Wrap ( info . This ( ) ) ;
js_instance - > _self_ref = true ;
js_instance - > Ref ( ) ; /* increase ref counter because file transfer is running */
info . GetReturnValue ( ) . Set ( info . This ( ) ) ;
} else {
if ( info . Length ( ) ! = 1 ) {
Nan : : ThrowError ( " invalid argument count " ) ;
return ;
}
v8 : : Local < v8 : : Function > cons = Nan : : New ( constructor ( ) ) ;
v8 : : Local < v8 : : Value > argv [ 1 ] = { info [ 0 ] } ;
Nan : : TryCatch try_catch ;
auto result = Nan : : NewInstance ( cons , info . Length ( ) , argv ) ;
if ( try_catch . HasCaught ( ) ) {
try_catch . ReThrow ( ) ;
return ;
}
info . GetReturnValue ( ) . Set ( result . ToLocalChecked ( ) ) ;
}
}
JSTransfer : : JSTransfer ( std : : shared_ptr < tc : : ft : : Transfer > transfer ) : _transfer ( move ( transfer ) ) {
2020-06-11 13:50:37 +02:00
log_allocate ( " JSTransfer " , this ) ;
2019-10-26 01:51:40 +02:00
this - > call_failed = Nan : : async_callback ( [ & ] ( std : : string error ) {
Nan : : HandleScope scope ;
2020-06-11 13:50:37 +02:00
this - > callback_failed ( std : : move ( error ) ) ;
2019-10-26 01:51:40 +02:00
} ) ;
this - > call_finished = Nan : : async_callback ( [ & ] ( bool f ) {
Nan : : HandleScope scope ;
this - > callback_finished ( f ) ;
} ) ;
this - > call_start = Nan : : async_callback ( [ & ] {
Nan : : HandleScope scope ;
this - > callback_start ( ) ;
} ) ;
this - > call_progress = Nan : : async_callback ( [ & ] ( uint64_t a , uint64_t b ) {
Nan : : HandleScope scope ;
this - > callback_progress ( a , b ) ;
} ) ;
this - > _transfer - > callback_failed = [ & ] ( std : : string error ) { this - > call_failed ( std : : forward < string > ( error ) ) ; } ;
this - > _transfer - > callback_finished = [ & ] ( bool f ) { this - > call_finished ( std : : forward < bool > ( f ) ) ; } ;
this - > _transfer - > callback_start = [ & ] { this - > call_start ( ) ; } ;
2019-10-26 01:17:36 +01:00
this - > _transfer - > callback_process = [ & ] ( uint64_t a , uint64_t b ) { this - > call_progress . call_cpy ( a , b , true ) ; } ;
2019-10-26 01:51:40 +02:00
}
JSTransfer : : ~ JSTransfer ( ) {
2020-06-11 13:50:37 +02:00
log_free ( " JSTransfer " , this ) ;
this - > _transfer - > callback_failed = nullptr ;
this - > _transfer - > callback_finished = nullptr ;
this - > _transfer - > callback_start = nullptr ;
this - > _transfer - > callback_process = nullptr ;
2019-10-26 01:51:40 +02:00
}
NAN_METHOD ( JSTransfer : : destory_transfer ) {
//TODO!
Nan : : ThrowError ( " Not implemented! " ) ;
}
NAN_METHOD ( JSTransfer : : _start ) {
return ObjectWrap : : Unwrap < JSTransfer > ( info . Holder ( ) ) - > start ( info ) ;
}
NAN_METHOD ( JSTransfer : : start ) {
if ( ! this - > _transfer - > connect ( ) ) {
2019-10-29 18:11:47 +01:00
log_debug ( category : : file_transfer , tr ( " Failed to start file transfer. Error callback should be called! " ) ) ;
info . GetReturnValue ( ) . Set ( Nan : : New < v8 : : Boolean > ( false ) ) ;
2019-10-26 01:51:40 +02:00
return ;
}
log_info ( category : : file_transfer , tr ( " Connecting to {}:{} " ) , this - > _transfer - > options ( ) . remote_address , this - > _transfer - > options ( ) . remote_port ) ;
2019-10-29 18:11:47 +01:00
info . GetReturnValue ( ) . Set ( Nan : : New < v8 : : Boolean > ( true ) ) ;
2019-10-26 01:51:40 +02:00
}
NAN_METHOD ( JSTransfer : : _abort ) {
return ObjectWrap : : Unwrap < JSTransfer > ( info . Holder ( ) ) - > abort ( info ) ;
}
NAN_METHOD ( JSTransfer : : abort ) {
//TODO!
2019-10-29 18:11:47 +01:00
Nan : : ThrowError ( " Not implemented " ) ;
2019-10-26 01:51:40 +02:00
}
void JSTransfer : : callback_finished ( bool flag ) {
if ( this - > _self_ref ) {
this - > _self_ref = false ;
this - > Unref ( ) ;
}
auto callback = Nan : : Get ( this - > handle ( ) , Nan : : New < v8 : : String > ( " callback_finished " ) . ToLocalChecked ( ) ) . ToLocalChecked ( ) . As < v8 : : Function > ( ) ;
if ( callback . IsEmpty ( ) | | ! callback - > IsFunction ( ) )
return ;
v8 : : Local < v8 : : Value > arguments [ 1 ] ;
arguments [ 0 ] = Nan : : New < v8 : : Boolean > ( flag ) ;
2020-06-11 13:50:37 +02:00
( void ) callback - > Call ( Nan : : GetCurrentContext ( ) , Nan : : Undefined ( ) , 1 , arguments ) ;
2019-10-26 01:51:40 +02:00
}
void JSTransfer : : callback_start ( ) {
auto callback = Nan : : Get ( this - > handle ( ) , Nan : : New < v8 : : String > ( " callback_start " ) . ToLocalChecked ( ) ) . ToLocalChecked ( ) . As < v8 : : Function > ( ) ;
if ( callback . IsEmpty ( ) | | ! callback - > IsFunction ( ) )
return ;
2020-06-11 13:50:37 +02:00
( void ) callback - > Call ( Nan : : GetCurrentContext ( ) , Nan : : Undefined ( ) , 0 , nullptr ) ;
2019-10-26 01:51:40 +02:00
}
void JSTransfer : : callback_progress ( uint64_t a , uint64_t b ) {
auto callback = Nan : : Get ( this - > handle ( ) , Nan : : New < v8 : : String > ( " callback_progress " ) . ToLocalChecked ( ) ) . ToLocalChecked ( ) . As < v8 : : Function > ( ) ;
if ( callback . IsEmpty ( ) | | ! callback - > IsFunction ( ) )
return ;
v8 : : Local < v8 : : Value > arguments [ 2 ] ;
2020-03-28 15:04:55 +01:00
arguments [ 0 ] = Nan : : New < v8 : : Number > ( ( uint32_t ) a ) ;
arguments [ 1 ] = Nan : : New < v8 : : Number > ( ( uint32_t ) b ) ;
2020-06-11 13:50:37 +02:00
( void ) callback - > Call ( Nan : : GetCurrentContext ( ) , Nan : : Undefined ( ) , 2 , arguments ) ;
2019-10-26 01:51:40 +02:00
}
void JSTransfer : : callback_failed ( std : : string error ) {
if ( this - > _self_ref ) {
this - > _self_ref = false ;
this - > Unref ( ) ;
}
auto callback = Nan : : Get ( this - > handle ( ) , Nan : : New < v8 : : String > ( " callback_failed " ) . ToLocalChecked ( ) ) . ToLocalChecked ( ) . As < v8 : : Function > ( ) ;
if ( callback . IsEmpty ( ) | | ! callback - > IsFunction ( ) )
return ;
v8 : : Local < v8 : : Value > arguments [ 1 ] ;
2020-06-11 13:50:37 +02:00
arguments [ 0 ] = Nan : : New < v8 : : String > ( std : : move ( error ) ) . ToLocalChecked ( ) ;
( void ) callback - > Call ( Nan : : GetCurrentContext ( ) , Nan : : Undefined ( ) , 1 , arguments ) ;
2019-10-26 01:51:40 +02:00
}
# endif