]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - libcurl.c
added vid_grabkeyboard cvar which defaults to 0, this means that the glx client no...
[xonotic/darkplaces.git] / libcurl.c
index 0af8955058b19566af3f27c1c3bb40829c837d56..4061f5516732e1e430ef9943b25c3a16cfea5e23 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","0", "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
@@ -182,6 +185,118 @@ 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("Map 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("Map downloads FAILED, executing %s\n", command_when_error);
+               Cbuf_AddText("\n");
+               Cbuf_AddText(command_when_error);
+               Cbuf_AddText("\n");
+               Curl_Clear_forthismap();
+       }
+}
+
 /*
 ====================
 CURL_CloseLibrary
@@ -198,7 +313,8 @@ static qboolean CURL_OpenLibrary (void)
 #elif defined(WIN32)
                "libcurl-3.dll",
 #elif defined(MACOSX)
-               "libcurl.3.dylib",
+               "libcurl.3.dylib", // Mac OS X Tiger
+               "libcurl.2.dylib", // Mac OS X Panther
 #else
                "libcurl.so.3",
 #endif
@@ -269,29 +385,6 @@ typedef enum
 }
 CurlStatus;
 
-/*
-====================
-Curl_Clear_forthismap
-
-Clears the "will disconnect on failure" flags.
-====================
-*/
-void Curl_Clear_forthismap()
-{
-       downloadinfo *di;
-       for(di = downloads; di; di = di->next)
-               di->forthismap = false;
-}
-
-static qboolean Curl_Have_forthismap()
-{
-       downloadinfo *di;
-       for(di = downloads; di; di = di->next)
-               if(di->forthismap)
-                       return true;
-       return false;
-}
-
 /*
 ====================
 Curl_EndDownload
@@ -346,20 +439,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 +447,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);
 }
 
 /*
@@ -415,6 +502,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);
@@ -542,8 +631,13 @@ void Curl_Begin(const char *URL, const char *name, qboolean ispak, qboolean fort
                                Con_Printf("Can't download %s, already getting it from %s!\n", fn, di->url);
 
                                // however, if it was not for this map yet...
-                               if(forthismap)
+                               if(forthismap && !di->forthismap)
+                               {
                                        di->forthismap = true;
+                                       // this "fakes" a download attempt so the client will wait for
+                                       // the download to finish and then reconnect
+                                       ++numdownloads_added;
+                               }
 
                                return;
                        }
@@ -558,8 +652,13 @@ void Curl_Begin(const char *URL, const char *name, qboolean ispak, qboolean fort
                                if(already_loaded)
                                        Con_DPrintf("(pak was already loaded)\n");
                                else
+                               {
                                        if(forthismap)
-                                               Mod_Reload();
+                                       {
+                                               ++numdownloads_added;
+                                               ++numdownloads_success;
+                                       }
+                               }
                                return;
                        }
                        else
@@ -587,6 +686,8 @@ void Curl_Begin(const char *URL, const char *name, qboolean ispak, qboolean fort
                        }
                }
 
+               if(forthismap)
+                       ++numdownloads_added;
                di = (downloadinfo *) Z_Malloc(sizeof(*di));
                strlcpy(di->filename, fn, sizeof(di->filename));
                strlcpy(di->url, URL, sizeof(di->url));
@@ -617,12 +718,16 @@ blocking.
 */
 void Curl_Run()
 {
+       noclear = FALSE;
+
        if(!cl_curl_enabled.integer)
                return;
 
        if(!curl_dll)
                return;
 
+       Curl_CheckCommandWhenDone();
+
        if(!downloads)
                return;
 
@@ -649,7 +754,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)
@@ -820,7 +924,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)
@@ -832,14 +943,17 @@ void Curl_Curl_f(void)
        const char *url;
        const char *name = 0;
 
-       if(!cl_curl_enabled.integer)
+       if(!curl_dll)
        {
-               Con_Print("curl support not enabled. Set cl_curl_enabled to 1 to enable.\n");
+               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 +983,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 +1025,24 @@ void Curl_Curl_f(void)
                }
                else if(!strcmp(a, "--finish_autodownload"))
                {
-                       // nothing
+                       if(numdownloads_added)
+                       {
+                               char donecommand[256];
+                               if(cls.netcon)
+                               {
+                                       if(cls.signon >= 3)
+                                       {
+                                               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 == '-')
@@ -935,6 +1069,7 @@ 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");
 }
 
@@ -988,11 +1123,15 @@ Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **addition
        
        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
@@ -1099,6 +1238,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 +1263,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_ParseTokenConsole(&p))
+       {
+               Con_DPrintf("Require: %s\n", com_token);
+               Curl_RequireFile(com_token);
+       }
 }
 
 /*
@@ -1139,7 +1286,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 +1296,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 +1315,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 +1326,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);