diff --git a/engine/client/cl_custom.c b/engine/client/cl_custom.c index a152fc25..d743fc60 100644 --- a/engine/client/cl_custom.c +++ b/engine/client/cl_custom.c @@ -70,6 +70,13 @@ qboolean CL_CheckFile( sizebuf_t *msg, resource_t *pResource ) return true; } + if( cl.downloadUrl[0] ) + { + HTTP_AddDownload( filepath, pResource->nDownloadSize, true ); + host.downloadcount++; + return false; + } + MSG_BeginClientCmd( msg, clc_stringcmd ); MSG_WriteString( msg, va( "dlfile %s", filepath )); host.downloadcount++; @@ -140,4 +147,4 @@ void CL_ClearResourceLists( void ) { CL_ClearResourceList( &cl.resourcesneeded ); CL_ClearResourceList( &cl.resourcesonhand ); -} \ No newline at end of file +} diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 84548de3..729b7e70 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -1334,6 +1334,7 @@ void CL_ClearState( void ) Cvar_SetValue( "scr_download", -1.0f ); Cvar_SetValue( "scr_loading", 0.0f ); host.allow_console = host.allow_console_init; + HTTP_ClearCustomServers(); } /* diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index db23748d..8a36b823 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -534,7 +534,11 @@ void CL_BatchResourceRequest( qboolean initialize ) if( cls.state != ca_disconnected ) { - if( !MSG_GetNumBytesWritten( &msg ) && CL_PrecacheResources( )) + if( !cl.downloadUrl[0] && !MSG_GetNumBytesWritten( &msg ) && CL_PrecacheResources( )) + { + CL_RegisterResources( &msg ); + } + if( cl.downloadUrl[0] && host.downloadcount == 0 && CL_PrecacheResources( ) ) { CL_RegisterResources( &msg ); } @@ -1702,16 +1706,23 @@ CL_ParseResLocation */ void CL_ParseResLocation( sizebuf_t *msg ) { - const char *url = MSG_ReadString( msg ); + const char *data = MSG_ReadString( msg ); + char token[256]; + + if( Q_strlen( data ) > 256 ) + { + Con_Printf( S_ERROR "Resource location too long!\n" ); + return; + } - if( url && ( !Q_strnicmp( "http://", url, 7 ) || !Q_strnicmp( "https://", url, 8 ))) + while( ( data = COM_ParseFile( data, token ) ) ) { - const char *lastSlash = Q_strrchr( url, '/' ); + Con_Reportf( "Adding %s as download location\n", token ); + + if( !cl.downloadUrl[0] ) + Q_strncpy( cl.downloadUrl, token, sizeof( token ) ); - if( lastSlash && lastSlash[1] == '\0' ) - Q_strncpy( cl.downloadUrl, url, sizeof( cl.downloadUrl )); - else Q_snprintf( cl.downloadUrl, sizeof( cl.downloadUrl ), "%s/", url ); - Con_Reportf( "Using %s as primary download location\n", cl.downloadUrl ); + HTTP_AddCustomServer( token ); } } diff --git a/engine/common/common.h b/engine/common/common.h index 0ee4dfd2..6a728b1b 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -1053,6 +1053,7 @@ qboolean CL_IsBackgroundDemo( void ); qboolean CL_IsBackgroundMap( void ); qboolean SV_Initialized( void ); qboolean CL_LoadProgs( const char *name ); +void CL_ProcessFile( qboolean successfully_received, const char *filename ); int SV_GetSaveComment( const char *savename, char *comment ); qboolean SV_NewGame( const char *mapName, qboolean loadGame ); void SV_ClipPMoveToEntity( struct physent_s *pe, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, struct pmtrace_s *tr ); diff --git a/engine/common/dedicated.c b/engine/common/dedicated.c index 6e573ae2..db647c76 100644 --- a/engine/common/dedicated.c +++ b/engine/common/dedicated.c @@ -84,6 +84,11 @@ const char *svc_strings[256] = "svc_unused63", }; +void CL_ProcessFile( qboolean successfully_received, const char *filename ) +{ + +} + int CL_Active( void ) { return false; diff --git a/engine/common/host.c b/engine/common/host.c index 24f968cc..e117abb5 100644 --- a/engine/common/host.c +++ b/engine/common/host.c @@ -542,6 +542,7 @@ void Host_Frame( float time ) Host_GetCommands (); // dedicated in Host_ServerFrame (); // server frame Host_ClientFrame (); // client frame + HTTP_Run(); // both server and client host.framecount++; } @@ -978,6 +979,7 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa SV_Init(); CL_Init(); + HTTP_Init(); ID_Init(); if( Host_IsDedicated() ) @@ -1060,6 +1062,7 @@ void EXPORT Host_Shutdown( void ) Mod_Shutdown(); NET_Shutdown(); + HTTP_Shutdown(); Host_FreeCommon(); #ifdef _WIN32 Wcon_DestroyConsole(); diff --git a/engine/common/net_ws.c b/engine/common/net_ws.c index 08e03261..4879f556 100644 --- a/engine/common/net_ws.c +++ b/engine/common/net_ws.c @@ -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 \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; +} diff --git a/engine/common/net_ws.h b/engine/common/net_ws.h index c8f45d6f..e104820b 100644 --- a/engine/common/net_ws.h +++ b/engine/common/net_ws.h @@ -67,4 +67,11 @@ void NET_ClearLagData( qboolean bClient, qboolean bServer ); qboolean CL_LegacyMode( void ); #endif +void HTTP_AddCustomServer( const char *url ); +void HTTP_AddDownload( const char *path, int size, qboolean process ); +void HTTP_ClearCustomServers( void ); +void HTTP_Shutdown( void ); +void HTTP_Init( void ); +void HTTP_Run( void ); + #endif//NET_WS_H