mirror of
https://github.com/Stone-Red-Code/DesktopMagic.git
synced 2026-09-04 00:46:12 +02:00
816 lines
30 KiB
C#
816 lines
30 KiB
C#
using DesktopMagic.DataContexts;
|
|
using DesktopMagic.Dialogs;
|
|
using DesktopMagic.Helpers;
|
|
|
|
using Modio;
|
|
using Modio.Filters;
|
|
using Modio.Models;
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Text.Json;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Threading;
|
|
using System.Xml.Linq;
|
|
|
|
using File = System.IO.File;
|
|
using Path = System.IO.Path;
|
|
|
|
namespace DesktopMagic.Plugins;
|
|
|
|
/// <summary>
|
|
/// Interaction logic for PluginManager.xaml
|
|
/// </summary>
|
|
public partial class PluginManager : Page
|
|
{
|
|
private const int ModIoGameId = 5665;
|
|
private const string ModIoApiKey = "88e6ea774c3a502b06114e7fee0829ac";
|
|
private readonly HttpClient httpClient = new();
|
|
private readonly PluginManagerDataContext pluginManagerDataContext = new();
|
|
private readonly string pluginsPath = Path.Combine(App.ApplicationDataPath, "Plugins");
|
|
private readonly string pluginDevelopmentPath = Path.Combine(App.ApplicationDataPath, "PluginDevelopment");
|
|
private readonly Manager _manager = Manager.Instance;
|
|
|
|
private bool changed = false;
|
|
|
|
private readonly DispatcherTimer searchTimer = new()
|
|
{
|
|
Interval = TimeSpan.FromMilliseconds(300),
|
|
};
|
|
|
|
private Client modIoClient;
|
|
|
|
public PluginManager()
|
|
{
|
|
Resources.MergedDictionaries.Add(App.LanguageDictionary);
|
|
|
|
string? modIoAccessToken = MainWindowDataContext.GetSettings().ModIoAccessToken;
|
|
|
|
if (modIoAccessToken is null)
|
|
{
|
|
modIoClient = new Client(new Credentials(ModIoApiKey));
|
|
App.Logger.LogInfo("Initialized mod.io client without authentication", source: "PluginManager");
|
|
}
|
|
else
|
|
{
|
|
modIoClient = new Client(new Credentials(ModIoApiKey, modIoAccessToken));
|
|
pluginManagerDataContext.IsAuthenticated = true;
|
|
App.Logger.LogInfo("Initialized mod.io client with authentication", source: "PluginManager");
|
|
}
|
|
|
|
InitializeComponent();
|
|
|
|
DataContext = pluginManagerDataContext;
|
|
searchTimer.Tick += async (sender, e) =>
|
|
{
|
|
searchTimer.Stop();
|
|
await SearchAllPlugins(pluginManagerDataContext.AllPluginsSearchText);
|
|
};
|
|
|
|
Loaded += PluginManager_Loaded;
|
|
Unloaded += PluginManager_Unloaded;
|
|
}
|
|
|
|
private async void PluginManager_Loaded(object sender, RoutedEventArgs e)
|
|
{
|
|
await InitializePluginManager();
|
|
}
|
|
|
|
private void PluginManager_Unloaded(object sender, RoutedEventArgs e)
|
|
{
|
|
searchTimer.Stop();
|
|
|
|
if (changed)
|
|
{
|
|
_manager.ReloadPlugins();
|
|
}
|
|
}
|
|
|
|
private async Task InitializePluginManager()
|
|
{
|
|
App.Logger.LogInfo("Initializing Plugin Manager", source: "PluginManager");
|
|
HashSet<uint> pluginIds = [];
|
|
|
|
pluginManagerDataContext.IsLoading = true;
|
|
pluginManagerDataContext.InstalledPlugins.Clear();
|
|
pluginManagerDataContext.AllPlugins.Clear();
|
|
|
|
foreach (string pluginPath in Directory.GetDirectories(pluginsPath))
|
|
{
|
|
string pluginMetadataPath = Path.Combine(pluginPath, "metadata.json");
|
|
|
|
if (!File.Exists(pluginMetadataPath))
|
|
{
|
|
App.Logger.LogWarn($"Plugin metadata not found at: {pluginMetadataPath}", source: "PluginManager");
|
|
continue;
|
|
}
|
|
|
|
PluginMetadata? pluginMetadata = JsonSerializer.Deserialize<PluginMetadata>(await File.ReadAllTextAsync(pluginMetadataPath));
|
|
|
|
if (pluginMetadata is null)
|
|
{
|
|
App.Logger.LogWarn($"Failed to deserialize plugin metadata from: {pluginMetadataPath}", source: "PluginManager");
|
|
continue;
|
|
}
|
|
|
|
string csprojPath = GetCsprojPath(pluginMetadata.Name);
|
|
|
|
App.Logger.LogInfo($"Loaded plugin: {pluginMetadata.Name} (ID: {pluginMetadata.Id})", source: "PluginManager");
|
|
pluginManagerDataContext.InstalledPlugins.Add(new PluginEntryDataContext(pluginMetadata, new CommandHandler(async () => await Remove(pluginPath, pluginMetadata.Id)), PluginEntryDataContext.Mode.Uninstall, pluginPath, csprojPath));
|
|
_ = pluginIds.Add(pluginMetadata.Id);
|
|
|
|
if (pluginMetadata.IsLocalPlugin)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
Mod mod = await modIoClient.Games[ModIoGameId].Mods[pluginMetadata.Id].Get();
|
|
|
|
if (DateTimeOffset.FromUnixTimeSeconds(mod.DateUpdated).DateTime > pluginMetadata.Updated && pluginMetadata.SupportsUnloading)
|
|
{
|
|
App.Logger.LogInfo($"Plugin {pluginMetadata.Id} has an update available, reinstalling", source: "PluginManager");
|
|
await Remove(pluginPath, pluginMetadata.Id);
|
|
await Install(mod);
|
|
pluginManagerDataContext.IsLoading = true;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
App.Logger.LogError($"Failed to check for plugin {pluginMetadata.Id} updates: {ex.Message}", source: "PluginManager");
|
|
}
|
|
}
|
|
|
|
Filter filter = ModFilter.Popular.Desc().Limit(100);
|
|
|
|
try
|
|
{
|
|
App.Logger.LogInfo("Fetching popular plugins from mod.io", source: "PluginManager");
|
|
IAsyncEnumerable<Mod> mods = modIoClient.Games[ModIoGameId].Mods.Search(filter).ToEnumerable();
|
|
await foreach (Mod mod in mods)
|
|
{
|
|
if (pluginIds.Contains(mod.Id))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
pluginManagerDataContext.AllPlugins.Add(new PluginEntryDataContext(new(mod), new CommandHandler(async () => await Install(mod)), PluginEntryDataContext.Mode.Install));
|
|
}
|
|
App.Logger.LogInfo($"Loaded {pluginManagerDataContext.AllPlugins.Count} available plugins", source: "PluginManager");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
App.Logger.LogError($"Failed to fetch plugins from mod.io: {ex.Message}", source: "PluginManager");
|
|
}
|
|
|
|
await SyncPlugins();
|
|
|
|
pluginManagerDataContext.IsLoading = false;
|
|
App.Logger.LogInfo("Plugin Manager initialization complete", source: "PluginManager");
|
|
}
|
|
|
|
public async Task Remove(string pluginPath, uint id)
|
|
{
|
|
App.Logger.LogInfo($"Removing plugin with ID {id} from path: {pluginPath}", source: "PluginManager");
|
|
pluginManagerDataContext.IsLoading = true;
|
|
changed = true;
|
|
|
|
PluginEntryDataContext? pluginEntryDataContext = pluginManagerDataContext.InstalledPlugins.FirstOrDefault(p => p.Id == id);
|
|
|
|
if (Directory.Exists(pluginPath))
|
|
{
|
|
try
|
|
{
|
|
Directory.Delete(pluginPath, true);
|
|
App.Logger.LogInfo($"Successfully deleted plugin directory: {pluginPath}", source: "PluginManager");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Wpf.Ui.Controls.MessageBox messageBox = new Wpf.Ui.Controls.MessageBox
|
|
{
|
|
Title = "Plugin Manager",
|
|
Content = ex.Message,
|
|
CloseButtonText = "Ok"
|
|
};
|
|
_ = await messageBox.ShowDialogAsync();
|
|
App.Logger.LogError(ex.Message, source: "PluginManager");
|
|
}
|
|
}
|
|
|
|
if (pluginEntryDataContext is not null)
|
|
{
|
|
_ = pluginManagerDataContext.InstalledPlugins.Remove(pluginEntryDataContext);
|
|
App.Logger.LogInfo($"Removed plugin {id} from installed plugins list", source: "PluginManager");
|
|
}
|
|
|
|
if (pluginManagerDataContext.IsAuthenticated)
|
|
{
|
|
try
|
|
{
|
|
await modIoClient.Games[ModIoGameId].Mods.Unsubscribe(id);
|
|
App.Logger.LogInfo($"Unsubscribed from plugin {id} on mod.io", source: "PluginManager");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
App.Logger.LogError($"Failed to unsubscribe from plugin {id}: {ex.Message}", source: "PluginManager");
|
|
}
|
|
}
|
|
|
|
pluginManagerDataContext.IsLoading = false;
|
|
}
|
|
|
|
[GeneratedRegex(@"[^a-zA-Z0-9]")]
|
|
private static partial Regex IdentifierNameRegex();
|
|
|
|
private async Task Install(Mod mod)
|
|
{
|
|
App.Logger.LogInfo($"Installing plugin: {mod.Name} (ID: {mod.Id})", source: "PluginManager");
|
|
pluginManagerDataContext.IsLoading = true;
|
|
changed = true;
|
|
|
|
if (mod.Modfile?.Download?.BinaryUrl is null)
|
|
{
|
|
App.Logger.LogWarn($"Plugin {mod.Id} has no download URL", source: "PluginManager");
|
|
return;
|
|
}
|
|
|
|
string pluginGuid = Guid.NewGuid().ToString();
|
|
string pluginPath = Path.Combine(pluginsPath, pluginGuid);
|
|
string zipFilePath = Path.Combine(pluginsPath, pluginGuid + ".zip");
|
|
|
|
try
|
|
{
|
|
App.Logger.LogInfo($"Downloading plugin from: {mod.Modfile.Download.BinaryUrl}", source: "PluginManager");
|
|
using (Stream fileStream = await httpClient.GetStreamAsync(mod.Modfile.Download.BinaryUrl))
|
|
{
|
|
using FileStream outputFileStream = new FileStream(zipFilePath, FileMode.Create);
|
|
await fileStream.CopyToAsync(outputFileStream);
|
|
}
|
|
App.Logger.LogInfo($"Plugin downloaded to: {zipFilePath}", source: "PluginManager");
|
|
|
|
using (ZipArchive zipArchive = ZipFile.OpenRead(zipFilePath))
|
|
{
|
|
zipArchive.ExtractToDirectory(pluginPath);
|
|
}
|
|
App.Logger.LogInfo($"Plugin extracted to: {pluginPath}", source: "PluginManager");
|
|
|
|
string pluginMetadataPath = Path.Combine(pluginPath, "metadata.json");
|
|
await File.WriteAllTextAsync(pluginMetadataPath, JsonSerializer.Serialize(new PluginMetadata(mod)));
|
|
|
|
File.Delete(zipFilePath);
|
|
|
|
if (!File.Exists(Path.Combine(pluginPath, "main.dll")) && !File.Exists(Path.Combine(pluginPath, "main.html")))
|
|
{
|
|
App.Logger.LogError($"Plugin {mod.Id} does not contain main.dll or main.html", source: "PluginManager");
|
|
await Remove(pluginPath, mod.Id);
|
|
pluginManagerDataContext.IsLoading = false;
|
|
Wpf.Ui.Controls.MessageBox messageBox = new Wpf.Ui.Controls.MessageBox
|
|
{
|
|
Title = "Plugin Manager",
|
|
Content = "The plugin you are trying to install does not contain a \"main.dll\" or \"main.html\" file. Please contact the plugin author.",
|
|
CloseButtonText = "Ok"
|
|
};
|
|
_ = await messageBox.ShowDialogAsync();
|
|
return;
|
|
}
|
|
|
|
PluginEntryDataContext? pluginEntryDataContext = pluginManagerDataContext.AllPlugins.FirstOrDefault(p => p.Id == mod.Id);
|
|
|
|
if (pluginEntryDataContext is not null)
|
|
{
|
|
_ = pluginManagerDataContext.AllPlugins.Remove(pluginEntryDataContext);
|
|
}
|
|
|
|
if (pluginManagerDataContext.IsAuthenticated)
|
|
{
|
|
try
|
|
{
|
|
await modIoClient.Games[ModIoGameId].Mods.Subscribe(mod.Id);
|
|
App.Logger.LogInfo($"Subscribed to plugin {mod.Id} on mod.io", source: "PluginManager");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
App.Logger.LogError($"Failed to subscribe to plugin {mod.Id}: {ex.Message}", source: "PluginManager");
|
|
}
|
|
}
|
|
|
|
pluginManagerDataContext.InstalledPlugins.Add(new PluginEntryDataContext(new PluginMetadata(mod), new CommandHandler(async () => await Remove(pluginPath, mod.Id)), PluginEntryDataContext.Mode.Uninstall, pluginPath));
|
|
App.Logger.LogInfo($"Successfully installed plugin: {mod.Name} (ID: {mod.Id})", source: "PluginManager");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
App.Logger.LogError($"Failed to install plugin {mod.Id}: {ex.Message}", source: "PluginManager");
|
|
Wpf.Ui.Controls.MessageBox messageBox = new Wpf.Ui.Controls.MessageBox
|
|
{
|
|
Title = "Plugin Manager",
|
|
Content = $"Failed to install plugin: {ex.Message}",
|
|
CloseButtonText = "Ok"
|
|
};
|
|
_ = await messageBox.ShowDialogAsync();
|
|
}
|
|
|
|
pluginManagerDataContext.IsLoading = false;
|
|
}
|
|
|
|
private void Image_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
|
{
|
|
string uri = "https://mod.io/g/DesktopMagic";
|
|
ProcessStartInfo psi = new ProcessStartInfo
|
|
{
|
|
UseShellExecute = true,
|
|
FileName = uri
|
|
};
|
|
_ = Process.Start(psi);
|
|
App.Logger.LogInfo($"Opened mod.io page: {uri}", source: "PluginManager");
|
|
}
|
|
|
|
private void AllPluginsSearchTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
|
|
{
|
|
pluginManagerDataContext.IsSearching = true;
|
|
searchTimer.Stop();
|
|
searchTimer.Start();
|
|
}
|
|
|
|
private void InstalledPluginsSearchTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(pluginManagerDataContext.InstalledPluginsSearchText))
|
|
{
|
|
foreach (PluginEntryDataContext pluginEntryDataContext in pluginManagerDataContext.InstalledPlugins)
|
|
{
|
|
pluginEntryDataContext.IsVisible = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (PluginEntryDataContext pluginEntryDataContext in pluginManagerDataContext.InstalledPlugins)
|
|
{
|
|
pluginEntryDataContext.IsVisible = pluginEntryDataContext.Name.Contains(pluginManagerDataContext.InstalledPluginsSearchText, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
}
|
|
}
|
|
|
|
private async Task SearchAllPlugins(string searchString)
|
|
{
|
|
App.Logger.LogInfo($"Searching plugins with query: {searchString}", source: "PluginManager");
|
|
pluginManagerDataContext.AllPlugins.Clear();
|
|
|
|
if (!searchString.Contains('*'))
|
|
{
|
|
searchString = $"*{searchString.Trim()}*";
|
|
}
|
|
|
|
Filter filter = ModFilter.Name.Like($"{searchString}").And(ModFilter.Popular.Desc()).Limit(100);
|
|
|
|
try
|
|
{
|
|
IAsyncEnumerable<Mod> mods = modIoClient.Games[ModIoGameId].Mods.Search(filter).ToEnumerable();
|
|
await foreach (Mod mod in mods)
|
|
{
|
|
if (pluginManagerDataContext.InstalledPlugins.Any(p => p.Id == mod.Id))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
pluginManagerDataContext.AllPlugins.Add(new PluginEntryDataContext(new(mod), new CommandHandler(async () => await Install(mod)), PluginEntryDataContext.Mode.Install));
|
|
}
|
|
App.Logger.LogInfo($"Search completed: {pluginManagerDataContext.AllPlugins.Count} plugins found", source: "PluginManager");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
App.Logger.LogError($"Plugin search failed: {ex.Message}", source: "PluginManager");
|
|
}
|
|
|
|
pluginManagerDataContext.IsSearching = false;
|
|
}
|
|
|
|
private async void ReloadPluginsButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
_manager.ReloadPlugins();
|
|
await InitializePluginManager();
|
|
changed = false;
|
|
}
|
|
|
|
private async void CreatePluginButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
await CreateNewPlugin();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
App.Logger.LogError(ex.Message, source: "PluginManager");
|
|
Wpf.Ui.Controls.MessageBox messageBox = new Wpf.Ui.Controls.MessageBox
|
|
{
|
|
Title = "Plugin Manager",
|
|
Content = ex.Message,
|
|
CloseButtonText = "Ok"
|
|
};
|
|
_ = await messageBox.ShowDialogAsync();
|
|
}
|
|
}
|
|
|
|
private async Task CreateNewPlugin()
|
|
{
|
|
App.Logger.LogInfo("Creating new plugin", source: "PluginManager");
|
|
|
|
if (!FileUtilities.ExistsOnPath("dotnet.exe"))
|
|
{
|
|
App.Logger.LogError(".NET SDK not found on PATH", source: "PluginManager");
|
|
Wpf.Ui.Controls.MessageBox messageBox = new Wpf.Ui.Controls.MessageBox
|
|
{
|
|
Title = "Plugin Manager",
|
|
Content = "The .NET SDK is required to create a plugin. Please install it and try again.",
|
|
CloseButtonText = "Ok"
|
|
};
|
|
_ = await messageBox.ShowDialogAsync();
|
|
return;
|
|
}
|
|
|
|
string pluginGuid = Guid.NewGuid().ToString();
|
|
string pluginPath = Path.Combine(pluginsPath, pluginGuid);
|
|
uint pluginId = (uint)Random.Shared.Next(1000, 9999);
|
|
|
|
CreatePluginDialog inputDialog = new()
|
|
{
|
|
Owner = Window.GetWindow(this),
|
|
};
|
|
|
|
if (inputDialog.ShowDialog() != true)
|
|
{
|
|
App.Logger.LogInfo("Plugin creation cancelled by user", source: "PluginManager");
|
|
return;
|
|
}
|
|
|
|
string pluginName = inputDialog.ResponseText;
|
|
string pluginSafeName = GetPluginSafeName(pluginName);
|
|
|
|
App.Logger.LogInfo($"Creating plugin with name: {pluginName} (safe name: {pluginSafeName})", source: "PluginManager");
|
|
|
|
if (!Directory.Exists(pluginDevelopmentPath))
|
|
{
|
|
_ = Directory.CreateDirectory(pluginDevelopmentPath);
|
|
}
|
|
|
|
string pluginProjectPath = Path.Combine(pluginDevelopmentPath, pluginSafeName);
|
|
|
|
if (Directory.Exists(pluginProjectPath))
|
|
{
|
|
App.Logger.LogWarn($"Plugin project already exists: {pluginProjectPath}", source: "PluginManager");
|
|
Wpf.Ui.Controls.MessageBox messageBox = new Wpf.Ui.Controls.MessageBox
|
|
{
|
|
Title = "Plugin Manager",
|
|
Content = "A plugin with the same name already exists. Please choose a different name.",
|
|
CloseButtonText = "Ok"
|
|
};
|
|
_ = await messageBox.ShowDialogAsync();
|
|
return;
|
|
}
|
|
|
|
pluginManagerDataContext.IsLoading = true;
|
|
|
|
PluginMetadata pluginMetadata = new(pluginName, pluginId);
|
|
|
|
_ = Directory.CreateDirectory(pluginPath);
|
|
_ = Directory.CreateDirectory(pluginProjectPath);
|
|
|
|
string pluginMetadataPath = Path.Combine(pluginPath, "metadata.json");
|
|
await File.WriteAllTextAsync(pluginMetadataPath, JsonSerializer.Serialize(pluginMetadata));
|
|
|
|
if (inputDialog.IsWebPlugin)
|
|
{
|
|
await CreateNewWebPlugin(pluginPath, pluginName);
|
|
changed = true;
|
|
await InitializePluginManager();
|
|
pluginManagerDataContext.IsLoading = false;
|
|
return;
|
|
}
|
|
|
|
App.Logger.LogInfo($"Creating .NET project at: {pluginProjectPath}", source: "PluginManager");
|
|
string cmd = $"new classlib -n {pluginSafeName} -o {pluginProjectPath} -f net8.0 --target-framework-override net8.0-windows7";
|
|
Process process = Process.Start("dotnet", cmd);
|
|
await process.WaitForExitAsync();
|
|
|
|
App.Logger.LogInfo("Installing NuGet package: DesktopMagic.Api", source: "PluginManager");
|
|
process = Process.Start("dotnet", $"add {pluginProjectPath} package DesktopMagic.Api");
|
|
await process.WaitForExitAsync();
|
|
|
|
File.Move(Path.Combine(pluginProjectPath, "Class1.cs"), Path.Combine(pluginProjectPath, $"{pluginSafeName}.cs"));
|
|
|
|
string code = $@"using DesktopMagic.Api;
|
|
using System.Drawing;
|
|
|
|
namespace {pluginSafeName};
|
|
|
|
public class {pluginSafeName}Plugin : Plugin
|
|
{{
|
|
public override Bitmap Main()
|
|
{{
|
|
Bitmap bmp = new Bitmap(2000, 1000);
|
|
bmp.SetResolution(100, 100); // Set DPI to avoid scaling issues.
|
|
|
|
using (Graphics g = Graphics.FromImage(bmp))
|
|
{{
|
|
g.DrawString(""Hello World!"", new Font(Application.Theme.Font, 100), new SolidBrush(Application.Theme.PrimaryColor), new PointF(0, 0)); // Draw ""Hello World"" to the image.
|
|
}}
|
|
|
|
return bmp; // Return the image.
|
|
}}
|
|
}}
|
|
";
|
|
|
|
string csprojPath = Path.Combine(pluginProjectPath, $"{pluginSafeName}.csproj");
|
|
|
|
await File.WriteAllTextAsync(Path.Combine(pluginProjectPath, $"{pluginSafeName}.cs"), code);
|
|
|
|
XDocument doc = XDocument.Load(csprojPath);
|
|
XElement? propertyGroup = doc.Root?.Element("PropertyGroup");
|
|
|
|
if (propertyGroup is not null)
|
|
{
|
|
XElement? outputPathElement = propertyGroup.Element("OutputPath");
|
|
|
|
if (outputPathElement is not null)
|
|
{
|
|
outputPathElement.Value = pluginPath;
|
|
}
|
|
else
|
|
{
|
|
propertyGroup.Add(new XElement("OutputPath", pluginPath));
|
|
}
|
|
|
|
XElement? appendTargetFrameworkToOutputPathElement = propertyGroup.Element("AppendTargetFrameworkToOutputPath");
|
|
|
|
if (appendTargetFrameworkToOutputPathElement is not null)
|
|
{
|
|
appendTargetFrameworkToOutputPathElement.Value = "false";
|
|
}
|
|
else
|
|
{
|
|
propertyGroup.Add(new XElement("AppendTargetFrameworkToOutputPath", "false"));
|
|
}
|
|
|
|
XElement? targetNameElement = propertyGroup.Element("TargetName");
|
|
|
|
if (targetNameElement is not null)
|
|
{
|
|
targetNameElement.Value = "main";
|
|
}
|
|
else
|
|
{
|
|
propertyGroup.Add(new XElement("TargetName", "main"));
|
|
}
|
|
|
|
doc.Save(csprojPath);
|
|
App.Logger.LogInfo("Updated .csproj file with custom build settings", source: "PluginManager");
|
|
}
|
|
else
|
|
{
|
|
App.Logger.LogError("PropertyGroup element not found in .csproj", source: "PluginManager");
|
|
Wpf.Ui.Controls.MessageBox messageBox = new Wpf.Ui.Controls.MessageBox
|
|
{
|
|
Title = "Error",
|
|
Content = "PropertyGroup element not found in .csproj",
|
|
CloseButtonText = "Ok"
|
|
};
|
|
_ = await messageBox.ShowDialogAsync();
|
|
return;
|
|
}
|
|
|
|
App.Logger.LogInfo("Building plugin project", source: "PluginManager");
|
|
process = Process.Start("dotnet", $"build {csprojPath} -c Release");
|
|
await process.WaitForExitAsync();
|
|
|
|
changed = true;
|
|
await InitializePluginManager();
|
|
|
|
string? associatedProgram = FileUtilities.GetAssociatedProgram(".csproj");
|
|
|
|
if (associatedProgram is null)
|
|
{
|
|
App.Logger.LogInfo($"Opening plugin project in Explorer: {pluginProjectPath}", source: "PluginManager");
|
|
_ = Process.Start("explorer.exe", pluginProjectPath);
|
|
}
|
|
else
|
|
{
|
|
App.Logger.LogInfo($"Opening plugin project in IDE: {associatedProgram}", source: "PluginManager");
|
|
ProcessStartInfo psi = new ProcessStartInfo
|
|
{
|
|
FileName = associatedProgram,
|
|
Arguments = csprojPath,
|
|
};
|
|
|
|
_ = Process.Start(psi);
|
|
}
|
|
|
|
App.Logger.LogInfo($"Successfully created plugin: {pluginName}", source: "PluginManager");
|
|
|
|
pluginManagerDataContext.IsLoading = false;
|
|
}
|
|
|
|
private async Task CreateNewWebPlugin(string pluginPath, string pluginName)
|
|
{
|
|
App.Logger.LogInfo($"Creating web plugin at: {pluginPath}", source: "PluginManager");
|
|
|
|
string mainHtml = $@"<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset=""utf-8"">
|
|
<style>
|
|
body {{ margin: 0; padding: 16px; font-family: var(--font-family); }}
|
|
h1 {{ color: var(--primary-color); }}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1 id=""greeting"">{pluginName}</h1>
|
|
<script>
|
|
function render() {{
|
|
const s = window.desktopMagic.getSettings();
|
|
document.getElementById('greeting').innerHTML = s.message;
|
|
document.getElementById('greeting').style.color = s.color;
|
|
document.getElementById('greeting').style.fontWeight = s.bold ? 'bold' : 'normal';
|
|
}}
|
|
|
|
window.desktopMagic.onSettingChanged = (id, value) => render();
|
|
render();
|
|
</script>
|
|
</body>
|
|
</html>";
|
|
|
|
var settings = new List<Dictionary<string, object?>>
|
|
{
|
|
new() { ["id"] = "message", ["name"] = "Greeting Message", ["type"] = "textbox", ["default"] = "Hello, World!" },
|
|
new() { ["id"] = "bold", ["name"] = "Bold Text", ["type"] = "checkbox", ["default"] = true },
|
|
new() { ["id"] = "color", ["name"] = "Text Color", ["type"] = "colorpicker", ["default"] = "#FF5722" }
|
|
};
|
|
|
|
string settingsJson = JsonSerializer.Serialize(settings, new JsonSerializerOptions { WriteIndented = true });
|
|
await File.WriteAllTextAsync(Path.Combine(pluginPath, "settings.json"), settingsJson);
|
|
await File.WriteAllTextAsync(Path.Combine(pluginPath, "main.html"), mainHtml);
|
|
|
|
App.Logger.LogInfo($"Successfully created web plugin: {pluginName}", source: "PluginManager");
|
|
}
|
|
|
|
private async void LogInButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (pluginManagerDataContext.IsAuthenticated)
|
|
{
|
|
App.Logger.LogInfo("Logging out from mod.io", source: "PluginManager");
|
|
MainWindowDataContext.GetSettings().ModIoAccessToken = null;
|
|
pluginManagerDataContext.IsAuthenticated = false;
|
|
return;
|
|
}
|
|
|
|
App.Logger.LogInfo("Starting mod.io authentication", source: "PluginManager");
|
|
|
|
try
|
|
{
|
|
InputDialog inputDialog = new((string)FindResource("enterModIoEmail"), "Plugin Manager")
|
|
{
|
|
Owner = Window.GetWindow(this),
|
|
};
|
|
|
|
if (inputDialog.ShowDialog() != true)
|
|
{
|
|
App.Logger.LogInfo("Authentication cancelled by user", source: "PluginManager");
|
|
return;
|
|
}
|
|
|
|
App.Logger.LogInfo($"Requesting authentication code for email: {inputDialog.ResponseText}", source: "PluginManager");
|
|
await modIoClient.Auth.RequestCode(ModIoApiKey, inputDialog.ResponseText);
|
|
|
|
inputDialog = new((string)FindResource("enterModIoAccessToken"), "Plugin Manager")
|
|
{
|
|
Owner = Window.GetWindow(this),
|
|
};
|
|
|
|
if (inputDialog.ShowDialog() != true)
|
|
{
|
|
App.Logger.LogInfo("Authentication cancelled by user", source: "PluginManager");
|
|
return;
|
|
}
|
|
|
|
pluginManagerDataContext.IsLoading = true;
|
|
|
|
AccessToken accessToken = await modIoClient.Auth.SecurityCode(ModIoApiKey, inputDialog.ResponseText);
|
|
|
|
if (accessToken.Value is not null)
|
|
{
|
|
modIoClient = new Client(new Credentials(ModIoApiKey, accessToken.Value));
|
|
App.Logger.LogInfo("Successfully authenticated with mod.io", source: "PluginManager");
|
|
}
|
|
|
|
MainWindowDataContext.GetSettings().ModIoAccessToken = accessToken.Value;
|
|
pluginManagerDataContext.IsAuthenticated = true;
|
|
|
|
App.Logger.LogInfo("Subscribing to installed plugins on mod.io", source: "PluginManager");
|
|
foreach (PluginEntryDataContext plugin in pluginManagerDataContext.InstalledPlugins)
|
|
{
|
|
if (!plugin.IsLocalPlugin)
|
|
{
|
|
try
|
|
{
|
|
await modIoClient.Games[ModIoGameId].Mods.Subscribe(plugin.Id);
|
|
App.Logger.LogInfo($"Subscribed to plugin {plugin.Id}", source: "PluginManager");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
App.Logger.LogError($"Failed to subscribe to plugin {plugin.Id}: {ex.Message}", source: "PluginManager");
|
|
}
|
|
}
|
|
}
|
|
|
|
await SyncPlugins();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
App.Logger.LogError($"Authentication failed: {ex.Message}", source: "PluginManager");
|
|
Wpf.Ui.Controls.MessageBox messageBox = new Wpf.Ui.Controls.MessageBox
|
|
{
|
|
Title = "Plugin Manager",
|
|
Content = ex.Message,
|
|
CloseButtonText = "Ok"
|
|
};
|
|
_ = await messageBox.ShowDialogAsync();
|
|
}
|
|
|
|
pluginManagerDataContext.IsLoading = false;
|
|
}
|
|
|
|
private async Task SyncPlugins()
|
|
{
|
|
if (!pluginManagerDataContext.IsAuthenticated)
|
|
{
|
|
return;
|
|
}
|
|
|
|
App.Logger.LogInfo("Syncing plugins with mod.io subscriptions", source: "PluginManager");
|
|
|
|
try
|
|
{
|
|
IReadOnlyList<Mod> mods = await modIoClient.User.GetSubscriptions(ModFilter.GameId.Eq(ModIoGameId)).ToList();
|
|
App.Logger.LogInfo($"Found {mods.Count} subscribed plugins on mod.io", source: "PluginManager");
|
|
|
|
List<PluginEntryDataContext> pluginsToRemove = [];
|
|
|
|
foreach (PluginEntryDataContext plugin in pluginManagerDataContext.InstalledPlugins)
|
|
{
|
|
if (!plugin.IsLocalPlugin && !mods.Any(m => m.Id == plugin.Id))
|
|
{
|
|
App.Logger.LogInfo($"Plugin {plugin.Id} is no longer subscribed, marking for removal", source: "PluginManager");
|
|
pluginsToRemove.Add(plugin);
|
|
}
|
|
}
|
|
|
|
foreach (PluginEntryDataContext plugin in pluginsToRemove)
|
|
{
|
|
if (plugin.Path is null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
await Remove(plugin.Path, plugin.Id);
|
|
}
|
|
|
|
IEnumerable<Mod> notInstalledMods = mods.Where(m => !pluginManagerDataContext.InstalledPlugins.Any(p => p.Id == m.Id));
|
|
|
|
foreach (Mod mod in notInstalledMods)
|
|
{
|
|
App.Logger.LogInfo($"Installing subscribed plugin: {mod.Name} (ID: {mod.Id})", source: "PluginManager");
|
|
await Install(mod);
|
|
}
|
|
|
|
App.Logger.LogInfo("Plugin sync completed", source: "PluginManager");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
App.Logger.LogError($"Plugin sync failed: {ex.Message}", source: "PluginManager");
|
|
}
|
|
}
|
|
|
|
private static string GetPluginSafeName(string pluginName)
|
|
{
|
|
string pluginSafeName = pluginName.ToLower().Replace("_", " ");
|
|
|
|
TextInfo info = CultureInfo.CurrentCulture.TextInfo;
|
|
pluginSafeName = info.ToTitleCase(pluginSafeName);
|
|
pluginSafeName = IdentifierNameRegex().Replace(pluginSafeName, "");
|
|
|
|
return pluginSafeName;
|
|
}
|
|
|
|
private string GetCsprojPath(string pluginName)
|
|
{
|
|
string pluginSafeName = GetPluginSafeName(pluginName);
|
|
return Path.Combine(pluginDevelopmentPath, pluginSafeName, $"{pluginSafeName}.csproj");
|
|
}
|
|
} |