/* =============== CL_FinishHTTPDownload 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 void CL_FinishHTTPDownload (void) { size_t i; int msgs_in_queue; CURLMsg *msg; CURLcode result; dlhandle_t *dl; CURL *curl; long responseCode; double timeTaken; double fileSize; char tempName[MAX_OSPATH]; qboolean isFile; do { msg = curl_multi_info_read (multi, &msgs_in_queue); if (!msg) { Com_Printf ("CL_FinishHTTPDownload: Odd, no message for us...\n"); return; } if (msg->msg != CURLMSG_DONE) { Com_Printf ("CL_FinishHTTPDownload: Got some weird message...\n"); continue; } curl = msg->easy_handle; // curl doesn't provide reverse-lookup of the void * ptr, so search for it for (i = 0; i < 4; i++) { if (cls.HTTPHandles[i].curl == curl) { dl = &cls.HTTPHandles[i]; break; } } if (i == 4) Com_Error (ERR_DROP, "CL_FinishHTTPDownload: Handle not found"); //we mark everything as done even if it errored to prevent multiple //attempts. dl->queueEntry->state = DLQ_STATE_DONE; //filelist processing is done on read if (dl->file) isFile = true; else isFile = false; if (isFile) { fclose (dl->file); dl->file = NULL; } //might be aborted if (pendingCount) pendingCount--; handleCount--; //Com_Printf ("finished dl: hc = %d\n", handleCount); cls.downloadname[0] = 0; cls.downloadposition = 0; 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, &responseCode); if (responseCode == 404) { i = strlen (dl->queueEntry->quakePath); if (!strcmp (dl->queueEntry->quakePath + i - 4, ".pak")) downloading_pak = false; if (isFile) remove (dl->filePath); Com_Printf ("HTTP(%s): 404 File Not Found [%d remaining files]\n", dl->queueEntry->quakePath, pendingCount); curl_easy_getinfo (curl, CURLINFO_SIZE_DOWNLOAD, &fileSize); if (fileSize > 512) { //ick isFile = false; result = CURLE_FILESIZE_EXCEEDED; Com_Printf ("Oversized 404 body received (%d bytes), aborting HTTP downloading.\n", (int)fileSize); } else { curl_multi_remove_handle (multi, dl->curl); continue; } } else if (responseCode == 200) { if (!isFile && !abortDownloads) CL_ParseFileList (dl); break; } //every other code is treated as fatal, fallthrough here //fatal error, disable http case CURLE_COULDNT_RESOLVE_HOST: case CURLE_COULDNT_CONNECT: case CURLE_COULDNT_RESOLVE_PROXY: if (isFile) remove (dl->filePath); Com_Printf ("Fatal HTTP error: %s\n", curl_easy_strerror (result)); curl_multi_remove_handle (multi, dl->curl); if (abortDownloads) continue; CL_CancelHTTPDownloads (true); continue; default: i = strlen (dl->queueEntry->quakePath); if (!strcmp (dl->queueEntry->quakePath + i - 4, ".pak")) downloading_pak = false; if (isFile) remove (dl->filePath); Com_Printf ("HTTP download failed: %s\n", curl_easy_strerror (result)); curl_multi_remove_handle (multi, dl->curl); continue; } if (isFile) { //rename the temp file Com_sprintf (tempName, sizeof(tempName), "%s/%s", FS_Gamedir(), dl->queueEntry->quakePath); if (rename (dl->filePath, tempName)) Com_Printf ("Failed to rename %s for some odd reason...", dl->filePath); //a pak file is very special... i = strlen (tempName); if (!strcmp (tempName + i - 4, ".pak")) { CL_RestartFilesystem(true); CL_ReVerifyHTTPQueue (); downloading_pak = false; } } //show some stats curl_easy_getinfo (curl, CURLINFO_TOTAL_TIME, &timeTaken); curl_easy_getinfo (curl, CURLINFO_SIZE_DOWNLOAD, &fileSize); //FIXME: //technically i shouldn't need to do this as curl will auto reuse the //existing handle when you change the URL. however, the handleCount 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 (multi, dl->curl); Com_Printf ("HTTP(%s): %.f bytes, %.2fkB/sec [%d remaining files]\n", dl->queueEntry->quakePath, fileSize, (fileSize / 1024.0) / timeTaken, pendingCount); } while (msgs_in_queue > 0); //FS_FlushCache (); if (handleCount == 0) { if (abortDownloads == HTTPDL_ABORT_SOFT) abortDownloads = HTTPDL_ABORT_NONE; else if (abortDownloads == HTTPDL_ABORT_HARD) cls.downloadServer[0] = 0; } // done current batch, see if we have more to dl - maybe a .bsp needs downloaded if (cls.state == ca_connected && !CL_PendingHTTPDownloads()) 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; }
/* ================== CL_ParseServerData ================== */ qboolean CL_ParseServerData (sizebuf_t *msg) { char *str; int i; Com_DPrintf ("Serverdata packet received.\n"); // // wipe the client_state_t struct // CL_ClearState (); cls.state = ca_connected; // parse protocol version number i = MSG_ReadLong (msg); cls.serverProtocol = i; cl.servercount = MSG_ReadLong (msg); cl.attractloop = MSG_ReadByte (msg); if (cl.attractloop) { //cls.serverProtocol = PROTOCOL_VERSION_DEFAULT; } else if (i != PROTOCOL_VERSION_DEFAULT && i != PROTOCOL_VERSION_R1Q2) { Com_Error (ERR_DROP, "Server is using unknown protocol %d.", i); } // game directory str = MSG_ReadString (msg); Q_strncpyz (cl.gamedir, str, sizeof(cl.gamedir)); str = cl.gamedir; // set gamedir if (!Com_ServerState()) { Cvar_SetLatched("game", str); if( FS_NeedRestart() ) { CL_RestartFilesystem(true); } } // parse player entity number cl.playernum = MSG_ReadShort (msg); // get the full level name str = MSG_ReadString (msg); cl.pmp.strafeHack = false; cl.pmp.speedMultiplier = 1; cl.pmp.airaccelerate = 0; cls.protocolVersion = 0; if (cls.serverProtocol == PROTOCOL_VERSION_R1Q2) { i = MSG_ReadByte(msg); if( i ) { Com_Printf("'Enhanced' R1Q2 servers are not supported, falling back to protocol 34.\n" ); CL_Disconnect(); cls.serverProtocol = PROTOCOL_VERSION_DEFAULT; CL_Reconnect_f (); return false; } i = MSG_ReadShort(msg); if (i < PROTOCOL_VERSION_R1Q2_MINIMUM || i > PROTOCOL_VERSION_R1Q2_CURRENT) { if (cl.attractloop) { if ( i < PROTOCOL_VERSION_R1Q2_MINIMUM ) Com_Printf("This demo was recorded with an earlier version of the R1Q2 protocol. It may not play back properly.\n"); else Com_Printf("This demo was recorded with a later version of the R1Q2 protocol. It may not play back properly.\n"); } else { if( i < PROTOCOL_VERSION_R1Q2_MINIMUM ) { Com_Printf("Server uses OLDER minor R1Q2 protocol version than minimum supported (%i < %i), falling back to protocol 34.\n", i, PROTOCOL_VERSION_R1Q2_MINIMUM); CL_Disconnect(); cls.serverProtocol = PROTOCOL_VERSION_DEFAULT; CL_Reconnect_f (); return false; } Com_Printf("Server uses NEWER minor R1Q2 protocol version (%i > %i), some features will be unavailable.\n", i, PROTOCOL_VERSION_R1Q2_CURRENT); } } if (i >= 1903) { MSG_ReadByte(msg); cl.pmp.strafeHack = MSG_ReadByte(msg); } cl.pmp.speedMultiplier = 2; cls.protocolVersion = i; } if (cl.playernum == -1) { // playing a cinematic or showing a pic, not a level SCR_PlayCinematic (str); } else { // seperate the printfs so the server message can have a color Com_Printf("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n"); Com_Printf ("%c%s\n", 2, str); // need to prep refresh at next oportunity cl.refresh_prepped = false; if((unsigned)cl.playernum >= MAX_CLIENTS) { cl.playernum = ( MAX_CLIENTS - 1 ); } } return true; }