2026-04-23 00:27:01 +08:00
using System.Globalization ;
2026-04-21 20:59:52 +08:00
using System.Text.Json ;
using LanMountainDesktop.Launcher.Models ;
using LanMountainDesktop.Shared.Contracts.Launcher ;
namespace LanMountainDesktop.Launcher.Services ;
internal sealed class DeploymentLocator
{
private readonly string _appRoot ;
public DeploymentLocator ( string appRoot )
{
_appRoot = appRoot ;
}
public string GetAppRoot ( ) = > _appRoot ;
public string? FindCurrentDeploymentDirectory ( )
{
Console . WriteLine ( "[DeploymentLocator] Searching for deployment directories (ClassIsland style)..." ) ;
if ( ! Directory . Exists ( _appRoot ) )
{
Console . WriteLine ( "[DeploymentLocator] App root directory does not exist" ) ;
return null ;
}
var executable = OperatingSystem . IsWindows ( ) ? "LanMountainDesktop.exe" : "LanMountainDesktop" ;
try
{
var candidates = Directory . GetDirectories ( _appRoot , "app-*" , SearchOption . TopDirectoryOnly ) ;
Console . WriteLine ( $"[DeploymentLocator] Found {candidates.Length} app-* directories" ) ;
var validInstallations = candidates
. Where ( path = >
{
var hasDestroy = File . Exists ( Path . Combine ( path , ".destroy" ) ) ;
var hasPartial = File . Exists ( Path . Combine ( path , ".partial" ) ) ;
var hasExe = File . Exists ( Path . Combine ( path , executable ) ) ;
var hasCurrent = File . Exists ( Path . Combine ( path , ".current" ) ) ;
var version = ParseVersionFromDirectory ( path ) ;
Console . WriteLine ( $"[DeploymentLocator] Candidate: {Path.GetFileName(path)} | " +
$"Version={version} | " +
$"Current={hasCurrent} | " +
$"Destroy={hasDestroy} | " +
$"Partial={hasPartial} | " +
$"HasExe={hasExe}" ) ;
return ! hasDestroy & & ! hasPartial & & hasExe ;
} )
. Select ( path = > new
{
Path = path ,
Version = ParseVersionFromDirectory ( path ) ,
HasCurrentMarker = File . Exists ( Path . Combine ( path , ".current" ) )
} )
2026-04-23 00:27:01 +08:00
. OrderBy ( x = > x . HasCurrentMarker ? 0 : 1 ) // .current 鏍囪鐨勬帓鍓嶉潰
. ThenByDescending ( x = > x . Version ) // 鐒跺悗鎸夌増鏈彿闄嶅簭
2026-04-21 20:59:52 +08:00
. ToList ( ) ;
if ( validInstallations . Count = = 0 )
{
Console . WriteLine ( "[DeploymentLocator] No valid deployment directories found" ) ;
return null ;
}
var best = validInstallations [ 0 ] ;
Console . WriteLine ( $"[DeploymentLocator] Selected: {Path.GetFileName(best.Path)} (current={best.HasCurrentMarker}, version={best.Version})" ) ;
return best . Path ;
}
catch ( Exception ex )
{
Console . Error . WriteLine ( $"[DeploymentLocator] Error searching for deployments: {ex}" ) ;
return null ;
}
}
2026-04-22 07:31:54 +08:00
public HostResolutionResult ResolveHostExecutable ( CommandContext context )
2026-04-21 20:59:52 +08:00
{
2026-04-22 07:31:54 +08:00
ArgumentNullException . ThrowIfNull ( context ) ;
var executable = OperatingSystem . IsWindows ( ) ? "LanMountainDesktop.exe" : "LanMountainDesktop" ;
var searchedPaths = new List < string > ( ) ;
var explicitAppRoot = context . ExplicitAppRoot ;
var devModeConfigIgnored = ! context . IsDebugMode & & Views . ErrorWindow . CheckDevModeEnabled ( ) ;
string? resolvedPath ;
string? source ;
if ( ! string . IsNullOrWhiteSpace ( explicitAppRoot ) )
{
var explicitRoot = Path . GetFullPath ( explicitAppRoot ) ;
resolvedPath = TryResolveExplicitAppRoot ( explicitRoot , executable , searchedPaths , out source ) ;
}
else
{
resolvedPath = TryResolvePublishedOrPortableHost ( executable , searchedPaths , out source ) ;
}
if ( resolvedPath is null & & context . IsDebugMode )
2026-04-21 20:59:52 +08:00
{
2026-04-22 07:31:54 +08:00
resolvedPath = TryResolveDebugHost ( executable , searchedPaths , out source ) ;
}
if ( resolvedPath is null )
{
resolvedPath = ResolveHostExecutablePathLegacy ( ) ;
if ( ! string . IsNullOrWhiteSpace ( resolvedPath ) )
2026-04-21 20:59:52 +08:00
{
2026-04-22 07:31:54 +08:00
searchedPaths . Add ( Path . GetFullPath ( resolvedPath ) ) ;
source = "legacy_fallback" ;
2026-04-21 20:59:52 +08:00
}
2026-04-22 07:31:54 +08:00
}
return new HostResolutionResult
{
Success = ! string . IsNullOrWhiteSpace ( resolvedPath ) ,
ResolvedHostPath = resolvedPath ,
ResolutionSource = source ,
AppRoot = _appRoot ,
ExplicitAppRoot = explicitAppRoot ,
DevModeConfigIgnored = devModeConfigIgnored ,
SearchedPaths = searchedPaths
. Where ( path = > ! string . IsNullOrWhiteSpace ( path ) )
. Distinct ( StringComparer . OrdinalIgnoreCase )
. ToList ( )
2026-04-21 20:59:52 +08:00
} ;
2026-04-22 07:31:54 +08:00
}
2026-04-21 20:59:52 +08:00
2026-04-22 07:31:54 +08:00
public string? ResolveHostExecutablePath ( )
{
return ResolveHostExecutablePathLegacy ( ) ;
}
private string? TryResolveExplicitAppRoot (
string explicitRoot ,
string executable ,
List < string > searchedPaths ,
out string? source )
{
var directPath = Path . Combine ( explicitRoot , executable ) ;
searchedPaths . Add ( directPath ) ;
if ( File . Exists ( directPath ) )
2026-04-21 20:59:52 +08:00
{
2026-04-22 07:31:54 +08:00
source = "explicit_app_root_direct" ;
return directPath ;
2026-04-21 20:59:52 +08:00
}
2026-04-22 07:31:54 +08:00
var deployment = FindBestDeploymentHost ( explicitRoot , executable , searchedPaths ) ;
if ( deployment is not null )
{
source = "explicit_app_root_deployment" ;
return deployment ;
}
source = null ;
return null ;
}
private string? TryResolvePublishedOrPortableHost (
string executable ,
List < string > searchedPaths ,
out string? source )
{
var deployment = FindBestDeploymentHost ( _appRoot , executable , searchedPaths ) ;
if ( deployment is not null )
{
source = "published_deployment" ;
return deployment ;
}
var portableCandidates = new [ ]
{
Path . Combine ( _appRoot , executable ) ,
Path . Combine ( AppContext . BaseDirectory , executable )
} ;
foreach ( var candidate in portableCandidates
. Select ( Path . GetFullPath )
. Distinct ( StringComparer . OrdinalIgnoreCase ) )
{
searchedPaths . Add ( candidate ) ;
if ( File . Exists ( candidate ) )
{
source = "portable_host" ;
return candidate ;
}
}
source = null ;
return null ;
}
private string? TryResolveDebugHost (
string executable ,
List < string > searchedPaths ,
out string? source )
{
if ( Views . ErrorWindow . CheckDevModeEnabled ( ) )
{
var savedCustomPath = Views . ErrorWindow . GetSavedCustomHostPath ( ) ;
if ( ! string . IsNullOrWhiteSpace ( savedCustomPath ) )
{
2026-04-23 19:04:39 +08:00
if ( TryNormalizeSavedDebugPath ( savedCustomPath , out var fullSavedPath ) )
2026-04-22 07:31:54 +08:00
{
2026-04-23 19:04:39 +08:00
searchedPaths . Add ( fullSavedPath ) ;
if ( File . Exists ( fullSavedPath ) )
{
source = "debug_saved_custom_path" ;
return fullSavedPath ;
}
Logger . Warn ( $"Saved launcher debug host path is invalid; falling back to development paths. Path='{fullSavedPath}'." ) ;
2026-04-22 07:31:54 +08:00
}
}
}
foreach ( var devPath in GetDevelopmentPaths ( executable ) )
{
var fullPath = Path . GetFullPath ( devPath ) ;
searchedPaths . Add ( fullPath ) ;
if ( File . Exists ( fullPath ) )
{
source = "debug_build_output" ;
return fullPath ;
}
}
source = null ;
return null ;
}
2026-04-23 19:04:39 +08:00
private static bool TryNormalizeSavedDebugPath ( string savedPath , out string fullSavedPath )
{
try
{
fullSavedPath = Path . GetFullPath ( savedPath ) ;
return true ;
}
catch ( Exception ex )
{
fullSavedPath = string . Empty ;
Logger . Warn ( $"Saved launcher debug host path is invalid and cannot be normalized; falling back to development paths. Path='{savedPath}'; Error='{ex.Message}'." ) ;
return false ;
}
}
2026-04-22 07:31:54 +08:00
private static string? FindBestDeploymentHost (
string root ,
string executable ,
List < string > searchedPaths )
{
if ( ! Directory . Exists ( root ) )
{
searchedPaths . Add ( Path . Combine ( root , "app-*" , executable ) ) ;
return null ;
}
var appDirs = Directory . GetDirectories ( root , "app-*" , SearchOption . TopDirectoryOnly )
. Where ( path = > ! File . Exists ( Path . Combine ( path , ".destroy" ) ) )
. Where ( path = > ! File . Exists ( Path . Combine ( path , ".partial" ) ) )
. Select ( path = > new
{
Path = path ,
HostPath = Path . Combine ( path , executable ) ,
HasCurrent = File . Exists ( Path . Combine ( path , ".current" ) ) ,
Version = ParseVersionFromDirectory ( path )
} )
. OrderByDescending ( item = > item . HasCurrent )
. ThenByDescending ( item = > item . Version )
. ToList ( ) ;
foreach ( var candidate in appDirs )
{
searchedPaths . Add ( candidate . HostPath ) ;
if ( File . Exists ( candidate . HostPath ) )
{
return candidate . HostPath ;
}
}
if ( appDirs . Count = = 0 )
{
searchedPaths . Add ( Path . Combine ( root , "app-*" , executable ) ) ;
}
return null ;
2026-04-21 20:59:52 +08:00
}
private string? ResolveHostExecutablePathLegacy ( )
{
var executable = OperatingSystem . IsWindows ( ) ? "LanMountainDesktop.exe" : "LanMountainDesktop" ;
2026-04-23 00:27:01 +08:00
// 1. 棣栧厛鏌ユ壘 app-{version} 鐩綍锛堢敓浜х幆澧冿級
2026-04-21 20:59:52 +08:00
var currentDeployment = FindCurrentDeploymentDirectory ( ) ;
if ( ! string . IsNullOrWhiteSpace ( currentDeployment ) )
{
var inDeployment = Path . Combine ( currentDeployment , executable ) ;
if ( File . Exists ( inDeployment ) )
{
return inDeployment ;
}
}
var inRoot = Path . Combine ( _appRoot , executable ) ;
if ( File . Exists ( inRoot ) )
{
return inRoot ;
}
var parent = Path . GetFullPath ( Path . Combine ( _appRoot , ".." ) ) ;
var inParent = Path . Combine ( parent , executable ) ;
if ( File . Exists ( inParent ) )
{
return inParent ;
}
2026-04-23 00:27:01 +08:00
// 4. 寮€鍙戞ā寮忥細濡傛灉鍚敤浜嗗紑鍙戞ā寮忥紝浼樺厛浣跨敤淇濆瓨鐨勮嚜瀹氫箟璺緞
2026-04-21 20:59:52 +08:00
if ( Views . ErrorWindow . CheckDevModeEnabled ( ) )
{
var savedCustomPath = Views . ErrorWindow . GetSavedCustomHostPath ( ) ;
2026-04-23 19:04:39 +08:00
if ( ! string . IsNullOrWhiteSpace ( savedCustomPath ) )
2026-04-21 20:59:52 +08:00
{
2026-04-23 19:04:39 +08:00
if ( TryNormalizeSavedDebugPath ( savedCustomPath , out var fullSavedPath ) & &
File . Exists ( fullSavedPath ) )
{
return fullSavedPath ;
}
else if ( ! string . IsNullOrWhiteSpace ( fullSavedPath ) )
{
Logger . Warn ( $"Saved launcher debug host path is invalid; falling back to development paths. Path='{fullSavedPath}'." ) ;
}
2026-04-21 20:59:52 +08:00
}
var devPath = ScanDevelopmentPaths ( executable ) ;
if ( ! string . IsNullOrWhiteSpace ( devPath ) )
{
return devPath ;
}
}
2026-04-23 00:27:01 +08:00
// 5. 寮€鍙戞ā寮忥細鏌ユ壘涓荤▼搴忛」鐩殑杈撳嚭鐩綍
2026-04-21 20:59:52 +08:00
var devPaths = GetDevelopmentPaths ( executable ) ;
foreach ( var devPath in devPaths )
{
if ( File . Exists ( devPath ) )
{
return devPath ;
}
}
return null ;
}
/// <summary>
2026-04-23 00:27:01 +08:00
/// 鎵弿寮€鍙戣矾寰勶紙寮€鍙戞ā寮忥級
2026-04-21 20:59:52 +08:00
/// </summary>
private static string? ScanDevelopmentPaths ( string executable )
{
var possiblePaths = new [ ]
{
2026-04-23 00:27:01 +08:00
// 浠?Launcher 椤圭洰杩愯
2026-04-21 20:59:52 +08:00
Path . Combine ( AppContext . BaseDirectory , ".." , ".." , "LanMountainDesktop" , "bin" , "Debug" , "net10.0" , executable ) ,
Path . Combine ( AppContext . BaseDirectory , ".." , ".." , "LanMountainDesktop" , "bin" , "Release" , "net10.0" , executable ) ,
2026-04-23 00:27:01 +08:00
// 浠庤В鍐虫柟妗堟牴鐩綍杩愯
2026-04-21 20:59:52 +08:00
Path . Combine ( AppContext . BaseDirectory , ".." , "LanMountainDesktop" , "bin" , "Debug" , "net10.0" , executable ) ,
Path . Combine ( AppContext . BaseDirectory , ".." , "LanMountainDesktop" , "bin" , "Release" , "net10.0" , executable ) ,
2026-04-23 00:27:01 +08:00
// dev-test 鐩綍
2026-04-21 20:59:52 +08:00
Path . Combine ( AppContext . BaseDirectory , ".." , "dev-test" , "app-1.0.0-dev" , executable ) ,
} ;
foreach ( var path in possiblePaths . Select ( Path . GetFullPath ) . Distinct ( ) )
{
if ( File . Exists ( path ) )
{
return path ;
}
}
return null ;
}
/// <summary>
2026-04-23 00:27:01 +08:00
/// 鑾峰彇寮€鍙戠幆澧冨彲鑳界殑涓荤▼搴忚矾寰? /// </summary>
2026-04-21 20:59:52 +08:00
private static IEnumerable < string > GetDevelopmentPaths ( string executable )
{
var launcherDir = AppContext . BaseDirectory ;
var possiblePaths = new [ ]
{
2026-04-23 00:27:01 +08:00
// 浠?Launcher 椤圭洰杩愯锛?.\LanMountainDesktop\bin\Debug\net10.0\LanMountainDesktop.exe
2026-04-21 20:59:52 +08:00
Path . Combine ( launcherDir , ".." , ".." , "LanMountainDesktop" , "bin" , "Debug" , "net10.0" , executable ) ,
Path . Combine ( launcherDir , ".." , ".." , "LanMountainDesktop" , "bin" , "Release" , "net10.0" , executable ) ,
2026-04-23 00:27:01 +08:00
// 浠庤В 鍐虫柟妗堟牴鐩綍杩愯锛歀anMountainDesktop\bin\Debug\net10.0\LanMountainDesktop.exe
2026-04-21 20:59:52 +08:00
Path . Combine ( launcherDir , ".." , "LanMountainDesktop" , "bin" , "Debug" , "net10.0" , executable ) ,
Path . Combine ( launcherDir , ".." , "LanMountainDesktop" , "bin" , "Release" , "net10.0" , executable ) ,
2026-04-23 00:27:01 +08:00
// 浠?dev-test 鐩綍杩愯
2026-04-21 20:59:52 +08:00
Path . Combine ( launcherDir , ".." , "dev-test" , "app-1.0.0-dev" , executable ) ,
} ;
return possiblePaths . Select ( Path . GetFullPath ) . Distinct ( ) ;
}
public string GetCurrentVersion ( )
{
var deployment = FindCurrentDeploymentDirectory ( ) ;
if ( string . IsNullOrWhiteSpace ( deployment ) )
{
return "0.0.0" ;
}
return ParseVersionTextFromDirectory ( deployment ) ? ? "0.0.0" ;
}
public string BuildNextDeploymentDirectory ( string targetVersion )
{
var sanitized = string . IsNullOrWhiteSpace ( targetVersion ) ? "0.0.0" : targetVersion . Trim ( ) ;
var index = 0 ;
while ( true )
{
var candidate = Path . Combine ( _appRoot , $"app-{sanitized}-{index.ToString(CultureInfo.InvariantCulture)}" ) ;
if ( ! Directory . Exists ( candidate ) )
{
return candidate ;
}
index + + ;
}
}
/// <summary>
2026-04-23 00:27:01 +08:00
/// 娓呯悊鏃х 増鏈儴缃诧紝淇濈暀鏈€杩戠殑N涓増鏈? /// </summary>
/// <param name="minVersionsToKeep">鏈€灏戜繚鐣欑増鏈暟锛岄粯璁?涓?/param>
2026-04-21 20:59:52 +08:00
public void CleanupOldDeployments ( int minVersionsToKeep = 3 )
{
try
{
Console . WriteLine ( $"[DeploymentLocator] Starting cleanup with retention policy: keep at least {minVersionsToKeep} versions" ) ;
if ( ! Directory . Exists ( _appRoot ) )
{
return ;
}
var candidates = Directory . GetDirectories ( _appRoot , "app-*" , SearchOption . TopDirectoryOnly ) ;
var validDeployments = candidates
. Where ( path = > ! File . Exists ( Path . Combine ( path , ".partial" ) ) )
. Select ( path = > new
{
Path = path ,
Version = ParseVersionFromDirectory ( path ) ,
IsDestroyed = File . Exists ( Path . Combine ( path , ".destroy" ) ) ,
IsCurrent = File . Exists ( Path . Combine ( path , ".current" ) )
} )
. OrderByDescending ( item = > item . Version )
. ToList ( ) ;
Console . WriteLine ( $"[DeploymentLocator] Found {validDeployments.Count} valid deployments" ) ;
2026-04-23 00:27:01 +08:00
// 纭畾瑕佷繚鐣欑殑鐗堟湰
2026-04-21 20:59:52 +08:00
var versionsToKeep = new HashSet < string > ( ) ;
2026-04-23 00:27:01 +08:00
// 1. 鎬绘槸淇濈暀褰撳墠鐗堟湰
2026-04-21 20:59:52 +08:00
var currentVersion = validDeployments . FirstOrDefault ( d = > d . IsCurrent ) ;
if ( currentVersion ! = null )
{
versionsToKeep . Add ( currentVersion . Path ) ;
Console . WriteLine ( $"[DeploymentLocator] Keep current version: {currentVersion.Path}" ) ;
}
2026-04-23 00:27:01 +08:00
// 2. 淇濈暀鏈€杩戠殑N涓湁鏁堢増鏈紙涓嶅寘鎷凡鏍囪destroy鐨勶級
2026-04-21 20:59:52 +08:00
var activeVersions = validDeployments
. Where ( d = > ! d . IsDestroyed )
. Take ( minVersionsToKeep )
. ToList ( ) ;
foreach ( var ver in activeVersions )
{
versionsToKeep . Add ( ver . Path ) ;
Console . WriteLine ( $"[DeploymentLocator] Keep recent version: {ver.Path}" ) ;
}
2026-04-23 00:27:01 +08:00
// 3. 淇濈暀鏈夊揩鐓х殑鐗堟湰锛堢敤浜庡洖婊氾級
2026-04-21 20:59:52 +08:00
var snapshotDir = Path . Combine ( _appRoot , ".launcher" , "snapshots" ) ;
if ( Directory . Exists ( snapshotDir ) )
{
try
{
var snapshotFiles = Directory . GetFiles ( snapshotDir , "*.json" , SearchOption . TopDirectoryOnly ) ;
foreach ( var snapshotFile in snapshotFiles )
{
try
{
var json = File . ReadAllText ( snapshotFile ) ;
var snapshot = System . Text . Json . JsonSerializer . Deserialize ( json , AppJsonContext . Default . SnapshotMetadata ) ;
if ( snapshot ! = null & & ! string . IsNullOrEmpty ( snapshot . SourceDirectory ) )
{
if ( Directory . Exists ( snapshot . SourceDirectory ) )
{
versionsToKeep . Add ( snapshot . SourceDirectory ) ;
Console . WriteLine ( $"[DeploymentLocator] Keep version for rollback: {snapshot.SourceDirectory}" ) ;
}
}
}
catch
{
2026-04-23 00:27:01 +08:00
// 蹇界暐蹇収瑙f瀽閿欒
2026-04-21 20:59:52 +08:00
}
}
}
catch
{
2026-04-23 00:27:01 +08:00
// 蹇界暐蹇収鐩綍璁块棶閿欒
2026-04-21 20:59:52 +08:00
}
}
2026-04-23 00:27:01 +08:00
// 娓呯悊涓嶉渶瑕佺殑鐗堟湰
2026-04-21 20:59:52 +08:00
foreach ( var deployment in validDeployments )
{
if ( versionsToKeep . Contains ( deployment . Path ) )
{
if ( deployment . IsDestroyed )
{
try
{
File . Delete ( Path . Combine ( deployment . Path , ".destroy" ) ) ;
Console . WriteLine ( $"[DeploymentLocator] Unmarked for deletion (kept): {deployment.Path}" ) ;
}
catch
{
2026-04-23 00:27:01 +08:00
// 蹇界暐鍙栨秷鏍囪澶辫触
2026-04-21 20:59:52 +08:00
}
}
continue ;
}
if ( ! deployment . IsDestroyed )
{
try
{
File . WriteAllText ( Path . Combine ( deployment . Path , ".destroy" ) , string . Empty ) ;
Console . WriteLine ( $"[DeploymentLocator] Marked for deletion: {deployment.Path}" ) ;
}
catch
{
2026-04-23 00:27:01 +08:00
// 蹇界暐鏍囪澶辫触
2026-04-21 20:59:52 +08:00
}
}
2026-04-23 00:27:01 +08:00
// 灏濊瘯鍒犻櫎
2026-04-21 20:59:52 +08:00
try
{
Directory . Delete ( deployment . Path , recursive : true ) ;
Console . WriteLine ( $"[DeploymentLocator] Deleted: {deployment.Path}" ) ;
}
catch
{
2026-04-23 00:27:01 +08:00
// 蹇界暐鍒犻櫎澶辫触(鍙兘鏂囦欢琚崰鐢?,涓嬫鍚姩鍐嶈瘯
2026-04-21 20:59:52 +08:00
Console . WriteLine ( $"[DeploymentLocator] Failed to delete (will retry later): {deployment.Path}" ) ;
}
}
}
catch ( Exception ex )
{
Console . Error . WriteLine ( $"[DeploymentLocator] Cleanup failed: {ex.Message}" ) ;
2026-04-23 00:27:01 +08:00
// 蹇界暐娓呯悊澶辫触
2026-04-21 20:59:52 +08:00
}
}
/// <summary>
2026-04-23 00:27:01 +08:00
/// 浠呮竻鐞嗗凡鏍囪涓?destroy鐨勯儴缃诧紙鍏煎鏃ф柟娉曪級
2026-04-21 20:59:52 +08:00
/// </summary>
[Obsolete("Use CleanupOldDeployments instead")]
public void CleanupDestroyedDeployments ( )
{
CleanupOldDeployments ( 3 ) ;
}
public static Version ParseVersionFromDirectory ( string path )
{
var text = ParseVersionTextFromDirectory ( path ) ;
return Version . TryParse ( text , out var version ) ? version : new Version ( 0 , 0 , 0 ) ;
}
private static string? ParseVersionTextFromDirectory ( string path )
{
var fileName = Path . GetFileName ( path ) ;
if ( string . IsNullOrWhiteSpace ( fileName ) )
{
return null ;
}
var segments = fileName . Split ( '-' ) ;
if ( segments . Length < 2 )
{
return null ;
}
return segments [ 1 ] ;
}
/// <summary>
2026-04-23 00:27:01 +08:00
/// 浠庨儴缃茬洰褰曡鍙栫増鏈俊鎭? /// </summary>
2026-04-21 20:59:52 +08:00
public AppVersionInfo GetVersionInfo ( )
{
2026-04-23 00:27:01 +08:00
var executableName = OperatingSystem . IsWindows ( ) ? "LanMountainDesktop.exe" : "LanMountainDesktop" ;
var resolved = AppVersionProvider . ResolveFromPackageRoot ( _appRoot , executableName ) ;
return string . IsNullOrWhiteSpace ( resolved . Version )
? new AppVersionInfo
2026-04-21 20:59:52 +08:00
{
2026-04-23 00:27:01 +08:00
Version = GetCurrentVersion ( ) ,
Codename = "Administrate"
2026-04-21 20:59:52 +08:00
}
2026-04-23 00:27:01 +08:00
: resolved ;
}
2026-04-21 20:59:52 +08:00
}