// A pak file just downloaded, let's see if we can remove some stuff from // the queue which is in the .pak. static void rescan_queue (void) { dlqueue_t *q; FOR_EACH_DLQ (q) { if (q->state == DL_PENDING && q->type < DL_LIST && FS_FileExists (q->path)) CL_FinishDownload (q); } }
// Fatal HTTP error occured, remove any special entries from // queue and fall back to UDP downloading. static void abort_downloads (void) { dlqueue_t *q; HTTP_CleanupDownloads(); cls.download.current = NULL; cls.download.percent = 0; FOR_EACH_DLQ (q) { if (q->state != DL_DONE && q->type >= DL_LIST) CL_FinishDownload (q); else if (q->state == DL_RUNNING) q->state = DL_PENDING; } CL_RequestNextDownload (); CL_StartNextDownload (); }
void CL_ParseDownload (qboolean dataIsCompressed) { int size, percent; char name[MAX_OSPATH]; // read the data size = MSG_ReadShort (&net_message); percent = MSG_ReadByte (&net_message); if (size < 0) { if (size == -1) Com_Printf ("Server does not have this file.\n", LOG_CLIENT); else Com_Printf ("Bad download data from server.\n", LOG_CLIENT); //r1: nuke the temp filename cls.downloadtempname[0] = 0; cls.downloadname[0] = 0; cls.failed_download = true; if (cls.download) { // if here, we tried to resume a file but the server said no fclose (cls.download); cls.download = NULL; } cls.downloadpending = false; CL_RequestNextDownload (); return; } // open the file if not opened yet if (!cls.download) { if (!cls.downloadtempname[0]) { Com_Printf ("Received download packet without request. Ignored.\n", LOG_CLIENT); net_message.readcount += size; return; } CL_DownloadFileName(name, sizeof(name), cls.downloadtempname); FS_CreatePath (name); cls.download = fopen (name, "wb"); if (!cls.download) { net_message.readcount += size; Com_Printf ("Failed to open %s\n", LOG_CLIENT, cls.downloadtempname); cls.downloadpending = false; CL_RequestNextDownload (); return; } } //r1: downloading something, drop to console to show status bar SCR_EndLoadingPlaque(); //r1: if we're stuck with udp, may as well make best use of the bandwidth... if (dataIsCompressed) { #ifndef NO_ZLIB uint16 uncompressedLen; byte uncompressed[0xFFFF]; uncompressedLen = (uint16)MSG_ReadShort (&net_message); if (!uncompressedLen) Com_Error (ERR_DROP, "uncompressedLen == 0"); ZLibDecompress (net_message_buffer + net_message.readcount, size, uncompressed, uncompressedLen, -15); fwrite (uncompressed, 1, uncompressedLen, cls.download); Com_DPrintf ("svc_zdownload(%s): %d -> %d\n", cls.downloadname, size, uncompressedLen); #else Com_Error (ERR_DROP, "Received a unrequested compressed download"); #endif } else { fwrite (net_message_buffer + net_message.readcount, 1, size, cls.download); } net_message.readcount += size; if (percent != 100) { cls.downloadpercent = percent; MSG_WriteByte (clc_stringcmd); MSG_Print ("nextdl"); MSG_EndWriting (&cls.netchan.message); send_packet_now = true; } else { CL_FinishDownload (); // get another file if needed CL_RequestNextDownload (); } }
// A download finished, find out what it was, whether there were any errors and // if so, how severe. If none, rename file and other such stuff. static qboolean finish_download(void) { int msgs_in_queue; CURLMsg *msg; CURLcode result; dlhandle_t *dl; CURL *curl; long response; double sec, bytes; char size[16], speed[16]; char temp[MAX_OSPATH]; qboolean fatal_error = qfalse; const char *err; print_type_t level; do { msg = curl_multi_info_read(curl_multi, &msgs_in_queue); if (!msg) break; if (msg->msg != CURLMSG_DONE) continue; curl = msg->easy_handle; dl = find_handle(curl); cls.download.current = NULL; cls.download.percent = 0; //filelist processing is done on read if (dl->file) { fclose(dl->file); dl->file = NULL; } curl_handles--; result = msg->data.result; switch (result) { //for some reason curl returns CURLE_OK for a 404... case CURLE_HTTP_RETURNED_ERROR: case CURLE_OK: curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response); if (result == CURLE_OK && response == 200) { //success break; } err = http_strerror(response); //404 is non-fatal if (response == 404) { level = PRINT_ALL; goto fail1; } //every other code is treated as fatal //not marking download as done since //we are falling back to UDP level = PRINT_ERROR; fatal_error = qtrue; goto fail2; case CURLE_COULDNT_RESOLVE_HOST: case CURLE_COULDNT_CONNECT: case CURLE_COULDNT_RESOLVE_PROXY: //connection problems are fatal err = curl_easy_strerror(result); level = PRINT_ERROR; fatal_error = qtrue; goto fail2; default: err = curl_easy_strerror(result); level = PRINT_WARNING; fail1: //we mark download as done even if it errored //to prevent multiple attempts. CL_FinishDownload(dl->queue); fail2: Com_LPrintf(level, "[HTTP] %s [%s] [%d remaining file%s]\n", dl->queue->path, err, cls.download.pending, cls.download.pending == 1 ? "" : "s"); if (dl->path[0]) { remove(dl->path); dl->path[0] = 0; } if (dl->buffer) { Z_Free(dl->buffer); dl->buffer = NULL; } curl_multi_remove_handle(curl_multi, curl); continue; } //mark as done CL_FinishDownload(dl->queue); //show some stats curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &sec); curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &bytes); if (sec < 0.001) sec = 0.001; Com_FormatSizeLong(size, sizeof(size), bytes); Com_FormatSizeLong(speed, sizeof(speed), bytes / sec); //FIXME: //technically i shouldn't need to do this as curl will auto reuse the //existing handle when you change the url. however, the curl_handles goes //all weird when reusing a download slot in this way. if you can figure //out why, please let me know. curl_multi_remove_handle(curl_multi, curl); Com_Printf("[HTTP] %s [%s, %s/sec] [%d remaining file%s]\n", dl->queue->path, size, speed, cls.download.pending, cls.download.pending == 1 ? "" : "s"); if (dl->path[0]) { //rename the temp file Q_snprintf(temp, sizeof(temp), "%s/%s", fs_gamedir, dl->queue->path); if (rename(dl->path, temp)) Com_EPrintf("[HTTP] Failed to rename '%s' to '%s': %s\n", dl->path, dl->queue->path, strerror(errno)); dl->path[0] = 0; //a pak file is very special... if (dl->queue->type == DL_PAK) { CL_RestartFilesystem(qfalse); rescan_queue(); } } else if (!fatal_error) { parse_file_list(dl); } } while (msgs_in_queue > 0); //fatal error occured, disable HTTP if (fatal_error) { abort_downloads(); return qfalse; } // see if we have more to dl CL_RequestNextDownload(); return qtrue; }
// Actually starts a download by adding it to the curl multi handle. static void start_download(dlqueue_t *entry, dlhandle_t *dl) { size_t len; char temp[MAX_QPATH]; char escaped[MAX_QPATH * 4]; CURLMcode ret; qerror_t err; //yet another hack to accomodate filelists, how i wish i could push :( //NULL file handle indicates filelist. if (entry->type == DL_LIST) { dl->file = NULL; dl->path[0] = 0; //filelist paths are absolute escape_path(entry->path, escaped); } else { len = Q_snprintf(dl->path, sizeof(dl->path), "%s/%s.tmp", fs_gamedir, entry->path); if (len >= sizeof(dl->path)) { Com_EPrintf("[HTTP] Refusing oversize temporary file path.\n"); goto fail; } //prepend quake path with gamedir len = Q_snprintf(temp, sizeof(temp), "%s/%s", http_gamedir(), entry->path); if (len >= sizeof(temp)) { Com_EPrintf("[HTTP] Refusing oversize server file path.\n"); goto fail; } escape_path(temp, escaped); err = FS_CreatePath(dl->path); if (err < 0) { Com_EPrintf("[HTTP] Couldn't create path to '%s': %s\n", dl->path, Q_ErrorString(err)); goto fail; } //don't bother with http resume... too annoying if server doesn't support it. dl->file = fopen(dl->path, "wb"); if (!dl->file) { Com_EPrintf("[HTTP] Couldn't open '%s' for writing: %s\n", dl->path, strerror(errno)); goto fail; } } len = Q_snprintf(dl->url, sizeof(dl->url), "%s%s", download_server, escaped); if (len >= sizeof(dl->url)) { Com_EPrintf("[HTTP] Refusing oversize download URL.\n"); goto fail; } dl->buffer = NULL; dl->size = 0; dl->position = 0; dl->queue = entry; if (!dl->curl) dl->curl = curl_easy_init(); curl_easy_setopt(dl->curl, CURLOPT_ENCODING, ""); #ifdef _DEBUG if (cl_http_debug->integer) { curl_easy_setopt(dl->curl, CURLOPT_DEBUGFUNCTION, debug_func); curl_easy_setopt(dl->curl, CURLOPT_VERBOSE, 1); } #endif curl_easy_setopt(dl->curl, CURLOPT_NOPROGRESS, 0); if (dl->file) { curl_easy_setopt(dl->curl, CURLOPT_WRITEDATA, dl->file); curl_easy_setopt(dl->curl, CURLOPT_WRITEFUNCTION, NULL); } else { curl_easy_setopt(dl->curl, CURLOPT_WRITEDATA, dl); curl_easy_setopt(dl->curl, CURLOPT_WRITEFUNCTION, recv_func); } curl_easy_setopt(dl->curl, CURLOPT_FAILONERROR, 1); curl_easy_setopt(dl->curl, CURLOPT_PROXY, cl_http_proxy->string); curl_easy_setopt(dl->curl, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(dl->curl, CURLOPT_MAXREDIRS, 5); curl_easy_setopt(dl->curl, CURLOPT_PROGRESSFUNCTION, progress_func); curl_easy_setopt(dl->curl, CURLOPT_PROGRESSDATA, dl); curl_easy_setopt(dl->curl, CURLOPT_USERAGENT, com_version->string); curl_easy_setopt(dl->curl, CURLOPT_REFERER, download_referer); curl_easy_setopt(dl->curl, CURLOPT_URL, dl->url); ret = curl_multi_add_handle(curl_multi, dl->curl); if (ret != CURLM_OK) { Com_EPrintf("[HTTP] Failed to add download handle: %s\n", curl_multi_strerror(ret)); fail: CL_FinishDownload(entry); // see if we have more to dl CL_RequestNextDownload(); return; } Com_DPrintf("[HTTP] Fetching %s...\n", dl->url); entry->state = DL_RUNNING; curl_handles++; }
/* ===================== CL_ParseDownload A download message has been received from the server ===================== */ void CL_ParseDownload (sizebuf_t *msg, qboolean dataIsCompressed) { int size, percent; char name[MAX_OSPATH]; // int r; // read the data size = MSG_ReadShort (msg); percent = MSG_ReadByte (msg); if (size < 0) { if (size == -1) Com_Printf ("Server does not have this file.\n"); else Com_Printf ("Bad download data from server.\n"); cls.downloadtempname[0] = 0; cls.downloadname[0] = 0; cls.failed_download = true; if (cls.download) { // if here, we tried to resume a file but the server said no fclose (cls.download); cls.download = NULL; } CL_RequestNextDownload (); return; } // open the file if not opened yet if (!cls.download) { if (!cls.downloadtempname[0]) { Com_Printf ("Received download packet without request. Ignored.\n"); msg->readcount += size; return; } CL_DownloadFileName(name, sizeof(name), cls.downloadtempname); FS_CreatePath (name); cls.download = fopen (name, "wb"); if (!cls.download) { msg->readcount += size; Com_Printf ("Failed to open %s\n", cls.downloadtempname); CL_RequestNextDownload (); return; } } if (dataIsCompressed) { uint16 uncompressedLen; byte uncompressed[0xFFFF]; uncompressedLen = MSG_ReadShort (msg); if (!uncompressedLen) Com_Error (ERR_DROP, "uncompressedLen == 0"); ZLibDecompress (msg->data + msg->readcount, size, uncompressed, uncompressedLen, -15); fwrite (uncompressed, 1, uncompressedLen, cls.download); Com_DPrintf ("svc_zdownload(%s): %d -> %d\n", cls.downloadname, size, uncompressedLen); } else { fwrite (msg->data + msg->readcount, 1, size, cls.download); } //fwrite (net_message.data + net_message.readcount, 1, size, cls.download); msg->readcount += size; if (percent != 100) { // request next block // change display routines by zoid cls.downloadpercent = percent; MSG_WriteByte (&cls.netchan.message, clc_stringcmd); SZ_Print (&cls.netchan.message, "nextdl"); } else { CL_FinishDownload(); // get another file if needed CL_RequestNextDownload (); } }