From 4f97dcec4cee8bfcc402f2eedcdf8da50031f6e9 Mon Sep 17 00:00:00 2001 From: "jozsef.b@aycode.com" <9Rj@D}fVwBaN> Date: Wed, 5 Jun 2024 16:00:02 +0200 Subject: [PATCH] Add SignalRDataSource, SignalRDataSourceAsync --- TIAM.Services/SignalRTags.cs | 2 + .../Shared/Components/Grids/TiamGrid.cs | 16 +- .../Shared/Services/AcSignalRClientBase.cs | 7 +- .../Shared/Utility/SignalRDataSource.cs | 380 ++++++++++++++++++ .../Shared/Utility/SignalRDataSourceAsync.cs | 117 ++++++ 5 files changed, 513 insertions(+), 9 deletions(-) create mode 100644 TIAMWebApp/Shared/Utility/SignalRDataSource.cs create mode 100644 TIAMWebApp/Shared/Utility/SignalRDataSourceAsync.cs diff --git a/TIAM.Services/SignalRTags.cs b/TIAM.Services/SignalRTags.cs index 398b13a0..684bfbd6 100644 --- a/TIAM.Services/SignalRTags.cs +++ b/TIAM.Services/SignalRTags.cs @@ -4,6 +4,8 @@ namespace TIAM.Services; public class SignalRTags : AcSignalRTags { + public const int None = 0; + public const int GetTransfer = 3; public const int GetTransfers = 4; public const int GetTransfersByContextId = 5; diff --git a/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs b/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs index 0dbdf208..356e92f0 100644 --- a/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs +++ b/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs @@ -194,10 +194,10 @@ namespace TIAMSharedUI.Shared.Components.Grids protected virtual async Task PostDataToServerAsync(TDataItem dataItem, int messageTag, DataChangeMode dataChangeMode) { - var eventArgs = new GridDataItemChangingEventArgs(this, dataItem, dataChangeMode); - await OnDataItemChanging.InvokeAsync(eventArgs); + var changingEventArgs = new GridDataItemChangingEventArgs(this, dataItem, dataChangeMode); + await OnDataItemChanging.InvokeAsync(changingEventArgs); - if (eventArgs.IsCanceled) + if (changingEventArgs.IsCanceled) { Logger.Debug($"{_gridLogName} OnDataItemChanging canceled"); return; @@ -211,17 +211,19 @@ namespace TIAMSharedUI.Shared.Components.Grids _dataSource.UpdateCollection(dataItem, dataChangeMode == DataChangeMode.Remove); //egyből látszódik a változás a grid-ben, nem csak a callback lefutásakor! felhasználóbarátabb... - J. - SignalRClient.PostDataAsync(messageTag, dataItem, async repsonse => + SignalRClient.PostDataAsync(messageTag, dataItem, async response => { - if (repsonse.Status != SignalResponseStatus.Success || repsonse.ResponseData == null) + if (response.Status != SignalResponseStatus.Success || response.ResponseData == null) { RefreshDataSourceAsync().Forget(); return; } - _dataSource.UpdateCollection(repsonse.ResponseData, dataChangeMode == DataChangeMode.Remove); + _dataSource.UpdateCollection(response.ResponseData, dataChangeMode == DataChangeMode.Remove); + + var changedEventArgs = new GridDataItemChangedEventArgs(this, response.ResponseData, dataChangeMode); + await OnDataItemChanged.InvokeAsync(changedEventArgs); - await OnDataItemChanged.InvokeAsync(eventArgs); InvokeAsync(StateHasChanged).Forget(); }).Forget(); diff --git a/TIAMWebApp/Shared/Services/AcSignalRClientBase.cs b/TIAMWebApp/Shared/Services/AcSignalRClientBase.cs index cefac36e..b9200d75 100644 --- a/TIAMWebApp/Shared/Services/AcSignalRClientBase.cs +++ b/TIAMWebApp/Shared/Services/AcSignalRClientBase.cs @@ -94,13 +94,16 @@ namespace TIAMWebApp.Shared.Application.Services public virtual Task GetByIdAsync(int messageTag, Guid id, Action> responseCallback) => SendMessageToServerAsync(messageTag, new SignalPostJsonDataMessage(new IdMessage(id)), responseCallback); - public virtual Task GetAllAsync(int messageTag) where TResponse : class - => SendMessageToServerAsync(messageTag); + public virtual Task GetAllAsync(int messageTag) where TResponseData : class + => SendMessageToServerAsync(messageTag); public virtual Task GetAllAsync(int messageTag, Action> responseCallback) => SendMessageToServerAsync(messageTag, null, responseCallback); public virtual Task GetAllAsync(int messageTag, Guid? contextId, Action> responseCallback) => SendMessageToServerAsync(messageTag, (contextId.IsNullOrEmpty() ? null : new SignalPostJsonDataMessage(new IdMessage(contextId.Value))), responseCallback); + public virtual Task GetAllAsync(int messageTag, Guid? contextId) where TResponseData : class + => SendMessageToServerAsync(messageTag, contextId.IsNullOrEmpty() ? null : new SignalPostJsonDataMessage(new IdMessage(contextId.Value)), AcDomain.NextUniqueInt32); + public virtual Task PostDataAsync(int messageTag, TPostData postData) where TPostData : class => SendMessageToServerAsync(messageTag, new SignalPostJsonDataMessage(postData), AcDomain.NextUniqueInt32); public virtual Task PostDataAsync(int messageTag, TPostData postData, Action> responseCallback) where TPostData : class diff --git a/TIAMWebApp/Shared/Utility/SignalRDataSource.cs b/TIAMWebApp/Shared/Utility/SignalRDataSource.cs new file mode 100644 index 00000000..37780551 --- /dev/null +++ b/TIAMWebApp/Shared/Utility/SignalRDataSource.cs @@ -0,0 +1,380 @@ +using System.Collections; +using System.Collections.ObjectModel; +using System.Diagnostics; +using AyCode.Core.Enums; +using AyCode.Core.Extensions; +using AyCode.Core.Interfaces; +using AyCode.Services.SignalRs; +using TIAM.Services; +using TIAMWebApp.Shared.Application.Services; + +namespace TIAMWebApp.Shared.Application.Utility +{ + public class ChangeTracking(DataChangeMode dataChangeMode, T newItem, T originalItem = default(T))where T: class, IId + { + public DataChangeMode DataChangeMode { get; init; } = dataChangeMode; + public T NewItem { get; init; } = newItem; + public T OriginalItem { get; init; } = originalItem; + } + + [Serializable] + [DebuggerDisplay("Count = {Count}")] + public class SignalRDataSource : IList, IList, IReadOnlyList where T: class, IId + { + protected readonly List InnerList = []; + private readonly object _syncRoot = new(); + + protected Guid? ContextId; + protected AcSignalRClientBase SignalRClient; + protected readonly SignalRCrudTags SignalRCrudTags; + + public SignalRDataSource(AcSignalRClientBase signalRClient, SignalRCrudTags signalRCrudTags, Guid? contextId = null, bool autoLoadDataSource = true) + { + ContextId = contextId; + + SignalRCrudTags = signalRCrudTags; + SignalRClient = signalRClient; + + if (autoLoadDataSource) LoadDataSource(); + } + + public bool IsSynchronized => true; + public object SyncRoot => _syncRoot; + public bool IsFixedSize => false; + + /// + /// GetAllMessageTag + /// + /// + /// + public void LoadDataSource() + { + if (SignalRCrudTags.GetAllMessageTag == SignalRTags.None) throw new ArgumentException($"_signalRCrudTags.GetAllMessageTag == SignalRTags.None;"); + + lock (_syncRoot) + { + var resultList = SignalRClient.GetAllAsync>(SignalRCrudTags.GetAllMessageTag, ContextId).GetAwaiter().GetResult() ?? throw new NullReferenceException(); + + Clear(); + InnerList.AddRange(resultList); + } + } + + /// + /// set: UpdateMessageTag + /// + /// + /// + /// + public T this[int index] + { + get + { + if ((uint)index >= (uint)Count) throw new ArgumentOutOfRangeException(nameof(index)); + + lock (_syncRoot) + { + return InnerList[index]; + } + } + set + { + lock (_syncRoot) + { + Update(index, value); + } + } + } + + public int Count + { + get + { + lock (_syncRoot) return InnerList.Count; + } + } + + /// + /// AddMessageTag + /// + /// + /// + public void Add(T item) + { + lock (_syncRoot) + { + if (Contains(item)) + throw new ArgumentException($@"It already contains this Id! Id: {item.Id}", nameof(item)); + + UnsafeAdd(item); + } + } + + /// + /// AddMessageTag or UpdateMessageTag + /// + /// + /// + public T AddOrUpdate(T item) + { + lock (_syncRoot) + { + var index = IndexOf(item); + + return index > -1 ? Update(index, item) : UnsafeAdd(item); + } + } + + //public void AddRange(IEnumerable collection) + //{ + // lock (_syncRoot) + // { + + // } + //} + + private T UnsafeAdd(T item) + { + if (SignalRCrudTags.AddMessageTag == SignalRTags.None) throw new ArgumentException($"_signalRCrudTags.AddMessageTag == SignalRTags.None;"); + + var result = SignalRClient.PostDataAsync(SignalRCrudTags.AddMessageTag, item).GetAwaiter().GetResult() ?? throw new NullReferenceException(); + InnerList.Add(result); + + return result; + } + + /// + /// AddMessageTag + /// + /// + /// + /// + /// + public void Insert(int index, T item) + { + if (SignalRCrudTags.AddMessageTag == SignalRTags.None) throw new ArgumentException($"_signalRCrudTags.AddMessageTag == SignalRTags.None;"); + + lock (_syncRoot) + { + if (Contains(item)) + throw new ArgumentException($@"It already contains this Id! Id: {item.Id}", nameof(item)); + + var result = SignalRClient.PostDataAsync(SignalRCrudTags.AddMessageTag, item).GetAwaiter().GetResult() ?? throw new NullReferenceException(); + InnerList.Insert(index, result); + } + } + + /// + /// UpdateMessageTag + /// + /// + public T Update(T item) => Update(IndexOf(item), item); + + /// + /// UpdateMessageTag + /// + /// + /// + /// /// + /// /// + /// + /// + public T Update(int index, T item) + { + if (SignalRCrudTags.UpdateMessageTag == SignalRTags.None) throw new ArgumentException($"_signalRCrudTags.UpdateMessageTag == SignalRTags.None;"); + + if (default(T) != null && item == null) throw new NullReferenceException(nameof(item)); + if (item.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(item), "Update->item.Id.IsNullOrEmpty()"); + if ((uint)index >= (uint)Count) throw new ArgumentOutOfRangeException(nameof(index)); + + lock (_syncRoot) + { + if (InnerList[index].Id != item.Id) + throw new ArgumentException($@"_list[index].Id != item.Id! Id: {item.Id}", nameof(item)); + + var result = SignalRClient.PostDataAsync(SignalRCrudTags.UpdateMessageTag, item).GetAwaiter().GetResult() ?? throw new NullReferenceException(); + + InnerList[index] = result; + return result; + } + } + + /// + /// RemoveMessageTag + /// + /// + /// + public bool Remove(T item) + { + lock (_syncRoot) + { + var index = IndexOf(item); + + if (index < 0) return false; + + RemoveAt(index); + return true; + } + } + + /// + /// RemoveMessageTag + /// + /// + /// + /// /// + /// + public void RemoveAt(int index) + { + if (SignalRCrudTags.RemoveMessageTag == SignalRTags.None) throw new ArgumentException($"_signalRCrudTags.RemoveMessageTag == SignalRTags.None;"); + + lock (_syncRoot) + { + var item = InnerList[index]; + if (item.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(item), $@"RemoveAt->item.Id.IsNullOrEmpty(); index: {index}"); + + var result = SignalRClient.PostDataAsync(SignalRCrudTags.RemoveMessageTag, item).GetAwaiter().GetResult() ?? throw new NullReferenceException(); + + InnerList.RemoveAt(index); + } + } + + public void Clear() + { + lock (_syncRoot) InnerList.Clear(); + } + + public int IndexOf(T item) + { + lock (_syncRoot) + return InnerList.FindIndex(x => x.Id == item.Id); + } + + public bool Contains(T item) + { + lock (_syncRoot) + return IndexOf(item) > -1; + } + + public void CopyTo(T[] array) => CopyTo(array, 0); + + public void CopyTo(T[] array, int arrayIndex) + { + lock (_syncRoot) InnerList.CopyTo(array, arrayIndex); + } + + public int BinarySearch(int index, int count, T item, IComparer? comparer) + { + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index)); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count)); + if (Count - index < count) + throw new ArgumentException("Invalid length"); + + lock (_syncRoot) + return InnerList.BinarySearch(index, count, item, comparer); + } + + public int BinarySearch(T item) => BinarySearch(0, Count, item, null); + public int BinarySearch(T item, IComparer? comparer) => BinarySearch(0, Count, item, comparer); + + public IEnumerator GetEnumerator() + { + lock (_syncRoot) + return InnerList.ToList().GetEnumerator(); + } + + public ReadOnlyCollection AsReadOnly() => new(this); + private static bool IsCompatibleObject(object? value) => (value is T) || (value == null && default(T) == null); + + + #region IList, ICollection + bool IList.IsReadOnly => false; + + object? IList.this[int index] + { + get => this[index]; + set + { + if (default(T) != null && value == null) throw new NullReferenceException(nameof(value)); + + try + { + this[index] = (T)value!; + } + catch (InvalidCastException) + { + throw new InvalidCastException(nameof(value)); + } + } + } + + int IList.Add(object? item) + { + if (default(T) != null && item == null) throw new NullReferenceException(nameof(item)); + + try + { + Add((T)item!); + } + catch (InvalidCastException) + { + throw new InvalidCastException(nameof(item)); + } + + return Count - 1; + } + + void IList.Clear() => Clear(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + bool IList.Contains(object? item) => IsCompatibleObject(item) && Contains((T)item!); + int IList.IndexOf(object? item) => (IsCompatibleObject(item)) ? IndexOf((T)item!) : -1; + + void IList.Insert(int index, object? item) + { + if (default(T) != null && item == null) throw new NullReferenceException(nameof(item)); + + try + { + Insert(index, (T)item!); + } + catch (InvalidCastException) + { + throw new InvalidCastException(nameof(item)); + } + } + + void IList.Remove(object? item) + { + if (IsCompatibleObject(item)) Remove((T)item!); + } + + void ICollection.Clear() => Clear(); + + void ICollection.CopyTo(Array array, int arrayIndex) + { + if ((array != null) && (array.Rank != 1)) + { + throw new ArgumentException(); + } + + try + { + //TODO: _list.ToArray() - ez nem az igazi... - J. + Array.Copy(InnerList.ToArray(), 0, array!, arrayIndex, InnerList.Count); + } + catch (ArrayTypeMismatchException) + { + throw new ArrayTypeMismatchException(); + } + } + + int ICollection.Count => Count; + int ICollection.Count => Count; + bool ICollection.IsReadOnly => false; + void IList.RemoveAt(int index) => RemoveAt(index); + int IReadOnlyCollection.Count => Count; + #endregion IList, ICollection + } +} diff --git a/TIAMWebApp/Shared/Utility/SignalRDataSourceAsync.cs b/TIAMWebApp/Shared/Utility/SignalRDataSourceAsync.cs new file mode 100644 index 00000000..f46ff930 --- /dev/null +++ b/TIAMWebApp/Shared/Utility/SignalRDataSourceAsync.cs @@ -0,0 +1,117 @@ +using System.Diagnostics; +using AyCode.Core.Enums; +using AyCode.Core.Helpers; +using AyCode.Core.Interfaces; +using AyCode.Services.SignalRs; +using TIAM.Services; +using TIAMWebApp.Shared.Application.Services; + +namespace TIAMWebApp.Shared.Application.Utility; + +[Serializable] +[DebuggerDisplay("Count = {Count}")] +public class SignalRDataSourceAsync : SignalRDataSource where T : class, IId +{ + public Action>? OnItemChanged; + public Action>? OnDataSourceLoaded; + + public SignalRDataSourceAsync(AcSignalRClientBase signalRClient, SignalRCrudTags signalRCrudTags, Guid? contextId = null, Action>? onDataSourceLoaded = null, bool autoLoadDataSource = false) + : base(signalRClient, signalRCrudTags, contextId, false) + { + OnDataSourceLoaded = onDataSourceLoaded; + + if (autoLoadDataSource) LoadDataSourceAsync(); + } + + public void LoadDataSourceAsync() + { + if (SignalRCrudTags.GetAllMessageTag == SignalRTags.None) throw new ArgumentException($"_signalRCrudTags.GetAllMessageTag == SignalRTags.None;"); + + Monitor.Exit(SyncRoot); //Exception test - J. + + Monitor.Enter(SyncRoot); + try + { + SignalRClient.GetAllAsync>(SignalRCrudTags.GetAllMessageTag, ContextId, response => + { + try + { + if (response.Status == SignalResponseStatus.Error) throw new Exception($"LoadDataSourceAsync; response.Status == SignalResponseStatus.Error"); + if (response.ResponseData == null) throw new NullReferenceException($"response.ResponseData == null"); + + Clear(); + InnerList.AddRange(response.ResponseData); + } + finally + { + Monitor.Exit(SyncRoot); + } + + OnDataSourceLoaded?.Invoke(this); + }).Forget(); + } + catch (Exception) + { + Monitor.Exit(SyncRoot); + throw; + } + } + + + //public T Add(T item, int messageTag) => PostDataToServerAsync(item, messageTag, DataChangeMode.Add).GetAwaiter().GetResult(); + //public Task AddAsync(T item, int messageTag) => PostDataToServerAsync(item, messageTag, DataChangeMode.Add); + + + //public Task UpdateAsync(T item, int messageTag) => PostDataToServerAsync(item, messageTag, DataChangeMode.Update); + + //public Task RemoveAsync(T item, int messageTag) => PostDataToServerAsync(item, messageTag, DataChangeMode.Remove); + + //public Task RemoveAsync(Guid id, int messageTag) + //{ + // var item = _list.FirstOrDefault(x => x.Id == id); + + // return item == null ? Task.CompletedTask : RemoveAsync(item, messageTag); + //} + + //protected virtual Task PostDataToServerAsync(T item, int messageTag, DataChangeMode dataChangeMode) + //{ + // if (messageTag == 0) return Task.CompletedTask; + + // logger.Info($"{_listLogName} PostDataToServerAsync called; transferId " + item.Id); + + // if (item.Id.IsNullOrEmpty()) item.Id = Guid.NewGuid(); + + // _list.UpdateCollection(item, dataChangeMode == DataChangeMode.Remove); //egyből látszódik a változás a grid-ben, nem csak a callback lefutásakor! felhasználóbarátabb... - J. + + // await _signalRClient.PostDataAsync(messageTag, item, async repsonse => + // { + // if (repsonse.Status != SignalResponseStatus.Success || repsonse.ResponseData == null) + // { + // RefreshDataSourceAsync().Forget(); + // return; + // } + + // _list.UpdateCollection(repsonse.ResponseData, dataChangeMode == DataChangeMode.Remove); + + // var eventArgs = new ItemChangedEventArgs(repsonse.ResponseData, dataChangeMode); + // OnItemChanged.Invoke(eventArgs); + // }); + + // //transfer = await devAdminSignalClient.PostDataAsync(SignalRTags.UpdateTransferAsync, transfer); + + // return Task.CompletedTask; + //} + + public class ItemChangedEventArgs where T : IId + { + internal ItemChangedEventArgs(T item, DataChangeMode dataChangeMode) + { + Item = item; + DataChangeMode = dataChangeMode; + } + + public T Item { get; } + public DataChangeMode DataChangeMode { get; } + } + +} \ No newline at end of file