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 Results; internal QueryMultipleResults(Dictionary results) { Results = results; } public CoreTable this[string name] => Results[name]; public CoreTable Get() => Results[typeof(T).Name]; /// /// Like , but calls on the table. /// /// /// public IEnumerable GetObjects() where T: BaseObject, new() => Results[typeof(T).Name].ToObjects(); /// /// Like , but calls on the table. /// /// /// public T[] GetArray() where T: BaseObject, new() => Results[typeof(T).Name].ToArray(); /// /// Like , but calls on the table. /// /// /// public List GetList() where T: BaseObject, new() => Results[typeof(T).Name].ToList(); 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, string auditNote); private static IClient CheckClient() { return ClientFactory.CreateClient(); } public static Dictionary QueryMultiple(Dictionary 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 CheckClient() where TEntity : Entity, IRemotable, IPersistent, new() { return ClientFactory.CreateClient(); } public static void EnsureColumns(TEntity entity, Columns columns) where TEntity : Entity, IRemotable, IPersistent, new() { var newColumns = Columns.None() .AddRange(columns.Where(x => !entity.HasColumn(x.Property))); if (newColumns.Count > 0) { var row = Query(new Filter(x => x.ID).IsEqualTo(entity.ID), newColumns).Rows.FirstOrDefault(); row?.FillObject(entity); } } public static void EnsureColumns(ICollection entities, Columns columns) where TEntity : Entity, IRemotable, IPersistent, new() { var newColumns = Columns.None() .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(x => x.ID).InList(entities.Select(x => x.ID).ToArray()), newColumns); foreach(var row in table.Rows) { var id = row.Get(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(Filter? filter = null, Columns? columns = null, SortOrder? orderby = null, CoreRange? range = null) where TEntity : Entity, IRemotable, IPersistent, new() { return new Client().Query(filter, columns, orderby, range); } public static void Query(Filter? filter, Columns? columns, SortOrder? orderby, CoreRange? range, Action callback) where TEntity : Entity, IRemotable, IPersistent, new() { new Client().Query(filter, columns, orderby, range, callback); } public static void Query(Filter? filter, Columns? columns, SortOrder? orderby, Action callback) where TEntity : Entity, IRemotable, IPersistent, new() { new Client().Query(filter, columns, orderby, null, callback); } public static void Save(TEntity entity, string auditNote) where TEntity : Entity, IRemotable, IPersistent, new() { new Client().Save(entity, auditNote); } public static void Save(IEnumerable entities, string auditNote) where TEntity : Entity, IRemotable, IPersistent, new() { new Client().Save(entities, auditNote); } public static void Save(TEntity entity, string auditNote, Action callback) where TEntity : Entity, IRemotable, IPersistent, new() { new Client().Save(entity, auditNote, callback); } public static void Save(IEnumerable entities, string auditNote, Action, Exception?> callback) where TEntity : Entity, IRemotable, IPersistent, new() { new Client().Save(entities, auditNote, callback); } public static void Delete(TEntity entity, string auditNote) where TEntity : Entity, IRemotable, IPersistent, new() { new Client().Delete(entity, auditNote); } public static void Delete(TEntity entity, string auditNote, Action callback) where TEntity : Entity, IRemotable, IPersistent, new() { new Client().Delete(entity, auditNote, callback); } public static void Delete(IEnumerable entities, string auditNote) where TEntity : Entity, IRemotable, IPersistent, new() { new Client().Delete(entities, auditNote); } public static void QueryMultiple( Action?, Exception?> callback, Dictionary 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 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 queries) => new QueryMultipleResults(QueryMultiple(queries.ToDictionary(x => x.Key, x => x as IQueryDef))); public static void QueryMultiple(Action callback, IEnumerable 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 : Client, IDisposable where TEntity : Entity, IPersistent, IRemotable, new() { private IClient _client; public Client() { _client = ClientFactory.CreateClient(); } public void Dispose() { } private void CheckSupported() { if (!ClientFactory.IsSupported()) throw new NotSupportedException(string.Format("{0} is not supported in this context", typeof(TEntity).EntityName())); } public CoreTable Query(Filter? filter = null, Columns? columns = null, SortOrder? orderby = null, CoreRange? range = null) { try { using var timer = new Profiler(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, columns as Columns, sortOrder as SortOrder, range); } public void Query(Filter? filter, Columns? columns, SortOrder? sort, CoreRange? range, Action? callback) { try { var timer = new Profiler(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? filter = null, SortOrder? sort = null, CoreRange? range = null) { try { using (var timer = new Profiler(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 filter, SortOrder sort, CoreRange? range, Action? callback) { try { var timer = new Profiler(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 entities, string auditNote) { try { Save(entities.Cast(), auditNote); } catch (RequestException e) { ClientFactory.RaiseRequestError(e); throw; } } public void Save(TEntity entity, string auditnote) { try { using (new Profiler(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 callback) { try { var timer = new Profiler(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 entities, string auditnote) { try { using var timer = new Profiler(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 entities, string auditnote, Action, Exception?> callback) { try { var timer = new Profiler(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(true)) { CheckSupported(); _client.Delete(entity, auditnote); } } catch (RequestException e) { ClientFactory.RaiseRequestError(e); throw; } } public void Delete(TEntity entity, string auditnote, Action callback) { try { var timer = new Profiler(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 entities, string auditnote) { try { using var timer = new Profiler(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 entities, string auditnote, Action, Exception?> callback) { try { var timer = new Profiler(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 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 { /// /// Load the properties of any s on this where the is not . /// 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. /// /// public static void LoadForeignProperties(this IEnumerable items, Columns 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>, HashSet>>(); 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>, HashSet>( linkType, new List>(), new HashSet()); 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(remaining, property)); } } } var queryDefs = new List(); foreach (var (prop, data) in newData) { if (data.Item2.Count != 0) { var ids = data.Item3.Select(prop.Getter()).Cast().ToArray(); queryDefs.Add(new KeyedQueryDef(prop.Name, data.Item1, Filter.Create(data.Item1, x => x.ID).InList(ids), Columns.None(data.Item1) .Add(data.Item2.Select(x => x.Item1)) .Add(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; } foreach (var entity in data.Item3) { var linkID = (Guid)prop.Getter()(entity); var row = table.Rows.FirstOrDefault(x => x.Get(x => x.ID) == linkID); if (row != null) { foreach (var (name, property) in data.Item2) { if (!entity.LoadedColumns.Contains(property.Name)) { property.Setter()(entity, row[name]); entity.LoadedColumns.Add(property.Name); } } } } } } } }