mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-23 01:44:26 +08:00
0.3.12
修复
This commit is contained in:
@@ -7,6 +7,7 @@ namespace LanMountainDesktop.Services;
|
||||
|
||||
public sealed record DailyArtworkQuery(
|
||||
string? Locale = null,
|
||||
string? MirrorSource = null,
|
||||
bool ForceRefresh = false);
|
||||
|
||||
public sealed record DailyPoetryQuery(
|
||||
@@ -35,11 +36,16 @@ public sealed record RecommendationApiOptions
|
||||
public string JinriShiciPoetryUrl { get; init; } = "https://v1.jinrishici.com/all.json";
|
||||
|
||||
public string ArtInstituteArtworkApiTemplate { get; init; } =
|
||||
"https://api.artic.edu/api/v1/artworks?page={0}&limit={1}&fields=id,title,artist_title,artist_display,date_display,image_id,api_link";
|
||||
"https://api.artic.edu/api/v1/artworks?page={0}&limit={1}&fields=id,title,artist_title,artist_display,date_display,image_id,api_link,thumbnail";
|
||||
|
||||
public string ArtInstituteImageUrlTemplate { get; init; } =
|
||||
"https://www.artic.edu/iiif/2/{0}/full/843,/0/default.jpg";
|
||||
|
||||
public string DomesticArtworkApiUrl { get; init; } =
|
||||
"https://cn.bing.com/HPImageArchive.aspx?format=js&idx=0&n=8&mkt=zh-CN";
|
||||
|
||||
public string DomesticArtworkHost { get; init; } = "https://cn.bing.com";
|
||||
|
||||
public TimeSpan CacheDuration { get; init; } = TimeSpan.FromMinutes(20);
|
||||
|
||||
public TimeSpan RequestTimeout { get; init; } = TimeSpan.FromSeconds(8);
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace LanMountainDesktop.Services;
|
||||
|
||||
public sealed class RecommendationDataService : IRecommendationInfoService, IDisposable
|
||||
{
|
||||
private const string UserAgent = "Mozilla/5.0";
|
||||
|
||||
private sealed record DailyArtworkCacheEntry(DailyArtworkSnapshot Snapshot, DateTimeOffset ExpireAt);
|
||||
private sealed record DailyPoetryCacheEntry(DailyPoetrySnapshot Snapshot, DateTimeOffset ExpireAt);
|
||||
private sealed record ArtworkCandidate(
|
||||
@@ -19,13 +21,16 @@ public sealed class RecommendationDataService : IRecommendationInfoService, IDis
|
||||
string? Artist,
|
||||
string? Year,
|
||||
string? ArtworkUrl,
|
||||
string? ImageId);
|
||||
string? ImageId,
|
||||
string? ThumbnailDataUrl);
|
||||
|
||||
private readonly RecommendationApiOptions _options;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly bool _ownsHttpClient;
|
||||
private readonly AppSettingsService _appSettingsService = new();
|
||||
private readonly object _cacheGate = new();
|
||||
private DailyArtworkCacheEntry? _dailyArtworkCache;
|
||||
private readonly Dictionary<string, DailyArtworkCacheEntry> _dailyArtworkCacheBySource =
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
private DailyPoetryCacheEntry? _dailyPoetryCache;
|
||||
|
||||
public RecommendationDataService(
|
||||
@@ -60,7 +65,7 @@ public sealed class RecommendationDataService : IRecommendationInfoService, IDis
|
||||
{
|
||||
lock (_cacheGate)
|
||||
{
|
||||
_dailyArtworkCache = null;
|
||||
_dailyArtworkCacheBySource.Clear();
|
||||
_dailyPoetryCache = null;
|
||||
}
|
||||
}
|
||||
@@ -79,7 +84,7 @@ public sealed class RecommendationDataService : IRecommendationInfoService, IDis
|
||||
try
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, _options.JinriShiciPoetryUrl);
|
||||
request.Headers.TryAddWithoutValidation("User-Agent", "Mozilla/5.0");
|
||||
request.Headers.TryAddWithoutValidation("User-Agent", UserAgent);
|
||||
using var response = await _httpClient.SendAsync(request, cancellationToken);
|
||||
responseText = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
@@ -132,45 +137,25 @@ public sealed class RecommendationDataService : IRecommendationInfoService, IDis
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var normalizedQuery = query ?? new DailyArtworkQuery();
|
||||
if (!normalizedQuery.ForceRefresh && TryGetDailyArtworkFromCache(out var cached))
|
||||
var mirrorSource = ResolveArtworkMirrorSource(normalizedQuery);
|
||||
if (!normalizedQuery.ForceRefresh && TryGetDailyArtworkFromCache(mirrorSource, out var cached))
|
||||
{
|
||||
return RecommendationQueryResult<DailyArtworkSnapshot>.Ok(cached);
|
||||
}
|
||||
|
||||
var candidateCount = Math.Clamp(_options.DefaultArtworkCandidateCount, 10, 100);
|
||||
return string.Equals(mirrorSource, DailyArtworkMirrorSources.Domestic, StringComparison.OrdinalIgnoreCase)
|
||||
? await GetDailyArtworkFromDomesticSourceAsync(mirrorSource, cancellationToken)
|
||||
: await GetDailyArtworkFromOverseasSourceAsync(mirrorSource, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<RecommendationQueryResult<DailyArtworkSnapshot>> GetDailyArtworkFromOverseasSourceAsync(
|
||||
string mirrorSource,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var localDate = GetChinaLocalDate();
|
||||
var page = Math.Clamp((localDate.DayOfYear % 100) + 1, 1, 100);
|
||||
var requestUrl = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_options.ArtInstituteArtworkApiTemplate,
|
||||
page,
|
||||
candidateCount);
|
||||
|
||||
string responseText;
|
||||
try
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
|
||||
request.Headers.TryAddWithoutValidation("User-Agent", "Mozilla/5.0");
|
||||
using var response = await _httpClient.SendAsync(request, cancellationToken);
|
||||
responseText = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return RecommendationQueryResult<DailyArtworkSnapshot>.Fail(
|
||||
"upstream_http_error",
|
||||
$"HTTP {(int)response.StatusCode}: {Truncate(responseText, 180)}");
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return RecommendationQueryResult<DailyArtworkSnapshot>.Fail("upstream_network_error", ex.Message);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var responseText = await FetchOverseasArtworkPayloadAsync(localDate, cancellationToken);
|
||||
using var document = JsonDocument.Parse(responseText);
|
||||
var root = document.RootElement;
|
||||
if (!root.TryGetProperty("data", out var dataArray) || dataArray.ValueKind != JsonValueKind.Array)
|
||||
@@ -183,7 +168,9 @@ public sealed class RecommendationDataService : IRecommendationInfoService, IDis
|
||||
{
|
||||
var title = ReadString(item, "title");
|
||||
var imageId = ReadString(item, "image_id");
|
||||
if (string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(imageId))
|
||||
var thumbnailDataUrl = ReadString(item, "thumbnail", "lqip");
|
||||
if (string.IsNullOrWhiteSpace(title) ||
|
||||
(string.IsNullOrWhiteSpace(imageId) && string.IsNullOrWhiteSpace(thumbnailDataUrl)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -199,7 +186,8 @@ public sealed class RecommendationDataService : IRecommendationInfoService, IDis
|
||||
artist,
|
||||
ReadString(item, "date_display"),
|
||||
ReadString(item, "api_link"),
|
||||
imageId.Trim()));
|
||||
string.IsNullOrWhiteSpace(imageId) ? null : imageId.Trim(),
|
||||
string.IsNullOrWhiteSpace(thumbnailDataUrl) ? null : thumbnailDataUrl.Trim()));
|
||||
}
|
||||
|
||||
if (candidates.Count == 0)
|
||||
@@ -217,24 +205,121 @@ public sealed class RecommendationDataService : IRecommendationInfoService, IDis
|
||||
Museum: "The Art Institute of Chicago",
|
||||
ArtworkUrl: selected.ArtworkUrl,
|
||||
ImageUrl: BuildArtworkImageUrl(selected.ImageId),
|
||||
ThumbnailDataUrl: selected.ThumbnailDataUrl,
|
||||
FetchedAt: DateTimeOffset.UtcNow);
|
||||
|
||||
SetDailyArtworkCache(snapshot);
|
||||
SetDailyArtworkCache(mirrorSource, snapshot);
|
||||
return RecommendationQueryResult<DailyArtworkSnapshot>.Ok(snapshot);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
return RecommendationQueryResult<DailyArtworkSnapshot>.Fail("upstream_network_error", ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return RecommendationQueryResult<DailyArtworkSnapshot>.Fail("upstream_parse_error", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetDailyArtworkFromCache(out DailyArtworkSnapshot snapshot)
|
||||
private async Task<RecommendationQueryResult<DailyArtworkSnapshot>> GetDailyArtworkFromDomesticSourceAsync(
|
||||
string mirrorSource,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, _options.DomesticArtworkApiUrl);
|
||||
request.Headers.TryAddWithoutValidation("User-Agent", UserAgent);
|
||||
using var response = await _httpClient.SendAsync(request, cancellationToken);
|
||||
var responseText = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return RecommendationQueryResult<DailyArtworkSnapshot>.Fail(
|
||||
"upstream_http_error",
|
||||
$"HTTP {(int)response.StatusCode}: {Truncate(responseText, 180)}");
|
||||
}
|
||||
|
||||
using var document = JsonDocument.Parse(responseText);
|
||||
var root = document.RootElement;
|
||||
if (!root.TryGetProperty("images", out var images) || images.ValueKind != JsonValueKind.Array)
|
||||
{
|
||||
return RecommendationQueryResult<DailyArtworkSnapshot>.Fail("upstream_parse_error", "Daily image list is missing.");
|
||||
}
|
||||
|
||||
var candidates = images.EnumerateArray().ToArray();
|
||||
if (candidates.Length == 0)
|
||||
{
|
||||
return RecommendationQueryResult<DailyArtworkSnapshot>.Fail("upstream_empty_result", "No daily image candidates were returned.");
|
||||
}
|
||||
|
||||
var localDate = GetChinaLocalDate();
|
||||
var indexSeed = localDate.Year * 1000 + localDate.DayOfYear;
|
||||
var selected = candidates[Math.Abs(indexSeed) % candidates.Length];
|
||||
|
||||
var imageUrl = BuildDomesticImageUrl(
|
||||
ReadString(selected, "url"),
|
||||
_options.DomesticArtworkHost);
|
||||
if (string.IsNullOrWhiteSpace(imageUrl))
|
||||
{
|
||||
return RecommendationQueryResult<DailyArtworkSnapshot>.Fail("upstream_parse_error", "Daily image URL is missing.");
|
||||
}
|
||||
|
||||
var title = ReadString(selected, "title");
|
||||
if (string.IsNullOrWhiteSpace(title))
|
||||
{
|
||||
title = ExtractDomesticTitle(ReadString(selected, "copyright"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(title))
|
||||
{
|
||||
title = "Bing Daily Image";
|
||||
}
|
||||
|
||||
var dateText = ParseDomesticDateText(ReadString(selected, "startdate"));
|
||||
var artworkUrl = BuildDomesticImageUrl(
|
||||
ReadString(selected, "copyrightlink"),
|
||||
_options.DomesticArtworkHost);
|
||||
if (string.IsNullOrWhiteSpace(artworkUrl) ||
|
||||
artworkUrl.StartsWith("javascript:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
artworkUrl = null;
|
||||
}
|
||||
|
||||
var snapshot = new DailyArtworkSnapshot(
|
||||
Provider: "BingCN",
|
||||
Title: title.Trim(),
|
||||
Artist: "Bing China",
|
||||
Year: dateText,
|
||||
Museum: "Bing China",
|
||||
ArtworkUrl: artworkUrl,
|
||||
ImageUrl: imageUrl,
|
||||
ThumbnailDataUrl: null,
|
||||
FetchedAt: DateTimeOffset.UtcNow);
|
||||
|
||||
SetDailyArtworkCache(mirrorSource, snapshot);
|
||||
return RecommendationQueryResult<DailyArtworkSnapshot>.Ok(snapshot);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return RecommendationQueryResult<DailyArtworkSnapshot>.Fail("upstream_network_error", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetDailyArtworkFromCache(string mirrorSource, out DailyArtworkSnapshot snapshot)
|
||||
{
|
||||
lock (_cacheGate)
|
||||
{
|
||||
if (_dailyArtworkCache is not null && _dailyArtworkCache.ExpireAt > DateTimeOffset.UtcNow)
|
||||
if (_dailyArtworkCacheBySource.TryGetValue(mirrorSource, out var cacheEntry) &&
|
||||
cacheEntry.ExpireAt > DateTimeOffset.UtcNow)
|
||||
{
|
||||
snapshot = _dailyArtworkCache.Snapshot;
|
||||
snapshot = cacheEntry.Snapshot;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -243,11 +328,11 @@ public sealed class RecommendationDataService : IRecommendationInfoService, IDis
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SetDailyArtworkCache(DailyArtworkSnapshot snapshot)
|
||||
private void SetDailyArtworkCache(string mirrorSource, DailyArtworkSnapshot snapshot)
|
||||
{
|
||||
lock (_cacheGate)
|
||||
{
|
||||
_dailyArtworkCache = new DailyArtworkCacheEntry(
|
||||
_dailyArtworkCacheBySource[mirrorSource] = new DailyArtworkCacheEntry(
|
||||
snapshot,
|
||||
DateTimeOffset.UtcNow.Add(_options.CacheDuration));
|
||||
}
|
||||
@@ -325,6 +410,105 @@ public sealed class RecommendationDataService : IRecommendationInfoService, IDis
|
||||
imageId.Trim());
|
||||
}
|
||||
|
||||
private string ResolveArtworkMirrorSource(DailyArtworkQuery query)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(query.MirrorSource))
|
||||
{
|
||||
return DailyArtworkMirrorSources.Normalize(query.MirrorSource);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var snapshot = _appSettingsService.Load();
|
||||
return DailyArtworkMirrorSources.Normalize(snapshot.DailyArtworkMirrorSource);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return DailyArtworkMirrorSources.Overseas;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> FetchOverseasArtworkPayloadAsync(DateOnly localDate, CancellationToken cancellationToken)
|
||||
{
|
||||
var candidateCount = Math.Clamp(_options.DefaultArtworkCandidateCount, 10, 100);
|
||||
var page = Math.Clamp((localDate.DayOfYear % 100) + 1, 1, 100);
|
||||
var requestUrl = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_options.ArtInstituteArtworkApiTemplate,
|
||||
page,
|
||||
candidateCount);
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
|
||||
request.Headers.TryAddWithoutValidation("User-Agent", UserAgent);
|
||||
using var response = await _httpClient.SendAsync(request, cancellationToken);
|
||||
var responseText = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
throw new HttpRequestException($"HTTP {(int)response.StatusCode}: {Truncate(responseText, 180)}");
|
||||
}
|
||||
|
||||
return responseText;
|
||||
}
|
||||
|
||||
private static string? BuildDomesticImageUrl(string? rawValue, string fallbackHost)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rawValue))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var candidate = rawValue.Trim();
|
||||
if (Uri.TryCreate(candidate, UriKind.Absolute, out var absoluteUri))
|
||||
{
|
||||
return absoluteUri.ToString();
|
||||
}
|
||||
|
||||
if (!Uri.TryCreate(fallbackHost, UriKind.Absolute, out var hostUri))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var normalizedPath = candidate.StartsWith("/", StringComparison.Ordinal) ? candidate : $"/{candidate}";
|
||||
return new Uri(hostUri, normalizedPath).ToString();
|
||||
}
|
||||
|
||||
private static string ExtractDomesticTitle(string? copyrightText)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(copyrightText))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var compact = copyrightText.Trim();
|
||||
var bracketIndex = compact.IndexOf('(');
|
||||
if (bracketIndex <= 0)
|
||||
{
|
||||
return compact;
|
||||
}
|
||||
|
||||
return compact[..bracketIndex].Trim();
|
||||
}
|
||||
|
||||
private static string? ParseDomesticDateText(string? rawDate)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rawDate) || rawDate.Length < 8)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (DateTime.TryParseExact(
|
||||
rawDate[..8],
|
||||
"yyyyMMdd",
|
||||
CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.None,
|
||||
out var date))
|
||||
{
|
||||
return date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string? ReadFirstNonEmptyLine(string? text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
|
||||
126
LanMountainDesktop/Services/WebView2RuntimeProbe.cs
Normal file
126
LanMountainDesktop/Services/WebView2RuntimeProbe.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Versioning;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace LanMountainDesktop.Services;
|
||||
|
||||
public sealed record WebView2RuntimeAvailability(
|
||||
bool IsAvailable,
|
||||
string? Version,
|
||||
string Message);
|
||||
|
||||
public static class WebView2RuntimeProbe
|
||||
{
|
||||
private const string WebView2RuntimeClientId = "{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}";
|
||||
private const string WebView2RuntimeKeyPath = @"SOFTWARE\Microsoft\EdgeUpdate\Clients\" + WebView2RuntimeClientId;
|
||||
public const string RuntimeDownloadUrl = "https://go.microsoft.com/fwlink/p/?LinkId=2124703";
|
||||
|
||||
public static WebView2RuntimeAvailability GetAvailability()
|
||||
{
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
return new WebView2RuntimeAvailability(
|
||||
IsAvailable: true,
|
||||
Version: null,
|
||||
Message: string.Empty);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var version = TryGetVersionFromWebView2Api();
|
||||
if (string.IsNullOrWhiteSpace(version))
|
||||
{
|
||||
version = TryGetVersionFromRegistry();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(version))
|
||||
{
|
||||
return new WebView2RuntimeAvailability(
|
||||
IsAvailable: true,
|
||||
Version: version.Trim(),
|
||||
Message: string.Empty);
|
||||
}
|
||||
|
||||
return new WebView2RuntimeAvailability(
|
||||
IsAvailable: false,
|
||||
Version: null,
|
||||
Message: $"WebView2 Runtime is missing. Install it from {RuntimeDownloadUrl} and restart the app.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new WebView2RuntimeAvailability(
|
||||
IsAvailable: false,
|
||||
Version: null,
|
||||
Message: $"WebView2 runtime check failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public static string ResolveUserDataFolder()
|
||||
{
|
||||
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
if (string.IsNullOrWhiteSpace(localAppData))
|
||||
{
|
||||
localAppData = AppContext.BaseDirectory;
|
||||
}
|
||||
|
||||
var userDataFolder = Path.Combine(localAppData, "LanMountainDesktop", "WebView2");
|
||||
Directory.CreateDirectory(userDataFolder);
|
||||
return userDataFolder;
|
||||
}
|
||||
|
||||
private static string? TryGetVersionFromWebView2Api()
|
||||
{
|
||||
var type = Type.GetType(
|
||||
"Microsoft.Web.WebView2.Core.CoreWebView2Environment, Microsoft.Web.WebView2.Core",
|
||||
throwOnError: false);
|
||||
if (type is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var method = type.GetMethod(
|
||||
"GetAvailableBrowserVersionString",
|
||||
BindingFlags.Public | BindingFlags.Static,
|
||||
binder: null,
|
||||
types: Type.EmptyTypes,
|
||||
modifiers: null);
|
||||
if (method is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return method.Invoke(null, null) as string;
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
private static string? TryGetVersionFromRegistry()
|
||||
{
|
||||
return TryReadVersionFromRegistry(RegistryHive.LocalMachine, RegistryView.Registry64)
|
||||
?? TryReadVersionFromRegistry(RegistryHive.LocalMachine, RegistryView.Registry32)
|
||||
?? TryReadVersionFromRegistry(RegistryHive.CurrentUser, RegistryView.Registry64)
|
||||
?? TryReadVersionFromRegistry(RegistryHive.CurrentUser, RegistryView.Registry32);
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
private static string? TryReadVersionFromRegistry(RegistryHive hive, RegistryView view)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var baseKey = RegistryKey.OpenBaseKey(hive, view);
|
||||
using var runtimeKey = baseKey.OpenSubKey(WebView2RuntimeKeyPath, writable: false);
|
||||
if (runtimeKey is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var value = runtimeKey.GetValue("pv") as string;
|
||||
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
75
LanMountainDesktop/Services/WindowsStartupService.cs
Normal file
75
LanMountainDesktop/Services/WindowsStartupService.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace LanMountainDesktop.Services;
|
||||
|
||||
public sealed class WindowsStartupService
|
||||
{
|
||||
private const string RunKeyPath = @"Software\Microsoft\Windows\CurrentVersion\Run";
|
||||
private const string ValueName = "LanMountainDesktop";
|
||||
private readonly string _startupCommand;
|
||||
|
||||
public WindowsStartupService()
|
||||
{
|
||||
var processPath = Environment.ProcessPath;
|
||||
_startupCommand = string.IsNullOrWhiteSpace(processPath)
|
||||
? string.Empty
|
||||
: $"\"{processPath}\"";
|
||||
}
|
||||
|
||||
public bool IsEnabled()
|
||||
{
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var runKey = Registry.CurrentUser.OpenSubKey(RunKeyPath, writable: false);
|
||||
return runKey?.GetValue(ValueName) is string value &&
|
||||
!string.IsNullOrWhiteSpace(value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool SetEnabled(bool enabled)
|
||||
{
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (enabled && string.IsNullOrWhiteSpace(_startupCommand))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var runKey = Registry.CurrentUser.CreateSubKey(RunKeyPath);
|
||||
if (runKey is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
runKey.SetValue(ValueName, _startupCommand, RegistryValueKind.String);
|
||||
}
|
||||
else
|
||||
{
|
||||
runKey.DeleteValue(ValueName, throwOnMissingValue: false);
|
||||
}
|
||||
|
||||
return IsEnabled() == enabled;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user