actually now its usable

This commit is contained in:
zznty
2023-11-13 23:17:39 +07:00
parent aecc7ee66f
commit ce07a1e86a
41 changed files with 1401 additions and 138 deletions

43
heh/DbManager.cs Normal file
View 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
View 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
View 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;
}
}

View 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
}

View 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
}

View 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
}

View 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
View 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>