mirror of
https://github.com/Stone-Red-Code/DesktopMagic.git
synced 2026-09-04 00:46:12 +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();
|
||||
}
|
||||
|
||||
public void SaveState()
|
||||
{
|
||||
window.SavePluginState();
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Timers;
|
||||
using System.Windows;
|
||||
@@ -107,6 +108,14 @@ public partial class PluginWindow : Window, IPluginWindow
|
||||
UpdateTimer_Elapsed(updateTimer, null);
|
||||
}
|
||||
|
||||
public void SavePluginState()
|
||||
{
|
||||
if (pluginClassInstance is not null)
|
||||
{
|
||||
SaveState(pluginClassInstance);
|
||||
}
|
||||
}
|
||||
|
||||
public void Exit()
|
||||
{
|
||||
IsRunning = false;
|
||||
@@ -258,6 +267,7 @@ public partial class PluginWindow : Window, IPluginWindow
|
||||
return;
|
||||
}
|
||||
|
||||
LoadState(pluginClassInstance);
|
||||
LoadOptions(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)
|
||||
{
|
||||
try
|
||||
@@ -479,7 +623,11 @@ public partial class PluginWindow : Window, IPluginWindow
|
||||
|
||||
try
|
||||
{
|
||||
pluginClassInstance?.Stop();
|
||||
if (pluginClassInstance is not null)
|
||||
{
|
||||
SaveState(pluginClassInstance);
|
||||
pluginClassInstance.Stop();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Windows;
|
||||
|
||||
@@ -16,6 +17,7 @@ public class PluginSettings : INotifyPropertyChanged
|
||||
|
||||
private string? currentThemeName;
|
||||
private List<SettingElement> settings = [];
|
||||
private Dictionary<string, JsonElement> state = [];
|
||||
private bool enabled = false;
|
||||
private Point position = new Point(100, 100);
|
||||
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
|
||||
{
|
||||
get => enabled;
|
||||
|
||||
@@ -50,4 +50,13 @@ public interface IPluginData
|
||||
/// <param name="message">The message to display.</param>
|
||||
/// <param name="title">The title of the message box.</param>
|
||||
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