diff options
| author | Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> | 2026-03-12 04:39:41 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-03-11 22:39:41 -0400 |
| commit | dc1d7552c3dca135fcc6432455c2f011d801752d (patch) | |
| tree | 3926e3d32449b23c402b0080cec9b034de253c65 /indra/newview/llvelopack.cpp | |
| parent | 1c8bb96bb460611aa5d05f5570d627ec5ebcc836 (diff) | |
Velopack download failure diagnostic (#5520)
* Velopack download failure diagnostic
* Fix up velopack downloading updates. Handle updates internally then hand them off to velopack. (#5524)
* Update llvelopack.cpp
* More velopack changes. Should download updates properly now.
* Update llvvmquery.cpp
* Don't include NSI files
* Restore optional updates, refine viewer restart behavior. (#5527)
* Add support for optional updates.
* Don't restart the viewer after the update unless it was optional.
* Setup UpdaterServiceSetting with velopack properly.
* Refine the restart behavior a bit - readd the old "the viewer must update" UX.
* If the update is still downloading, close should just reopen the downloading dialog.
Also add a login guard - probably unnecessary, but you never know with how creative our residents get.
---------
Co-authored-by: Jonathan "Geenz" Goodman <geenz@lindenlab.com>
Diffstat (limited to 'indra/newview/llvelopack.cpp')
| -rw-r--r-- | indra/newview/llvelopack.cpp | 432 |
1 files changed, 429 insertions, 3 deletions
diff --git a/indra/newview/llvelopack.cpp b/indra/newview/llvelopack.cpp index 694b75960b..774eb811dc 100644 --- a/indra/newview/llvelopack.cpp +++ b/indra/newview/llvelopack.cpp @@ -29,6 +29,15 @@ #include "llviewerprecompiledheaders.h" #include "llvelopack.h" #include "llstring.h" +#include "llcorehttputil.h" + +#include <boost/json.hpp> +#include <fstream> +#include <unordered_map> +#include "llnotificationsutil.h" +#include "llviewercontrol.h" +#include "llappviewer.h" +#include "llcoros.h" #include "Velopack.h" @@ -50,6 +59,210 @@ static std::string sUpdateUrl; static std::function<void(int)> sProgressCallback; static vpkc_update_manager_t* sUpdateManager = nullptr; static vpkc_update_info_t* sPendingUpdate = nullptr; +static vpkc_update_source_t* sUpdateSource = nullptr; +static LLNotificationPtr sDownloadingNotification; +static bool sRestartAfterUpdate = false; + +// Forward declarations +static void show_required_update_prompt(); +static void show_downloading_notification(const std::string& version); +static bool sRequiredUpdateInProgress = false; +static std::string sRequiredUpdateVersion; +static std::string sRequiredUpdateRelnotes; +static std::unordered_map<std::string, std::string> sAssetUrlMap; // basename -> original absolute URL + +// +// Custom update source helpers +// + +static std::string extract_basename(const std::string& url) +{ + // Strip query params / fragment + std::string path = url; + auto qpos = path.find('?'); + if (qpos != std::string::npos) path = path.substr(0, qpos); + auto fpos = path.find('#'); + if (fpos != std::string::npos) path = path.substr(0, fpos); + + auto spos = path.rfind('/'); + if (spos != std::string::npos && spos + 1 < path.size()) + return path.substr(spos + 1); + return path; +} + +static void rewrite_asset_urls(boost::json::value& jv) +{ + if (jv.is_object()) + { + auto& obj = jv.as_object(); + auto it = obj.find("FileName"); + if (it != obj.end() && it->value().is_string()) + { + std::string filename(it->value().as_string()); + if (filename.find("://") != std::string::npos) + { + std::string basename = extract_basename(filename); + sAssetUrlMap[basename] = filename; + it->value() = basename; + LL_DEBUGS("Velopack") << "Rewrote FileName: " << basename << LL_ENDL; + } + } + for (auto& kv : obj) + { + rewrite_asset_urls(kv.value()); + } + } + else if (jv.is_array()) + { + for (auto& elem : jv.as_array()) + { + rewrite_asset_urls(elem); + } + } +} + +static std::string rewrite_release_feed(const std::string& json_str) +{ + boost::json::value jv = boost::json::parse(json_str); + rewrite_asset_urls(jv); + return boost::json::serialize(jv); +} + +static std::string download_url_raw(const std::string& url) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + auto httpAdapter = std::make_shared<LLCoreHttpUtil::HttpCoroutineAdapter>("VelopackSource", httpPolicy); + auto httpRequest = std::make_shared<LLCore::HttpRequest>(); + auto httpOpts = std::make_shared<LLCore::HttpOptions>(); + httpOpts->setFollowRedirects(true); + + LLSD result = httpAdapter->getRawAndSuspend(httpRequest, url, httpOpts); + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + if (!status) + { + LL_WARNS("Velopack") << "HTTP request failed for " << url << ": " << status.toString() << LL_ENDL; + return {}; + } + + const LLSD::Binary& rawBody = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_RAW].asBinary(); + return std::string(rawBody.begin(), rawBody.end()); +} + +static bool download_url_to_file(const std::string& url, const std::string& local_path) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + auto httpAdapter = std::make_shared<LLCoreHttpUtil::HttpCoroutineAdapter>("VelopackDownload", httpPolicy); + auto httpRequest = std::make_shared<LLCore::HttpRequest>(); + auto httpOpts = std::make_shared<LLCore::HttpOptions>(); + httpOpts->setFollowRedirects(true); + httpOpts->setTransferTimeout(1200); + + LLSD result = httpAdapter->getRawAndSuspend(httpRequest, url, httpOpts); + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + if (!status) + { + LL_WARNS("Velopack") << "Download failed for " << url << ": " << status.toString() << LL_ENDL; + return false; + } + + const LLSD::Binary& rawBody = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_RAW].asBinary(); + llofstream outFile(local_path, std::ios::binary | std::ios::trunc); + if (!outFile.is_open()) + { + LL_WARNS("Velopack") << "Failed to open file for writing: " << local_path << LL_ENDL; + return false; + } + outFile.write(reinterpret_cast<const char*>(rawBody.data()), rawBody.size()); + outFile.close(); + return true; +} + +// +// Custom source callbacks +// + +static char* custom_get_release_feed(void* user_data, const char* releases_name) +{ + std::string base = sUpdateUrl; + if (!base.empty() && base.back() == '/') + base.pop_back(); + std::string url = base + "/" + releases_name; + LL_INFOS("Velopack") << "Fetching release feed: " << url << LL_ENDL; + + std::string json_str = download_url_raw(url); + if (json_str.empty()) + { + return nullptr; + } + + try + { + std::string rewritten = rewrite_release_feed(json_str); + char* result = static_cast<char*>(malloc(rewritten.size() + 1)); + if (result) + { + memcpy(result, rewritten.c_str(), rewritten.size() + 1); + } + return result; + } + catch (const std::exception& e) + { + LL_WARNS("Velopack") << "Failed to parse/rewrite release feed: " << e.what() << LL_ENDL; + // Return original unmodified feed as fallback + char* result = static_cast<char*>(malloc(json_str.size() + 1)); + if (result) + { + memcpy(result, json_str.c_str(), json_str.size() + 1); + } + return result; + } +} + +static void custom_free_release_feed(void* user_data, char* feed) +{ + free(feed); +} + +static std::string sPreDownloadedAssetPath; + +static bool custom_download_asset(void* user_data, + const vpkc_asset_t* asset, + const char* local_path, + size_t progress_callback_id) +{ + // The asset has already been downloaded at the coroutine level (before vpkc_download_updates). + // This callback just copies the pre-downloaded file to where Velopack expects it. + // We cannot use getRawAndSuspend here — coroutine context is lost through the Rust FFI boundary. + if (sPreDownloadedAssetPath.empty()) + { + LL_WARNS("Velopack") << "No pre-downloaded asset available" << LL_ENDL; + return false; + } + + std::string filename = asset->FileName ? asset->FileName : ""; + LL_INFOS("Velopack") << "Download asset callback: filename=" << filename + << " local_path=" << local_path + << " size=" << asset->Size << LL_ENDL; + vpkc_source_report_progress(progress_callback_id, 0); + + std::ifstream src(sPreDownloadedAssetPath, std::ios::binary); + llofstream dst(local_path, std::ios::binary | std::ios::trunc); + if (!src.is_open() || !dst.is_open()) + { + LL_WARNS("Velopack") << "Failed to open files for copy" << LL_ENDL; + return false; + } + + dst << src.rdbuf(); + dst.close(); + src.close(); + + vpkc_source_report_progress(progress_callback_id, 100); + LL_INFOS("Velopack") << "Asset copy complete" << LL_ENDL; + return true; +} // // Platform-specific helpers and hooks @@ -421,6 +634,13 @@ static void on_progress(void* user_data, size_t progress) } } +static void on_vpk_log(void* p_user_data, + const char* psz_level, + const char* psz_message) +{ + LL_DEBUGS("Velopack") << ll_safe_string(psz_message) << LL_ENDL; +} + // // Public API - Cross-platform // @@ -428,6 +648,7 @@ static void on_progress(void* user_data, size_t progress) bool velopack_initialize() { vpkc_set_logger(on_log_message, nullptr); + vpkc_app_set_auto_apply_on_startup(false); #if LL_WINDOWS || LL_DARWIN vpkc_app_set_hook_after_install(on_after_install); @@ -438,7 +659,7 @@ bool velopack_initialize() return true; } -void velopack_check_for_updates() +static void velopack_download_update() { if (sUpdateUrl.empty()) { @@ -452,7 +673,16 @@ void velopack_check_for_updates() options.AllowVersionDowngrade = false; options.ExplicitChannel = nullptr; - if (!vpkc_new_update_manager(sUpdateUrl.c_str(), &options, nullptr, &sUpdateManager)) + if (!sUpdateSource) + { + sUpdateSource = vpkc_new_source_custom_callback( + custom_get_release_feed, + custom_free_release_feed, + custom_download_asset, + nullptr); + } + + if (!vpkc_new_update_manager_with_source(sUpdateSource, &options, nullptr, &sUpdateManager)) { LL_WARNS("Velopack") << "Failed to create update manager" << LL_ENDL; return; @@ -464,7 +694,43 @@ void velopack_check_for_updates() if (result == UPDATE_AVAILABLE && update_info) { + LL_DEBUGS("Velopack") << "Setting up detailed logging"; + // Will be executed only with debug level enabled. + vpkc_set_logger(on_vpk_log, nullptr); + LL_CONT << LL_ENDL; LL_INFOS("Velopack") << "Update available, downloading..." << LL_ENDL; + + // Pre-download the nupkg at the coroutine level where getRawAndSuspend works. + // The download callback inside the Rust FFI cannot use coroutine HTTP. + std::string asset_filename = update_info->TargetFullRelease->FileName + ? update_info->TargetFullRelease->FileName : ""; + std::string asset_url; + auto url_it = sAssetUrlMap.find(asset_filename); + if (url_it != sAssetUrlMap.end()) + { + asset_url = url_it->second; + } + else + { + std::string base = sUpdateUrl; + if (!base.empty() && base.back() == '/') + base.pop_back(); + asset_url = base + "/" + asset_filename; + } + + sPreDownloadedAssetPath = gDirUtilp->getExpandedFilename(LL_PATH_TEMP, asset_filename); + LL_INFOS("Velopack") << "Pre-downloading " << asset_url + << " to " << sPreDownloadedAssetPath << LL_ENDL; + + if (!download_url_to_file(asset_url, sPreDownloadedAssetPath)) + { + LL_WARNS("Velopack") << "Failed to pre-download update asset" << LL_ENDL; + sPreDownloadedAssetPath.clear(); + vpkc_free_update_info(update_info); + return; + } + + LL_INFOS("Velopack") << "Pre-download complete, handing to Velopack" << LL_ENDL; if (vpkc_download_updates(sUpdateManager, update_info, on_progress, nullptr)) { if (sPendingUpdate) @@ -476,9 +742,12 @@ void velopack_check_for_updates() } else { - LL_WARNS("Velopack") << "Failed to download update" << LL_ENDL; + char descr[512]; + vpkc_get_last_error(descr, sizeof(descr)); + LL_WARNS("Velopack") << "Failed to download update: " << ll_safe_string((const char*)descr) << LL_ENDL; vpkc_free_update_info(update_info); } + } else { @@ -486,6 +755,123 @@ void velopack_check_for_updates() } } +static void on_downloading_closed(const LLSD& notification, const LLSD& response) +{ + sDownloadingNotification = nullptr; + if (sRequiredUpdateInProgress) + { + // User closed the downloading dialog during a required update — re-show it + show_downloading_notification(sRequiredUpdateVersion); + } +} + +static void show_downloading_notification(const std::string& version) +{ + LLSD args; + args["VERSION"] = version; + sDownloadingNotification = LLNotificationsUtil::add("DownloadingUpdate", args, LLSD(), on_downloading_closed); +} + +static void dismiss_downloading_notification() +{ + if (sDownloadingNotification) + { + LLNotificationsUtil::cancel(sDownloadingNotification); + sDownloadingNotification = nullptr; + } +} + +static void on_required_update_response(const LLSD& notification, const LLSD& response) +{ + std::string version = notification["substitutions"]["VERSION"].asString(); + LL_INFOS("Velopack") << "Required update acknowledged, starting download" << LL_ENDL; + show_downloading_notification(version); + LLCoros::instance().launch("VelopackRequiredUpdate", []() + { + velopack_download_update(); + dismiss_downloading_notification(); + if (velopack_is_update_pending()) + { + LL_INFOS("Velopack") << "Required update downloaded, quitting to apply" << LL_ENDL; + velopack_request_restart_after_update(); + LLAppViewer::instance()->requestQuit(); + } + }); +} + +static void on_optional_update_response(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) // "Install" + { + std::string version = notification["substitutions"]["VERSION"].asString(); + LL_INFOS("Velopack") << "User accepted optional update, starting download" << LL_ENDL; + show_downloading_notification(version); + LLCoros::instance().launch("VelopackOptionalUpdate", []() + { + velopack_download_update(); + dismiss_downloading_notification(); + if (velopack_is_update_pending()) + { + LL_INFOS("Velopack") << "Optional update downloaded, quitting to apply" << LL_ENDL; + velopack_request_restart_after_update(); + LLAppViewer::instance()->requestQuit(); + } + }); + } + else + { + LL_INFOS("Velopack") << "User declined optional update (option=" << option << ")" << LL_ENDL; + } +} + +static void show_required_update_prompt() +{ + LLSD args; + args["VERSION"] = sRequiredUpdateVersion; + args["URL"] = sRequiredUpdateRelnotes; + LLNotificationsUtil::add("PauseForUpdate", args, LLSD(), on_required_update_response); +} + +void velopack_check_for_updates(bool required, const std::string& version, const std::string& relnotes_url) +{ + if (required) + { + LL_INFOS("Velopack") << "Required update to version " << version << ", prompting user" << LL_ENDL; + sRequiredUpdateInProgress = true; + sRequiredUpdateVersion = version; + sRequiredUpdateRelnotes = relnotes_url; + show_required_update_prompt(); + return; + } + + // Optional update — check user preference + U32 updater_setting = gSavedSettings.getU32("UpdaterServiceSetting"); + if (updater_setting == 0) + { + // "Install only mandatory updates" — skip optional + LL_INFOS("Velopack") << "Optional update to version " << version + << " skipped (UpdaterServiceSetting=0)" << LL_ENDL; + return; + } + + if (updater_setting == 3) + { + // "Install each update automatically" — download silently, apply on quit + LL_INFOS("Velopack") << "Optional update to version " << version + << ", downloading automatically (UpdaterServiceSetting=3)" << LL_ENDL; + velopack_download_update(); + return; + } + + // Default / value 1: "Ask me when an optional update is ready to install" + LL_INFOS("Velopack") << "Optional update available (version " << version << "), prompting user" << LL_ENDL; + LLSD args; + args["VERSION"] = version; + args["URL"] = relnotes_url; + LLNotificationsUtil::add("PromptOptionalUpdate", args, LLSD(), on_optional_update_response); +} + std::string velopack_get_current_version() { if (!sUpdateManager) @@ -507,6 +893,26 @@ bool velopack_is_update_pending() return sPendingUpdate != nullptr; } +bool velopack_is_required_update_in_progress() +{ + return sRequiredUpdateInProgress; +} + +std::string velopack_get_required_update_version() +{ + return sRequiredUpdateVersion; +} + +bool velopack_should_restart_after_update() +{ + return sRestartAfterUpdate; +} + +void velopack_request_restart_after_update() +{ + sRestartAfterUpdate = true; +} + void velopack_apply_pending_update(bool restart) { if (!sUpdateManager || !sPendingUpdate || !sPendingUpdate->TargetFullRelease) @@ -523,6 +929,26 @@ void velopack_apply_pending_update(bool restart) nullptr, 0); } +void velopack_cleanup() +{ + if (sUpdateManager) + { + vpkc_free_update_manager(sUpdateManager); + sUpdateManager = nullptr; + } + if (sUpdateSource) + { + vpkc_free_source(sUpdateSource); + sUpdateSource = nullptr; + } + if (sPendingUpdate) + { + vpkc_free_update_info(sPendingUpdate); + sPendingUpdate = nullptr; + } + sAssetUrlMap.clear(); +} + void velopack_set_update_url(const std::string& url) { sUpdateUrl = url; |
