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","0", "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;
185 static downloadinfo *downloads = NULL;
186 static int numdownloads = 0;
188 static qboolean noclear = FALSE;
190 static int numdownloads_fail = 0;
191 static int numdownloads_success = 0;
192 static int numdownloads_added = 0;
193 static char command_when_done[256] = "";
194 static char command_when_error[256] = "";
200 Sets the command which is to be executed when the last download completes AND
201 all downloads since last server connect ended with a successful status.
202 Setting the command to NULL clears it.
205 void Curl_CommandWhenDone(const char *cmd)
210 strlcpy(command_when_done, cmd, sizeof(command_when_done));
212 *command_when_done = 0;
217 Do not use yet. Not complete.
218 Problem: what counts as an error?
221 void Curl_CommandWhenError(const char *cmd)
226 strlcpy(command_when_error, cmd, sizeof(command_when_error));
228 *command_when_error = 0;
233 Curl_Clear_forthismap
235 Clears the "will disconnect on failure" flags.
238 void Curl_Clear_forthismap()
243 for(di = downloads; di; di = di->next)
244 di->forthismap = false;
245 Curl_CommandWhenError(NULL);
246 Curl_CommandWhenDone(NULL);
247 numdownloads_fail = 0;
248 numdownloads_success = 0;
249 numdownloads_added = 0;
256 Returns true if a download needed for the current game is running.
259 qboolean Curl_Have_forthismap()
261 return numdownloads_added;
264 void Curl_Register_predownload()
266 Curl_CommandWhenDone("cl_begindownloads");
267 Curl_CommandWhenError("cl_begindownloads");
272 Curl_CheckCommandWhenDone
274 Checks if a "done command" is to be executed.
275 All downloads finished, at least one success since connect, no single failure
276 -> execute the command.
278 static void Curl_CheckCommandWhenDone()
282 if(numdownloads_added && (numdownloads_success == numdownloads_added) && *command_when_done)
284 Con_DPrintf("cURL downloads occurred, executing %s\n", command_when_done);
286 Cbuf_AddText(command_when_done);
288 Curl_Clear_forthismap();
290 else if(numdownloads_added && numdownloads_fail && *command_when_error)
292 Con_DPrintf("cURL downloads FAILED, executing %s\n", command_when_error);
294 Cbuf_AddText(command_when_error);
296 Curl_Clear_forthismap();
307 static qboolean CURL_OpenLibrary (void)
309 const char* dllnames [] =
315 #elif defined(MACOSX)
316 "libcurl.3.dylib", // Mac OS X Tiger
317 "libcurl.2.dylib", // Mac OS X Panther
329 if (! Sys_LoadLibrary (dllnames, &curl_dll, curlfuncs))
331 Con_Printf ("cURL support disabled\n");
335 Con_Printf ("cURL support enabled\n");
347 static void CURL_CloseLibrary (void)
349 Sys_UnloadLibrary (&curl_dll);
353 static CURLM *curlm = NULL;
354 static unsigned long bytes_received = 0; // used for bandwidth throttling
355 static double curltime = 0;
361 fwrite-compatible function that writes the data to a file. libcurl can call
365 static size_t CURL_fwrite(void *data, size_t size, size_t nmemb, void *vdi)
368 size_t bytes = size * nmemb;
369 downloadinfo *di = (downloadinfo *) vdi;
371 bytes_received += bytes;
372 di->bytes_received += bytes;
374 ret = FS_Write(di->stream, data, bytes);
376 return ret; // why not ret / nmemb?
381 CURL_DOWNLOAD_SUCCESS = 0,
382 CURL_DOWNLOAD_FAILED,
383 CURL_DOWNLOAD_ABORTED,
384 CURL_DOWNLOAD_SERVERERROR
392 stops a download. It receives a status (CURL_DOWNLOAD_SUCCESS,
393 CURL_DOWNLOAD_FAILED or CURL_DOWNLOAD_ABORTED) and in the second case the error
394 code from libcurl, or 0, if another error has occurred.
397 static void Curl_EndDownload(downloadinfo *di, CurlStatus status, CURLcode error)
404 case CURL_DOWNLOAD_SUCCESS:
405 Con_Printf("Download of %s: OK\n", di->filename);
408 case CURL_DOWNLOAD_FAILED:
409 Con_Printf("Download of %s: FAILED\n", di->filename);
411 Con_Printf("Reason given by libcurl: %s\n", qcurl_easy_strerror(error));
413 case CURL_DOWNLOAD_ABORTED:
414 Con_Printf("Download of %s: ABORTED\n", di->filename);
416 case CURL_DOWNLOAD_SERVERERROR:
417 Con_Printf("Download of %s: %d\n", di->filename, (int) error);
419 // reopen to enforce it to have zero bytes again
420 FS_Close(di->stream);
421 di->stream = FS_Open(di->filename, "w", false, false);
428 qcurl_multi_remove_handle(curlm, di->curle);
429 qcurl_easy_cleanup(di->curle);
432 if(ok && !di->bytes_received)
434 Con_Printf("ERROR: empty file\n");
439 FS_Close(di->stream);
442 ok = FS_AddPack(di->filename, NULL, true);
445 di->prev->next = di->next;
447 downloads = di->next;
449 di->next->prev = di->prev;
455 ++numdownloads_success;
464 CheckPendingDownloads
466 checks if there are free download slots to start new downloads in.
467 To not start too many downloads at once, only one download is added at a time,
468 up to a maximum number of cl_curl_maxdownloads are running.
471 static void CheckPendingDownloads()
475 if(numdownloads < cl_curl_maxdownloads.integer)
478 for(di = downloads; di; di = di->next)
482 Con_Printf("Downloading %s -> %s", di->url, di->filename);
484 di->stream = FS_Open(di->filename, "ab", false, false);
487 Con_Printf("\nFAILED: Could not open output file %s\n", di->filename);
488 Curl_EndDownload(di, CURL_DOWNLOAD_FAILED, CURLE_OK);
492 FS_Seek(di->stream, 0, SEEK_END);
493 di->startpos = FS_Tell(di->stream);
495 Con_Printf(", resuming from position %ld", (long) di->startpos);
498 di->curle = qcurl_easy_init();
499 qcurl_easy_setopt(di->curle, CURLOPT_URL, di->url);
500 qcurl_easy_setopt(di->curle, CURLOPT_USERAGENT, engineversion);
501 qcurl_easy_setopt(di->curle, CURLOPT_REFERER, di->referer);
502 qcurl_easy_setopt(di->curle, CURLOPT_RESUME_FROM, (long) di->startpos);
503 qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 1);
504 qcurl_easy_setopt(di->curle, CURLOPT_WRITEFUNCTION, CURL_fwrite);
505 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_LIMIT, (long) 256);
506 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_TIME, (long) 45);
507 qcurl_easy_setopt(di->curle, CURLOPT_WRITEDATA, (void *) di);
508 qcurl_easy_setopt(di->curle, CURLOPT_PRIVATE, (void *) di);
509 qcurl_multi_add_handle(curlm, di->curle);
512 if(numdownloads >= cl_curl_maxdownloads.integer)
523 this function MUST be called before using anything else in this file.
524 On Win32, this must be called AFTER WSAStartup has been done!
532 qcurl_global_init(CURL_GLOBAL_NOTHING);
533 curlm = qcurl_multi_init();
540 Surprise... closes all the stuff. Please do this BEFORE shutting down LHNET.
543 void Curl_ClearRequirements();
548 Curl_ClearRequirements();
558 Finds the internal information block for a download given by file name.
561 static downloadinfo *Curl_Find(const char *filename)
566 for(di = downloads; di; di = di->next)
567 if(!strcasecmp(di->filename, filename))
576 Starts a download of a given URL to the file name portion of this URL (or name
577 if given) in the "dlcache/" folder.
580 void Curl_Begin(const char *URL, const char *name, qboolean ispak, qboolean forthismap)
591 // Note: This extraction of the file name portion is NOT entirely correct.
593 // It does the following:
595 // http://host/some/script.cgi/SomeFile.pk3?uid=ABCDE -> SomeFile.pk3
596 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3 -> SomeFile.pk3
597 // http://host/some/script.php?uid=ABCDE&file=SomeFile.pk3 -> script.php
599 // However, I'd like to keep this "buggy" behavior so that PHP script
600 // authors can write download scripts without having to enable
601 // AcceptPathInfo on Apache. They just have to ensure that their script
602 // can be called with such a "fake" path name like
603 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3
605 // By the way, such PHP scripts should either send the file or a
606 // "Location:" redirect; PHP code example:
608 // header("Location: http://www.example.com/");
610 // By the way, this will set User-Agent to something like
611 // "Nexuiz build 22:27:55 Mar 17 2006" (engineversion) and Referer to
612 // dp://serverhost:serverport/ so you can filter on this; an example
613 // httpd log file line might be:
615 // 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"
619 p = strrchr(name, '/');
620 p = p ? (p+1) : name;
622 length = q ? (size_t)(q - p) : strlen(p);
623 dpsnprintf(fn, sizeof(fn), "dlcache/%.*s", (int)length, p);
626 // already downloading the file?
628 downloadinfo *di = Curl_Find(fn);
631 Con_Printf("Can't download %s, already getting it from %s!\n", fn, di->url);
633 // however, if it was not for this map yet...
634 if(forthismap && !di->forthismap)
636 di->forthismap = true;
637 // this "fakes" a download attempt so the client will wait for
638 // the download to finish and then reconnect
639 ++numdownloads_added;
646 if(ispak && FS_FileExists(fn))
648 qboolean already_loaded;
649 if(FS_AddPack(fn, &already_loaded, true))
651 Con_DPrintf("%s already exists, not downloading!\n", fn);
653 Con_DPrintf("(pak was already loaded)\n");
658 ++numdownloads_added;
659 ++numdownloads_success;
666 qfile_t *f = FS_Open(fn, "rb", false, false);
670 FS_Read(f, buf, sizeof(buf)); // no "-1", I will use memcmp
672 if(memcmp(buf, "PK\x03\x04", 4) && memcmp(buf, "PACK", 4))
674 Con_DPrintf("Detected non-PAK %s, clearing and NOT resuming.\n", fn);
676 f = FS_Open(fn, "w", false, false);
690 ++numdownloads_added;
691 di = (downloadinfo *) Z_Malloc(sizeof(*di));
692 strlcpy(di->filename, fn, sizeof(di->filename));
693 strlcpy(di->url, URL, sizeof(di->url));
694 dpsnprintf(di->referer, sizeof(di->referer), "dp://%s/", cls.netcon ? cls.netcon->address : "notconnected.invalid");
695 di->forthismap = forthismap;
701 di->bytes_received = 0;
702 di->next = downloads;
715 call this regularily as this will always download as much as possible without
723 if(!cl_curl_enabled.integer)
729 Curl_CheckCommandWhenDone();
734 if(realtime < curltime) // throttle
743 mc = qcurl_multi_perform(curlm, &remaining);
745 while(mc == CURLM_CALL_MULTI_PERFORM);
749 CURLMsg *msg = qcurl_multi_info_read(curlm, &remaining);
752 if(msg->msg == CURLMSG_DONE)
755 CurlStatus failed = CURL_DOWNLOAD_SUCCESS;
757 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &di);
758 result = msg->data.result;
761 failed = CURL_DOWNLOAD_FAILED;
766 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code);
771 failed = CURL_DOWNLOAD_SERVERERROR;
777 Curl_EndDownload(di, failed, result);
782 CheckPendingDownloads();
784 // when will we curl the next time?
785 // we will wait a bit to ensure our download rate is kept.
786 // we now know that realtime >= curltime... so set up a new curltime
787 if(cl_curl_maxspeed.value > 0)
789 unsigned long bytes = bytes_received; // maybe smoothen a bit?
790 curltime = realtime + bytes / (cl_curl_maxspeed.value * 1024.0);
791 bytes_received -= bytes;
804 void Curl_CancelAll()
811 Curl_EndDownload(downloads, CURL_DOWNLOAD_ABORTED, CURLE_OK);
812 // INVARIANT: downloads will point to the next download after that!
820 returns true iff there is a download running.
823 qboolean Curl_Running()
828 return downloads != NULL;
833 Curl_GetDownloadAmount
835 returns a value from 0.0 to 1.0 which represents the downloaded amount of data
836 for the given download.
839 static double Curl_GetDownloadAmount(downloadinfo *di)
846 qcurl_easy_getinfo(di->curle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
848 return di->bytes_received / length;
858 Curl_GetDownloadSpeed
860 returns the speed of the given download in bytes per second
863 static double Curl_GetDownloadSpeed(downloadinfo *di)
870 qcurl_easy_getinfo(di->curle, CURLINFO_SPEED_DOWNLOAD, &speed);
881 prints the download list
884 // TODO rewrite using Curl_GetDownloadInfo?
885 static void Curl_Info_f()
892 Con_Print("Currently running downloads:\n");
893 for(di = downloads; di; di = di->next)
895 double speed, percent;
896 Con_Printf(" %s -> %s ", di->url, di->filename);
897 percent = 100.0 * Curl_GetDownloadAmount(di);
898 speed = Curl_GetDownloadSpeed(di);
900 Con_Printf("(%.1f%% @ %.1f KiB/s)\n", percent, speed / 1024.0);
902 Con_Print("(queued)\n");
907 Con_Print("No downloads running.\n");
915 implements the "curl" console command
919 curl --cancel filename
924 curl [--pak] [--forthismap] [--for filename filename...] url
925 --pak: after downloading, load the package into the virtual file system
926 --for filename...: only download of at least one of the named files is missing
927 --forthismap: don't reconnect on failure
929 curl --clear_autodownload
930 clears the download success/failure counters
932 curl --finish_autodownload
933 if at least one download has been started, disconnect and drop to the menu
934 once the last download completes successfully, reconnect to the current server
937 void Curl_Curl_f(void)
941 qboolean pak = false;
942 qboolean forthismap = false;
944 const char *name = 0;
948 Con_Print("libcurl DLL not found, this command is inactive.\n");
952 if(!cl_curl_enabled.integer)
954 Con_Print("curl support not enabled. Set cl_curl_enabled to 1 to enable.\n");
958 for(i = 0; i != Cmd_Argc(); ++i)
959 Con_DPrintf("%s ", Cmd_Argv(i));
964 Con_Print("usage:\ncurl --info, curl --cancel [filename], curl url\n");
968 url = Cmd_Argv(Cmd_Argc() - 1);
971 for(i = 1; i != end; ++i)
973 const char *a = Cmd_Argv(i);
974 if(!strcmp(a, "--info"))
979 else if(!strcmp(a, "--cancel"))
981 if(i == end - 1) // last argument
985 downloadinfo *di = Curl_Find(url);
987 Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
989 Con_Print("download not found\n");
993 else if(!strcmp(a, "--pak"))
997 else if(!strcmp(a, "--for"))
999 for(i = i + 1; i != end - 1; ++i)
1001 if(!FS_FileExists(Cmd_Argv(i)))
1002 goto needthefile; // why can't I have a "double break"?
1004 // if we get here, we have all the files...
1007 else if(!strcmp(a, "--forthismap"))
1011 else if(!strcmp(a, "--as"))
1019 else if(!strcmp(a, "--clear_autodownload"))
1021 // mark all running downloads as "not for this map", so if they
1022 // fail, it does not matter
1023 Curl_Clear_forthismap();
1026 else if(!strcmp(a, "--finish_autodownload"))
1028 if(numdownloads_added)
1030 char donecommand[256];
1033 if(cl.loadbegun) // curling won't inhibit loading the map any more when at this stage, so bail out and force a reconnect
1035 dpsnprintf(donecommand, sizeof(donecommand), "connect %s", cls.netcon->address);
1036 Curl_CommandWhenDone(donecommand);
1040 Curl_CheckCommandWhenDone();
1043 Curl_Register_predownload();
1050 Con_Printf("invalid option %s\n", a);
1056 Curl_Begin(url, name, pak, forthismap);
1060 ====================
1063 loads the commands and cvars this library uses
1064 ====================
1066 void Curl_Init_Commands(void)
1068 Cvar_RegisterVariable (&cl_curl_enabled);
1069 Cvar_RegisterVariable (&cl_curl_maxdownloads);
1070 Cvar_RegisterVariable (&cl_curl_maxspeed);
1071 Cvar_RegisterVariable (&sv_curl_defaulturl);
1072 Cvar_RegisterVariable (&sv_curl_serverpackages);
1073 Cmd_AddCommand ("curl", Curl_Curl_f, "download data from an URL and add to search path");
1077 ====================
1078 Curl_GetDownloadInfo
1080 returns an array of Curl_downloadinfo_t structs for usage by GUIs.
1081 The number of elements in the array is returned in int *nDownloads.
1082 const char **additional_info may be set to a string of additional user
1083 information, or to NULL if no such display shall occur. The returned
1084 array must be freed later using Z_Free.
1085 ====================
1087 Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **additional_info)
1091 Curl_downloadinfo_t *downinfo;
1092 static char addinfo[128];
1098 *additional_info = NULL;
1103 for(di = downloads; di; di = di->next)
1106 downinfo = (Curl_downloadinfo_t *) Z_Malloc(sizeof(*downinfo) * n);
1108 for(di = downloads; di; di = di->next)
1110 strlcpy(downinfo[i].filename, di->filename, sizeof(downinfo[i].filename));
1113 downinfo[i].progress = Curl_GetDownloadAmount(di);
1114 downinfo[i].speed = Curl_GetDownloadSpeed(di);
1115 downinfo[i].queued = false;
1119 downinfo[i].queued = true;
1126 // TODO: can I clear command_when_done as soon as the first download fails?
1127 if(*command_when_done && !numdownloads_fail && numdownloads_added)
1129 if(!strncmp(command_when_done, "connect ", 8))
1130 dpsnprintf(addinfo, sizeof(addinfo), "(will join %s when done)", command_when_done + 8);
1131 else if(!strcmp(command_when_done, "cl_begindownloads"))
1132 dpsnprintf(addinfo, sizeof(addinfo), "(will enter the game when done)");
1134 dpsnprintf(addinfo, sizeof(addinfo), "(will do '%s' when done)", command_when_done);
1135 *additional_info = addinfo;
1138 *additional_info = NULL;
1147 ====================
1150 finds the URL where to find a given package.
1152 For this, it reads a file "curl_urls.txt" of the following format:
1155 revdm*.pk3 http://revdm/downloads/are/here/
1156 * http://any/other/stuff/is/here/
1158 The URLs should end in /. If not, downloads will still work, but the cached files
1159 can't be just put into the data directory with the same download configuration
1160 (you might want to do this if you want to tag downloaded files from your
1161 server, but you should not). "-" means "don't download".
1163 If no single pattern matched, the cvar sv_curl_defaulturl is used as download
1166 Note: pak1.pak and data*.pk3 are excluded from autodownload at another point in
1167 this file for obvious reasons.
1168 ====================
1170 static const char *Curl_FindPackURL(const char *filename)
1172 static char foundurl[256];
1173 fs_offset_t filesize;
1174 char *buf = (char *) FS_LoadFile("curl_urls.txt", tempmempool, true, &filesize);
1177 // read lines of format "pattern url"
1179 char *pattern = NULL, *patternend = NULL, *url = NULL, *urlend = NULL;
1180 qboolean eof = false;
1192 if(pattern && url && patternend)
1198 if(matchpattern(filename, pattern, true))
1200 strlcpy(foundurl, url, sizeof(foundurl));
1212 if(pattern && !patternend)
1214 else if(url && !urlend)
1220 else if(pattern && patternend && !url)
1229 return sv_curl_defaulturl.string;
1232 typedef struct requirement_s
1234 struct requirement_s *next;
1235 char filename[MAX_QPATH];
1238 static requirement *requirements = NULL;
1242 ====================
1245 Adds the given file to the list of requirements.
1246 ====================
1248 void Curl_RequireFile(const char *filename)
1250 requirement *req = (requirement *) Z_Malloc(sizeof(*requirements));
1251 req->next = requirements;
1252 strlcpy(req->filename, filename, sizeof(req->filename));
1257 ====================
1258 Curl_ClearRequirements
1260 Clears the list of required files for playing on the current map.
1261 This should be called at every map change.
1262 ====================
1264 void Curl_ClearRequirements()
1269 requirement *req = requirements;
1270 requirements = requirements->next;
1273 p = sv_curl_serverpackages.string;
1274 Con_DPrintf("Require all of: %s\n", p);
1275 while(COM_ParseTokenConsole(&p))
1277 Con_DPrintf("Require: %s\n", com_token);
1278 Curl_RequireFile(com_token);
1283 ====================
1284 Curl_SendRequirements
1286 Makes the current host_clients download all files he needs.
1287 This is done by sending him the following console commands:
1289 curl --clear_autodownload
1290 curl --pak --for maps/pushmoddm1.bsp --forthismap http://where/this/darn/map/is/pushmoddm1.pk3
1291 curl --finish_autodownload
1292 ====================
1294 void Curl_SendRequirements()
1296 // for each requirement, find the pack name
1297 char sendbuffer[4096] = "";
1299 qboolean foundone = false;
1301 for(req = requirements; req; req = req->next)
1304 const char *thispack = FS_WhichPack(req->filename);
1305 const char *packurl;
1310 p = strrchr(thispack, '/');
1314 packurl = Curl_FindPackURL(thispack);
1316 if(packurl && *packurl && strcmp(packurl, "-"))
1319 strlcat(sendbuffer, "curl --clear_autodownload\n", sizeof(sendbuffer));
1321 strlcat(sendbuffer, "curl --pak --forthismap --as ", sizeof(sendbuffer));
1322 strlcat(sendbuffer, thispack, sizeof(sendbuffer));
1323 strlcat(sendbuffer, " --for ", sizeof(sendbuffer));
1324 strlcat(sendbuffer, req->filename, sizeof(sendbuffer));
1325 strlcat(sendbuffer, " ", sizeof(sendbuffer));
1326 strlcat(sendbuffer, packurl, sizeof(sendbuffer));
1327 strlcat(sendbuffer, thispack, sizeof(sendbuffer));
1328 strlcat(sendbuffer, "\n", sizeof(sendbuffer));
1335 strlcat(sendbuffer, "curl --finish_autodownload\n", sizeof(sendbuffer));
1337 if(strlen(sendbuffer) + 1 < sizeof(sendbuffer))
1338 Host_ClientCommands("%s", sendbuffer);
1340 Con_Printf("Could not initiate autodownload due to URL buffer overflow\n");