|
|
|
@ -1861,3 +1861,850 @@ void NET_Shutdown( void )
@@ -1861,3 +1861,850 @@ void NET_Shutdown( void )
|
|
|
|
|
#endif |
|
|
|
|
net.initialized = false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
================================================= |
|
|
|
|
|
|
|
|
|
HTTP downloader |
|
|
|
|
|
|
|
|
|
================================================= |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
typedef struct httpserver_s |
|
|
|
|
{ |
|
|
|
|
char host[256]; |
|
|
|
|
int port; |
|
|
|
|
char path[PATH_MAX]; |
|
|
|
|
qboolean needfree; |
|
|
|
|
struct httpserver_s *next; |
|
|
|
|
|
|
|
|
|
} httpserver_t; |
|
|
|
|
|
|
|
|
|
enum connectionstate |
|
|
|
|
{ |
|
|
|
|
HTTP_QUEUE = 0, |
|
|
|
|
HTTP_OPENED, |
|
|
|
|
HTTP_SOCKET, |
|
|
|
|
HTTP_NS_RESOLVED, |
|
|
|
|
HTTP_CONNECTED, |
|
|
|
|
HTTP_REQUEST, |
|
|
|
|
HTTP_REQUEST_SENT, |
|
|
|
|
HTTP_RESPONSE_RECEIVED, |
|
|
|
|
HTTP_FREE |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
typedef struct httpfile_s |
|
|
|
|
{ |
|
|
|
|
struct httpfile_s *next; |
|
|
|
|
httpserver_t *server; |
|
|
|
|
char path[PATH_MAX]; |
|
|
|
|
file_t *file; |
|
|
|
|
int socket; |
|
|
|
|
int size; |
|
|
|
|
int downloaded; |
|
|
|
|
int lastchecksize; |
|
|
|
|
float checktime; |
|
|
|
|
float blocktime; |
|
|
|
|
int id; |
|
|
|
|
enum connectionstate state; |
|
|
|
|
qboolean process; |
|
|
|
|
|
|
|
|
|
// query or response
|
|
|
|
|
char buf[BUFSIZ]; |
|
|
|
|
int header_size, query_length, bytes_sent; |
|
|
|
|
} httpfile_t; |
|
|
|
|
|
|
|
|
|
static struct http_static_s |
|
|
|
|
{ |
|
|
|
|
// file and server lists
|
|
|
|
|
httpfile_t *first_file, *last_file; |
|
|
|
|
httpserver_t *first_server, *last_server; |
|
|
|
|
} http; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static convar_t *http_useragent; |
|
|
|
|
static convar_t *http_autoremove; |
|
|
|
|
static convar_t *http_timeout; |
|
|
|
|
static convar_t *http_maxconnections; |
|
|
|
|
|
|
|
|
|
#ifdef _WIN32 |
|
|
|
|
#define ISBLOCK() (pWSAGetLastError() == WSAEWOULDBLOCK ) |
|
|
|
|
#else |
|
|
|
|
#define ISBLOCK() ( errno == EWOULDBLOCK ) |
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
#ifdef _WIN32 |
|
|
|
|
#define ISINPROGRESS() ( pWSAGetLastError() == WSAEINPROGRESS || pWSAGetLastError() == WSAEWOULDBLOCK ) |
|
|
|
|
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined __EMSCRIPTEN__ |
|
|
|
|
#define ISINPROGRESS() ( errno == EINPROGRESS || errno == EWOULDBLOCK ) |
|
|
|
|
#else |
|
|
|
|
#define ISINPROGRESS() ( errno == EINPROGRESS ) // Should give EWOOLDBLOCK if try recv too soon
|
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
#ifdef _WIN32 |
|
|
|
|
#define ISNOTCONN() ( pWSAGetLastError() == WSAEWOULDBLOCK || pWSAGetLastError() == WSAENOTCONN ) |
|
|
|
|
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined __EMSCRIPTEN__ |
|
|
|
|
#define ISNOTCONN() ( errno == EWOULDBLOCK || errno == ENOTCONN ) |
|
|
|
|
#else |
|
|
|
|
#define ISNOTCONN() ( errno == EWOULDBLOCK ) |
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
======================== |
|
|
|
|
HTTP_ClearCustomServers |
|
|
|
|
======================== |
|
|
|
|
*/ |
|
|
|
|
void HTTP_ClearCustomServers( void ) |
|
|
|
|
{ |
|
|
|
|
if( http.first_file ) |
|
|
|
|
return; // may be referenced
|
|
|
|
|
|
|
|
|
|
while( http.first_server && http.first_server->needfree ) |
|
|
|
|
{ |
|
|
|
|
httpserver_t *tmp = http.first_server; |
|
|
|
|
|
|
|
|
|
http.first_server = http.first_server->next; |
|
|
|
|
Mem_Free( tmp ); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
============== |
|
|
|
|
HTTP_FreeFile |
|
|
|
|
|
|
|
|
|
Skip to next server/file |
|
|
|
|
============== |
|
|
|
|
*/ |
|
|
|
|
static void HTTP_FreeFile( httpfile_t *file, qboolean error ) |
|
|
|
|
{ |
|
|
|
|
char incname[256]; |
|
|
|
|
|
|
|
|
|
// Allways close file and socket
|
|
|
|
|
if( file->file ) |
|
|
|
|
FS_Close( file->file ); |
|
|
|
|
|
|
|
|
|
file->file = NULL; |
|
|
|
|
|
|
|
|
|
if( file->socket != -1 ) |
|
|
|
|
pCloseSocket( file->socket ); |
|
|
|
|
|
|
|
|
|
file->socket = -1; |
|
|
|
|
|
|
|
|
|
Q_snprintf( incname, 256, "%s.incomplete", file->path ); |
|
|
|
|
if( error ) |
|
|
|
|
{ |
|
|
|
|
// Switch to next fastdl server if present
|
|
|
|
|
if( file->server && ( file->state > HTTP_QUEUE ) && (file->state != HTTP_FREE ) ) |
|
|
|
|
{ |
|
|
|
|
file->server = file->server->next; |
|
|
|
|
file->state = HTTP_QUEUE; // Reset download state, HTTP_Run() will open file again
|
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Called because there was no servers to download, free file now
|
|
|
|
|
if( http_autoremove->value == 1 ) // remove broken file
|
|
|
|
|
FS_Delete( incname ); |
|
|
|
|
else // autoremove disabled, keep file
|
|
|
|
|
Con_Printf( "cannot download %s from any server. " |
|
|
|
|
"You may remove %s now\n", file->path, incname ); // Warn about trash file
|
|
|
|
|
|
|
|
|
|
if( file->process ) |
|
|
|
|
CL_ProcessFile( false, file->path ); // Process file, increase counter
|
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
// Success, rename and process file
|
|
|
|
|
char name[256]; |
|
|
|
|
|
|
|
|
|
Q_snprintf( name, 256, "%s", file->path ); |
|
|
|
|
FS_Rename( incname, name ); |
|
|
|
|
|
|
|
|
|
if( file->process ) |
|
|
|
|
CL_ProcessFile( true, name ); |
|
|
|
|
else |
|
|
|
|
Con_Printf( "successfully downloaded %s, processing disabled!\n", name ); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
file->state = HTTP_FREE; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
=================== |
|
|
|
|
HTTP_AutoClean |
|
|
|
|
|
|
|
|
|
remove files with HTTP_FREE state from list |
|
|
|
|
=================== |
|
|
|
|
*/ |
|
|
|
|
static void HTTP_AutoClean( void ) |
|
|
|
|
{ |
|
|
|
|
httpfile_t *curfile, *prevfile = 0; |
|
|
|
|
|
|
|
|
|
// clean all files marked to free
|
|
|
|
|
for( curfile = http.first_file; curfile; curfile = curfile->next ) |
|
|
|
|
{ |
|
|
|
|
if( curfile->state != HTTP_FREE ) |
|
|
|
|
{ |
|
|
|
|
prevfile = curfile; |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if( curfile == http.first_file ) |
|
|
|
|
{ |
|
|
|
|
http.first_file = http.first_file->next; |
|
|
|
|
Mem_Free( curfile ); |
|
|
|
|
curfile = http.first_file; |
|
|
|
|
if( !curfile ) |
|
|
|
|
break; |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if( prevfile ) |
|
|
|
|
prevfile->next = curfile->next; |
|
|
|
|
Mem_Free( curfile ); |
|
|
|
|
curfile = prevfile; |
|
|
|
|
if( !curfile ) |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
http.last_file = prevfile; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
=================== |
|
|
|
|
HTTP_ProcessStream |
|
|
|
|
|
|
|
|
|
process incoming data |
|
|
|
|
=================== |
|
|
|
|
*/ |
|
|
|
|
static qboolean HTTP_ProcessStream( httpfile_t *curfile ) |
|
|
|
|
{ |
|
|
|
|
char buf[BUFSIZ+1]; |
|
|
|
|
char *begin = 0; |
|
|
|
|
int res; |
|
|
|
|
|
|
|
|
|
while( ( res = pRecv( curfile->socket, buf, BUFSIZ, 0 ) ) > 0) // if we got there, we are receiving data
|
|
|
|
|
{ |
|
|
|
|
curfile->blocktime = 0; |
|
|
|
|
|
|
|
|
|
if( curfile->state < HTTP_RESPONSE_RECEIVED ) // Response still not received
|
|
|
|
|
{ |
|
|
|
|
buf[res] = 0; // string break to search \r\n\r\n
|
|
|
|
|
memcpy( curfile->buf + curfile->header_size, buf, res ); |
|
|
|
|
begin = Q_strstr( curfile->buf, "\r\n\r\n" ); |
|
|
|
|
|
|
|
|
|
if( begin ) // Got full header
|
|
|
|
|
{ |
|
|
|
|
int cutheadersize = begin - curfile->buf + 4; // after that begin of data
|
|
|
|
|
char *length; |
|
|
|
|
|
|
|
|
|
Con_Reportf( "HTTP: Got response!\n" ); |
|
|
|
|
|
|
|
|
|
if( !Q_strstr( curfile->buf, "200 OK" ) ) |
|
|
|
|
{ |
|
|
|
|
*begin = 0; // cut string to print out response
|
|
|
|
|
begin = Q_strchr( curfile->buf, '\r' ); |
|
|
|
|
|
|
|
|
|
if( !begin ) begin = Q_strchr( curfile->buf, '\n' ); |
|
|
|
|
if( begin ) |
|
|
|
|
*begin = 0; |
|
|
|
|
|
|
|
|
|
Con_Printf( S_ERROR "bad response: %s\n", curfile->buf ); |
|
|
|
|
HTTP_FreeFile( curfile, true ); |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// print size
|
|
|
|
|
length = Q_stristr( curfile->buf, "Content-Length: " ); |
|
|
|
|
if( length ) |
|
|
|
|
{ |
|
|
|
|
int size = Q_atoi( length += 16 ); |
|
|
|
|
|
|
|
|
|
Con_Reportf( "HTTP: File size is %d\n", size ); |
|
|
|
|
|
|
|
|
|
if( ( curfile->size != -1 ) && ( curfile->size != size ) ) // check size if specified, not used
|
|
|
|
|
Con_Reportf( S_WARN "Server reports wrong file size!\n" ); |
|
|
|
|
|
|
|
|
|
curfile->size = size; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if( curfile->size == -1 ) |
|
|
|
|
{ |
|
|
|
|
// Usually fastdl's reports file size if link is correct
|
|
|
|
|
Con_Printf( S_ERROR "file size is unknown, refusing download!\n" ); |
|
|
|
|
HTTP_FreeFile( curfile, true ); |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
curfile->state = HTTP_RESPONSE_RECEIVED; // got response, let's start download
|
|
|
|
|
begin += 4; |
|
|
|
|
|
|
|
|
|
// Write remaining message part
|
|
|
|
|
if( res - cutheadersize - curfile->header_size > 0 ) |
|
|
|
|
{ |
|
|
|
|
int ret = FS_Write( curfile->file, begin, res - cutheadersize - curfile->header_size ); |
|
|
|
|
|
|
|
|
|
if( ret != res - cutheadersize - curfile->header_size ) // could not write file
|
|
|
|
|
{ |
|
|
|
|
// close it and go to next
|
|
|
|
|
Con_Printf( S_ERROR "write failed for %s!\n", curfile->path ); |
|
|
|
|
HTTP_FreeFile( curfile, true ); |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
curfile->downloaded += ret; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
curfile->header_size += res; |
|
|
|
|
} |
|
|
|
|
else if( res > 0 ) |
|
|
|
|
{ |
|
|
|
|
// data download
|
|
|
|
|
int ret = FS_Write( curfile->file, buf, res ); |
|
|
|
|
|
|
|
|
|
if ( ret != res ) |
|
|
|
|
{ |
|
|
|
|
// close it and go to next
|
|
|
|
|
Con_Printf( S_ERROR "write failed for %s!\n", curfile->path ); |
|
|
|
|
curfile->state = HTTP_FREE; |
|
|
|
|
HTTP_FreeFile( curfile, true ); |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
curfile->downloaded += ret; |
|
|
|
|
curfile->lastchecksize += ret; |
|
|
|
|
|
|
|
|
|
// as after it will run in same frame
|
|
|
|
|
if( curfile->checktime > 5 ) |
|
|
|
|
{ |
|
|
|
|
curfile->checktime = 0; |
|
|
|
|
Con_Reportf( "download speed %f KB/s\n", (float)curfile->lastchecksize / ( 5.0 * 1024 ) ); |
|
|
|
|
curfile->lastchecksize = 0; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
curfile->checktime += host.frametime; |
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
============== |
|
|
|
|
HTTP_Run |
|
|
|
|
|
|
|
|
|
Download next file block of each active file |
|
|
|
|
Call every frame |
|
|
|
|
============== |
|
|
|
|
*/ |
|
|
|
|
void HTTP_Run( void ) |
|
|
|
|
{ |
|
|
|
|
httpfile_t *curfile; |
|
|
|
|
int iActiveCount = 0; |
|
|
|
|
int iProgressCount = 0; |
|
|
|
|
float flProgress; |
|
|
|
|
qboolean fResolving = false; |
|
|
|
|
|
|
|
|
|
for( curfile = http.first_file; curfile; curfile = curfile->next ) |
|
|
|
|
{ |
|
|
|
|
int res; |
|
|
|
|
struct sockaddr addr; |
|
|
|
|
|
|
|
|
|
if( curfile->state == HTTP_FREE ) |
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
if( curfile->state == HTTP_QUEUE ) |
|
|
|
|
{ |
|
|
|
|
char name[PATH_MAX]; |
|
|
|
|
|
|
|
|
|
if( iActiveCount > http_maxconnections->value ) |
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
if( !curfile->server ) |
|
|
|
|
{ |
|
|
|
|
Con_Printf( S_ERROR "no servers to download %s!\n", curfile->path ); |
|
|
|
|
HTTP_FreeFile( curfile, true ); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Con_Reportf( "HTTP: Starting download %s from %s\n", curfile->path, curfile->server->host ); |
|
|
|
|
Q_snprintf( name, PATH_MAX, "%s.incomplete", curfile->path ); |
|
|
|
|
|
|
|
|
|
curfile->file = FS_Open( name, "wb", true ); |
|
|
|
|
|
|
|
|
|
if( !curfile->file ) |
|
|
|
|
{ |
|
|
|
|
Con_Printf( S_ERROR "cannot open %s!\n", name ); |
|
|
|
|
HTTP_FreeFile( curfile, true ); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
curfile->state = HTTP_OPENED; |
|
|
|
|
curfile->blocktime = 0; |
|
|
|
|
curfile->downloaded = 0; |
|
|
|
|
curfile->lastchecksize = 0; |
|
|
|
|
curfile->checktime = 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
iActiveCount++; |
|
|
|
|
|
|
|
|
|
if( curfile->state < HTTP_SOCKET ) // Socket is not created
|
|
|
|
|
{ |
|
|
|
|
dword mode; |
|
|
|
|
|
|
|
|
|
curfile->socket = pSocket( AF_INET, SOCK_STREAM, IPPROTO_TCP ); |
|
|
|
|
|
|
|
|
|
// Now set non-blocking mode
|
|
|
|
|
// You may skip this if not supported by system,
|
|
|
|
|
// but download will lock engine, maybe you will need to add manual returns
|
|
|
|
|
#if defined(_WIN32) || defined(__APPLE__) || defined(__FreeBSD__) || defined __EMSCRIPTEN__ |
|
|
|
|
mode = 1; |
|
|
|
|
pIoctlSocket( curfile->socket, FIONBIO, &mode ); |
|
|
|
|
#else |
|
|
|
|
// SOCK_NONBLOCK is not portable, so use fcntl
|
|
|
|
|
fcntl( curfile->socket, F_SETFL, fcntl( curfile->socket, F_GETFL, 0 ) | O_NONBLOCK ); |
|
|
|
|
#endif |
|
|
|
|
curfile->state = HTTP_SOCKET; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if( curfile->state < HTTP_NS_RESOLVED ) |
|
|
|
|
{ |
|
|
|
|
if( fResolving ) |
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
res = NET_StringToSockaddr( va( "%s:%d", curfile->server->host, curfile->server->port ), &addr, true ); |
|
|
|
|
|
|
|
|
|
if( res == 2 ) |
|
|
|
|
{ |
|
|
|
|
fResolving = true; |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if( !res ) |
|
|
|
|
{ |
|
|
|
|
Con_Printf( S_ERROR "failed to resolve server address for %s!\n", curfile->server->host ); |
|
|
|
|
HTTP_FreeFile( curfile, true ); // Cannot connect
|
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
curfile->state = HTTP_NS_RESOLVED; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if( curfile->state < HTTP_CONNECTED ) // Connection not enstabilished
|
|
|
|
|
{ |
|
|
|
|
res = pConnect( curfile->socket, &addr, sizeof( struct sockaddr ) ); |
|
|
|
|
|
|
|
|
|
if( res ) |
|
|
|
|
{ |
|
|
|
|
if( ISINPROGRESS() ) // Should give EWOOLDBLOCK if try recv too soon
|
|
|
|
|
curfile->state = HTTP_CONNECTED; |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
Con_Printf( S_ERROR "cannot connect to server: %s\n", NET_ErrorString( ) ); |
|
|
|
|
HTTP_FreeFile( curfile, true ); // Cannot connect
|
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
continue; // skip to next file
|
|
|
|
|
} |
|
|
|
|
curfile->state = HTTP_CONNECTED; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if( curfile->state < HTTP_REQUEST ) // Request not formatted
|
|
|
|
|
{ |
|
|
|
|
curfile->query_length = Q_snprintf( curfile->buf, sizeof( curfile->buf ), |
|
|
|
|
"GET %s%s HTTP/1.0\r\n" |
|
|
|
|
"Host: %s\r\n" |
|
|
|
|
"User-Agent: %s\r\n\r\n", curfile->server->path, |
|
|
|
|
curfile->path, curfile->server->host, http_useragent->string ); |
|
|
|
|
curfile->header_size = 0; |
|
|
|
|
curfile->bytes_sent = 0; |
|
|
|
|
curfile->state = HTTP_REQUEST; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if( curfile->state < HTTP_REQUEST_SENT ) // Request not sent
|
|
|
|
|
{ |
|
|
|
|
qboolean wait = false; |
|
|
|
|
|
|
|
|
|
while( curfile->bytes_sent < curfile->query_length ) |
|
|
|
|
{ |
|
|
|
|
res = pSend( curfile->socket, curfile->buf + curfile->bytes_sent, curfile->query_length - curfile->bytes_sent, 0 ); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if( res < 0 ) |
|
|
|
|
{ |
|
|
|
|
if( !ISNOTCONN() ) |
|
|
|
|
{ |
|
|
|
|
Con_Printf( S_ERROR "failed to send request: %s\n", NET_ErrorString() ); |
|
|
|
|
HTTP_FreeFile( curfile, true ); |
|
|
|
|
wait = true; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
// blocking while waiting connection
|
|
|
|
|
// increase counter when blocking
|
|
|
|
|
curfile->blocktime += host.frametime; |
|
|
|
|
wait = true; |
|
|
|
|
|
|
|
|
|
if( curfile->blocktime > http_timeout->value ) |
|
|
|
|
{ |
|
|
|
|
Con_Printf( S_ERROR "timeout on request send:\n%s\n", curfile->buf ); |
|
|
|
|
HTTP_FreeFile( curfile, true ); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
curfile->bytes_sent += res; |
|
|
|
|
curfile->blocktime = 0; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if( wait ) |
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
Con_Reportf( "HTTP: Request sent!\n"); |
|
|
|
|
memset( curfile->buf, 0, sizeof( curfile->buf ) ); |
|
|
|
|
curfile->state = HTTP_REQUEST_SENT; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if( !HTTP_ProcessStream( curfile ) ) |
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
if( curfile->size > 0 ) |
|
|
|
|
{ |
|
|
|
|
flProgress += (float)curfile->downloaded / curfile->size; |
|
|
|
|
iProgressCount++; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if( curfile->size > 0 && curfile->downloaded >= curfile->size ) |
|
|
|
|
{ |
|
|
|
|
HTTP_FreeFile( curfile, false ); // success
|
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
else if( !ISBLOCK() ) |
|
|
|
|
Con_Reportf( "problem downloading %s:\n%s\n", curfile->path, NET_ErrorString() ); |
|
|
|
|
else |
|
|
|
|
curfile->blocktime += host.frametime; |
|
|
|
|
|
|
|
|
|
if( curfile->blocktime > http_timeout->value ) |
|
|
|
|
{ |
|
|
|
|
Con_Printf( S_ERROR "timeout on receiving data!\n"); |
|
|
|
|
HTTP_FreeFile( curfile, true ); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// update progress
|
|
|
|
|
Cvar_SetValue( "scr_download", flProgress/iProgressCount * 100 ); |
|
|
|
|
|
|
|
|
|
HTTP_AutoClean(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
=================== |
|
|
|
|
HTTP_AddDownload |
|
|
|
|
|
|
|
|
|
Add new download to end of queue |
|
|
|
|
=================== |
|
|
|
|
*/ |
|
|
|
|
void HTTP_AddDownload( const char *path, int size, qboolean process ) |
|
|
|
|
{ |
|
|
|
|
httpfile_t *httpfile = Z_Calloc( sizeof( httpfile_t ) ); |
|
|
|
|
|
|
|
|
|
Con_Reportf( "File %s queued to download\n", path ); |
|
|
|
|
|
|
|
|
|
httpfile->size = size; |
|
|
|
|
httpfile->downloaded = 0; |
|
|
|
|
httpfile->socket = -1; |
|
|
|
|
Q_strncpy ( httpfile->path, path, sizeof( httpfile->path ) ); |
|
|
|
|
|
|
|
|
|
if( http.last_file ) |
|
|
|
|
{ |
|
|
|
|
// Add next to last download
|
|
|
|
|
httpfile->id = http.last_file->id + 1; |
|
|
|
|
http.last_file->next= httpfile; |
|
|
|
|
http.last_file = httpfile; |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
// It will be the only download
|
|
|
|
|
httpfile->id = 0; |
|
|
|
|
http.last_file = http.first_file = httpfile; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
httpfile->file = NULL; |
|
|
|
|
httpfile->next = NULL; |
|
|
|
|
httpfile->state = HTTP_QUEUE; |
|
|
|
|
httpfile->server = http.first_server; |
|
|
|
|
httpfile->process = process; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
=============== |
|
|
|
|
HTTP_Download_f |
|
|
|
|
|
|
|
|
|
Console wrapper |
|
|
|
|
=============== |
|
|
|
|
*/ |
|
|
|
|
static void HTTP_Download_f( void ) |
|
|
|
|
{ |
|
|
|
|
if( Cmd_Argc() < 2 ) |
|
|
|
|
{ |
|
|
|
|
Con_Printf( S_USAGE "download <gamedir_path>\n"); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
HTTP_AddDownload( Cmd_Argv( 1 ), -1, false ); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
============== |
|
|
|
|
HTTP_ParseURL |
|
|
|
|
============== |
|
|
|
|
*/ |
|
|
|
|
static httpserver_t *HTTP_ParseURL( const char *url ) |
|
|
|
|
{ |
|
|
|
|
httpserver_t *server; |
|
|
|
|
int i; |
|
|
|
|
|
|
|
|
|
url = Q_strstr( url, "http://" ); |
|
|
|
|
|
|
|
|
|
if( !url ) |
|
|
|
|
return NULL; |
|
|
|
|
|
|
|
|
|
url += 7; |
|
|
|
|
server = Z_Calloc( sizeof( httpserver_t ) ); |
|
|
|
|
i = 0; |
|
|
|
|
|
|
|
|
|
while( *url && ( *url != ':' ) && ( *url != '/' ) && ( *url != '\r' ) && ( *url != '\n' ) ) |
|
|
|
|
{ |
|
|
|
|
if( i > sizeof( server->host ) ) |
|
|
|
|
return NULL; |
|
|
|
|
|
|
|
|
|
server->host[i++] = *url++; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
server->host[i] = 0; |
|
|
|
|
|
|
|
|
|
if( *url == ':' ) |
|
|
|
|
{ |
|
|
|
|
server->port = Q_atoi( ++url ); |
|
|
|
|
|
|
|
|
|
while( *url && ( *url != '/' ) && ( *url != '\r' ) && ( *url != '\n' ) ) |
|
|
|
|
url++; |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
server->port = 80; |
|
|
|
|
|
|
|
|
|
i = 0; |
|
|
|
|
|
|
|
|
|
while( *url && ( *url != '\r' ) && ( *url != '\n' ) ) |
|
|
|
|
{ |
|
|
|
|
if( i > sizeof( server->path ) ) |
|
|
|
|
return NULL; |
|
|
|
|
|
|
|
|
|
server->path[i++] = *url++; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
server->path[i] = 0; |
|
|
|
|
server->next = NULL; |
|
|
|
|
server->needfree = false; |
|
|
|
|
|
|
|
|
|
return server; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
======================= |
|
|
|
|
HTTP_AddCustomServer |
|
|
|
|
======================= |
|
|
|
|
*/ |
|
|
|
|
void HTTP_AddCustomServer( const char *url ) |
|
|
|
|
{ |
|
|
|
|
httpserver_t *server = HTTP_ParseURL( url ); |
|
|
|
|
|
|
|
|
|
if( !server ) |
|
|
|
|
{ |
|
|
|
|
Con_Printf( S_ERROR "\"%s\" is not valid url!\n", url ); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
server->needfree = true; |
|
|
|
|
server->next = http.first_server; |
|
|
|
|
http.first_server = server; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
======================= |
|
|
|
|
HTTP_AddCustomServer_f |
|
|
|
|
======================= |
|
|
|
|
*/ |
|
|
|
|
static void HTTP_AddCustomServer_f( void ) |
|
|
|
|
{ |
|
|
|
|
if( Cmd_Argc() == 2 ) |
|
|
|
|
{ |
|
|
|
|
HTTP_AddCustomServer( Cmd_Argv( 1 ) ); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
============ |
|
|
|
|
HTTP_Clear_f |
|
|
|
|
|
|
|
|
|
Clear all queue |
|
|
|
|
============ |
|
|
|
|
*/ |
|
|
|
|
static void HTTP_Clear_f( void ) |
|
|
|
|
{ |
|
|
|
|
http.last_file = NULL; |
|
|
|
|
|
|
|
|
|
while( http.first_file ) |
|
|
|
|
{ |
|
|
|
|
httpfile_t *file = http.first_file; |
|
|
|
|
|
|
|
|
|
http.first_file = http.first_file->next; |
|
|
|
|
|
|
|
|
|
if( file->file ) |
|
|
|
|
FS_Close( file->file ); |
|
|
|
|
|
|
|
|
|
if( file->socket != -1 ) |
|
|
|
|
pCloseSocket ( file->socket ); |
|
|
|
|
|
|
|
|
|
Mem_Free( file ); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
============== |
|
|
|
|
HTTP_Cancel_f |
|
|
|
|
|
|
|
|
|
Stop current download, skip to next file |
|
|
|
|
============== |
|
|
|
|
*/ |
|
|
|
|
static void HTTP_Cancel_f( void ) |
|
|
|
|
{ |
|
|
|
|
if( !http.first_file ) |
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
http.first_file->state = HTTP_FREE; |
|
|
|
|
HTTP_FreeFile( http.first_file, true ); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
============= |
|
|
|
|
HTTP_Skip_f |
|
|
|
|
|
|
|
|
|
Stop current download, skip to next server |
|
|
|
|
============= |
|
|
|
|
*/ |
|
|
|
|
static void HTTP_Skip_f( void ) |
|
|
|
|
{ |
|
|
|
|
if( http.first_file ) |
|
|
|
|
HTTP_FreeFile( http.first_file, true ); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
============= |
|
|
|
|
HTTP_List_f |
|
|
|
|
|
|
|
|
|
Print all pending downloads to console |
|
|
|
|
============= |
|
|
|
|
*/ |
|
|
|
|
static void HTTP_List_f( void ) |
|
|
|
|
{ |
|
|
|
|
httpfile_t *file = http.first_file; |
|
|
|
|
|
|
|
|
|
while( file ) |
|
|
|
|
{ |
|
|
|
|
if ( file->server ) |
|
|
|
|
Con_Printf ( "\t%d %d http://%s:%d/%s%s %d\n", file->id, file->state, |
|
|
|
|
file->server->host, file->server->port, file->server->path, |
|
|
|
|
file->path, file->downloaded ); |
|
|
|
|
else |
|
|
|
|
Con_Printf ( "\t%d %d (no server) %s\n", file->id, file->state, file->path ); |
|
|
|
|
|
|
|
|
|
file = file->next; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
================ |
|
|
|
|
HTTP_ResetProcessState |
|
|
|
|
|
|
|
|
|
When connected to new server, all old files should not increase counter |
|
|
|
|
================ |
|
|
|
|
*/ |
|
|
|
|
void HTTP_ResetProcessState( void ) |
|
|
|
|
{ |
|
|
|
|
httpfile_t *file = http.first_file; |
|
|
|
|
|
|
|
|
|
while( file ) |
|
|
|
|
{ |
|
|
|
|
file->process = false; |
|
|
|
|
file = file->next; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
============= |
|
|
|
|
HTTP_Init |
|
|
|
|
============= |
|
|
|
|
*/ |
|
|
|
|
void HTTP_Init( void ) |
|
|
|
|
{ |
|
|
|
|
char *serverfile, *line, token[1024]; |
|
|
|
|
|
|
|
|
|
http.last_server = NULL; |
|
|
|
|
|
|
|
|
|
http.first_file = http.last_file = NULL; |
|
|
|
|
|
|
|
|
|
Cmd_AddCommand("http_download", &HTTP_Download_f, "add file to download queue"); |
|
|
|
|
Cmd_AddCommand("http_skip", &HTTP_Skip_f, "skip current download server"); |
|
|
|
|
Cmd_AddCommand("http_cancel", &HTTP_Cancel_f, "cancel current download"); |
|
|
|
|
Cmd_AddCommand("http_clear", &HTTP_Clear_f, "cancel all downloads"); |
|
|
|
|
Cmd_AddCommand("http_list", &HTTP_List_f, "list all queued downloads"); |
|
|
|
|
Cmd_AddCommand("http_addcustomserver", &HTTP_AddCustomServer_f, "add custom fastdl server"); |
|
|
|
|
http_useragent = Cvar_Get( "http_useragent", "xash3d", FCVAR_ARCHIVE, "User-Agent string" ); |
|
|
|
|
http_autoremove = Cvar_Get( "http_autoremove", "1", FCVAR_ARCHIVE, "remove broken files" ); |
|
|
|
|
http_timeout = Cvar_Get( "http_timeout", "45", FCVAR_ARCHIVE, "timeout for http downloader" ); |
|
|
|
|
http_maxconnections = Cvar_Get( "http_maxconnections", "4", FCVAR_ARCHIVE, "maximum http connection number" ); |
|
|
|
|
|
|
|
|
|
// Read servers from fastdl.txt
|
|
|
|
|
line = serverfile = (char *)FS_LoadFile( "fastdl.txt", 0, false ); |
|
|
|
|
|
|
|
|
|
if( serverfile ) |
|
|
|
|
{ |
|
|
|
|
while( ( line = COM_ParseFile( line, token ) ) ) |
|
|
|
|
{ |
|
|
|
|
httpserver_t *server = HTTP_ParseURL( token ); |
|
|
|
|
|
|
|
|
|
if( !server ) |
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
if( !http.last_server ) |
|
|
|
|
http.last_server = http.first_server = server; |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
http.last_server->next = server; |
|
|
|
|
http.last_server = server; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Mem_Free( serverfile ); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
==================== |
|
|
|
|
HTTP_Shutdown |
|
|
|
|
==================== |
|
|
|
|
*/ |
|
|
|
|
void HTTP_Shutdown( void ) |
|
|
|
|
{ |
|
|
|
|
HTTP_Clear_f(); |
|
|
|
|
|
|
|
|
|
while( http.first_server ) |
|
|
|
|
{ |
|
|
|
|
httpserver_t *tmp = http.first_server; |
|
|
|
|
|
|
|
|
|
http.first_server = http.first_server->next; |
|
|
|
|
Mem_Free( tmp ); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
http.last_server = 0; |
|
|
|
|
} |
|
|
|
|