| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796 | using System;using System.Collections.Generic;using System.Linq;using System.Reflection;using System.Timers;using InABox.Core;namespace InABox.Clients{    public enum SerializerProtocol    {        Rest,        RPC    }    public class QueryMultipleResults    {        private readonly Dictionary<string, CoreTable> Results;        internal QueryMultipleResults(Dictionary<string, CoreTable> results)        {            Results = results;        }        public CoreTable this[string name] => Results[name];        public CoreTable Get<T>() => Results[typeof(T).Name];        /// <summary>        /// Like <see cref="Get{T}"/>, but calls <see cref="CoreTable.ToObjects{T}"/> on the table.        /// </summary>        /// <typeparam name="T"></typeparam>        /// <returns></returns>        public IEnumerable<T> GetObjects<T>()            where T: BaseObject, new()             => Results[typeof(T).Name].ToObjects<T>();        /// <summary>        /// Like <see cref="Get{T}"/>, but calls <see cref="CoreTable.ToArray{T}"/> on the table.        /// </summary>        /// <typeparam name="T"></typeparam>        /// <returns></returns>        public T[] GetArray<T>()            where T: BaseObject, new()             => Results[typeof(T).Name].ToArray<T>();        /// <summary>        /// Like <see cref="Get{T}"/>, but calls <see cref="CoreTable.ToList{T}"/> on the table.        /// </summary>        /// <typeparam name="T"></typeparam>        /// <returns></returns>        public List<T> GetList<T>()            where T: BaseObject, new()             => Results[typeof(T).Name].ToList<T>();        public CoreTable Get(string name) => Results[name];        public CoreTable GetOrDefault(string name) => Results.GetValueOrDefault(name);    }    public abstract class Client    {        public abstract CoreTable Query(IFilter? filter = null, IColumns? columns = null, ISortOrder? sortOrder = null, CoreRange? range = null);        public abstract void Save(Entity entity, string auditNote);        public abstract void Save(IEnumerable<Entity> entity, string auditNote);        private static IClient CheckClient()        {            return ClientFactory.CreateClient<User>();        }        public static Dictionary<string, CoreTable> QueryMultiple(Dictionary<string, IQueryDef> queries)        {            try            {                using var timer = new Profiler(false);                var result = CheckClient().QueryMultiple(queries);                timer.Log(result.Sum(x => x.Value.Rows.Count));                return result;            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        private static IClient<TEntity> CheckClient<TEntity>() where TEntity : Entity, IRemotable, new()        {            return ClientFactory.CreateClient<TEntity>();        }        public static void EnsureColumns<TEntity>(TEntity entity, Columns<TEntity> columns)            where TEntity : Entity, IRemotable, new()        {            var newColumns = Columns.None<TEntity>()                .AddRange(columns.Where(x => !entity.HasColumn(x.Property)));            if (newColumns.Count > 0)            {                var row = Query(new Filter<TEntity>(x => x.ID).IsEqualTo(entity.ID), newColumns).Rows.FirstOrDefault();                row?.FillObject(entity);            }        }        public static void EnsureColumns<TEntity>(ICollection<TEntity> entities, Columns<TEntity> columns)            where TEntity : Entity, IRemotable, new()        {            var newColumns = Columns.None<TEntity>()                .AddRange(columns.Where(x => entities.Any(entity => !entity.HasColumn(x.Property))));            if (newColumns.Count > 0)            {                newColumns.Add(x => x.ID);                var table = Query(new Filter<TEntity>(x => x.ID).InList(entities.Select(x => x.ID).ToArray()), newColumns);                foreach(var row in table.Rows)                {                    var id = row.Get<TEntity, Guid>(x => x.ID);                    var entity = entities.FirstOrDefault(x => x.ID == id);                    if(entity is null)                    {                        // Shouldn't happen, but just in case.                        continue;                    }                    row?.FillObject(entity);                }            }        }        public static CoreTable Query<TEntity>(Filter<TEntity>? filter = null, Columns<TEntity>? columns = null, SortOrder<TEntity>? orderby = null, CoreRange? range = null)            where TEntity : Entity, IRemotable, new()        {            return new Client<TEntity>().Query(filter, columns, orderby, range);        }        public static void Query<TEntity>(Filter<TEntity>? filter, Columns<TEntity>? columns, SortOrder<TEntity>? orderby, CoreRange? range, Action<CoreTable?, Exception?> callback)            where TEntity : Entity, IRemotable, new()        {            new Client<TEntity>().Query(filter, columns, orderby, range, callback);        }        public static void Query<TEntity>(Filter<TEntity>? filter, Columns<TEntity>? columns, SortOrder<TEntity>? orderby, Action<CoreTable?, Exception?> callback)            where TEntity : Entity, IRemotable, new()        {            new Client<TEntity>().Query(filter, columns, orderby, null, callback);        }        public static void Save<TEntity>(TEntity entity, string auditNote)            where TEntity : Entity, IRemotable, new()        {            new Client<TEntity>().Save(entity, auditNote);        }        public static void Save<TEntity>(IEnumerable<TEntity> entities, string auditNote)            where TEntity : Entity, IRemotable, new()        {            new Client<TEntity>().Save(entities, auditNote);        }        public static void Save<TEntity>(TEntity entity, string auditNote, Action<TEntity, Exception?> callback)            where TEntity : Entity, IRemotable, new()        {            new Client<TEntity>().Save(entity, auditNote, callback);        }        public static void Save<TEntity>(IEnumerable<TEntity> entities, string auditNote, Action<IEnumerable<TEntity>, Exception?> callback)            where TEntity : Entity, IRemotable, new()        {            new Client<TEntity>().Save(entities, auditNote, callback);        }        public static void Delete<TEntity>(TEntity entity, string auditNote)            where TEntity : Entity, IRemotable, new()        {            new Client<TEntity>().Delete(entity, auditNote);        }        public static void Delete<TEntity>(TEntity entity, string auditNote, Action<TEntity, Exception?> callback)            where TEntity : Entity, IRemotable, new()        {            new Client<TEntity>().Delete(entity, auditNote, callback);        }        public static void Delete<TEntity>(IEnumerable<TEntity> entities, string auditNote)            where TEntity : Entity, IRemotable, new()        {            new Client<TEntity>().Delete(entities, auditNote);        }        public static void QueryMultiple(            Action<Dictionary<string, CoreTable>?, Exception?> callback,            Dictionary<string, IQueryDef> queries)        {            try            {                using var timer = new Profiler(false);                CheckClient().QueryMultiple((result, e) =>                {                    timer.Dispose(result != null ? result.Sum(x => x.Value.Rows.Count) : -1);                    callback?.Invoke(result, e);                }, queries);            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        public static QueryMultipleResults QueryMultiple(params IKeyedQueryDef[] queries) =>            new QueryMultipleResults(QueryMultiple(queries.ToDictionary(x => x.Key, x => x as IQueryDef)));        public static void QueryMultiple(Action<QueryMultipleResults?, Exception?> callback, params IKeyedQueryDef[] queries) =>            QueryMultiple((results, e) =>            {                if (results != null)                {                    callback?.Invoke(new QueryMultipleResults(results), e);                }                else                {                    callback?.Invoke(null, e);                }            }, queries.ToDictionary(x => x.Key, x => x as IQueryDef));        public static QueryMultipleResults QueryMultiple(IEnumerable<IKeyedQueryDef> queries) =>            new QueryMultipleResults(QueryMultiple(queries.ToDictionary(x => x.Key, x => x as IQueryDef)));        public static void QueryMultiple(Action<QueryMultipleResults?, Exception?> callback, IEnumerable<IKeyedQueryDef> queries) =>            QueryMultiple((results, e) =>            {                if(results != null)                {                    callback?.Invoke(new QueryMultipleResults(results), e);                }                else                {                    callback?.Invoke(null, e);                }            }, queries.ToDictionary(x => x.Key, x => x as IQueryDef));        public static IValidationData Validate(Guid session)        {            try            {                using (new Profiler(true))                    return CheckClient().Validate(session);            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        public static IValidationData Validate(string pin, Guid session = default)        {            try            {                using (new Profiler(true))                    return CheckClient().Validate(pin, session);            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        public static IValidationData Validate(string userid, string password, Guid session = default)        {            try            {                using (new Profiler(true))                    return CheckClient().Validate(userid, password, session);            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        public static bool Check2FA(string code, Guid? session = null)        {            try            {                using (new Profiler(true))                    return CheckClient().Check2FA(code, session);            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        public static bool Ping()        {            try            {                return CheckClient().Ping();            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        public static DatabaseInfo? Info()        {            try            {                using (new Profiler(true))                    return CheckClient().Info();            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        public static string Version()        {            try            {                using (new Profiler(true))                    return CheckClient().Version();            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        public static string ReleaseNotes()        {            try            {                using (new Profiler(true))                    return CheckClient().ReleaseNotes();            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        public static byte[]? Installer()        {            try            {                using (new Profiler(true))                    return CheckClient().Installer();            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        public static Client Create(Type TEntity) =>             (Activator.CreateInstance(typeof(Client<>).MakeGenericType(TEntity)) as Client)!;    }    public class Client<TEntity> : Client, IDisposable where TEntity : Entity, IRemotable, new()    {        #region IQueryProvider        private class ClientQueryProvider : IQueryProvider<TEntity>        {            public CoreTable Query(Filter<TEntity>? filter = null, Columns<TEntity>? columns = null, SortOrder<TEntity>? sort = null, CoreRange? range = null)            {                return Client.Query(filter, columns, sort, range);            }        }        public static IQueryProvider<TEntity> Provider { get; private set; } = new ClientQueryProvider();        #endregion        private IClient<TEntity> _client;        public Client()        {            _client = ClientFactory.CreateClient<TEntity>();        }        public void Dispose()        {        }        private void CheckSupported()        {            if (!ClientFactory.IsSupported<TEntity>())                throw new NotSupportedException(string.Format("{0} is not supported in this context", typeof(TEntity).EntityName()));        }        public CoreTable Query(Filter<TEntity>? filter = null, Columns<TEntity>? columns = null, SortOrder<TEntity>? orderby = null, CoreRange? range = null)        {            try            {                using var timer = new Profiler<TEntity>(false);                CheckSupported();                var result = _client.Query(filter, columns, orderby, range);                timer.Log(result.Rows.Count);                return result;            }            catch(RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        public override CoreTable Query(IFilter? filter = null, IColumns? columns = null, ISortOrder? sortOrder = null, CoreRange? range = null)        {            return Query(filter as Filter<TEntity>, columns as Columns<TEntity>, sortOrder as SortOrder<TEntity>, range);        }        public void Query(Filter<TEntity>? filter, Columns<TEntity>? columns, SortOrder<TEntity>? sort, CoreRange? range, Action<CoreTable?, Exception?>? callback)        {            try            {                var timer = new Profiler<TEntity>(false);                CheckSupported();                _client.Query(filter, columns, sort, range, (c, e) =>                {                    timer.Log(c != null ? c.Rows.Count : -1);                    callback?.Invoke(c, e);                });            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        public TEntity[] Load(Filter<TEntity>? filter = null, SortOrder<TEntity>? sort = null, CoreRange? range = null)        {            try            {                using (var timer = new Profiler<TEntity>(false))                {                    CheckSupported();                    var result = _client.Load(filter, sort, range);                    foreach (var entity in result)                        entity.CommitChanges();                    timer.Log(result.Length);                    return result;                }            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        public void Load(Filter<TEntity> filter, SortOrder<TEntity> sort, CoreRange? range, Action<TEntity[]?, Exception?>? callback)        {            try            {                var timer = new Profiler<TEntity>(false);                CheckSupported();                _client.Load(filter, sort, range,(i, e) =>                {                    timer.Dispose(i != null ? i.Length : -1);                    callback?.Invoke(i, e);                });            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        public override void Save(Entity entity, string auditNote)        {            try            {                Save((entity as TEntity)!, auditNote);            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        public override void Save(IEnumerable<Entity> entities, string auditNote)        {            try            {                Save(entities.Cast<TEntity>(), auditNote);            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        public void Save(TEntity entity, string auditnote)        {            try            {                using (new Profiler<TEntity>(true))                {                    CheckSupported();                    entity.LastUpdate = DateTime.Now;                    entity.LastUpdateBy = ClientFactory.UserID;                    _client.Save(entity, auditnote);                    entity.CommitChanges();                }            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        public void Save(TEntity entity, string auditnote, Action<TEntity, Exception?> callback)        {            try            {                var timer = new Profiler<TEntity>(false);                CheckSupported();                _client.Save(entity, auditnote, (i, c) =>                {                    timer.Dispose();                    callback?.Invoke(i, c);                });            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        public void Save(IEnumerable<TEntity> entities, string auditnote)        {            try            {                using var timer = new Profiler<TEntity>(false);                CheckSupported();                var items = entities.AsArray();                if (items.Any())                    _client.Save(items, auditnote);                timer.Log(items.Length);            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        public void Save(IEnumerable<TEntity> entities, string auditnote, Action<IEnumerable<TEntity>, Exception?> callback)        {            try            {                var timer = new Profiler<TEntity>(false);                CheckSupported();                var items = entities.AsArray();                if (items.Any())                {                    _client.Save(items, auditnote, (i, e) =>                    {                        timer.Dispose(i.Count());                        callback?.Invoke(i, e);                    });                }                else                {                    timer.Dispose(0);                    callback?.Invoke(items, null);                }            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        public void Delete(TEntity entity, string auditnote)        {            try            {                using (new Profiler<TEntity>(true))                {                    CheckSupported();                    _client.Delete(entity, auditnote);                }            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        public void Delete(TEntity entity, string auditnote, Action<TEntity, Exception?> callback)        {            try            {                var timer = new Profiler<TEntity>(true);                CheckSupported();                _client.Delete(entity, auditnote, (i, e) =>                {                    timer.Dispose();                    callback?.Invoke(i, e);                });            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        public void Delete(IEnumerable<TEntity> entities, string auditnote)        {            try            {                using var timer = new Profiler<TEntity>(false);                CheckSupported();                var items = entities.AsArray();                _client.Delete(items, auditnote);                timer.Log(items.Length);            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        public void Delete(IEnumerable<TEntity> entities, string auditnote, Action<IList<TEntity>, Exception?> callback)        {            try            {                var timer = new Profiler<TEntity>(false);                CheckSupported();                var items = entities.AsArray();                _client.Delete(items, auditnote, (i, e) =>                {                    timer.Dispose(i.Count);                    callback?.Invoke(i, e);                });            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        public IEnumerable<string> SupportedTypes()        {            try            {                using (new Profiler(true))                    return _client.SupportedTypes();            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }        public new DatabaseInfo Info()        {            try            {                using (new Profiler(true))                    return _client.Info();            }            catch (RequestException e)            {                ClientFactory.RaiseRequestError(e);                throw;            }        }    }    public static class ClientExtensions    {        /// <summary>        /// Load the properties of any <see cref="EntityLink{T}"/>s on this <typeparamref name="T"/> where the <see cref="IEntityLink.ID"/> is not <see cref="Guid.Empty"/>.        /// This allows us to populate columns of transient objects, as long as they are linked by the ID. What this actually then does is query each        /// linked table with the required columns.        /// </summary>        /// <param name="columns"></param>        public static void LoadForeignProperties<T>(this IEnumerable<T> items, Columns<T> columns)            where T : BaseObject, new()        {            // Lists of properties that we need, arranged by the entity link property which is their parent.            // LinkIDProperty : (Type, Properties: [(columnName, property)], Objects)            var newData = new Dictionary<IProperty, Tuple<Type, List<Tuple<string, IProperty>>, HashSet<T>>>();            foreach (var column in columns)            {                var property = DatabaseSchema.Property(typeof(T), column.Property);                if (property?.GetOuterParent(x => x.IsEntityLink) is IProperty linkProperty)                {                    var remaining = column.Property[(linkProperty.Name.Length + 1)..];                    if (remaining.Equals(nameof(IEntityLink.ID)))                    {                        // This guy isn't foreign, so we don't pull him.                        continue;                    }                    var idProperty = DatabaseSchema.Property(typeof(T), linkProperty.Name + "." + nameof(IEntityLink.ID))!;                    var linkType = linkProperty.PropertyType.GetInterfaceDefinition(typeof(IEntityLink<>))!.GenericTypeArguments[0];                    if (!newData.TryGetValue(idProperty, out var data))                    {                        data = new Tuple<Type, List<Tuple<string, IProperty>>, HashSet<T>>(                            linkType,                            new List<Tuple<string, IProperty>>(),                            new HashSet<T>());                        newData.Add(idProperty, data);                    }                    var any = false;                    foreach (var item in items)                    {                        if (!item.LoadedColumns.Contains(column.Property))                        {                            var linkID = (Guid)idProperty.Getter()(item);                            if (linkID != Guid.Empty)                            {                                any = true;                                data.Item3.Add(item);                            }                        }                    }                    if (any)                    {                        data.Item2.Add(new Tuple<string, IProperty>(remaining, property));                    }                }            }            var queryDefs = new List<IKeyedQueryDef>();            foreach (var (prop, data) in newData)            {                if (data.Item2.Count != 0)                {                    var ids = data.Item3.Select(prop.Getter()).Cast<Guid>().ToArray();                    queryDefs.Add(new KeyedQueryDef(prop.Name, data.Item1,                        Filter.Create<Entity>(data.Item1, x => x.ID).InList(ids),                        Columns.None(data.Item1)                            .Add(data.Item2.Select(x => x.Item1))                            .Add<Entity>(x => x.ID)));                }            }            var results = Client.QueryMultiple(queryDefs);            foreach(var (prop, data) in newData)            {                var table = results.GetOrDefault(prop.Name);                if(table is null)                {                    continue;                }                var keyCol = table.GetColumnIndex<Entity, Guid>(x => x.ID);                var dict = table.Rows.ToDictionary(x => x.Get<Guid>(keyCol));                foreach (var entity in data.Item3)                {                    var linkID = (Guid)prop.Getter()(entity);                    if (dict.TryGetValue(linkID, out var row))                    {                        foreach (var (name, property) in data.Item2)                        {                            if (!entity.LoadedColumns.Contains(property.Name))                            {                                property.Setter()(entity, row[name]);                                entity.LoadedColumns.Add(property.Name);                            }                        }                    }                }            }        }    }}
 |