]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - libcurl.c
fix lightmap loading from TGAs; fix display of QC-initiated downloads
[xonotic/darkplaces.git] / libcurl.c
index 10e8aea39aefe18d1acdc7852d1a1c92c514973f..f97946fe1bd2b365c1fe399d429837574eab03dc 100644 (file)
--- a/libcurl.c
+++ b/libcurl.c
@@ -2,10 +2,11 @@
 #include "fs.h"
 #include "libcurl.h"
 
-static cvar_t cl_curl_maxdownloads = {1, "cl_curl_maxdownloads","1", "maximum number of concurrent HTTP/FTP downloads"};
-static cvar_t cl_curl_maxspeed = {1, "cl_curl_maxspeed","100", "maximum download speed (KiB/s)"};
-static cvar_t sv_curl_defaulturl = {1, "sv_curl_defaulturl","", "default autodownload source URL"};
-static cvar_t cl_curl_enabled = {1, "cl_curl_enabled","0", "whether client's download support is enabled"};
+static cvar_t cl_curl_maxdownloads = {CVAR_SAVE, "cl_curl_maxdownloads","1", "maximum number of concurrent HTTP/FTP downloads"};
+static cvar_t cl_curl_maxspeed = {CVAR_SAVE, "cl_curl_maxspeed","100", "maximum download speed (KiB/s)"};
+static cvar_t sv_curl_defaulturl = {CVAR_SAVE, "sv_curl_defaulturl","", "default autodownload source URL"};
+static cvar_t sv_curl_serverpackages = {CVAR_SAVE, "sv_curl_serverpackages","", "list of required files for the clients, separated by spaces"};
+static cvar_t cl_curl_enabled = {CVAR_SAVE, "cl_curl_enabled","1", "whether client's download support is enabled"};
 
 /*
 =================================================================
@@ -50,6 +51,8 @@ typedef enum
        CINIT(RESUME_FROM, LONG, 21),
        CINIT(FOLLOWLOCATION, LONG, 52),  /* use Location: Luke! */
        CINIT(PRIVATE, OBJECTPOINT, 103),
+       CINIT(LOW_SPEED_LIMIT, LONG , 19),
+       CINIT(LOW_SPEED_TIME, LONG, 20),
 }
 CURLoption;
 typedef enum
@@ -177,11 +180,128 @@ typedef struct downloadinfo_s
        unsigned long bytes_received;
        struct downloadinfo_s *next, *prev;
        qboolean forthismap;
+
+       unsigned char *buffer;
+       size_t buffersize;
+       curl_callback_t callback;
+       void *callback_data;
 }
 downloadinfo;
 static downloadinfo *downloads = NULL;
 static int numdownloads = 0;
 
+static qboolean noclear = FALSE;
+
+static int numdownloads_fail = 0;
+static int numdownloads_success = 0;
+static int numdownloads_added = 0;
+static char command_when_done[256] = "";
+static char command_when_error[256] = "";
+
+/*
+====================
+Curl_CommandWhenDone
+
+Sets the command which is to be executed when the last download completes AND
+all downloads since last server connect ended with a successful status.
+Setting the command to NULL clears it.
+====================
+*/
+void Curl_CommandWhenDone(const char *cmd)
+{
+       if(!curl_dll)
+               return;
+       if(cmd)
+               strlcpy(command_when_done, cmd, sizeof(command_when_done));
+       else
+               *command_when_done = 0;
+}
+
+/*
+FIXME
+Do not use yet. Not complete.
+Problem: what counts as an error?
+*/
+
+void Curl_CommandWhenError(const char *cmd)
+{
+       if(!curl_dll)
+               return;
+       if(cmd)
+               strlcpy(command_when_error, cmd, sizeof(command_when_error));
+       else
+               *command_when_error = 0;
+}
+
+/*
+====================
+Curl_Clear_forthismap
+
+Clears the "will disconnect on failure" flags.
+====================
+*/
+void Curl_Clear_forthismap()
+{
+       downloadinfo *di;
+       if(noclear)
+               return;
+       for(di = downloads; di; di = di->next)
+               di->forthismap = false;
+       Curl_CommandWhenError(NULL);
+       Curl_CommandWhenDone(NULL);
+       numdownloads_fail = 0;
+       numdownloads_success = 0;
+       numdownloads_added = 0;
+}
+
+/*
+====================
+Curl_Have_forthismap
+
+Returns true if a download needed for the current game is running.
+====================
+*/
+qboolean Curl_Have_forthismap()
+{
+       return numdownloads_added;
+}
+
+void Curl_Register_predownload()
+{
+       Curl_CommandWhenDone("cl_begindownloads");
+       Curl_CommandWhenError("cl_begindownloads");
+}
+
+/*
+====================
+Curl_CheckCommandWhenDone
+
+Checks if a "done command" is to be executed.
+All downloads finished, at least one success since connect, no single failure
+-> execute the command.
+*/
+static void Curl_CheckCommandWhenDone()
+{
+       if(!curl_dll)
+               return;
+       if(numdownloads_added && (numdownloads_success == numdownloads_added) && *command_when_done)
+       {
+               Con_DPrintf("cURL downloads occurred, executing %s\n", command_when_done);
+               Cbuf_AddText("\n");
+               Cbuf_AddText(command_when_done);
+               Cbuf_AddText("\n");
+               Curl_Clear_forthismap();
+       }
+       else if(numdownloads_added && numdownloads_fail && *command_when_error)
+       {
+               Con_DPrintf("cURL downloads FAILED, executing %s\n", command_when_error);
+               Cbuf_AddText("\n");
+               Cbuf_AddText(command_when_error);
+               Cbuf_AddText("\n");
+               Curl_Clear_forthismap();
+       }
+}
+
 /*
 ====================
 CURL_CloseLibrary
@@ -196,11 +316,16 @@ static qboolean CURL_OpenLibrary (void)
 #if defined(WIN64)
                "libcurl64.dll",
 #elif defined(WIN32)
+               "libcurl-4.dll",
                "libcurl-3.dll",
 #elif defined(MACOSX)
-               "libcurl.3.dylib",
+               "libcurl.4.dylib", // Mac OS X Notyetreleased
+               "libcurl.3.dylib", // Mac OS X Tiger
+               "libcurl.2.dylib", // Mac OS X Panther
 #else
+               "libcurl.so.4",
                "libcurl.so.3",
+               "libcurl.so", // FreeBSD
 #endif
                NULL
        };
@@ -210,14 +335,7 @@ static qboolean CURL_OpenLibrary (void)
                return true;
 
        // Load the DLL
-       if (! Sys_LoadLibrary (dllnames, &curl_dll, curlfuncs))
-       {
-               Con_Printf ("cURL support disabled\n");
-               return false;
-       }
-
-       Con_Printf ("cURL support enabled\n");
-       return true;
+       return Sys_LoadLibrary (dllnames, &curl_dll, curlfuncs);
 }
 
 
@@ -248,15 +366,28 @@ this.
 */
 static size_t CURL_fwrite(void *data, size_t size, size_t nmemb, void *vdi)
 {
-       fs_offset_t ret;
+       fs_offset_t ret = -1;
        size_t bytes = size * nmemb;
        downloadinfo *di = (downloadinfo *) vdi;
 
+       if(di->buffer)
+       {
+               if(di->bytes_received + bytes <= di->buffersize)
+               {
+                       memcpy(di->buffer + di->bytes_received, data, bytes);
+                       ret = bytes;
+               }
+               // otherwise: buffer overrun, ret stays -1
+       }
+
+       if(di->stream)
+       {
+               ret = FS_Write(di->stream, data, bytes);
+       }
+
        bytes_received += bytes;
        di->bytes_received += bytes;
 
-       ret = FS_Write(di->stream, data, bytes);
-
        return ret; // why not ret / nmemb?
 }
 
@@ -269,27 +400,36 @@ typedef enum
 }
 CurlStatus;
 
-/*
-====================
-Curl_Clear_forthismap
-
-Clears the "will disconnect on failure" flags.
-====================
-*/
-void Curl_Clear_forthismap()
+static void curl_default_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata)
 {
-       downloadinfo *di;
-       for(di = downloads; di; di = di->next)
-               di->forthismap = false;
+       downloadinfo *di = (downloadinfo *) cbdata;
+       switch(status)
+       {
+               case CURLCBSTATUS_OK:
+                       Con_Printf("Download of %s: OK\n", di->filename);
+                       break;
+               case CURLCBSTATUS_FAILED:
+                       Con_Printf("Download of %s: FAILED\n", di->filename);
+                       break;
+               case CURLCBSTATUS_ABORTED:
+                       Con_Printf("Download of %s: ABORTED\n", di->filename);
+                       break;
+               case CURLCBSTATUS_SERVERERROR:
+                       Con_Printf("Download of %s: (unknown server error)\n", di->filename);
+                       break;
+               case CURLCBSTATUS_UNKNOWN:
+                       Con_Printf("Download of %s: (unknown client error)\n", di->filename);
+                       break;
+               default:
+                       Con_Printf("Download of %s: %d\n", di->filename, status);
+                       break;
+       }
 }
 
-static qboolean Curl_Have_forthismap()
+static void curl_quiet_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata)
 {
-       downloadinfo *di;
-       for(di = downloads; di; di = di->next)
-               if(di->forthismap)
-                       return true;
-       return false;
+       if(developer.integer)
+               curl_default_callback(status, length_received, buffer, cbdata);
 }
 
 /*
@@ -309,24 +449,29 @@ static void Curl_EndDownload(downloadinfo *di, CurlStatus status, CURLcode error
        switch(status)
        {
                case CURL_DOWNLOAD_SUCCESS:
-                       Con_Printf("Download of %s: OK\n", di->filename);
                        ok = true;
+                       di->callback(CURLCBSTATUS_OK, di->bytes_received, di->buffer, di->callback_data);
                        break;
                case CURL_DOWNLOAD_FAILED:
-                       Con_Printf("Download of %s: FAILED\n", di->filename);
-                       if(error)
-                               Con_Printf("Reason given by libcurl: %s\n", qcurl_easy_strerror(error));
+                       di->callback(CURLCBSTATUS_FAILED, di->bytes_received, di->buffer, di->callback_data);
                        break;
                case CURL_DOWNLOAD_ABORTED:
-                       Con_Printf("Download of %s: ABORTED\n", di->filename);
+                       di->callback(CURLCBSTATUS_ABORTED, di->bytes_received, di->buffer, di->callback_data);
                        break;
                case CURL_DOWNLOAD_SERVERERROR:
-                       Con_Printf("Download of %s: %d\n", di->filename, (int) error);
-
                        // reopen to enforce it to have zero bytes again
-                       FS_Close(di->stream);
-                       di->stream = FS_Open(di->filename, "w", false, false);
+                       if(di->stream)
+                       {
+                               FS_Close(di->stream);
+                               di->stream = FS_OpenRealFile(di->filename, "wb", false);
+                       }
 
+                       if(di->callback)
+                               di->callback(error ? (int) error : CURLCBSTATUS_SERVERERROR, di->bytes_received, di->buffer, di->callback_data);
+                       break;
+               default:
+                       if(di->callback)
+                               di->callback(CURLCBSTATUS_UNKNOWN, di->bytes_received, di->buffer, di->callback_data);
                        break;
        }
 
@@ -346,20 +491,7 @@ static void Curl_EndDownload(downloadinfo *di, CurlStatus status, CURLcode error
                FS_Close(di->stream);
 
        if(ok && di->ispak)
-       {
                ok = FS_AddPack(di->filename, NULL, true);
-               if(ok && di->forthismap)
-                       Mod_Reload();
-       }
-
-       if(!ok && di->forthismap)
-       {
-               // BAD. Something went totally wrong.
-               // The best we can do is clean up the forthismap flags...
-               Curl_Clear_forthismap();
-               // and disconnect.
-               CL_Disconnect_f();
-       }
 
        if(di->prev)
                di->prev->next = di->next;
@@ -367,9 +499,16 @@ static void Curl_EndDownload(downloadinfo *di, CurlStatus status, CURLcode error
                downloads = di->next;
        if(di->next)
                di->next->prev = di->prev;
-       Z_Free(di);
 
        --numdownloads;
+       if(di->forthismap)
+       {
+               if(ok)
+                       ++numdownloads_success;
+               else
+                       ++numdownloads_fail;
+       }
+       Z_Free(di);
 }
 
 /*
@@ -392,22 +531,30 @@ static void CheckPendingDownloads()
                {
                        if(!di->started)
                        {
-                               Con_Printf("Downloading %s -> %s", di->url, di->filename);
+                               if(!di->buffer)
+                               {
+                                       Con_Printf("Downloading %s -> %s", di->url, di->filename);
+
+                                       di->stream = FS_OpenRealFile(di->filename, "ab", false);
+                                       if(!di->stream)
+                                       {
+                                               Con_Printf("\nFAILED: Could not open output file %s\n", di->filename);
+                                               Curl_EndDownload(di, CURL_DOWNLOAD_FAILED, CURLE_OK);
+                                               return;
+                                       }
+                                       FS_Seek(di->stream, 0, SEEK_END);
+                                       di->startpos = FS_Tell(di->stream);
 
-                               di->stream = FS_Open(di->filename, "ab", false, false);
-                               if(!di->stream)
+                                       if(di->startpos > 0)
+                                               Con_Printf(", resuming from position %ld", (long) di->startpos);
+                                       Con_Print("...\n");
+                               }
+                               else
                                {
-                                       Con_Printf("\nFAILED: Could not open output file %s\n", di->filename);
-                                       Curl_EndDownload(di, CURL_DOWNLOAD_FAILED, CURLE_OK);
-                                       return;
+                                       Con_DPrintf("Downloading %s -> memory\n", di->url);
+                                       di->startpos = 0;
                                }
 
-                               FS_Seek(di->stream, 0, SEEK_END);
-                               di->startpos = FS_Tell(di->stream);
-                               if(di->startpos > 0)
-                                       Con_Printf(", resuming from position %ld", (long) di->startpos);
-                               Con_Print("...\n");
-
                                di->curle = qcurl_easy_init();
                                qcurl_easy_setopt(di->curle, CURLOPT_URL, di->url);
                                qcurl_easy_setopt(di->curle, CURLOPT_USERAGENT, engineversion);
@@ -415,6 +562,8 @@ static void CheckPendingDownloads()
                                qcurl_easy_setopt(di->curle, CURLOPT_RESUME_FROM, (long) di->startpos);
                                qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 1);
                                qcurl_easy_setopt(di->curle, CURLOPT_WRITEFUNCTION, CURL_fwrite);
+                               qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_LIMIT, (long) 256);
+                               qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_TIME, (long) 45);
                                qcurl_easy_setopt(di->curle, CURLOPT_WRITEDATA, (void *) di);
                                qcurl_easy_setopt(di->curle, CURLOPT_PRIVATE, (void *) di);
                                qcurl_multi_add_handle(curlm, di->curle);
@@ -480,6 +629,24 @@ static downloadinfo *Curl_Find(const char *filename)
        return NULL;
 }
 
+void Curl_Cancel_ToMemory(curl_callback_t callback, void *cbdata)
+{
+       downloadinfo *di;
+       if(!curl_dll)
+               return;
+       for(di = downloads; di; )
+       {
+               if(di->callback == callback && di->callback_data == cbdata)
+               {
+                       di->callback = curl_quiet_callback; // do NOT call the callback
+                       Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
+                       di = downloads;
+               }
+               else
+                       di = di->next;
+       }
+}
+
 /*
 ====================
 Curl_Begin
@@ -488,10 +655,12 @@ Starts a download of a given URL to the file name portion of this URL (or name
 if given) in the "dlcache/" folder.
 ====================
 */
-void Curl_Begin(const char *URL, const char *name, qboolean ispak, qboolean forthismap)
+static qboolean Curl_Begin(const char *URL, const char *name, qboolean ispak, qboolean forthismap, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata)
 {
        if(!curl_dll)
-               return;
+       {
+               return false;
+       }
        else
        {
                char fn[MAX_QPATH];
@@ -527,68 +696,94 @@ void Curl_Begin(const char *URL, const char *name, qboolean ispak, qboolean fort
 
                if(!name)
                        name = URL;
-               p = strrchr(name, '/');
-               p = p ? (p+1) : name;
-               q = strchr(p, '?');
-               length = q ? (size_t)(q - p) : strlen(p);
-               dpsnprintf(fn, sizeof(fn), "dlcache/%.*s", (int)length, p);
-
 
-               // already downloading the file?
+               if(!buf)
                {
-                       downloadinfo *di = Curl_Find(fn);
-                       if(di)
-                       {
-                               Con_Printf("Can't download %s, already getting it from %s!\n", fn, di->url);
+                       p = strrchr(name, '/');
+                       p = p ? (p+1) : name;
+                       q = strchr(p, '?');
+                       length = q ? (size_t)(q - p) : strlen(p);
+                       dpsnprintf(fn, sizeof(fn), "dlcache/%.*s", (int)length, p);
 
-                               // however, if it was not for this map yet...
-                               if(forthismap)
-                                       di->forthismap = true;
+                       name = fn; // make it point back
 
-                               return;
-                       }
-               }
-               
-               if(ispak && FS_FileExists(fn))
-               {
-                       qboolean already_loaded;
-                       if(FS_AddPack(fn, &already_loaded, true))
+                       // already downloading the file?
                        {
-                               Con_DPrintf("%s already exists, not downloading!\n", fn);
-                               if(already_loaded)
-                                       Con_DPrintf("(pak was already loaded)\n");
-                               else
-                                       if(forthismap)
-                                               Mod_Reload();
-                               return;
-                       }
-                       else
-                       {
-                               qfile_t *f = FS_Open(fn, "rb", false, false);
-                               if(f)
+                               downloadinfo *di = Curl_Find(fn);
+                               if(di)
                                {
-                                       char buf[4] = {0};
-                                       FS_Read(f, buf, sizeof(buf)); // no "-1", I will use memcmp
+                                       Con_Printf("Can't download %s, already getting it from %s!\n", fn, di->url);
 
-                                       if(memcmp(buf, "PK\x03\x04", 4) && memcmp(buf, "PACK", 4))
+                                       // however, if it was not for this map yet...
+                                       if(forthismap && !di->forthismap)
                                        {
-                                               Con_DPrintf("Detected non-PAK %s, clearing and NOT resuming.\n", fn);
-                                               FS_Close(f);
-                                               f = FS_Open(fn, "w", false, false);
-                                               if(f)
-                                                       FS_Close(f);
+                                               di->forthismap = true;
+                                               // this "fakes" a download attempt so the client will wait for
+                                               // the download to finish and then reconnect
+                                               ++numdownloads_added;
                                        }
+
+                                       return false;
+                               }
+                       }
+
+                       if(ispak && FS_FileExists(fn))
+                       {
+                               qboolean already_loaded;
+                               if(FS_AddPack(fn, &already_loaded, true))
+                               {
+                                       Con_DPrintf("%s already exists, not downloading!\n", fn);
+                                       if(already_loaded)
+                                               Con_DPrintf("(pak was already loaded)\n");
                                        else
                                        {
-                                               // OK
-                                               FS_Close(f);
+                                               if(forthismap)
+                                               {
+                                                       ++numdownloads_added;
+                                                       ++numdownloads_success;
+                                               }
+                                       }
+
+                                       return false;
+                               }
+                               else
+                               {
+                                       qfile_t *f = FS_OpenVirtualFile(fn, false);
+                                       if(f)
+                                       {
+                                               char buf[4] = {0};
+                                               FS_Read(f, buf, sizeof(buf)); // no "-1", I will use memcmp
+
+                                               if(memcmp(buf, "PK\x03\x04", 4) && memcmp(buf, "PACK", 4))
+                                               {
+                                                       Con_DPrintf("Detected non-PAK %s, clearing and NOT resuming.\n", fn);
+                                                       FS_Close(f);
+                                                       f = FS_OpenRealFile(fn, "wb", false);
+                                                       if(f)
+                                                               FS_Close(f);
+                                               }
+                                               else
+                                               {
+                                                       // OK
+                                                       FS_Close(f);
+                                               }
                                        }
                                }
                        }
                }
 
+               // if we get here, we actually want to download... so first verify the
+               // URL scheme (so one can't read local files using file://)
+               if(strncmp(URL, "http://", 7) && strncmp(URL, "ftp://", 6) && strncmp(URL, "https://", 8))
+               {
+                       Con_Printf("Curl_Begin(\"%s\")): nasty URL scheme rejected\n", URL);
+                       return false;
+               }
+
+               if(forthismap)
+                       ++numdownloads_added;
                di = (downloadinfo *) Z_Malloc(sizeof(*di));
-               strlcpy(di->filename, fn, sizeof(di->filename));
+               strlcpy(di->filename, name, sizeof(di->filename));
                strlcpy(di->url, URL, sizeof(di->url));
                dpsnprintf(di->referer, sizeof(di->referer), "dp://%s/", cls.netcon ? cls.netcon->address : "notconnected.invalid");
                di->forthismap = forthismap;
@@ -602,10 +797,33 @@ void Curl_Begin(const char *URL, const char *name, qboolean ispak, qboolean fort
                di->prev = NULL;
                if(di->next)
                        di->next->prev = di;
+
+               di->buffer = buf;
+               di->buffersize = bufsize;
+               if(callback == NULL)
+               {
+                       di->callback = curl_default_callback;
+                       di->callback_data = di;
+               }
+               else
+               {
+                       di->callback = callback;
+                       di->callback_data = cbdata;
+               }
+
                downloads = di;
+               return true;
        }
 }
 
+qboolean Curl_Begin_ToFile(const char *URL, const char *name, qboolean ispak, qboolean forthismap)
+{
+       return Curl_Begin(URL, name, ispak, forthismap, NULL, 0, NULL, NULL);
+}
+qboolean Curl_Begin_ToMemory(const char *URL, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata)
+{
+       return Curl_Begin(URL, NULL, false, false, buf, bufsize, callback, cbdata);
+}
 
 /*
 ====================
@@ -617,15 +835,16 @@ blocking.
 */
 void Curl_Run()
 {
+       noclear = FALSE;
+
        if(!cl_curl_enabled.integer)
-       {
-               Con_Print("curl support not enabled. Set cl_curl_enabled to 1 to enable.\n");
                return;
-       }
 
        if(!curl_dll)
                return;
 
+       Curl_CheckCommandWhenDone();
+
        if(!downloads)
                return;
 
@@ -635,7 +854,7 @@ void Curl_Run()
        {
                int remaining;
                CURLMcode mc;
-               
+
                do
                {
                        mc = qcurl_multi_perform(curlm, &remaining);
@@ -652,7 +871,6 @@ void Curl_Run()
                                downloadinfo *di;
                                CurlStatus failed = CURL_DOWNLOAD_SUCCESS;
                                CURLcode result;
-
                                qcurl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &di);
                                result = msg->data.result;
                                if(result)
@@ -672,7 +890,7 @@ void Curl_Run()
                                                        break;
                                        }
                                }
-                               
+
                                Curl_EndDownload(di, failed, result);
                        }
                }
@@ -823,7 +1041,14 @@ For internal use:
 curl [--pak] [--forthismap] [--for filename filename...] url
        --pak: after downloading, load the package into the virtual file system
        --for filename...: only download of at least one of the named files is missing
-       --forthismap: disconnect on failure
+       --forthismap: don't reconnect on failure
+
+curl --clear_autodownload
+       clears the download success/failure counters
+
+curl --finish_autodownload
+       if at least one download has been started, disconnect and drop to the menu
+       once the last download completes successfully, reconnect to the current server
 ====================
 */
 void Curl_Curl_f(void)
@@ -835,11 +1060,17 @@ void Curl_Curl_f(void)
        const char *url;
        const char *name = 0;
 
-       if(!cl_curl_enabled.integer)
+       if(!curl_dll)
+       {
+               Con_Print("libcurl DLL not found, this command is inactive.\n");
                return;
+       }
 
-       if(!curl_dll)
+       if(!cl_curl_enabled.integer)
+       {
+               Con_Print("curl support not enabled. Set cl_curl_enabled to 1 to enable.\n");
                return;
+       }
 
        for(i = 0; i != Cmd_Argc(); ++i)
                Con_DPrintf("%s ", Cmd_Argv(i));
@@ -869,7 +1100,10 @@ void Curl_Curl_f(void)
                        else
                        {
                                downloadinfo *di = Curl_Find(url);
-                               Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
+                               if(di)
+                                       Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
+                               else
+                                       Con_Print("download not found\n");
                        }
                        return;
                }
@@ -908,7 +1142,24 @@ void Curl_Curl_f(void)
                }
                else if(!strcmp(a, "--finish_autodownload"))
                {
-                       // nothing
+                       if(numdownloads_added)
+                       {
+                               char donecommand[256];
+                               if(cls.netcon)
+                               {
+                                       if(cl.loadbegun) // curling won't inhibit loading the map any more when at this stage, so bail out and force a reconnect
+                                       {
+                                               dpsnprintf(donecommand, sizeof(donecommand), "connect %s", cls.netcon->address);
+                                               Curl_CommandWhenDone(donecommand);
+                                               noclear = TRUE;
+                                               CL_Disconnect();
+                                               noclear = FALSE;
+                                               Curl_CheckCommandWhenDone();
+                                       }
+                                       else
+                                               Curl_Register_predownload();
+                               }
+                       }
                        return;
                }
                else if(*a == '-')
@@ -919,9 +1170,25 @@ void Curl_Curl_f(void)
        }
 
 needthefile:
-       Curl_Begin(url, name, pak, forthismap);
+       Curl_Begin_ToFile(url, name, pak, forthismap);
 }
 
+/*
+static void curl_curlcat_callback(int code, size_t length_received, unsigned char *buffer, void *cbdata)
+{
+       Con_Printf("Received %d bytes (status %d):\n%.*s\n", (int) length_received, code, (int) length_received, buffer);
+       Z_Free(buffer);
+}
+
+void Curl_CurlCat_f(void)
+{
+       unsigned char *buf;
+       const char *url = Cmd_Argv(1);
+       buf = Z_Malloc(16384);
+       Curl_Begin_ToMemory(url, buf, 16384, curl_curlcat_callback, NULL);
+}
+*/
+
 /*
 ====================
 Curl_Init_Commands
@@ -935,7 +1202,9 @@ void Curl_Init_Commands(void)
        Cvar_RegisterVariable (&cl_curl_maxdownloads);
        Cvar_RegisterVariable (&cl_curl_maxspeed);
        Cvar_RegisterVariable (&sv_curl_defaulturl);
+       Cvar_RegisterVariable (&sv_curl_serverpackages);
        Cmd_AddCommand ("curl", Curl_Curl_f, "download data from an URL and add to search path");
+       //Cmd_AddCommand ("curlcat", Curl_CurlCat_f, "display data from an URL (debugging command)");
 }
 
 /*
@@ -951,7 +1220,7 @@ array must be freed later using Z_Free.
 */
 Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **additional_info)
 {
-       int n, i;
+       int i;
        downloadinfo *di;
        Curl_downloadinfo_t *downinfo;
        static char addinfo[128];
@@ -964,14 +1233,18 @@ Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **addition
                return NULL;
        }
 
-       n = 0;
+       i = 0;
        for(di = downloads; di; di = di->next)
-               ++n;
+               ++i;
 
-       downinfo = (Curl_downloadinfo_t *) Z_Malloc(sizeof(*downinfo) * n);
+       downinfo = (Curl_downloadinfo_t *) Z_Malloc(sizeof(*downinfo) * i);
        i = 0;
        for(di = downloads; di; di = di->next)
        {
+               // do not show infobars for background downloads
+               if(!developer.integer)
+                       if(di->buffer)
+                               continue;
                strlcpy(downinfo[i].filename, di->filename, sizeof(downinfo[i].filename));
                if(di->curle)
                {
@@ -985,21 +1258,25 @@ Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **addition
                }
                ++i;
        }
-       
+
        if(additional_info)
        {
-               // TODO put something better here?
-               // maybe... check if the file is actually needed for the current map?
-               if(Curl_Have_forthismap())
+               // TODO: can I clear command_when_done as soon as the first download fails?
+               if(*command_when_done && !numdownloads_fail && numdownloads_added)
                {
-                       dpsnprintf(addinfo, sizeof(addinfo), "please wait for the download to complete");
+                       if(!strncmp(command_when_done, "connect ", 8))
+                               dpsnprintf(addinfo, sizeof(addinfo), "(will join %s when done)", command_when_done + 8);
+                       else if(!strcmp(command_when_done, "cl_begindownloads"))
+                               dpsnprintf(addinfo, sizeof(addinfo), "(will enter the game when done)");
+                       else
+                               dpsnprintf(addinfo, sizeof(addinfo), "(will do '%s' when done)", command_when_done);
                        *additional_info = addinfo;
                }
                else
                        *additional_info = NULL;
        }
 
-       *nDownloads = n;
+       *nDownloads = i;
        return downinfo;
 }
 
@@ -1039,7 +1316,7 @@ static const char *Curl_FindPackURL(const char *filename)
                char *p = buf;
                char *pattern = NULL, *patternend = NULL, *url = NULL, *urlend = NULL;
                qboolean eof = false;
-               
+
                pattern = p;
                while(!eof)
                {
@@ -1099,6 +1376,21 @@ requirement;
 static requirement *requirements = NULL;
 
 
+/*
+====================
+Curl_RequireFile
+
+Adds the given file to the list of requirements.
+====================
+*/
+void Curl_RequireFile(const char *filename)
+{
+       requirement *req = (requirement *) Z_Malloc(sizeof(*requirements));
+       req->next = requirements;
+       strlcpy(req->filename, filename, sizeof(req->filename));
+       requirements = req;
+}
+
 /*
 ====================
 Curl_ClearRequirements
@@ -1109,27 +1401,20 @@ This should be called at every map change.
 */
 void Curl_ClearRequirements()
 {
+       const char *p;
        while(requirements)
        {
                requirement *req = requirements;
                requirements = requirements->next;
                Z_Free(req);
        }
-}
-
-/*
-====================
-Curl_RequireFile
-
-Adds the given file to the list of requirements.
-====================
-*/
-void Curl_RequireFile(const char *filename)
-{
-       requirement *req = (requirement *) Z_Malloc(sizeof(*requirements));
-       req->next = requirements;
-       strlcpy(req->filename, filename, sizeof(req->filename));
-       requirements = req;
+       p = sv_curl_serverpackages.string;
+       Con_DPrintf("Require all of: %s\n", p);
+       while(COM_ParseToken_Simple(&p, false, false))
+       {
+               Con_DPrintf("Require: %s\n", com_token);
+               Curl_RequireFile(com_token);
+       }
 }
 
 /*
@@ -1139,7 +1424,7 @@ Curl_SendRequirements
 Makes the current host_clients download all files he needs.
 This is done by sending him the following console commands:
 
-       curl --start_autodownload
+       curl --clear_autodownload
        curl --pak --for maps/pushmoddm1.bsp --forthismap http://where/this/darn/map/is/pushmoddm1.pk3
        curl --finish_autodownload
 ====================
@@ -1149,8 +1434,7 @@ void Curl_SendRequirements()
        // for each requirement, find the pack name
        char sendbuffer[4096] = "";
        requirement *req;
-
-       strlcat(sendbuffer, "curl --clear_autodownload\n", sizeof(sendbuffer));
+       qboolean foundone = false;
 
        for(req = requirements; req; req = req->next)
        {
@@ -1169,6 +1453,9 @@ void Curl_SendRequirements()
 
                if(packurl && *packurl && strcmp(packurl, "-"))
                {
+                       if(!foundone)
+                               strlcat(sendbuffer, "curl --clear_autodownload\n", sizeof(sendbuffer));
+
                        strlcat(sendbuffer, "curl --pak --forthismap --as ", sizeof(sendbuffer));
                        strlcat(sendbuffer, thispack, sizeof(sendbuffer));
                        strlcat(sendbuffer, " --for ", sizeof(sendbuffer));
@@ -1177,10 +1464,13 @@ void Curl_SendRequirements()
                        strlcat(sendbuffer, packurl, sizeof(sendbuffer));
                        strlcat(sendbuffer, thispack, sizeof(sendbuffer));
                        strlcat(sendbuffer, "\n", sizeof(sendbuffer));
+
+                       foundone = true;
                }
        }
 
-       strlcat(sendbuffer, "curl --finish_autodownload\n", sizeof(sendbuffer));
+       if(foundone)
+               strlcat(sendbuffer, "curl --finish_autodownload\n", sizeof(sendbuffer));
 
        if(strlen(sendbuffer) + 1 < sizeof(sendbuffer))
                Host_ClientCommands("%s", sendbuffer);