5 static cvar_t cl_curl_maxdownloads = {CVAR_SAVE, "cl_curl_maxdownloads","1", "maximum number of concurrent HTTP/FTP downloads"};
6 static cvar_t cl_curl_maxspeed = {CVAR_SAVE, "cl_curl_maxspeed","100", "maximum download speed (KiB/s)"};
7 static cvar_t sv_curl_defaulturl = {CVAR_SAVE, "sv_curl_defaulturl","", "default autodownload source URL"};
8 static cvar_t sv_curl_serverpackages = {CVAR_SAVE, "sv_curl_serverpackages","", "list of required files for the clients, separated by spaces"};
9 static cvar_t cl_curl_enabled = {CVAR_SAVE, "cl_curl_enabled","1", "whether client's download support is enabled"};
12 =================================================================
14 Minimal set of definitions from libcurl
16 WARNING: for a matter of simplicity, several pointer types are
17 casted to "void*", and most enumerated values are not included
19 =================================================================
22 typedef struct CURL_s CURL;
23 typedef struct CURLM_s CURLM;
31 CURLM_CALL_MULTI_PERFORM=-1, /* please call curl_multi_perform() soon */
35 #define CURL_GLOBAL_NOTHING 0
36 #define CURL_GLOBAL_SSL 1
37 #define CURL_GLOBAL_WIN32 2
38 #define CURLOPTTYPE_LONG 0
39 #define CURLOPTTYPE_OBJECTPOINT 10000
40 #define CURLOPTTYPE_FUNCTIONPOINT 20000
41 #define CURLOPTTYPE_OFF_T 30000
42 #define CINIT(name,type,number) CURLOPT_ ## name = CURLOPTTYPE_ ## type + number
45 CINIT(WRITEDATA, OBJECTPOINT, 1),
46 CINIT(URL, OBJECTPOINT, 2),
47 CINIT(ERRORBUFFER, OBJECTPOINT, 10),
48 CINIT(WRITEFUNCTION, FUNCTIONPOINT, 11),
49 CINIT(REFERER, OBJECTPOINT, 16),
50 CINIT(USERAGENT, OBJECTPOINT, 18),
51 CINIT(RESUME_FROM, LONG, 21),
52 CINIT(FOLLOWLOCATION, LONG, 52), /* use Location: Luke! */
53 CINIT(PRIVATE, OBJECTPOINT, 103),
54 CINIT(LOW_SPEED_LIMIT, LONG , 19),
55 CINIT(LOW_SPEED_TIME, LONG, 20),
56 CINIT(PROTOCOLS, LONG, 181),
57 CINIT(REDIR_PROTOCOLS, LONG, 182),
60 #define CURLPROTO_HTTP (1<<0)
61 #define CURLPROTO_HTTPS (1<<1)
62 #define CURLPROTO_FTP (1<<2)
66 CURLINFO_HEADER_IN, /* 1 */
67 CURLINFO_HEADER_OUT, /* 2 */
68 CURLINFO_DATA_IN, /* 3 */
69 CURLINFO_DATA_OUT, /* 4 */
70 CURLINFO_SSL_DATA_IN, /* 5 */
71 CURLINFO_SSL_DATA_OUT, /* 6 */
75 #define CURLINFO_STRING 0x100000
76 #define CURLINFO_LONG 0x200000
77 #define CURLINFO_DOUBLE 0x300000
78 #define CURLINFO_SLIST 0x400000
79 #define CURLINFO_MASK 0x0fffff
80 #define CURLINFO_TYPEMASK 0xf00000
83 CURLINFO_NONE, /* first, never use this */
84 CURLINFO_EFFECTIVE_URL = CURLINFO_STRING + 1,
85 CURLINFO_RESPONSE_CODE = CURLINFO_LONG + 2,
86 CURLINFO_TOTAL_TIME = CURLINFO_DOUBLE + 3,
87 CURLINFO_NAMELOOKUP_TIME = CURLINFO_DOUBLE + 4,
88 CURLINFO_CONNECT_TIME = CURLINFO_DOUBLE + 5,
89 CURLINFO_PRETRANSFER_TIME = CURLINFO_DOUBLE + 6,
90 CURLINFO_SIZE_UPLOAD = CURLINFO_DOUBLE + 7,
91 CURLINFO_SIZE_DOWNLOAD = CURLINFO_DOUBLE + 8,
92 CURLINFO_SPEED_DOWNLOAD = CURLINFO_DOUBLE + 9,
93 CURLINFO_SPEED_UPLOAD = CURLINFO_DOUBLE + 10,
94 CURLINFO_HEADER_SIZE = CURLINFO_LONG + 11,
95 CURLINFO_REQUEST_SIZE = CURLINFO_LONG + 12,
96 CURLINFO_SSL_VERIFYRESULT = CURLINFO_LONG + 13,
97 CURLINFO_FILETIME = CURLINFO_LONG + 14,
98 CURLINFO_CONTENT_LENGTH_DOWNLOAD = CURLINFO_DOUBLE + 15,
99 CURLINFO_CONTENT_LENGTH_UPLOAD = CURLINFO_DOUBLE + 16,
100 CURLINFO_STARTTRANSFER_TIME = CURLINFO_DOUBLE + 17,
101 CURLINFO_CONTENT_TYPE = CURLINFO_STRING + 18,
102 CURLINFO_REDIRECT_TIME = CURLINFO_DOUBLE + 19,
103 CURLINFO_REDIRECT_COUNT = CURLINFO_LONG + 20,
104 CURLINFO_PRIVATE = CURLINFO_STRING + 21,
105 CURLINFO_HTTP_CONNECTCODE = CURLINFO_LONG + 22,
106 CURLINFO_HTTPAUTH_AVAIL = CURLINFO_LONG + 23,
107 CURLINFO_PROXYAUTH_AVAIL = CURLINFO_LONG + 24,
108 CURLINFO_OS_ERRNO = CURLINFO_LONG + 25,
109 CURLINFO_NUM_CONNECTS = CURLINFO_LONG + 26,
110 CURLINFO_SSL_ENGINES = CURLINFO_SLIST + 27,
116 CURLMSG_NONE, /* first, not used */
117 CURLMSG_DONE, /* This easy handle has completed. 'result' contains
118 the CURLcode of the transfer */
124 CURLMSG msg; /* what this message means */
125 CURL *easy_handle; /* the handle it concerns */
128 void *whatever; /* message-specific data */
129 CURLcode result; /* return code for transfer */
135 static void (*qcurl_global_init) (long flags);
136 static void (*qcurl_global_cleanup) (void);
138 static CURL * (*qcurl_easy_init) (void);
139 static void (*qcurl_easy_cleanup) (CURL *handle);
140 static CURLcode (*qcurl_easy_setopt) (CURL *handle, CURLoption option, ...);
141 static CURLcode (*qcurl_easy_getinfo) (CURL *handle, CURLINFO info, ...);
142 static const char * (*qcurl_easy_strerror) (CURLcode);
144 static CURLM * (*qcurl_multi_init) (void);
145 static CURLMcode (*qcurl_multi_perform) (CURLM *multi_handle, int *running_handles);
146 static CURLMcode (*qcurl_multi_add_handle) (CURLM *multi_handle, CURL *easy_handle);
147 static CURLMcode (*qcurl_multi_remove_handle) (CURLM *multi_handle, CURL *easy_handle);
148 static CURLMsg * (*qcurl_multi_info_read) (CURLM *multi_handle, int *msgs_in_queue);
149 static void (*qcurl_multi_cleanup) (CURLM *);
150 static const char * (*qcurl_multi_strerror) (CURLcode);
152 static dllfunction_t curlfuncs[] =
154 {"curl_global_init", (void **) &qcurl_global_init},
155 {"curl_global_cleanup", (void **) &qcurl_global_cleanup},
156 {"curl_easy_init", (void **) &qcurl_easy_init},
157 {"curl_easy_cleanup", (void **) &qcurl_easy_cleanup},
158 {"curl_easy_setopt", (void **) &qcurl_easy_setopt},
159 {"curl_easy_strerror", (void **) &qcurl_easy_strerror},
160 {"curl_easy_getinfo", (void **) &qcurl_easy_getinfo},
161 {"curl_multi_init", (void **) &qcurl_multi_init},
162 {"curl_multi_perform", (void **) &qcurl_multi_perform},
163 {"curl_multi_add_handle", (void **) &qcurl_multi_add_handle},
164 {"curl_multi_remove_handle",(void **) &qcurl_multi_remove_handle},
165 {"curl_multi_info_read", (void **) &qcurl_multi_info_read},
166 {"curl_multi_cleanup", (void **) &qcurl_multi_cleanup},
167 {"curl_multi_strerror", (void **) &qcurl_multi_strerror},
171 // Handle for CURL DLL
172 static dllhandle_t curl_dll = NULL;
173 // will be checked at many places to find out if qcurl calls are allowed
175 typedef struct downloadinfo_s
177 char filename[MAX_OSPATH];
181 fs_offset_t startpos;
185 unsigned long bytes_received;
186 struct downloadinfo_s *next, *prev;
189 unsigned char *buffer;
191 curl_callback_t callback;
195 static downloadinfo *downloads = NULL;
196 static int numdownloads = 0;
198 static qboolean noclear = FALSE;
200 static int numdownloads_fail = 0;
201 static int numdownloads_success = 0;
202 static int numdownloads_added = 0;
203 static char command_when_done[256] = "";
204 static char command_when_error[256] = "";
210 Sets the command which is to be executed when the last download completes AND
211 all downloads since last server connect ended with a successful status.
212 Setting the command to NULL clears it.
215 void Curl_CommandWhenDone(const char *cmd)
220 strlcpy(command_when_done, cmd, sizeof(command_when_done));
222 *command_when_done = 0;
227 Do not use yet. Not complete.
228 Problem: what counts as an error?
231 void Curl_CommandWhenError(const char *cmd)
236 strlcpy(command_when_error, cmd, sizeof(command_when_error));
238 *command_when_error = 0;
243 Curl_Clear_forthismap
245 Clears the "will disconnect on failure" flags.
248 void Curl_Clear_forthismap(void)
253 for(di = downloads; di; di = di->next)
254 di->forthismap = false;
255 Curl_CommandWhenError(NULL);
256 Curl_CommandWhenDone(NULL);
257 numdownloads_fail = 0;
258 numdownloads_success = 0;
259 numdownloads_added = 0;
266 Returns true if a download needed for the current game is running.
269 qboolean Curl_Have_forthismap(void)
271 return numdownloads_added != 0;
274 void Curl_Register_predownload(void)
276 Curl_CommandWhenDone("cl_begindownloads");
277 Curl_CommandWhenError("cl_begindownloads");
282 Curl_CheckCommandWhenDone
284 Checks if a "done command" is to be executed.
285 All downloads finished, at least one success since connect, no single failure
286 -> execute the command.
288 static void Curl_CheckCommandWhenDone(void)
292 if(numdownloads_added && (numdownloads_success == numdownloads_added) && *command_when_done)
294 Con_DPrintf("cURL downloads occurred, executing %s\n", command_when_done);
296 Cbuf_AddText(command_when_done);
298 Curl_Clear_forthismap();
300 else if(numdownloads_added && numdownloads_fail && *command_when_error)
302 Con_DPrintf("cURL downloads FAILED, executing %s\n", command_when_error);
304 Cbuf_AddText(command_when_error);
306 Curl_Clear_forthismap();
317 static qboolean CURL_OpenLibrary (void)
319 const char* dllnames [] =
326 #elif defined(MACOSX)
327 "libcurl.4.dylib", // Mac OS X Notyetreleased
328 "libcurl.3.dylib", // Mac OS X Tiger
329 "libcurl.2.dylib", // Mac OS X Panther
333 "libcurl.so", // FreeBSD
343 return Sys_LoadLibrary (dllnames, &curl_dll, curlfuncs);
354 static void CURL_CloseLibrary (void)
356 Sys_UnloadLibrary (&curl_dll);
360 static CURLM *curlm = NULL;
361 static unsigned long bytes_received = 0; // used for bandwidth throttling
362 static double curltime = 0;
368 fwrite-compatible function that writes the data to a file. libcurl can call
372 static size_t CURL_fwrite(void *data, size_t size, size_t nmemb, void *vdi)
374 fs_offset_t ret = -1;
375 size_t bytes = size * nmemb;
376 downloadinfo *di = (downloadinfo *) vdi;
380 if(di->bytes_received + bytes <= di->buffersize)
382 memcpy(di->buffer + di->bytes_received, data, bytes);
385 // otherwise: buffer overrun, ret stays -1
390 ret = FS_Write(di->stream, data, bytes);
393 bytes_received += bytes;
394 di->bytes_received += bytes;
396 return ret; // why not ret / nmemb?
401 CURL_DOWNLOAD_SUCCESS = 0,
402 CURL_DOWNLOAD_FAILED,
403 CURL_DOWNLOAD_ABORTED,
404 CURL_DOWNLOAD_SERVERERROR
408 static void curl_default_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata)
410 downloadinfo *di = (downloadinfo *) cbdata;
413 case CURLCBSTATUS_OK:
414 Con_Printf("Download of %s: OK\n", di->filename);
416 case CURLCBSTATUS_FAILED:
417 Con_Printf("Download of %s: FAILED\n", di->filename);
419 case CURLCBSTATUS_ABORTED:
420 Con_Printf("Download of %s: ABORTED\n", di->filename);
422 case CURLCBSTATUS_SERVERERROR:
423 Con_Printf("Download of %s: (unknown server error)\n", di->filename);
425 case CURLCBSTATUS_UNKNOWN:
426 Con_Printf("Download of %s: (unknown client error)\n", di->filename);
429 Con_Printf("Download of %s: %d\n", di->filename, status);
434 static void curl_quiet_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata)
436 if(developer.integer)
437 curl_default_callback(status, length_received, buffer, cbdata);
444 stops a download. It receives a status (CURL_DOWNLOAD_SUCCESS,
445 CURL_DOWNLOAD_FAILED or CURL_DOWNLOAD_ABORTED) and in the second case the error
446 code from libcurl, or 0, if another error has occurred.
449 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);
450 static void Curl_EndDownload(downloadinfo *di, CurlStatus status, CURLcode error)
457 case CURL_DOWNLOAD_SUCCESS:
459 di->callback(CURLCBSTATUS_OK, di->bytes_received, di->buffer, di->callback_data);
461 case CURL_DOWNLOAD_FAILED:
462 di->callback(CURLCBSTATUS_FAILED, di->bytes_received, di->buffer, di->callback_data);
464 case CURL_DOWNLOAD_ABORTED:
465 di->callback(CURLCBSTATUS_ABORTED, di->bytes_received, di->buffer, di->callback_data);
467 case CURL_DOWNLOAD_SERVERERROR:
468 // reopen to enforce it to have zero bytes again
471 FS_Close(di->stream);
472 di->stream = FS_OpenRealFile(di->filename, "wb", false);
476 di->callback(error ? (int) error : CURLCBSTATUS_SERVERERROR, di->bytes_received, di->buffer, di->callback_data);
480 di->callback(CURLCBSTATUS_UNKNOWN, di->bytes_received, di->buffer, di->callback_data);
486 qcurl_multi_remove_handle(curlm, di->curle);
487 qcurl_easy_cleanup(di->curle);
490 if(!di->callback && ok && !di->bytes_received)
492 Con_Printf("ERROR: empty file\n");
497 FS_Close(di->stream);
501 ok = FS_AddPack(di->filename, NULL, true);
504 // pack loading failed?
506 // better clear the file again...
507 di->stream = FS_OpenRealFile(di->filename, "wb", false);
508 FS_Close(di->stream);
510 if(di->startpos && !di->callback)
512 // this was a resume?
513 // then try to redownload it without reporting the error
514 Curl_Begin(di->url, di->filename, di->ispak, di->forthismap, NULL, 0, NULL, NULL);
515 di->forthismap = false; // don't count the error
521 di->prev->next = di->next;
523 downloads = di->next;
525 di->next->prev = di->prev;
531 ++numdownloads_success;
540 CheckPendingDownloads
542 checks if there are free download slots to start new downloads in.
543 To not start too many downloads at once, only one download is added at a time,
544 up to a maximum number of cl_curl_maxdownloads are running.
547 static void CheckPendingDownloads(void)
551 if(numdownloads < cl_curl_maxdownloads.integer)
554 for(di = downloads; di; di = di->next)
560 Con_Printf("Downloading %s -> %s", di->url, di->filename);
562 di->stream = FS_OpenRealFile(di->filename, "ab", false);
565 Con_Printf("\nFAILED: Could not open output file %s\n", di->filename);
566 Curl_EndDownload(di, CURL_DOWNLOAD_FAILED, CURLE_OK);
569 FS_Seek(di->stream, 0, SEEK_END);
570 di->startpos = FS_Tell(di->stream);
573 Con_Printf(", resuming from position %ld", (long) di->startpos);
578 Con_DPrintf("Downloading %s -> memory\n", di->url);
582 di->curle = qcurl_easy_init();
583 qcurl_easy_setopt(di->curle, CURLOPT_URL, di->url);
584 qcurl_easy_setopt(di->curle, CURLOPT_USERAGENT, engineversion);
585 qcurl_easy_setopt(di->curle, CURLOPT_REFERER, di->referer);
586 qcurl_easy_setopt(di->curle, CURLOPT_RESUME_FROM, (long) di->startpos);
587 qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 1);
588 qcurl_easy_setopt(di->curle, CURLOPT_WRITEFUNCTION, CURL_fwrite);
589 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_LIMIT, (long) 256);
590 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_TIME, (long) 45);
591 qcurl_easy_setopt(di->curle, CURLOPT_WRITEDATA, (void *) di);
592 qcurl_easy_setopt(di->curle, CURLOPT_PRIVATE, (void *) di);
593 qcurl_easy_setopt(di->curle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP);
594 if(qcurl_easy_setopt(di->curle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP) != CURLE_OK)
596 Con_Printf("^1WARNING:^7 for security reasons, please upgrade to libcurl 7.19.4 or above. In a later version of DarkPlaces, HTTP redirect support will be disabled for this libcurl version.\n");
597 //qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 0);
600 qcurl_multi_add_handle(curlm, di->curle);
603 if(numdownloads >= cl_curl_maxdownloads.integer)
614 this function MUST be called before using anything else in this file.
615 On Win32, this must be called AFTER WSAStartup has been done!
623 qcurl_global_init(CURL_GLOBAL_NOTHING);
624 curlm = qcurl_multi_init();
631 Surprise... closes all the stuff. Please do this BEFORE shutting down LHNET.
634 void Curl_ClearRequirements(void);
635 void Curl_Shutdown(void)
639 Curl_ClearRequirements();
649 Finds the internal information block for a download given by file name.
652 static downloadinfo *Curl_Find(const char *filename)
657 for(di = downloads; di; di = di->next)
658 if(!strcasecmp(di->filename, filename))
663 void Curl_Cancel_ToMemory(curl_callback_t callback, void *cbdata)
668 for(di = downloads; di; )
670 if(di->callback == callback && di->callback_data == cbdata)
672 di->callback = curl_quiet_callback; // do NOT call the callback
673 Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
685 Starts a download of a given URL to the file name portion of this URL (or name
686 if given) in the "dlcache/" folder.
689 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)
702 // Note: This extraction of the file name portion is NOT entirely correct.
704 // It does the following:
706 // http://host/some/script.cgi/SomeFile.pk3?uid=ABCDE -> SomeFile.pk3
707 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3 -> SomeFile.pk3
708 // http://host/some/script.php?uid=ABCDE&file=SomeFile.pk3 -> script.php
710 // However, I'd like to keep this "buggy" behavior so that PHP script
711 // authors can write download scripts without having to enable
712 // AcceptPathInfo on Apache. They just have to ensure that their script
713 // can be called with such a "fake" path name like
714 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3
716 // By the way, such PHP scripts should either send the file or a
717 // "Location:" redirect; PHP code example:
719 // header("Location: http://www.example.com/");
721 // By the way, this will set User-Agent to something like
722 // "Nexuiz build 22:27:55 Mar 17 2006" (engineversion) and Referer to
723 // dp://serverhost:serverport/ so you can filter on this; an example
724 // httpd log file line might be:
726 // 141.2.16.3 - - [17/Mar/2006:22:32:43 +0100] "GET /maps/tznex07.pk3 HTTP/1.1" 200 1077455 "dp://141.2.16.7:26000/" "Nexuiz Linux 22:07:43 Mar 17 2006"
733 p = strrchr(name, '/');
734 p = p ? (p+1) : name;
736 length = q ? (size_t)(q - p) : strlen(p);
737 dpsnprintf(fn, sizeof(fn), "dlcache/%.*s", (int)length, p);
739 name = fn; // make it point back
741 // already downloading the file?
743 downloadinfo *di = Curl_Find(fn);
746 Con_Printf("Can't download %s, already getting it from %s!\n", fn, di->url);
748 // however, if it was not for this map yet...
749 if(forthismap && !di->forthismap)
751 di->forthismap = true;
752 // this "fakes" a download attempt so the client will wait for
753 // the download to finish and then reconnect
754 ++numdownloads_added;
761 if(ispak && FS_FileExists(fn))
763 qboolean already_loaded;
764 if(FS_AddPack(fn, &already_loaded, true))
766 Con_DPrintf("%s already exists, not downloading!\n", fn);
768 Con_DPrintf("(pak was already loaded)\n");
773 ++numdownloads_added;
774 ++numdownloads_success;
782 qfile_t *f = FS_OpenRealFile(fn, "rb", false);
786 FS_Read(f, buf, sizeof(buf)); // no "-1", I will use memcmp
788 if(memcmp(buf, "PK\x03\x04", 4) && memcmp(buf, "PACK", 4))
790 Con_DPrintf("Detected non-PAK %s, clearing and NOT resuming.\n", fn);
792 f = FS_OpenRealFile(fn, "wb", false);
806 // if we get here, we actually want to download... so first verify the
807 // URL scheme (so one can't read local files using file://)
808 if(strncmp(URL, "http://", 7) && strncmp(URL, "ftp://", 6) && strncmp(URL, "https://", 8))
810 Con_Printf("Curl_Begin(\"%s\"): nasty URL scheme rejected\n", URL);
815 ++numdownloads_added;
816 di = (downloadinfo *) Z_Malloc(sizeof(*di));
817 strlcpy(di->filename, name, sizeof(di->filename));
818 strlcpy(di->url, URL, sizeof(di->url));
819 dpsnprintf(di->referer, sizeof(di->referer), "dp://%s/", cls.netcon ? cls.netcon->address : "notconnected.invalid");
820 di->forthismap = forthismap;
825 di->ispak = (ispak && !buf);
826 di->bytes_received = 0;
827 di->next = downloads;
833 di->buffersize = bufsize;
836 di->callback = curl_default_callback;
837 di->callback_data = di;
841 di->callback = callback;
842 di->callback_data = cbdata;
850 qboolean Curl_Begin_ToFile(const char *URL, const char *name, qboolean ispak, qboolean forthismap)
852 return Curl_Begin(URL, name, ispak, forthismap, NULL, 0, NULL, NULL);
854 qboolean Curl_Begin_ToMemory(const char *URL, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata)
856 return Curl_Begin(URL, NULL, false, false, buf, bufsize, callback, cbdata);
863 call this regularily as this will always download as much as possible without
871 if(!cl_curl_enabled.integer)
877 Curl_CheckCommandWhenDone();
882 if(realtime < curltime) // throttle
891 mc = qcurl_multi_perform(curlm, &remaining);
893 while(mc == CURLM_CALL_MULTI_PERFORM);
897 CURLMsg *msg = qcurl_multi_info_read(curlm, &remaining);
900 if(msg->msg == CURLMSG_DONE)
903 CurlStatus failed = CURL_DOWNLOAD_SUCCESS;
905 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &di);
906 result = msg->data.result;
909 failed = CURL_DOWNLOAD_FAILED;
914 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code);
919 failed = CURL_DOWNLOAD_SERVERERROR;
920 result = (CURLcode) code;
925 Curl_EndDownload(di, failed, result);
930 CheckPendingDownloads();
932 // when will we curl the next time?
933 // we will wait a bit to ensure our download rate is kept.
934 // we now know that realtime >= curltime... so set up a new curltime
935 if(cl_curl_maxspeed.value > 0)
937 unsigned long bytes = bytes_received; // maybe smoothen a bit?
938 curltime = realtime + bytes / (cl_curl_maxspeed.value * 1024.0);
939 bytes_received -= bytes;
952 void Curl_CancelAll(void)
959 Curl_EndDownload(downloads, CURL_DOWNLOAD_ABORTED, CURLE_OK);
960 // INVARIANT: downloads will point to the next download after that!
968 returns true iff there is a download running.
971 qboolean Curl_Running(void)
976 return downloads != NULL;
981 Curl_GetDownloadAmount
983 returns a value from 0.0 to 1.0 which represents the downloaded amount of data
984 for the given download.
987 static double Curl_GetDownloadAmount(downloadinfo *di)
994 qcurl_easy_getinfo(di->curle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
996 return (di->startpos + di->bytes_received) / (di->startpos + length);
1005 ====================
1006 Curl_GetDownloadSpeed
1008 returns the speed of the given download in bytes per second
1009 ====================
1011 static double Curl_GetDownloadSpeed(downloadinfo *di)
1018 qcurl_easy_getinfo(di->curle, CURLINFO_SPEED_DOWNLOAD, &speed);
1026 ====================
1029 prints the download list
1030 ====================
1032 // TODO rewrite using Curl_GetDownloadInfo?
1033 static void Curl_Info_f(void)
1040 Con_Print("Currently running downloads:\n");
1041 for(di = downloads; di; di = di->next)
1043 double speed, percent;
1044 Con_Printf(" %s -> %s ", di->url, di->filename);
1045 percent = 100.0 * Curl_GetDownloadAmount(di);
1046 speed = Curl_GetDownloadSpeed(di);
1048 Con_Printf("(%.1f%% @ %.1f KiB/s)\n", percent, speed / 1024.0);
1050 Con_Print("(queued)\n");
1055 Con_Print("No downloads running.\n");
1060 ====================
1063 implements the "curl" console command
1067 curl --cancel filename
1072 curl [--pak] [--forthismap] [--for filename filename...] url
1073 --pak: after downloading, load the package into the virtual file system
1074 --for filename...: only download of at least one of the named files is missing
1075 --forthismap: don't reconnect on failure
1077 curl --clear_autodownload
1078 clears the download success/failure counters
1080 curl --finish_autodownload
1081 if at least one download has been started, disconnect and drop to the menu
1082 once the last download completes successfully, reconnect to the current server
1083 ====================
1085 void Curl_Curl_f(void)
1089 qboolean pak = false;
1090 qboolean forthismap = false;
1092 const char *name = 0;
1096 Con_Print("libcurl DLL not found, this command is inactive.\n");
1100 if(!cl_curl_enabled.integer)
1102 Con_Print("curl support not enabled. Set cl_curl_enabled to 1 to enable.\n");
1106 for(i = 0; i != Cmd_Argc(); ++i)
1107 Con_DPrintf("%s ", Cmd_Argv(i));
1112 Con_Print("usage:\ncurl --info, curl --cancel [filename], curl url\n");
1116 url = Cmd_Argv(Cmd_Argc() - 1);
1119 for(i = 1; i != end; ++i)
1121 const char *a = Cmd_Argv(i);
1122 if(!strcmp(a, "--info"))
1127 else if(!strcmp(a, "--cancel"))
1129 if(i == end - 1) // last argument
1133 downloadinfo *di = Curl_Find(url);
1135 Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
1137 Con_Print("download not found\n");
1141 else if(!strcmp(a, "--pak"))
1145 else if(!strcmp(a, "--for"))
1147 for(i = i + 1; i != end - 1; ++i)
1149 if(!FS_FileExists(Cmd_Argv(i)))
1150 goto needthefile; // why can't I have a "double break"?
1152 // if we get here, we have all the files...
1155 else if(!strcmp(a, "--forthismap"))
1159 else if(!strcmp(a, "--as"))
1167 else if(!strcmp(a, "--clear_autodownload"))
1169 // mark all running downloads as "not for this map", so if they
1170 // fail, it does not matter
1171 Curl_Clear_forthismap();
1174 else if(!strcmp(a, "--finish_autodownload"))
1176 if(numdownloads_added)
1178 char donecommand[256];
1181 if(cl.loadbegun) // curling won't inhibit loading the map any more when at this stage, so bail out and force a reconnect
1183 dpsnprintf(donecommand, sizeof(donecommand), "connect %s", cls.netcon->address);
1184 Curl_CommandWhenDone(donecommand);
1188 Curl_CheckCommandWhenDone();
1191 Curl_Register_predownload();
1198 Con_Printf("invalid option %s\n", a);
1204 Curl_Begin_ToFile(url, name, pak, forthismap);
1208 static void curl_curlcat_callback(int code, size_t length_received, unsigned char *buffer, void *cbdata)
1210 Con_Printf("Received %d bytes (status %d):\n%.*s\n", (int) length_received, code, (int) length_received, buffer);
1214 void Curl_CurlCat_f(void)
1217 const char *url = Cmd_Argv(1);
1218 buf = Z_Malloc(16384);
1219 Curl_Begin_ToMemory(url, buf, 16384, curl_curlcat_callback, NULL);
1224 ====================
1227 loads the commands and cvars this library uses
1228 ====================
1230 void Curl_Init_Commands(void)
1232 Cvar_RegisterVariable (&cl_curl_enabled);
1233 Cvar_RegisterVariable (&cl_curl_maxdownloads);
1234 Cvar_RegisterVariable (&cl_curl_maxspeed);
1235 Cvar_RegisterVariable (&sv_curl_defaulturl);
1236 Cvar_RegisterVariable (&sv_curl_serverpackages);
1237 Cmd_AddCommand ("curl", Curl_Curl_f, "download data from an URL and add to search path");
1238 //Cmd_AddCommand ("curlcat", Curl_CurlCat_f, "display data from an URL (debugging command)");
1242 ====================
1243 Curl_GetDownloadInfo
1245 returns an array of Curl_downloadinfo_t structs for usage by GUIs.
1246 The number of elements in the array is returned in int *nDownloads.
1247 const char **additional_info may be set to a string of additional user
1248 information, or to NULL if no such display shall occur. The returned
1249 array must be freed later using Z_Free.
1250 ====================
1252 Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **additional_info)
1256 Curl_downloadinfo_t *downinfo;
1257 static char addinfo[128];
1263 *additional_info = NULL;
1268 for(di = downloads; di; di = di->next)
1271 downinfo = (Curl_downloadinfo_t *) Z_Malloc(sizeof(*downinfo) * i);
1273 for(di = downloads; di; di = di->next)
1275 // do not show infobars for background downloads
1276 if(!developer.integer)
1279 strlcpy(downinfo[i].filename, di->filename, sizeof(downinfo[i].filename));
1282 downinfo[i].progress = Curl_GetDownloadAmount(di);
1283 downinfo[i].speed = Curl_GetDownloadSpeed(di);
1284 downinfo[i].queued = false;
1288 downinfo[i].queued = true;
1295 // TODO: can I clear command_when_done as soon as the first download fails?
1296 if(*command_when_done && !numdownloads_fail && numdownloads_added)
1298 if(!strncmp(command_when_done, "connect ", 8))
1299 dpsnprintf(addinfo, sizeof(addinfo), "(will join %s when done)", command_when_done + 8);
1300 else if(!strcmp(command_when_done, "cl_begindownloads"))
1301 dpsnprintf(addinfo, sizeof(addinfo), "(will enter the game when done)");
1303 dpsnprintf(addinfo, sizeof(addinfo), "(will do '%s' when done)", command_when_done);
1304 *additional_info = addinfo;
1307 *additional_info = NULL;
1316 ====================
1319 finds the URL where to find a given package.
1321 For this, it reads a file "curl_urls.txt" of the following format:
1324 revdm*.pk3 http://revdm/downloads/are/here/
1325 * http://any/other/stuff/is/here/
1327 The URLs should end in /. If not, downloads will still work, but the cached files
1328 can't be just put into the data directory with the same download configuration
1329 (you might want to do this if you want to tag downloaded files from your
1330 server, but you should not). "-" means "don't download".
1332 If no single pattern matched, the cvar sv_curl_defaulturl is used as download
1335 Note: pak1.pak and data*.pk3 are excluded from autodownload at another point in
1336 this file for obvious reasons.
1337 ====================
1339 static const char *Curl_FindPackURL(const char *filename)
1341 static char foundurl[1024];
1342 fs_offset_t filesize;
1343 char *buf = (char *) FS_LoadFile("curl_urls.txt", tempmempool, true, &filesize);
1346 // read lines of format "pattern url"
1348 char *pattern = NULL, *patternend = NULL, *url = NULL, *urlend = NULL;
1349 qboolean eof = false;
1361 if(pattern && url && patternend)
1367 if(matchpattern(filename, pattern, true))
1369 strlcpy(foundurl, url, sizeof(foundurl));
1381 if(pattern && !patternend)
1383 else if(url && !urlend)
1389 else if(pattern && patternend && !url)
1398 return sv_curl_defaulturl.string;
1401 typedef struct requirement_s
1403 struct requirement_s *next;
1404 char filename[MAX_OSPATH];
1407 static requirement *requirements = NULL;
1411 ====================
1414 Adds the given file to the list of requirements.
1415 ====================
1417 void Curl_RequireFile(const char *filename)
1419 requirement *req = (requirement *) Z_Malloc(sizeof(*requirements));
1420 req->next = requirements;
1421 strlcpy(req->filename, filename, sizeof(req->filename));
1426 ====================
1427 Curl_ClearRequirements
1429 Clears the list of required files for playing on the current map.
1430 This should be called at every map change.
1431 ====================
1433 void Curl_ClearRequirements(void)
1438 requirement *req = requirements;
1439 requirements = requirements->next;
1442 p = sv_curl_serverpackages.string;
1443 Con_DPrintf("Require all of: %s\n", p);
1444 while(COM_ParseToken_Simple(&p, false, false))
1446 Con_DPrintf("Require: %s\n", com_token);
1447 Curl_RequireFile(com_token);
1452 ====================
1453 Curl_SendRequirements
1455 Makes the current host_clients download all files he needs.
1456 This is done by sending him the following console commands:
1458 curl --clear_autodownload
1459 curl --pak --for maps/pushmoddm1.bsp --forthismap http://where/this/darn/map/is/pushmoddm1.pk3
1460 curl --finish_autodownload
1461 ====================
1463 void Curl_SendRequirements(void)
1465 // for each requirement, find the pack name
1466 char sendbuffer[4096] = "";
1468 qboolean foundone = false;
1470 for(req = requirements; req; req = req->next)
1473 const char *thispack = FS_WhichPack(req->filename);
1474 const char *packurl;
1479 p = strrchr(thispack, '/');
1483 packurl = Curl_FindPackURL(thispack);
1485 if(packurl && *packurl && strcmp(packurl, "-"))
1488 strlcat(sendbuffer, "curl --clear_autodownload\n", sizeof(sendbuffer));
1490 strlcat(sendbuffer, "curl --pak --forthismap --as ", sizeof(sendbuffer));
1491 strlcat(sendbuffer, thispack, sizeof(sendbuffer));
1492 strlcat(sendbuffer, " --for ", sizeof(sendbuffer));
1493 strlcat(sendbuffer, req->filename, sizeof(sendbuffer));
1494 strlcat(sendbuffer, " ", sizeof(sendbuffer));
1495 strlcat(sendbuffer, packurl, sizeof(sendbuffer));
1496 strlcat(sendbuffer, thispack, sizeof(sendbuffer));
1497 strlcat(sendbuffer, "\n", sizeof(sendbuffer));
1504 strlcat(sendbuffer, "curl --finish_autodownload\n", sizeof(sendbuffer));
1506 if(strlen(sendbuffer) + 1 < sizeof(sendbuffer))
1507 Host_ClientCommands("%s", sendbuffer);
1509 Con_Printf("Could not initiate autodownload due to URL buffer overflow\n");