Files
CRSim/CRSim.Core/Services/PluginService.cs
2026-01-01 00:03:12 +08:00

277 lines
11 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using CRSim.Core.Abstractions;
using CRSim.Core.Attributes;
using CRSim.Core.Enums;
using CRSim.Core.Models;
using CRSim.Core.Models.Plugin;
using CRSim.Core.Utils;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Reflection;
using System.Text.Json;
using Windows.System;
using Downloader;
namespace CRSim.Core.Services;
public class PluginService(INetworkService networkService, ISettingsService settingsService) : IPluginService
{
public static readonly string PluginManifestFileName = "manifest.json";
public static readonly string StyleInfoFileName = "style.json";
private string IndexUrl => $"{_settings.Api.BaseApi}/GetFile?fileName=plugins.json";
public static void InitializePlugins(HostBuilderContext context, IServiceCollection services,string externalPluginPath)
{
Console.WriteLine("开始加载插件");
if (!Directory.Exists(AppPaths.PluginsRootPath))
{
Directory.CreateDirectory(AppPaths.PluginsRootPath);
}
var pluginDirs = Directory.EnumerateDirectories(AppPaths.PluginsRootPath);
if(!string.IsNullOrEmpty(externalPluginPath))
{
pluginDirs = pluginDirs.Append(externalPluginPath);
}
foreach (var pluginDir in pluginDirs)
{
Console.WriteLine($"当前插件搜寻目录: {pluginDir}");
if (string.IsNullOrWhiteSpace(pluginDir))
continue;
var manifestPath = Path.Combine(pluginDir, PluginManifestFileName);
if (!File.Exists(manifestPath))
{
continue;
}
var manifestYaml = File.ReadAllText(manifestPath);
PluginManifest manifest = new();
try
{
manifest = JsonSerializer.Deserialize(manifestYaml,JsonContextWithCamelCase.Default.PluginManifest);
Console.WriteLine("成功解析JSON文件: " + manifestPath);
}
catch (Exception ex)
{
Console.WriteLine("JSON解析失败");
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" ? JsonSerializer.Deserialize(File.ReadAllText(Path.Combine(Path.GetFullPath(pluginDir), StyleInfoFileName)),JsonContextWithCamelCase.Default.StyleInfo) : null,
Data = manifest.Type == "StationData" ? JsonSerializer.Deserialize(File.ReadAllText(Path.Combine(Path.GetFullPath(pluginDir), manifest.EntranceAssembly)), JsonContext.Default.Json) : null,
};
if (info.IsUninstalling)
{
Directory.Delete(pluginDir, true);
continue;
}
if (!info.IsEnabled)
{
info.LoadStatus = PluginLoadStatus.Disabled;
}
IPluginService.LoadedPluginsInternal.Add(info);
}
foreach (var info in IPluginService.LoadedPluginsInternal.Where(x => x.Manifest.Type == "StationData"))
{
if (info.LoadStatus != PluginLoadStatus.Disabled)
{
info.LoadStatus = PluginLoadStatus.Loaded;
}
}
foreach (var info in IPluginService.LoadedPluginsInternal.Where(x => x.Manifest.Type == "ScreenStyle"))
{
if(info.LoadStatus == PluginLoadStatus.Disabled)
{
continue;
}
var manifest = info.Manifest;
var pluginDir = info.PluginFolderPath;
try
{
Console.WriteLine("尝试加载插件DLL: " + manifest.Id);
var fullPath = Path.GetFullPath(Path.Combine(pluginDir, manifest.EntranceAssembly));
var loadContext = new PluginLoadContext(fullPath);
var assembly = loadContext.LoadFromAssemblyPath(fullPath);
var entrance = assembly.ExportedTypes.FirstOrDefault(x =>
x.BaseType == typeof(PluginBase) ||
x.GetCustomAttributes().FirstOrDefault(a => a.GetType() == typeof(PluginEntrance)) != null);
if (entrance == null)
{
continue;
}
if (Activator.CreateInstance(entrance) is not PluginBase entranceObj)
{
continue;
}
entranceObj.Info = info;
entranceObj.Initialize(context, services);
services.AddSingleton(typeof(PluginBase), entranceObj);
services.AddSingleton(entrance, entranceObj);
info.LoadStatus = PluginLoadStatus.Loaded;
Console.WriteLine($"加载插件DLL成功: {pluginDir} ({manifest.Version})");
}
catch (Exception ex)
{
Console.WriteLine($"加载插件DLL失败: {ex}");
info.Exception = ex;
info.LoadStatus = PluginLoadStatus.Error;
}
}
}
private readonly Settings _settings = settingsService.GetSettings();
public async Task LoadOnlinePluginsAsync()
{
var pluginManifests = await networkService.GetOnlinePluginsAsync(IndexUrl);
IPluginService.OnlinePluginsInternal.Clear();
foreach (var Manifest in pluginManifests ?? [])
{
var localInfo = IPluginService.LoadedPluginsInternal.FirstOrDefault(x => x.Manifest.Id == Manifest.Id);
var info = new PluginInfo
{
Manifest = Manifest,
RealIconPath = $"{_settings.Api.BaseApi}/GetFile?fileName=icons/{Manifest.Id}.png",
LoadStatus = localInfo?.LoadStatus ?? PluginLoadStatus.NotLoaded,
PluginFolderPath = localInfo?.PluginFolderPath ?? string.Empty,
};
IPluginService.OnlinePluginsInternal.Add(info);
}
await Task.CompletedTask;
}
public async Task InstallPluginOnlineAsync(PluginInfo plugin)
{
var id = plugin.Manifest.Id;
var packageUrl = $"{_settings.Api.BaseApi}/GetFile?fileName=plugins/{id}{IPluginService.PluginPackageExtension}";
var tempDir = Path.Combine(AppPaths.TempPath, "Plugins", Guid.NewGuid().ToString());
Directory.CreateDirectory(tempDir);
var packagePath = Path.Combine(tempDir, $"{id}{IPluginService.PluginPackageExtension}");
DispatcherQueue dispatcherQueue = DispatcherQueue.GetForCurrentThread();
var downloader = new DownloadService();
plugin.DownloadProgress = 0;
try
{
downloader.DownloadProgressChanged += (s, e) =>
{
dispatcherQueue.TryEnqueue(() =>
{
plugin.DownloadProgress = (int)e.ProgressPercentage;
Console.WriteLine(e.ProgressPercentage);
});
};
await downloader.DownloadFileTaskAsync(packageUrl, packagePath);
}
catch (Exception e)
{
Console.WriteLine(e);
plugin.DownloadProgress = 0;
return;
}
// 解压到插件目录
var destDir = Path.Combine(AppPaths.PluginsRootPath, id);
if (Directory.Exists(destDir))
Directory.Delete(destDir, true);
Directory.CreateDirectory(destDir);
System.IO.Compression.ZipFile.ExtractToDirectory(packagePath, destDir);
// 清理临时文件
try { Directory.Delete(tempDir, true); } catch { }
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 List<(string, Station)> GetStationData()
{
var result = IPluginService.LoadedPluginsInternal
.Where(p => p.Manifest.Type == "StationData" && p.Data is not null)
.SelectMany(p => p.Data.Stations.Select(s => (p.Manifest.Name, s)))
.ToList();
return result;
}
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;
}
}