mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-21 16:14:28 +08:00
1436 lines
52 KiB
C#
1436 lines
52 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Avalonia.Media;
|
|
using Avalonia.Media.Imaging;
|
|
using LanMountainDesktop.Models;
|
|
using LanMountainDesktop.PluginSdk;
|
|
using LanMountainDesktop.Services;
|
|
using LanMountainDesktop.Settings.Core;
|
|
using LanMountainDesktop.Services.PluginMarket;
|
|
|
|
namespace LanMountainDesktop.Services.Settings;
|
|
|
|
internal sealed class GridSettingsService : IGridSettingsService
|
|
{
|
|
private readonly ISettingsService _settingsService;
|
|
private readonly DesktopGridLayoutService _gridLayoutService = new();
|
|
|
|
public GridSettingsService(ISettingsService settingsService)
|
|
{
|
|
_settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
|
|
}
|
|
|
|
public GridSettingsState Get()
|
|
{
|
|
var snapshot = _settingsService.Load();
|
|
return new GridSettingsState(
|
|
snapshot.GridShortSideCells,
|
|
snapshot.GridSpacingPreset,
|
|
snapshot.DesktopEdgeInsetPercent);
|
|
}
|
|
|
|
public void Save(GridSettingsState state)
|
|
{
|
|
var snapshot = _settingsService.Load();
|
|
snapshot.GridShortSideCells = state.ShortSideCells;
|
|
snapshot.GridSpacingPreset = state.SpacingPreset;
|
|
snapshot.DesktopEdgeInsetPercent = state.EdgeInsetPercent;
|
|
_settingsService.SaveSnapshot(
|
|
SettingsScope.App,
|
|
snapshot,
|
|
changedKeys:
|
|
[
|
|
nameof(AppSettingsSnapshot.GridShortSideCells),
|
|
nameof(AppSettingsSnapshot.GridSpacingPreset),
|
|
nameof(AppSettingsSnapshot.DesktopEdgeInsetPercent)
|
|
]);
|
|
}
|
|
|
|
public string NormalizeSpacingPreset(string? value)
|
|
{
|
|
return _gridLayoutService.NormalizeSpacingPreset(value);
|
|
}
|
|
|
|
public double ResolveGapRatio(string? preset)
|
|
{
|
|
return _gridLayoutService.ResolveGapRatio(preset);
|
|
}
|
|
|
|
public double CalculateEdgeInset(double hostWidth, double hostHeight, int shortSideCells, int insetPercent)
|
|
{
|
|
return _gridLayoutService.CalculateEdgeInset(hostWidth, hostHeight, shortSideCells, insetPercent);
|
|
}
|
|
|
|
public DesktopGridMetrics CalculateGridMetrics(
|
|
double hostWidth,
|
|
double hostHeight,
|
|
int shortSideCells,
|
|
double gapRatio,
|
|
double edgeInsetPx)
|
|
{
|
|
return _gridLayoutService.CalculateGridMetrics(
|
|
hostWidth,
|
|
hostHeight,
|
|
shortSideCells,
|
|
gapRatio,
|
|
edgeInsetPx);
|
|
}
|
|
}
|
|
|
|
internal sealed class WallpaperSettingsService : IWallpaperSettingsService
|
|
{
|
|
private readonly ISettingsService _settingsService;
|
|
|
|
public WallpaperSettingsService(ISettingsService settingsService)
|
|
{
|
|
_settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
|
|
}
|
|
|
|
public WallpaperSettingsState Get()
|
|
{
|
|
var snapshot = _settingsService.Load();
|
|
var normalizedType = snapshot.WallpaperType ?? "Image";
|
|
return new WallpaperSettingsState(
|
|
string.Equals(normalizedType, "SolidColor", StringComparison.OrdinalIgnoreCase)
|
|
? null
|
|
: snapshot.WallpaperPath,
|
|
normalizedType,
|
|
snapshot.WallpaperColor,
|
|
snapshot.WallpaperPlacement,
|
|
CustomColor: null,
|
|
SystemWallpaperRefreshIntervalSeconds: NormalizeRefreshInterval(snapshot.SystemWallpaperRefreshIntervalSeconds));
|
|
}
|
|
|
|
public void Save(WallpaperSettingsState state)
|
|
{
|
|
var snapshot = _settingsService.Load();
|
|
var normalizedType = string.IsNullOrWhiteSpace(state.Type)
|
|
? "Image"
|
|
: state.Type.Trim();
|
|
var normalizedPath = string.IsNullOrWhiteSpace(state.WallpaperPath)
|
|
? null
|
|
: state.WallpaperPath.Trim();
|
|
var normalizedColor = string.IsNullOrWhiteSpace(state.Color)
|
|
? null
|
|
: state.Color.Trim();
|
|
|
|
if (string.Equals(normalizedType, "SolidColor", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
normalizedPath = null;
|
|
}
|
|
|
|
snapshot.WallpaperPath = normalizedPath;
|
|
snapshot.WallpaperType = normalizedType;
|
|
snapshot.WallpaperColor = normalizedColor;
|
|
snapshot.WallpaperPlacement = string.IsNullOrWhiteSpace(state.Placement)
|
|
? "Fill"
|
|
: state.Placement.Trim();
|
|
snapshot.SystemWallpaperRefreshIntervalSeconds = NormalizeRefreshInterval(state.SystemWallpaperRefreshIntervalSeconds);
|
|
_settingsService.SaveSnapshot(
|
|
SettingsScope.App,
|
|
snapshot,
|
|
changedKeys:
|
|
[
|
|
nameof(AppSettingsSnapshot.WallpaperPath),
|
|
nameof(AppSettingsSnapshot.WallpaperType),
|
|
nameof(AppSettingsSnapshot.WallpaperColor),
|
|
nameof(AppSettingsSnapshot.WallpaperPlacement),
|
|
nameof(AppSettingsSnapshot.SystemWallpaperRefreshIntervalSeconds)
|
|
]);
|
|
}
|
|
|
|
private static int NormalizeRefreshInterval(int seconds)
|
|
{
|
|
return seconds switch
|
|
{
|
|
<= 0 => 300,
|
|
< 30 => 30,
|
|
> 86400 => 86400,
|
|
_ => seconds
|
|
};
|
|
}
|
|
}
|
|
|
|
internal sealed class WallpaperMediaService : IWallpaperMediaService
|
|
{
|
|
private static readonly HashSet<string> ImageExtensions = new(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
".png", ".jpg", ".jpeg", ".bmp", ".gif", ".webp"
|
|
};
|
|
|
|
private readonly string _wallpapersDirectory;
|
|
|
|
public WallpaperMediaService()
|
|
{
|
|
var appDataRoot = Path.Combine(
|
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
|
"LanMountainDesktop");
|
|
_wallpapersDirectory = Path.Combine(appDataRoot, "Wallpapers");
|
|
}
|
|
|
|
public WallpaperMediaType DetectMediaType(string? path)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(path))
|
|
{
|
|
return WallpaperMediaType.None;
|
|
}
|
|
|
|
var extension = Path.GetExtension(path.Trim());
|
|
if (string.IsNullOrWhiteSpace(extension))
|
|
{
|
|
return WallpaperMediaType.None;
|
|
}
|
|
|
|
if (ImageExtensions.Contains(extension))
|
|
{
|
|
return WallpaperMediaType.Image;
|
|
}
|
|
|
|
return WallpaperMediaType.None;
|
|
}
|
|
|
|
public async Task<string?> ImportAssetAsync(string sourcePath, CancellationToken cancellationToken = default)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(sourcePath))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var fullSourcePath = Path.GetFullPath(sourcePath);
|
|
if (!File.Exists(fullSourcePath))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (DetectMediaType(fullSourcePath) == WallpaperMediaType.None)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Directory.CreateDirectory(_wallpapersDirectory);
|
|
|
|
var extension = Path.GetExtension(fullSourcePath);
|
|
var baseName = Path.GetFileNameWithoutExtension(fullSourcePath);
|
|
var normalizedBaseName = string.IsNullOrWhiteSpace(baseName)
|
|
? "wallpaper"
|
|
: string.Concat(baseName.Select(ch => Path.GetInvalidFileNameChars().Contains(ch) ? '_' : ch));
|
|
|
|
var destinationPath = Path.Combine(_wallpapersDirectory, $"{normalizedBaseName}{extension}");
|
|
if (string.Equals(fullSourcePath, destinationPath, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return destinationPath;
|
|
}
|
|
|
|
var suffix = 1;
|
|
while (File.Exists(destinationPath))
|
|
{
|
|
destinationPath = Path.Combine(_wallpapersDirectory, $"{normalizedBaseName}_{suffix}{extension}");
|
|
suffix++;
|
|
}
|
|
|
|
await using var source = File.OpenRead(fullSourcePath);
|
|
await using var destination = File.Create(destinationPath);
|
|
await source.CopyToAsync(destination, cancellationToken);
|
|
return destinationPath;
|
|
}
|
|
}
|
|
|
|
internal sealed class ThemeAppearanceService : IThemeAppearanceService
|
|
{
|
|
private readonly ISettingsService _settingsService;
|
|
private readonly MonetColorService _monetColorService = new();
|
|
private readonly WallpaperMediaService _wallpaperMediaService = new();
|
|
|
|
public ThemeAppearanceService(ISettingsService settingsService)
|
|
{
|
|
_settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
|
|
}
|
|
|
|
public ThemeAppearanceSettingsState Get()
|
|
{
|
|
var snapshot = _settingsService.Load();
|
|
var cornerRadiusStyle = GlobalAppearanceSettings.NormalizeCornerRadiusStyle(snapshot.CornerRadiusStyle);
|
|
if (string.Equals(cornerRadiusStyle, GlobalAppearanceSettings.DefaultCornerRadiusStyle, StringComparison.OrdinalIgnoreCase) &&
|
|
string.IsNullOrWhiteSpace(snapshot.CornerRadiusStyle) &&
|
|
Math.Abs(snapshot.GlobalCornerRadiusScale - GlobalAppearanceSettings.DefaultCornerRadiusScale) > 0.01)
|
|
{
|
|
cornerRadiusStyle = GlobalAppearanceSettings.MigrateScaleToStyle(snapshot.GlobalCornerRadiusScale);
|
|
}
|
|
|
|
return new ThemeAppearanceSettingsState(
|
|
snapshot.IsNightMode ?? false,
|
|
snapshot.ThemeColor,
|
|
snapshot.UseSystemChrome,
|
|
cornerRadiusStyle,
|
|
ThemeAppearanceValues.NormalizeThemeColorMode(snapshot.ThemeColorMode, snapshot.ThemeColor),
|
|
ThemeAppearanceValues.NormalizeSystemMaterialMode(snapshot.SystemMaterialMode),
|
|
snapshot.SelectedWallpaperSeed);
|
|
}
|
|
|
|
public void Save(ThemeAppearanceSettingsState state)
|
|
{
|
|
var snapshot = _settingsService.Load();
|
|
var changedKeys = new List<string>();
|
|
var normalizedThemeColor = string.IsNullOrWhiteSpace(state.ThemeColor) ? null : state.ThemeColor;
|
|
var normalizedCornerRadiusStyle = GlobalAppearanceSettings.NormalizeCornerRadiusStyle(state.CornerRadiusStyle);
|
|
var normalizedThemeColorMode = ThemeAppearanceValues.NormalizeThemeColorMode(state.ThemeColorMode, state.ThemeColor);
|
|
var normalizedSystemMaterialMode = ThemeAppearanceValues.NormalizeSystemMaterialMode(state.SystemMaterialMode);
|
|
var normalizedSelectedWallpaperSeed = string.IsNullOrWhiteSpace(state.SelectedWallpaperSeed)
|
|
? null
|
|
: state.SelectedWallpaperSeed;
|
|
|
|
if ((snapshot.IsNightMode ?? false) != state.IsNightMode)
|
|
{
|
|
snapshot.IsNightMode = state.IsNightMode;
|
|
changedKeys.Add(nameof(AppSettingsSnapshot.IsNightMode));
|
|
}
|
|
|
|
if (!string.Equals(snapshot.ThemeColor, normalizedThemeColor, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
snapshot.ThemeColor = normalizedThemeColor;
|
|
changedKeys.Add(nameof(AppSettingsSnapshot.ThemeColor));
|
|
}
|
|
|
|
if (snapshot.UseSystemChrome != state.UseSystemChrome)
|
|
{
|
|
snapshot.UseSystemChrome = state.UseSystemChrome;
|
|
changedKeys.Add(nameof(AppSettingsSnapshot.UseSystemChrome));
|
|
}
|
|
|
|
if (!string.Equals(GlobalAppearanceSettings.NormalizeCornerRadiusStyle(snapshot.CornerRadiusStyle), normalizedCornerRadiusStyle, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
snapshot.CornerRadiusStyle = normalizedCornerRadiusStyle;
|
|
changedKeys.Add(nameof(AppSettingsSnapshot.CornerRadiusStyle));
|
|
}
|
|
|
|
if (!string.Equals(snapshot.ThemeColorMode, normalizedThemeColorMode, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
snapshot.ThemeColorMode = normalizedThemeColorMode;
|
|
changedKeys.Add(nameof(AppSettingsSnapshot.ThemeColorMode));
|
|
}
|
|
|
|
if (!string.Equals(snapshot.SystemMaterialMode, normalizedSystemMaterialMode, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
snapshot.SystemMaterialMode = normalizedSystemMaterialMode;
|
|
changedKeys.Add(nameof(AppSettingsSnapshot.SystemMaterialMode));
|
|
}
|
|
|
|
if (!string.Equals(snapshot.SelectedWallpaperSeed, normalizedSelectedWallpaperSeed, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
snapshot.SelectedWallpaperSeed = normalizedSelectedWallpaperSeed;
|
|
changedKeys.Add(nameof(AppSettingsSnapshot.SelectedWallpaperSeed));
|
|
}
|
|
|
|
if (changedKeys.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_settingsService.SaveSnapshot(
|
|
SettingsScope.App,
|
|
snapshot,
|
|
changedKeys: changedKeys);
|
|
}
|
|
|
|
public MonetPalette BuildPalette(bool nightMode, string? wallpaperPath, string? preferredSeedColor = null)
|
|
{
|
|
Bitmap? bitmap = null;
|
|
Color? preferredSeed = null;
|
|
|
|
if (!string.IsNullOrWhiteSpace(preferredSeedColor) && Color.TryParse(preferredSeedColor, out var parsedSeed))
|
|
{
|
|
preferredSeed = parsedSeed;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (_wallpaperMediaService.DetectMediaType(wallpaperPath) == WallpaperMediaType.Image &&
|
|
!string.IsNullOrWhiteSpace(wallpaperPath) &&
|
|
File.Exists(wallpaperPath))
|
|
{
|
|
bitmap = new Bitmap(wallpaperPath);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
AppLogger.Warn(
|
|
"Settings.Theme",
|
|
$"Failed to load wallpaper bitmap for palette generation. Path='{wallpaperPath}'.",
|
|
ex);
|
|
}
|
|
|
|
try
|
|
{
|
|
return _monetColorService.BuildPalette(bitmap, nightMode, preferredSeed);
|
|
}
|
|
finally
|
|
{
|
|
bitmap?.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
internal sealed class StatusBarSettingsService : IStatusBarSettingsService
|
|
{
|
|
private readonly ISettingsService _settingsService;
|
|
|
|
public StatusBarSettingsService(ISettingsService settingsService)
|
|
{
|
|
_settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
|
|
}
|
|
|
|
public StatusBarSettingsState Get()
|
|
{
|
|
var snapshot = _settingsService.Load();
|
|
return new StatusBarSettingsState(
|
|
snapshot.TopStatusComponentIds?.ToArray() ?? [],
|
|
snapshot.PinnedTaskbarActions?.ToArray() ?? [],
|
|
snapshot.EnableDynamicTaskbarActions,
|
|
snapshot.TaskbarLayoutMode,
|
|
snapshot.ClockDisplayFormat,
|
|
snapshot.StatusBarClockTransparentBackground,
|
|
snapshot.ClockPosition,
|
|
snapshot.ClockFontSize,
|
|
snapshot.ShowTextCapsule,
|
|
snapshot.TextCapsuleContent,
|
|
snapshot.TextCapsulePosition,
|
|
snapshot.TextCapsuleTransparentBackground,
|
|
snapshot.TextCapsuleFontSize,
|
|
snapshot.ShowNetworkSpeed,
|
|
snapshot.NetworkSpeedPosition,
|
|
snapshot.NetworkSpeedDisplayMode,
|
|
snapshot.NetworkSpeedTransparentBackground,
|
|
snapshot.ShowNetworkTypeIcon,
|
|
snapshot.NetworkSpeedFontSize,
|
|
snapshot.StatusBarSpacingMode,
|
|
snapshot.StatusBarCustomSpacingPercent,
|
|
snapshot.StatusBarShadowEnabled,
|
|
snapshot.StatusBarShadowColor,
|
|
snapshot.StatusBarShadowOpacity);
|
|
}
|
|
|
|
public void Save(StatusBarSettingsState state)
|
|
{
|
|
var snapshot = _settingsService.Load();
|
|
snapshot.TopStatusComponentIds = state.TopStatusComponentIds?.ToList() ?? [];
|
|
snapshot.PinnedTaskbarActions = state.PinnedTaskbarActions?.ToList() ?? [];
|
|
snapshot.EnableDynamicTaskbarActions = state.EnableDynamicTaskbarActions;
|
|
snapshot.TaskbarLayoutMode = state.TaskbarLayoutMode;
|
|
snapshot.ClockDisplayFormat = state.ClockDisplayFormat;
|
|
snapshot.StatusBarClockTransparentBackground = state.ClockTransparentBackground;
|
|
snapshot.ClockPosition = state.ClockPosition;
|
|
snapshot.ClockFontSize = state.ClockFontSize;
|
|
snapshot.ShowTextCapsule = state.ShowTextCapsule;
|
|
snapshot.TextCapsuleContent = state.TextCapsuleContent;
|
|
snapshot.TextCapsulePosition = state.TextCapsulePosition;
|
|
snapshot.TextCapsuleTransparentBackground = state.TextCapsuleTransparentBackground;
|
|
snapshot.TextCapsuleFontSize = state.TextCapsuleFontSize;
|
|
snapshot.ShowNetworkSpeed = state.ShowNetworkSpeed;
|
|
snapshot.NetworkSpeedPosition = state.NetworkSpeedPosition;
|
|
snapshot.NetworkSpeedDisplayMode = state.NetworkSpeedDisplayMode;
|
|
snapshot.NetworkSpeedTransparentBackground = state.NetworkSpeedTransparentBackground;
|
|
snapshot.ShowNetworkTypeIcon = state.ShowNetworkTypeIcon;
|
|
snapshot.NetworkSpeedFontSize = state.NetworkSpeedFontSize;
|
|
snapshot.StatusBarSpacingMode = state.SpacingMode;
|
|
snapshot.StatusBarCustomSpacingPercent = state.CustomSpacingPercent;
|
|
snapshot.StatusBarShadowEnabled = state.ShadowEnabled;
|
|
snapshot.StatusBarShadowColor = state.ShadowColor;
|
|
snapshot.StatusBarShadowOpacity = state.ShadowOpacity;
|
|
_settingsService.SaveSnapshot(
|
|
SettingsScope.App,
|
|
snapshot,
|
|
changedKeys:
|
|
[
|
|
nameof(AppSettingsSnapshot.TopStatusComponentIds),
|
|
nameof(AppSettingsSnapshot.PinnedTaskbarActions),
|
|
nameof(AppSettingsSnapshot.EnableDynamicTaskbarActions),
|
|
nameof(AppSettingsSnapshot.TaskbarLayoutMode),
|
|
nameof(AppSettingsSnapshot.ClockDisplayFormat),
|
|
nameof(AppSettingsSnapshot.StatusBarClockTransparentBackground),
|
|
nameof(AppSettingsSnapshot.ClockPosition),
|
|
nameof(AppSettingsSnapshot.ClockFontSize),
|
|
nameof(AppSettingsSnapshot.ShowTextCapsule),
|
|
nameof(AppSettingsSnapshot.TextCapsuleContent),
|
|
nameof(AppSettingsSnapshot.TextCapsulePosition),
|
|
nameof(AppSettingsSnapshot.TextCapsuleTransparentBackground),
|
|
nameof(AppSettingsSnapshot.TextCapsuleFontSize),
|
|
nameof(AppSettingsSnapshot.ShowNetworkSpeed),
|
|
nameof(AppSettingsSnapshot.NetworkSpeedPosition),
|
|
nameof(AppSettingsSnapshot.NetworkSpeedDisplayMode),
|
|
nameof(AppSettingsSnapshot.NetworkSpeedTransparentBackground),
|
|
nameof(AppSettingsSnapshot.ShowNetworkTypeIcon),
|
|
nameof(AppSettingsSnapshot.NetworkSpeedFontSize),
|
|
nameof(AppSettingsSnapshot.StatusBarSpacingMode),
|
|
nameof(AppSettingsSnapshot.StatusBarCustomSpacingPercent),
|
|
nameof(AppSettingsSnapshot.StatusBarShadowEnabled),
|
|
nameof(AppSettingsSnapshot.StatusBarShadowColor),
|
|
nameof(AppSettingsSnapshot.StatusBarShadowOpacity)
|
|
]);
|
|
}
|
|
}
|
|
|
|
internal sealed class TextCapsuleSettingsService : ITextCapsuleSettingsService
|
|
{
|
|
private readonly ISettingsService _settingsService;
|
|
|
|
public TextCapsuleSettingsService(ISettingsService settingsService)
|
|
{
|
|
_settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
|
|
}
|
|
|
|
public TextCapsuleSettingsState Get()
|
|
{
|
|
var snapshot = _settingsService.Load();
|
|
return new TextCapsuleSettingsState(
|
|
snapshot.ShowTextCapsule,
|
|
snapshot.TextCapsuleContent,
|
|
snapshot.TextCapsulePosition,
|
|
snapshot.TextCapsuleTransparentBackground);
|
|
}
|
|
|
|
public void Save(TextCapsuleSettingsState state)
|
|
{
|
|
var snapshot = _settingsService.Load();
|
|
snapshot.ShowTextCapsule = state.ShowTextCapsule;
|
|
snapshot.TextCapsuleContent = state.Content;
|
|
snapshot.TextCapsulePosition = state.Position;
|
|
snapshot.TextCapsuleTransparentBackground = state.TransparentBackground;
|
|
_settingsService.SaveSnapshot(
|
|
SettingsScope.App,
|
|
snapshot,
|
|
changedKeys:
|
|
[
|
|
nameof(AppSettingsSnapshot.ShowTextCapsule),
|
|
nameof(AppSettingsSnapshot.TextCapsuleContent),
|
|
nameof(AppSettingsSnapshot.TextCapsulePosition),
|
|
nameof(AppSettingsSnapshot.TextCapsuleTransparentBackground)
|
|
]);
|
|
}
|
|
}
|
|
|
|
internal sealed class WeatherProviderAdapter : IWeatherProvider, IWeatherInfoService, IDisposable
|
|
{
|
|
private readonly IWeatherDataService _weatherDataService = new XiaomiWeatherService();
|
|
|
|
public Task<WeatherQueryResult<IReadOnlyList<WeatherLocation>>> SearchLocationsAsync(
|
|
string keyword,
|
|
string? locale = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return _weatherDataService.SearchLocationsAsync(keyword, locale, cancellationToken);
|
|
}
|
|
|
|
public Task<WeatherQueryResult<WeatherSnapshot>> GetWeatherAsync(
|
|
WeatherQuery query,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return _weatherDataService.GetWeatherAsync(query, cancellationToken);
|
|
}
|
|
|
|
public Task<WeatherQueryResult<WeatherLocation>> ResolveLocationAsync(
|
|
double latitude,
|
|
double longitude,
|
|
string? locale = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return _weatherDataService.ResolveLocationAsync(latitude, longitude, locale, cancellationToken);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_weatherDataService is IDisposable disposable)
|
|
{
|
|
disposable.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
internal sealed class WeatherSettingsService : IWeatherSettingsService, IDisposable
|
|
{
|
|
private readonly ISettingsService _settingsService;
|
|
private readonly WeatherProviderAdapter _weatherProvider = new();
|
|
|
|
public WeatherSettingsService(ISettingsService settingsService)
|
|
{
|
|
_settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
|
|
}
|
|
|
|
public WeatherSettingsState Get()
|
|
{
|
|
var snapshot = _settingsService.Load();
|
|
return new WeatherSettingsState(
|
|
snapshot.WeatherLocationMode,
|
|
snapshot.WeatherLocationKey,
|
|
snapshot.WeatherLocationName,
|
|
snapshot.WeatherLatitude,
|
|
snapshot.WeatherLongitude,
|
|
snapshot.WeatherAutoRefreshLocation,
|
|
snapshot.WeatherExcludedAlerts,
|
|
NormalizeIconPackId(snapshot.WeatherIconPackId),
|
|
snapshot.WeatherNoTlsRequests,
|
|
snapshot.WeatherLocationQuery);
|
|
}
|
|
|
|
public void Save(WeatherSettingsState state)
|
|
{
|
|
var snapshot = _settingsService.Load();
|
|
snapshot.WeatherLocationMode = state.LocationMode;
|
|
snapshot.WeatherLocationKey = state.LocationKey;
|
|
snapshot.WeatherLocationName = state.LocationName;
|
|
snapshot.WeatherLatitude = state.Latitude;
|
|
snapshot.WeatherLongitude = state.Longitude;
|
|
snapshot.WeatherAutoRefreshLocation = state.AutoRefreshLocation;
|
|
snapshot.WeatherExcludedAlerts = state.ExcludedAlerts;
|
|
snapshot.WeatherIconPackId = NormalizeIconPackId(state.IconPackId);
|
|
snapshot.WeatherNoTlsRequests = state.NoTlsRequests;
|
|
snapshot.WeatherLocationQuery = state.LocationQuery;
|
|
_settingsService.SaveSnapshot(
|
|
SettingsScope.App,
|
|
snapshot,
|
|
changedKeys:
|
|
[
|
|
nameof(AppSettingsSnapshot.WeatherLocationMode),
|
|
nameof(AppSettingsSnapshot.WeatherLocationKey),
|
|
nameof(AppSettingsSnapshot.WeatherLocationName),
|
|
nameof(AppSettingsSnapshot.WeatherLatitude),
|
|
nameof(AppSettingsSnapshot.WeatherLongitude),
|
|
nameof(AppSettingsSnapshot.WeatherAutoRefreshLocation),
|
|
nameof(AppSettingsSnapshot.WeatherExcludedAlerts),
|
|
nameof(AppSettingsSnapshot.WeatherIconPackId),
|
|
nameof(AppSettingsSnapshot.WeatherNoTlsRequests),
|
|
nameof(AppSettingsSnapshot.WeatherLocationQuery)
|
|
]);
|
|
}
|
|
|
|
public Task<WeatherQueryResult<IReadOnlyList<WeatherLocation>>> SearchLocationsAsync(
|
|
string keyword,
|
|
string? locale = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return _weatherProvider.SearchLocationsAsync(keyword, locale, cancellationToken);
|
|
}
|
|
|
|
public Task<WeatherQueryResult<WeatherLocation>> ResolveLocationAsync(
|
|
double latitude,
|
|
double longitude,
|
|
string? locale = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return _weatherProvider.ResolveLocationAsync(latitude, longitude, locale, cancellationToken);
|
|
}
|
|
|
|
public IWeatherInfoService GetWeatherInfoService()
|
|
{
|
|
return _weatherProvider;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_weatherProvider.Dispose();
|
|
}
|
|
|
|
private static string NormalizeIconPackId(string? iconPackId)
|
|
{
|
|
return string.IsNullOrWhiteSpace(iconPackId)
|
|
? "HyperOS3"
|
|
: "HyperOS3";
|
|
}
|
|
}
|
|
|
|
internal sealed class RegionSettingsService : IRegionSettingsService
|
|
{
|
|
private readonly ISettingsService _settingsService;
|
|
private readonly TimeZoneService _timeZoneService = new();
|
|
|
|
public RegionSettingsService(ISettingsService settingsService)
|
|
{
|
|
_settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
|
|
ApplyTimeZone(_settingsService.Load().TimeZoneId);
|
|
}
|
|
|
|
public RegionSettingsState Get()
|
|
{
|
|
var snapshot = _settingsService.Load();
|
|
return new RegionSettingsState(snapshot.LanguageCode, snapshot.TimeZoneId);
|
|
}
|
|
|
|
public void Save(RegionSettingsState state)
|
|
{
|
|
var snapshot = _settingsService.Load();
|
|
snapshot.LanguageCode = string.IsNullOrWhiteSpace(state.LanguageCode)
|
|
? "zh-CN"
|
|
: state.LanguageCode.Trim();
|
|
snapshot.TimeZoneId = string.IsNullOrWhiteSpace(state.TimeZoneId)
|
|
? null
|
|
: state.TimeZoneId.Trim();
|
|
_settingsService.SaveSnapshot(
|
|
SettingsScope.App,
|
|
snapshot,
|
|
changedKeys:
|
|
[
|
|
nameof(AppSettingsSnapshot.LanguageCode),
|
|
nameof(AppSettingsSnapshot.TimeZoneId)
|
|
]);
|
|
ApplyTimeZone(snapshot.TimeZoneId);
|
|
}
|
|
|
|
public TimeZoneService GetTimeZoneService()
|
|
{
|
|
return _timeZoneService;
|
|
}
|
|
|
|
private void ApplyTimeZone(string? timeZoneId)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(timeZoneId))
|
|
{
|
|
_timeZoneService.CurrentTimeZone = TimeZoneInfo.Local;
|
|
return;
|
|
}
|
|
|
|
if (!_timeZoneService.SetTimeZoneById(timeZoneId))
|
|
{
|
|
_timeZoneService.CurrentTimeZone = TimeZoneInfo.Local;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal sealed class PrivacySettingsService : IPrivacySettingsService
|
|
{
|
|
private readonly ISettingsService _settingsService;
|
|
|
|
public PrivacySettingsService(ISettingsService settingsService)
|
|
{
|
|
_settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
|
|
}
|
|
|
|
public PrivacySettingsState Get()
|
|
{
|
|
var snapshot = _settingsService.Load();
|
|
return new PrivacySettingsState(
|
|
snapshot.UploadAnonymousCrashData,
|
|
snapshot.UploadAnonymousUsageData);
|
|
}
|
|
|
|
public void Save(PrivacySettingsState state)
|
|
{
|
|
var snapshot = _settingsService.Load();
|
|
var changedKeys = new List<string>();
|
|
|
|
if (snapshot.UploadAnonymousCrashData != state.UploadAnonymousCrashData)
|
|
{
|
|
snapshot.UploadAnonymousCrashData = state.UploadAnonymousCrashData;
|
|
changedKeys.Add(nameof(AppSettingsSnapshot.UploadAnonymousCrashData));
|
|
}
|
|
|
|
if (snapshot.UploadAnonymousUsageData != state.UploadAnonymousUsageData)
|
|
{
|
|
snapshot.UploadAnonymousUsageData = state.UploadAnonymousUsageData;
|
|
changedKeys.Add(nameof(AppSettingsSnapshot.UploadAnonymousUsageData));
|
|
}
|
|
|
|
if (changedKeys.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
AppLogger.Info(
|
|
"PrivacySettings",
|
|
$"Saving: UploadAnonymousCrashData={state.UploadAnonymousCrashData}, UploadAnonymousUsageData={state.UploadAnonymousUsageData}");
|
|
_settingsService.SaveSnapshot(
|
|
SettingsScope.App,
|
|
snapshot,
|
|
changedKeys: changedKeys);
|
|
}
|
|
}
|
|
|
|
internal sealed class UpdateSettingsService : IUpdateSettingsService, IDisposable
|
|
{
|
|
private readonly ISettingsService _settingsService;
|
|
private readonly GitHubReleaseUpdateService _githubReleaseUpdateService = new("wwiinnddyy", "LanMountainDesktop");
|
|
private readonly PlondsReleaseUpdateService _plondsReleaseUpdateService = new();
|
|
|
|
public UpdateSettingsService(ISettingsService settingsService)
|
|
{
|
|
_settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
|
|
}
|
|
|
|
public UpdateSettingsState Get()
|
|
{
|
|
var snapshot = _settingsService.Load();
|
|
var normalizedChannel = UpdateSettingsValues.NormalizeChannel(
|
|
snapshot.UpdateChannel,
|
|
snapshot.IncludePrereleaseUpdates);
|
|
return new UpdateSettingsState(
|
|
string.Equals(normalizedChannel, UpdateSettingsValues.ChannelPreview, StringComparison.OrdinalIgnoreCase),
|
|
normalizedChannel,
|
|
UpdateSettingsValues.NormalizeMode(snapshot.UpdateMode),
|
|
UpdateSettingsValues.NormalizeDownloadSource(snapshot.UpdateDownloadSource),
|
|
UpdateSettingsValues.NormalizeDownloadThreads(snapshot.UpdateDownloadThreads),
|
|
snapshot.PendingUpdateInstallerPath,
|
|
snapshot.PendingUpdateVersion,
|
|
snapshot.PendingUpdatePublishedAtUtcMs,
|
|
snapshot.LastUpdateCheckUtcMs,
|
|
snapshot.PendingUpdateSha256);
|
|
}
|
|
|
|
public void Save(UpdateSettingsState state)
|
|
{
|
|
var snapshot = _settingsService.Load();
|
|
var normalizedChannel = UpdateSettingsValues.NormalizeChannel(
|
|
state.UpdateChannel,
|
|
state.IncludePrereleaseUpdates);
|
|
snapshot.IncludePrereleaseUpdates = string.Equals(
|
|
normalizedChannel,
|
|
UpdateSettingsValues.ChannelPreview,
|
|
StringComparison.OrdinalIgnoreCase);
|
|
snapshot.UpdateChannel = normalizedChannel;
|
|
snapshot.UpdateMode = UpdateSettingsValues.NormalizeMode(state.UpdateMode);
|
|
snapshot.UpdateDownloadSource = UpdateSettingsValues.NormalizeDownloadSource(state.UpdateDownloadSource);
|
|
snapshot.UpdateDownloadThreads = UpdateSettingsValues.NormalizeDownloadThreads(state.UpdateDownloadThreads);
|
|
snapshot.PendingUpdateInstallerPath = string.IsNullOrWhiteSpace(state.PendingUpdateInstallerPath)
|
|
? null
|
|
: state.PendingUpdateInstallerPath.Trim();
|
|
snapshot.PendingUpdateVersion = string.IsNullOrWhiteSpace(state.PendingUpdateVersion)
|
|
? null
|
|
: state.PendingUpdateVersion.Trim();
|
|
snapshot.PendingUpdatePublishedAtUtcMs = state.PendingUpdatePublishedAtUtcMs is > 0
|
|
? state.PendingUpdatePublishedAtUtcMs
|
|
: null;
|
|
snapshot.LastUpdateCheckUtcMs = state.LastUpdateCheckUtcMs is > 0
|
|
? state.LastUpdateCheckUtcMs
|
|
: null;
|
|
snapshot.PendingUpdateSha256 = string.IsNullOrWhiteSpace(state.PendingUpdateSha256)
|
|
? null
|
|
: state.PendingUpdateSha256.Trim().ToLowerInvariant();
|
|
_settingsService.SaveSnapshot(
|
|
SettingsScope.App,
|
|
snapshot,
|
|
changedKeys:
|
|
[
|
|
nameof(AppSettingsSnapshot.IncludePrereleaseUpdates),
|
|
nameof(AppSettingsSnapshot.IncludePrereleaseUpdates),
|
|
nameof(AppSettingsSnapshot.UpdateChannel),
|
|
nameof(AppSettingsSnapshot.UpdateMode),
|
|
nameof(AppSettingsSnapshot.UpdateDownloadSource),
|
|
nameof(AppSettingsSnapshot.UpdateDownloadThreads),
|
|
nameof(AppSettingsSnapshot.PendingUpdateInstallerPath),
|
|
nameof(AppSettingsSnapshot.PendingUpdateVersion),
|
|
nameof(AppSettingsSnapshot.PendingUpdatePublishedAtUtcMs),
|
|
nameof(AppSettingsSnapshot.LastUpdateCheckUtcMs),
|
|
nameof(AppSettingsSnapshot.PendingUpdateSha256)
|
|
]);
|
|
}
|
|
|
|
public Task<UpdateCheckResult> CheckForUpdatesAsync(
|
|
Version currentVersion,
|
|
bool includePrerelease,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return CheckForUpdatesCoreAsync(currentVersion, includePrerelease, isForce: false, cancellationToken);
|
|
}
|
|
|
|
public Task<UpdateCheckResult> ForceCheckForUpdatesAsync(
|
|
Version currentVersion,
|
|
bool includePrerelease,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return CheckForUpdatesCoreAsync(currentVersion, includePrerelease, isForce: true, cancellationToken);
|
|
}
|
|
|
|
public async Task<PlondsUpdatePayload?> GetPlondsUpdatePayloadAsync(
|
|
Version currentVersion,
|
|
bool includePrerelease,
|
|
bool isForce = false,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var result = isForce
|
|
? await _plondsReleaseUpdateService.ForceCheckForUpdatesAsync(currentVersion, includePrerelease, cancellationToken)
|
|
: await _plondsReleaseUpdateService.CheckForUpdatesAsync(currentVersion, includePrerelease, cancellationToken);
|
|
return result.Success ? result.PlondsPayload : null;
|
|
}
|
|
|
|
public Task<UpdateDownloadResult> DownloadAssetAsync(
|
|
GitHubReleaseAsset asset,
|
|
string destinationFilePath,
|
|
string downloadSource,
|
|
int maxParallelSegments,
|
|
IProgress<double>? progress = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return _githubReleaseUpdateService.DownloadAssetAsync(
|
|
asset,
|
|
destinationFilePath,
|
|
downloadSource,
|
|
maxParallelSegments,
|
|
progress,
|
|
cancellationToken);
|
|
}
|
|
|
|
public Task<UpdateDownloadResult> RedownloadAssetAsync(
|
|
GitHubReleaseAsset asset,
|
|
string destinationFilePath,
|
|
string downloadSource,
|
|
int maxParallelSegments,
|
|
IProgress<double>? progress = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return _githubReleaseUpdateService.RedownloadAssetAsync(
|
|
asset,
|
|
destinationFilePath,
|
|
downloadSource,
|
|
maxParallelSegments,
|
|
progress,
|
|
cancellationToken);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_githubReleaseUpdateService.Dispose();
|
|
_plondsReleaseUpdateService.Dispose();
|
|
}
|
|
|
|
private async Task<UpdateCheckResult> CheckForUpdatesCoreAsync(
|
|
Version currentVersion,
|
|
bool includePrerelease,
|
|
bool isForce,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var source = UpdateSettingsValues.NormalizeDownloadSource(_settingsService.Load().UpdateDownloadSource);
|
|
if (string.Equals(source, UpdateSettingsValues.DownloadSourcePlonds, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var plondsResult = isForce
|
|
? await _plondsReleaseUpdateService.ForceCheckForUpdatesAsync(currentVersion, includePrerelease, cancellationToken)
|
|
: await _plondsReleaseUpdateService.CheckForUpdatesAsync(currentVersion, includePrerelease, cancellationToken);
|
|
|
|
if (plondsResult.Success)
|
|
{
|
|
return plondsResult;
|
|
}
|
|
|
|
AppLogger.Warn(
|
|
"UpdateSettings",
|
|
$"PLONDS update check failed and will fallback to GitHub. Error: {plondsResult.ErrorMessage}");
|
|
|
|
var githubFallbackResult = isForce
|
|
? await _githubReleaseUpdateService.ForceCheckForUpdatesAsync(currentVersion, includePrerelease, cancellationToken)
|
|
: await _githubReleaseUpdateService.CheckForUpdatesAsync(currentVersion, includePrerelease, cancellationToken);
|
|
|
|
if (githubFallbackResult.Success)
|
|
{
|
|
AppLogger.Info(
|
|
"UpdateSettings",
|
|
$"GitHub fallback succeeded after PLONDS failure. Original PLONDS error: {plondsResult.ErrorMessage}");
|
|
}
|
|
else
|
|
{
|
|
AppLogger.Warn(
|
|
"UpdateSettings",
|
|
$"GitHub fallback also failed after PLONDS failure. PLONDS error: {plondsResult.ErrorMessage}; GitHub error: {githubFallbackResult.ErrorMessage}");
|
|
}
|
|
|
|
return githubFallbackResult;
|
|
}
|
|
|
|
return isForce
|
|
? await _githubReleaseUpdateService.ForceCheckForUpdatesAsync(currentVersion, includePrerelease, cancellationToken)
|
|
: await _githubReleaseUpdateService.CheckForUpdatesAsync(currentVersion, includePrerelease, cancellationToken);
|
|
}
|
|
}
|
|
|
|
internal sealed class LauncherCatalogService : ILauncherCatalogService
|
|
{
|
|
private readonly WindowsStartMenuService _startMenuService = new();
|
|
|
|
public StartMenuFolderNode LoadCatalog()
|
|
{
|
|
return _startMenuService.Load();
|
|
}
|
|
}
|
|
|
|
internal sealed class LauncherPolicyService : ILauncherPolicyService
|
|
{
|
|
private readonly LauncherSettingsService _launcherSettingsService = new();
|
|
|
|
public LauncherSettingsSnapshot Get()
|
|
{
|
|
return _launcherSettingsService.Load();
|
|
}
|
|
|
|
public void Save(LauncherSettingsSnapshot snapshot)
|
|
{
|
|
_launcherSettingsService.Save(snapshot ?? new LauncherSettingsSnapshot());
|
|
}
|
|
}
|
|
|
|
internal sealed class PluginManagementSettingsService : IPluginManagementSettingsService
|
|
{
|
|
private readonly ISettingsService _settingsService;
|
|
private PluginRuntimeService? _pluginRuntimeService;
|
|
|
|
public PluginManagementSettingsService(ISettingsService settingsService, PluginRuntimeService? pluginRuntimeService)
|
|
{
|
|
_settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
|
|
_pluginRuntimeService = pluginRuntimeService;
|
|
}
|
|
|
|
public void SetPluginRuntime(PluginRuntimeService? pluginRuntimeService)
|
|
{
|
|
_pluginRuntimeService = pluginRuntimeService;
|
|
}
|
|
|
|
public PluginManagementSettingsState Get()
|
|
{
|
|
var snapshot = _settingsService.Load();
|
|
return new PluginManagementSettingsState(snapshot.DisabledPluginIds?.ToArray() ?? []);
|
|
}
|
|
|
|
public void Save(PluginManagementSettingsState state)
|
|
{
|
|
var snapshot = _settingsService.Load();
|
|
snapshot.DisabledPluginIds = state.DisabledPluginIds?.ToList() ?? [];
|
|
_settingsService.SaveSnapshot(
|
|
SettingsScope.App,
|
|
snapshot,
|
|
changedKeys: [nameof(AppSettingsSnapshot.DisabledPluginIds)]);
|
|
}
|
|
|
|
public IReadOnlyList<InstalledPluginInfo> GetInstalledPlugins()
|
|
{
|
|
return _pluginRuntimeService?.GetInstalledPluginsSnapshot() ?? [];
|
|
}
|
|
|
|
public bool SetPluginEnabled(string pluginId, bool isEnabled)
|
|
{
|
|
return _pluginRuntimeService?.SetPluginEnabled(pluginId, isEnabled) ?? false;
|
|
}
|
|
|
|
public bool DeleteInstalledPlugin(string pluginId)
|
|
{
|
|
return _pluginRuntimeService?.DeleteInstalledPlugin(pluginId) ?? false;
|
|
}
|
|
}
|
|
|
|
internal sealed class PluginCatalogSettingsService : IPluginCatalogSettingsService, IDisposable
|
|
{
|
|
private PluginRuntimeService? _pluginRuntimeService;
|
|
private AirAppMarketIndexService _indexService;
|
|
private AirAppMarketInstallService? _installService;
|
|
private readonly Dictionary<string, AirAppMarketPluginEntry> _cachedPlugins = new(StringComparer.OrdinalIgnoreCase);
|
|
|
|
public PluginCatalogSettingsService(PluginRuntimeService? pluginRuntimeService)
|
|
{
|
|
_pluginRuntimeService = pluginRuntimeService;
|
|
|
|
var dataRoot = Path.Combine(
|
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
|
"LanMountainDesktop",
|
|
"PluginMarket");
|
|
var cacheService = new AirAppMarketCacheService(dataRoot);
|
|
_indexService = new AirAppMarketIndexService(cacheService);
|
|
if (_pluginRuntimeService is not null)
|
|
{
|
|
_installService = new AirAppMarketInstallService(_pluginRuntimeService, dataRoot);
|
|
}
|
|
}
|
|
|
|
public void SetPluginRuntime(PluginRuntimeService? pluginRuntimeService)
|
|
{
|
|
_pluginRuntimeService = pluginRuntimeService;
|
|
_installService?.Dispose();
|
|
_installService = null;
|
|
|
|
if (_pluginRuntimeService is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var dataRoot = Path.Combine(
|
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
|
"LanMountainDesktop",
|
|
"PluginMarket");
|
|
_installService = new AirAppMarketInstallService(_pluginRuntimeService, dataRoot);
|
|
}
|
|
|
|
public Task<PluginCatalogIndexResult> LoadCatalogAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
return LoadCatalogCoreAsync(cancellationToken);
|
|
}
|
|
|
|
public Task<PluginCatalogInstallResult> InstallAsync(
|
|
string pluginId,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return InstallCatalogCoreAsync(pluginId, cancellationToken);
|
|
}
|
|
|
|
private async Task<PluginCatalogIndexResult> LoadCatalogCoreAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
var result = await _indexService.LoadAsync(cancellationToken).ConfigureAwait(false);
|
|
var sources = BuildCatalogSources(result.Source?.ToString(), result.SourceLocation, result.WarningMessage);
|
|
if (!result.Success || result.Document is null)
|
|
{
|
|
_cachedPlugins.Clear();
|
|
return new PluginCatalogIndexResult(
|
|
false,
|
|
[],
|
|
sources,
|
|
result.Source?.ToString(),
|
|
result.SourceLocation,
|
|
result.WarningMessage,
|
|
result.ErrorMessage);
|
|
}
|
|
|
|
_cachedPlugins.Clear();
|
|
var plugins = result.Document.Plugins
|
|
.Select(entry =>
|
|
{
|
|
_cachedPlugins[entry.Id] = entry;
|
|
return MapCatalogItem(entry);
|
|
})
|
|
.ToArray();
|
|
|
|
return new PluginCatalogIndexResult(
|
|
true,
|
|
plugins,
|
|
sources,
|
|
result.Source?.ToString(),
|
|
result.SourceLocation,
|
|
result.WarningMessage,
|
|
null);
|
|
}
|
|
|
|
private async Task<PluginCatalogInstallResult> InstallCatalogCoreAsync(
|
|
string pluginId,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(pluginId))
|
|
{
|
|
return new PluginCatalogInstallResult(
|
|
false,
|
|
null,
|
|
null,
|
|
null,
|
|
[new PluginInstallDiagnostic("invalid_request", "Plugin id is required.")],
|
|
"Plugin id is required.");
|
|
}
|
|
|
|
if (_installService is null || _pluginRuntimeService is null)
|
|
{
|
|
return new PluginCatalogInstallResult(
|
|
false,
|
|
pluginId,
|
|
null,
|
|
null,
|
|
[new PluginInstallDiagnostic("runtime_unavailable", "Plugin runtime is unavailable.")],
|
|
"Plugin runtime is unavailable.");
|
|
}
|
|
|
|
if (!_cachedPlugins.TryGetValue(pluginId, out var entry))
|
|
{
|
|
var load = await LoadCatalogCoreAsync(cancellationToken).ConfigureAwait(false);
|
|
if (!load.Success)
|
|
{
|
|
return new PluginCatalogInstallResult(
|
|
false,
|
|
pluginId,
|
|
null,
|
|
null,
|
|
[new PluginInstallDiagnostic("catalog_load_failed", load.ErrorMessage ?? "Failed to load the plugin catalog.")],
|
|
load.ErrorMessage);
|
|
}
|
|
|
|
if (!_cachedPlugins.TryGetValue(pluginId, out entry))
|
|
{
|
|
return new PluginCatalogInstallResult(
|
|
false,
|
|
pluginId,
|
|
null,
|
|
null,
|
|
[new PluginInstallDiagnostic("not_found", "Plugin was not found in the official catalog.")],
|
|
"Plugin was not found in the official catalog.");
|
|
}
|
|
}
|
|
|
|
var result = await _installService.InstallAsync(entry, cancellationToken).ConfigureAwait(false);
|
|
if (!result.Success)
|
|
{
|
|
return new PluginCatalogInstallResult(
|
|
false,
|
|
entry.Id,
|
|
entry.Name,
|
|
null,
|
|
[new PluginInstallDiagnostic("install_failed", result.ErrorMessage ?? "Plugin install failed.")],
|
|
result.ErrorMessage);
|
|
}
|
|
|
|
return new PluginCatalogInstallResult(
|
|
true,
|
|
result.Manifest?.Id ?? entry.Id,
|
|
result.Manifest?.Name ?? entry.Name,
|
|
result.Manifest,
|
|
[],
|
|
null);
|
|
}
|
|
|
|
private static PluginCatalogItemInfo MapCatalogItem(AirAppMarketPluginEntry entry)
|
|
{
|
|
var manifest = new PluginCatalogManifestInfo(
|
|
entry.Id,
|
|
entry.Name,
|
|
entry.Description,
|
|
entry.Author,
|
|
entry.Version,
|
|
entry.ApiVersion,
|
|
string.Empty,
|
|
entry.SharedContracts
|
|
.Select(contract => new PluginCatalogSharedContractInfo(
|
|
contract.Id,
|
|
contract.Version,
|
|
contract.AssemblyName))
|
|
.ToArray());
|
|
|
|
var compatibility = new PluginCatalogCompatibilityInfo(
|
|
entry.MinHostVersion,
|
|
entry.ApiVersion);
|
|
|
|
var repository = new PluginCatalogRepositoryInfo(
|
|
entry.IconUrl,
|
|
entry.ProjectUrl,
|
|
entry.ReadmeUrl,
|
|
entry.HomepageUrl,
|
|
entry.RepositoryUrl,
|
|
entry.Tags.ToArray(),
|
|
entry.ReleaseNotes);
|
|
|
|
var publication = new PluginCatalogPublicationInfo(
|
|
entry.ReleaseTag,
|
|
entry.ReleaseAssetName,
|
|
entry.PublishedAt,
|
|
entry.UpdatedAt,
|
|
entry.PackageSizeBytes,
|
|
entry.Sha256,
|
|
null);
|
|
|
|
var sources = BuildPackageSources(entry);
|
|
|
|
return new PluginCatalogItemInfo(
|
|
manifest,
|
|
compatibility,
|
|
repository,
|
|
publication,
|
|
sources,
|
|
[]);
|
|
}
|
|
|
|
private static IReadOnlyList<PluginPackageSourceInfo> BuildPackageSources(AirAppMarketPluginEntry entry)
|
|
{
|
|
var sources = entry.GetPackageSourcesInInstallOrder();
|
|
if (sources.Count == 0)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
return sources
|
|
.Select(source => new PluginPackageSourceInfo(
|
|
source.SourceKind switch
|
|
{
|
|
LanMountainDesktop.Services.PluginMarket.PluginPackageSourceKind.ReleaseAsset => PluginPackageSourceKind.ReleaseAsset,
|
|
LanMountainDesktop.Services.PluginMarket.PluginPackageSourceKind.RawFallback => PluginPackageSourceKind.RawFallback,
|
|
LanMountainDesktop.Services.PluginMarket.PluginPackageSourceKind.WorkspaceLocal => PluginPackageSourceKind.WorkspaceLocal,
|
|
_ => PluginPackageSourceKind.RawFallback
|
|
},
|
|
source.Url,
|
|
entry.Sha256,
|
|
entry.PackageSizeBytes))
|
|
.ToArray();
|
|
}
|
|
|
|
private static IReadOnlyList<PluginCatalogSourceInfo> BuildCatalogSources(
|
|
string? sourceId,
|
|
string? sourceLocation,
|
|
string? warningMessage)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(sourceId) && string.IsNullOrWhiteSpace(sourceLocation))
|
|
{
|
|
return [];
|
|
}
|
|
|
|
var normalizedSourceId = string.IsNullOrWhiteSpace(sourceId)
|
|
? "plugin-catalog"
|
|
: sourceId.Trim();
|
|
|
|
return
|
|
[
|
|
new PluginCatalogSourceInfo(
|
|
normalizedSourceId,
|
|
normalizedSourceId,
|
|
string.IsNullOrWhiteSpace(warningMessage) ? null : warningMessage.Trim(),
|
|
string.IsNullOrWhiteSpace(sourceLocation) ? null : sourceLocation.Trim(),
|
|
null,
|
|
true,
|
|
0)
|
|
];
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_indexService.Dispose();
|
|
_installService?.Dispose();
|
|
}
|
|
}
|
|
|
|
internal sealed class ApplicationInfoService : IApplicationInfoService
|
|
{
|
|
private const string DefaultCodename = "Administrate";
|
|
|
|
public string GetAppVersionText()
|
|
{
|
|
// 浼樺厛浠庣幆澧冨彉閲忚鍙栵紙Launcher 浼犻€掞級
|
|
var envVersion = Environment.GetEnvironmentVariable(LanMountainDesktop.Shared.Contracts.Launcher.LauncherIpcConstants.VersionEnvVar);
|
|
if (!string.IsNullOrWhiteSpace(envVersion))
|
|
{
|
|
return envVersion;
|
|
}
|
|
|
|
// Fallback: read from application assembly.
|
|
var assembly = typeof(App).Assembly;
|
|
var informationalVersion = assembly
|
|
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?
|
|
.InformationalVersion;
|
|
if (!string.IsNullOrWhiteSpace(informationalVersion))
|
|
{
|
|
var normalizedInformationalVersion = informationalVersion.Split('+', 2)[0].Trim();
|
|
if (!string.IsNullOrWhiteSpace(normalizedInformationalVersion))
|
|
{
|
|
return normalizedInformationalVersion;
|
|
}
|
|
}
|
|
|
|
var version = assembly.GetName().Version;
|
|
if (version is null)
|
|
{
|
|
return "0.0.0";
|
|
}
|
|
|
|
if (version.Revision >= 0)
|
|
{
|
|
return version.ToString(4);
|
|
}
|
|
|
|
if (version.Build >= 0)
|
|
{
|
|
return version.ToString(3);
|
|
}
|
|
|
|
if (version.Minor >= 0)
|
|
{
|
|
return version.ToString(2);
|
|
}
|
|
|
|
return version.ToString();
|
|
}
|
|
|
|
public string GetAppCodenameText()
|
|
{
|
|
// 浼樺厛浠庣幆澧冨彉閲忚鍙栵紙Launcher 浼犻€掞級
|
|
var envCodename = Environment.GetEnvironmentVariable(LanMountainDesktop.Shared.Contracts.Launcher.LauncherIpcConstants.CodenameEnvVar);
|
|
if (!string.IsNullOrWhiteSpace(envCodename))
|
|
{
|
|
return envCodename;
|
|
}
|
|
|
|
// Fallback: use default codename.
|
|
return DefaultCodename;
|
|
}
|
|
|
|
public AppRenderBackendInfo GetRenderBackendInfo()
|
|
{
|
|
return AppRenderBackendDiagnostics.Detect();
|
|
}
|
|
}
|
|
|
|
internal sealed class SettingsFacadeService : ISettingsFacadeService, IDisposable
|
|
{
|
|
private readonly UpdateSettingsService _updateSettingsService;
|
|
private readonly PluginCatalogSettingsService _pluginCatalogSettingsService;
|
|
private readonly PluginManagementSettingsService _pluginManagementSettingsService;
|
|
private readonly WeatherSettingsService _weatherSettingsService;
|
|
|
|
public SettingsFacadeService(PluginRuntimeService? pluginRuntimeService = null)
|
|
{
|
|
Settings = new SettingsService();
|
|
Catalog = new SettingsCatalogService();
|
|
Grid = new GridSettingsService(Settings);
|
|
Wallpaper = new WallpaperSettingsService(Settings);
|
|
WallpaperMedia = new WallpaperMediaService();
|
|
Theme = new ThemeAppearanceService(Settings);
|
|
StatusBar = new StatusBarSettingsService(Settings);
|
|
TextCapsule = new TextCapsuleSettingsService(Settings);
|
|
_weatherSettingsService = new WeatherSettingsService(Settings);
|
|
Weather = _weatherSettingsService;
|
|
Region = new RegionSettingsService(Settings);
|
|
Privacy = new PrivacySettingsService(Settings);
|
|
_updateSettingsService = new UpdateSettingsService(Settings);
|
|
Update = _updateSettingsService;
|
|
LauncherCatalog = new LauncherCatalogService();
|
|
LauncherPolicy = new LauncherPolicyService();
|
|
_pluginManagementSettingsService = new PluginManagementSettingsService(Settings, pluginRuntimeService);
|
|
PluginManagement = _pluginManagementSettingsService;
|
|
_pluginCatalogSettingsService = new PluginCatalogSettingsService(pluginRuntimeService);
|
|
PluginCatalog = _pluginCatalogSettingsService;
|
|
ApplicationInfo = new ApplicationInfoService();
|
|
}
|
|
|
|
public ISettingsService Settings { get; }
|
|
|
|
public ISettingsCatalog Catalog { get; }
|
|
|
|
public IGridSettingsService Grid { get; }
|
|
|
|
public IWallpaperSettingsService Wallpaper { get; }
|
|
|
|
public IWallpaperMediaService WallpaperMedia { get; }
|
|
|
|
public IThemeAppearanceService Theme { get; }
|
|
|
|
public IStatusBarSettingsService StatusBar { get; }
|
|
|
|
public ITextCapsuleSettingsService TextCapsule { get; }
|
|
|
|
public IWeatherSettingsService Weather { get; }
|
|
|
|
public IRegionSettingsService Region { get; }
|
|
|
|
public IPrivacySettingsService Privacy { get; }
|
|
|
|
public IUpdateSettingsService Update { get; }
|
|
|
|
public ILauncherCatalogService LauncherCatalog { get; }
|
|
|
|
public ILauncherPolicyService LauncherPolicy { get; }
|
|
|
|
public IPluginManagementSettingsService PluginManagement { get; }
|
|
|
|
public IPluginCatalogSettingsService PluginCatalog { get; }
|
|
|
|
public IApplicationInfoService ApplicationInfo { get; }
|
|
|
|
public void BindPluginRuntime(PluginRuntimeService? pluginRuntimeService)
|
|
{
|
|
_pluginManagementSettingsService.SetPluginRuntime(pluginRuntimeService);
|
|
_pluginCatalogSettingsService.SetPluginRuntime(pluginRuntimeService);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_weatherSettingsService.Dispose();
|
|
_updateSettingsService.Dispose();
|
|
_pluginCatalogSettingsService.Dispose();
|
|
}
|
|
}
|