123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757 |
- using System.Collections;
- using System.Collections.ObjectModel;
- using System.ComponentModel;
- using System.Linq.Expressions;
- using System.Runtime.CompilerServices;
- using Avalonia.Controls.Selection;
- using Avalonia.Threading;
- using CommunityToolkit.Mvvm.ComponentModel;
- using InABox.Clients;
- using InABox.Configuration;
- using InABox.Core;
- using JetBrains.Annotations;
- namespace InABox.Avalonia
- {
-
- public class CoreRepositoryItemCreatedArgs<TShell> : EventArgs
- {
- public TShell Item { get; private set; }
- public CoreRepositoryItemCreatedArgs(TShell item)
- {
- Item = item;
- }
- }
- public delegate void CoreRepositoryItemCreatedEvent<TShell>(object sender, CoreRepositoryItemCreatedArgs<TShell> args);
-
-
- public abstract class CoreRepository
- {
- public static bool IsCached(string? filename) =>
- !String.IsNullOrWhiteSpace(filename)
- && File.Exists(CacheFileName(filename));
-
- public static string CacheFileName(string? filename) =>
- Path.Combine(CacheFolder(), filename);
- public static string CacheFolder()
- {
- var result = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
- if (OperatingSystem.IsWindows())
- {
- var assembly = Path.GetFileNameWithoutExtension(System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName);
- result = Path.Combine(result, assembly);
- }
- if (CacheID != Guid.Empty)
- result = Path.Combine(result,CacheID.ToString());
- if (!Directory.Exists(result))
- Directory.CreateDirectory(result);
- return result;
- }
- public static Guid CacheID { get; set; }
-
- }
- public partial class CoreRepositoryFilter : ObservableObject
- {
- [ObservableProperty]
- private string? _name;
-
- [ObservableProperty]
- private string? _filter;
-
- [ObservableProperty]
- private bool _selected;
- }
-
- public abstract class CoreRepository<TParent, TItem, TEntity> : CoreRepository, ICoreRepository, IEnumerable<TItem>
- where TParent : CoreRepository<TParent, TItem, TEntity>
- where TEntity : Entity, IRemotable, IPersistent, new()
- where TItem : Shell<TParent,TEntity>, new()
- {
- readonly MultiQuery _query = new();
-
- public Func<Filter<TEntity>> Filter { get; set; }
- protected virtual Filter<TEntity> BaseFilter() => null;
-
- public IModelHost Host { get; set; }
-
- private DateTime _lastUpdated = DateTime.MinValue;
- public DateTime LastUpdated
- {
- get => _lastUpdated;
- protected set
- {
- _lastUpdated = value;
- OnPropertyChanged();
- }
- }
-
- public Func<string>? FileName { get; }
-
- private string DataFileName() => FileName != null
- ? $"{FileName.Invoke()}.data"
- : string.Empty;
-
- private string FilterFileName() => !string.IsNullOrWhiteSpace(FilterTag) && FileName != null
- ? $"{FileName.Invoke()}.filter"
- : string.Empty;
-
- protected CoreRepository(IModelHost host, Func<Filter<TEntity>> filter, Func<string>? filename = null)
- {
- AllItems = new CoreObservableCollection<TItem>();
- AllItems.CollectionChanged += (sender, args) => ItemsChanged(AllItems);
- EnableSynchronization(AllItems);
-
- Items = new CoreObservableCollection<TItem>();
- EnableSynchronization(Items);
- SelectedItems = new CoreObservableCollection<TItem>();
- EnableSynchronization(SelectedItems);
-
- Reset();
- Host = host;
- Filter = filter;
- FileName = filename;
- }
- protected virtual void ItemsChanged(IEnumerable<TItem> items)
- {
- }
- private void EnableSynchronization(IEnumerable items)
- {
- // BindingBase.EnableCollectionSynchronization(items, null,
- // (collection, context, method, access) =>
- // {
- // lock (collection)
- // {
- // method?.Invoke();
- // }
- // }
- // );
- }
- #region INotifyPropertyChanged
-
- public event PropertyChangedEventHandler PropertyChanged;
- protected void DoPropertyChanged(object sender, PropertyChangedEventArgs args)
- {
- PropertyChanged?.Invoke(sender, args);
- }
-
- protected bool SetProperty<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;
- }
- protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
- => DoPropertyChanged(this, new PropertyChangedEventArgs(propertyName));
-
- #endregion
-
- #region Image Lookups
-
- public Dictionary<Guid, byte[]> Images { get; private set; } = new Dictionary<Guid, byte[]>();
- public byte[]? GetImageSource(Guid id)
- {
- return Images.GetValueOrDefault(id);
- }
-
- public byte[] GetImage(Guid id) => Images.GetValueOrDefault(id);
-
- public bool HasImages() => Images.Any();
- #endregion
-
- protected virtual string FilterTag => typeof(TEntity).EntityName().Split('.').Last();
-
- public CoreObservableCollection<CoreRepositoryFilter> AvailableFilters { get; } = new();
- IEnumerable ICoreRepository.AvailableFilters => AvailableFilters;
-
- protected Filter<TEntity>? SelectedFilter =>
- Serialization.Deserialize<Filter<TEntity>>(AvailableFilters.FirstOrDefault(x => x.Selected)?.Filter);
-
- public bool FiltersVisible => AvailableFilters.Any();
-
- public void SelectFilter(String? name)
- {
- var definition = AvailableFilters.FirstOrDefault(x => String.Equals(x.Name, name));
- foreach (var availableFilter in AvailableFilters)
- availableFilter.Selected = definition == availableFilter;
- OnPropertyChanged(nameof(AvailableFilters));
- }
-
- protected Filter<TEntity> EffectiveFilter()
- {
- var filters = new Filters<TEntity>();
- filters.Add(BaseFilter());
- filters.Add(Filter?.Invoke());
- filters.Add(SelectedFilter);
- var result = filters.Combine();
- return result;
- }
-
- protected Columns<TOtherEntity> GetColumns<TOtherItem, TOtherEntity>()
- where TOtherItem : Shell<TParent, TOtherEntity>, new()
- where TOtherEntity : Entity, IRemotable, IPersistent, new()
- {
- return new TOtherItem().Columns.Columns;
- }
- protected virtual void Initialize()
- {
- Loaded = false;
- AllItems.Clear();
- Items.Clear();
- SelectedItems.Clear();
- Images.Clear();
- }
-
-
-
- public bool Loaded { get; protected set; }
-
- private void DoRefresh(bool force)
- {
- var curselected = SelectedItems.ToArray();
- Items.Clear();
- SelectedItems.Clear();
- var dataFileName = DataFileName();
- if (!force && !Loaded && CoreRepository.IsCached(dataFileName))
- {
- DoBeforeLoad();
- if (LoadFromStorage())
- {
- DoAfterLoad();
- SelectedItems.AddRange(Items.Where(x=>curselected.Contains(x)));
- return;
- }
- }
-
- if ((force || !Loaded) && (Host.Status == ConnectionStatus.Connected))
- {
- DoLoad();
- SaveToStorage();
- SelectedItems.AddRange(Items.Where(x=>curselected.Contains(x)));
- return;
- }
- SelectedItems.AddRange(Items.Where(x => curselected.Contains(x)));
- }
- private void AfterRefresh()
- {
- Loaded = true;
- Dispatcher.UIThread.Invoke(Search);
- NotifyChanged();
- }
- public virtual ICoreRepository Refresh(bool force)
- {
- DoRefresh(force);
- AfterRefresh();
- return this;
- }
-
- public void Refresh(bool force, Action loaded)
- {
- Task.Run(
- () =>
- {
- DoRefresh(force);
- Dispatcher.UIThread.Post(
- () =>
- {
- AfterRefresh();
- loaded?.Invoke();
- }
- );
- }
- );
- }
- public Task<ICoreRepository> RefreshAsync(bool force)
- {
- return Task.Run(() => Refresh(force));
- }
-
- public void Reset()
- {
- Initialize();
- }
-
- public event CoreRepositoryChangedEvent Changed;
- protected void NotifyChanged() => Changed?.Invoke(this, new CoreRepositoryChangedEventArgs());
-
- public virtual SortOrder<TEntity> Sort => LookupFactory.DefineSort<TEntity>();
- protected CoreObservableCollection<TItem> AllItems { get; private set; }
-
- private CoreTable _table = new CoreTable();
- public CoreObservableCollection<TItem> Items { get; private set; }
- public int ItemCount => Items.Count;
-
- IEnumerable ICoreRepository.Items => Items;
-
- #region Item Selection
-
- public CoreObservableCollection<TItem> SelectedItems { get; private set; }
-
- IEnumerable ICoreRepository.SelectedItems => SelectedItems;
-
- public bool IsSelected(TItem item) => (item != null) && SelectedItems.Contains(item);
-
- public void SetSelectedItems(IEnumerable<TItem> items)
- {
- SelectedItems.ReplaceRange(items);
- Search();
- }
-
- public void SelectItem([CanBeNull] TItem item)
- {
- if ((item != null) && !SelectedItems.Contains(item))
- {
- SelectedItems.Add(item);
- Search();
- }
- }
-
- public void UnselectItem([CanBeNull] TItem item)
- {
- if ((item != null) && SelectedItems.Contains(item))
- {
- SelectedItems.Remove(item);
- Search();
- }
- }
-
- public void ToggleSelection(TItem item)
- {
- if (IsSelected(item))
- UnselectItem(item);
- else
- SelectItem(item);
- }
-
- public void SelectNone()
- {
- SelectedItems.Clear();
- Search();
- }
- public void SelectAll()
- {
- SelectedItems.ReplaceRange(Items);
- Search();
- }
-
- void ICoreRepository.SelectItem(object item) => SelectItem(item as TItem);
- void ICoreRepository.UnselectItem(object item) => UnselectItem(item as TItem);
- void ICoreRepository.ToggleSelection(object item) => ToggleSelection(item as TItem);
- bool ICoreRepository.IsSelected(object item) => IsSelected(item as TItem);
- void ICoreRepository.SetSelectedItems(IEnumerable<object> items) => SetSelectedItems(items.OfType<TItem>());
-
- #endregion
-
- #region Searching
-
- public Func<TItem, bool> SearchPredicate { get; set; }
-
- public Func<List<TItem>,List<TItem>> SortPredicate { get; set; }
- public ICoreRepository Search(Func<TItem, bool> searchpredicate, Func<List<TItem>,List<TItem>> sortpredicate)
- {
- SortPredicate = sortpredicate;
- SearchPredicate = searchpredicate;
- Search();
- return this;
- }
-
- public ICoreRepository Search(Func<TItem, bool> searchpredicate)
- {
- SearchPredicate = searchpredicate;
- Search();
- return this;
- }
- public ICoreRepository Search()
- {
- var curselected = SelectedItems.ToArray();
- var items = AllItems == null
- ? new List<TItem>()
- : SearchPredicate != null
- ? new List<TItem>(AllItems.Where(SearchPredicate))
- : new List<TItem>(AllItems);
-
- if (SortPredicate != null)
- items = SortPredicate(items);
-
- SelectedItems.ReplaceRange(items.Where(x=>curselected.Contains(x)));
- OnPropertyChanged(nameof(SelectedItems));
-
- Items.ReplaceRange(items);
- OnPropertyChanged(nameof(Items));
- OnPropertyChanged(nameof(ItemCount));
- return this;
- }
-
- ICoreRepository ICoreRepository.Search(Func<object,bool> method)
- => Search((o) => method(o as TItem));
- #endregion
-
- protected virtual Expression<Func<TEntity, object>> ImageColumn => null;
-
- #region Loading
-
-
- private void DoBeforeLoad()
- {
- _query.Clear();
-
- _query.Add(
- EffectiveFilter(),
- GetColumns<TItem,TEntity>(),
- Sort
- );
-
- if (ImageColumn != null)
- {
- _query.Add(
- new Filter<Document>(x => x.ID).InQuery(EffectiveFilter(), ImageColumn),
- Columns.None<Document>().Add(x => x.ID)
- .Add(x => x.Data)
- );
- }
-
-
- }
-
- protected virtual void BeforeLoad(MultiQuery query)
- {
-
- }
-
- protected virtual void AfterLoad(MultiQuery query)
- {
- }
-
- protected void DoLoad()
- {
- try
- {
- var selected = AvailableFilters.FirstOrDefault(x => x.Selected)?.Name;
- if (!string.IsNullOrWhiteSpace(FilterTag))
- {
- var filters = new GlobalConfiguration<CoreFilterDefinitions>(FilterTag).Load()
- .Where(x => x.Visibility == CoreFilterDefinitionVisibility.DesktopAndMobile)
- .Select(x => new CoreRepositoryFilter() { Name = x.Name, Filter = x.Filter, Selected = string.Equals(x.Name,selected) })
- .ToList();
- if (filters.Any())
- filters.Insert(0, new CoreRepositoryFilter() { Name="All", Filter = "", Selected = !filters.Any(x => string.Equals(x.Name, selected)) });
- AvailableFilters.ReplaceRange(filters);
- Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(FiltersVisible)));
- }
-
- DoBeforeLoad();
- BeforeLoad(_query);
-
- Task.Run(() =>
- {
- _query.Query();
- DoAfterLoad();
- }).Wait();
-
- Search();
- AfterLoad(_query);
- LastUpdated = DateTime.Now;
- }
- catch (Exception e)
- {
- MobileLogging.Log(e,"CoreRepository");
- }
- }
-
- protected void DoAfterLoad()
- {
- _table = _query.Get<TEntity>();
- AllItems.ReplaceRange(_query.Get<TEntity>().Rows.Select(CreateItem<TItem>));
-
- if (ImageColumn != null)
- {
- Images.Clear();
- _query.Get<Document>().IntoDictionary<Document, Guid, byte[]>(Images, x => x.ID,
- r => r.Get<Document, byte[]>(x => x.Data));
- }
-
- }
-
- #endregion
-
- #region Persistent Storage
-
- protected void InitializeTables()
- {
- var defs = _query.Definitions();
- foreach (var def in defs)
- {
- var table = InitializeTable(def.Value);
- _query.Set(def.Key, table);
- }
- }
- protected CoreTable InitializeTable(IQueryDef def)
- {
- var table = new CoreTable();
- if (def.Columns != null)
- table.LoadColumns(def.Columns);
- else
- table.LoadColumns(def.Type);
- return table;
- }
- protected class QueryStorage : ISerializeBinary
- {
- private readonly Dictionary<String, CoreTable> _data = new Dictionary<string, CoreTable>();
- public CoreTable Get([NotNull] String key) => _data[key];
-
- public void Set([NotNull] String key, CoreTable table) => _data[key] = table;
- public bool Contains([NotNull] String key) => _data.ContainsKey(key);
-
- public void SerializeBinary(CoreBinaryWriter writer)
- {
- writer.Write(_data.Count);
- foreach (var key in _data.Keys)
- {
- writer.Write(key);
- _data[key].SerializeBinary(writer);
- }
- }
- public void DeserializeBinary(CoreBinaryReader reader)
- {
- int count = reader.ReadInt32();
- for (int i = 0; i < count; i++)
- {
- String key = reader.ReadString();
- CoreTable table = new CoreTable();
- table.DeserializeBinary(reader);
- _data[key] = table;
- }
- }
- }
-
- protected bool LoadFromStorage()
- {
-
- var filterFileName = FilterFileName();
- if (!string.IsNullOrWhiteSpace(filterFileName))
- {
- filterFileName = CacheFileName(filterFileName);
- if (File.Exists(filterFileName))
- {
- var json = File.ReadAllText(filterFileName);
- var filters = Serialization.Deserialize<ObservableCollection<CoreRepositoryFilter>>(json) ?? new ObservableCollection<CoreRepositoryFilter>();
- AvailableFilters.ReplaceRange(filters);
- Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(FiltersVisible)));
- }
- }
- var dataFileName = DataFileName();
- if (String.IsNullOrWhiteSpace(dataFileName))
- {
- InitializeTables();
- return true;
- }
-
- dataFileName = CacheFileName(dataFileName);
- if (File.Exists(dataFileName))
- {
- LastUpdated = File.GetLastWriteTime(dataFileName);
- using (var stream = new FileStream(dataFileName, FileMode.Open))
- {
- QueryStorage storage = Serialization.ReadBinary<QueryStorage>(stream,
- BinarySerializationSettings.Latest);
- var defs = _query.Definitions();
- foreach (var key in defs.Keys)
- {
- var table = storage.Contains(key.ToString())
- ? storage.Get(key.ToString())
- : InitializeTable(defs[key]);
- if (CheckColumns(table, _query.Definitions()[key].Columns))
- _query.Set(key, table);
- else
- return false;
- }
- }
- }
- else
- InitializeTables();
-
- return true;
- }
- private bool CheckColumns(CoreTable table, IColumns required)
- {
- foreach (var column in required.ColumnNames())
- {
- if (!table.Columns.Any(x => String.Equals(x.ColumnName, column)))
- return false;
- }
- return true;
- }
- protected void SaveToStorage()
- {
-
- var filterFileName = FilterFileName();
- if (!string.IsNullOrWhiteSpace(filterFileName))
- {
- filterFileName = CacheFileName(filterFileName);
- var json = Serialization.Serialize(AvailableFilters);
- File.WriteAllText(filterFileName,json);
- }
-
- var dataFileName = DataFileName();
- if (String.IsNullOrWhiteSpace(dataFileName))
- return;
-
- QueryStorage storage = new QueryStorage();
- var results = _query.Results();
- foreach (var key in results.Keys)
- storage.Set(key.ToString(),results[key]);
- var data = storage.WriteBinary(BinarySerializationSettings.Latest);
- try
- {
- var file = CacheFileName(dataFileName);
- File.WriteAllBytes(file,data);
- }
- catch (Exception e)
- {
- MobileLogging.Log(e);
- }
- }
-
- #endregion
- #region CRUD Operations
-
- public event CoreRepositoryItemCreatedEvent<TItem> ItemAdded;
-
- private T CreateItem<T>(CoreRow row)
- where T : Shell<TParent,TEntity>, new()
- {
- var result = new T() { Row = row, Parent = (TParent)this };
- result.PropertyChanged += (_, args) => DoPropertyChanged(result, args);
- return result;
- }
- public virtual TItem CreateItem()
- {
-
- CoreRow row = _table.NewRow();
-
- var entity = new TEntity();
- _table.FillRow(row,entity);
- var result = CreateItem<TItem>(row);
- ItemAdded?.Invoke(this, new CoreRepositoryItemCreatedArgs<TItem>(result));
- return result;
- }
-
- public virtual void CommitItem(TItem item)
- {
- _table.Rows.Add(item.Row);
- AllItems.Add(item);
- Search(null);
- NotifyChanged();
- }
-
- public virtual TItem AddItem()
- {
- var result = CreateItem();
- CommitItem(result);
- return result;
- }
-
- public virtual void DeleteItem(TItem item)
- {
- _table.Rows.Remove(item.Row);
- AllItems.Remove(item);
- Search(null);
- NotifyChanged();
- }
- public virtual void Save(string auditMessage)
- {
- new Client<TEntity>().Save(Items.Select(x=>x.Entity).Where(x=>x.IsChanged()),auditMessage);
- }
- object ICoreRepository.CreateItem() => this.CreateItem();
-
- void ICoreRepository.CommitItem(object item)
- {
- if (item is TItem titem)
- CommitItem(titem);
- }
-
- object ICoreRepository.AddItem() => this.AddItem();
-
- void ICoreRepository.DeleteItem(object item)
- {
- if (item is TItem titem)
- DeleteItem(titem);
- }
-
- #endregion
-
- #region IEnumerable Interface
- IEnumerator<TItem> IEnumerable<TItem>.GetEnumerator()
- {
- return Items.GetEnumerator();
- }
- public IEnumerator GetEnumerator()
- {
- return Items.GetEnumerator();
- }
-
- #endregion
- }
-
-
- }
|