Skip to content

Commit

Permalink
feat: New Settings Class
Browse files Browse the repository at this point in the history
  • Loading branch information
nt153133 committed Jul 7, 2024
1 parent eac3466 commit a4cc871
Show file tree
Hide file tree
Showing 2 changed files with 331 additions and 0 deletions.
258 changes: 258 additions & 0 deletions Settings/Base/BaseSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Windows.Media;
using System.Windows.Threading;
using ff14bot.Helpers;
using LlamaLibrary.Logging;
using LlamaLibrary.Memory;
using Newtonsoft.Json;
using LogLevel = LlamaLibrary.Logging.LogLevel;

namespace LlamaLibrary.Settings.Base;

using LogLevel = LogLevel;

public abstract class BaseSettings : INotifyPropertyChanged
{
private readonly LLogger _logger;
private readonly DebounceDispatcher _saveDebounceDispatcher;
private Dictionary<string, DebounceDispatcher>? _debounceDispatchers;
private bool _loaded;

public BaseSettings(string path)
{
Dispatcher = Dispatcher.CurrentDispatcher;
_saveDebounceDispatcher = new DebounceDispatcher(SaveLocal);
_logger = new LLogger($"{GetType().Name}", Colors.Peru, LogLevel.Debug);
FilePath = path;
LoadFrom(FilePath);
}

public static string AssemblyDirectory => JsonSettings.AssemblyPath ?? throw new InvalidOperationException();

public static string AssemblyPath => AssemblyDirectory;

public static string SettingsPath => Path.Combine(AssemblyPath, "Settings");

private Dispatcher Dispatcher { get; }

[JsonIgnore]
private string FilePath { get; }

public static string GetSettingsFilePath(params string[] subPathParts)
{
var list = new List<string> { SettingsPath };
list.AddRange(subPathParts);
return Path.Combine(list.ToArray());
}

protected void LoadFrom(string file)
{
var properties = GetType().GetProperties();

foreach (var propertyInfo in properties)
{
_debounceDispatchers ??= new Dictionary<string, DebounceDispatcher>();
_debounceDispatchers[propertyInfo.Name] = new DebounceDispatcher(OnPropertyChangedDebounce);

var customAttributes = propertyInfo.GetCustomAttributes<DefaultValueAttribute>(true).ToList();

if (customAttributes.Count == 0)
{
continue;
}

foreach (var custom in customAttributes)
{
if (propertyInfo.GetSetMethod() != null)
{
propertyInfo.SetValue(this, custom.Value, null);
}
}
}

if (File.Exists(file))
{
try
{
Dispatcher.Invoke(() =>
{
try
{
JsonConvert.PopulateObject(File.ReadAllText(file), this);
}
catch (Exception e)
{
_logger.Exception(e);
}
});

foreach (var propertyInfo in properties)
{
//Check if property is a observable collection
if (typeof(INotifyCollectionChanged).IsAssignableFrom(propertyInfo.PropertyType))
{
//Set list changed event to trigger on property change
var collection = propertyInfo.GetValue(this) as INotifyCollectionChanged;
if (collection != null)
{
if (propertyInfo.PropertyType.IsGenericType && typeof(INotifyPropertyChanged).IsAssignableFrom(propertyInfo.PropertyType.GenericTypeArguments[0]))
{
collection.CollectionChanged += (_, args) =>
{
OnPropertyChanged(propertyInfo.Name);

if (args is { Action: NotifyCollectionChangedAction.Add, NewItems: not null })
{
foreach (var item in args.NewItems)
{
if (item is INotifyPropertyChanged notifyPropertyChanged)
{
notifyPropertyChanged.PropertyChanged += OnNotifyPropertyChangedOnPropertyChanged;
}
}
}

if (args is { Action: NotifyCollectionChangedAction.Remove, OldItems: not null })
{
foreach (var item in args.OldItems)
{
if (item is INotifyPropertyChanged notifyPropertyChanged)
{
notifyPropertyChanged.PropertyChanged -= OnNotifyPropertyChangedOnPropertyChanged;
}
}
}

return;

void OnNotifyPropertyChangedOnPropertyChanged(object? o, PropertyChangedEventArgs eventArgs)
{
OnPropertyChanged(propertyInfo.Name);
}
};
}
else
{
collection.CollectionChanged += (_, _) => { OnPropertyChanged(propertyInfo.Name); };
}
}
else
{
_logger.Error($"Property {propertyInfo.Name} is not an INotifyCollectionChanged it is {propertyInfo.PropertyType}");
}
}

if (typeof(IBindingList).IsAssignableFrom(propertyInfo.PropertyType))
{
//Set list changed event to trigger on property change
var collection = propertyInfo.GetValue(this) as IBindingList;
if (collection != null)
{
collection.ListChanged += (_, _) => { OnPropertyChanged(propertyInfo.Name); };
}
else
{
_logger.Error($"Property {propertyInfo.Name} is not an IBindingList it is {propertyInfo.PropertyType}");
}
}

if (typeof(INotifyPropertyChanged).IsAssignableFrom(propertyInfo.PropertyType))
{
var notifyPropertyChanged = propertyInfo.GetValue(this) as INotifyPropertyChanged;
if (notifyPropertyChanged != null)
{
notifyPropertyChanged.PropertyChanged += (_, _) => { OnPropertyChanged(propertyInfo.Name); };
}
else
{
_logger.Error($"Property {propertyInfo.Name} is not an INotifyPropertyChanged it is {propertyInfo.PropertyType}");
}
}
}
}
catch (Exception e)
{
_logger.Exception(e);
}
}

_loaded = true;
if (file != FilePath || !File.Exists(file))
{
Save();
}
}

public virtual void Save()
{
_saveDebounceDispatcher.Debounce(500, null!, DispatcherPriority.ApplicationIdle, Dispatcher);
}

private void SaveLocal(object? state = null)
{
SaveAs(FilePath);
}

public void SaveAs(string file)
{
try
{
if (!_loaded)
{
return;
}

if (!File.Exists(file))
{
var directoryName = Path.GetDirectoryName(file);
if (directoryName != null && !Directory.Exists(directoryName))
{
Directory.CreateDirectory(directoryName);
}
}

File.WriteAllText(file, JsonConvert.SerializeObject(this, Formatting.Indented));
}
catch (Exception e)
{
_logger.Exception(e);
}
}

private void OnPropertyChangedDebounce(object propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs((string)propertyName));
Save();
}

protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
if (propertyName == null)
{
return;
}

_debounceDispatchers![propertyName].Debounce(50, propertyName, DispatcherPriority.ApplicationIdle, Dispatcher);
}

protected bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
{
return false;
}

field = value;
OnPropertyChanged(propertyName);
return true;
}

public event PropertyChangedEventHandler? PropertyChanged;
}
73 changes: 73 additions & 0 deletions Settings/Base/BaseSettingsTyped.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using ff14bot;
using LlamaLibrary.Extensions;

namespace LlamaLibrary.Settings.Base;

public class BaseSettings<T> : BaseSettings
where T : BaseSettings<T>, new()
{
private static T? _instance;

public BaseSettings() : base(GetSettingsFilePath($"{typeof(T).Name}.json"))
{
}

public BaseSettings(string settingsFilePath) : base(settingsFilePath)
{
}

public static T Instance
{
get => _instance ??= new T();
set => _instance = value;
}

public static void SetInstance(T instance)
{
_instance = instance;
}
}

public class CharacterBaseSettings<T> : BaseSettings<T>
where T : BaseSettings<T>, new()
{
public CharacterBaseSettings() : base(GetSettingsFilePath($"{Core.Me.Name}_{Core.Me.PlayerId()}", $"{typeof(T).Name}.json"))
{
}

public CharacterBaseSettings(string fileName) : base(GetSettingsFilePath($"{Core.Me.Name}_{Core.Me.PlayerId()}", fileName))
{
}
}

public class AccountBaseSettings<T> : BaseSettings<T>
where T : BaseSettings<T>, new()
{
public AccountBaseSettings() : base(GetSettingsFilePath($"Account_{Core.Me.AccountId()}", $"{typeof(T).Name}.json"))
{
}

public AccountBaseSettings(string fileName) : base(GetSettingsFilePath($"Account_{Core.Me.AccountId()}", fileName))
{
}

public AccountBaseSettings(int accountId, string fileName) : base(GetSettingsFilePath($"Account_{accountId}", fileName))
{
}

public AccountBaseSettings(int accountId) : base(GetSettingsFilePath($"Account_{accountId}", $"{typeof(T).Name}.json"))
{
}
}

public class HomeWorldBaseSettings<T> : BaseSettings<T>
where T : BaseSettings<T>, new()
{
public HomeWorldBaseSettings() : base(GetSettingsFilePath($"HomeWorld_{Core.Me.HomeWorld()}", $"{typeof(T).Name}.json"))
{
}

public HomeWorldBaseSettings(string fileName) : base(GetSettingsFilePath($"HomeWorld_{Core.Me.HomeWorld()}", fileName))
{
}
}

0 comments on commit a4cc871

Please sign in to comment.