mirror of
https://github.com/denglihong2007/CRSim
synced 2026-08-05 08:55:52 +08:00
277 lines
11 KiB
C#
277 lines
11 KiB
C#
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;
|
||
}
|
||
} |