actually now its usable
This commit is contained in:
43
heh/DbManager.cs
Normal file
43
heh/DbManager.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System.IO;
|
||||
using NLog;
|
||||
using PetaPoco;
|
||||
using PetaPoco.Core.Inflection;
|
||||
using PetaPoco.Providers;
|
||||
using Torch;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.Managers;
|
||||
|
||||
namespace heh;
|
||||
|
||||
public interface IDbManager : IManager
|
||||
{
|
||||
IDatabase Create(string name);
|
||||
}
|
||||
public class DbManager : Manager, IDbManager
|
||||
{
|
||||
private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
#pragma warning disable CS0618
|
||||
public static readonly IDbManager Static = new DbManager(TorchBase.Instance);
|
||||
#pragma warning restore CS0618
|
||||
|
||||
public DbManager(ITorchBase torchInstance) : base(torchInstance)
|
||||
{
|
||||
}
|
||||
public IDatabase Create(string name)
|
||||
{
|
||||
return DatabaseConfiguration.Build()
|
||||
.UsingProvider<SQLiteDatabaseProvider>()
|
||||
.UsingExceptionThrown((_, args) => Log.Error(args.Exception))
|
||||
.WithAutoSelect()
|
||||
.UsingConnectionString($"Data Source={Path.Combine(Torch.Config.InstancePath, $"{name}.db")};Version=3;")
|
||||
.UsingDefaultMapper<ConventionMapper>(mapper =>
|
||||
{
|
||||
string UnFuckIt(IInflector inflector, string s) => inflector.Underscore(s).ToLower();
|
||||
mapper.InflectColumnName = UnFuckIt;
|
||||
mapper.InflectTableName = UnFuckIt;
|
||||
})
|
||||
.Create();
|
||||
}
|
||||
}
|
3
heh/FodyWeavers.xml
Normal file
3
heh/FodyWeavers.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<PropertyChanged />
|
||||
</Weavers>
|
35
heh/StringExtensions.cs
Normal file
35
heh/StringExtensions.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
namespace heh;
|
||||
|
||||
// https://github.com/ServiceStack/ServiceStack.Text/blob/master/src/ServiceStack.Text/StringExtensions.cs
|
||||
public static class StringExtensions
|
||||
{
|
||||
public static bool Glob(this string value, string pattern)
|
||||
{
|
||||
int pos;
|
||||
for (pos = 0; pattern.Length != pos; pos++)
|
||||
{
|
||||
switch (pattern[pos])
|
||||
{
|
||||
case '?':
|
||||
break;
|
||||
|
||||
case '*':
|
||||
for (var i = value.Length; i >= pos; i--)
|
||||
{
|
||||
if (Glob(value.Substring(i), pattern.Substring(pos + 1)))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
default:
|
||||
if (value.Length == pos || char.ToUpper(pattern[pos]) != char.ToUpper(value[pos]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return value.Length == pos;
|
||||
}
|
||||
}
|
71
heh/Utils/ChangeListener.cs
Normal file
71
heh/Utils/ChangeListener.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using NLog;
|
||||
namespace heh.Utils;
|
||||
|
||||
public abstract class ChangeListener : INotifyPropertyChanged, IDisposable
|
||||
{
|
||||
#region *** Members ***
|
||||
protected static readonly ILogger Log = LogManager.GetCurrentClassLogger();
|
||||
protected string? PropertyName;
|
||||
#endregion
|
||||
|
||||
|
||||
#region *** Abstract Members ***
|
||||
protected abstract void Unsubscribe();
|
||||
#endregion
|
||||
|
||||
|
||||
#region *** INotifyPropertyChanged Members and Invoker ***
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
protected virtual void RaisePropertyChanged(string propertyName)
|
||||
{
|
||||
var temp = PropertyChanged;
|
||||
temp?.Invoke(this, new(propertyName));
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region *** Disposable Pattern ***
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
~ChangeListener()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region *** Factory ***
|
||||
public static ChangeListener? Create(object value, string? propertyName = null)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case INotifyCollectionChanged collectionChanged and IEnumerable:
|
||||
return new CollectionChangeListener(collectionChanged, propertyName);
|
||||
case INotifyPropertyChanged propertyChanged:
|
||||
return new ChildChangeListener(propertyChanged, propertyName);
|
||||
default:
|
||||
Log.Warn("changes in {0} type cannot be watched", value.GetType().FullName);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
#endregion
|
||||
}
|
131
heh/Utils/ChildChangeListener.cs
Normal file
131
heh/Utils/ChildChangeListener.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
namespace heh.Utils;
|
||||
|
||||
public class ChildChangeListener : ChangeListener
|
||||
{
|
||||
#region *** Members ***
|
||||
protected static readonly Type InotifyType = typeof(INotifyPropertyChanged);
|
||||
|
||||
private readonly INotifyPropertyChanged _value;
|
||||
private readonly Type _type;
|
||||
private readonly Dictionary<string?, ChangeListener?> _childListeners = new();
|
||||
#endregion
|
||||
|
||||
|
||||
#region *** Constructors ***
|
||||
public ChildChangeListener(INotifyPropertyChanged instance)
|
||||
{
|
||||
_value = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
_type = _value.GetType();
|
||||
|
||||
Subscribe();
|
||||
}
|
||||
|
||||
public ChildChangeListener(INotifyPropertyChanged instance, string? propertyName)
|
||||
: this(instance)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region *** Private Methods ***
|
||||
private void Subscribe()
|
||||
{
|
||||
_value.PropertyChanged += value_PropertyChanged;
|
||||
|
||||
var query =
|
||||
from property
|
||||
in _type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
|
||||
where InotifyType.IsAssignableFrom(property.PropertyType)
|
||||
select property;
|
||||
|
||||
foreach (var property in query)
|
||||
{
|
||||
// Declare property as known "Child", then register it
|
||||
_childListeners.Add(property.Name, null);
|
||||
ResetChildListener(property.Name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Resets known (must exist in children collection) child event handlers
|
||||
/// </summary>
|
||||
/// <param name="propertyName">Name of known child property</param>
|
||||
private void ResetChildListener(string? propertyName)
|
||||
{
|
||||
if (propertyName is null || !_childListeners.TryGetValue(propertyName, out var childListener))
|
||||
return;
|
||||
|
||||
// Unsubscribe if existing
|
||||
if (childListener != null)
|
||||
{
|
||||
childListener.PropertyChanged -= child_PropertyChanged;
|
||||
|
||||
// Should unsubscribe all events
|
||||
childListener.Dispose();
|
||||
_childListeners.Remove(propertyName);
|
||||
}
|
||||
|
||||
var property = _type.GetProperty(propertyName);
|
||||
if (property == null)
|
||||
throw new InvalidOperationException($"Was unable to get '{propertyName}' property information from Type '{_type.Name}'");
|
||||
|
||||
var newValue = property.GetValue(_value, null);
|
||||
|
||||
if (newValue is not null)
|
||||
_childListeners[propertyName] = Create(newValue, propertyName);
|
||||
|
||||
if (_childListeners[propertyName] != null)
|
||||
_childListeners[propertyName]!.PropertyChanged += child_PropertyChanged;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region *** Event Handler ***
|
||||
private void child_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
RaisePropertyChanged(e.PropertyName);
|
||||
}
|
||||
|
||||
private void value_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
// First, reset child on change, if required...
|
||||
ResetChildListener(e.PropertyName);
|
||||
|
||||
// ...then, notify about it
|
||||
RaisePropertyChanged(e.PropertyName);
|
||||
}
|
||||
|
||||
protected override void RaisePropertyChanged(string propertyName)
|
||||
{
|
||||
// Special Formatting
|
||||
base.RaisePropertyChanged($"{PropertyName}{(PropertyName != null ? "." : null)}{propertyName}");
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region *** Overrides ***
|
||||
/// <summary>
|
||||
/// Release all child handlers and self handler
|
||||
/// </summary>
|
||||
protected override void Unsubscribe()
|
||||
{
|
||||
_value.PropertyChanged -= value_PropertyChanged;
|
||||
|
||||
foreach (var kv in _childListeners)
|
||||
{
|
||||
kv.Value?.Dispose();
|
||||
}
|
||||
|
||||
_childListeners.Clear();
|
||||
|
||||
Debug.WriteLine("ChildChangeListener '{0}' unsubscribed", PropertyName);
|
||||
}
|
||||
#endregion
|
||||
}
|
123
heh/Utils/CollectionChangeListener.cs
Normal file
123
heh/Utils/CollectionChangeListener.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
namespace heh.Utils;
|
||||
|
||||
public class CollectionChangeListener : ChangeListener
|
||||
{
|
||||
#region *** Members ***
|
||||
private readonly INotifyCollectionChanged _value;
|
||||
private readonly Dictionary<INotifyPropertyChanged, ChangeListener> _collectionListeners = new();
|
||||
#endregion
|
||||
|
||||
|
||||
#region *** Constructors ***
|
||||
public CollectionChangeListener(INotifyCollectionChanged collection, string? propertyName)
|
||||
{
|
||||
_value = collection;
|
||||
PropertyName = propertyName;
|
||||
|
||||
if (_value.GetType().IsGenericType && !typeof(INotifyPropertyChanged).IsAssignableFrom(_value.GetType().GetGenericArguments()[0]))
|
||||
return;
|
||||
Subscribe();
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region *** Private Methods ***
|
||||
private void Subscribe()
|
||||
{
|
||||
_value.CollectionChanged += value_CollectionChanged;
|
||||
|
||||
foreach (INotifyPropertyChanged item in (IEnumerable)_value)
|
||||
{
|
||||
ResetChildListener(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetChildListener(INotifyPropertyChanged item)
|
||||
{
|
||||
if (item == null)
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
|
||||
RemoveItem(item);
|
||||
|
||||
var listener = Create(item)!;
|
||||
|
||||
listener.PropertyChanged += listener_PropertyChanged;
|
||||
_collectionListeners.Add(item, listener);
|
||||
}
|
||||
|
||||
private void RemoveItem(INotifyPropertyChanged item)
|
||||
{
|
||||
// Remove old
|
||||
if (!_collectionListeners.ContainsKey(item))
|
||||
return;
|
||||
_collectionListeners[item].PropertyChanged -= listener_PropertyChanged;
|
||||
|
||||
_collectionListeners[item].Dispose();
|
||||
_collectionListeners.Remove(item);
|
||||
}
|
||||
|
||||
|
||||
private void ClearCollection()
|
||||
{
|
||||
foreach (var key in _collectionListeners.Keys)
|
||||
{
|
||||
_collectionListeners[key].Dispose();
|
||||
}
|
||||
|
||||
_collectionListeners.Clear();
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region *** Event handlers ***
|
||||
private void value_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (e.Action == NotifyCollectionChangedAction.Reset)
|
||||
{
|
||||
ClearCollection();
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't care about e.Action, if there are old items, Remove them...
|
||||
if (e.OldItems != null)
|
||||
{
|
||||
foreach (INotifyPropertyChanged item in e.OldItems)
|
||||
RemoveItem(item);
|
||||
}
|
||||
|
||||
// ...add new items as well
|
||||
if (e.NewItems != null)
|
||||
{
|
||||
foreach (INotifyPropertyChanged item in e.NewItems)
|
||||
ResetChildListener(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void listener_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
// ...then, notify about it
|
||||
RaisePropertyChanged($"{PropertyName}{(PropertyName != null ? "[]." : null)}{e.PropertyName}");
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region *** Overrides ***
|
||||
/// <summary>
|
||||
/// Releases all collection item handlers and self handler
|
||||
/// </summary>
|
||||
protected override void Unsubscribe()
|
||||
{
|
||||
ClearCollection();
|
||||
|
||||
_value.CollectionChanged -= value_CollectionChanged;
|
||||
|
||||
Debug.WriteLine("CollectionChangeListener unsubscribed");
|
||||
}
|
||||
#endregion
|
||||
}
|
84
heh/Utils/ProperPersistent.cs
Normal file
84
heh/Utils/ProperPersistent.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
using NLog;
|
||||
using Torch;
|
||||
namespace heh.Utils;
|
||||
|
||||
/// <summary>
|
||||
/// Simple class that manages saving <see cref="Persistent{T}.Data"/> to disk using XML serialization.
|
||||
/// Will automatically save on changes in the data class.
|
||||
/// </summary>
|
||||
/// <typeparam name="TViewModel">Data class type</typeparam>
|
||||
public sealed class ProperPersistent<TViewModel> : IDisposable where TViewModel : class, INotifyPropertyChanged, new()
|
||||
{
|
||||
private static readonly XmlSerializer Serializer = new(typeof(TViewModel));
|
||||
private static readonly ILogger Log = LogManager.GetLogger($"ProperPersistent_{typeof(TViewModel)}");
|
||||
|
||||
private readonly ChangeListener _listener;
|
||||
private Timer? _saveConfigTimer;
|
||||
|
||||
public TViewModel Data { get; }
|
||||
public string Path { get; set; }
|
||||
|
||||
public ProperPersistent(string path, TViewModel? defaultViewModel = default)
|
||||
{
|
||||
Path = path;
|
||||
if (File.Exists(path))
|
||||
{
|
||||
try
|
||||
{
|
||||
using var stream = File.OpenRead(path);
|
||||
Data = (TViewModel) Serializer.Deserialize(stream);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
Data = defaultViewModel ?? new TViewModel();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Data = defaultViewModel ?? new TViewModel();
|
||||
Save();
|
||||
}
|
||||
|
||||
_listener = ChangeListener.Create(Data)!;
|
||||
_listener.PropertyChanged += ListenerOnPropertyChanged;
|
||||
}
|
||||
private void ListenerOnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
SaveAsync();
|
||||
}
|
||||
|
||||
private void SaveAsync()
|
||||
{
|
||||
_saveConfigTimer ??= new(_ => Save());
|
||||
_saveConfigTimer.Change(1000, -1);
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
_listener.Dispose();
|
||||
_saveConfigTimer?.Dispose();
|
||||
_saveConfigTimer = null;
|
||||
}
|
||||
#region Backwards compatibility
|
||||
public void Save(string? newPath = null)
|
||||
{
|
||||
if (newPath is not null)
|
||||
Path = newPath;
|
||||
|
||||
using var stream = File.Create(Path);
|
||||
using var writer = new XmlTextWriter(stream, Encoding.UTF8)
|
||||
{
|
||||
Formatting = Formatting.Indented
|
||||
};
|
||||
|
||||
Serializer.Serialize(writer, Data);
|
||||
}
|
||||
|
||||
public static ProperPersistent<TViewModel> Load(string path, bool saveIfNew = true) => new(path);
|
||||
#endregion
|
||||
}
|
21
heh/heh.csproj
Normal file
21
heh/heh.csproj
Normal file
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<LangVersion>10</LangVersion>
|
||||
<UseWpf>true</UseWpf>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DebugType>none</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="PetaPoco.Compiled" Version="6.0.480" />
|
||||
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Torch.Server.ReferenceAssemblies" Version="1.3.1.207-master" PrivateAssets="all" IncludeAssets="compile" />
|
||||
</ItemGroup>
|
||||
</Project>
|
Reference in New Issue
Block a user