From e41ed32db4a798f3c7da7de0af20c3d3f55fbf7e Mon Sep 17 00:00:00 2001 From: denglihong2007 <98096191+denglihong2007@users.noreply.github.com> Date: Tue, 29 Jul 2025 15:06:01 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8F=92=E4=BB=B6=E5=AF=BC=E5=87=BA/?= =?UTF-8?q?=E5=AF=BC=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CRSim.Core/Abstractions/IPluginService.cs | 6 +- CRSim.Core/Models/Plugin/PluginManifest.cs | 2 +- CRSim.Core/Services/PluginService.cs | 83 ++++++++++++++++++- CRSim.ScreenSimulator/StyleManager.cs | 4 +- .../PluginManagementPageViewModel.cs | 54 +++++++++--- CRSim/Views/PluginManagementPage.xaml | 17 ++-- 6 files changed, 141 insertions(+), 25 deletions(-) diff --git a/CRSim.Core/Abstractions/IPluginService.cs b/CRSim.Core/Abstractions/IPluginService.cs index 3331853..44a2c2a 100644 --- a/CRSim.Core/Abstractions/IPluginService.cs +++ b/CRSim.Core/Abstractions/IPluginService.cs @@ -24,6 +24,8 @@ public interface IPluginService public static ObservableCollection OnlinePlugins => OnlinePluginsInternal; - Task InstallPluginAsync(PluginInfo plugin); - void LoadOnlinePlugins(); + Task InstallPluginOnlineAsync(PluginInfo plugin); + Task InstallPluginLocalAsync(string filePath); + Task PackPluginAsync(PluginInfo plugin, string filePath); + Task LoadOnlinePluginsAsync(); } \ No newline at end of file diff --git a/CRSim.Core/Models/Plugin/PluginManifest.cs b/CRSim.Core/Models/Plugin/PluginManifest.cs index a9874cf..e0679fa 100644 --- a/CRSim.Core/Models/Plugin/PluginManifest.cs +++ b/CRSim.Core/Models/Plugin/PluginManifest.cs @@ -29,7 +29,7 @@ public class PluginManifest /// /// 项目 Url /// - public string? Url { get; set; } + public string Url { get; set; } = ""; /// /// 插件版本 diff --git a/CRSim.Core/Services/PluginService.cs b/CRSim.Core/Services/PluginService.cs index 515b18f..6cd850b 100644 --- a/CRSim.Core/Services/PluginService.cs +++ b/CRSim.Core/Services/PluginService.cs @@ -64,7 +64,7 @@ public class PluginService : IPluginService Manifest = manifest, PluginFolderPath = Path.GetFullPath(pluginDir), RealIconPath = Path.Combine(Path.GetFullPath(pluginDir), "icon.png"), - StyleInfo = JsonSerializer.Deserialize(File.ReadAllText(Path.Combine(Path.GetFullPath(pluginDir), StyleInfoFileName)),JsonContextWithCamelCase.Default.StyleInfo), + StyleInfo = manifest.Type == "ScreenStyle" ? JsonSerializer.Deserialize(File.ReadAllText(Path.Combine(Path.GetFullPath(pluginDir), StyleInfoFileName)),JsonContextWithCamelCase.Default.StyleInfo) : null, }; if (info.IsUninstalling) { @@ -127,9 +127,8 @@ public class PluginService : IPluginService { _networkService = networkService; _settings = settingsService.GetSettings(); - LoadOnlinePlugins(); } - public void LoadOnlinePlugins() + public async Task LoadOnlinePluginsAsync() { var pluginManifests = _networkService.GetOnlinePlugins(IndexUrl); IPluginService.OnlinePluginsInternal.Clear(); @@ -145,9 +144,10 @@ public class PluginService : IPluginService }; IPluginService.OnlinePluginsInternal.Add(info); } + await Task.CompletedTask; } - public async Task InstallPluginAsync(PluginInfo plugin) + public async Task InstallPluginOnlineAsync(PluginInfo plugin) { var id = plugin.Manifest.Id; var packageUrl = $"{_settings.ApiUri}/GetFile?fileName=plugins/{id}{IPluginService.PluginPackageExtension}"; @@ -189,4 +189,79 @@ public class PluginService : IPluginService plugin.DownloadProgress = 0; plugin.RestartRequired = true; } + + public async Task InstallPluginLocalAsync(string filePath) + { + var tempDir = Path.Combine(AppPaths.TempPath, "Plugins", Guid.NewGuid().ToString()); + Directory.CreateDirectory(tempDir); + System.IO.Compression.ZipFile.ExtractToDirectory(filePath, tempDir); + + var manifestPath = Path.Combine(tempDir, PluginManifestFileName); + if (!File.Exists(manifestPath)) + { + throw new FileNotFoundException("ȱ manifest.json ļ"); + } + var manifestYaml = File.ReadAllText(manifestPath); + if( JsonSerializer.Deserialize(manifestYaml, JsonContextWithCamelCase.Default.PluginManifest) is not PluginManifest manifest || manifest.Id == "") + { + throw new InvalidDataException("е manifest.json ļʽȷ"); + } + + // ѹĿ¼ + var destDir = Path.Combine(AppPaths.PluginsRootPath, manifest.Id); + if (Directory.Exists(destDir)) + Directory.Delete(destDir, true); + System.IO.Compression.ZipFile.ExtractToDirectory(filePath, destDir); + + var info = new PluginInfo + { + Manifest = manifest, + PluginFolderPath = destDir, + RealIconPath = Path.Combine(destDir, "icon.png"), + StyleInfo = manifest.Type == "ScreenStyle" ? JsonSerializer.Deserialize(File.ReadAllText(Path.Combine(destDir, StyleInfoFileName)), JsonContextWithCamelCase.Default.StyleInfo) : null, + RestartRequired = true + }; + // ʱļ + try { Directory.Delete(tempDir, true); } catch { } + IPluginService.LoadedPluginsInternal.Add(info); + await Task.CompletedTask; + } + + public async Task PackPluginAsync(PluginInfo plugin, string filePath) + { + var pluginDir = plugin.PluginFolderPath; + if (string.IsNullOrWhiteSpace(pluginDir) || !Directory.Exists(pluginDir)) + throw new DirectoryNotFoundException("Ŀ¼ڡ"); + + // ʱĿ¼ڴ + var tempDir = Path.Combine(AppPaths.TempPath, "Pack", Guid.NewGuid().ToString()); + Directory.CreateDirectory(tempDir); + + // ļʱĿ¼ųָdll + foreach (var file in Directory.EnumerateFiles(pluginDir, "*", SearchOption.AllDirectories)) + { + var fileName = Path.GetFileName(file); + if (fileName.Equals("Microsoft.Windows.SDK.NET.dll", StringComparison.OrdinalIgnoreCase) || + fileName.Equals("WinRT.Runtime.dll", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + var relativePath = Path.GetRelativePath(pluginDir, file); + var destPath = Path.Combine(tempDir, relativePath); + Directory.CreateDirectory(Path.GetDirectoryName(destPath)!); + File.Copy(file, destPath, true); + } + + // ĿļѴڣɾ + if (File.Exists(filePath)) + File.Delete(filePath); + + // Ϊzip + System.IO.Compression.ZipFile.CreateFromDirectory(tempDir, filePath); + + // ʱĿ¼ + try { Directory.Delete(tempDir, true); } catch { } + + await Task.CompletedTask; + } } \ No newline at end of file diff --git a/CRSim.ScreenSimulator/StyleManager.cs b/CRSim.ScreenSimulator/StyleManager.cs index 3db32fc..710aea7 100644 --- a/CRSim.ScreenSimulator/StyleManager.cs +++ b/CRSim.ScreenSimulator/StyleManager.cs @@ -1,15 +1,15 @@ using CRSim.Core.Abstractions; +using CRSim.Core.Enums; using CRSim.Core.Models; using CRSim.Core.Models.Plugin; using CRSim.ScreenSimulator.Views; using System.Windows; using System.Windows.Controls; -using static System.Net.Mime.MediaTypeNames; namespace CRSim.ScreenSimulator { public class StyleManager { - public static List StyleInfos => [.. IPluginService.LoadedPlugins.Where(x => x.Manifest.Type == "ScreenStyle")]; + public static List StyleInfos => [.. IPluginService.LoadedPlugins.Where(x => x.Manifest.Type == "ScreenStyle" && x.LoadStatus == PluginLoadStatus.Loaded)]; public static IServiceProvider ServiceProvider; private static IDatabaseService _databaseService; public StyleManager(IEnumerable pluginBases,IServiceProvider serviceProvider,IDatabaseService databaseService) diff --git a/CRSim/ViewModels/PluginManagementPageViewModel.cs b/CRSim/ViewModels/PluginManagementPageViewModel.cs index 58ba52f..97ab75e 100644 --- a/CRSim/ViewModels/PluginManagementPageViewModel.cs +++ b/CRSim/ViewModels/PluginManagementPageViewModel.cs @@ -8,7 +8,6 @@ namespace CRSim.ViewModels; public partial class PluginManagementPageViewModel : ObservableObject { - [ObservableProperty] public partial string PageTitle { get; set; } = "插件管理"; @@ -17,11 +16,14 @@ public partial class PluginManagementPageViewModel : ObservableObject public ObservableCollection Plugins = IPluginService.OnlinePlugins; - private IPluginService _pluginService; + private readonly IPluginService _pluginService; + private readonly IDialogService _dialogService; - public PluginManagementPageViewModel(IPluginService pluginService) + public PluginManagementPageViewModel(IPluginService pluginService,IDialogService dialogService) { _pluginService = pluginService; + _dialogService = dialogService; + _pluginService.LoadOnlinePluginsAsync(); } [RelayCommand] @@ -34,12 +36,30 @@ public partial class PluginManagementPageViewModel : ObservableObject } [RelayCommand] - public async Task InstallPlugin() + public async Task InstallPluginOnline() { - await _pluginService.InstallPluginAsync(SelectedPlugin); + await _pluginService.InstallPluginOnlineAsync(SelectedPlugin); OnPropertyChanged(nameof(SelectedPlugin)); } - + [RelayCommand] + public async Task InstallPluginLocal() + { + if (await _dialogService.GetFileAsync([".crsp"]) is string filePath) + { + try + { + await _pluginService.InstallPluginLocalAsync(filePath); + } + catch (Exception ex) + { + await _dialogService.ShowTextAsync("安装失败", $"{ex}"); + } + } + else + { + await _dialogService.ShowMessageAsync("未选择文件", "请先选择一个插件包文件。"); + } + } [RelayCommand] public void UninstallPlugin() { @@ -53,11 +73,25 @@ public partial class PluginManagementPageViewModel : ObservableObject SelectedPlugin.IsUninstalling = false; OnPropertyChanged(nameof(SelectedPlugin)); } - + [RelayCommand] + public async Task PackPlugin() + { + if (SelectedPlugin == null) return; + string? filePath = await _dialogService.SaveFileAsync(".crsp", $"{SelectedPlugin.Manifest.Id}"); + if (filePath == null) return; + try + { + await _pluginService.PackPluginAsync(SelectedPlugin, filePath); + } + catch (Exception ex) + { + await _dialogService.ShowTextAsync("打包失败", $"{ex}"); + } + } [RelayCommand] public static void Restart() { - string exePath = Process.GetCurrentProcess().MainModule.FileName; + string exePath = Environment.ProcessPath; Process.Start(new ProcessStartInfo(exePath) { UseShellExecute = true @@ -87,8 +121,8 @@ public partial class PluginManagementPageViewModel : ObservableObject } [RelayCommand] - public void Refresh() + public async Task Refresh() { - _pluginService.LoadOnlinePlugins(); + await _pluginService.LoadOnlinePluginsAsync(); } } \ No newline at end of file diff --git a/CRSim/Views/PluginManagementPage.xaml b/CRSim/Views/PluginManagementPage.xaml index 0876b9d..e46b57a 100644 --- a/CRSim/Views/PluginManagementPage.xaml +++ b/CRSim/Views/PluginManagementPage.xaml @@ -45,7 +45,6 @@ - @@ -56,10 +55,13 @@ - - +