mirror of
https://github.com/Stone-Red-Code/DesktopMagic.git
synced 2026-09-04 08:56:15 +02:00
Add plugin state persistence via PersistStateAttribute serialization
This commit is contained in:
@@ -46,4 +46,9 @@ internal class PluginData(PluginWindow window, PluginSettings pluginSettings) :
|
|||||||
{
|
{
|
||||||
window.UpdatePluginWindow();
|
window.UpdatePluginWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SaveState()
|
||||||
|
{
|
||||||
|
window.SavePluginState();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.Loader;
|
using System.Runtime.Loader;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Timers;
|
using System.Timers;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
@@ -107,6 +108,14 @@ public partial class PluginWindow : Window, IPluginWindow
|
|||||||
UpdateTimer_Elapsed(updateTimer, null);
|
UpdateTimer_Elapsed(updateTimer, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SavePluginState()
|
||||||
|
{
|
||||||
|
if (pluginClassInstance is not null)
|
||||||
|
{
|
||||||
|
SaveState(pluginClassInstance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Exit()
|
public void Exit()
|
||||||
{
|
{
|
||||||
IsRunning = false;
|
IsRunning = false;
|
||||||
@@ -258,6 +267,7 @@ public partial class PluginWindow : Window, IPluginWindow
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LoadState(pluginClassInstance);
|
||||||
LoadOptions(pluginClassInstance);
|
LoadOptions(pluginClassInstance);
|
||||||
BindDefaultSettings(pluginClassInstance);
|
BindDefaultSettings(pluginClassInstance);
|
||||||
|
|
||||||
@@ -401,6 +411,140 @@ public partial class PluginWindow : Window, IPluginWindow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LoadState(object instance)
|
||||||
|
{
|
||||||
|
App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Loading plugin state", source: "Plugin");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (settings.State.Count == 0)
|
||||||
|
{
|
||||||
|
App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - No state found, using defaults", source: "Plugin");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
|
||||||
|
FieldInfo[] fields = instance.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||||
|
PropertyInfo[] properties = instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||||
|
#pragma warning restore S3011
|
||||||
|
|
||||||
|
// Load fields
|
||||||
|
foreach (FieldInfo field in fields)
|
||||||
|
{
|
||||||
|
PersistStateAttribute? persistAttribute = field.GetCustomAttribute<PersistStateAttribute>();
|
||||||
|
if (persistAttribute is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.State.TryGetValue(field.Name, out JsonElement jsonElement))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
object? value = jsonElement.Deserialize(field.FieldType);
|
||||||
|
field.SetValue(instance, value);
|
||||||
|
App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Restored state field: {field.Name}", source: "Plugin");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
App.Logger.LogWarn($"\"{PluginMetadata.Name}\" - Failed to restore field {field.Name}: {ex.Message}", source: "Plugin");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load properties
|
||||||
|
foreach (PropertyInfo property in properties)
|
||||||
|
{
|
||||||
|
PersistStateAttribute? persistAttribute = property.GetCustomAttribute<PersistStateAttribute>();
|
||||||
|
if (persistAttribute is null || !property.CanWrite)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.State.TryGetValue(property.Name, out JsonElement jsonElement))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
object? value = jsonElement.Deserialize(property.PropertyType);
|
||||||
|
property.SetValue(instance, value);
|
||||||
|
App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Restored state property: {property.Name}", source: "Plugin");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
App.Logger.LogWarn($"\"{PluginMetadata.Name}\" - Failed to restore property {property.Name}: {ex.Message}", source: "Plugin");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
App.Logger.LogError($"\"{PluginMetadata.Name}\" - Error loading state: {ex.Message}", source: "Plugin");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveState(object instance)
|
||||||
|
{
|
||||||
|
App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Saving plugin state", source: "Plugin");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
|
||||||
|
FieldInfo[] fields = instance.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||||
|
PropertyInfo[] properties = instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||||
|
#pragma warning restore S3011
|
||||||
|
|
||||||
|
// Save fields
|
||||||
|
foreach (FieldInfo field in fields)
|
||||||
|
{
|
||||||
|
PersistStateAttribute? persistAttribute = field.GetCustomAttribute<PersistStateAttribute>();
|
||||||
|
if (persistAttribute is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
object? value = field.GetValue(instance);
|
||||||
|
JsonElement jsonElement = JsonSerializer.SerializeToElement(value, field.FieldType);
|
||||||
|
settings.State[field.Name] = jsonElement;
|
||||||
|
App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Saved state field: {field.Name}", source: "Plugin");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
App.Logger.LogWarn($"\"{PluginMetadata.Name}\" - Failed to save field {field.Name}: {ex.Message}", source: "Plugin");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save properties
|
||||||
|
foreach (PropertyInfo property in properties)
|
||||||
|
{
|
||||||
|
PersistStateAttribute? persistAttribute = property.GetCustomAttribute<PersistStateAttribute>();
|
||||||
|
if (persistAttribute is null || !property.CanRead)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
object? value = property.GetValue(instance);
|
||||||
|
JsonElement jsonElement = JsonSerializer.SerializeToElement(value, property.PropertyType);
|
||||||
|
settings.State[property.Name] = jsonElement;
|
||||||
|
App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Saved state property: {property.Name}", source: "Plugin");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
App.Logger.LogWarn($"\"{PluginMetadata.Name}\" - Failed to save property {property.Name}: {ex.Message}", source: "Plugin");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - State saved successfully ({settings.State.Count} items)", source: "Plugin");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
App.Logger.LogError($"\"{PluginMetadata.Name}\" - Error saving state: {ex.Message}", source: "Plugin");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async void UpdateTimer_Elapsed(object? sender, ElapsedEventArgs? e)
|
private async void UpdateTimer_Elapsed(object? sender, ElapsedEventArgs? e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -479,7 +623,11 @@ public partial class PluginWindow : Window, IPluginWindow
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
pluginClassInstance?.Stop();
|
if (pluginClassInstance is not null)
|
||||||
|
{
|
||||||
|
SaveState(pluginClassInstance);
|
||||||
|
pluginClassInstance.Stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ public class PluginSettings : INotifyPropertyChanged
|
|||||||
|
|
||||||
private string? currentThemeName;
|
private string? currentThemeName;
|
||||||
private List<SettingElement> settings = [];
|
private List<SettingElement> settings = [];
|
||||||
|
private Dictionary<string, JsonElement> state = [];
|
||||||
private bool enabled = false;
|
private bool enabled = false;
|
||||||
private Point position = new Point(100, 100);
|
private Point position = new Point(100, 100);
|
||||||
private Point size = new Point(300, 300);
|
private Point size = new Point(300, 300);
|
||||||
@@ -61,6 +63,23 @@ public class PluginSettings : INotifyPropertyChanged
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dictionary storing plugin state. Key is the field/property name, value is the JsonElement.
|
||||||
|
/// This is persisted per layout in settings.json.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, JsonElement> State
|
||||||
|
{
|
||||||
|
get => state;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (state != value)
|
||||||
|
{
|
||||||
|
state = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool Enabled
|
public bool Enabled
|
||||||
{
|
{
|
||||||
get => enabled;
|
get => enabled;
|
||||||
|
|||||||
@@ -50,4 +50,13 @@ public interface IPluginData
|
|||||||
/// <param name="message">The message to display.</param>
|
/// <param name="message">The message to display.</param>
|
||||||
/// <param name="title">The title of the message box.</param>
|
/// <param name="title">The title of the message box.</param>
|
||||||
void ShowMessage(string message, string title);
|
void ShowMessage(string message, string title);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves the current state of fields and properties marked with <see cref="PersistStateAttribute"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// State is automatically saved when the plugin stops, but this method can be called
|
||||||
|
/// to save state at any time, such as after important user interactions.
|
||||||
|
/// </remarks>
|
||||||
|
void SaveState();
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace DesktopMagic.Api;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks a field or property to be automatically persisted between plugin sessions.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The field or property must be JSON serializable. Complex types should have parameterless constructors.
|
||||||
|
/// State is saved when the plugin stops and loaded when it starts.
|
||||||
|
/// </remarks>
|
||||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||||
|
public sealed class PersistStateAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user