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),
61 CURLINFO_HEADER_IN, /* 1 */
62 CURLINFO_HEADER_OUT, /* 2 */
63 CURLINFO_DATA_IN, /* 3 */
64 CURLINFO_DATA_OUT, /* 4 */
65 CURLINFO_SSL_DATA_IN, /* 5 */
66 CURLINFO_SSL_DATA_OUT, /* 6 */
70 #define CURLINFO_STRING 0x100000
71 #define CURLINFO_LONG 0x200000
72 #define CURLINFO_DOUBLE 0x300000
73 #define CURLINFO_SLIST 0x400000
74 #define CURLINFO_MASK 0x0fffff
75 #define CURLINFO_TYPEMASK 0xf00000
78 CURLINFO_NONE, /* first, never use this */
79 CURLINFO_EFFECTIVE_URL = CURLINFO_STRING + 1,
80 CURLINFO_RESPONSE_CODE = CURLINFO_LONG + 2,
81 CURLINFO_TOTAL_TIME = CURLINFO_DOUBLE + 3,
82 CURLINFO_NAMELOOKUP_TIME = CURLINFO_DOUBLE + 4,
83 CURLINFO_CONNECT_TIME = CURLINFO_DOUBLE + 5,
84 CURLINFO_PRETRANSFER_TIME = CURLINFO_DOUBLE + 6,
85 CURLINFO_SIZE_UPLOAD = CURLINFO_DOUBLE + 7,
86 CURLINFO_SIZE_DOWNLOAD = CURLINFO_DOUBLE + 8,
87 CURLINFO_SPEED_DOWNLOAD = CURLINFO_DOUBLE + 9,
88 CURLINFO_SPEED_UPLOAD = CURLINFO_DOUBLE + 10,
89 CURLINFO_HEADER_SIZE = CURLINFO_LONG + 11,
90 CURLINFO_REQUEST_SIZE = CURLINFO_LONG + 12,
91 CURLINFO_SSL_VERIFYRESULT = CURLINFO_LONG + 13,
92 CURLINFO_FILETIME = CURLINFO_LONG + 14,
93 CURLINFO_CONTENT_LENGTH_DOWNLOAD = CURLINFO_DOUBLE + 15,
94 CURLINFO_CONTENT_LENGTH_UPLOAD = CURLINFO_DOUBLE + 16,
95 CURLINFO_STARTTRANSFER_TIME = CURLINFO_DOUBLE + 17,
96 CURLINFO_CONTENT_TYPE = CURLINFO_STRING + 18,
97 CURLINFO_REDIRECT_TIME = CURLINFO_DOUBLE + 19,
98 CURLINFO_REDIRECT_COUNT = CURLINFO_LONG + 20,
99 CURLINFO_PRIVATE = CURLINFO_STRING + 21,
100 CURLINFO_HTTP_CONNECTCODE = CURLINFO_LONG + 22,
101 CURLINFO_HTTPAUTH_AVAIL = CURLINFO_LONG + 23,
102 CURLINFO_PROXYAUTH_AVAIL = CURLINFO_LONG + 24,
103 CURLINFO_OS_ERRNO = CURLINFO_LONG + 25,
104 CURLINFO_NUM_CONNECTS = CURLINFO_LONG + 26,
105 CURLINFO_SSL_ENGINES = CURLINFO_SLIST + 27,
111 CURLMSG_NONE, /* first, not used */
112 CURLMSG_DONE, /* This easy handle has completed. 'result' contains
113 the CURLcode of the transfer */
119 CURLMSG msg; /* what this message means */
120 CURL *easy_handle; /* the handle it concerns */
123 void *whatever; /* message-specific data */
124 CURLcode result; /* return code for transfer */
130 static void (*qcurl_global_init) (long flags);
131 static void (*qcurl_global_cleanup) ();
133 static CURL * (*qcurl_easy_init) ();
134 static void (*qcurl_easy_cleanup) (CURL *handle);
135 static CURLcode (*qcurl_easy_setopt) (CURL *handle, CURLoption option, ...);
136 static CURLcode (*qcurl_easy_getinfo) (CURL *handle, CURLINFO info, ...);
137 static const char * (*qcurl_easy_strerror) (CURLcode);
139 static CURLM * (*qcurl_multi_init) ();
140 static CURLMcode (*qcurl_multi_perform) (CURLM *multi_handle, int *running_handles);
141 static CURLMcode (*qcurl_multi_add_handle) (CURLM *multi_handle, CURL *easy_handle);
142 static CURLMcode (*qcurl_multi_remove_handle) (CURLM *multi_handle, CURL *easy_handle);
143 static CURLMsg * (*qcurl_multi_info_read) (CURLM *multi_handle, int *msgs_in_queue);
144 static void (*qcurl_multi_cleanup) (CURLM *);
145 static const char * (*qcurl_multi_strerror) (CURLcode);
147 static dllfunction_t curlfuncs[] =
149 {"curl_global_init", (void **) &qcurl_global_init},
150 {"curl_global_cleanup", (void **) &qcurl_global_cleanup},
151 {"curl_easy_init", (void **) &qcurl_easy_init},
152 {"curl_easy_cleanup", (void **) &qcurl_easy_cleanup},
153 {"curl_easy_setopt", (void **) &qcurl_easy_setopt},
154 {"curl_easy_strerror", (void **) &qcurl_easy_strerror},
155 {"curl_easy_getinfo", (void **) &qcurl_easy_getinfo},
156 {"curl_multi_init", (void **) &qcurl_multi_init},
157 {"curl_multi_perform", (void **) &qcurl_multi_perform},
158 {"curl_multi_add_handle", (void **) &qcurl_multi_add_handle},
159 {"curl_multi_remove_handle",(void **) &qcurl_multi_remove_handle},
160 {"curl_multi_info_read", (void **) &qcurl_multi_info_read},
161 {"curl_multi_cleanup", (void **) &qcurl_multi_cleanup},
162 {"curl_multi_strerror", (void **) &qcurl_multi_strerror},
166 // Handle for CURL DLL
167 static dllhandle_t curl_dll = NULL;
168 // will be checked at many places to find out if qcurl calls are allowed
170 typedef struct downloadinfo_s
172 char filename[MAX_QPATH];
176 fs_offset_t startpos;
180 unsigned long bytes_received;
181 struct downloadinfo_s *next, *prev;
184 unsigned char *buffer;
186 curl_callback_t callback;
190 static downloadinfo *downloads = NULL;
191 static int numdownloads = 0;
193 static qboolean noclear = FALSE;
195 static int numdownloads_fail = 0;
196 static int numdownloads_success = 0;
197 static int numdownloads_added = 0;
198 static char command_when_done[256] = "";
199 static char command_when_error[256] = "";
205 Sets the command which is to be executed when the last download completes AND
206 all downloads since last server connect ended with a successful status.
207 Setting the command to NULL clears it.
210 void Curl_CommandWhenDone(const char *cmd)
215 strlcpy(command_when_done, cmd, sizeof(command_when_done));
217 *command_when_done = 0;
222 Do not use yet. Not complete.
223 Problem: what counts as an error?
226 void Curl_CommandWhenError(const char *cmd)
231 strlcpy(command_when_error, cmd, sizeof(command_when_error));
233 *command_when_error = 0;
238 Curl_Clear_forthismap
240 Clears the "will disconnect on failure" flags.
243 void Curl_Clear_forthismap()
248 for(di = downloads; di; di = di->next)
249 di->forthismap = false;
250 Curl_CommandWhenError(NULL);
251 Curl_CommandWhenDone(NULL);
252 numdownloads_fail = 0;
253 numdownloads_success = 0;
254 numdownloads_added = 0;
261 Returns true if a download needed for the current game is running.
264 qboolean Curl_Have_forthismap()
266 return numdownloads_added;
269 void Curl_Register_predownload()
271 Curl_CommandWhenDone("cl_begindownloads");
272 Curl_CommandWhenError("cl_begindownloads");
277 Curl_CheckCommandWhenDone
279 Checks if a "done command" is to be executed.
280 All downloads finished, at least one success since connect, no single failure
281 -> execute the command.
283 static void Curl_CheckCommandWhenDone()
287 if(numdownloads_added && (numdownloads_success == numdownloads_added) && *command_when_done)
289 Con_DPrintf("cURL downloads occurred, executing %s\n", command_when_done);
291 Cbuf_AddText(command_when_done);
293 Curl_Clear_forthismap();
295 else if(numdownloads_added && numdownloads_fail && *command_when_error)
297 Con_DPrintf("cURL downloads FAILED, executing %s\n", command_when_error);
299 Cbuf_AddText(command_when_error);
301 Curl_Clear_forthismap();
312 static qboolean CURL_OpenLibrary (void)
314 const char* dllnames [] =
321 #elif defined(MACOSX)
322 "libcurl.4.dylib", // Mac OS X Notyetreleased
323 "libcurl.3.dylib", // Mac OS X Tiger
324 "libcurl.2.dylib", // Mac OS X Panther
328 "libcurl.so", // FreeBSD
338 return Sys_LoadLibrary (dllnames, &curl_dll, curlfuncs);
349 static void CURL_CloseLibrary (void)
351 Sys_UnloadLibrary (&curl_dll);
355 static CURLM *curlm = NULL;
356 static unsigned long bytes_received = 0; // used for bandwidth throttling
357 static double curltime = 0;
363 fwrite-compatible function that writes the data to a file. libcurl can call
367 static size_t CURL_fwrite(void *data, size_t size, size_t nmemb, void *vdi)
369 fs_offset_t ret = -1;
370 size_t bytes = size * nmemb;
371 downloadinfo *di = (downloadinfo *) vdi;
375 if(di->bytes_received + bytes <= di->buffersize)
377 memcpy(di->buffer + di->bytes_received, data, bytes);
380 // otherwise: buffer overrun, ret stays -1
385 ret = FS_Write(di->stream, data, bytes);
388 bytes_received += bytes;
389 di->bytes_received += bytes;
391 return ret; // why not ret / nmemb?
396 CURL_DOWNLOAD_SUCCESS = 0,
397 CURL_DOWNLOAD_FAILED,
398 CURL_DOWNLOAD_ABORTED,
399 CURL_DOWNLOAD_SERVERERROR
403 static void curl_default_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata)
405 downloadinfo *di = (downloadinfo *) cbdata;
408 case CURLCBSTATUS_OK:
409 Con_Printf("Download of %s: OK\n", di->filename);
411 case CURLCBSTATUS_FAILED:
412 Con_Printf("Download of %s: FAILED\n", di->filename);
414 case CURLCBSTATUS_ABORTED:
415 Con_Printf("Download of %s: ABORTED\n", di->filename);
417 case CURLCBSTATUS_SERVERERROR:
418 Con_Printf("Download of %s: (unknown server error)\n", di->filename);
420 case CURLCBSTATUS_UNKNOWN:
421 Con_Printf("Download of %s: (unknown client error)\n", di->filename);
424 Con_Printf("Download of %s: %d\n", di->filename, status);
429 static void curl_quiet_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata)
431 if(developer.integer)
432 curl_default_callback(status, length_received, buffer, cbdata);
439 stops a download. It receives a status (CURL_DOWNLOAD_SUCCESS,
440 CURL_DOWNLOAD_FAILED or CURL_DOWNLOAD_ABORTED) and in the second case the error
441 code from libcurl, or 0, if another error has occurred.
444 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);
445 static void Curl_EndDownload(downloadinfo *di, CurlStatus status, CURLcode error)
452 case CURL_DOWNLOAD_SUCCESS:
454 di->callback(CURLCBSTATUS_OK, di->bytes_received, di->buffer, di->callback_data);
456 case CURL_DOWNLOAD_FAILED:
457 di->callback(CURLCBSTATUS_FAILED, di->bytes_received, di->buffer, di->callback_data);
459 case CURL_DOWNLOAD_ABORTED:
460 di->callback(CURLCBSTATUS_ABORTED, di->bytes_received, di->buffer, di->callback_data);
462 case CURL_DOWNLOAD_SERVERERROR:
463 // reopen to enforce it to have zero bytes again
466 FS_Close(di->stream);
467 di->stream = FS_OpenRealFile(di->filename, "wb", false);
471 di->callback(error ? (int) error : CURLCBSTATUS_SERVERERROR, di->bytes_received, di->buffer, di->callback_data);
475 di->callback(CURLCBSTATUS_UNKNOWN, di->bytes_received, di->buffer, di->callback_data);
481 qcurl_multi_remove_handle(curlm, di->curle);
482 qcurl_easy_cleanup(di->curle);
485 if(!di->callback && ok && !di->bytes_received)
487 Con_Printf("ERROR: empty file\n");
492 FS_Close(di->stream);
496 ok = FS_AddPack(di->filename, NULL, true);
499 // pack loading failed?
501 // better clear the file again...
502 di->stream = FS_OpenRealFile(di->filename, "wb", false);
503 FS_Close(di->stream);
505 if(di->startpos && !di->callback)
507 // this was a resume?
508 // then try to redownload it without reporting the error
509 Curl_Begin(di->url, di->filename, di->ispak, di->forthismap, NULL, 0, NULL, NULL);
510 di->forthismap = false; // don't count the error
516 di->prev->next = di->next;
518 downloads = di->next;
520 di->next->prev = di->prev;
526 ++numdownloads_success;
535 CheckPendingDownloads
537 checks if there are free download slots to start new downloads in.
538 To not start too many downloads at once, only one download is added at a time,
539 up to a maximum number of cl_curl_maxdownloads are running.
542 static void CheckPendingDownloads()
546 if(numdownloads < cl_curl_maxdownloads.integer)
549 for(di = downloads; di; di = di->next)
555 Con_Printf("Downloading %s -> %s", di->url, di->filename);
557 di->stream = FS_OpenRealFile(di->filename, "ab", false);
560 Con_Printf("\nFAILED: Could not open output file %s\n", di->filename);
561 Curl_EndDownload(di, CURL_DOWNLOAD_FAILED, CURLE_OK);
564 FS_Seek(di->stream, 0, SEEK_END);
565 di->startpos = FS_Tell(di->stream);
568 Con_Printf(", resuming from position %ld", (long) di->startpos);
573 Con_DPrintf("Downloading %s -> memory\n", di->url);
577 di->curle = qcurl_easy_init();
578 qcurl_easy_setopt(di->curle, CURLOPT_URL, di->url);
579 qcurl_easy_setopt(di->curle, CURLOPT_USERAGENT, engineversion);
580 qcurl_easy_setopt(di->curle, CURLOPT_REFERER, di->referer);
581 qcurl_easy_setopt(di->curle, CURLOPT_RESUME_FROM, (long) di->startpos);
582 qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 1);
583 qcurl_easy_setopt(di->curle, CURLOPT_WRITEFUNCTION, CURL_fwrite);
584 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_LIMIT, (long) 256);
585 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_TIME, (long) 45);
586 qcurl_easy_setopt(di->curle, CURLOPT_WRITEDATA, (void *) di);
587 qcurl_easy_setopt(di->curle, CURLOPT_PRIVATE, (void *) di);
588 qcurl_multi_add_handle(curlm, di->curle);
591 if(numdownloads >= cl_curl_maxdownloads.integer)
602 this function MUST be called before using anything else in this file.
603 On Win32, this must be called AFTER WSAStartup has been done!
611 qcurl_global_init(CURL_GLOBAL_NOTHING);
612 curlm = qcurl_multi_init();
619 Surprise... closes all the stuff. Please do this BEFORE shutting down LHNET.
622 void Curl_ClearRequirements();
627 Curl_ClearRequirements();
637 Finds the internal information block for a download given by file name.
640 static downloadinfo *Curl_Find(const char *filename)
645 for(di = downloads; di; di = di->next)
646 if(!strcasecmp(di->filename, filename))
651 void Curl_Cancel_ToMemory(curl_callback_t callback, void *cbdata)
656 for(di = downloads; di; )
658 if(di->callback == callback && di->callback_data == cbdata)
660 di->callback = curl_quiet_callback; // do NOT call the callback
661 Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
673 Starts a download of a given URL to the file name portion of this URL (or name
674 if given) in the "dlcache/" folder.
677 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)
690 // Note: This extraction of the file name portion is NOT entirely correct.
692 // It does the following:
694 // http://host/some/script.cgi/SomeFile.pk3?uid=ABCDE -> SomeFile.pk3
695 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3 -> SomeFile.pk3
696 // http://host/some/script.php?uid=ABCDE&file=SomeFile.pk3 -> script.php
698 // However, I'd like to keep this "buggy" behavior so that PHP script
699 // authors can write download scripts without having to enable
700 // AcceptPathInfo on Apache. They just have to ensure that their script
701 // can be called with such a "fake" path name like
702 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3
704 // By the way, such PHP scripts should either send the file or a
705 // "Location:" redirect; PHP code example:
707 // header("Location: http://www.example.com/");
709 // By the way, this will set User-Agent to something like
710 // "Nexuiz build 22:27:55 Mar 17 2006" (engineversion) and Referer to
711 // dp://serverhost:serverport/ so you can filter on this; an example
712 // httpd log file line might be:
714 // 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"
721 p = strrchr(name, '/');
722 p = p ? (p+1) : name;
724 length = q ? (size_t)(q - p) : strlen(p);
725 dpsnprintf(fn, sizeof(fn), "dlcache/%.*s", (int)length, p);
727 name = fn; // make it point back
729 // already downloading the file?
731 downloadinfo *di = Curl_Find(fn);
734 Con_Printf("Can't download %s, already getting it from %s!\n", fn, di->url);
736 // however, if it was not for this map yet...
737 if(forthismap && !di->forthismap)
739 di->forthismap = true;
740 // this "fakes" a download attempt so the client will wait for
741 // the download to finish and then reconnect
742 ++numdownloads_added;
749 if(ispak && FS_FileExists(fn))
751 qboolean already_loaded;
752 if(FS_AddPack(fn, &already_loaded, true))
754 Con_DPrintf("%s already exists, not downloading!\n", fn);
756 Con_DPrintf("(pak was already loaded)\n");
761 ++numdownloads_added;
762 ++numdownloads_success;
770 qfile_t *f = FS_OpenRealFile(fn, "rb", false);
774 FS_Read(f, buf, sizeof(buf)); // no "-1", I will use memcmp
776 if(memcmp(buf, "PK\x03\x04", 4) && memcmp(buf, "PACK", 4))
778 Con_DPrintf("Detected non-PAK %s, clearing and NOT resuming.\n", fn);
780 f = FS_OpenRealFile(fn, "wb", false);
794 // if we get here, we actually want to download... so first verify the
795 // URL scheme (so one can't read local files using file://)
796 if(strncmp(URL, "http://", 7) && strncmp(URL, "ftp://", 6) && strncmp(URL, "https://", 8))
798 Con_Printf("Curl_Begin(\"%s\"): nasty URL scheme rejected\n", URL);
803 ++numdownloads_added;
804 di = (downloadinfo *) Z_Malloc(sizeof(*di));
805 strlcpy(di->filename, name, sizeof(di->filename));
806 strlcpy(di->url, URL, sizeof(di->url));
807 dpsnprintf(di->referer, sizeof(di->referer), "dp://%s/", cls.netcon ? cls.netcon->address : "notconnected.invalid");
808 di->forthismap = forthismap;
813 di->ispak = (ispak && !buf);
814 di->bytes_received = 0;
815 di->next = downloads;
821 di->buffersize = bufsize;
824 di->callback = curl_default_callback;
825 di->callback_data = di;
829 di->callback = callback;
830 di->callback_data = cbdata;
838 qboolean Curl_Begin_ToFile(const char *URL, const char *name, qboolean ispak, qboolean forthismap)
840 return Curl_Begin(URL, name, ispak, forthismap, NULL, 0, NULL, NULL);
842 qboolean Curl_Begin_ToMemory(const char *URL, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata)
844 return Curl_Begin(URL, NULL, false, false, buf, bufsize, callback, cbdata);
851 call this regularily as this will always download as much as possible without
859 if(!cl_curl_enabled.integer)
865 Curl_CheckCommandWhenDone();
870 if(realtime < curltime) // throttle
879 mc = qcurl_multi_perform(curlm, &remaining);
881 while(mc == CURLM_CALL_MULTI_PERFORM);
885 CURLMsg *msg = qcurl_multi_info_read(curlm, &remaining);
888 if(msg->msg == CURLMSG_DONE)
891 CurlStatus failed = CURL_DOWNLOAD_SUCCESS;
893 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &di);
894 result = msg->data.result;
897 failed = CURL_DOWNLOAD_FAILED;
902 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code);
907 failed = CURL_DOWNLOAD_SERVERERROR;
908 result = (CURLcode) code;
913 Curl_EndDownload(di, failed, result);
918 CheckPendingDownloads();
920 // when will we curl the next time?
921 // we will wait a bit to ensure our download rate is kept.
922 // we now know that realtime >= curltime... so set up a new curltime
923 if(cl_curl_maxspeed.value > 0)
925 unsigned long bytes = bytes_received; // maybe smoothen a bit?
926 curltime = realtime + bytes / (cl_curl_maxspeed.value * 1024.0);
927 bytes_received -= bytes;
940 void Curl_CancelAll()
947 Curl_EndDownload(downloads, CURL_DOWNLOAD_ABORTED, CURLE_OK);
948 // INVARIANT: downloads will point to the next download after that!
956 returns true iff there is a download running.
959 qboolean Curl_Running()
964 return downloads != NULL;
969 Curl_GetDownloadAmount
971 returns a value from 0.0 to 1.0 which represents the downloaded amount of data
972 for the given download.
975 static double Curl_GetDownloadAmount(downloadinfo *di)
982 qcurl_easy_getinfo(di->curle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
984 return (di->startpos + di->bytes_received) / (di->startpos + length);
994 Curl_GetDownloadSpeed
996 returns the speed of the given download in bytes per second
999 static double Curl_GetDownloadSpeed(downloadinfo *di)
1006 qcurl_easy_getinfo(di->curle, CURLINFO_SPEED_DOWNLOAD, &speed);
1014 ====================
1017 prints the download list
1018 ====================
1020 // TODO rewrite using Curl_GetDownloadInfo?
1021 static void Curl_Info_f()
1028 Con_Print("Currently running downloads:\n");
1029 for(di = downloads; di; di = di->next)
1031 double speed, percent;
1032 Con_Printf(" %s -> %s ", di->url, di->filename);
1033 percent = 100.0 * Curl_GetDownloadAmount(di);
1034 speed = Curl_GetDownloadSpeed(di);
1036 Con_Printf("(%.1f%% @ %.1f KiB/s)\n", percent, speed / 1024.0);
1038 Con_Print("(queued)\n");
1043 Con_Print("No downloads running.\n");
1048 ====================
1051 implements the "curl" console command
1055 curl --cancel filename
1060 curl [--pak] [--forthismap] [--for filename filename...] url
1061 --pak: after downloading, load the package into the virtual file system
1062 --for filename...: only download of at least one of the named files is missing
1063 --forthismap: don't reconnect on failure
1065 curl --clear_autodownload
1066 clears the download success/failure counters
1068 curl --finish_autodownload
1069 if at least one download has been started, disconnect and drop to the menu
1070 once the last download completes successfully, reconnect to the current server
1071 ====================
1073 void Curl_Curl_f(void)
1077 qboolean pak = false;
1078 qboolean forthismap = false;
1080 const char *name = 0;
1084 Con_Print("libcurl DLL not found, this command is inactive.\n");
1088 if(!cl_curl_enabled.integer)
1090 Con_Print("curl support not enabled. Set cl_curl_enabled to 1 to enable.\n");
1094 for(i = 0; i != Cmd_Argc(); ++i)
1095 Con_DPrintf("%s ", Cmd_Argv(i));
1100 Con_Print("usage:\ncurl --info, curl --cancel [filename], curl url\n");
1104 url = Cmd_Argv(Cmd_Argc() - 1);
1107 for(i = 1; i != end; ++i)
1109 const char *a = Cmd_Argv(i);
1110 if(!strcmp(a, "--info"))
1115 else if(!strcmp(a, "--cancel"))
1117 if(i == end - 1) // last argument
1121 downloadinfo *di = Curl_Find(url);
1123 Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
1125 Con_Print("download not found\n");
1129 else if(!strcmp(a, "--pak"))
1133 else if(!strcmp(a, "--for"))
1135 for(i = i + 1; i != end - 1; ++i)
1137 if(!FS_FileExists(Cmd_Argv(i)))
1138 goto needthefile; // why can't I have a "double break"?
1140 // if we get here, we have all the files...
1143 else if(!strcmp(a, "--forthismap"))
1147 else if(!strcmp(a, "--as"))
1155 else if(!strcmp(a, "--clear_autodownload"))
1157 // mark all running downloads as "not for this map", so if they
1158 // fail, it does not matter
1159 Curl_Clear_forthismap();
1162 else if(!strcmp(a, "--finish_autodownload"))
1164 if(numdownloads_added)
1166 char donecommand[256];
1169 if(cl.loadbegun) // curling won't inhibit loading the map any more when at this stage, so bail out and force a reconnect
1171 dpsnprintf(donecommand, sizeof(donecommand), "connect %s", cls.netcon->address);
1172 Curl_CommandWhenDone(donecommand);
1176 Curl_CheckCommandWhenDone();
1179 Curl_Register_predownload();
1186 Con_Printf("invalid option %s\n", a);
1192 Curl_Begin_ToFile(url, name, pak, forthismap);
1196 static void curl_curlcat_callback(int code, size_t length_received, unsigned char *buffer, void *cbdata)
1198 Con_Printf("Received %d bytes (status %d):\n%.*s\n", (int) length_received, code, (int) length_received, buffer);
1202 void Curl_CurlCat_f(void)
1205 const char *url = Cmd_Argv(1);
1206 buf = Z_Malloc(16384);
1207 Curl_Begin_ToMemory(url, buf, 16384, curl_curlcat_callback, NULL);
1212 ====================
1215 loads the commands and cvars this library uses
1216 ====================
1218 void Curl_Init_Commands(void)
1220 Cvar_RegisterVariable (&cl_curl_enabled);
1221 Cvar_RegisterVariable (&cl_curl_maxdownloads);
1222 Cvar_RegisterVariable (&cl_curl_maxspeed);
1223 Cvar_RegisterVariable (&sv_curl_defaulturl);
1224 Cvar_RegisterVariable (&sv_curl_serverpackages);
1225 Cmd_AddCommand ("curl", Curl_Curl_f, "download data from an URL and add to search path");
1226 //Cmd_AddCommand ("curlcat", Curl_CurlCat_f, "display data from an URL (debugging command)");
1230 ====================
1231 Curl_GetDownloadInfo
1233 returns an array of Curl_downloadinfo_t structs for usage by GUIs.
1234 The number of elements in the array is returned in int *nDownloads.
1235 const char **additional_info may be set to a string of additional user
1236 information, or to NULL if no such display shall occur. The returned
1237 array must be freed later using Z_Free.
1238 ====================
1240 Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **additional_info)
1244 Curl_downloadinfo_t *downinfo;
1245 static char addinfo[128];
1251 *additional_info = NULL;
1256 for(di = downloads; di; di = di->next)
1259 downinfo = (Curl_downloadinfo_t *) Z_Malloc(sizeof(*downinfo) * i);
1261 for(di = downloads; di; di = di->next)
1263 // do not show infobars for background downloads
1264 if(!developer.integer)
1267 strlcpy(downinfo[i].filename, di->filename, sizeof(downinfo[i].filename));
1270 downinfo[i].progress = Curl_GetDownloadAmount(di);
1271 downinfo[i].speed = Curl_GetDownloadSpeed(di);
1272 downinfo[i].queued = false;
1276 downinfo[i].queued = true;
1283 // TODO: can I clear command_when_done as soon as the first download fails?
1284 if(*command_when_done && !numdownloads_fail && numdownloads_added)
1286 if(!strncmp(command_when_done, "connect ", 8))
1287 dpsnprintf(addinfo, sizeof(addinfo), "(will join %s when done)", command_when_done + 8);
1288 else if(!strcmp(command_when_done, "cl_begindownloads"))
1289 dpsnprintf(addinfo, sizeof(addinfo), "(will enter the game when done)");
1291 dpsnprintf(addinfo, sizeof(addinfo), "(will do '%s' when done)", command_when_done);
1292 *additional_info = addinfo;
1295 *additional_info = NULL;
1304 ====================
1307 finds the URL where to find a given package.
1309 For this, it reads a file "curl_urls.txt" of the following format:
1312 revdm*.pk3 http://revdm/downloads/are/here/
1313 * http://any/other/stuff/is/here/
1315 The URLs should end in /. If not, downloads will still work, but the cached files
1316 can't be just put into the data directory with the same download configuration
1317 (you might want to do this if you want to tag downloaded files from your
1318 server, but you should not). "-" means "don't download".
1320 If no single pattern matched, the cvar sv_curl_defaulturl is used as download
1323 Note: pak1.pak and data*.pk3 are excluded from autodownload at another point in
1324 this file for obvious reasons.
1325 ====================
1327 static const char *Curl_FindPackURL(const char *filename)
1329 static char foundurl[1024];
1330 fs_offset_t filesize;
1331 char *buf = (char *) FS_LoadFile("curl_urls.txt", tempmempool, true, &filesize);
1334 // read lines of format "pattern url"
1336 char *pattern = NULL, *patternend = NULL, *url = NULL, *urlend = NULL;
1337 qboolean eof = false;
1349 if(pattern && url && patternend)
1355 if(matchpattern(filename, pattern, true))
1357 strlcpy(foundurl, url, sizeof(foundurl));
1369 if(pattern && !patternend)
1371 else if(url && !urlend)
1377 else if(pattern && patternend && !url)
1386 return sv_curl_defaulturl.string;
1389 typedef struct requirement_s
1391 struct requirement_s *next;
1392 char filename[MAX_QPATH];
1395 static requirement *requirements = NULL;
1399 ====================
1402 Adds the given file to the list of requirements.
1403 ====================
1405 void Curl_RequireFile(const char *filename)
1407 requirement *req = (requirement *) Z_Malloc(sizeof(*requirements));
1408 req->next = requirements;
1409 strlcpy(req->filename, filename, sizeof(req->filename));
1414 ====================
1415 Curl_ClearRequirements
1417 Clears the list of required files for playing on the current map.
1418 This should be called at every map change.
1419 ====================
1421 void Curl_ClearRequirements()
1426 requirement *req = requirements;
1427 requirements = requirements->next;
1430 p = sv_curl_serverpackages.string;
1431 Con_DPrintf("Require all of: %s\n", p);
1432 while(COM_ParseToken_Simple(&p, false, false))
1434 Con_DPrintf("Require: %s\n", com_token);
1435 Curl_RequireFile(com_token);
1440 ====================
1441 Curl_SendRequirements
1443 Makes the current host_clients download all files he needs.
1444 This is done by sending him the following console commands:
1446 curl --clear_autodownload
1447 curl --pak --for maps/pushmoddm1.bsp --forthismap http://where/this/darn/map/is/pushmoddm1.pk3
1448 curl --finish_autodownload
1449 ====================
1451 void Curl_SendRequirements()
1453 // for each requirement, find the pack name
1454 char sendbuffer[4096] = "";
1456 qboolean foundone = false;
1458 for(req = requirements; req; req = req->next)
1461 const char *thispack = FS_WhichPack(req->filename);
1462 const char *packurl;
1467 p = strrchr(thispack, '/');
1471 packurl = Curl_FindPackURL(thispack);
1473 if(packurl && *packurl && strcmp(packurl, "-"))
1476 strlcat(sendbuffer, "curl --clear_autodownload\n", sizeof(sendbuffer));
1478 strlcat(sendbuffer, "curl --pak --forthismap --as ", sizeof(sendbuffer));
1479 strlcat(sendbuffer, thispack, sizeof(sendbuffer));
1480 strlcat(sendbuffer, " --for ", sizeof(sendbuffer));
1481 strlcat(sendbuffer, req->filename, sizeof(sendbuffer));
1482 strlcat(sendbuffer, " ", sizeof(sendbuffer));
1483 strlcat(sendbuffer, packurl, sizeof(sendbuffer));
1484 strlcat(sendbuffer, thispack, sizeof(sendbuffer));
1485 strlcat(sendbuffer, "\n", sizeof(sendbuffer));
1492 strlcat(sendbuffer, "curl --finish_autodownload\n", sizeof(sendbuffer));
1494 if(strlen(sendbuffer) + 1 < sizeof(sendbuffer))
1495 Host_ClientCommands("%s", sendbuffer);
1497 Con_Printf("Could not initiate autodownload due to URL buffer overflow\n");