using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using AxCopilot.Handlers; using AxCopilot.Models; using AxCopilot.SDK; using AxCopilot.Services; namespace AxCopilot.Core; public class PluginHost { private readonly SettingsService _settings; private readonly CommandResolver _resolver; private readonly List _loadedPlugins = new List(); public IReadOnlyList LoadedPlugins => _loadedPlugins; public PluginHost(SettingsService settings, CommandResolver resolver) { _settings = settings; _resolver = resolver; } public void LoadAll() { _loadedPlugins.Clear(); foreach (PluginEntry item in _settings.Settings.Plugins.Where((PluginEntry p) => p.Enabled)) { LoadPlugin(item.Path); } string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "skills"); if (!Directory.Exists(path)) { return; } foreach (string item2 in Directory.EnumerateFiles(path, "*.skill.json")) { LoadJsonSkill(item2); } } private void LoadPlugin(string dllPath) { if (!File.Exists(dllPath)) { LogService.Warn("플러그인 파일 없음: " + dllPath); return; } try { Assembly assembly = Assembly.LoadFrom(dllPath); IEnumerable enumerable = from t in assembly.GetExportedTypes() where typeof(IActionHandler).IsAssignableFrom(t) && !t.IsAbstract select t; foreach (Type item in enumerable) { if (Activator.CreateInstance(item) is IActionHandler actionHandler) { _resolver.RegisterHandler(actionHandler); _loadedPlugins.Add(actionHandler); LogService.Info("플러그인 로드: " + actionHandler.Metadata.Name + " v" + actionHandler.Metadata.Version); } } } catch (Exception ex) { LogService.Error("플러그인 로드 실패 (" + dllPath + "): " + ex.Message); } } private void LoadJsonSkill(string skillPath) { try { IActionHandler actionHandler = JsonSkillLoader.Load(skillPath); if (actionHandler != null) { _resolver.RegisterHandler(actionHandler); _loadedPlugins.Add(actionHandler); LogService.Info("JSON 스킬 로드: " + actionHandler.Metadata.Name); } } catch (Exception ex) { LogService.Error("JSON 스킬 로드 실패 (" + skillPath + "): " + ex.Message); } } public void Reload() { LogService.Info("플러그인 전체 재로드 시작"); LoadAll(); } public int InstallFromZip(string zipPath) { if (!File.Exists(zipPath)) { LogService.Warn("플러그인 zip 파일 없음: " + zipPath); return 0; } string text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "plugins"); Directory.CreateDirectory(text); int num = 0; try { using ZipArchive zipArchive = ZipFile.OpenRead(zipPath); string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(zipPath); string text2 = Path.Combine(text, fileNameWithoutExtension); Directory.CreateDirectory(text2); foreach (ZipArchiveEntry entry in zipArchive.Entries) { if (!string.IsNullOrEmpty(entry.Name)) { string text3 = Path.Combine(text2, entry.Name); if (!Path.GetFullPath(text3).StartsWith(Path.GetFullPath(text2))) { LogService.Warn("플러그인 zip 경로 위험: " + entry.FullName); } else { entry.ExtractToFile(text3, overwrite: true); } } } foreach (string dllFile in Directory.EnumerateFiles(text2, "*.dll")) { if (!_settings.Settings.Plugins.Any((PluginEntry p) => p.Path == dllFile)) { _settings.Settings.Plugins.Add(new PluginEntry { Enabled = true, Path = dllFile }); LoadPlugin(dllFile); num++; } } string text4 = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "skills"); Directory.CreateDirectory(text4); foreach (string item in Directory.EnumerateFiles(text2, "*.skill.json")) { string text5 = Path.Combine(text4, Path.GetFileName(item)); File.Copy(item, text5, overwrite: true); LoadJsonSkill(text5); num++; } if (num > 0) { _settings.Save(); } LogService.Info($"플러그인 설치 완료: {zipPath} → {num}개 핸들러"); } catch (Exception ex) { LogService.Error("플러그인 zip 설치 실패: " + ex.Message); } return num; } public bool UninstallPlugin(string dllPath) { try { PluginEntry pluginEntry = _settings.Settings.Plugins.FirstOrDefault((PluginEntry p) => p.Path == dllPath); if (pluginEntry != null) { _settings.Settings.Plugins.Remove(pluginEntry); _settings.Save(); } string directoryName = Path.GetDirectoryName(dllPath); if (directoryName != null && Directory.Exists(directoryName)) { string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "plugins"); if (Path.GetFullPath(directoryName).StartsWith(Path.GetFullPath(path))) { Directory.Delete(directoryName, recursive: true); } } LogService.Info("플러그인 제거 완료: " + dllPath); return true; } catch (Exception ex) { LogService.Error("플러그인 제거 실패: " + ex.Message); return false; } } }