chore: 一些杂乱的更改

This commit is contained in:
denglihong2007
2025-07-26 18:02:55 +08:00
parent 3d33246bf7
commit c51f151c58
22 changed files with 137 additions and 85 deletions

View File

@@ -10,6 +10,8 @@ public abstract class PluginBase
/// </summary>
public PluginInfo Info { get; internal set; } = null!;
public abstract Type ViewModel { get; }
public abstract Type View { get; }
/// <summary>
/// 初始化插件。一般在这个方法中完成插件的各项服务的注册。
/// </summary>

View File

@@ -5,16 +5,13 @@
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TrimMode>partial</TrimMode>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Downloader" Version="4.0.2" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.6" />
<PackageReference Include="YamlDotNet" Version="16.3.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.7" />
</ItemGroup>
</Project>

View File

@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using CRSim.Core.Models.Plugin;
using System.Text.Json.Serialization;
namespace CRSim.Core.Models
{
@@ -7,4 +8,12 @@ namespace CRSim.Core.Models
public partial class JsonContext : JsonSerializerContext
{
}
[JsonSerializable(typeof(List<PluginManifest>))]
[JsonSerializable(typeof(StyleInfo))]
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
public partial class JsonContextWithCamelCase : JsonSerializerContext
{
}
}

View File

@@ -1,13 +1,10 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CRSim.Core.Abstractions;
using CRSim.Core.Enums;
using Downloader;
namespace CRSim.Core.Models.Plugin;
public partial class PluginInfo : ObservableRecipient
{
public DownloadService? DownloadService;
[ObservableProperty]
private int _downloadProgress = 0;

View File

@@ -1,7 +1,4 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System.Text.Json.Serialization;
namespace CRSim.Core.Models.Plugin;
namespace CRSim.Core.Models.Plugin;
/// <summary>
/// 插件元数据
@@ -12,54 +9,45 @@ public class PluginManifest
/// 入口程序集。加载插件时,将在此入口程序集中搜索插件类。
/// </summary>
/// <example>MyPlugin.dll</example>
[JsonPropertyName("entranceAssembly")]
public string EntranceAssembly { get; set; } = "";
/// <summary>
/// 插件显示名称。
/// </summary>
[JsonPropertyName("name")]
public string Name { get; set; } = "";
/// <summary>
/// 插件ID。
/// </summary>
[JsonPropertyName("id")]
public string Id { get; set; } = "";
/// <summary>
/// 插件自述。
/// </summary>
[JsonPropertyName("description")]
public string Description { get; set; } = "";
/// <summary>
/// 项目 Url
/// </summary>
[JsonPropertyName("url")]
public string? Url { get; set; }
/// <summary>
/// 插件版本
/// </summary>
[JsonPropertyName("version")]
public string Version { get; set; } = "";
/// <summary>
/// 插件目标 CRSim 版本
/// </summary>
[JsonPropertyName("apiVersion")]
public string ApiVersion { get; set; } = "";
/// <summary>
/// 插件作者
/// </summary>
[JsonPropertyName("author")]
public string Author { get; set; } = "";
/// <summary>
/// 插件类型
/// </summary>
[JsonPropertyName("type")]
public string Type { get; set; } = "";
}

View File

@@ -4,8 +4,8 @@ namespace CRSim.Core.Models
{
public class StyleInfo
{
public required string[] Parameters;
public required string Type;
public required string Region;
public string[] Parameters { get; set; } = [];
public string Type { get; set; } = "";
public string Region { get; set; } = "";
}
}

View File

@@ -144,7 +144,7 @@ namespace CRSim.Core.Services
{
var client = new HttpClient();
var response = client.GetStringAsync(url).Result;
return JsonSerializer.Deserialize<List<PluginManifest>>(response);
return JsonSerializer.Deserialize(response, JsonContextWithCamelCase.Default.ListPluginManifest);
}
catch
{

View File

@@ -4,32 +4,29 @@ using CRSim.Core.Enums;
using CRSim.Core.Models;
using CRSim.Core.Models.Plugin;
using CRSim.Core.Utils;
using Downloader;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Reflection;
using System.Text.Json;
using Windows.System;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace CRSim.Core.Services;
public class PluginService : IPluginService
{
public static readonly string PluginManifestFileName = "manifest.yml";
public static readonly string PluginManifestFileName = "manifest.json";
public static readonly string StyleInfoFileName = "style.json";
private string IndexUrl => $"{_settings.ApiUri}/GetFile?fileName=plugins.json";
public static void InitializePlugins(HostBuilderContext context, IServiceCollection services,string externalPluginPath)
{
if(!Directory.Exists(AppPaths.PluginsRootPath))
Console.WriteLine("开始加载插件");
if (!Directory.Exists(AppPaths.PluginsRootPath))
{
Directory.CreateDirectory(AppPaths.PluginsRootPath);
}
var deserializer = new DeserializerBuilder()
.IgnoreUnmatchedProperties()
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.Build();
var pluginDirs = Directory.EnumerateDirectories(AppPaths.PluginsRootPath);
if(!string.IsNullOrEmpty(externalPluginPath))
{
@@ -38,6 +35,7 @@ public class PluginService : IPluginService
foreach (var pluginDir in pluginDirs)
{
Console.WriteLine($"当前插件搜寻目录: {pluginDir}");
if (string.IsNullOrWhiteSpace(pluginDir))
continue;
var manifestPath = Path.Combine(pluginDir, PluginManifestFileName);
@@ -47,17 +45,26 @@ public class PluginService : IPluginService
}
var manifestYaml = File.ReadAllText(manifestPath);
var manifest = deserializer.Deserialize<PluginManifest?>(manifestYaml);
if (manifest == null)
PluginManifest manifest = new();
try
{
Console.WriteLine(manifestYaml);
manifest = JsonSerializer.Deserialize(manifestYaml,JsonContextWithCamelCase.Default.PluginManifest);
Console.WriteLine("成功解析YAML文件: " + manifestPath);
}
catch (Exception ex)
{
Console.WriteLine("YAML解析失败");
Console.WriteLine(ex.ToString());
continue;
}
Console.WriteLine("找到插件: " + manifest.Id);
var info = new PluginInfo
{
Manifest = manifest,
PluginFolderPath = Path.GetFullPath(pluginDir),
RealIconPath = Path.Combine(Path.GetFullPath(pluginDir), "icon.png"),
StyleInfo = manifest.Type == "ScreenStyle" ? deserializer.Deserialize<StyleInfo?>(File.ReadAllText(Path.Combine(Path.GetFullPath(pluginDir), "style.yml"))) : null,
StyleInfo = JsonSerializer.Deserialize(File.ReadAllText(Path.Combine(Path.GetFullPath(pluginDir), StyleInfoFileName)),JsonContextWithCamelCase.Default.StyleInfo),
};
if (info.IsUninstalling)
{
@@ -81,6 +88,7 @@ public class PluginService : IPluginService
var pluginDir = info.PluginFolderPath;
try
{
Console.WriteLine("尝试加载插件: " + manifest.Id);
var fullPath = Path.GetFullPath(Path.Combine(pluginDir, manifest.EntranceAssembly));
var asm = Assembly.LoadFrom(fullPath);
var entrance = asm.ExportedTypes.FirstOrDefault(x =>
@@ -102,10 +110,11 @@ public class PluginService : IPluginService
services.AddSingleton(typeof(PluginBase), entranceObj);
services.AddSingleton(entrance, entranceObj);
info.LoadStatus = PluginLoadStatus.Loaded;
Console.WriteLine($"Initialize plugin: {pluginDir} ({manifest.Version})");
Console.WriteLine($"加载插件成功: {pluginDir} ({manifest.Version})");
}
catch (Exception ex)
{
Console.WriteLine($"加载插件失败: {ex}");
info.Exception = ex;
info.LoadStatus = PluginLoadStatus.Error;
}
@@ -145,20 +154,21 @@ public class PluginService : IPluginService
Directory.CreateDirectory(tempDir);
var packagePath = Path.Combine(tempDir, $"{id}{IPluginService.PluginPackageExtension}");
DispatcherQueue dispatcherQueue = DispatcherQueue.GetForCurrentThread();
plugin.DownloadService = new DownloadService();
plugin.DownloadService.DownloadProgressChanged += (s, e) =>
{
dispatcherQueue.TryEnqueue(() =>
{
plugin.DownloadProgress = (int)(e.ProgressPercentage);
Console.WriteLine(e.ProgressPercentage);
});
};
var downloader = new Downloader();
plugin.DownloadProgress = 0;
try
{
await plugin.DownloadService.DownloadFileTaskAsync(packageUrl,packagePath);
await downloader.DownloadFileAsync(packageUrl, packagePath, new Progress<double>(p =>
{
dispatcherQueue.TryEnqueue(() =>
{
plugin.DownloadProgress = (int)p;
Console.WriteLine(p);
});
}));
}
catch(Exception e)
catch (Exception e)
{
Console.WriteLine(e.Message);
plugin.DownloadProgress = 0;

View File

@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CRSim.Core.Utils
{
public class Downloader
{
private readonly HttpClient _httpClient = new();
public async Task DownloadFileAsync(string url, string outputPath, IProgress<double>? progress = null)
{
using var response = await _httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
var totalBytes = response.Content.Headers.ContentLength ?? -1L;
var canReportProgress = totalBytes != -1 && progress != null;
await using var contentStream = await response.Content.ReadAsStreamAsync();
await using var fileStream = new FileStream(outputPath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true);
var buffer = new byte[8192];
long totalRead = 0;
int bytesRead;
while ((bytesRead = await contentStream.ReadAsync(buffer)) > 0)
{
await fileStream.WriteAsync(buffer.AsMemory(0, bytesRead));
totalRead += bytesRead;
if (canReportProgress)
{
double percentage = totalRead * 100d / totalBytes;
progress!.Report(percentage);
}
}
}
}
}

View File

@@ -9,7 +9,6 @@
<Platforms>AnyCPU;x64</Platforms>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TrimMode>partial</TrimMode>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\CRSim.Core\CRSim.Core.csproj">
@@ -23,10 +22,10 @@
<None Update="icon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="manifest.yml">
<None Update="manifest.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="style.yml">
<None Update="style.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

View File

@@ -11,8 +11,8 @@ namespace CRSim.ExamplePlugin;
[PluginEntrance]
public class Plugin : PluginBase
{
public Type ViewModel => typeof(ScreenViewModel);
public Type View => typeof(ScreenView);
public override Type ViewModel => typeof(ScreenViewModel);
public override Type View => typeof(ScreenView);
public override void Initialize(HostBuilderContext context, IServiceCollection services)
{
services.AddTransient(ViewModel);

View File

@@ -0,0 +1,11 @@
{
"entranceAssembly": "CRSim.ExamplePlugin.dll",
"name": "示例插件",
"id": "CRSim.ExamplePlugin",
"url": "https://crsim.tech",
"version": "0.0.0.0",
"apiVersion": "3.0.0.0",
"author": "电排骨",
"type": "ScreenStyle",
"description": "这是一个示例插件。"
}

View File

@@ -1,9 +0,0 @@
entranceAssembly: "CRSim.ExamplePlugin.dll"
name: "示例插件"
id: "denglihong2007.ExamplePlugin"
url: "https://crsim.tech"
version: "0.0.0.0"
apiVersion: "3.0.0.0"
author: "电排骨"
type: "ScreenStyle"
description: "这是一个示例插件。"

View File

@@ -0,0 +1,5 @@
{
"parameters": [ "station", "text" ],
"region": "广元",
"type": "车站大屏"
}

View File

@@ -1,5 +0,0 @@
parameters :
- "station"
- "text"
region: "广元"
type: "车站大屏"

View File

@@ -4,14 +4,13 @@
<TargetFramework>net9.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<Nullable>enable</Nullable>
<TrimMode>partial</TrimMode>
<UseWPF>true</UseWPF>
<Platforms>AnyCPU;x64</Platforms>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.7" />
<PackageReference Include="PinYinConverterCore" Version="1.0.2" />
<PackageReference Include="PP.Wpf" Version="1.0.8" />
</ItemGroup>

View File

@@ -46,6 +46,7 @@
AppHost.Services.GetRequiredService<ITimeService>().Start();
MainWindow = AppHost.Services.GetService<MainWindow>();
MainWindow?.Activate();
var win = AppHost.Services.GetService<StyleManager>();
}
[System.Runtime.InteropServices.DllImport("kernel32.dll")]

View File

@@ -13,8 +13,6 @@
<Nullable>enable</Nullable>
<WindowsPackageType>None</WindowsPackageType>
<LangVersion>preview</LangVersion>
<TrimMode>partial</TrimMode>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<Version>2.3.102.0</Version>
</PropertyGroup>
<ItemGroup>
@@ -56,10 +54,10 @@
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
<PackageReference Include="EPPlus" Version="8.0.7" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.6" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
<PackageReference Include="EPPlus" Version="8.0.8" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.7" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.7" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4654" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="3.0.0" />
</ItemGroup>
@@ -131,11 +129,12 @@
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">False</PublishTrimmed>
<SupportedOSPlatformVersion>10.0.17763.0</SupportedOSPlatformVersion>
<ApplicationIcon>Assets\CRSimIcon.ico</ApplicationIcon>
<SignAssembly>False</SignAssembly>
<PackageIcon>CRSimIcon.png</PackageIcon>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PublishAot>False</PublishAot>
</PropertyGroup>
</Project>

View File

@@ -15,7 +15,7 @@ public partial class PluginManagementPageViewModel : ObservableObject
[ObservableProperty]
public partial PluginInfo? SelectedPlugin { get; set; }
public ObservableCollection<PluginInfo> Plugins = [];
public ObservableCollection<PluginInfo> Plugins = IPluginService.OnlinePlugins;
private IPluginService _pluginService;
@@ -66,12 +66,14 @@ public partial class PluginManagementPageViewModel : ObservableObject
}
[RelayCommand]
public void SourceSelected(SelectorBarItem selectorBarItem)
public void SourceSelected(object args)
{
SelectedPlugin = null;
Plugins = (selectorBarItem?.Text == "本地" || selectorBarItem is null)
? IPluginService.OnlinePlugins : IPluginService.LoadedPlugins;
OnPropertyChanged(nameof(Plugins));
if(args is SelectorBarItem selectorBarItem)
{
SelectedPlugin = null;
Plugins = (selectorBarItem?.Text == "本地") ? IPluginService.OnlinePlugins : IPluginService.LoadedPlugins;
OnPropertyChanged(nameof(Plugins));
}
}
[RelayCommand]

View File

@@ -1,4 +1,5 @@
using CRSim.Core.Models.Plugin;
using System.Diagnostics.CodeAnalysis;
namespace CRSim.ViewModels;
@@ -132,7 +133,7 @@ public partial class ScreenSimulatorPageViewModel(IServiceProvider serviceProvid
[RelayCommand]
public async Task StartSimulation()
{
StyleManager.ShowWindow(((dynamic)SelectedStylePlugin).View,
StyleManager.ShowWindow(SelectedStylePlugin.View,
databaseService.GetStationByName(SelectedStationName),
TicketCheckNeeded ? SelectedTicketCheck : string.Empty,
PlatformNeeded ? SelectedPlatformName : string.Empty,

View File

@@ -47,7 +47,7 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<SelectorBar x:Name="SelectorBar" Grid.Column="0">
<SelectorBar x:Name="SelectorBar" Grid.Column="0" SelectionChanged="SelectorBar_SelectionChanged">
<interactivity:Interaction.Behaviors>
<interactivity:EventTriggerBehavior EventName="SelectionChanged">
<interactivity:InvokeCommandAction Command="{x:Bind ViewModel.SourceSelectedCommand}" CommandParameter="{x:Bind SelectorBar.SelectedItem,Mode=OneWay}"/>

View File

@@ -11,4 +11,9 @@ public sealed partial class PluginManagementPage : Page
InitializeComponent();
DataContext = ViewModel;
}
private void SelectorBar_SelectionChanged(SelectorBar sender, SelectorBarSelectionChangedEventArgs args)
{
Console.WriteLine($"SelectorBar selection changed");
}
}