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
407 stops a download. It receives a status (CURL_DOWNLOAD_SUCCESS,
408 CURL_DOWNLOAD_FAILED or CURL_DOWNLOAD_ABORTED) and in the second case the error
409 code from libcurl, or 0, if another error has occurred.
412 static void Curl_EndDownload(downloadinfo *di, CurlStatus status, CURLcode error)
419 case CURL_DOWNLOAD_SUCCESS:
420 Con_Printf("Download of %s: OK\n", di->filename);
424 di->callback(CURLCBSTATUS_OK, di->bytes_received, di->buffer, di->callback_data);
426 case CURL_DOWNLOAD_FAILED:
427 Con_Printf("Download of %s: FAILED\n", di->filename);
429 Con_Printf("Reason given by libcurl: %s\n", qcurl_easy_strerror(error));
432 di->callback(CURLCBSTATUS_FAILED, di->bytes_received, di->buffer, di->callback_data);
434 case CURL_DOWNLOAD_ABORTED:
435 Con_Printf("Download of %s: ABORTED\n", di->filename);
438 di->callback(CURLCBSTATUS_ABORTED, di->bytes_received, di->buffer, di->callback_data);
440 case CURL_DOWNLOAD_SERVERERROR:
441 Con_Printf("Download of %s: %d\n", di->filename, (int) error);
443 // reopen to enforce it to have zero bytes again
446 FS_Close(di->stream);
447 di->stream = FS_OpenRealFile(di->filename, "wb", false);
451 di->callback(error ? (int) error : CURLCBSTATUS_SERVERERROR, di->bytes_received, di->buffer, di->callback_data);
454 Con_Printf("Download of %s: ???\n", di->filename);
457 di->callback(CURLCBSTATUS_UNKNOWN, di->bytes_received, di->buffer, di->callback_data);
463 qcurl_multi_remove_handle(curlm, di->curle);
464 qcurl_easy_cleanup(di->curle);
467 if(ok && !di->bytes_received)
469 Con_Printf("ERROR: empty file\n");
474 FS_Close(di->stream);
477 ok = FS_AddPack(di->filename, NULL, true);
480 di->prev->next = di->next;
482 downloads = di->next;
484 di->next->prev = di->prev;
490 ++numdownloads_success;
499 CheckPendingDownloads
501 checks if there are free download slots to start new downloads in.
502 To not start too many downloads at once, only one download is added at a time,
503 up to a maximum number of cl_curl_maxdownloads are running.
506 static void CheckPendingDownloads()
510 if(numdownloads < cl_curl_maxdownloads.integer)
513 for(di = downloads; di; di = di->next)
517 Con_Printf("Downloading %s -> %s", di->url, di->filename);
521 di->stream = FS_OpenRealFile(di->filename, "ab", false);
524 Con_Printf("\nFAILED: Could not open output file %s\n", di->filename);
525 Curl_EndDownload(di, CURL_DOWNLOAD_FAILED, CURLE_OK);
528 FS_Seek(di->stream, 0, SEEK_END);
529 di->startpos = FS_Tell(di->stream);
537 Con_Printf(", resuming from position %ld", (long) di->startpos);
540 di->curle = qcurl_easy_init();
541 qcurl_easy_setopt(di->curle, CURLOPT_URL, di->url);
542 qcurl_easy_setopt(di->curle, CURLOPT_USERAGENT, engineversion);
543 qcurl_easy_setopt(di->curle, CURLOPT_REFERER, di->referer);
544 qcurl_easy_setopt(di->curle, CURLOPT_RESUME_FROM, (long) di->startpos);
545 qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 1);
546 qcurl_easy_setopt(di->curle, CURLOPT_WRITEFUNCTION, CURL_fwrite);
547 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_LIMIT, (long) 256);
548 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_TIME, (long) 45);
549 qcurl_easy_setopt(di->curle, CURLOPT_WRITEDATA, (void *) di);
550 qcurl_easy_setopt(di->curle, CURLOPT_PRIVATE, (void *) di);
551 qcurl_multi_add_handle(curlm, di->curle);
554 if(numdownloads >= cl_curl_maxdownloads.integer)
565 this function MUST be called before using anything else in this file.
566 On Win32, this must be called AFTER WSAStartup has been done!
574 qcurl_global_init(CURL_GLOBAL_NOTHING);
575 curlm = qcurl_multi_init();
582 Surprise... closes all the stuff. Please do this BEFORE shutting down LHNET.
585 void Curl_ClearRequirements();
590 Curl_ClearRequirements();
600 Finds the internal information block for a download given by file name.
603 static downloadinfo *Curl_Find(const char *filename)
608 for(di = downloads; di; di = di->next)
609 if(!strcasecmp(di->filename, filename))
618 Starts a download of a given URL to the file name portion of this URL (or name
619 if given) in the "dlcache/" folder.
622 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)
635 // Note: This extraction of the file name portion is NOT entirely correct.
637 // It does the following:
639 // http://host/some/script.cgi/SomeFile.pk3?uid=ABCDE -> SomeFile.pk3
640 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3 -> SomeFile.pk3
641 // http://host/some/script.php?uid=ABCDE&file=SomeFile.pk3 -> script.php
643 // However, I'd like to keep this "buggy" behavior so that PHP script
644 // authors can write download scripts without having to enable
645 // AcceptPathInfo on Apache. They just have to ensure that their script
646 // can be called with such a "fake" path name like
647 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3
649 // By the way, such PHP scripts should either send the file or a
650 // "Location:" redirect; PHP code example:
652 // header("Location: http://www.example.com/");
654 // By the way, this will set User-Agent to something like
655 // "Nexuiz build 22:27:55 Mar 17 2006" (engineversion) and Referer to
656 // dp://serverhost:serverport/ so you can filter on this; an example
657 // httpd log file line might be:
659 // 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"
666 p = strrchr(name, '/');
667 p = p ? (p+1) : name;
669 length = q ? (size_t)(q - p) : strlen(p);
670 dpsnprintf(fn, sizeof(fn), "dlcache/%.*s", (int)length, p);
672 name = fn; // make it point back
674 // already downloading the file?
676 downloadinfo *di = Curl_Find(fn);
679 Con_Printf("Can't download %s, already getting it from %s!\n", fn, di->url);
681 // however, if it was not for this map yet...
682 if(forthismap && !di->forthismap)
684 di->forthismap = true;
685 // this "fakes" a download attempt so the client will wait for
686 // the download to finish and then reconnect
687 ++numdownloads_added;
694 if(ispak && FS_FileExists(fn))
696 qboolean already_loaded;
697 if(FS_AddPack(fn, &already_loaded, true))
699 Con_DPrintf("%s already exists, not downloading!\n", fn);
701 Con_DPrintf("(pak was already loaded)\n");
706 ++numdownloads_added;
707 ++numdownloads_success;
715 qfile_t *f = FS_OpenVirtualFile(fn, false);
719 FS_Read(f, buf, sizeof(buf)); // no "-1", I will use memcmp
721 if(memcmp(buf, "PK\x03\x04", 4) && memcmp(buf, "PACK", 4))
723 Con_DPrintf("Detected non-PAK %s, clearing and NOT resuming.\n", fn);
725 f = FS_OpenRealFile(fn, "wb", false);
739 // if we get here, we actually want to download... so first verify the
740 // URL scheme (so one can't read local files using file://)
741 if(strncmp(URL, "http://", 7) && strncmp(URL, "ftp://", 6) && strncmp(URL, "https://", 8))
743 Con_Printf("Curl_Begin(\"%s\")): nasty URL scheme rejected\n", URL);
748 ++numdownloads_added;
749 di = (downloadinfo *) Z_Malloc(sizeof(*di));
750 strlcpy(di->filename, name, sizeof(di->filename));
751 strlcpy(di->url, URL, sizeof(di->url));
752 dpsnprintf(di->referer, sizeof(di->referer), "dp://%s/", cls.netcon ? cls.netcon->address : "notconnected.invalid");
753 di->forthismap = forthismap;
759 di->bytes_received = 0;
760 di->next = downloads;
766 di->buffersize = bufsize;
767 di->callback = callback;
768 di->callback_data = cbdata;
775 qboolean Curl_Begin_ToFile(const char *URL, const char *name, qboolean ispak, qboolean forthismap)
777 return Curl_Begin(URL, name, ispak, forthismap, NULL, 0, NULL, NULL);
779 qboolean Curl_Begin_ToMemory(const char *URL, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata)
781 return Curl_Begin(URL, NULL, false, false, buf, bufsize, callback, cbdata);
788 call this regularily as this will always download as much as possible without
796 if(!cl_curl_enabled.integer)
802 Curl_CheckCommandWhenDone();
807 if(realtime < curltime) // throttle
816 mc = qcurl_multi_perform(curlm, &remaining);
818 while(mc == CURLM_CALL_MULTI_PERFORM);
822 CURLMsg *msg = qcurl_multi_info_read(curlm, &remaining);
825 if(msg->msg == CURLMSG_DONE)
828 CurlStatus failed = CURL_DOWNLOAD_SUCCESS;
830 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &di);
831 result = msg->data.result;
834 failed = CURL_DOWNLOAD_FAILED;
839 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code);
844 failed = CURL_DOWNLOAD_SERVERERROR;
850 Curl_EndDownload(di, failed, result);
855 CheckPendingDownloads();
857 // when will we curl the next time?
858 // we will wait a bit to ensure our download rate is kept.
859 // we now know that realtime >= curltime... so set up a new curltime
860 if(cl_curl_maxspeed.value > 0)
862 unsigned long bytes = bytes_received; // maybe smoothen a bit?
863 curltime = realtime + bytes / (cl_curl_maxspeed.value * 1024.0);
864 bytes_received -= bytes;
877 void Curl_CancelAll()
884 Curl_EndDownload(downloads, CURL_DOWNLOAD_ABORTED, CURLE_OK);
885 // INVARIANT: downloads will point to the next download after that!
893 returns true iff there is a download running.
896 qboolean Curl_Running()
901 return downloads != NULL;
906 Curl_GetDownloadAmount
908 returns a value from 0.0 to 1.0 which represents the downloaded amount of data
909 for the given download.
912 static double Curl_GetDownloadAmount(downloadinfo *di)
919 qcurl_easy_getinfo(di->curle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
921 return di->bytes_received / length;
931 Curl_GetDownloadSpeed
933 returns the speed of the given download in bytes per second
936 static double Curl_GetDownloadSpeed(downloadinfo *di)
943 qcurl_easy_getinfo(di->curle, CURLINFO_SPEED_DOWNLOAD, &speed);
954 prints the download list
957 // TODO rewrite using Curl_GetDownloadInfo?
958 static void Curl_Info_f()
965 Con_Print("Currently running downloads:\n");
966 for(di = downloads; di; di = di->next)
968 double speed, percent;
969 Con_Printf(" %s -> %s ", di->url, di->filename);
970 percent = 100.0 * Curl_GetDownloadAmount(di);
971 speed = Curl_GetDownloadSpeed(di);
973 Con_Printf("(%.1f%% @ %.1f KiB/s)\n", percent, speed / 1024.0);
975 Con_Print("(queued)\n");
980 Con_Print("No downloads running.\n");
988 implements the "curl" console command
992 curl --cancel filename
997 curl [--pak] [--forthismap] [--for filename filename...] url
998 --pak: after downloading, load the package into the virtual file system
999 --for filename...: only download of at least one of the named files is missing
1000 --forthismap: don't reconnect on failure
1002 curl --clear_autodownload
1003 clears the download success/failure counters
1005 curl --finish_autodownload
1006 if at least one download has been started, disconnect and drop to the menu
1007 once the last download completes successfully, reconnect to the current server
1008 ====================
1010 void Curl_Curl_f(void)
1014 qboolean pak = false;
1015 qboolean forthismap = false;
1017 const char *name = 0;
1021 Con_Print("libcurl DLL not found, this command is inactive.\n");
1025 if(!cl_curl_enabled.integer)
1027 Con_Print("curl support not enabled. Set cl_curl_enabled to 1 to enable.\n");
1031 for(i = 0; i != Cmd_Argc(); ++i)
1032 Con_DPrintf("%s ", Cmd_Argv(i));
1037 Con_Print("usage:\ncurl --info, curl --cancel [filename], curl url\n");
1041 url = Cmd_Argv(Cmd_Argc() - 1);
1044 for(i = 1; i != end; ++i)
1046 const char *a = Cmd_Argv(i);
1047 if(!strcmp(a, "--info"))
1052 else if(!strcmp(a, "--cancel"))
1054 if(i == end - 1) // last argument
1058 downloadinfo *di = Curl_Find(url);
1060 Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
1062 Con_Print("download not found\n");
1066 else if(!strcmp(a, "--pak"))
1070 else if(!strcmp(a, "--for"))
1072 for(i = i + 1; i != end - 1; ++i)
1074 if(!FS_FileExists(Cmd_Argv(i)))
1075 goto needthefile; // why can't I have a "double break"?
1077 // if we get here, we have all the files...
1080 else if(!strcmp(a, "--forthismap"))
1084 else if(!strcmp(a, "--as"))
1092 else if(!strcmp(a, "--clear_autodownload"))
1094 // mark all running downloads as "not for this map", so if they
1095 // fail, it does not matter
1096 Curl_Clear_forthismap();
1099 else if(!strcmp(a, "--finish_autodownload"))
1101 if(numdownloads_added)
1103 char donecommand[256];
1106 if(cl.loadbegun) // curling won't inhibit loading the map any more when at this stage, so bail out and force a reconnect
1108 dpsnprintf(donecommand, sizeof(donecommand), "connect %s", cls.netcon->address);
1109 Curl_CommandWhenDone(donecommand);
1113 Curl_CheckCommandWhenDone();
1116 Curl_Register_predownload();
1123 Con_Printf("invalid option %s\n", a);
1129 Curl_Begin_ToFile(url, name, pak, forthismap);
1133 static void curl_curlcat_callback(int code, size_t length_received, unsigned char *buffer, void *cbdata)
1135 Con_Printf("Received %d bytes (status %d):\n%.*s\n", (int) length_received, code, (int) length_received, buffer);
1139 void Curl_CurlCat_f(void)
1142 const char *url = Cmd_Argv(1);
1143 buf = Z_Malloc(16384);
1144 Curl_Begin_ToMemory(url, buf, 16384, curl_curlcat_callback, NULL);
1149 ====================
1152 loads the commands and cvars this library uses
1153 ====================
1155 void Curl_Init_Commands(void)
1157 Cvar_RegisterVariable (&cl_curl_enabled);
1158 Cvar_RegisterVariable (&cl_curl_maxdownloads);
1159 Cvar_RegisterVariable (&cl_curl_maxspeed);
1160 Cvar_RegisterVariable (&sv_curl_defaulturl);
1161 Cvar_RegisterVariable (&sv_curl_serverpackages);
1162 Cmd_AddCommand ("curl", Curl_Curl_f, "download data from an URL and add to search path");
1163 //Cmd_AddCommand ("curlcat", Curl_CurlCat_f, "display data from an URL (debugging command)");
1167 ====================
1168 Curl_GetDownloadInfo
1170 returns an array of Curl_downloadinfo_t structs for usage by GUIs.
1171 The number of elements in the array is returned in int *nDownloads.
1172 const char **additional_info may be set to a string of additional user
1173 information, or to NULL if no such display shall occur. The returned
1174 array must be freed later using Z_Free.
1175 ====================
1177 Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **additional_info)
1181 Curl_downloadinfo_t *downinfo;
1182 static char addinfo[128];
1188 *additional_info = NULL;
1193 for(di = downloads; di; di = di->next)
1196 downinfo = (Curl_downloadinfo_t *) Z_Malloc(sizeof(*downinfo) * n);
1198 for(di = downloads; di; di = di->next)
1200 strlcpy(downinfo[i].filename, di->filename, sizeof(downinfo[i].filename));
1203 downinfo[i].progress = Curl_GetDownloadAmount(di);
1204 downinfo[i].speed = Curl_GetDownloadSpeed(di);
1205 downinfo[i].queued = false;
1209 downinfo[i].queued = true;
1216 // TODO: can I clear command_when_done as soon as the first download fails?
1217 if(*command_when_done && !numdownloads_fail && numdownloads_added)
1219 if(!strncmp(command_when_done, "connect ", 8))
1220 dpsnprintf(addinfo, sizeof(addinfo), "(will join %s when done)", command_when_done + 8);
1221 else if(!strcmp(command_when_done, "cl_begindownloads"))
1222 dpsnprintf(addinfo, sizeof(addinfo), "(will enter the game when done)");
1224 dpsnprintf(addinfo, sizeof(addinfo), "(will do '%s' when done)", command_when_done);
1225 *additional_info = addinfo;
1228 *additional_info = NULL;
1237 ====================
1240 finds the URL where to find a given package.
1242 For this, it reads a file "curl_urls.txt" of the following format:
1245 revdm*.pk3 http://revdm/downloads/are/here/
1246 * http://any/other/stuff/is/here/
1248 The URLs should end in /. If not, downloads will still work, but the cached files
1249 can't be just put into the data directory with the same download configuration
1250 (you might want to do this if you want to tag downloaded files from your
1251 server, but you should not). "-" means "don't download".
1253 If no single pattern matched, the cvar sv_curl_defaulturl is used as download
1256 Note: pak1.pak and data*.pk3 are excluded from autodownload at another point in
1257 this file for obvious reasons.
1258 ====================
1260 static const char *Curl_FindPackURL(const char *filename)
1262 static char foundurl[256];
1263 fs_offset_t filesize;
1264 char *buf = (char *) FS_LoadFile("curl_urls.txt", tempmempool, true, &filesize);
1267 // read lines of format "pattern url"
1269 char *pattern = NULL, *patternend = NULL, *url = NULL, *urlend = NULL;
1270 qboolean eof = false;
1282 if(pattern && url && patternend)
1288 if(matchpattern(filename, pattern, true))
1290 strlcpy(foundurl, url, sizeof(foundurl));
1302 if(pattern && !patternend)
1304 else if(url && !urlend)
1310 else if(pattern && patternend && !url)
1319 return sv_curl_defaulturl.string;
1322 typedef struct requirement_s
1324 struct requirement_s *next;
1325 char filename[MAX_QPATH];
1328 static requirement *requirements = NULL;
1332 ====================
1335 Adds the given file to the list of requirements.
1336 ====================
1338 void Curl_RequireFile(const char *filename)
1340 requirement *req = (requirement *) Z_Malloc(sizeof(*requirements));
1341 req->next = requirements;
1342 strlcpy(req->filename, filename, sizeof(req->filename));
1347 ====================
1348 Curl_ClearRequirements
1350 Clears the list of required files for playing on the current map.
1351 This should be called at every map change.
1352 ====================
1354 void Curl_ClearRequirements()
1359 requirement *req = requirements;
1360 requirements = requirements->next;
1363 p = sv_curl_serverpackages.string;
1364 Con_DPrintf("Require all of: %s\n", p);
1365 while(COM_ParseToken_Simple(&p, false, false))
1367 Con_DPrintf("Require: %s\n", com_token);
1368 Curl_RequireFile(com_token);
1373 ====================
1374 Curl_SendRequirements
1376 Makes the current host_clients download all files he needs.
1377 This is done by sending him the following console commands:
1379 curl --clear_autodownload
1380 curl --pak --for maps/pushmoddm1.bsp --forthismap http://where/this/darn/map/is/pushmoddm1.pk3
1381 curl --finish_autodownload
1382 ====================
1384 void Curl_SendRequirements()
1386 // for each requirement, find the pack name
1387 char sendbuffer[4096] = "";
1389 qboolean foundone = false;
1391 for(req = requirements; req; req = req->next)
1394 const char *thispack = FS_WhichPack(req->filename);
1395 const char *packurl;
1400 p = strrchr(thispack, '/');
1404 packurl = Curl_FindPackURL(thispack);
1406 if(packurl && *packurl && strcmp(packurl, "-"))
1409 strlcat(sendbuffer, "curl --clear_autodownload\n", sizeof(sendbuffer));
1411 strlcat(sendbuffer, "curl --pak --forthismap --as ", sizeof(sendbuffer));
1412 strlcat(sendbuffer, thispack, sizeof(sendbuffer));
1413 strlcat(sendbuffer, " --for ", sizeof(sendbuffer));
1414 strlcat(sendbuffer, req->filename, sizeof(sendbuffer));
1415 strlcat(sendbuffer, " ", sizeof(sendbuffer));
1416 strlcat(sendbuffer, packurl, sizeof(sendbuffer));
1417 strlcat(sendbuffer, thispack, sizeof(sendbuffer));
1418 strlcat(sendbuffer, "\n", sizeof(sendbuffer));
1425 strlcat(sendbuffer, "curl --finish_autodownload\n", sizeof(sendbuffer));
1427 if(strlen(sendbuffer) + 1 < sizeof(sendbuffer))
1428 Host_ClientCommands("%s", sendbuffer);
1430 Con_Printf("Could not initiate autodownload due to URL buffer overflow\n");