2022-03-01 05:53:29 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "UnsyncCmdQuery.h"
2023-08-18 21:41:29 -04:00
# include "UnsyncFile.h"
2022-03-01 05:53:29 -05:00
# include "UnsyncHttp.h"
2023-08-18 21:41:29 -04:00
# include "UnsyncProxy.h"
2022-03-01 05:53:29 -05:00
# include "UnsyncThread.h"
# include "UnsyncUtil.h"
2023-08-29 19:18:18 -04:00
# include "UnsyncAuth.h"
2023-09-22 20:13:26 -04:00
# include "UnsyncPool.h"
2024-05-09 15:37:00 -04:00
# include "UnsyncScheduler.h"
2022-03-01 05:53:29 -05:00
2023-06-21 15:01:35 -04:00
# include <float.h>
2022-03-01 05:53:29 -05:00
# include <algorithm>
2023-09-22 20:13:26 -04:00
# include <regex>
2022-03-01 05:53:29 -05:00
# include <json11.hpp>
2023-08-18 21:41:29 -04:00
# include <fmt/format.h>
2022-03-01 05:53:29 -05:00
namespace unsync {
using FMirrorInfoResult = TResult < std : : vector < FMirrorInfo > > ;
// Runs a basic HTTP request against the remote server and returns the time it took to get the response, -1 if connection could not be
// established.
static double
RunHttpPing ( std : : string_view Address , uint16 Port )
{
2024-04-19 22:35:37 -04:00
FTlsClientSettings TlsSettings ;
TlsSettings . Subject = Address ;
ETlsRequirement TlsRequirement = ETlsRequirement : : None ;
if ( Port = = 443 )
{
TlsRequirement = ETlsRequirement : : Preferred ;
}
FHttpConnection Connection ( Address , Port , TlsRequirement , TlsSettings ) ;
2022-03-01 05:53:29 -05:00
FHttpRequest Request ;
2023-08-16 16:34:36 -04:00
Request . Url = " /api/v1/ping " ;
2022-03-01 05:53:29 -05:00
Request . Method = EHttpMethod : : GET ;
2024-04-19 22:35:37 -04:00
// Don't time the connection handshake, only query time itself
Connection . Open ( ) ;
FTimePoint TimeBegin = TimePointNow ( ) ;
2022-03-01 05:53:29 -05:00
FHttpResponse PingResponse = HttpRequest ( Connection , Request ) ;
FTimePoint TimeEnd = TimePointNow ( ) ;
if ( PingResponse . Success ( ) )
{
return DurationSec ( TimeBegin , TimeEnd ) ;
}
else
{
return - 1 ;
}
}
FMirrorInfoResult
RunQueryMirrors ( const FRemoteDesc & RemoteDesc )
{
2023-06-21 15:01:35 -04:00
const char * Url = " /api/v1/mirrors " ;
2022-03-01 05:53:29 -05:00
FHttpResponse Response = HttpRequest ( RemoteDesc , EHttpMethod : : GET , Url ) ;
if ( ! Response . Success ( ) )
{
return HttpError ( Url , Response . Code ) ;
}
using namespace json11 ;
std : : string JsonString = std : : string ( Response . AsStringView ( ) ) ;
std : : string JsonErrorString ;
Json JsonObject = Json : : parse ( JsonString , JsonErrorString ) ;
if ( ! JsonErrorString . empty ( ) )
{
return AppError ( std : : string ( " JSON parse error while getting server mirrors: " ) + JsonErrorString ) ;
}
std : : vector < FMirrorInfo > Result ;
for ( const auto & Elem : JsonObject . array_items ( ) )
{
FMirrorInfo Info ;
for ( const auto & Field : Elem . object_items ( ) )
{
if ( Field . first = = " name " )
{
Info . Name = Field . second . string_value ( ) ;
}
2024-04-18 22:55:33 -04:00
else if ( Field . first = = " description " )
{
Info . Description = Field . second . string_value ( ) ;
}
2022-03-01 05:53:29 -05:00
else if ( Field . first = = " address " )
{
Info . Address = Field . second . string_value ( ) ;
}
else if ( Field . first = = " port " )
{
int PortValue = Field . second . int_value ( ) ;
if ( PortValue > 0 & & PortValue < 65536 )
{
Info . Port = uint16 ( PortValue ) ;
}
else
{
UNSYNC_WARNING ( L " Unexpected port value: %d " , PortValue ) ;
Info . Port = 0 ;
}
}
}
Result . push_back ( Info ) ;
}
return ResultOk ( Result ) ;
}
2023-08-18 21:41:29 -04:00
int32
CmdQueryMirrors ( const FCmdQueryOptions & Options )
{
FMirrorInfoResult MirrorsResult = RunQueryMirrors ( Options . Remote ) ;
if ( MirrorsResult . IsError ( ) )
{
2024-06-27 00:49:36 -04:00
LogError ( MirrorsResult . GetError ( ) , L " Failed to get mirror list from the server " ) ;
2023-08-18 21:41:29 -04:00
return 1 ;
}
std : : vector < FMirrorInfo > Mirrors = MirrorsResult . GetData ( ) ;
2023-09-08 12:19:57 -04:00
ParallelForEach ( Mirrors , [ ] ( FMirrorInfo & Mirror ) { Mirror . Ping = RunHttpPing ( Mirror . Address , Mirror . Port ) ; } ) ;
2023-08-18 21:41:29 -04:00
std : : sort ( Mirrors . begin ( ) , Mirrors . end ( ) , [ ] ( const FMirrorInfo & InA , const FMirrorInfo & InB ) {
double A = InA . Ping > 0 ? InA . Ping : FLT_MAX ;
double B = InB . Ping > 0 ? InB . Ping : FLT_MAX ;
return A < B ;
} ) ;
2023-08-29 19:18:18 -04:00
LogPrintf ( ELogLevel : : MachineReadable , L " [ \n " ) ;
2023-08-18 21:41:29 -04:00
for ( size_t I = 0 ; I < Mirrors . size ( ) ; + + I )
{
const FMirrorInfo & Mirror = Mirrors [ I ] ;
2024-04-18 22:55:33 -04:00
int32 PingMs = ( Mirror . Ping = = 0 ) ? 0 : std : : max ( 1 , int32 ( Mirror . Ping * 1000.0 ) ) ;
2023-08-29 19:18:18 -04:00
LogPrintf ( ELogLevel : : MachineReadable ,
2024-04-18 22:55:33 -04:00
L " { \" address \" : \" %hs \" , \" port \" :%d, \" ok \" :%hs, \" ping \" :%d, \" name \" : \" %hs \" , \" description \" : \" %hs \" }%hs \n " ,
2023-09-23 01:59:00 -04:00
StringEscape ( Mirror . Address ) . c_str ( ) ,
2023-08-18 21:41:29 -04:00
Mirror . Port ,
Mirror . Ping > 0 ? " true " : " false " ,
2024-04-18 22:55:33 -04:00
PingMs ,
2023-09-23 01:59:00 -04:00
StringEscape ( Mirror . Name ) . c_str ( ) ,
2024-04-18 22:55:33 -04:00
StringEscape ( Mirror . Description ) . c_str ( ) ,
2023-08-18 21:41:29 -04:00
I + 1 = = Mirrors . size ( ) ? " " : " , " ) ;
}
2023-08-29 19:18:18 -04:00
LogPrintf ( ELogLevel : : MachineReadable , L " ] \n " ) ;
2023-08-18 21:41:29 -04:00
return 0 ;
}
2023-08-24 10:44:55 -04:00
int32
CmdQueryList ( const FCmdQueryOptions & Options )
{
2024-06-19 20:06:19 -04:00
FHttpConnection Connection = FHttpConnection : : CreateDefaultHttps ( Options . Remote ) ;
TResult < ProxyQuery : : FHelloResponse > HelloResponse = ProxyQuery : : Hello ( Options . Remote . Protocol , Connection ) ;
2023-11-21 18:25:48 -05:00
if ( HelloResponse . IsError ( ) )
{
UNSYNC_ERROR ( " Failed establish a handshake with server '%hs' " , Options . Remote . Host . Address . c_str ( ) ) ;
LogError ( HelloResponse . GetError ( ) ) ;
return - 1 ;
}
FAuthDesc AuthDesc = FAuthDesc : : FromHelloResponse ( * HelloResponse ) ;
2023-09-22 20:13:26 -04:00
if ( Options . Args . empty ( ) )
{
UNSYNC_ERROR ( L " Path argument is required " ) ;
return - 1 ;
}
2024-06-20 18:37:09 -04:00
TResult < ProxyQuery : : FDirectoryListing > ListingResult = ProxyQuery : : ListDirectory ( Options . Remote . Protocol , Connection , & AuthDesc , Options . Args [ 0 ] ) ;
2023-08-24 10:44:55 -04:00
2024-06-19 20:06:19 -04:00
if ( ListingResult . IsError ( ) )
2024-05-08 01:56:22 -04:00
{
2024-06-27 00:49:36 -04:00
LogError ( ListingResult . GetError ( ) , L " Failed to list remote directory " ) ;
2024-05-08 01:56:22 -04:00
return - 1 ;
}
2024-06-19 20:06:19 -04:00
std : : string ListingJson = ListingResult - > ToJson ( ) ;
2023-08-24 10:44:55 -04:00
2024-06-19 20:06:19 -04:00
LogPrintf ( ELogLevel : : MachineReadable , L " %hs \n " , ListingJson . c_str ( ) ) ;
2023-08-31 16:20:09 -04:00
return 0 ;
}
2023-09-22 20:13:26 -04:00
int32
CmdQuerySearch ( const FCmdQueryOptions & Options )
{
2023-09-25 21:53:38 -04:00
using namespace ProxyQuery ;
2023-09-22 20:13:26 -04:00
if ( Options . Args . empty ( ) )
{
UNSYNC_ERROR ( L " Path argument is required " ) ;
return - 1 ;
}
auto CreateConnection = [ Remote = Options . Remote ]
{
2023-10-24 15:07:43 -04:00
FTlsClientSettings TlsSettings = Remote . GetTlsClientSettings ( ) ;
2024-04-19 22:35:37 -04:00
return new FHttpConnection ( Remote . Host . Address , Remote . Host . Port , Remote . TlsRequirement , TlsSettings ) ;
2023-09-22 20:13:26 -04:00
} ;
TObjectPool < FHttpConnection > ConnectionPool ( CreateConnection ) ;
2024-06-19 20:06:19 -04:00
TResult < ProxyQuery : : FHelloResponse > HelloResponse = [ & ConnectionPool , & Options ]
2023-11-21 18:25:48 -05:00
{
std : : unique_ptr < FHttpConnection > Connection = ConnectionPool . Acquire ( ) ;
2024-06-19 20:06:19 -04:00
TResult < ProxyQuery : : FHelloResponse > Result = ProxyQuery : : Hello ( Options . Remote . Protocol , * Connection ) ;
2023-11-21 18:25:48 -05:00
ConnectionPool . Release ( std : : move ( Connection ) ) ;
2024-06-19 20:06:19 -04:00
return Result ;
} ( ) ;
2023-11-21 18:25:48 -05:00
2024-06-19 20:06:19 -04:00
if ( HelloResponse . IsError ( ) )
{
UNSYNC_ERROR ( " Failed establish a handshake with server '%hs' " , Options . Remote . Host . Address . c_str ( ) ) ;
LogError ( HelloResponse . GetError ( ) ) ;
return - 1 ;
2023-11-21 18:25:48 -05:00
}
2024-06-19 20:06:19 -04:00
FAuthDesc AuthDesc = FAuthDesc : : FromHelloResponse ( * HelloResponse ) ;
2023-09-22 20:13:26 -04:00
const std : : string & RootPath = Options . Args [ 0 ] ;
std : : vector < std : : regex > SubdirPatterns ;
2023-10-31 16:34:04 -04:00
UNSYNC_LOG ( " Searching '%hs' " , RootPath . c_str ( ) ) ;
2023-09-22 20:13:26 -04:00
if ( Options . Args . size ( ) > 1 )
{
UNSYNC_LOG_INDENT ;
2023-10-31 16:34:04 -04:00
UNSYNC_LOG ( " Subdirectory patterns: " , RootPath . c_str ( ) ) ;
2023-09-22 20:13:26 -04:00
UNSYNC_LOG_INDENT ;
for ( size_t i = 1 ; i < Options . Args . size ( ) ; + + i )
{
2023-10-31 16:34:04 -04:00
UNSYNC_LOG ( " %hs " , Options . Args [ i ] . c_str ( ) ) ;
2023-09-22 20:13:26 -04:00
std : : regex Pattern = std : : regex ( Options . Args [ i ] , std : : regex_constants : : icase ) ;
SubdirPatterns . push_back ( Pattern ) ;
}
}
struct FEntry
{
std : : string Path ;
uint32 Depth = 0 ;
} ;
std : : vector < FEntry > PendingDirectories ;
{
FEntry RootEntry ;
RootEntry . Path = RootPath ;
RootEntry . Depth = 0 ;
PendingDirectories . push_back ( RootEntry ) ;
}
2023-09-25 21:53:38 -04:00
struct FResultEntry : FEntry
{
FDirectoryListingEntry DirEntry ;
} ;
2023-09-22 20:13:26 -04:00
struct FTaskContext
{
2024-06-25 18:31:35 -04:00
std : : mutex Mutex ;
std : : vector < FResultEntry > FoundEntries ;
std : : unordered_set < std : : string > VisitedDirectories ;
bool bParentThreadVerbose = false ;
int32 ParentThreadIndent = 0 ;
2023-09-22 20:13:26 -04:00
} ;
2024-05-09 15:37:00 -04:00
FTaskGroup Tasks = GScheduler - > CreateTaskGroup ( ) ;
2023-09-22 20:13:26 -04:00
FTaskContext Context ;
Context . bParentThreadVerbose = GLogVerbose ;
Context . ParentThreadIndent = GLogIndent ;
2024-06-25 18:31:35 -04:00
std : : function < void ( std : : string , int32 , const FDirectoryListing * ) > ExploreDirectory =
[ & Context , & AuthDesc , & ConnectionPool , & ExploreDirectory , & SubdirPatterns , & Tasks , & Options ] (
std : : string Path ,
int32 CurrentDepth ,
const FDirectoryListing * DirectoryListingPtr )
2023-09-22 20:13:26 -04:00
{
FLogVerbosityScope VerboseScope ( Context . bParentThreadVerbose ) ;
FLogIndentScope IndentScope ( Context . ParentThreadIndent , true ) ;
2024-06-25 18:31:35 -04:00
FDirectoryListing RemoteDirectoryListing ;
2023-09-22 20:13:26 -04:00
2024-06-25 18:31:35 -04:00
if ( ! DirectoryListingPtr )
2023-09-22 20:13:26 -04:00
{
2024-06-25 18:31:35 -04:00
UNSYNC_VERBOSE2 ( L " Listing '%hs' " , Path . c_str ( ) ) ;
GScheduler - > NetworkSemaphore . Acquire ( false ) ;
std : : unique_ptr < FHttpConnection > Connection = ConnectionPool . Acquire ( ) ;
TResult < FDirectoryListing > DirectoryListingResult =
ProxyQuery : : ListDirectory ( Options . Remote . Protocol , * Connection , & AuthDesc , Path ) ;
ConnectionPool . Release ( std : : move ( Connection ) ) ;
GScheduler - > NetworkSemaphore . Release ( ) ;
if ( DirectoryListingResult . IsError ( ) )
{
2024-06-27 00:49:36 -04:00
LogError ( DirectoryListingResult . GetError ( ) , L " Failed to list remote directory " ) ;
2024-06-25 18:31:35 -04:00
return ;
}
std : : swap ( RemoteDirectoryListing , DirectoryListingResult . GetData ( ) ) ;
DirectoryListingPtr = & RemoteDirectoryListing ;
2023-09-22 20:13:26 -04:00
}
2024-06-25 18:31:35 -04:00
const FDirectoryListing & DirectoryListing = * DirectoryListingPtr ;
2023-09-22 20:13:26 -04:00
2023-09-25 21:53:38 -04:00
for ( const FDirectoryListingEntry & DirEntry : DirectoryListing . Entries )
2023-09-22 20:13:26 -04:00
{
2024-06-25 18:31:35 -04:00
std : : string DirEntryName = DirEntry . Name ;
// Only include one subdirectory level
const size_t SeparatorPos = DirEntryName . find ( PATH_SEPARATOR ) ;
if ( SeparatorPos ! = std : : string : : npos )
{
DirEntryName = DirEntryName . substr ( 0 , PATH_SEPARATOR ) ;
}
2023-10-06 13:37:16 -04:00
FEntry NextEntry ;
2024-06-25 18:31:35 -04:00
NextEntry . Path = Path + PATH_SEPARATOR + DirEntryName ;
2023-10-06 13:37:16 -04:00
NextEntry . Depth = CurrentDepth + 1 ;
2023-09-22 20:13:26 -04:00
2024-06-25 18:31:35 -04:00
// Include only leaf directory entries in the final output
if ( SeparatorPos = = std : : string : : npos )
2023-10-06 13:37:16 -04:00
{
std : : lock_guard < std : : mutex > LockGuard ( Context . Mutex ) ;
FResultEntry ResultEntry ;
ResultEntry . Path = NextEntry . Path ;
ResultEntry . Depth = NextEntry . Depth ;
ResultEntry . DirEntry = DirEntry ;
Context . FoundEntries . push_back ( ResultEntry ) ;
}
if ( NextEntry . Depth > SubdirPatterns . size ( ) )
{
continue ;
}
2024-06-25 18:31:35 -04:00
if ( DirEntry . bDirectory & & std : : regex_match ( DirEntryName , SubdirPatterns [ CurrentDepth ] , std : : regex_constants : : match_any ) )
2023-09-22 20:13:26 -04:00
{
2024-06-25 18:31:35 -04:00
UNSYNC_VERBOSE2 ( L " Matched: '%hs' " , DirEntryName . c_str ( ) ) ;
// Directory listing may already include some child sub-directories, so we can skip some network requests
FDirectoryListing SubDirectoryListing ;
std : : string RequiredPrefix = DirEntryName + PATH_SEPARATOR ;
for ( const FDirectoryListingEntry & DirEntry2 : DirectoryListing . Entries )
{
if ( DirEntry2 . Name . starts_with ( RequiredPrefix ) )
{
FDirectoryListingEntry SubDirEntry = DirEntry2 ;
SubDirEntry . Name = SubDirEntry . Name . substr ( RequiredPrefix . length ( ) ) ;
SubDirectoryListing . Entries . push_back ( SubDirEntry ) ;
}
}
{
// Only visit each sub-directory once
std : : lock_guard < std : : mutex > LockGuard ( Context . Mutex ) ;
if ( ! Context . VisitedDirectories . insert ( NextEntry . Path ) . second )
{
continue ;
}
}
if ( SubDirectoryListing . Entries . empty ( ) )
{
Tasks . run ( [ ExploreDirectory , NextEntry ] ( ) { ExploreDirectory ( NextEntry . Path , NextEntry . Depth , nullptr ) ; } ) ;
}
else
{
ExploreDirectory ( NextEntry . Path , NextEntry . Depth , & SubDirectoryListing ) ;
}
2023-09-22 20:13:26 -04:00
}
}
} ;
2024-06-25 18:31:35 -04:00
ExploreDirectory ( RootPath , 0 , nullptr ) ;
2023-09-22 20:13:26 -04:00
Tasks . wait ( ) ;
2023-09-25 21:53:38 -04:00
std : : vector < FResultEntry > & ResultEntries = Context . FoundEntries ;
2023-09-22 20:13:26 -04:00
2023-09-25 21:53:38 -04:00
std : : sort ( ResultEntries . begin ( ) , ResultEntries . end ( ) , [ ] ( const FResultEntry & A , const FResultEntry & B ) { return A . Path < B . Path ; } ) ;
2023-09-22 20:13:26 -04:00
2023-09-25 21:53:38 -04:00
LogPrintf ( ELogLevel : : MachineReadable , L " { \n " ) ;
LogPrintf ( ELogLevel : : MachineReadable , L " \" root \" : \" %hs \" , \n " , StringEscape ( RootPath ) . c_str ( ) ) ;
LogPrintf ( ELogLevel : : MachineReadable , L " \" entries \" : [ \n " ) ;
for ( size_t i = 0 ; i < ResultEntries . size ( ) ; + + i )
2023-09-22 20:13:26 -04:00
{
2023-09-25 21:53:38 -04:00
const FResultEntry & ResultEntry = ResultEntries [ i ] ;
const char * TrailingComma = i + 1 = = ResultEntries . size ( ) ? " " : " , " ;
std : : string_view RelativePath = std : : string_view ( ResultEntry . Path ) . substr ( RootPath . length ( ) + 1 ) ;
LogPrintf ( ELogLevel : : MachineReadable ,
L " { \" path \" : \" %hs \" , \" is_directory \" : %hs, \" mtime \" : %llu, \" size \" : %llu }%hs \n " ,
StringEscape ( RelativePath ) . c_str ( ) ,
ResultEntry . DirEntry . bDirectory ? " true " : " false " ,
llu ( ResultEntry . DirEntry . Mtime ) ,
llu ( ResultEntry . DirEntry . Size ) ,
TrailingComma ) ;
2023-09-22 20:13:26 -04:00
}
2023-09-25 21:53:38 -04:00
LogPrintf ( ELogLevel : : MachineReadable , L " ] \n " ) ;
LogPrintf ( ELogLevel : : MachineReadable , L " } \n " ) ;
2023-09-22 20:13:26 -04:00
return 0 ;
}
2023-08-31 16:20:09 -04:00
int32
CmdQueryFile ( const FCmdQueryOptions & Options )
{
2023-09-22 20:13:26 -04:00
if ( Options . Args . empty ( ) )
{
UNSYNC_ERROR ( L " Path argument is required " ) ;
return - 1 ;
}
2023-11-21 18:25:48 -05:00
// TODO: use a global connection pool and use it for ProxyQuery::DownloadFile too
2024-06-19 20:06:19 -04:00
FHttpConnection HelloConnection = FHttpConnection : : CreateDefaultHttps ( Options . Remote ) ;
TResult < ProxyQuery : : FHelloResponse > HelloResponse = ProxyQuery : : Hello ( Options . Remote . Protocol , HelloConnection ) ;
2023-11-21 18:25:48 -05:00
if ( HelloResponse . IsError ( ) )
{
UNSYNC_ERROR ( " Failed establish a handshake with server '%hs' " , Options . Remote . Host . Address . c_str ( ) ) ;
LogError ( HelloResponse . GetError ( ) ) ;
return - 1 ;
}
FAuthDesc AuthDesc = FAuthDesc : : FromHelloResponse ( * HelloResponse ) ;
HelloConnection . Close ( ) ;
2024-06-20 18:37:09 -04:00
TResult < FAuthToken > AuthToken = Authenticate ( AuthDesc ) ;
2023-11-21 18:25:48 -05:00
if ( ! AuthToken . IsOk ( ) )
{
2024-06-27 00:49:36 -04:00
LogError ( AuthToken . GetError ( ) , L " Failed to authenticate " ) ;
2023-11-21 18:25:48 -05:00
return - 1 ;
}
2023-09-22 20:13:26 -04:00
UNSYNC_LOG ( L " Downloading file: '%hs' " , Options . Args [ 0 ] . c_str ( ) ) ;
2023-09-08 12:19:57 -04:00
UNSYNC_LOG_INDENT ;
std : : unique_ptr < FNativeFile > ResultWriter ;
2023-10-06 13:37:16 -04:00
FPath OutputPath = Options . OutputPath ;
if ( OutputPath . empty ( ) )
{
FPath RequestPath = Options . Args [ 0 ] ;
if ( RequestPath . has_filename ( ) )
{
OutputPath = RequestPath . filename ( ) ;
OutputPath = GetAbsoluteNormalPath ( OutputPath ) ;
}
else
{
UNSYNC_ERROR (
L " Output could not be derived from the request string. "
L " Use `-o <filename>` command line argument to specify it explicitly. " ) ;
return 1 ;
}
}
UNSYNC_LOG ( L " Output file: '%ls' " , OutputPath . wstring ( ) . c_str ( ) ) ;
auto OutputCallback = [ & ResultWriter , & Options , OutputPath ] ( uint64 Size ) - > FIOWriter &
{
2023-09-08 12:19:57 -04:00
UNSYNC_LOG ( L " Size: %llu bytes (%.3f MB) " , llu ( Size ) , SizeMb ( Size ) ) ;
2023-10-06 13:37:16 -04:00
ResultWriter = std : : make_unique < FNativeFile > ( OutputPath , EFileMode : : CreateWriteOnly , Size ) ;
2023-09-08 12:19:57 -04:00
return * ResultWriter ;
} ;
2024-05-07 13:10:40 -04:00
FHttpConnection Connection = FHttpConnection : : CreateDefaultHttps ( Options . Remote ) ;
TResult < > Response = ProxyQuery : : DownloadFile ( Connection , & AuthDesc , Options . Args [ 0 ] , OutputCallback ) ;
2023-08-31 17:15:46 -04:00
2023-08-31 16:20:09 -04:00
if ( Response . IsOk ( ) )
2023-08-24 10:44:55 -04:00
{
2023-10-06 13:37:16 -04:00
UNSYNC_LOG ( L " Output written to file '%ls' " , OutputPath . wstring ( ) . c_str ( ) ) ;
2023-08-24 10:44:55 -04:00
}
2023-08-31 16:20:09 -04:00
else
{
2024-06-27 00:49:36 -04:00
LogError ( Response . GetError ( ) , L " Failed to download file " ) ;
2023-08-31 16:20:09 -04:00
}
2023-08-24 10:44:55 -04:00
return 0 ;
}
2023-08-18 21:41:29 -04:00
2024-06-27 00:49:36 -04:00
int32
CmdQueryHttpGet ( const FCmdQueryOptions & Options )
{
FHttpConnection HttpConnection = FHttpConnection : : CreateDefaultHttps ( Options . Remote ) ;
std : : string BearerToken ;
if ( Options . Remote . bAuthenticationRequired )
{
TResult < ProxyQuery : : FHelloResponse > HelloResponse = ProxyQuery : : Hello ( Options . Remote . Protocol , HttpConnection ) ;
if ( ! HelloResponse . IsOk ( ) )
{
LogError ( HelloResponse . GetError ( ) , L " Failed to query basic server information " ) ;
return - 1 ;
}
FAuthDesc AuthDesc = FAuthDesc : : FromHelloResponse ( * HelloResponse ) ;
TResult < FAuthToken > AuthToken = Authenticate ( AuthDesc ) ;
if ( ! AuthToken . IsOk ( ) )
{
LogError ( AuthToken . GetError ( ) , L " Failed to authenticate with the server " ) ;
return - 1 ;
}
BearerToken = AuthToken - > Access ;
}
// TODO: RequestPath should include leading slash
std : : string RequestUrl = fmt : : format ( " /{} " , Options . Remote . RequestPath ) ;
FHttpRequest Request ;
Request . Method = EHttpMethod : : GET ;
Request . BearerToken = BearerToken ;
Request . Url = RequestUrl ;
FHttpResponse Response = HttpRequest ( HttpConnection , Request ) ;
if ( ! Response . Success ( ) )
{
LogError ( HttpError ( std : : move ( RequestUrl ) , Response . Code ) ) ;
2024-06-28 19:51:06 -04:00
return - 1 ;
2024-06-27 00:49:36 -04:00
}
FPath OutputPath = Options . OutputPath ;
if ( OutputPath . empty ( ) )
{
2024-06-28 19:51:06 -04:00
if ( Response . Buffer . Empty ( ) )
{
return 0 ;
}
else if ( Response . ContentType = = EHttpContentType : : Application_Json | | Response . ContentType = = EHttpContentType : : Text_Plain | |
2024-06-27 00:49:36 -04:00
Response . ContentType = = EHttpContentType : : Text_Html )
{
Response . Buffer . PushBack ( ' \n ' ) ;
Response . Buffer . PushBack ( 0 ) ;
LogPrintf ( ELogLevel : : MachineReadable , L " %hs " , ( const char * ) Response . Buffer . Data ( ) ) ;
return 0 ;
}
else
{
UNSYNC_ERROR ( L " Unexpected response content type. Only plain text or json are supported. Use `-o <filename>` command line argument to write response body to a file. " ) ;
return - 1 ;
}
}
else
{
UNSYNC_LOG ( L " Output file: '%ls' " , OutputPath . wstring ( ) . c_str ( ) ) ;
OutputPath = GetAbsoluteNormalPath ( OutputPath ) ;
if ( EnsureDirectoryExists ( OutputPath . parent_path ( ) ) )
{
if ( WriteBufferToFile ( OutputPath , Response . Buffer ) )
{
UNSYNC_VERBOSE ( L " Wrote bytes: %llu " , llu ( Response . Buffer . Size ( ) ) ) ;
return 0 ;
}
else
{
UNSYNC_ERROR ( L " Failed to write output file '%ls' " , OutputPath . wstring ( ) . c_str ( ) ) ;
}
}
else
{
UNSYNC_ERROR ( L " Failed to create output directory '%ls' " , OutputPath . parent_path ( ) . wstring ( ) . c_str ( ) ) ;
}
return - 1 ;
}
}
2022-03-01 05:53:29 -05:00
int32
CmdQuery ( const FCmdQueryOptions & Options )
{
2024-05-10 00:54:55 -04:00
if ( ! Options . Remote . IsValid ( ) )
{
UNSYNC_ERROR ( L " Server address is not specified or is invalid " ) ;
return 1 ;
}
2022-03-01 05:53:29 -05:00
if ( Options . Query = = " mirrors " )
{
2023-08-18 21:41:29 -04:00
return CmdQueryMirrors ( Options ) ;
2022-03-01 05:53:29 -05:00
}
2023-08-24 10:44:55 -04:00
else if ( Options . Query = = " list " )
{
return CmdQueryList ( Options ) ;
}
2024-05-10 00:54:55 -04:00
else if ( Options . Query = = " search " | | Options . Query = = " explore " )
2023-09-22 20:13:26 -04:00
{
return CmdQuerySearch ( Options ) ;
}
2023-08-31 16:20:09 -04:00
else if ( Options . Query = = " file " )
{
return CmdQueryFile ( Options ) ;
}
2024-06-27 00:49:36 -04:00
if ( Options . Query = = " http-get " )
{
return CmdQueryHttpGet ( Options ) ;
}
2022-03-01 05:53:29 -05:00
else
{
2024-06-27 00:49:36 -04:00
UNSYNC_ERROR ( L " Unknown query command. Allowed options: mirrors, list, search, file, http-get " ) ;
2022-03-01 05:53:29 -05:00
return 1 ;
}
}
2023-06-21 15:01:35 -04:00
TResult < FMirrorInfo >
FindClosestMirror ( const FRemoteDesc & Remote )
{
FMirrorInfoResult MirrorsResult = RunQueryMirrors ( Remote ) ;
if ( MirrorsResult . IsError ( ) )
{
return FError ( MirrorsResult . GetError ( ) ) ;
}
std : : vector < FMirrorInfo > Mirrors = MirrorsResult . GetData ( ) ;
2023-09-08 12:19:57 -04:00
ParallelForEach ( Mirrors , [ ] ( FMirrorInfo & Mirror ) { Mirror . Ping = RunHttpPing ( Mirror . Address , Mirror . Port ) ; } ) ;
2023-06-21 15:01:35 -04:00
std : : sort ( Mirrors . begin ( ) , Mirrors . end ( ) , [ ] ( const FMirrorInfo & InA , const FMirrorInfo & InB ) {
double A = InA . Ping > 0 ? InA . Ping : FLT_MAX ;
double B = InB . Ping > 0 ? InB . Ping : FLT_MAX ;
return A < B ;
} ) ;
for ( const FMirrorInfo & Mirror : Mirrors )
{
if ( Mirror . Ping > 0 )
{
return ResultOk ( Mirror ) ;
}
}
return AppError ( " No reachable mirror found " ) ;
}
2022-03-01 05:53:29 -05:00
} // namespace unsync