diff --git a/CRSim.Core/Abstractions/PluginBase.cs b/CRSim.Core/Abstractions/PluginBase.cs index 305238f..0056c3d 100644 --- a/CRSim.Core/Abstractions/PluginBase.cs +++ b/CRSim.Core/Abstractions/PluginBase.cs @@ -10,6 +10,8 @@ public abstract class PluginBase /// public PluginInfo Info { get; internal set; } = null!; + public abstract Type ViewModel { get; } + public abstract Type View { get; } /// /// 初始化插件。一般在这个方法中完成插件的各项服务的注册。 /// diff --git a/CRSim.Core/CRSim.Core.csproj b/CRSim.Core/CRSim.Core.csproj index b87a12d..df72598 100644 --- a/CRSim.Core/CRSim.Core.csproj +++ b/CRSim.Core/CRSim.Core.csproj @@ -5,16 +5,13 @@ 10.0.17763.0 enable enable - partial AnyCPU;x64 - - - + diff --git a/CRSim.Core/Models/JsonSerializationContext.cs b/CRSim.Core/Models/JsonSerializationContext.cs index 8f5691a..6e431a4 100644 --- a/CRSim.Core/Models/JsonSerializationContext.cs +++ b/CRSim.Core/Models/JsonSerializationContext.cs @@ -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))] + [JsonSerializable(typeof(StyleInfo))] + [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] + public partial class JsonContextWithCamelCase : JsonSerializerContext + { + } } + diff --git a/CRSim.Core/Models/Plugin/PluginInfo.cs b/CRSim.Core/Models/Plugin/PluginInfo.cs index dbfd2f7..62c8fc8 100644 --- a/CRSim.Core/Models/Plugin/PluginInfo.cs +++ b/CRSim.Core/Models/Plugin/PluginInfo.cs @@ -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; diff --git a/CRSim.Core/Models/Plugin/PluginManifest.cs b/CRSim.Core/Models/Plugin/PluginManifest.cs index 476afc1..a9874cf 100644 --- a/CRSim.Core/Models/Plugin/PluginManifest.cs +++ b/CRSim.Core/Models/Plugin/PluginManifest.cs @@ -1,7 +1,4 @@ -using CommunityToolkit.Mvvm.ComponentModel; -using System.Text.Json.Serialization; - -namespace CRSim.Core.Models.Plugin; +namespace CRSim.Core.Models.Plugin; /// /// 插件元数据 @@ -12,54 +9,45 @@ public class PluginManifest /// 入口程序集。加载插件时,将在此入口程序集中搜索插件类。 /// /// MyPlugin.dll - [JsonPropertyName("entranceAssembly")] public string EntranceAssembly { get; set; } = ""; /// /// 插件显示名称。 /// - [JsonPropertyName("name")] public string Name { get; set; } = ""; /// /// 插件ID。 /// - [JsonPropertyName("id")] public string Id { get; set; } = ""; /// /// 插件自述。 /// - [JsonPropertyName("description")] public string Description { get; set; } = ""; /// /// 项目 Url /// - [JsonPropertyName("url")] public string? Url { get; set; } /// /// 插件版本 /// - [JsonPropertyName("version")] public string Version { get; set; } = ""; /// /// 插件目标 CRSim 版本 /// - [JsonPropertyName("apiVersion")] public string ApiVersion { get; set; } = ""; /// /// 插件作者 /// - [JsonPropertyName("author")] public string Author { get; set; } = ""; /// /// 插件类型 /// - [JsonPropertyName("type")] public string Type { get; set; } = ""; } \ No newline at end of file diff --git a/CRSim.Core/Models/StyleInfo.cs b/CRSim.Core/Models/StyleInfo.cs index 981c8ee..cee4ce3 100644 --- a/CRSim.Core/Models/StyleInfo.cs +++ b/CRSim.Core/Models/StyleInfo.cs @@ -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; } = ""; } } diff --git a/CRSim.Core/Services/NetworkService.cs b/CRSim.Core/Services/NetworkService.cs index 7172ec5..34bbaee 100644 --- a/CRSim.Core/Services/NetworkService.cs +++ b/CRSim.Core/Services/NetworkService.cs @@ -144,7 +144,7 @@ namespace CRSim.Core.Services { var client = new HttpClient(); var response = client.GetStringAsync(url).Result; - return JsonSerializer.Deserialize>(response); + return JsonSerializer.Deserialize(response, JsonContextWithCamelCase.Default.ListPluginManifest); } catch { diff --git a/CRSim.Core/Services/PluginService.cs b/CRSim.Core/Services/PluginService.cs index f475654..2e582cb 100644 --- a/CRSim.Core/Services/PluginService.cs +++ b/CRSim.Core/Services/PluginService.cs @@ -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(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(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(p => + { + dispatcherQueue.TryEnqueue(() => + { + plugin.DownloadProgress = (int)p; + Console.WriteLine(p); + }); + })); } - catch(Exception e) + catch (Exception e) { Console.WriteLine(e.Message); plugin.DownloadProgress = 0; diff --git a/CRSim.Core/Utils/Downloader.cs b/CRSim.Core/Utils/Downloader.cs new file mode 100644 index 0000000..c671144 --- /dev/null +++ b/CRSim.Core/Utils/Downloader.cs @@ -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? 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); + } + } + } + } +} diff --git a/CRSim.ExamplePlugin/CRSim.ExamplePlugin.csproj b/CRSim.ExamplePlugin/CRSim.ExamplePlugin.csproj index 741e289..7d48045 100644 --- a/CRSim.ExamplePlugin/CRSim.ExamplePlugin.csproj +++ b/CRSim.ExamplePlugin/CRSim.ExamplePlugin.csproj @@ -9,7 +9,6 @@ AnyCPU;x64 enable enable - partial @@ -23,10 +22,10 @@ PreserveNewest - + PreserveNewest - + PreserveNewest diff --git a/CRSim.ExamplePlugin/Plugin.cs b/CRSim.ExamplePlugin/Plugin.cs index 85867a8..d80be26 100644 --- a/CRSim.ExamplePlugin/Plugin.cs +++ b/CRSim.ExamplePlugin/Plugin.cs @@ -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); diff --git a/CRSim.ExamplePlugin/manifest.json b/CRSim.ExamplePlugin/manifest.json new file mode 100644 index 0000000..0df8be5 --- /dev/null +++ b/CRSim.ExamplePlugin/manifest.json @@ -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": "这是一个示例插件。" +} \ No newline at end of file diff --git a/CRSim.ExamplePlugin/manifest.yml b/CRSim.ExamplePlugin/manifest.yml deleted file mode 100644 index 8309aa0..0000000 --- a/CRSim.ExamplePlugin/manifest.yml +++ /dev/null @@ -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: "这是一个示例插件。" \ No newline at end of file diff --git a/CRSim.ExamplePlugin/style.json b/CRSim.ExamplePlugin/style.json new file mode 100644 index 0000000..018c1d5 --- /dev/null +++ b/CRSim.ExamplePlugin/style.json @@ -0,0 +1,5 @@ +{ + "parameters": [ "station", "text" ], + "region": "广元", + "type": "车站大屏" +} \ No newline at end of file diff --git a/CRSim.ExamplePlugin/style.yml b/CRSim.ExamplePlugin/style.yml deleted file mode 100644 index 89562e7..0000000 --- a/CRSim.ExamplePlugin/style.yml +++ /dev/null @@ -1,5 +0,0 @@ -parameters : - - "station" - - "text" -region: "广元" -type: "车站大屏" \ No newline at end of file diff --git a/CRSim.ScreenSimulator/CRSim.ScreenSimulator.csproj b/CRSim.ScreenSimulator/CRSim.ScreenSimulator.csproj index 3f5ef60..d0529ec 100644 --- a/CRSim.ScreenSimulator/CRSim.ScreenSimulator.csproj +++ b/CRSim.ScreenSimulator/CRSim.ScreenSimulator.csproj @@ -4,14 +4,13 @@ net9.0-windows10.0.19041.0 10.0.17763.0 enable - partial true AnyCPU;x64 enable - + diff --git a/CRSim/App.xaml.cs b/CRSim/App.xaml.cs index a8ba0f1..7a3c95c 100644 --- a/CRSim/App.xaml.cs +++ b/CRSim/App.xaml.cs @@ -46,6 +46,7 @@ AppHost.Services.GetRequiredService().Start(); MainWindow = AppHost.Services.GetService(); MainWindow?.Activate(); + var win = AppHost.Services.GetService(); } [System.Runtime.InteropServices.DllImport("kernel32.dll")] diff --git a/CRSim/CRSim.csproj b/CRSim/CRSim.csproj index 82ce86a..cfea50b 100644 --- a/CRSim/CRSim.csproj +++ b/CRSim/CRSim.csproj @@ -13,8 +13,6 @@ enable None preview - partial - true 2.3.102.0 @@ -56,10 +54,10 @@ - - - - + + + + @@ -131,11 +129,12 @@ False True False - True + False 10.0.17763.0 Assets\CRSimIcon.ico False CRSimIcon.png true + False \ No newline at end of file diff --git a/CRSim/ViewModels/PluginManagementPageViewModel.cs b/CRSim/ViewModels/PluginManagementPageViewModel.cs index 6365747..58ba52f 100644 --- a/CRSim/ViewModels/PluginManagementPageViewModel.cs +++ b/CRSim/ViewModels/PluginManagementPageViewModel.cs @@ -15,7 +15,7 @@ public partial class PluginManagementPageViewModel : ObservableObject [ObservableProperty] public partial PluginInfo? SelectedPlugin { get; set; } - public ObservableCollection Plugins = []; + public ObservableCollection 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] diff --git a/CRSim/ViewModels/ScreenSimulatorPageViewModel.cs b/CRSim/ViewModels/ScreenSimulatorPageViewModel.cs index 4496c0c..6f4fc97 100644 --- a/CRSim/ViewModels/ScreenSimulatorPageViewModel.cs +++ b/CRSim/ViewModels/ScreenSimulatorPageViewModel.cs @@ -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, diff --git a/CRSim/Views/PluginManagementPage.xaml b/CRSim/Views/PluginManagementPage.xaml index 03a8379..0876b9d 100644 --- a/CRSim/Views/PluginManagementPage.xaml +++ b/CRSim/Views/PluginManagementPage.xaml @@ -47,7 +47,7 @@ - + diff --git a/CRSim/Views/PluginManagementPage.xaml.cs b/CRSim/Views/PluginManagementPage.xaml.cs index 43a2f0f..173b39c 100644 --- a/CRSim/Views/PluginManagementPage.xaml.cs +++ b/CRSim/Views/PluginManagementPage.xaml.cs @@ -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"); + } }