5 static cvar_t cl_curl_maxdownloads = {1, "cl_curl_maxdownloads","1", "maximum number of concurrent HTTP/FTP downloads"};
6 static cvar_t cl_curl_maxspeed = {1, "cl_curl_maxspeed","100", "maximum download speed (KiB/s)"};
7 static cvar_t sv_curl_defaulturl = {1, "sv_curl_defaulturl","", "default autodownload source URL"};
8 static cvar_t sv_curl_serverpackages = {1, "sv_curl_serverpackages","", "list of required files for the clients, separated by spaces"};
9 static cvar_t cl_curl_enabled = {1, "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 int numdownloads_fail = 0;
189 static int numdownloads_success = 0;
190 static int numdownloads_added = 0;
191 static char command_when_done[256] = "";
192 static char command_when_error[256] = "";
198 Sets the command which is to be executed when the last download completes AND
199 all downloads since last server connect ended with a successful status.
200 Setting the command to NULL clears it.
203 void Curl_CommandWhenDone(const char *cmd)
208 strlcpy(command_when_done, cmd, sizeof(command_when_done));
210 *command_when_done = 0;
215 Do not use yet. Not complete.
216 Problem: what counts as an error?
219 void Curl_CommandWhenError(const char *cmd)
224 strlcpy(command_when_error, cmd, sizeof(command_when_error));
226 *command_when_error = 0;
231 Curl_Clear_forthismap
233 Clears the "will disconnect on failure" flags.
236 void Curl_Clear_forthismap()
239 for(di = downloads; di; di = di->next)
240 di->forthismap = false;
241 Curl_CommandWhenError(NULL);
242 Curl_CommandWhenDone(NULL);
243 numdownloads_fail = 0;
244 numdownloads_success = 0;
245 numdownloads_added = 0;
248 /* obsolete: numdownloads_added contains the same
249 static qboolean Curl_Have_forthismap()
252 for(di = downloads; di; di = di->next)
261 Curl_CheckCommandWhenDone
263 Checks if a "done command" is to be executed.
264 All downloads finished, at least one success since connect, no single failure
265 -> execute the command.
267 static void Curl_CheckCommandWhenDone()
271 if(numdownloads_added && (numdownloads_success == numdownloads_added) && *command_when_done)
273 Con_DPrintf("Map downloads occurred, executing %s\n", command_when_done);
275 Cbuf_AddText(command_when_done);
277 Curl_Clear_forthismap();
279 else if(numdownloads_added && numdownloads_fail && *command_when_error)
281 Con_DPrintf("Map downloads FAILED, executing %s\n", command_when_error);
283 Cbuf_AddText(command_when_error);
285 Curl_Clear_forthismap();
296 static qboolean CURL_OpenLibrary (void)
298 const char* dllnames [] =
304 #elif defined(MACOSX)
305 "libcurl.3.dylib", // Mac OS X Tiger
306 "libcurl.2.dylib", // Mac OS X Panther
318 if (! Sys_LoadLibrary (dllnames, &curl_dll, curlfuncs))
320 Con_Printf ("cURL support disabled\n");
324 Con_Printf ("cURL support enabled\n");
336 static void CURL_CloseLibrary (void)
338 Sys_UnloadLibrary (&curl_dll);
342 static CURLM *curlm = NULL;
343 static unsigned long bytes_received = 0; // used for bandwidth throttling
344 static double curltime = 0;
350 fwrite-compatible function that writes the data to a file. libcurl can call
354 static size_t CURL_fwrite(void *data, size_t size, size_t nmemb, void *vdi)
357 size_t bytes = size * nmemb;
358 downloadinfo *di = (downloadinfo *) vdi;
360 bytes_received += bytes;
361 di->bytes_received += bytes;
363 ret = FS_Write(di->stream, data, bytes);
365 return ret; // why not ret / nmemb?
370 CURL_DOWNLOAD_SUCCESS = 0,
371 CURL_DOWNLOAD_FAILED,
372 CURL_DOWNLOAD_ABORTED,
373 CURL_DOWNLOAD_SERVERERROR
381 stops a download. It receives a status (CURL_DOWNLOAD_SUCCESS,
382 CURL_DOWNLOAD_FAILED or CURL_DOWNLOAD_ABORTED) and in the second case the error
383 code from libcurl, or 0, if another error has occurred.
386 static void Curl_EndDownload(downloadinfo *di, CurlStatus status, CURLcode error)
393 case CURL_DOWNLOAD_SUCCESS:
394 Con_Printf("Download of %s: OK\n", di->filename);
397 case CURL_DOWNLOAD_FAILED:
398 Con_Printf("Download of %s: FAILED\n", di->filename);
400 Con_Printf("Reason given by libcurl: %s\n", qcurl_easy_strerror(error));
402 case CURL_DOWNLOAD_ABORTED:
403 Con_Printf("Download of %s: ABORTED\n", di->filename);
405 case CURL_DOWNLOAD_SERVERERROR:
406 Con_Printf("Download of %s: %d\n", di->filename, (int) error);
408 // reopen to enforce it to have zero bytes again
409 FS_Close(di->stream);
410 di->stream = FS_Open(di->filename, "w", false, false);
417 qcurl_multi_remove_handle(curlm, di->curle);
418 qcurl_easy_cleanup(di->curle);
421 if(ok && !di->bytes_received)
423 Con_Printf("ERROR: empty file\n");
428 FS_Close(di->stream);
431 ok = FS_AddPack(di->filename, NULL, true);
434 di->prev->next = di->next;
436 downloads = di->next;
438 di->next->prev = di->prev;
444 ++numdownloads_success;
450 Curl_CheckCommandWhenDone();
455 CheckPendingDownloads
457 checks if there are free download slots to start new downloads in.
458 To not start too many downloads at once, only one download is added at a time,
459 up to a maximum number of cl_curl_maxdownloads are running.
462 static void CheckPendingDownloads()
466 if(numdownloads < cl_curl_maxdownloads.integer)
469 for(di = downloads; di; di = di->next)
473 Con_Printf("Downloading %s -> %s", di->url, di->filename);
475 di->stream = FS_Open(di->filename, "ab", false, false);
478 Con_Printf("\nFAILED: Could not open output file %s\n", di->filename);
479 Curl_EndDownload(di, CURL_DOWNLOAD_FAILED, CURLE_OK);
483 FS_Seek(di->stream, 0, SEEK_END);
484 di->startpos = FS_Tell(di->stream);
486 Con_Printf(", resuming from position %ld", (long) di->startpos);
489 di->curle = qcurl_easy_init();
490 qcurl_easy_setopt(di->curle, CURLOPT_URL, di->url);
491 qcurl_easy_setopt(di->curle, CURLOPT_USERAGENT, engineversion);
492 qcurl_easy_setopt(di->curle, CURLOPT_REFERER, di->referer);
493 qcurl_easy_setopt(di->curle, CURLOPT_RESUME_FROM, (long) di->startpos);
494 qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 1);
495 qcurl_easy_setopt(di->curle, CURLOPT_WRITEFUNCTION, CURL_fwrite);
496 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_LIMIT, (long) 256);
497 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_TIME, (long) 45);
498 qcurl_easy_setopt(di->curle, CURLOPT_WRITEDATA, (void *) di);
499 qcurl_easy_setopt(di->curle, CURLOPT_PRIVATE, (void *) di);
500 qcurl_multi_add_handle(curlm, di->curle);
503 if(numdownloads >= cl_curl_maxdownloads.integer)
514 this function MUST be called before using anything else in this file.
515 On Win32, this must be called AFTER WSAStartup has been done!
523 qcurl_global_init(CURL_GLOBAL_NOTHING);
524 curlm = qcurl_multi_init();
531 Surprise... closes all the stuff. Please do this BEFORE shutting down LHNET.
534 void Curl_ClearRequirements();
539 Curl_ClearRequirements();
549 Finds the internal information block for a download given by file name.
552 static downloadinfo *Curl_Find(const char *filename)
557 for(di = downloads; di; di = di->next)
558 if(!strcasecmp(di->filename, filename))
567 Starts a download of a given URL to the file name portion of this URL (or name
568 if given) in the "dlcache/" folder.
571 void Curl_Begin(const char *URL, const char *name, qboolean ispak, qboolean forthismap)
582 // Note: This extraction of the file name portion is NOT entirely correct.
584 // It does the following:
586 // http://host/some/script.cgi/SomeFile.pk3?uid=ABCDE -> SomeFile.pk3
587 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3 -> SomeFile.pk3
588 // http://host/some/script.php?uid=ABCDE&file=SomeFile.pk3 -> script.php
590 // However, I'd like to keep this "buggy" behavior so that PHP script
591 // authors can write download scripts without having to enable
592 // AcceptPathInfo on Apache. They just have to ensure that their script
593 // can be called with such a "fake" path name like
594 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3
596 // By the way, such PHP scripts should either send the file or a
597 // "Location:" redirect; PHP code example:
599 // header("Location: http://www.example.com/");
601 // By the way, this will set User-Agent to something like
602 // "Nexuiz build 22:27:55 Mar 17 2006" (engineversion) and Referer to
603 // dp://serverhost:serverport/ so you can filter on this; an example
604 // httpd log file line might be:
606 // 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"
610 p = strrchr(name, '/');
611 p = p ? (p+1) : name;
613 length = q ? (size_t)(q - p) : strlen(p);
614 dpsnprintf(fn, sizeof(fn), "dlcache/%.*s", (int)length, p);
617 // already downloading the file?
619 downloadinfo *di = Curl_Find(fn);
622 Con_Printf("Can't download %s, already getting it from %s!\n", fn, di->url);
624 // however, if it was not for this map yet...
625 if(forthismap && !di->forthismap)
627 di->forthismap = true;
628 // this "fakes" a download attempt so the client will wait for
629 // the download to finish and then reconnect
630 ++numdownloads_added;
637 if(ispak && FS_FileExists(fn))
639 qboolean already_loaded;
640 if(FS_AddPack(fn, &already_loaded, true))
642 Con_DPrintf("%s already exists, not downloading!\n", fn);
644 Con_DPrintf("(pak was already loaded)\n");
649 ++numdownloads_added;
650 ++numdownloads_success;
657 qfile_t *f = FS_Open(fn, "rb", false, false);
661 FS_Read(f, buf, sizeof(buf)); // no "-1", I will use memcmp
663 if(memcmp(buf, "PK\x03\x04", 4) && memcmp(buf, "PACK", 4))
665 Con_DPrintf("Detected non-PAK %s, clearing and NOT resuming.\n", fn);
667 f = FS_Open(fn, "w", false, false);
681 ++numdownloads_added;
682 di = (downloadinfo *) Z_Malloc(sizeof(*di));
683 strlcpy(di->filename, fn, sizeof(di->filename));
684 strlcpy(di->url, URL, sizeof(di->url));
685 dpsnprintf(di->referer, sizeof(di->referer), "dp://%s/", cls.netcon ? cls.netcon->address : "notconnected.invalid");
686 di->forthismap = forthismap;
692 di->bytes_received = 0;
693 di->next = downloads;
706 call this regularily as this will always download as much as possible without
712 if(!cl_curl_enabled.integer)
721 if(realtime < curltime) // throttle
730 mc = qcurl_multi_perform(curlm, &remaining);
732 while(mc == CURLM_CALL_MULTI_PERFORM);
736 CURLMsg *msg = qcurl_multi_info_read(curlm, &remaining);
739 if(msg->msg == CURLMSG_DONE)
742 CurlStatus failed = CURL_DOWNLOAD_SUCCESS;
744 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &di);
745 result = msg->data.result;
748 failed = CURL_DOWNLOAD_FAILED;
753 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code);
758 failed = CURL_DOWNLOAD_SERVERERROR;
764 Curl_EndDownload(di, failed, result);
769 CheckPendingDownloads();
771 // when will we curl the next time?
772 // we will wait a bit to ensure our download rate is kept.
773 // we now know that realtime >= curltime... so set up a new curltime
774 if(cl_curl_maxspeed.value > 0)
776 unsigned long bytes = bytes_received; // maybe smoothen a bit?
777 curltime = realtime + bytes / (cl_curl_maxspeed.value * 1024.0);
778 bytes_received -= bytes;
791 void Curl_CancelAll()
798 Curl_EndDownload(downloads, CURL_DOWNLOAD_ABORTED, CURLE_OK);
799 // INVARIANT: downloads will point to the next download after that!
807 returns true iff there is a download running.
810 qboolean Curl_Running()
815 return downloads != NULL;
820 Curl_GetDownloadAmount
822 returns a value from 0.0 to 1.0 which represents the downloaded amount of data
823 for the given download.
826 static double Curl_GetDownloadAmount(downloadinfo *di)
833 qcurl_easy_getinfo(di->curle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
835 return di->bytes_received / length;
845 Curl_GetDownloadSpeed
847 returns the speed of the given download in bytes per second
850 static double Curl_GetDownloadSpeed(downloadinfo *di)
857 qcurl_easy_getinfo(di->curle, CURLINFO_SPEED_DOWNLOAD, &speed);
868 prints the download list
871 // TODO rewrite using Curl_GetDownloadInfo?
872 static void Curl_Info_f()
879 Con_Print("Currently running downloads:\n");
880 for(di = downloads; di; di = di->next)
882 double speed, percent;
883 Con_Printf(" %s -> %s ", di->url, di->filename);
884 percent = 100.0 * Curl_GetDownloadAmount(di);
885 speed = Curl_GetDownloadSpeed(di);
887 Con_Printf("(%.1f%% @ %.1f KiB/s)\n", percent, speed / 1024.0);
889 Con_Print("(queued)\n");
894 Con_Print("No downloads running.\n");
902 implements the "curl" console command
906 curl --cancel filename
911 curl [--pak] [--forthismap] [--for filename filename...] url
912 --pak: after downloading, load the package into the virtual file system
913 --for filename...: only download of at least one of the named files is missing
914 --forthismap: don't reconnect on failure
916 curl --clear_autodownload
917 clears the download success/failure counters
919 curl --finish_autodownload
920 if at least one download has been started, disconnect and drop to the menu
921 once the last download completes successfully, reconnect to the current server
924 void Curl_Curl_f(void)
928 qboolean pak = false;
929 qboolean forthismap = false;
931 const char *name = 0;
935 Con_Print("libcurl DLL not found, this command is inactive.\n");
939 if(!cl_curl_enabled.integer)
941 Con_Print("curl support not enabled. Set cl_curl_enabled to 1 to enable.\n");
945 for(i = 0; i != Cmd_Argc(); ++i)
946 Con_DPrintf("%s ", Cmd_Argv(i));
951 Con_Print("usage:\ncurl --info, curl --cancel [filename], curl url\n");
955 url = Cmd_Argv(Cmd_Argc() - 1);
958 for(i = 1; i != end; ++i)
960 const char *a = Cmd_Argv(i);
961 if(!strcmp(a, "--info"))
966 else if(!strcmp(a, "--cancel"))
968 if(i == end - 1) // last argument
972 downloadinfo *di = Curl_Find(url);
973 Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
977 else if(!strcmp(a, "--pak"))
981 else if(!strcmp(a, "--for"))
983 for(i = i + 1; i != end - 1; ++i)
985 if(!FS_FileExists(Cmd_Argv(i)))
986 goto needthefile; // why can't I have a "double break"?
988 // if we get here, we have all the files...
991 else if(!strcmp(a, "--forthismap"))
995 else if(!strcmp(a, "--as"))
1003 else if(!strcmp(a, "--clear_autodownload"))
1005 // mark all running downloads as "not for this map", so if they
1006 // fail, it does not matter
1007 Curl_Clear_forthismap();
1010 else if(!strcmp(a, "--finish_autodownload"))
1012 if(numdownloads_added)
1014 char donecommand[256];
1017 dpsnprintf(donecommand, sizeof(donecommand), "connect %s", cls.netcon->address);
1018 Curl_CommandWhenDone(donecommand);
1022 Curl_CheckCommandWhenDone();
1028 Con_Printf("invalid option %s\n", a);
1034 Curl_Begin(url, name, pak, forthismap);
1038 ====================
1041 loads the commands and cvars this library uses
1042 ====================
1044 void Curl_Init_Commands(void)
1046 Cvar_RegisterVariable (&cl_curl_enabled);
1047 Cvar_RegisterVariable (&cl_curl_maxdownloads);
1048 Cvar_RegisterVariable (&cl_curl_maxspeed);
1049 Cvar_RegisterVariable (&sv_curl_defaulturl);
1050 Cvar_RegisterVariable (&sv_curl_serverpackages);
1051 Cmd_AddCommand ("curl", Curl_Curl_f, "download data from an URL and add to search path");
1055 ====================
1056 Curl_GetDownloadInfo
1058 returns an array of Curl_downloadinfo_t structs for usage by GUIs.
1059 The number of elements in the array is returned in int *nDownloads.
1060 const char **additional_info may be set to a string of additional user
1061 information, or to NULL if no such display shall occur. The returned
1062 array must be freed later using Z_Free.
1063 ====================
1065 Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **additional_info)
1069 Curl_downloadinfo_t *downinfo;
1070 static char addinfo[128];
1076 *additional_info = NULL;
1081 for(di = downloads; di; di = di->next)
1084 downinfo = (Curl_downloadinfo_t *) Z_Malloc(sizeof(*downinfo) * n);
1086 for(di = downloads; di; di = di->next)
1088 strlcpy(downinfo[i].filename, di->filename, sizeof(downinfo[i].filename));
1091 downinfo[i].progress = Curl_GetDownloadAmount(di);
1092 downinfo[i].speed = Curl_GetDownloadSpeed(di);
1093 downinfo[i].queued = false;
1097 downinfo[i].queued = true;
1104 // TODO: can I clear command_when_done as soon as the first download fails?
1105 if(*command_when_done && !numdownloads_fail && numdownloads_added)
1107 if(strncmp(command_when_done, "connect ", 8))
1108 dpsnprintf(addinfo, sizeof(addinfo), "(will do '%s' when done)", command_when_done);
1110 dpsnprintf(addinfo, sizeof(addinfo), "(will join %s when done)", command_when_done + 8);
1111 *additional_info = addinfo;
1114 *additional_info = NULL;
1123 ====================
1126 finds the URL where to find a given package.
1128 For this, it reads a file "curl_urls.txt" of the following format:
1131 revdm*.pk3 http://revdm/downloads/are/here/
1132 * http://any/other/stuff/is/here/
1134 The URLs should end in /. If not, downloads will still work, but the cached files
1135 can't be just put into the data directory with the same download configuration
1136 (you might want to do this if you want to tag downloaded files from your
1137 server, but you should not). "-" means "don't download".
1139 If no single pattern matched, the cvar sv_curl_defaulturl is used as download
1142 Note: pak1.pak and data*.pk3 are excluded from autodownload at another point in
1143 this file for obvious reasons.
1144 ====================
1146 static const char *Curl_FindPackURL(const char *filename)
1148 static char foundurl[256];
1149 fs_offset_t filesize;
1150 char *buf = (char *) FS_LoadFile("curl_urls.txt", tempmempool, true, &filesize);
1153 // read lines of format "pattern url"
1155 char *pattern = NULL, *patternend = NULL, *url = NULL, *urlend = NULL;
1156 qboolean eof = false;
1168 if(pattern && url && patternend)
1174 if(matchpattern(filename, pattern, true))
1176 strlcpy(foundurl, url, sizeof(foundurl));
1188 if(pattern && !patternend)
1190 else if(url && !urlend)
1196 else if(pattern && patternend && !url)
1205 return sv_curl_defaulturl.string;
1208 typedef struct requirement_s
1210 struct requirement_s *next;
1211 char filename[MAX_QPATH];
1214 static requirement *requirements = NULL;
1218 ====================
1221 Adds the given file to the list of requirements.
1222 ====================
1224 void Curl_RequireFile(const char *filename)
1226 requirement *req = (requirement *) Z_Malloc(sizeof(*requirements));
1227 req->next = requirements;
1228 strlcpy(req->filename, filename, sizeof(req->filename));
1233 ====================
1234 Curl_ClearRequirements
1236 Clears the list of required files for playing on the current map.
1237 This should be called at every map change.
1238 ====================
1240 void Curl_ClearRequirements()
1245 requirement *req = requirements;
1246 requirements = requirements->next;
1249 p = sv_curl_serverpackages.string;
1250 Con_DPrintf("Require all of: %s\n", p);
1251 while(COM_ParseTokenConsole(&p))
1253 Con_DPrintf("Require: %s\n", com_token);
1254 Curl_RequireFile(com_token);
1259 ====================
1260 Curl_SendRequirements
1262 Makes the current host_clients download all files he needs.
1263 This is done by sending him the following console commands:
1265 curl --clear_autodownload
1266 curl --pak --for maps/pushmoddm1.bsp --forthismap http://where/this/darn/map/is/pushmoddm1.pk3
1267 curl --finish_autodownload
1268 ====================
1270 void Curl_SendRequirements()
1272 // for each requirement, find the pack name
1273 char sendbuffer[4096] = "";
1275 qboolean foundone = false;
1277 for(req = requirements; req; req = req->next)
1280 const char *thispack = FS_WhichPack(req->filename);
1281 const char *packurl;
1286 p = strrchr(thispack, '/');
1290 packurl = Curl_FindPackURL(thispack);
1292 if(packurl && *packurl && strcmp(packurl, "-"))
1295 strlcat(sendbuffer, "curl --clear_autodownload\n", sizeof(sendbuffer));
1297 strlcat(sendbuffer, "curl --pak --forthismap --as ", sizeof(sendbuffer));
1298 strlcat(sendbuffer, thispack, sizeof(sendbuffer));
1299 strlcat(sendbuffer, " --for ", sizeof(sendbuffer));
1300 strlcat(sendbuffer, req->filename, sizeof(sendbuffer));
1301 strlcat(sendbuffer, " ", sizeof(sendbuffer));
1302 strlcat(sendbuffer, packurl, sizeof(sendbuffer));
1303 strlcat(sendbuffer, thispack, sizeof(sendbuffer));
1304 strlcat(sendbuffer, "\n", sizeof(sendbuffer));
1311 strlcat(sendbuffer, "curl --finish_autodownload\n", sizeof(sendbuffer));
1313 if(strlen(sendbuffer) + 1 < sizeof(sendbuffer))
1314 Host_ClientCommands("%s", sendbuffer);
1316 Con_Printf("Could not initiate autodownload due to URL buffer overflow\n");