Release/26.1.1 (#5530)

* Integrate Velopack installer and update framework

* Add Velopack update support for macOS and VVM integration

* Update Velopack version and dependencies

* Improve Velopack packaging for macOS

* #5346 Uninstall older non-velopack viewer (#5363)

* #5335 Fix silent uninstall asking about registry

* #5346 Uninstall older non-velopack viewer

* Use runtime viewer exe name, handle Velopack URL

* Velopack download failure diagnostic (#5520)

* Velopack download failure diagnostic

* Fix up velopack downloading updates.  Handle updates internally then hand them off to velopack. (#5524)

* More velopack changes.  Should download updates properly now.

* 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.

---------

Co-authored-by: Jonathan "Geenz" Goodman <geenz@lindenlab.com>

* Remove SLVersionChecker from the viewer with velopack. (#5528)

* Remove SLVersionChecker updater integration

* Ensure that the portable install has the correct version number.

* Don't produce shortcuts with VPK - we do this with our post install.

* Bump viewer version from 26.1.0 to 26.1.1

* Potential fix for uninstaller not being functional.

* Fix for UpdaterServiceSetting being ignored.

* Filter for release channel when generating shortcuts.

* Add some more logging for icons on Windows builds.

* More VPK logging.

* Move velopack packaging in CI to the sign and package step.

* Enable velopack downgrade and skip older updates

* Move the version required checking into velopack's checks.

* Potential fix for downgrade prompts.

* Make sure our macOS flow mirrors Windows.

* Make sure to use the dev version of the mac sign and package.

* p#553 Only one of two uninstallers displayed

* #5346 Don't force user to shutdown velopack build for NSIS uninstall

* #5346 Ignore option for the uninstall dialog

* #5346 Fix early exit crash

* #5346 Properly reset version flag.

* Add some autodetect logic on macOS.

* p#564 Clear legacy links

* p#553 Handle uninstall records

* p#549 Permit testing release notes on a test build

* p#564 Remake nsis to velopack update flow

* p#564 Remake nsis to velopack update flow #2

* p#564 Fix incorrect value type

* p#553 Clear velopack's own registry entry in favor of a custom one

* #5346 Resolve duplicated window class name

* Bump to 2.1.0 of sign and package.

---------

Co-authored-by: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com>
This commit is contained in:
Jonathan "Geenz" Goodman
2026-04-07 19:12:59 -04:00
committed by GitHub
co-authored by Andrey Kleshchev
parent 18db816ef7
commit 5c500ccf40
33 changed files with 2222 additions and 287 deletions
+57 -3
View File
@@ -2,6 +2,14 @@ name: Build
on:
workflow_dispatch:
inputs:
installer_type:
description: 'Windows installer type'
type: choice
options:
- velopack
- nsis
default: 'velopack'
pull_request:
push:
branches: ["main", "release/*", "project/*"]
@@ -53,6 +61,20 @@ jobs:
relnotes: ${{ steps.which-branch.outputs.relnotes }}
imagename: ${{ steps.build.outputs.imagename }}
configuration: ${{ matrix.configuration }}
# Windows Velopack outputs (passed to sign-pkg-windows)
velopack_pack_id: ${{ steps.build.outputs.velopack_pack_id }}
velopack_pack_version: ${{ steps.build.outputs.velopack_pack_version }}
velopack_pack_title: ${{ steps.build.outputs.velopack_pack_title }}
velopack_main_exe: ${{ steps.build.outputs.velopack_main_exe }}
velopack_exclude: ${{ steps.build.outputs.velopack_exclude }}
velopack_icon: ${{ steps.build.outputs.velopack_icon }}
velopack_installer_base: ${{ steps.build.outputs.velopack_installer_base }}
# macOS Velopack outputs (passed to sign-pkg-mac)
velopack_mac_pack_id: ${{ steps.build.outputs.velopack_mac_pack_id }}
velopack_mac_pack_version: ${{ steps.build.outputs.velopack_mac_pack_version }}
velopack_mac_pack_title: ${{ steps.build.outputs.velopack_mac_pack_title }}
velopack_mac_main_exe: ${{ steps.build.outputs.velopack_mac_main_exe }}
velopack_mac_bundle_id: ${{ steps.build.outputs.velopack_mac_bundle_id }}
env:
AUTOBUILD_ADDRSIZE: 64
AUTOBUILD_BUILD_ID: ${{ github.run_id }}
@@ -84,6 +106,8 @@ jobs:
# Only set variants to the one configuration: don't let build.sh loop
# over variants, let GitHub distribute variants over multiple hosts.
variants: ${{ matrix.configuration }}
# Pass USE_VELOPACK to CMake when using Velopack installer (default) - Windows and macOS
autobuild_configure_parameters: ${{ (contains(matrix.runner, 'windows') || contains(matrix.runner, 'macos')) && (github.event.inputs.installer_type || 'velopack') == 'velopack' && '-- -DUSE_VELOPACK:BOOL=ON' || '' }}
steps:
- name: Checkout code
uses: actions/checkout@v5
@@ -126,6 +150,17 @@ jobs:
with:
token: ${{ github.token }}
- name: Setup .NET for Velopack
if: (runner.os == 'Windows' || runner.os == 'macOS') && (github.event.inputs.installer_type || 'velopack') == 'velopack'
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: Install Velopack CLI
if: (runner.os == 'Windows' || runner.os == 'macOS') && (github.event.inputs.installer_type || 'velopack') == 'velopack'
shell: bash
run: dotnet tool install -g vpk
- name: Build
id: build
shell: bash
@@ -310,13 +345,21 @@ jobs:
steps:
- name: Sign and package Windows viewer
if: env.AZURE_KEY_VAULT_URI && env.AZURE_CERT_NAME && env.AZURE_CLIENT_ID && env.AZURE_CLIENT_SECRET && env.AZURE_TENANT_ID
uses: secondlife/viewer-build-util/sign-pkg-windows@v2.0.4
uses: secondlife/viewer-build-util/sign-pkg-windows@v2.1.0
with:
vault_uri: "${{ env.AZURE_KEY_VAULT_URI }}"
cert_name: "${{ env.AZURE_CERT_NAME }}"
client_id: "${{ env.AZURE_CLIENT_ID }}"
client_secret: "${{ env.AZURE_CLIENT_SECRET }}"
tenant_id: "${{ env.AZURE_TENANT_ID }}"
installer_type: "${{ github.event.inputs.installer_type || 'velopack' }}"
velopack_pack_id: "${{ needs.build.outputs.velopack_pack_id }}"
velopack_pack_version: "${{ needs.build.outputs.velopack_pack_version }}"
velopack_pack_title: "${{ needs.build.outputs.velopack_pack_title }}"
velopack_main_exe: "${{ needs.build.outputs.velopack_main_exe }}"
velopack_exclude: "${{ needs.build.outputs.velopack_exclude }}"
velopack_icon: "${{ needs.build.outputs.velopack_icon }}"
velopack_installer_base: "${{ needs.build.outputs.velopack_installer_base }}"
sign-and-package-mac:
env:
@@ -349,7 +392,7 @@ jobs:
- name: Sign and package Mac viewer
if: env.SIGNING_CERT_MACOS && env.SIGNING_CERT_MACOS_IDENTITY && env.SIGNING_CERT_MACOS_PASSWORD && steps.note-creds.outputs.note_user && steps.note-creds.outputs.note_pass && steps.note-creds.outputs.note_team
uses: secondlife/viewer-build-util/sign-pkg-mac@v2
uses: secondlife/viewer-build-util/sign-pkg-mac@v2.1.0
with:
channel: ${{ needs.build.outputs.viewer_channel }}
imagename: ${{ needs.build.outputs.imagename }}
@@ -359,6 +402,11 @@ jobs:
note_user: ${{ steps.note-creds.outputs.note_user }}
note_pass: ${{ steps.note-creds.outputs.note_pass }}
note_team: ${{ steps.note-creds.outputs.note_team }}
velopack_pack_id: "${{ needs.build.outputs.velopack_mac_pack_id }}"
velopack_pack_version: "${{ needs.build.outputs.velopack_mac_pack_version }}"
velopack_pack_title: "${{ needs.build.outputs.velopack_mac_pack_title }}"
velopack_main_exe: "${{ needs.build.outputs.velopack_mac_main_exe }}"
velopack_bundle_id: "${{ needs.build.outputs.velopack_mac_bundle_id }}"
post-windows-symbols:
env:
@@ -439,6 +487,10 @@ jobs:
with:
pattern: "*-metadata"
- uses: actions/download-artifact@v4
with:
pattern: "*-releases"
- name: Rename metadata
run: |
cp Windows-metadata/autobuild-package.xml Windows-autobuild-package.xml
@@ -464,12 +516,14 @@ jobs:
generate_release_notes: true
target_commitish: ${{ github.sha }}
append_body: true
fail_on_unmatched_files: true
fail_on_unmatched_files: false
files: |
macOS-installer/*.dmg
Windows-installer/*.exe
*-autobuild-package.xml
*-viewer_version.txt
Windows-releases/*
macOS-releases/*
- name: post release URL
run: |
+28 -8
View File
@@ -21,7 +21,9 @@ on:
project:
description: "Project Name (used for channel name in project builds, and tag name for all builds)"
default: "hippo"
# TODO - add an input for selecting another sha to build other than head of branch
tag_override:
description: "Override the tag name (optional). If the tag already exists, a numeric suffix is appended."
required: false
jobs:
tag-release:
@@ -34,7 +36,7 @@ jobs:
NIGHTLY_DATE=$(date --rfc-3339=date)
echo NIGHTLY_DATE=${NIGHTLY_DATE} >> ${GITHUB_ENV}
echo TAG_ID="$(echo ${{ github.sha }} | cut -c1-8)-${{ inputs.project || '${NIGHTLY_DATE}' }}" >> ${GITHUB_ENV}
- name: Update Tag
- name: Create Tag
uses: actions/github-script@v8
with:
# use a real access token instead of GITHUB_TOKEN default.
@@ -44,9 +46,27 @@ jobs:
# this token will need to be renewed anually in January
github-token: ${{ secrets.LL_TAG_RELEASE_TOKEN }}
script: |
github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: "refs/tags/${{ env.VIEWER_CHANNEL }}#${{ env.TAG_ID }}",
sha: context.sha
})
const override = `${{ inputs.tag_override }}`.trim();
const baseTag = override || `${{ env.VIEWER_CHANNEL }}#${{ env.TAG_ID }}`;
// Try the base tag first, then append -2, -3, etc. if it already exists
let tag = baseTag;
for (let attempt = 1; ; attempt++) {
try {
await github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `refs/tags/${tag}`,
sha: context.sha
});
core.info(`Created tag: ${tag}`);
break;
} catch (e) {
if (e.status === 422 && attempt < 10) {
core.info(`Tag '${tag}' already exists, trying next suffix...`);
tag = `${baseTag}-${attempt + 1}`;
} else {
throw e;
}
}
}
+50
View File
@@ -2914,6 +2914,56 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>description</key>
<string>Voxelized Hierarchical Approximate Convex Decomposition</string>
</map>
<key>velopack</key>
<map>
<key>platforms</key>
<map>
<key>windows64</key>
<map>
<key>archive</key>
<map>
<key>creds</key>
<string>github</string>
<key>hash</key>
<string>91abbc360640b5b2e0a4c001a36ad411a9a42602</string>
<key>hash_algorithm</key>
<string>sha1</string>
<key>url</key>
<string>https://api.github.com/repos/secondlife-3p/3p-velopack/releases/assets/380583560</string>
</map>
<key>name</key>
<string>windows64</string>
</map>
<key>darwin64</key>
<map>
<key>archive</key>
<map>
<key>creds</key>
<string>github</string>
<key>hash</key>
<string>05563a79bdeb83d66a72ac1e97587dc2a8f64511</string>
<key>hash_algorithm</key>
<string>sha1</string>
<key>url</key>
<string>https://api.github.com/repos/secondlife-3p/3p-velopack/releases/assets/380583554</string>
</map>
<key>name</key>
<string>darwin64</string>
</map>
</map>
<key>license</key>
<string>MIT</string>
<key>license_file</key>
<string>LICENSES/velopack.txt</string>
<key>copyright</key>
<string>Velopack Ltd.</string>
<key>version</key>
<string>40232ef.23500976684</string>
<key>name</key>
<string>velopack</string>
<key>description</key>
<string>Velopack C/C++ Library</string>
</map>
</map>
<key>package_description</key>
<map>
+1
View File
@@ -62,6 +62,7 @@ set(cmake_SOURCE_FILES
UI.cmake
UnixInstall.cmake
Variables.cmake
Velopack.cmake
VHACD.cmake
ViewerMiscLibs.cmake
VisualLeakDetector.cmake
+1 -1
View File
@@ -13,7 +13,7 @@ elseif (WINDOWS)
foreach(hive HKEY_CURRENT_USER HKEY_LOCAL_MACHINE)
# prefer more recent Python versions to older ones, if multiple versions
# are installed
foreach(pyver 3.13 3.12 3.11 3.10 3.9 3.8 3.7)
foreach(pyver 3.14 3.13 3.12 3.11 3.10 3.9 3.8 3.7)
list(APPEND regpaths "[${hive}\\SOFTWARE\\Python\\PythonCore\\${pyver}\\InstallPath]")
endforeach()
endforeach()
+68
View File
@@ -0,0 +1,68 @@
# -*- cmake -*-
# Velopack installer and update framework integration
# https://velopack.io/
include_guard()
# USE_VELOPACK controls whether to use Velopack for installer packaging (instead of NSIS/DMG)
option(USE_VELOPACK "Use Velopack for installer packaging" OFF)
if (WINDOWS)
include(Prebuilt)
use_prebuilt_binary(velopack)
add_library(ll::velopack INTERFACE IMPORTED)
target_include_directories(ll::velopack SYSTEM INTERFACE
${LIBS_PREBUILT_DIR}/include/velopack
)
target_link_libraries(ll::velopack INTERFACE
${ARCH_PREBUILT_DIRS_RELEASE}/velopack_libc.lib
)
# Windows system libraries required by Velopack
target_link_libraries(ll::velopack INTERFACE
winhttp
ole32
shell32
shlwapi
version
userenv
ws2_32
bcrypt
ntdll
)
target_compile_definitions(ll::velopack INTERFACE LL_VELOPACK=1)
elseif (DARWIN)
include(Prebuilt)
use_prebuilt_binary(velopack)
add_library(ll::velopack INTERFACE IMPORTED)
target_include_directories(ll::velopack SYSTEM INTERFACE
${LIBS_PREBUILT_DIR}/include/velopack
)
target_link_libraries(ll::velopack INTERFACE
${ARCH_PREBUILT_DIRS_RELEASE}/libvelopack_libc.a
)
# macOS system frameworks required by Velopack (Rust static library dependencies)
target_link_libraries(ll::velopack INTERFACE
"-framework Foundation"
"-framework Security"
"-framework SystemConfiguration"
"-framework AppKit"
"-framework CoreFoundation"
"-framework CoreServices"
"-framework IOKit"
"-liconv"
"-lresolv"
)
target_compile_definitions(ll::velopack INTERFACE LL_VELOPACK=1)
endif()
+2 -1
View File
@@ -157,7 +157,8 @@ BASE_ARGUMENTS=[
for use by a .bat file.""",
default=None),
dict(name='versionfile',
description="""The name of a file containing the full version number."""),
description="""The name of a file containing the full version number.""",
default=None),
]
def usage(arguments, srctree=""):
+15 -3
View File
@@ -38,7 +38,8 @@ LL::WorkQueueBase::WorkQueueBase(const std::string& name, bool auto_shutdown)
{
// Register for "LLApp" events so we can implicitly close() on viewer shutdown
std::string listener_name = "WorkQueue:" + getKey();
LLEventPumps::instance().obtain("LLApp").listen(
LLEventPumps* pump = LLEventPumps::getInstance();
pump->obtain("LLApp").listen(
listener_name,
[this](const LLSD& stat)
{
@@ -54,14 +55,25 @@ LL::WorkQueueBase::WorkQueueBase(const std::string& name, bool auto_shutdown)
// Store the listener name so we can unregister in the destructor
mListenerName = listener_name;
mPumpHandle = pump->getHandle();
}
}
LL::WorkQueueBase::~WorkQueueBase()
{
if (!mListenerName.empty() && !LLEventPumps::wasDeleted())
if (!mListenerName.empty() && !mPumpHandle.isDead())
{
LLEventPumps::instance().obtain("LLApp").stopListening(mListenerName);
// Due to shutdown order issues, use handle, not a singleton
// and ignore fiber issue.
try
{
LLEventPumps* pump = mPumpHandle.get();
pump->obtain("LLApp").stopListening(mListenerName);
}
catch (const boost::fibers::lock_error&)
{
// Likely mutex is down, ignore
}
}
}
+6
View File
@@ -14,6 +14,7 @@
#include "llcoros.h"
#include "llexception.h"
#include "llhandle.h"
#include "llinstancetracker.h"
#include "llinstancetrackersubclass.h"
#include "threadsafeschedule.h"
@@ -22,6 +23,9 @@
#include <functional> // std::function
#include <string>
class LLEventPumps;
namespace LL
{
@@ -202,6 +206,8 @@ namespace LL
// Name used for the LLApp event listener (empty if not registered)
std::string mListenerName;
// Due to shutdown order issues, store by handle
LLHandle<LLEventPumps> mPumpHandle;
};
/*****************************************************************************
+15
View File
@@ -43,6 +43,7 @@ include(TinyEXR)
include(ThreeJS)
include(Tracy)
include(UI)
include(Velopack)
include(ViewerMiscLibs)
include(ViewerManager)
include(VisualLeakDetector)
@@ -659,6 +660,7 @@ set(viewer_SOURCE_FILES
llurllineeditorctrl.cpp
llurlwhitelist.cpp
llversioninfo.cpp
llvvmquery.cpp
llviewchildren.cpp
llviewerassetstats.cpp
llviewerassetstorage.cpp
@@ -1337,6 +1339,7 @@ set(viewer_HEADER_FILES
llurllineeditorctrl.h
llurlwhitelist.h
llversioninfo.h
llvvmquery.h
llviewchildren.h
llviewerassetstats.h
llviewerassetstorage.h
@@ -1456,6 +1459,8 @@ if (DARWIN)
LIST(APPEND viewer_SOURCE_FILES llappviewermacosx-objc.h)
LIST(APPEND viewer_SOURCE_FILES llfilepicker_mac.mm)
LIST(APPEND viewer_HEADER_FILES llfilepicker_mac.h)
LIST(APPEND viewer_SOURCE_FILES llvelopack.cpp)
LIST(APPEND viewer_HEADER_FILES llvelopack.h)
set_source_files_properties(
llappviewermacosx-objc.mm
@@ -1518,16 +1523,19 @@ if (WINDOWS)
list(APPEND viewer_SOURCE_FILES
llappviewerwin32.cpp
llvelopack.cpp
llwindebug.cpp
)
set_source_files_properties(
llappviewerwin32.cpp
llvelopack.cpp
PROPERTIES
COMPILE_DEFINITIONS "${VIEWER_CHANNEL_VERSION_DEFINES}"
)
list(APPEND viewer_HEADER_FILES
llappviewerwin32.h
llvelopack.h
llwindebug.h
)
@@ -1941,6 +1949,7 @@ if (WINDOWS)
"--discord=${USE_DISCORD}"
"--openal=${USE_OPENAL}"
"--tracy=${USE_TRACY}"
"--velopack=${USE_VELOPACK}"
--build=${CMAKE_CURRENT_BINARY_DIR}
--buildtype=$<CONFIG>
"--channel=${VIEWER_CHANNEL}"
@@ -2064,6 +2073,10 @@ if (USE_DISCORD)
target_link_libraries(${VIEWER_BINARY_NAME} ll::discord_sdk )
endif ()
if (TARGET ll::velopack)
target_link_libraries(${VIEWER_BINARY_NAME} ll::velopack )
endif ()
if( TARGET ll::intel_memops )
target_link_libraries(${VIEWER_BINARY_NAME} ll::intel_memops )
endif()
@@ -2261,9 +2274,11 @@ if (DARWIN)
--arch=${ARCH}
--artwork=${ARTWORK_DIR}
"--bugsplat=${BUGSPLAT_DB}"
--bundleid=${MACOSX_BUNDLE_GUI_IDENTIFIER}
"--discord=${USE_DISCORD}"
"--openal=${USE_OPENAL}"
"--tracy=${USE_TRACY}"
"--velopack=${USE_VELOPACK}"
--build=${CMAKE_CURRENT_BINARY_DIR}
--buildtype=$<CONFIG>
"--channel=${VIEWER_CHANNEL}"
+1 -1
View File
@@ -1 +1 @@
26.1.0
26.1.1
+13 -3
View File
@@ -4237,7 +4237,17 @@
<key>Value</key>
<string>0.0.0</string>
</map>
<key>PreviousInstallChecked</key>
<map>
<key>Comment</key>
<string>Whether viewer checked previous install on the same channel for NSIS</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>0</integer>
</map>
<key>LimitDragDistance</key>
<map>
<key>Comment</key>
@@ -13057,11 +13067,11 @@
<key>UpdaterShowReleaseNotes</key>
<map>
<key>Comment</key>
<string>Enables displaying of the Release notes in a web floater after update.</string>
<string>Enables displaying of the Release notes in a web floater after update. 0 - don't show, 1 - show, 2 - show even for test viewers</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<string>S32</string>
<key>Value</key>
<integer>1</integer>
</map>
@@ -767,8 +767,21 @@ Function un.UserSettingsFiles
StrCmp $DO_UNINSTALL_V2 "true" Keep # Don't remove user's settings files on auto upgrade
# Ask if user wants to keep data files or not
MessageBox MB_YESNO|MB_ICONQUESTION $(RemoveDataFilesMB) IDYES Remove IDNO Keep
ClearErrors
Push $0
${GetParameters} $COMMANDLINE
${GetOptionsS} $COMMANDLINE "/clrusrfiles" $0
# GetOptionsS returns an error if option does not exist, jump past Goto.
IfErrors +3 0
Pop $0
Goto Remove
Pop $0
ClearErrors
ifSilent Keep 0
# Ask if user wants to keep data files or not
MessageBox MB_YESNO|MB_ICONQUESTION $(RemoveDataFilesMB) IDYES Remove IDNO Keep
Remove:
Push $0
@@ -864,11 +877,25 @@ RMDir "$INSTDIR"
IfFileExists "$INSTDIR" FOLDERFOUND NOFOLDER
FOLDERFOUND:
ifSilent NOFOLDER 0
MessageBox MB_OK $(DeleteProgramFilesMB) /SD IDOK IDOK NOFOLDER
NOFOLDER:
MessageBox MB_YESNO $(DeleteRegistryKeysMB) IDYES DeleteKeys IDNO NoDelete
ClearErrors
Push $0
${GetParameters} $COMMANDLINE
${GetOptionsS} $COMMANDLINE "/clearreg" $0
# GetOptionsS returns an error if option does not exist, jump past Goto.
IfErrors +3 0
Pop $0
Goto DeleteKeys
Pop $0
ClearErrors
ifSilent NoDelete 0
MessageBox MB_YESNO $(DeleteRegistryKeysMB) IDYES DeleteKeys IDNO NoDelete
DeleteKeys:
DeleteRegKey SHELL_CONTEXT "SOFTWARE\Classes\x-grid-location-info"
@@ -912,21 +939,7 @@ Function .onInstSuccess
Call CheckWindowsServPack # Warn if not on the latest SP before asking to launch.
StrCmp $SKIP_AUTORUN "true" +2;
# Assumes SetOutPath $INSTDIR
# Run INSTEXE (our updater), passing VIEWER_EXE plus the command-line
# arguments built into our shortcuts. This gives the updater a chance
# to verify that the viewer we just installed is appropriate for the
# running system -- or, if not, to download and install a different
# viewer. For instance, if a user running 32-bit Windows installs a
# 64-bit viewer, it cannot run on this system. But since the updater
# is a 32-bit executable even in the 64-bit viewer package, the
# updater can detect the problem and adapt accordingly.
# Once everything is in order, the updater will run the specified
# viewer with the specified params.
# Quote the updater executable and the viewer executable because each
# must be a distinct command-line token, but DO NOT quote the language
# string because it must decompose into separate command-line tokens.
Exec '"$INSTDIR\$INSTEXE" precheck "$INSTDIR\$VIEWER_EXE" $SHORTCUT_LANG_PARAM'
Exec '"$INSTDIR\$VIEWER_EXE" $SHORTCUT_LANG_PARAM'
#
FunctionEnd
+20 -70
View File
@@ -98,6 +98,11 @@
#include "llurlmatch.h"
#include "lltextutil.h"
#include "lllogininstance.h"
#include "llvvmquery.h"
#if LL_VELOPACK
#include "llvelopack.h"
#endif
#include "llprogressview.h"
#include "llvocache.h"
#include "lldiskcache.h"
@@ -382,9 +387,6 @@ const std::string ERROR_MARKER_FILE_NAME("SecondLife.error_marker");
const std::string LOGOUT_MARKER_FILE_NAME("SecondLife.logout_marker");
static std::string gLaunchFileOnQuit;
// Used on Win32 for other apps to identify our window (eg, win_setup)
const char* const VIEWER_WINDOW_CLASSNAME = "Second Life";
//----------------------------------------------------------------------------
// List of entries from strings.xml to always replace
@@ -656,7 +658,6 @@ LLAppViewer::LLAppViewer()
mPurgeCacheOnExit(false),
mPurgeUserDataOnExit(false),
mSecondInstance(false),
mUpdaterNotFound(false),
mSavedFinalSnapshot(false),
mSavePerAccountSettings(false), // don't save settings on logout unless login succeeded.
mQuitRequested(false),
@@ -1112,68 +1113,17 @@ bool LLAppViewer::init()
gGLActive = false;
#if LL_RELEASE_FOR_DOWNLOAD
// Skip updater if this is a non-interactive instance
//#if LL_RELEASE_FOR_DOWNLOAD
// Launch VVM update check
if (!gSavedSettings.getBOOL("CmdLineSkipUpdater") && !gNonInteractive)
{
LLProcess::Params updater;
updater.desc = "updater process";
// Because it's the updater, it MUST persist beyond the lifespan of the
// viewer itself.
updater.autokill = false;
std::string updater_file;
#if LL_WINDOWS
updater_file = "SLVersionChecker.exe";
updater.executable = gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, updater_file);
#elif LL_DARWIN
updater_file = "SLVersionChecker";
updater.executable = gDirUtilp->add(gDirUtilp->getAppRODataDir(), "updater", updater_file);
#else
updater_file = "SLVersionChecker";
updater.executable = gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, updater_file);
#endif
// add LEAP mode command-line argument to whichever of these we selected
updater.args.add("leap");
// UpdaterServiceSettings
if (gSavedSettings.getBOOL("FirstLoginThisInstall"))
{
// Befor first login, treat this as 'manual' updates,
// updater won't install anything, but required updates
updater.args.add("0");
}
else
{
updater.args.add(stringize(gSavedSettings.getU32("UpdaterServiceSetting")));
}
// channel
updater.args.add(LLVersionInfo::instance().getChannel());
// testok
updater.args.add(stringize(gSavedSettings.getBOOL("UpdaterWillingToTest")));
// ForceAddressSize
updater.args.add(stringize(gSavedSettings.getU32("ForceAddressSize")));
try
{
// Run the updater. An exception from launching the updater should bother us.
LLLeap::create(updater, true);
mUpdaterNotFound = false;
}
catch (...)
{
LLUIString details = LLNotifications::instance().getGlobalString("LLLeapUpdaterFailure");
details.setArg("[UPDATER_APP]", updater_file);
OSMessageBox(
details.getString(),
LLStringUtil::null,
OSMB_OK);
mUpdaterNotFound = true;
}
initVVMUpdateCheck();
}
else
{
LL_WARNS("InitInfo") << "Skipping updater check." << LL_ENDL;
}
#endif //LL_RELEASE_FOR_DOWNLOAD
//#endif //LL_RELEASE_FOR_DOWNLOAD
{
// Iterate over --leap command-line options. But this is a bit tricky: if
@@ -1711,6 +1661,16 @@ void LLAppViewer::flushLFSIO()
bool LLAppViewer::cleanup()
{
#if LL_VELOPACK
// Apply any pending Velopack update before shutdown
if (velopack_is_update_pending())
{
LL_INFOS("AppInit") << "Applying pending Velopack update on shutdown..." << LL_ENDL;
velopack_apply_pending_update(velopack_should_restart_after_update());
}
velopack_cleanup();
#endif
//ditch LLVOAvatarSelf instance
gAgentAvatarp = NULL;
@@ -3147,7 +3107,7 @@ bool LLAppViewer::initWindow()
LLViewerWindow::Params window_params;
window_params
.title(gWindowTitle)
.name(VIEWER_WINDOW_CLASSNAME)
.name(sWindowClass)
.x(gSavedSettings.getS32("WindowX"))
.y(gSavedSettings.getS32("WindowY"))
.width(gSavedSettings.getU32("WindowWidth"))
@@ -3272,16 +3232,6 @@ bool LLAppViewer::initWindow()
return true;
}
bool LLAppViewer::isUpdaterMissing()
{
return mUpdaterNotFound;
}
bool LLAppViewer::waitForUpdater()
{
return !gSavedSettings.getBOOL("CmdLineSkipUpdater") && !mUpdaterNotFound && !gNonInteractive;
}
void LLAppViewer::writeDebugInfo(bool isStatic)
{
#if LL_WINDOWS && LL_BUGSPLAT
+8 -4
View File
@@ -117,9 +117,6 @@ public:
bool quitRequested() { return mQuitRequested; }
bool logoutRequestSent() { return mLogoutRequestSent; }
bool isSecondInstance() { return mSecondInstance; }
bool isUpdaterMissing(); // In use by tests
bool waitForUpdater();
void writeDebugInfo(bool isStatic=true);
void setServerReleaseNotesURL(const std::string& url) { mServerReleaseNotesURL = url; }
@@ -287,6 +284,14 @@ protected:
virtual void sendOutOfDiskSpaceNotification();
protected:
// NSIS relies on this to detect if viewer is up.
// NSIS's method is somewhat unreliable since window
// can close long before cleanup is done.
// sendURLToOtherInstance also relies on this to detect if viewer is up.
static constexpr const char* sWindowClass = "Second Life";
private:
bool doFrame();
@@ -327,7 +332,6 @@ private:
static LLAppViewer* sInstance;
bool mSecondInstance; // Is this a second instance of the app?
bool mUpdaterNotFound; // True when attempt to start updater failed
std::string mMarkerFileName;
LLAPRFile mMarkerFile; // A file created to indicate the app is running.
+31 -2
View File
@@ -72,6 +72,11 @@
#include <fstream>
#include <exception>
// Velopack installer and update framework
#if LL_VELOPACK
#include "llvelopack.h"
#endif
// Bugsplat (http://bugsplat.com) crash reporting tool
#ifdef LL_BUGSPLAT
#include "BugSplat.h"
@@ -220,7 +225,6 @@ LONG WINAPI catchallCrashHandler(EXCEPTION_POINTERS * /*ExceptionInfo*/)
return 0;
}
const std::string LLAppViewerWin32::sWindowClass = "Second Life";
/*
This function is used to print to the command line a text message
@@ -424,6 +428,31 @@ int APIENTRY WINMAIN(HINSTANCE hInstance,
PWSTR pCmdLine,
int nCmdShow)
{
#if LL_VELOPACK
// Velopack MUST be initialized first - it may handle install/uninstall
// commands and exit the process before we do anything else.
if (!velopack_initialize())
{
// Velopack handled the invocation (install/uninstall hook)
// Drop install related settings
gDirUtilp->initAppDirs("SecondLife");
std::string user_settings_path = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "settings.xml");
LLControlGroup settings("global");
if (settings.loadFromFile(user_settings_path))
{
// If user reinstalls or updates, we want to recheck for nsis leftovers.
if (settings.controlExists("PreviousInstallChecked"))
{
settings.setBOOL("PreviousInstallChecked", false);
}
settings.saveToFile(user_settings_path, true);
}
return 0;
}
#endif
// Call Tracy first thing to have it allocate memory
// https://github.com/wolfpld/tracy/issues/196
LL_PROFILER_FRAME_END;
@@ -933,7 +962,7 @@ bool LLAppViewerWin32::restoreErrorTrap()
bool LLAppViewerWin32::sendURLToOtherInstance(const std::string& url)
{
wchar_t window_class[256]; /* Flawfinder: ignore */ // Assume max length < 255 chars.
mbstowcs(window_class, sWindowClass.c_str(), 255);
mbstowcs(window_class, sWindowClass, 255);
window_class[255] = 0;
// Use the class instead of the window name.
HWND other_window = FindWindow(window_class, NULL);
-2
View File
@@ -59,8 +59,6 @@ protected:
std::string generateSerialNumber();
static const std::string sWindowClass;
private:
void disableWinErrorReporting();
+12 -69
View File
@@ -277,11 +277,6 @@ void LLLoginInstance::constructAuthParams(LLPointer<LLCredential> user_credentia
mRequestData["params"] = request_params;
mRequestData["options"] = requested_options;
mRequestData["http_params"] = http_params;
#if LL_RELEASE_FOR_DOWNLOAD
mRequestData["wait_for_updater"] = LLAppViewer::instance()->waitForUpdater();
#else
mRequestData["wait_for_updater"] = false;
#endif
}
bool LLLoginInstance::handleLoginEvent(const LLSD& event)
@@ -316,13 +311,6 @@ void LLLoginInstance::handleLoginFailure(const LLSD& event)
// Login has failed.
// Figure out why and respond...
LLSD response = event["data"];
LLSD updater = response["updater"];
// Always provide a response to the updater, if in fact the updater
// contacted us, if in fact the ping contains a 'reply' key. Most code
// paths tell it not to proceed with updating.
ResponsePtr resp(std::make_shared<LLEventAPI::Response>
(LLSDMap("update", false), updater));
std::string reason_response = response["reason"].asString();
std::string message_response = response["message"].asString();
@@ -385,26 +373,15 @@ void LLLoginInstance::handleLoginFailure(const LLSD& event)
}
else if(reason_response == "update")
{
// This can happen if the user clicked Login quickly, before we heard
// back from the Viewer Version Manager, but login failed because
// login.cgi is insisting on a required update. We were called with an
// event that bundles both the login.cgi 'response' and the
// synchronization event from the 'updater'.
// login.cgi rejected login and requires an update. Since Velopack
// handles updates now, the best we can do here is tell the user
// to download the update manually via the release notes URL.
std::string login_version = response["message_args"]["VERSION"];
std::string vvm_version = updater["VERSION"];
std::string relnotes = updater["URL"];
LL_WARNS("LLLogin") << "Login failed because an update to version " << login_version << " is required." << LL_ENDL;
// vvm_version might be empty because we might not have gotten
// SLVersionChecker's LoginSync handshake. But if it IS populated, it
// should (!) be the same as the version we got from login.cgi.
if ((! vvm_version.empty()) && vvm_version != login_version)
{
LL_WARNS("LLLogin") << "VVM update version " << vvm_version
<< " differs from login version " << login_version
<< "; presenting VVM version to match release notes URL"
<< LL_ENDL;
login_version = vvm_version;
}
// Try to use the release notes URL from the VVM query if available,
// otherwise fall back to constructing one from the version.
std::string relnotes = LLVersionInfo::instance().getReleaseNotes();
if (relnotes.empty() || relnotes.find("://") == std::string::npos)
{
relnotes = LLTrans::getString("RELEASE_NOTES_BASE_URL");
@@ -420,32 +397,11 @@ void LLLoginInstance::handleLoginFailure(const LLSD& event)
args["VERSION"] = login_version;
args["URL"] = relnotes;
if (updater.isUndefined())
{
// If the updater failed to shake hands, better advise the user to
// download the update him/herself.
LLNotificationsUtil::add(
"RequiredUpdate",
args,
updater,
boost::bind(&LLLoginInstance::handleLoginDisallowed, this, _1, _2));
}
else
{
// If we've heard from the updater that an update is required,
// then display the prompt that assures the user we'll take care
// of it. This is the one case in which we bind 'resp':
// instead of destroying our Response object (and thus sending a
// negative reply to the updater) as soon as we exit this
// function, bind our shared_ptr so it gets passed into
// syncWithUpdater. That ensures that the response is delayed
// until the user has responded to the notification.
LLNotificationsUtil::add(
"PauseForUpdate",
args,
updater,
boost::bind(&LLLoginInstance::syncWithUpdater, this, resp, _1, _2));
}
LLNotificationsUtil::add(
"RequiredUpdate",
args,
LLSD(),
boost::bind(&LLLoginInstance::handleLoginDisallowed, this, _1, _2));
}
else if(reason_response == "mfa_challenge")
{
@@ -479,19 +435,6 @@ void LLLoginInstance::handleLoginFailure(const LLSD& event)
}
}
void LLLoginInstance::syncWithUpdater(ResponsePtr resp, const LLSD& notification, const LLSD& response)
{
LL_INFOS("LLLogin") << "LLLoginInstance::syncWithUpdater" << LL_ENDL;
// 'resp' points to an instance of LLEventAPI::Response that will be
// destroyed as soon as we return and the notification response functor is
// unregistered. Modify it so that it tells the updater to go ahead and
// perform the update. Naturally, if we allowed the user a choice as to
// whether to proceed or not, this assignment would reflect the user's
// selection.
(*resp)["update"] = true;
attemptComplete();
}
void LLLoginInstance::handleLoginDisallowed(const LLSD& notification, const LLSD& response)
{
attemptComplete();
-6
View File
@@ -28,8 +28,6 @@
#define LL_LLLOGININSTANCE_H
#include "lleventdispatcher.h"
#include "lleventapi.h"
#include <memory> // std::shared_ptr
#include "llsecapi.h"
class LLLogin;
class LLEventStream;
@@ -72,10 +70,7 @@ public:
void saveMFAHash(LLSD const& response);
private:
typedef std::shared_ptr<LLEventAPI::Response> ResponsePtr;
void constructAuthParams(LLPointer<LLCredential> user_credentials);
void updateApp(bool mandatory, const std::string& message);
bool updateDialogCallback(const LLSD& notification, const LLSD& response);
bool handleLoginEvent(const LLSD& event);
void handleLoginFailure(const LLSD& event);
@@ -83,7 +78,6 @@ private:
void handleDisconnect(const LLSD& event);
void handleIndeterminate(const LLSD& event);
void handleLoginDisallowed(const LLSD& notification, const LLSD& response);
void syncWithUpdater(ResponsePtr resp, const LLSD& notification, const LLSD& response);
bool handleTOSResponse(bool v, const std::string& key);
void showMFAChallange(const std::string& message);
+16
View File
@@ -37,6 +37,9 @@
#include "llappviewer.h"
#include "llbutton.h"
#if LL_VELOPACK
#include "llvelopack.h"
#endif
#include "llcheckboxctrl.h"
#include "llcommandhandler.h" // for secondlife:///app/login/
#include "llcombobox.h"
@@ -936,6 +939,19 @@ void LLPanelLogin::handleMediaEvent(LLPluginClassMedia* /*self*/, EMediaEvent ev
// static
void LLPanelLogin::onClickConnect(bool commit_fields)
{
#if LL_VELOPACK
// In theory, you should never be able to get here.
// If there's a required update, try as you might you're not supposed to actually close the downloading update dialog.
// But just in case...
if (velopack_is_required_update_in_progress())
{
LLSD args;
args["VERSION"] = velopack_get_required_update_version();
LLNotificationsUtil::add("DownloadingUpdate", args);
return;
}
#endif
if (sInstance && sInstance->mCallback)
{
if (commit_fields)
+83 -6
View File
@@ -29,6 +29,11 @@
#include "llappviewer.h"
#include "llstartup.h"
#if LL_VELOPACK && LL_WINDOWS
#include "llvelopack.h"
#include <shellapi.h>
#endif
#if LL_WINDOWS
# include <process.h> // _spawnl()
#else
@@ -266,6 +271,7 @@ std::unique_ptr<LLViewerStats::PhaseMap> LLStartUp::sPhases(new LLViewerStats::P
void login_show();
void login_callback(S32 option, void* userdata);
void uninstall_nsis_if_required();
void show_release_notes_if_required();
void show_first_run_dialog();
bool first_run_dialog_callback(const LLSD& notification, const LLSD& response);
@@ -921,6 +927,7 @@ bool idle_startup()
LL_DEBUGS("AppInit") << "PeekMessage processed" << LL_ENDL;
#endif
do_startup_frame();
uninstall_nsis_if_required();
timeout.reset();
return false;
}
@@ -2605,6 +2612,67 @@ void release_notes_coro(const std::string url)
LLWeb::loadURLInternal(url);
}
/**
* Check if this is a fresh velopack install and
* if uninstallation of old viewer is needed.
*/
void uninstall_nsis_if_required()
{
#if LL_VELOPACK && LL_WINDOWS
bool checked_for_legacy_install = gSavedSettings.getBOOL("PreviousInstallChecked");
if (checked_for_legacy_install)
{
return;
}
gSavedSettings.setBOOL("PreviousInstallChecked", true);
LL_INFOS() << "Looking for previous NSIS installs" << LL_ENDL;
S32 found_major = 0;
S32 found_minor = 0;
S32 found_patch = 0;
U64 found_build = 0;
if (!get_nsis_version(found_major, found_minor, found_patch, found_build))
{
return;
}
LLVersionInfo* ver_inst = LLVersionInfo::getInstance();
if (found_major > ver_inst->getMajor())
{
LL_INFOS() << "Found installed nsis version that is newer" << found_major << "." << found_minor << "." << found_patch << "." << found_build << LL_ENDL;
return;
}
if (found_major == ver_inst->getMajor()
&& found_minor > ver_inst->getMinor())
{
LL_INFOS() << "Found installed nsis version that is newer" << found_major << "." << found_minor << "." << found_patch << "." << found_build << LL_ENDL;
return;
}
if (found_major == ver_inst->getMajor()
&& found_minor == ver_inst->getMinor()
&& found_patch > ver_inst->getPatch())
{
LL_INFOS() << "Found installed nsis version that is newer" << found_major << "." << found_minor << "." << found_patch << "." << found_build << LL_ENDL;
return;
}
// Assume that nsis is going to be something like x.x.x, while velopack is x.x.(x+1),
// so there is no point to check build.
LL_INFOS() << "Found NSIS install " << found_major << "." << found_minor << "." << found_patch << "." << found_build << LL_ENDL;
clear_nsis_links();
LLSD args;
args["VERSION"] = llformat("%d.%d.%d", found_major, found_minor, found_patch);
LLNotificationsUtil::add("FoundLegacyNsisInstallation", args);
#endif
}
void validate_release_notes_coro(const std::string url)
{
LLVersionInfo& versionInfo(LLVersionInfo::instance());
@@ -2638,15 +2706,24 @@ void show_release_notes_if_required()
// below. If viewer release notes stop working, might be because that
// LLEventMailDrop got moved out of LLVersionInfo and hasn't yet been
// instantiated.
if (!release_notes_shown && (LLVersionInfo::instance().getChannelAndVersion() != gLastRunVersion)
&& LLVersionInfo::instance().getViewerMaturity() != LLVersionInfo::TEST_VIEWER // don't show Release Notes for the test builds
&& gSavedSettings.getBOOL("UpdaterShowReleaseNotes")
&& !gSavedSettings.getBOOL("FirstLoginThisInstall"))
if (release_notes_shown
|| LLVersionInfo::instance().getChannelAndVersion() == gLastRunVersion
|| gSavedSettings.getBOOL("FirstLoginThisInstall")) // New users don't need to see release notes
{
return;
}
S32 mode = gSavedSettings.getS32("UpdaterShowReleaseNotes");
if (mode == 0)
{
return;
}
if (mode == 2 // Show even for test builds
|| LLVersionInfo::instance().getViewerMaturity() != LLVersionInfo::TEST_VIEWER) // don't show Release Notes for the test builds
{
#if LL_RELEASE_FOR_DOWNLOAD
if (!gSavedSettings.getBOOL("CmdLineSkipUpdater")
&& !LLAppViewer::instance()->isUpdaterMissing())
if (!gSavedSettings.getBOOL("CmdLineSkipUpdater"))
{
// Instantiate a "relnotes" listener which assumes any arriving event
// is the release notes URL string. Since "relnotes" is an
File diff suppressed because it is too large Load Diff
+59
View File
@@ -0,0 +1,59 @@
/**
* @file llvelopack.h
* @brief Velopack installer and update framework integration
*
* $LicenseInfo:firstyear=2025&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2025, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef LL_LLVELOPACK_H
#define LL_LLVELOPACK_H
#if LL_VELOPACK
#include <string>
#include <functional>
bool velopack_initialize();
void velopack_check_for_updates(const std::string& required_version, const std::string& relnotes_url);
std::string velopack_get_current_version();
bool velopack_is_update_pending();
bool velopack_is_required_update_in_progress();
std::string velopack_get_required_update_version();
bool velopack_should_restart_after_update();
void velopack_request_restart_after_update();
void velopack_apply_pending_update(bool restart = true);
void velopack_set_update_url(const std::string& url);
void velopack_set_progress_callback(std::function<void(int)> callback);
void velopack_cleanup();
#if LL_WINDOWS
void clear_nsis_links();
bool get_nsis_version(
int& nsis_major,
int& nsis_minor,
int& nsis_patch,
uint64_t& nsis_build);
#endif
#endif // LL_VELOPACK
#endif
// EOF
+1 -1
View File
@@ -54,7 +54,7 @@ LLVersionInfo::LLVersionInfo():
mWorkingChannelName(LL_TO_STRING(LL_VIEWER_CHANNEL)),
build_configuration(LLBUILD_CONFIG), // set in indra/cmake/BuildVersion.cmake
// instantiate an LLEventMailDrop with canonical name to listen for news
// from SLVersionChecker
// from the Viewer Version Manager
mPump{new LLEventMailDrop("relnotes")},
// immediately listen on mPump, store arriving URL into mReleaseNotes
mStore{new LLStoreListener<std::string>(*mPump, mReleaseNotes)}
+2 -2
View File
@@ -112,8 +112,8 @@ private:
std::string mReleaseNotes;
// Store unique_ptrs to the next couple things so we don't have to explain
// to every consumer of this header file all the details of each.
// mPump is the LLEventMailDrop on which we listen for SLVersionChecker to
// post the release-notes URL from the Viewer Version Manager.
// mPump is the LLEventMailDrop on which we listen for the
// release-notes URL from the Viewer Version Manager.
std::unique_ptr<LLEventMailDrop> mPump;
// mStore is an adapter that stores the release-notes URL in mReleaseNotes.
std::unique_ptr<LLStoreListener<std::string>> mStore;
+8
View File
@@ -575,6 +575,7 @@ std::string LLGridManager::getGridLoginID()
std::string LLGridManager::getUpdateServiceURL()
{
auto env_update_service = LLStringUtil::getoptenv("SL_UPDATE_SERVICE");
std::string update_url_base = gSavedSettings.getString("CmdLineUpdateService");;
if ( !update_url_base.empty() )
{
@@ -582,6 +583,13 @@ std::string LLGridManager::getUpdateServiceURL()
<< "Update URL base overridden from command line: " << update_url_base
<< LL_ENDL;
}
else if (env_update_service && env_update_service->find("http") != std::string::npos)
{
update_url_base = *env_update_service;
LL_INFOS("UpdaterService", "GridManager")
<< "Update URL base overridden from SL_UPDATE_SERVICE environment variable: " << update_url_base
<< LL_ENDL;
}
else if ( mGridList[mGrid].has(GRID_UPDATE_SERVICE_URL) )
{
update_url_base = mGridList[mGrid][GRID_UPDATE_SERVICE_URL].asString();
+5 -1
View File
@@ -783,7 +783,11 @@ void send_viewer_stats(bool include_preferences)
fail["failed_resends"] = (S32) gMessageSystem->mFailedResendPackets;
fail["off_circuit"] = (S32) gMessageSystem->mOffCircuitPackets;
fail["invalid"] = (S32) gMessageSystem->mInvalidOnCircuitPackets;
fail["missing_updater"] = (S32) LLAppViewer::instance()->isUpdaterMissing();
#if LL_VELOPACK
fail["missing_updater"] = false;
#else
fail["missing_updater"] = true;
#endif
LLSD &inventory = body["inventory"];
inventory["usable"] = gInventory.isInventoryUsable();
+189
View File
@@ -0,0 +1,189 @@
/**
* @file llvvmquery.cpp
* @brief Query the Viewer Version Manager (VVM) for update information
*
* $LicenseInfo:firstyear=2025&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2025, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include "llvvmquery.h"
#include "llcorehttputil.h"
#include "llcoros.h"
#include "llevents.h"
#include "llviewernetwork.h"
#include "llversioninfo.h"
#include "llviewercontrol.h"
#include "llhasheduniqueid.h"
#include "lluri.h"
#include "llsys.h"
#if LL_VELOPACK
#include "llvelopack.h"
#endif
namespace
{
std::string get_platform_string()
{
#if LL_WINDOWS
return "win64";
#elif LL_DARWIN
return "mac64";
#elif LL_LINUX
return "lnx64";
#else
return "unknown";
#endif
}
std::string get_platform_version()
{
return LLOSInfo::instance().getOSVersionString();
}
std::string get_machine_id()
{
unsigned char id[MD5HEX_STR_SIZE];
if (llHashedUniqueID(id))
{
return std::string(reinterpret_cast<char*>(id));
}
return "unknown";
}
void query_vvm_coro()
{
// Get base URL from grid manager
std::string base_url = LLGridManager::getInstance()->getUpdateServiceURL();
// We use this for dev testing when working with VVM and working on the updater. Not advisable to uncomment it.
//std::string base_url = "https://update.qa.secondlife.io/update";
if (base_url.empty())
{
LL_WARNS("VVM") << "No update service URL configured" << LL_ENDL;
return;
}
// Gather parameters for VVM query
std::string channel = LLVersionInfo::instance().getChannel();
// We use this for dev testing when working with VVM and working on the updater. Not advisable to uncomment it.
// std::string channel = "QA Target for Velopack";
std::string version = LLVersionInfo::instance().getVersion();
std::string platform = get_platform_string();
std::string platform_version = get_platform_version();
std::string test_ok = gSavedSettings.getBOOL("UpdaterWillingToTest") ? "testok" : "testno";
std::string machine_id = get_machine_id();
// Build URL: {base}/v1.2/{channel}/{version}/{platform}/{platform_version}/{testok}/{uuid}
std::string url = base_url + "/v1.2/" +
LLURI::escape(channel) + "/" +
LLURI::escape(version) + "/" +
platform + "/" +
LLURI::escape(platform_version) + "/" +
test_ok + "/" +
machine_id;
LL_INFOS("VVM") << "Querying VVM: " << url << LL_ENDL;
// Make HTTP GET request
LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter =
std::make_shared<LLCoreHttpUtil::HttpCoroutineAdapter>("VVMQuery", httpPolicy);
LLCore::HttpRequest::ptr_t request = std::make_shared<LLCore::HttpRequest>();
LLSD result = adapter->getAndSuspend(request, url);
// Check HTTP status
LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
if (!status)
{
if (status.getType() == 404)
{
LL_INFOS("VVM") << "Unmanaged channel, no updates available" << LL_ENDL;
return;
}
LL_WARNS("VVM") << "VVM query failed: " << status.toString() << LL_ENDL;
return;
}
// Read whether this update is required or optional
bool update_required = result["required"].asBoolean();
std::string relnotes = result["more_info"].asString();
// Extract update URL for current platform
LLSD platforms = result["platforms"];
if (platforms.has(platform))
{
std::string update_url = platforms[platform]["url"].asString();
#if LL_VELOPACK
std::string velopack_url = platforms[platform]["velopack_url"].asString();
U32 updater_service = gSavedSettings.getU32("UpdaterServiceSetting");
std::string required_version = update_required ? result["version"].asString() : "";
// Skip network check if no required version AND user only wants mandatory updates
if (!velopack_url.empty() && (update_required || updater_service != 0))
{
LL_INFOS("VVM") << "Velopack feed URL: " << velopack_url
<< " required_version: " << required_version << LL_ENDL;
velopack_set_update_url(velopack_url);
LLCoros::instance().launch("VelopackUpdateCheck",
[required_version, relnotes]()
{
velopack_check_for_updates(required_version, relnotes);
});
}
else if (!velopack_url.empty())
{
LL_INFOS("VVM") << "Optional update skipped (UpdaterServiceSetting=0)" << LL_ENDL;
}
else
#endif
if (!update_url.empty())
{
LL_INFOS("VVM") << "Update available at: " << update_url << LL_ENDL;
}
}
else
{
LL_INFOS("VVM") << "No update available for platform: " << platform << LL_ENDL;
}
// Post release notes URL to the relnotes event pump
if (!relnotes.empty())
{
LL_INFOS("VVM") << "Release notes URL: " << relnotes << LL_ENDL;
LLEventPumps::instance().obtain("relnotes").post(relnotes);
}
}
}
void initVVMUpdateCheck()
{
LL_INFOS("VVM") << "Initializing VVM update check" << LL_ENDL;
LLCoros::instance().launch("VVMUpdateCheck", &query_vvm_coro);
}
+42
View File
@@ -0,0 +1,42 @@
/**
* @file llvvmquery.h
* @brief Query the Viewer Version Manager (VVM) for update information
*
* $LicenseInfo:firstyear=2025&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2025, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef LL_LLVVMQUERY_H
#define LL_LLVVMQUERY_H
/**
* Initialize the VVM update check.
*
* This launches a coroutine that queries the Viewer Version Manager (VVM)
* to check for available updates. If an update is available, it configures
* Velopack with the update URL and initiates the update check/download.
*
* The release notes URL from the VVM response is posted to the "relnotes"
* event pump for display.
*/
void initVVMUpdateCheck();
#endif // LL_LLVVMQUERY_H
@@ -200,6 +200,16 @@ No tutorial is currently available.
yestext="OK"/>
</notification>
<notification
icon="alertmodal.tga"
name="FoundLegacyNsisInstallation"
type="alertmodal">
[APP_NAME] found an installation of an older version [VERSION]. To uninstall the older version, please follow [https://community.secondlife.com/knowledgebase/english/how-to-uninstall-and-reinstall-second-life-r524 this manual].
<usetemplate
name="okbutton"
yestext="OK"/>
</notification>
<notification
icon="alertmodal.tga"
name="LoginFailedNoNetwork"
@@ -4421,6 +4431,14 @@ Click OK to download and install.
yestext="OK"/>
</notification>
<notification
icon="alertmodal.tga"
name="DownloadingUpdate"
type="alertmodal">
Downloading update [VERSION]...
The viewer will restart once the download is complete.
</notification>
<notification
icon="alertmodal.tga"
name="OptionalUpdateReady"
@@ -223,8 +223,6 @@ bool llHashedUniqueID(unsigned char* id)
//-----------------------------------------------------------------------------
#include "../llappviewer.h"
void LLAppViewer::forceQuit(void) {}
bool LLAppViewer::isUpdaterMissing() { return true; }
bool LLAppViewer::waitForUpdater() { return false; }
LLAppViewer * LLAppViewer::sInstance = 0;
//-----------------------------------------------------------------------------
+239 -22
View File
@@ -540,18 +540,6 @@ class Windows_x86_64_Manifest(ViewerManifest):
'*.bat',
'*.tar.xz')))
with self.prefix(src=os.path.join(pkgdir, "VMP")):
# include the compiled launcher scripts so that it gets included in the file_list
self.path('SLVersionChecker.exe')
with self.prefix(dst="vmp_icons"):
with self.prefix(src=self.icon_path()):
self.path("secondlife.ico")
#VMP Tkinter icons
with self.prefix(src="vmp_icons"):
self.path("*.png")
self.path("*.gif")
# Plugin host application
self.path2basename(os.path.join(os.pardir,
'llplugin', 'slplugin', self.args['configuration']),
@@ -765,6 +753,126 @@ class Windows_x86_64_Manifest(ViewerManifest):
return '\n'.join(result)
def package_finish(self):
# Check if we should use Velopack instead of NSIS
# Note: as of 2026.01's release, we will be building with Velopack's one click install.
# We maintain the legacy NSIS packaging mainly for TPVs at this point.
if self.args.get('velopack', 'OFF') == 'ON':
self.velopack_package_finish()
return
# NSIS packaging (legacy)
self.nsis_package_finish()
def velopack_package_finish(self):
# packId determines install folder: %LocalAppData%\{packId}
# Uses same naming as NSIS INSTNAME for channel separation
pack_id = self.app_name_oneword() # "SecondLife", "SecondLifeBeta", etc.
# Velopack requires SemVer2. Use major.minor.patch-buildnumber so that
# Velopack can distinguish builds and order them correctly.
pack_version = '.'.join(self.args['version'][:3])
if len(self.args['version']) > 3 and self.args['version'][3]:
pack_version += '-' + self.args['version'][3]
pack_title = self.app_name() # Display name with spaces
pack_dir = self.get_dst_prefix()
main_exe = self.final_exe()
installer_base = self.installer_base_name()
exclude_pattern = r'.*\.pdb|.*\.map|.*\.bat|.*\.exp|.*\.lib|.*\.nsi|.*\.tar\.xz|secondlife-bin\..*|.*_Setup\.exe|.*-Setup\.exe'
# Channel-specific icon for the Velopack installer.
# CMake copies icons/{channel}/secondlife.ico to res/ll_icon.ico at configure time.
# Try the CMake-generated copy first, fall back to the source icon.
icon_path = os.path.join(self.get_src_prefix(), 'res', 'll_icon.ico')
if not os.path.exists(icon_path):
icon_path = os.path.join(self.get_src_prefix(), self.icon_path(), 'secondlife.ico')
# In CI, defer Velopack packaging to the sign step where Azure credentials
# are available. Emit metadata as GitHub outputs so the sign step can run
# vpk pack with --signTemplate, producing a package with signed executables.
if os.getenv('GITHUB_ACTIONS'):
# Copy the icon into pack_dir so it's included in the Windows-app artifact
icon_filename = ''
if os.path.exists(icon_path):
icon_filename = os.path.basename(icon_path)
icon_dest = os.path.join(pack_dir, icon_filename)
shutil.copy2(icon_path, icon_dest)
print("Copied icon %s to %s" % (icon_path, icon_dest))
else:
print("WARNING: Icon not found at %s" % icon_path)
# Emit metadata for the sign step
self.set_github_output('velopack_pack_id', pack_id)
self.set_github_output('velopack_pack_version', pack_version)
self.set_github_output('velopack_pack_title', pack_title)
self.set_github_output('velopack_main_exe', main_exe)
self.set_github_output('velopack_icon', icon_filename)
self.set_github_output('velopack_installer_base', installer_base)
self.set_github_output('velopack_exclude', exclude_pattern)
# Set package_file so llmanifest's touched.bat logic doesn't crash
self.package_file = installer_base + '_Setup.exe'
print("CI mode: Velopack packaging deferred to sign step")
return
# Local builds: run vpk pack directly (unsigned)
vpk_args = [
'vpk', 'pack',
'--packId', pack_id,
'--packVersion', pack_version,
'--packDir', pack_dir,
'--mainExe', main_exe,
'--packTitle', pack_title,
'--exclude', exclude_pattern,
# Suppress Velopack's built-in shortcut creation; we create our own
# shortcuts in llvelopack.cpp on_after_install hook instead.
'--shortcuts', '',
]
# Add icon — CMake copies the channel-appropriate secondlife.ico to res/ll_icon.ico
if os.path.exists(icon_path):
print("Using icon: %s" % icon_path)
vpk_args.extend(['--icon', icon_path])
else:
print("WARNING: Icon not found at %s — Setup.exe will have no icon" % icon_path)
print("Running Velopack packaging: %s" % ' '.join(vpk_args))
# Run vpk command
import subprocess
result = subprocess.run(vpk_args, cwd=os.path.dirname(pack_dir), capture_output=True, text=True)
if result.stdout:
print("vpk stdout: %s" % result.stdout)
if result.stderr:
print("vpk stderr: %s" % result.stderr)
if result.returncode != 0:
raise ManifestError("Velopack packaging failed with code %d" % result.returncode)
# Velopack outputs to a Releases directory
releases_dir = os.path.join(os.path.dirname(pack_dir), 'Releases')
# Move the setup exe INTO pack_dir so it's included in the Windows-app artifact
# IMPORTANT: Use hyphen format (-Setup.exe) to avoid the *_Setup.exe exclusion pattern
# in viewer_app output (line ~538). The underscore pattern excludes NSIS installers
# which are rebuilt during signing, but Velopack installers are created here.
# Velopack creates: {packId}-win-Setup.exe
velopack_setup = os.path.join(releases_dir, '%s-win-Setup.exe' % pack_id)
self.package_file = installer_base + '_Setup.exe'
our_setup = os.path.join(pack_dir, self.package_file)
if os.path.exists(velopack_setup):
shutil.move(velopack_setup, our_setup)
print("Moved %s to %s" % (velopack_setup, our_setup))
# Rename the portable zip to include the version number
# Velopack creates: {packId}-win-Portable.zip
velopack_portable = os.path.join(releases_dir, '%s-win-Portable.zip' % pack_id)
if os.path.exists(velopack_portable):
our_portable = os.path.join(releases_dir, installer_base + '_Portable.zip')
shutil.move(velopack_portable, our_portable)
print("Moved %s to %s" % (velopack_portable, our_portable))
# Output the Releases directory path for artifact upload (contains nupkg, RELEASES for updates)
self.set_github_output('velopack_releases', releases_dir)
def nsis_package_finish(self):
"""Package the viewer using NSIS installer (legacy)"""
# a standard map of strings for replacing in the templates
substitution_strings = {
'version' : '.'.join(self.args['version']),
@@ -781,7 +889,7 @@ class Windows_x86_64_Manifest(ViewerManifest):
substitution_strings['installer_file'] = installer_file
version_vars = """
!define INSTEXE "SLVersionChecker.exe"
!define INSTEXE "%(final_exe)s"
!define VERSION "%(version_short)s"
!define VERSION_LONG "%(version)s"
!define VERSION_DASHES "%(version_dashes)s"
@@ -967,15 +1075,6 @@ class Darwin_x86_64_Manifest(ViewerManifest):
with self.prefix(src=self.icon_path(), dst="") :
self.path("secondlife.icns")
# Copy in the updater script and helper modules
self.path(src=os.path.join(pkgdir, 'VMP'), dst="updater")
with self.prefix(src="", dst=os.path.join("updater", "icons")):
self.path2basename(self.icon_path(), "secondlife.ico")
with self.prefix(src="vmp_icons", dst=""):
self.path("*.png")
self.path("*.gif")
with self.prefix(src_dst="cursors_mac"):
self.path("*.tif")
@@ -1127,6 +1226,123 @@ class Darwin_x86_64_Manifest(ViewerManifest):
arcname=self.app_name() + ".app")
self.set_github_output_path('viewer_app', tarpath)
# Generate Velopack update packages if enabled
# This creates the nupkg and RELEASES files needed for auto-updates
# Distribution is still via DMG, but updates use Velopack
if self.args.get('velopack', 'OFF') == 'ON':
self.velopack_package_finish()
def velopack_package_finish(self):
"""Generate Velopack update packages for macOS.
This creates the nupkg and releases.json files needed for auto-updates.
Distribution is still via DMG - Velopack only handles the update infrastructure.
"""
# packId determines install identification - same as Windows for consistency
pack_id = self.app_name_oneword() # "SecondLife", "SecondLifeBeta", etc.
# Velopack requires SemVer2. Use major.minor.patch-buildnumber so that
# Velopack can distinguish builds and order them correctly.
pack_version = '.'.join(self.args['version'][:3])
if len(self.args['version']) > 3 and self.args['version'][3]:
pack_version += '-' + self.args['version'][3]
pack_title = self.app_name() # Display name with spaces
# The .app bundle path (e.g., "/path/to/Second Life Release.app")
app_bundle = self.get_dst_prefix()
# Bundle ID from args (e.g., "com.secondlife.viewer")
bundle_id = self.args.get('bundleid', 'com.secondlife.indra.viewer')
# Icon path for macOS
icon_path = os.path.join(self.get_src_prefix(), self.icon_path(), 'secondlife.icns')
# The main executable inside Contents/MacOS/ is named after the channel
main_exe = self.channel()
# In CI, defer Velopack packaging to the sign step where code signing
# credentials are available. Emit metadata as GitHub outputs so the
# sign step can run vpk pack after signing the app bundle.
if os.getenv('GITHUB_ACTIONS'):
self.set_github_output('velopack_mac_pack_id', pack_id)
self.set_github_output('velopack_mac_pack_version', pack_version)
self.set_github_output('velopack_mac_pack_title', pack_title)
self.set_github_output('velopack_mac_main_exe', main_exe)
self.set_github_output('velopack_mac_bundle_id', bundle_id)
print("CI mode: macOS Velopack packaging deferred to sign step")
return
# Local builds: run vpk pack directly (unsigned)
# Parent directory containing the .app bundle - this is where we run vpk from
# and where the Releases directory will be created
work_dir = os.path.dirname(app_bundle)
# Output directory for releases - clean it first to avoid version conflicts
releases_dir = os.path.join(work_dir, 'Releases')
if os.path.exists(releases_dir):
print("Cleaning existing Releases directory: %s" % releases_dir)
shutil.rmtree(releases_dir)
# Build vpk command for macOS
# See: https://docs.velopack.io/reference/cli/content/vpk-osx
vpk_args = [
'vpk', 'pack',
'--packId', pack_id,
'--packVersion', pack_version,
'--packDir', app_bundle,
'--packTitle', pack_title,
'--mainExe', main_exe, # Executable name inside Contents/MacOS/
'--bundleId', bundle_id,
'--outputDir', releases_dir,
'--noInst', # Don't generate .pkg installer - we use DMG for distribution
'--verbose', # Show detailed output
]
# Add icon if exists
if os.path.exists(icon_path):
vpk_args.extend(['--icon', icon_path])
print("Running Velopack packaging for macOS:")
print(" Command: %s" % ' '.join(vpk_args))
print(" Working directory: %s" % work_dir)
print(" App bundle: %s" % app_bundle)
print(" Main executable: %s" % main_exe)
# Run vpk command
result = subprocess.run(vpk_args, cwd=work_dir, capture_output=True, text=True)
# Always print output for debugging
if result.stdout:
print("vpk stdout:\n%s" % result.stdout)
if result.stderr:
print("vpk stderr:\n%s" % result.stderr)
if result.returncode != 0:
raise ManifestError("Velopack packaging failed with code %d" % result.returncode)
# Verify the Releases directory was created and contains expected files
if not os.path.exists(releases_dir):
raise ManifestError("Velopack releases directory not found: %s" % releases_dir)
# List what was created
releases_contents = os.listdir(releases_dir)
print("Velopack releases directory contents: %s" % releases_contents)
# Verify we have the expected files (nupkg and releases JSON)
nupkg_files = [f for f in releases_contents if f.endswith('.nupkg')]
json_files = [f for f in releases_contents if f.endswith('.json')]
if not nupkg_files:
raise ManifestError("No .nupkg files found in releases directory")
if not json_files:
raise ManifestError("No releases JSON files found in releases directory")
print("Generated %d nupkg file(s): %s" % (len(nupkg_files), nupkg_files))
print("Generated %d JSON file(s): %s" % (len(json_files), json_files))
# Output the Releases directory path for artifact upload
self.set_github_output('velopack_releases', releases_dir)
print("Velopack releases directory: %s" % releases_dir)
class LinuxManifest(ViewerManifest):
build_data_json_platform = 'lnx'
@@ -1324,6 +1540,7 @@ if __name__ == "__main__":
dict(name='discord', description="""Indication discord social sdk libraries are needed""", default='OFF'),
dict(name='openal', description="""Indication openal libraries are needed""", default='OFF'),
dict(name='tracy', description="""Indication tracy profiler is enabled""", default='OFF'),
dict(name='velopack', description="""Use Velopack installer instead of NSIS""", default='OFF'),
]
try:
main(extra=extra_arguments)
-62
View File
@@ -34,7 +34,6 @@
#include "llcoros.h"
#include "llevents.h"
#include "lleventfilter.h"
#include "lleventcoro.h"
#include "llexception.h"
#include "stringize.h"
@@ -133,16 +132,6 @@ void LLLogin::Impl::connect(const std::string& uri, const LLSD& login_params)
LL_DEBUGS("LLLogin") << " connected with uri '" << uri << "', login_params " << login_params << LL_ENDL;
}
namespace
{
// Instantiate this rendezvous point at namespace scope so it's already
// present no matter how early the updater might post to it.
// Use an LLEventMailDrop, which has future-like semantics: regardless of the
// relative order in which post() or listen() are called, it delivers each
// post() event to its listener(s) until one of them consumes that event.
static LLEventMailDrop sSyncPoint("LoginSync");
}
void LLLogin::Impl::loginCoro(std::string uri, LLSD login_params)
{
LLSD printable_params = hidePasswd(login_params);
@@ -225,58 +214,7 @@ void LLLogin::Impl::loginCoro(std::string uri, LLSD login_params)
}
else
{
// Synchronize here with the updater. We synchronize here rather
// than in the fail.login handler, which actually examines the
// response from login.cgi, because here we are definitely in a
// coroutine and can definitely use suspendUntilBlah(). Whoever's
// listening for fail.login might not be.
// If the reason for login failure is that we must install a
// required update, we definitely want to pass control to the
// updater to manage that for us. We'll handle any other login
// failure ourselves, as usual. We figure that no matter where you
// are in the world, or what kind of network you're on, we can
// reasonably expect the Viewer Version Manager to respond more or
// less as quickly as login.cgi. This synchronization is only
// intended to smooth out minor races between the two services.
// But what if the updater crashes? Use a timeout so that
// eventually we'll tire of waiting for it and carry on as usual.
// Given the above, it can be a fairly short timeout, at least
// from a human point of view.
// Since sSyncPoint is an LLEventMailDrop, we DEFINITELY want to
// consume the posted event.
LLCoros::OverrideConsuming oc(true);
LLSD responses(mAuthResponse["responses"]);
LLSD updater;
if (printable_params["wait_for_updater"].asBoolean())
{
std::string reason_response = responses["data"]["reason"].asString();
// Timeout should produce the isUndefined() object passed here.
if (reason_response == "update")
{
LL_INFOS("LLLogin") << "Login failure, waiting for sync from updater" << LL_ENDL;
updater = llcoro::suspendUntilEventOnWithTimeout(sSyncPoint, 10, LLSD());
}
else
{
LL_DEBUGS("LLLogin") << "Login failure, waiting for sync from updater" << LL_ENDL;
updater = llcoro::suspendUntilEventOnWithTimeout(sSyncPoint, 3, LLSD());
}
if (updater.isUndefined())
{
LL_WARNS("LLLogin") << "Failed to hear from updater, proceeding with fail.login"
<< LL_ENDL;
}
else
{
LL_DEBUGS("LLLogin") << "Got responses from updater and login.cgi" << LL_ENDL;
}
}
// Let the fail.login handler deal with empty updater response.
responses["updater"] = updater;
sendProgressEvent("offline", "fail.login", responses);
}
return; // Done!