void LLUpdateDownloader::Implementation::throwOnCurlError(CURLcode code) { if(code != CURLE_OK) { const char * errorString = curl_easy_strerror(code); if(errorString != 0) { throw DownloadError(curl_easy_strerror(code)); } else { throw DownloadError("unknown curl error"); } } else { ; // No op. } }
void LLUpdateDownloader::Implementation::initializeCurlGet(std::string const & url, bool processHeader) { if(mCurl == 0) { mCurl = curl_easy_init(); } else { curl_easy_reset(mCurl); } if(mCurl == 0) throw DownloadError("failed to initialize curl"); throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_NOSIGNAL, true)); throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_FOLLOWLOCATION, true)); throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_WRITEFUNCTION, &write_function)); throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_WRITEDATA, this)); if(processHeader) { throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HEADERFUNCTION, &header_function)); throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HEADERDATA, this)); } throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HTTPGET, true)); throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_URL, url.c_str())); throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_PROGRESSFUNCTION, &progress_callback)); throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_PROGRESSDATA, this)); throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_NOPROGRESS, false)); // if it's a required update set the bandwidth limit to 0 (unlimited) curl_off_t limit = mDownloadData["required"].asBoolean() ? 0 : mBandwidthLimit; throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_MAX_RECV_SPEED_LARGE, limit)); mDownloadPercent = 0; }
void LLUpdateDownloader::Implementation::resumeDownloading(size_t startByte) { LL_INFOS("UpdateDownload") << "resuming download from " << mDownloadData["url"].asString() << " at byte " << startByte << LL_ENDL; initializeCurlGet(mDownloadData["url"].asString(), false); // The header 'Range: bytes n-' will request the bytes remaining in the // source begining with byte n and ending with the last byte. boost::format rangeHeaderFormat("Range: bytes=%u-"); rangeHeaderFormat % startByte; mHeaderList = curl_slist_append(mHeaderList, rangeHeaderFormat.str().c_str()); if(mHeaderList == 0) throw DownloadError("cannot add Range header"); throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HTTPHEADER, mHeaderList)); mDownloadStream.open(mDownloadData["path"].asString(), std::ios_base::out | std::ios_base::binary | std::ios_base::app); start(); }
void LLUpdateDownloader::Implementation::startDownloading(LLURI const & uri, std::string const & hash) { mDownloadData["url"] = uri.asString(); mDownloadData["hash"] = hash; mDownloadData["current_version"] = ll_get_version(); LLSD path = uri.pathArray(); if(path.size() == 0) throw DownloadError("no file path"); std::string fileName = path[path.size() - 1].asString(); std::string filePath = gDirUtilp->getExpandedFilename(LL_PATH_TEMP, fileName); mDownloadData["path"] = filePath; LL_INFOS("UpdateDownload") << "downloading " << filePath << " from " << uri.asString() << LL_ENDL; LL_INFOS("UpdateDownload") << "hash of file is " << hash << LL_ENDL; llofstream dataStream(mDownloadRecordPath); LLSDSerialize::toPrettyXML(mDownloadData, dataStream); mDownloadStream.open(filePath, std::ios_base::out | std::ios_base::binary); initializeCurlGet(uri.asString(), true); start(); }
/* * TransferStart: Retrieve files; the information needed to set up the * transfer is in info. * * This function is run in its own thread. */ void __cdecl TransferStart(void *download_info) { Bool done; char filename[MAX_PATH + FILENAME_MAX]; char local_filename[MAX_PATH + 1]; // Local filename of current downloaded file int i; int outfile; // Handle to output file DWORD size; // Size of block we're reading int bytes_read; // Total # of bytes we've read #if defined VANILLA_UPDATER const char *mime_types[2] = { "application/x-zip-compressed" }; #else const char *mime_types[4] = { "application/octet-stream", "text/plain", "application/x-msdownload", NULL }; //const char *mime_types[2] = { "application/octet-stream", NULL }; #endif DWORD file_size; DWORD file_size_buf_len; DWORD index = 0; DownloadInfo *info = (DownloadInfo *)download_info; aborted = False; hConnection = NULL; hSession = NULL; hFile = NULL; hConnection = InternetOpen(szAppName, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, INTERNET_FLAG_RELOAD); if (hConnection == NULL) { DownloadError(info->hPostWnd, GetString(hInst, IDS_CANTINIT)); return; } if (aborted) { TransferCloseHandles(); return; } hSession = InternetConnect(hConnection, info->machine, INTERNET_INVALID_PORT_NUMBER, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0); if (hSession == NULL) { DownloadError(info->hPostWnd, GetString(hInst, IDS_NOCONNECTION), info->machine); return; } for (i = info->current_file; i < info->num_files; i++) { if (aborted) { TransferCloseHandles(); return; } // Skip non-guest files if we're a guest if (config.guest && !(info->files[i].flags & DF_GUEST)) { PostMessage(info->hPostWnd, BK_FILEDONE, 0, i); continue; } // If not supposed to transfer file, inform main thread if (DownloadCommand(info->files[i].flags) != DF_RETRIEVE) { // Wait for main thread to finish processing previous file WaitForSingleObject(hSemaphore, INFINITE); if (aborted) { TransferCloseHandles(); return; } PostMessage(info->hPostWnd, BK_FILEDONE, 0, i); continue; } #if VANILLA_UPDATER sprintf(filename, "%s%s", info->path, info->files[i].filename); #else sprintf(filename, "%s//%s", info->path, info->files[i].filename); #endif hFile = HttpOpenRequest(hSession, NULL, filename, NULL, NULL, mime_types, INTERNET_FLAG_NO_UI, 0); if (hFile == NULL) { debug(("HTTPOpenRequest failed, error = %d, %s\n", GetLastError(), GetLastErrorStr())); DownloadError(info->hPostWnd, GetString(hInst, IDS_CANTFINDFILE), filename, info->machine); return; } if (!HttpSendRequest(hFile, NULL, 0, NULL, 0)) { debug(("HTTPSendRequest failed, error = %d, %s\n", GetLastError(), GetLastErrorStr())); DownloadError(info->hPostWnd, GetString(hInst, IDS_CANTSENDREQUEST), filename, info->machine); return; } // Get file size file_size_buf_len = sizeof(file_size); index = 0; if (!HttpQueryInfo(hFile, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &file_size, &file_size_buf_len, &index)) { debug(("HTTPQueryInfo failed, error = %d, %s\n", GetLastError(), GetLastErrorStr())); DownloadError(info->hPostWnd, GetString(hInst, IDS_CANTGETFILESIZE), filename, info->machine); return; } PostMessage(info->hPostWnd, BK_FILESIZE, i, file_size); #if VANILLA_UPDATER sprintf(local_filename, "%s\\%s", download_dir, info->files[i].filename); #else sprintf(local_filename, "%s\\%s", info->files[i].path, info->files[i].filename); #endif outfile = open(local_filename, O_BINARY | O_RDWR | O_CREAT | O_TRUNC, S_IWRITE | S_IREAD); if (outfile <= 0) { debug(("Couldn't open local file %s for writing\n", local_filename)); DownloadError(info->hPostWnd, GetString(hInst, IDS_CANTWRITELOCALFILE), local_filename); return; } // Read first block done = False; bytes_read = 0; while (!done) { if (!InternetReadFile(hFile, buf, BUFSIZE, &size)) { DownloadError(info->hPostWnd, GetString(hInst, IDS_CANTREADFTPFILE), filename); } if (size > 0) { if (write(outfile, buf, size) != size) { DownloadError(info->hPostWnd, GetString(hInst, IDS_CANTWRITELOCALFILE), local_filename); close(outfile); return; } } // Update graph position bytes_read += size; PostMessage(info->hPostWnd, BK_PROGRESS, 0, bytes_read); // See if done with file if (size == 0) { close(outfile); InternetCloseHandle(hFile); done = True; // Wait for main thread to finish processing previous file WaitForSingleObject(hSemaphore, INFINITE); if (aborted) { TransferCloseHandles(); return; } PostMessage(info->hPostWnd, BK_FILEDONE, 0, i); } } } InternetCloseHandle(hSession); InternetCloseHandle(hConnection); PostMessage(info->hPostWnd, BK_TRANSFERDONE, 0, 0); }
void game_compatibility::RequestCompatibility(bool online) { // Creates new map from database auto ReadJSON = [=](const QJsonObject& json_data, bool after_download) { int return_code = json_data["return_code"].toInt(); if (return_code < 0) { if (after_download) { std::string error_message; switch (return_code) { case -1: error_message = "Server Error - Internal Error"; break; case -2: error_message = "Server Error - Maintenance Mode"; break; default: error_message = "Server Error - Unknown Error"; break; } LOG_ERROR(GENERAL, "Compatibility error: { %s: return code %d }", error_message, return_code); Q_EMIT DownloadError(qstr(error_message) + " " + QString::number(return_code)); } else { LOG_ERROR(GENERAL, "Compatibility error: { Database Error - Invalid: return code %d }", return_code); } return false; } if (!json_data["results"].isObject()) { LOG_ERROR(GENERAL, "Compatibility error: { Database Error - No Results found }"); return false; } m_compat_database.clear(); QJsonObject json_results = json_data["results"].toObject(); // Retrieve status data for every valid entry for (const auto& key : json_results.keys()) { if (!json_results[key].isObject()) { LOG_ERROR(GENERAL, "Compatibility error: { Database Error - Unusable object %s }", sstr(key)); continue; } QJsonObject json_result = json_results[key].toObject(); // Retrieve compatibility information from json compat_status status = Status_Data.at(json_result.value("status").toString("NoResult")); // Add date if possible status.date = json_result.value("date").toString(); // Add status to map m_compat_database.emplace(std::pair<std::string, compat_status>(sstr(key), status)); } return true; }; if (!online) { // Retrieve database from file QFile file(m_filepath); if (!file.exists()) { LOG_NOTICE(GENERAL, "Compatibility notice: { Database file not found: %s }", sstr(m_filepath)); return; } if (!file.open(QIODevice::ReadOnly)) { LOG_ERROR(GENERAL, "Compatibility error: { Database Error - Could not read database from file: %s }", sstr(m_filepath)); return; } QByteArray data = file.readAll(); file.close(); LOG_NOTICE(GENERAL, "Compatibility notice: { Finished reading database from file: %s }", sstr(m_filepath)); // Create new map from database ReadJSON(QJsonDocument::fromJson(data).object(), online); return; } if (QSslSocket::supportsSsl() == false) { LOG_ERROR(GENERAL, "Can not retrieve the online database! Please make sure your system supports SSL."); QMessageBox::warning(nullptr, tr("Warning!"), tr("Can not retrieve the online database! Please make sure your system supports SSL.")); return; } LOG_NOTICE(GENERAL, "SSL supported! Beginning compatibility database download from: %s", sstr(m_url)); // Send request and wait for response m_network_access_manager.reset(new QNetworkAccessManager()); QNetworkReply* network_reply = m_network_access_manager->get(m_network_request); // Show Progress m_progress_dialog.reset(new QProgressDialog(tr(".Please wait."), tr("Abort"), 0, 100)); m_progress_dialog->setWindowTitle(tr("Downloading Database")); m_progress_dialog->setFixedWidth(QLabel("This is the very length of the progressbar due to hidpi reasons.").sizeHint().width()); m_progress_dialog->setValue(0); m_progress_dialog->show(); // Animate progress dialog a bit more m_progress_timer.reset(new QTimer(this)); connect(m_progress_timer.get(), &QTimer::timeout, [&]() { switch (++m_timer_count % 3) { case 0: m_timer_count = 0; m_progress_dialog->setLabelText(tr(".Please wait.")); break; case 1: m_progress_dialog->setLabelText(tr("..Please wait..")); break; default: m_progress_dialog->setLabelText(tr("...Please wait...")); break; } }); m_progress_timer->start(500); // Handle abort connect(m_progress_dialog.get(), &QProgressDialog::rejected, network_reply, &QNetworkReply::abort); // Handle progress connect(network_reply, &QNetworkReply::downloadProgress, [&](qint64 bytesReceived, qint64 bytesTotal) { m_progress_dialog->setMaximum(bytesTotal); m_progress_dialog->setValue(bytesReceived); }); // Handle response according to its contents connect(network_reply, &QNetworkReply::finished, [=]() { // Clean up Progress Dialog if (m_progress_dialog) { m_progress_dialog->close(); } if (m_progress_timer) { m_progress_timer->stop(); } // Handle Errors if (network_reply->error() != QNetworkReply::NoError) { // We failed to retrieve a new database, therefore refresh gamelist to old state QString error = network_reply->errorString(); Q_EMIT DownloadError(error); LOG_ERROR(GENERAL, "Compatibility error: { Network Error - %s }", sstr(error)); return; } LOG_NOTICE(GENERAL, "Compatibility notice: { Database download finished }"); // Read data from network reply QByteArray data = network_reply->readAll(); network_reply->deleteLater(); // Create new map from database and write database to file if database was valid if (ReadJSON(QJsonDocument::fromJson(data).object(), online)) { // We have a new database in map, therefore refresh gamelist to new state Q_EMIT DownloadFinished(); // Write database to file QFile file(m_filepath); if (file.exists()) { LOG_NOTICE(GENERAL, "Compatibility notice: { Database file found: %s }", sstr(m_filepath)); } if (!file.open(QIODevice::WriteOnly)) { LOG_ERROR(GENERAL, "Compatibility error: { Database Error - Could not write database to file: %s }", sstr(m_filepath)); return; } file.write(data); file.close(); LOG_SUCCESS(GENERAL, "Compatibility success: { Write database to file: %s }", sstr(m_filepath)); } }); // We want to retrieve a new database, therefore refresh gamelist and indicate that Q_EMIT DownloadStarted(); }