diff --git a/Esiur.Stores.EntityCore/Esiur.Stores.EntityCore.csproj b/Esiur.Stores.EntityCore/Esiur.Stores.EntityCore.csproj index 874d12a..1567d44 100644 --- a/Esiur.Stores.EntityCore/Esiur.Stores.EntityCore.csproj +++ b/Esiur.Stores.EntityCore/Esiur.Stores.EntityCore.csproj @@ -9,7 +9,7 @@ Esiur Entity Framework Extension true Esiur.Stores.EntityCore - 1.1.0 + 1.2.1 diff --git a/Esiur.Stores.EntityCore/EsiurProxyRewrite.cs b/Esiur.Stores.EntityCore/EsiurProxyRewrite.cs index 8f04468..5feb859 100644 --- a/Esiur.Stores.EntityCore/EsiurProxyRewrite.cs +++ b/Esiur.Stores.EntityCore/EsiurProxyRewrite.cs @@ -37,6 +37,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Esiur.Data; namespace Esiur.Stores.EntityCore { @@ -57,6 +58,7 @@ namespace Esiur.Stores.EntityCore public static object CreateInstance( IDbContextOptions dbContextOptions, IEntityType entityType, + //object id object[] properties // ILazyLoader loader, @@ -64,7 +66,6 @@ namespace Esiur.Stores.EntityCore //DbContext context, ) { - ///var id = constructorArguments.Last(); var id = properties.First(); var options = dbContextOptions.FindExtension(); @@ -75,15 +76,23 @@ namespace Esiur.Stores.EntityCore if (cache != null) return cache; - // check if the object exists - var obj = Warehouse.New(entityType.ClrType).Wait() as IResource;//, "", options.Store, null, manager); - //obj._PrimaryId = id; - options.Store.TypesByType[entityType.ClrType].PrimaryKey.SetValue(obj, id); - Warehouse.Put(id.ToString(), obj, options.Store, null, null, 0, manager).Wait(); + if (Codec.ImplementsInterface(entityType.ClrType, typeof(IResource))) + { + // check if the object exists + var obj = Warehouse.New(entityType.ClrType).Wait() as IResource; + options.Store.TypesByType[entityType.ClrType].PrimaryKey.SetValue(obj, id); + Warehouse.Put(id.ToString(), obj, options.Store, null, null, 0, manager).Wait(); + return obj; - // obj.Instance.IntVal = id;//.Variables.Add("eid", id); + } + else + { + // record + var obj = Activator.CreateInstance(entityType.ClrType); + options.Store.TypesByType[entityType.ClrType].PrimaryKey.SetValue(obj, id); - return obj; + return obj; + } } @@ -98,6 +107,10 @@ namespace Esiur.Stores.EntityCore { foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) { + + if (!Codec.ImplementsInterface(entityType.ClrType, typeof(IResource))) + continue; + var proxyType = ResourceProxy.GetProxy(entityType.ClrType); // var ann = entityType.GetAnnotation(CoreAnnotationNames.ConstructorBinding); @@ -114,8 +127,14 @@ namespace Esiur.Stores.EntityCore try - { + + var key = entityType.FindPrimaryKey().Properties.First(); + if (key == null) + continue; + + //var keys = entityType.FindPrimaryKey().Properties.Select(x=>new PropertyParameterBinding(x)); + entityType.SetAnnotation( #pragma warning disable EF1001 // Internal EF Core API usage. CoreAnnotationNames.ConstructorBinding, @@ -126,12 +145,14 @@ namespace Esiur.Stores.EntityCore { new DependencyInjectionParameterBinding(typeof(IDbContextOptions), typeof(IDbContextOptions)), new EntityTypeParameterBinding(), + //new PropertyParameterBinding(key) // constructor arguments //new ObjectArrayParameterBinding(binding.ParameterBindings), //new ContextParameterBinding(typeof(DbContext)), - new ObjectArrayParameterBinding(new ParameterBinding[]{ - new PropertyParameterBinding(entityType.FindPrimaryKey().Properties.FirstOrDefault()) - }) + //new ObjectArrayParameterBinding(entityType.FindPrimaryKey().Properties.Select(x=>new PropertyParameterBinding(x)).ToArray()) + new ObjectArrayParameterBinding(new ParameterBinding[]{ + new PropertyParameterBinding(key) }) + //}) // new Microsoft.EntityFrameworkCore.Metadata.ObjectArrayParameterBinding(), //new ObjectArrayParameterBinding() @@ -141,6 +162,7 @@ namespace Esiur.Stores.EntityCore } catch { + } } diff --git a/Esiur/Data/Codec.cs b/Esiur/Data/Codec.cs index 607227c..a91419d 100644 --- a/Esiur/Data/Codec.cs +++ b/Esiur/Data/Codec.cs @@ -81,6 +81,29 @@ namespace Esiur.Data return types; } + /// + /// Compare two records + /// + /// Initial record to compare with + /// Next record to compare with the initial + /// DistributedConnection is required in case a structure holds items at the other end + public static RecordComparisonResult Compare(IRecord initial, IRecord next) + { + if (next == null) + return RecordComparisonResult.Null; + + if (initial == null) + return RecordComparisonResult.Record; + + if (next == initial) + return RecordComparisonResult.Same; + + if (next.GetType() == initial.GetType()) + return RecordComparisonResult.RecordSameType; + + return RecordComparisonResult.Record; + } + /// /// Compare two structures /// @@ -231,6 +254,184 @@ namespace Esiur.Data return reply; } + + public static AsyncBag ParseRecordArray(byte[] data, uint offset, uint length, DistributedConnection connection) + { + + var reply = new AsyncBag(); + if (length == 0) + { + reply.Seal(); + return reply; + } + + var end = offset + length; + + var result = (RecordComparisonResult)data[offset++]; + + AsyncReply previous = null; + Guid? classId = null; + + if (result == RecordComparisonResult.Null) + previous = new AsyncReply(null); + else if (result == RecordComparisonResult.Record) + { + uint cs = data.GetUInt32(offset); + uint recordLength = cs - 16; + offset += 4; + classId = data.GetGuid(offset); + offset += 16; + previous = ParseRecord(data, offset, recordLength, connection, classId); + offset += recordLength; + } + + reply.Add(previous); + + + while (offset < end) + { + result = (RecordComparisonResult)data[offset++]; + + if (result == RecordComparisonResult.Null) + previous = new AsyncReply(null); + else if (result == RecordComparisonResult.Record) + { + uint cs = data.GetUInt32(offset); + uint recordLength = cs - 16; + offset += 4; + classId = data.GetGuid(offset); + offset += 16; + previous = ParseRecord(data, offset, recordLength, connection, classId); + offset += recordLength; + } + else if (result == RecordComparisonResult.RecordSameType) + { + uint cs = data.GetUInt32(offset); + offset += 4; + previous = ParseRecord(data, offset, cs, connection, classId); + offset += cs; + } + else if (result == RecordComparisonResult.Same) + { + // do nothing + } + + reply.Add(previous); + } + + reply.Seal(); + return reply; + } + + public static AsyncReply ParseRecord(byte[] data, uint offset, uint length, DistributedConnection connection, Guid? classId = null) + { + var reply = new AsyncReply(); + + if (classId == null) + { + classId = data.GetGuid(offset); + + offset += 16; + length -= 16; + } + + var template = Warehouse.GetTemplateByClassId((Guid)classId); + + if (template != null) + { + ParseVarArray(data, offset, length, connection).Then(ar => + { + if (template.ResourceType != null) + { + var record = Activator.CreateInstance(template.ResourceType) as IRecord; + for (var i = 0; i < template.Properties.Length; i++) + template.Properties[i].PropertyInfo.SetValue(record, ar[i]); + + reply.Trigger(record); + } + else + { + var record = new Record(); + + for (var i = 0; i < template.Properties.Length; i++) + record.Add(template.Properties[i].Name, ar[i]); + + reply.Trigger(record); + } + }); + } + else + { + connection.GetTemplate((Guid)classId).Then(tmp => { + ParseVarArray(data, offset, length, connection).Then(ar => + { + var record = new Record(); + + for (var i = 0; i < tmp.Properties.Length; i++) + record.Add(tmp.Properties[i].Name, ar[i]); + + reply.Trigger(record); + }); + }).Error(x=>reply.TriggerError(x)); + } + + return reply; + } + + public static byte[] ComposeRecord(IRecord record, DistributedConnection connection, bool includeClassId = true, bool prependLength = false) + { + var rt = new BinaryList(); + + var template = Warehouse.GetTemplateByType(record.GetType()); + + if (includeClassId) + rt.AddGuid(template.ClassId); + + foreach (var pt in template.Properties) + { + var value = pt.PropertyInfo.GetValue(record, null); + rt.AddUInt8Array(Compose(value, connection)); + } + + if (prependLength) + rt.InsertInt32(0, rt.Length); + + return rt.ToArray(); + } + + public static byte[] ComposeRecordArray(IRecord[] records, DistributedConnection connection, bool prependLength = false) + { + + if (records == null || records?.Length == 0) + return prependLength ? new byte[] { 0, 0, 0, 0 } : new byte[0]; + + var rt = new BinaryList(); + var comparsion = Compare(null, records[0]); + + rt.AddUInt8((byte)comparsion); + + + if (comparsion == RecordComparisonResult.Record) + rt.AddUInt8Array(ComposeRecord(records[0], connection, true, true)); + + for (var i = 1; i < records.Length; i++) + { + comparsion = Compare(records[i - 1], records[i]); + rt.AddUInt8((byte)comparsion); + + if (comparsion == RecordComparisonResult.Record) + rt.AddUInt8Array(ComposeRecord(records[i], connection, true, true)); + else if (comparsion == RecordComparisonResult.RecordSameType) + rt.AddUInt8Array(ComposeRecord(records[i], connection, false, true)); + } + + if (prependLength) + rt.InsertInt32(0, rt.Length); + + + return rt.ToArray(); + } + /// /// Compose a structure into an array of bytes /// @@ -474,6 +675,9 @@ namespace Esiur.Data case DataType.Structure: return ParseStructureArray(data, offset, contentLength, connection); + + case DataType.Record: + return ParseRecordArray(data, offset, contentLength, connection); } } else @@ -536,6 +740,9 @@ namespace Esiur.Data case DataType.Structure: return ParseStructure(data, offset, contentLength, connection); + + case DataType.Record: + return ParseRecord(data, offset, contentLength, connection); } } @@ -572,22 +779,6 @@ namespace Esiur.Data return connection.Fetch(iid);// Warehouse.Get(iid); } - public enum ResourceComparisonResult - { - Null, - Distributed, - Local, - Same - } - - public enum StructureComparisonResult : byte - { - Null, - Structure, - StructureSameKeys, - StructureSameTypes, - Same - } /// /// Check if a resource is local to a given connection. @@ -648,7 +839,6 @@ namespace Esiur.Data /// DistributedConnection is required to check locality. /// If True, prepend the length of the output at the beginning. /// Array of bytes in the network byte order. - public static byte[] ComposeResourceArray(IResource[] resources, DistributedConnection connection, bool prependLength = false) { if (resources == null || resources?.Length == 0) @@ -1041,6 +1231,10 @@ namespace Esiur.Data rt.AddUInt8Array(ComposeVarArray((Array)value, connection, true)); break; + case DataType.Record: + rt.AddUInt8Array(ComposeRecord((IRecord)value, connection, true, true)); + break; + case DataType.ResourceArray: if (value is IResource[]) rt.AddUInt8Array(ComposeResourceArray((IResource[])value, connection, true)); @@ -1052,6 +1246,10 @@ namespace Esiur.Data rt.AddUInt8Array(ComposeStructureArray((Structure[])value, connection, true)); break; + case DataType.RecordArray: + rt.AddUInt8Array(ComposeRecordArray((IRecord[])value, connection, true)); + break; + default: rt.Add(type, value); if (type.IsArray()) @@ -1240,7 +1438,7 @@ namespace Esiur.Data DataType type; - + if (t == typeof(bool)) type = DataType.Bool; else if (t == typeof(char)) @@ -1284,6 +1482,8 @@ namespace Esiur.Data return (IsLocalResource((IResource)value, connection) ? DataType.Resource : DataType.DistributedResource, value); } } + else if (ImplementsInterface(t, typeof(IRecord))) + type = DataType.Record; else type = DataType.Void; diff --git a/Esiur/Data/DataType.cs b/Esiur/Data/DataType.cs index 88a5f7a..e3dc564 100644 --- a/Esiur/Data/DataType.cs +++ b/Esiur/Data/DataType.cs @@ -53,6 +53,7 @@ namespace Esiur.Data ResourceLink, String, Structure, + Record, //Stream, //Array = 0x80, VarArray = 0x80, @@ -75,6 +76,7 @@ namespace Esiur.Data ResourceLinkArray, StringArray, StructureArray, + RecordArray, NotModified = 0x7f, Unspecified = 0xff, } @@ -113,7 +115,5 @@ namespace Esiur.Data return -1; } } - - } } diff --git a/Esiur/Data/IRecord.cs b/Esiur/Data/IRecord.cs new file mode 100644 index 0000000..dfba44a --- /dev/null +++ b/Esiur/Data/IRecord.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Esiur.Data +{ + public interface IRecord + { + + } +} diff --git a/Esiur/Data/Record.cs b/Esiur/Data/Record.cs new file mode 100644 index 0000000..aec3495 --- /dev/null +++ b/Esiur/Data/Record.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Esiur.Data +{ + public class Record: KeyList, IRecord + { + + } +} diff --git a/Esiur/Data/RecordComparisonResult.cs b/Esiur/Data/RecordComparisonResult.cs new file mode 100644 index 0000000..7318c33 --- /dev/null +++ b/Esiur/Data/RecordComparisonResult.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Esiur.Data +{ + public enum RecordComparisonResult : byte + { + Null, + Record, + RecordSameType, + Same + } +} diff --git a/Esiur/Data/ResourceComparisonResult.cs b/Esiur/Data/ResourceComparisonResult.cs new file mode 100644 index 0000000..b6b7a12 --- /dev/null +++ b/Esiur/Data/ResourceComparisonResult.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Esiur.Data +{ + public enum ResourceComparisonResult + { + Null, + Distributed, + Local, + Same + } +} diff --git a/Esiur/Data/StructureComparisonResult.cs b/Esiur/Data/StructureComparisonResult.cs new file mode 100644 index 0000000..c76d973 --- /dev/null +++ b/Esiur/Data/StructureComparisonResult.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Esiur.Data +{ + public enum StructureComparisonResult : byte + { + Null, + Structure, + StructureSameKeys, + StructureSameTypes, + Same + } +} diff --git a/Esiur/Esiur.csproj b/Esiur/Esiur.csproj index e7afce7..9ae0cc4 100644 --- a/Esiur/Esiur.csproj +++ b/Esiur/Esiur.csproj @@ -7,7 +7,7 @@ https://github.com/Esiur/Esiur-dotnet/blob/master/LICENSE http://www.esiur.com true - 1.8.1 + 1.8.2.18 https://github.com/esiur/esiur-dotnet Ahmed Kh. Zamil 1.8.1.0 @@ -41,6 +41,7 @@ + @@ -53,6 +54,7 @@ + @@ -79,6 +81,10 @@ + + + + \ No newline at end of file diff --git a/Esiur/Net/IIP/DistributedConnection.cs b/Esiur/Net/IIP/DistributedConnection.cs index 9bcca8f..95e8102 100644 --- a/Esiur/Net/IIP/DistributedConnection.cs +++ b/Esiur/Net/IIP/DistributedConnection.cs @@ -1050,7 +1050,7 @@ namespace Esiur.Net.IIP var host = Instance.Name.Split(':'); var address = host[0]; - var port = ushort.Parse(host[1]); + var port = host.Length > 1 ? ushort.Parse(host[1]) : (ushort) 10518; // assign domain from hostname if not provided var domain = Domain != null ? Domain : address; diff --git a/Esiur/Net/IIP/DistributedConnectionProtocol.cs b/Esiur/Net/IIP/DistributedConnectionProtocol.cs index a02b3fa..d11d3fe 100644 --- a/Esiur/Net/IIP/DistributedConnectionProtocol.cs +++ b/Esiur/Net/IIP/DistributedConnectionProtocol.cs @@ -1079,7 +1079,6 @@ namespace Esiur.Net.IIP void IIPRequestLinkTemplates(uint callback, string resourceLink) { - Console.WriteLine("IIPRequestLinkTemplates " + DateTime.UtcNow); Action queryCallback = (r) => { if (r == null) @@ -1123,7 +1122,7 @@ namespace Esiur.Net.IIP void IIPRequestTemplateFromClassName(uint callback, string className) { - Warehouse.GetTemplate(className).Then((t) => + Warehouse.GetTemplateByClassName(className).Then((t) => { if (t != null) SendReply(IIPPacket.IIPPacketAction.TemplateFromClassName, callback) @@ -1140,7 +1139,7 @@ namespace Esiur.Net.IIP void IIPRequestTemplateFromClassId(uint callback, Guid classId) { - var t = Warehouse.GetTemplate(classId); + var t = Warehouse.GetTemplateByClassId(classId); if (t != null) SendReply(IIPPacket.IIPPacketAction.TemplateFromClassId, callback) @@ -2209,7 +2208,7 @@ namespace Esiur.Net.IIP if (resource == null) { - var template = Warehouse.GetTemplate((Guid)rt[0]); + var template = Warehouse.GetTemplateByClassId((Guid)rt[0], true); if (template?.ResourceType != null) dr = Activator.CreateInstance(template.ResourceType, this, id, (ulong)rt[1], (string)rt[2]) as DistributedResource; else diff --git a/Esiur/Net/IIP/DistributedServer.cs b/Esiur/Net/IIP/DistributedServer.cs index 81006cf..6f4ef15 100644 --- a/Esiur/Net/IIP/DistributedServer.cs +++ b/Esiur/Net/IIP/DistributedServer.cs @@ -65,7 +65,7 @@ namespace Esiur.Net.IIP { get; set; - } + } = 10518; [Attribute] diff --git a/Esiur/Net/NetworkConnection.cs b/Esiur/Net/NetworkConnection.cs index b2f9382..4098f54 100644 --- a/Esiur/Net/NetworkConnection.cs +++ b/Esiur/Net/NetworkConnection.cs @@ -69,7 +69,7 @@ namespace Esiur.Net //sock.OnClose -= Socket_OnClose; //sock.OnConnect -= Socket_OnConnect; //sock.OnReceive -= Socket_OnReceive; - sock.Destroy(); + sock?.Destroy(); //Receiver = null; Close(); sock = null; diff --git a/Esiur/Proxy/ResourceGenerator.cs b/Esiur/Proxy/ResourceGenerator.cs index d297e9a..c75bf0b 100644 --- a/Esiur/Proxy/ResourceGenerator.cs +++ b/Esiur/Proxy/ResourceGenerator.cs @@ -21,10 +21,9 @@ namespace Esiur.Proxy { - private static Regex urlRegex = new Regex(@"^(?:([\S]*)://([^/]*)/?)"); private KeyList cache = new(); - // private List inProgress = new(); + // private List inProgress = new(); public void Initialize(GeneratorInitializationContext context) { @@ -33,148 +32,52 @@ namespace Esiur.Proxy context.RegisterForSyntaxNotifications(() => new ResourceGeneratorReceiver()); } - string GetTypeName(TemplateDataType templateDataType, ResourceTemplate[] templates) - { - - if (templateDataType.Type == DataType.Resource) - return templates.First(x => x.ClassId == templateDataType.TypeGuid).ClassName; - else if (templateDataType.Type == DataType.ResourceArray) - return templates.First(x => x.ClassId == templateDataType.TypeGuid).ClassName + "[]"; - - var name = templateDataType.Type switch - { - DataType.Bool => "bool", - DataType.BoolArray => "bool[]", - DataType.Char => "char", - DataType.CharArray => "char[]", - DataType.DateTime => "DateTime", - DataType.DateTimeArray => "DateTime[]", - DataType.Decimal => "decimal", - DataType.DecimalArray => "decimal[]", - DataType.Float32 => "float", - DataType.Float32Array => "float[]", - DataType.Float64 => "double", - DataType.Float64Array => "double[]", - DataType.Int16 => "short", - DataType.Int16Array => "short[]", - DataType.Int32 => "int", - DataType.Int32Array => "int[]", - DataType.Int64 => "long", - DataType.Int64Array => "long[]", - DataType.Int8 => "sbyte", - DataType.Int8Array => "sbyte[]", - DataType.String => "string", - DataType.StringArray => "string[]", - DataType.Structure => "Structure", - DataType.StructureArray => "Structure[]", - DataType.UInt16 => "ushort", - DataType.UInt16Array => "ushort[]", - DataType.UInt32 => "uint", - DataType.UInt32Array => "uint[]", - DataType.UInt64 => "ulong", - DataType.UInt64Array => "ulong[]", - DataType.UInt8 => "byte", - DataType.UInt8Array => "byte[]", - DataType.VarArray => "object[]", - DataType.Void => "object", - _ => "object" - }; - - return name; - } - + void ReportError(GeneratorExecutionContext context, string title, string msg, string category) { context.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor("MySG001", title, msg, category, DiagnosticSeverity.Error, true), Location.None)); } - string GenerateClass(ResourceTemplate template, ResourceTemplate[] templates) - { - var cls = template.ClassName.Split('.'); - - var nameSpace = string.Join(".", cls.Take(cls.Length - 1)); - var className = cls.Last(); - - var rt = new StringBuilder(); - - rt.AppendLine("using System;\r\nusing Esiur.Resource;\r\nusing Esiur.Core;\r\nusing Esiur.Data;\r\nusing Esiur.Net.IIP;"); - rt.AppendLine($"namespace { nameSpace} {{"); - rt.AppendLine($"public class {className} : DistributedResource {{"); - - rt.AppendLine($"public {className}(DistributedConnection connection, uint instanceId, ulong age, string link) : base(connection, instanceId, age, link) {{}}"); - rt.AppendLine($"public {className}() {{}}"); - - foreach (var f in template.Functions) - { - var rtTypeName = GetTypeName(f.ReturnType, templates); - rt.Append($"public AsyncReply<{rtTypeName}> {f.Name}("); - rt.Append(string.Join(",", f.Arguments.Select(x => GetTypeName(x.Type, templates) + " " + x.Name))); - - rt.AppendLine(") {"); - rt.AppendLine($"var rt = new AsyncReply<{rtTypeName}>();"); - rt.AppendLine($"_InvokeByArrayArguments({f.Index}, new object[] {{ { string.Join(", ", f.Arguments.Select(x => x.Name)) } }})"); - rt.AppendLine($".Then(x => rt.Trigger(({rtTypeName})x))"); - rt.AppendLine($".Error(x => rt.TriggerError(x))"); - rt.AppendLine($".Chunk(x => rt.TriggerChunk(x));"); - rt.AppendLine("return rt; }"); - } - - foreach (var p in template.Properties) - { - var ptTypeName = GetTypeName(p.ValueType, templates); - rt.AppendLine($"public {ptTypeName} {p.Name} {{"); - rt.AppendLine($"get => ({ptTypeName})properties[{p.Index}];"); - rt.AppendLine($"set => _Set({p.Index}, value);"); - rt.AppendLine("}"); - } - - if (template.Events.Length > 0) - { - rt.AppendLine("protected override void _EmitEventByIndex(byte index, object args) {"); - rt.AppendLine("switch (index) {"); - - var eventsList = new StringBuilder(); - - foreach (var e in template.Events) - { - var etTypeName = GetTypeName(e.ArgumentType, templates); - rt.AppendLine($"case {e.Index}: {e.Name}?.Invoke(({etTypeName})args); break;"); - eventsList.AppendLine($"public event ResourceEventHanlder<{etTypeName}> {e.Name};"); - } - - rt.AppendLine("}}"); - - rt.AppendLine(eventsList.ToString()); - - } - - rt.AppendLine("\r\n}\r\n}"); - - return rt.ToString(); - } - + + void GenerateModel(GeneratorExecutionContext context, ResourceTemplate[] templates) { foreach (var tmp in templates) { - var source = GenerateClass(tmp, templates); - //File.WriteAllText($@"C:\gen\{tmp.ClassName}.cs", source); - context.AddSource(tmp.ClassName + "_esiur.cs", source); + if (tmp.Type == TemplateType.Resource) + { + var source = TemplateGenerator.GenerateClass(tmp, templates); + // File.WriteAllText($@"C:\gen\{tmp.ClassName}.cs", source); + context.AddSource(tmp.ClassName + ".Generated.cs", source); + } + else if (tmp.Type == TemplateType.Record) + { + var source = TemplateGenerator.GenerateRecord(tmp, templates); + // File.WriteAllText($@"C:\gen\{tmp.ClassName}.cs", source); + context.AddSource(tmp.ClassName + ".Generated.cs", source); + } } // generate info class - var gen = "using System; \r\n namespace Esiur { public static class Generated { public static Type[] Types {get;} = new Type[]{ " + - string.Join(",", templates.Select(x => $"typeof({x.ClassName})")) - + " }; \r\n } \r\n}"; + + var typesFile = "using System; \r\n namespace Esiur { public static class Generated { public static Type[] Resources {get;} = new Type[] { " + + string.Join(",", templates.Where(x => x.Type == TemplateType.Resource).Select(x => $"typeof({x.ClassName})")) + + " }; \r\n public static Type[] Records { get; } = new Type[] { " + + string.Join(",", templates.Where(x => x.Type == TemplateType.Record).Select(x => $"typeof({x.ClassName})")) + + " }; " + + + "\r\n } \r\n}"; //File.WriteAllText($@"C:\gen\Esiur.Generated.cs", gen); - context.AddSource("Esiur.Generated.cs", gen); + context.AddSource("Esiur.Generated.cs", typesFile); } + + public void Execute(GeneratorExecutionContext context) { @@ -188,7 +91,7 @@ namespace Esiur.Proxy foreach (var path in receiver.Imports) { - if (!urlRegex.IsMatch(path)) + if (!TemplateGenerator.urlRegex.IsMatch(path)) continue; @@ -206,7 +109,7 @@ namespace Esiur.Proxy //inProgress.Add(path); - var url = urlRegex.Split(path); + var url = TemplateGenerator.urlRegex.Split(path); try @@ -222,7 +125,7 @@ namespace Esiur.Proxy } catch (Exception ex) { - ReportError(context, ex.Source, ex.Message, "Esiur"); + ReportError(context, ex.Source, ex.Message, "Esiur"); System.IO.File.AppendAllText("c:\\gen\\error.log", ex.ToString() + "\r\n"); } @@ -273,12 +176,12 @@ public virtual void Destroy() {{ OnDestroy?.Invoke(this); }} code += "}}\r\n"; //System.IO.File.WriteAllText("c:\\gen\\" + ci.Name + "_esiur.cs", code); - context.AddSource(ci.Name + "_esiur.cs", code); + context.AddSource(ci.Name + ".Generated.cs", code); } catch (Exception ex) { - System.IO.File.AppendAllText("c:\\gen\\error.log", ci.Name + " " + ex.ToString() + "\r\n"); + //System.IO.File.AppendAllText("c:\\gen\\error.log", ci.Name + " " + ex.ToString() + "\r\n"); } } } diff --git a/Esiur/Proxy/ResourceProxy.cs b/Esiur/Proxy/ResourceProxy.cs index fd3d319..2f2026d 100644 --- a/Esiur/Proxy/ResourceProxy.cs +++ b/Esiur/Proxy/ResourceProxy.cs @@ -1,4 +1,5 @@ -using Esiur.Resource; +using Esiur.Data; +using Esiur.Resource; using System; using System.Collections.Generic; using System.Linq; @@ -56,6 +57,12 @@ namespace Esiur.Proxy return type; } + if (!Codec.ImplementsInterface(type, typeof(IResource))) + { + cache.Add(type, type); + return type; + } + #if NETSTANDARD var typeInfo = type.GetTypeInfo(); diff --git a/Esiur/Proxy/TemplateGenerator.cs b/Esiur/Proxy/TemplateGenerator.cs new file mode 100644 index 0000000..8cd7dc3 --- /dev/null +++ b/Esiur/Proxy/TemplateGenerator.cs @@ -0,0 +1,231 @@ +using Esiur.Data; +using Esiur.Resource.Template; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Linq; +using System.Text.RegularExpressions; +using Esiur.Resource; +using Esiur.Net.IIP; +using System.Diagnostics; + +namespace Esiur.Proxy +{ + public static class TemplateGenerator + { + internal static Regex urlRegex = new Regex(@"^(?:([\S]*)://([^/]*)/?)"); + + internal static string GenerateRecord(ResourceTemplate template, ResourceTemplate[] templates) + { + var cls = template.ClassName.Split('.'); + + var nameSpace = string.Join(".", cls.Take(cls.Length - 1)); + var className = cls.Last(); + + var rt = new StringBuilder(); + + rt.AppendLine("using System;\r\nusing Esiur.Resource;\r\nusing Esiur.Core;\r\nusing Esiur.Data;\r\nusing Esiur.Net.IIP;"); + rt.AppendLine($"namespace { nameSpace} {{"); + rt.AppendLine($"public class {className} : IRecord {{"); + + + foreach (var p in template.Properties) + { + var ptTypeName = GetTypeName(p.ValueType, templates); + rt.AppendLine($"public {ptTypeName} {p.Name} {{ get; set; }}"); + rt.AppendLine(); + } + + rt.AppendLine("\r\n}\r\n}"); + + return rt.ToString(); + } + + static string GetTypeName(TemplateDataType templateDataType, ResourceTemplate[] templates) + { + + if (templateDataType.Type == DataType.Resource) + return templates.First(x => x.ClassId == templateDataType.TypeGuid).ClassName; + else if (templateDataType.Type == DataType.ResourceArray) + return templates.First(x => x.ClassId == templateDataType.TypeGuid).ClassName + "[]"; + + var name = templateDataType.Type switch + { + DataType.Bool => "bool", + DataType.BoolArray => "bool[]", + DataType.Char => "char", + DataType.CharArray => "char[]", + DataType.DateTime => "DateTime", + DataType.DateTimeArray => "DateTime[]", + DataType.Decimal => "decimal", + DataType.DecimalArray => "decimal[]", + DataType.Float32 => "float", + DataType.Float32Array => "float[]", + DataType.Float64 => "double", + DataType.Float64Array => "double[]", + DataType.Int16 => "short", + DataType.Int16Array => "short[]", + DataType.Int32 => "int", + DataType.Int32Array => "int[]", + DataType.Int64 => "long", + DataType.Int64Array => "long[]", + DataType.Int8 => "sbyte", + DataType.Int8Array => "sbyte[]", + DataType.String => "string", + DataType.StringArray => "string[]", + DataType.Structure => "Structure", + DataType.StructureArray => "Structure[]", + DataType.UInt16 => "ushort", + DataType.UInt16Array => "ushort[]", + DataType.UInt32 => "uint", + DataType.UInt32Array => "uint[]", + DataType.UInt64 => "ulong", + DataType.UInt64Array => "ulong[]", + DataType.UInt8 => "byte", + DataType.UInt8Array => "byte[]", + DataType.VarArray => "object[]", + DataType.Void => "object", + _ => "object" + }; + + return name; + } + + public static string GetTemplate(string url, string dir = null, string username= null, string password = null) + { + try + { + + if (!urlRegex.IsMatch(url)) + throw new Exception("Invalid IIP URL"); + + var path = urlRegex.Split(url); + var con = Warehouse.Get(path[1] + "://" + path[2], + !string.IsNullOrEmpty( username) && !string.IsNullOrEmpty( password) ? new { Username = username, Password = password } : null + ).Wait(20000); + + if (con == null) + throw new Exception("Can't connect to server"); + + if (string.IsNullOrEmpty(dir)) + dir = path[2].Replace(":", "_"); + + var templates = con.GetLinkTemplates(path[3]).Wait(60000); + + var tempDir = new DirectoryInfo(Path.GetTempPath() + Path.DirectorySeparatorChar + + Misc.Global.GenerateCode(20) + Path.DirectorySeparatorChar + dir); + + if (!tempDir.Exists) + tempDir.Create(); + else + { + foreach (FileInfo file in tempDir.GetFiles()) + file.Delete(); + } + + // make sources + foreach (var tmp in templates) + { + if (tmp.Type == TemplateType.Resource) + { + var source = GenerateClass(tmp, templates); + File.WriteAllText(tempDir.FullName + Path.DirectorySeparatorChar + tmp.ClassName + ".Generated.cs", source); + } + else if (tmp.Type == TemplateType.Record) + { + var source = GenerateRecord(tmp, templates); + File.WriteAllText(tempDir.FullName + Path.DirectorySeparatorChar + tmp.ClassName + ".Generated.cs", source); + } + } + + // generate info class + + var typesFile = "using System; \r\n namespace Esiur { public static class Generated { public static Type[] Resources {get;} = new Type[] { " + + string.Join(",", templates.Where(x => x.Type == TemplateType.Resource).Select(x => $"typeof({x.ClassName})")) + + " }; \r\n public static Type[] Records { get; } = new Type[] { " + + string.Join(",", templates.Where(x => x.Type == TemplateType.Record).Select(x => $"typeof({x.ClassName})")) + + " }; " + + + "\r\n } \r\n}"; + + + File.WriteAllText(tempDir.FullName + Path.DirectorySeparatorChar + "Esiur.Generated.cs", typesFile); + + return tempDir.FullName; + + } + catch(Exception ex) + { + //File.WriteAllText("C:\\gen\\gettemplate.err", ex.ToString()); + throw ex; + } + } + + internal static string GenerateClass(ResourceTemplate template, ResourceTemplate[] templates) + { + var cls = template.ClassName.Split('.'); + + var nameSpace = string.Join(".", cls.Take(cls.Length - 1)); + var className = cls.Last(); + + var rt = new StringBuilder(); + + rt.AppendLine("using System;\r\nusing Esiur.Resource;\r\nusing Esiur.Core;\r\nusing Esiur.Data;\r\nusing Esiur.Net.IIP;"); + rt.AppendLine($"namespace { nameSpace} {{"); + rt.AppendLine($"public class {className} : DistributedResource {{"); + + rt.AppendLine($"public {className}(DistributedConnection connection, uint instanceId, ulong age, string link) : base(connection, instanceId, age, link) {{}}"); + rt.AppendLine($"public {className}() {{}}"); + + foreach (var f in template.Functions) + { + var rtTypeName = GetTypeName(f.ReturnType, templates); + rt.Append($"public AsyncReply<{rtTypeName}> {f.Name}("); + rt.Append(string.Join(",", f.Arguments.Select(x => GetTypeName(x.Type, templates) + " " + x.Name))); + + rt.AppendLine(") {"); + rt.AppendLine($"var rt = new AsyncReply<{rtTypeName}>();"); + rt.AppendLine($"_InvokeByArrayArguments({f.Index}, new object[] {{ { string.Join(", ", f.Arguments.Select(x => x.Name)) } }})"); + rt.AppendLine($".Then(x => rt.Trigger(({rtTypeName})x))"); + rt.AppendLine($".Error(x => rt.TriggerError(x))"); + rt.AppendLine($".Chunk(x => rt.TriggerChunk(x));"); + rt.AppendLine("return rt; }"); + } + + foreach (var p in template.Properties) + { + var ptTypeName = GetTypeName(p.ValueType, templates); + rt.AppendLine($"public {ptTypeName} {p.Name} {{"); + rt.AppendLine($"get => ({ptTypeName})properties[{p.Index}];"); + rt.AppendLine($"set => _Set({p.Index}, value);"); + rt.AppendLine("}"); + } + + if (template.Events.Length > 0) + { + rt.AppendLine("protected override void _EmitEventByIndex(byte index, object args) {"); + rt.AppendLine("switch (index) {"); + + var eventsList = new StringBuilder(); + + foreach (var e in template.Events) + { + var etTypeName = GetTypeName(e.ArgumentType, templates); + rt.AppendLine($"case {e.Index}: {e.Name}?.Invoke(({etTypeName})args); break;"); + eventsList.AppendLine($"public event ResourceEventHanlder<{etTypeName}> {e.Name};"); + } + + rt.AppendLine("}}"); + + rt.AppendLine(eventsList.ToString()); + + } + + rt.AppendLine("\r\n}\r\n}"); + + return rt.ToString(); + } + + } +} diff --git a/Esiur/Resource/Instance.cs b/Esiur/Resource/Instance.cs index 231e3d1..36b172f 100644 --- a/Esiur/Resource/Instance.cs +++ b/Esiur/Resource/Instance.cs @@ -875,7 +875,7 @@ namespace Esiur.Resource if (customTemplate != null) this.template = customTemplate; else - this.template = Warehouse.GetTemplate(resource.GetType()); + this.template = Warehouse.GetTemplateByType(resource.GetType()); // set ages for (byte i = 0; i < template.Properties.Length; i++) diff --git a/Esiur/Resource/Template/RecordTemplate.cs b/Esiur/Resource/Template/RecordTemplate.cs new file mode 100644 index 0000000..6bb33e6 --- /dev/null +++ b/Esiur/Resource/Template/RecordTemplate.cs @@ -0,0 +1,277 @@ +using Esiur.Data; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +namespace Esiur.Resource.Template +{ + public class RecordTemplate : ResourceTemplate + { + //Guid classId; + //public Guid ClassId => classId; + + //string className; + //public string ClassName => className; + + public RecordTemplate() + { + + } + + public new static RecordTemplate Parse(byte[] data, uint offset, uint contentLength) + { + + uint ends = offset + contentLength; + + uint oOffset = offset; + + // start parsing... + + var od = new RecordTemplate(); + od.content = data.Clip(offset, contentLength); + + od.classId = data.GetGuid(offset); + offset += 16; + od.className = data.GetString(offset + 1, data[offset]); + offset += (uint)data[offset] + 1; + + od.version = data.GetInt32(offset); + offset += 4; + + ushort methodsCount = data.GetUInt16(offset); + offset += 2; + + + + for (int i = 0; i < methodsCount; i++) + { + var type = data[offset] >> 5; + + if (type == 0) // function + { + string expansion = null; + var hasExpansion = ((data[offset++] & 0x10) == 0x10); + + var name = data.GetString(offset + 1, data[offset]); + offset += (uint)data[offset] + 1; + + // return type + var (rts, returnType) = TemplateDataType.Parse(data, offset); + offset += rts; + + // arguments count + var argsCount = data[offset++]; + List arguments = new(); + + for (var a = 0; a < argsCount; a++) + { + var (cs, argType) = ArgumentTemplate.Parse(data, offset); + arguments.Add(argType); + offset += cs; + } + + // arguments + if (hasExpansion) // expansion ? + { + var cs = data.GetUInt32(offset); + offset += 4; + expansion = data.GetString(offset, cs); + offset += cs; + } + + var ft = new FunctionTemplate(od, functionIndex++, name, arguments.ToArray(), returnType, expansion); + + od.functions.Add(ft); + } + else if (type == 1) // property + { + + string readExpansion = null, writeExpansion = null; + + var hasReadExpansion = ((data[offset] & 0x8) == 0x8); + var hasWriteExpansion = ((data[offset] & 0x10) == 0x10); + var recordable = ((data[offset] & 1) == 1); + var permission = (PropertyTemplate.PropertyPermission)((data[offset++] >> 1) & 0x3); + var name = data.GetString(offset + 1, data[offset]);// Encoding.ASCII.GetString(data, (int)offset + 1, data[offset]); + + offset += (uint)data[offset] + 1; + + var (dts, valueType) = TemplateDataType.Parse(data, offset); + + offset += dts; + + if (hasReadExpansion) // expansion ? + { + var cs = data.GetUInt32(offset); + offset += 4; + readExpansion = data.GetString(offset, cs); + offset += cs; + } + + if (hasWriteExpansion) // expansion ? + { + var cs = data.GetUInt32(offset); + offset += 4; + writeExpansion = data.GetString(offset, cs); + offset += cs; + } + + var pt = new PropertyTemplate(od, propertyIndex++, name, valueType, readExpansion, writeExpansion, recordable); + + od.properties.Add(pt); + } + else if (type == 2) // Event + { + + string expansion = null; + var hasExpansion = ((data[offset] & 0x10) == 0x10); + var listenable = ((data[offset++] & 0x8) == 0x8); + + var name = data.GetString(offset + 1, data[offset]);// Encoding.ASCII.GetString(data, (int)offset + 1, (int)data[offset]); + offset += (uint)data[offset] + 1; + + var (dts, argType) = TemplateDataType.Parse(data, offset); + + offset += dts; + + if (hasExpansion) // expansion ? + { + var cs = data.GetUInt32(offset); + offset += 4; + expansion = data.GetString(offset, cs); + offset += cs; + } + + var et = new EventTemplate(od, eventIndex++, name, argType, expansion, listenable); + + od.events.Add(et); + + } + } + + // append signals + for (int i = 0; i < od.events.Count; i++) + od.members.Add(od.events[i]); + // append slots + for (int i = 0; i < od.functions.Count; i++) + od.members.Add(od.functions[i]); + // append properties + for (int i = 0; i < od.properties.Count; i++) + od.members.Add(od.properties[i]); + + + //od.isReady = true; + /* + var oo = owner.Socket.Engine.GetObjectDescription(od.GUID); + if (oo != null) + { + Console.WriteLine("Already there ! description"); + return oo; + } + else + { + owner.Socket.Engine.AddObjectDescription(od); + return od; + } + */ + + return od; + } + + public RecordTemplate(Type type) + { + if (!Codec.ImplementsInterface(type, typeof(IRecord))) + throw new Exception("Type is not a record."); + + className = type.FullName; + classId = ResourceTemplate.GetTypeGuid(className); + +#if NETSTANDARD + PropertyInfo[] propsInfo = type.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance); +#else + PropertyInfo[] propsInfo = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); +#endif + + bool classIsPublic = type.GetCustomAttribute() != null; + + byte i = 0; + + if (classIsPublic) + { + foreach (var pi in propsInfo) + { + var privateAttr = pi.GetCustomAttribute(true); + + if (privateAttr == null) + continue; + + var annotationAttr = pi.GetCustomAttribute(true); + var storageAttr = pi.GetCustomAttribute(true); + + var pt = new PropertyTemplate(this, i++, pi.Name, TemplateDataType.FromType(pi.PropertyType)); + + if (storageAttr != null) + pt.Recordable = storageAttr.Mode == StorageMode.Recordable; + + if (annotationAttr != null) + pt.ReadExpansion = annotationAttr.Annotation; + else + pt.ReadExpansion = pi.PropertyType.Name; + + pt.PropertyInfo = pi; + //pt.Serilize = publicAttr.Serialize; + properties.Add(pt); + members.Add(pt); + } + } + else + { + foreach (var pi in propsInfo) + { + var publicAttr = pi.GetCustomAttribute(true); + + if (publicAttr == null) + continue; + + + var annotationAttr = pi.GetCustomAttribute(true); + var storageAttr = pi.GetCustomAttribute(true); + var valueType = TemplateDataType.FromType(pi.PropertyType); + + var pt = new PropertyTemplate(this, i++, pi.Name, valueType);//, rp.ReadExpansion, rp.WriteExpansion, rp.Storage); + if (storageAttr != null) + pt.Recordable = storageAttr.Mode == StorageMode.Recordable; + + if (annotationAttr != null) + pt.ReadExpansion = annotationAttr.Annotation; + else + pt.ReadExpansion = pi.PropertyType.Name; + + pt.PropertyInfo = pi; + //pt.Serilize = publicAttr.Serialize; + properties.Add(pt); + members.Add(pt); + } + } + + + + // bake it binarily + var b = new BinaryList(); + b.AddGuid(classId) + .AddUInt8((byte)className.Length) + .AddString(className) + .AddInt32(version) + .AddUInt16((ushort)members.Count); + + + foreach (var pt in properties) + b.AddUInt8Array(pt.Compose()); + + content = b.ToArray(); + + + } + } +} diff --git a/Esiur/Resource/Template/ResourceTemplate.cs b/Esiur/Resource/Template/ResourceTemplate.cs index 144dcec..124dc1c 100644 --- a/Esiur/Resource/Template/ResourceTemplate.cs +++ b/Esiur/Resource/Template/ResourceTemplate.cs @@ -12,28 +12,42 @@ using Esiur.Net.IIP; namespace Esiur.Resource.Template { + //public enum TemplateType + //{ + // Resource, + // Record + //} + public class ResourceTemplate { - Guid classId; - string className; - List members = new List(); - List functions = new List(); - List events = new List(); - List properties = new List(); - List attributes = new List(); - int version; + protected Guid classId; + protected string className; + protected List members = new List(); + protected List functions = new List(); + protected List events = new List(); + protected List properties = new List(); + protected List attributes = new List(); + protected int version; + protected TemplateType templateType; + + // protected TemplateType //bool isReady; - byte[] content; + protected byte[] content; public byte[] Content { get { return content; } } + public TemplateType Type => templateType; + + public Type ResourceType { get; set; } + + public MemberTemplate GetMemberTemplate(MemberInfo member) { if (member is MethodInfo) @@ -174,7 +188,7 @@ namespace Esiur.Resource.Template // functions foreach (var f in tmp.functions) { - var frtt = Warehouse.GetTemplate(GetElementType(f.MethodInfo.ReturnType)); + var frtt = Warehouse.GetTemplateByType(GetElementType(f.MethodInfo.ReturnType)); if (frtt != null) { if (!bag.Contains(frtt)) @@ -188,7 +202,7 @@ namespace Esiur.Resource.Template for(var i = 0; i < args.Length - 1; i++) { - var fpt = Warehouse.GetTemplate(GetElementType(args[i].ParameterType)); + var fpt = Warehouse.GetTemplateByType(GetElementType(args[i].ParameterType)); if (fpt != null) { if (!bag.Contains(fpt)) @@ -205,7 +219,7 @@ namespace Esiur.Resource.Template var last = args.Last(); if (last.ParameterType != typeof(DistributedConnection)) { - var fpt = Warehouse.GetTemplate(GetElementType(last.ParameterType)); + var fpt = Warehouse.GetTemplateByType(GetElementType(last.ParameterType)); if (fpt != null) { if (!bag.Contains(fpt)) @@ -222,7 +236,7 @@ namespace Esiur.Resource.Template // properties foreach (var p in tmp.properties) { - var pt = Warehouse.GetTemplate(GetElementType(p.PropertyInfo.PropertyType)); + var pt = Warehouse.GetTemplateByType(GetElementType(p.PropertyInfo.PropertyType)); if (pt != null) { if (!bag.Contains(pt)) @@ -236,7 +250,7 @@ namespace Esiur.Resource.Template // events foreach (var e in tmp.events) { - var et = Warehouse.GetTemplate(GetElementType(e.EventInfo.EventHandlerType.GenericTypeArguments[0])); + var et = Warehouse.GetTemplateByType(GetElementType(e.EventInfo.EventHandlerType.GenericTypeArguments[0])); if (et != null) { @@ -255,8 +269,18 @@ namespace Esiur.Resource.Template public ResourceTemplate(Type type) { - if (!Codec.ImplementsInterface(type, typeof(IResource))) - throw new Exception("Type is not a resource."); + if (Codec.ImplementsInterface(type, typeof(IRecord))) + templateType = TemplateType.Record; + else if (Codec.ImplementsInterface(type, typeof(IResource))) + templateType = TemplateType.Resource; + else + throw new Exception("Type is neither a resource nor a record."); + + //if (isRecord && isResource) + // throw new Exception("Type can't have both IResource and IRecord interfaces"); + + //if (!(isResource || isRecord)) + // throw new Exception("Type is neither a resource nor a record."); type = ResourceProxy.GetBaseType(type); @@ -322,65 +346,68 @@ namespace Esiur.Resource.Template } } - i = 0; - - foreach (var ei in eventsInfo) + if (templateType == TemplateType.Resource) { - var privateAttr = ei.GetCustomAttribute(true); - if (privateAttr == null) + i = 0; + + foreach (var ei in eventsInfo) { - var annotationAttr = ei.GetCustomAttribute(true); - var listenableAttr = ei.GetCustomAttribute(true); - - var argType = ei.EventHandlerType.GenericTypeArguments[0]; - var et = new EventTemplate(this, i++, ei.Name, TemplateDataType.FromType(argType)); - et.EventInfo = ei; - - if (annotationAttr != null) - et.Expansion = annotationAttr.Annotation; - - if (listenableAttr != null) - et.Listenable = true; - - events.Add(et); - } - } - - i = 0; - foreach (MethodInfo mi in methodsInfo) - { - var privateAttr = mi.GetCustomAttribute(true); - if (privateAttr == null) - { - var annotationAttr = mi.GetCustomAttribute(true); - - var returnType = TemplateDataType.FromType(mi.ReturnType); - - var args = mi.GetParameters(); - - if (args.Length > 0) + var privateAttr = ei.GetCustomAttribute(true); + if (privateAttr == null) { - if (args.Last().ParameterType == typeof(DistributedConnection)) - args = args.Take(args.Count() - 1).ToArray(); + var annotationAttr = ei.GetCustomAttribute(true); + var listenableAttr = ei.GetCustomAttribute(true); + + var argType = ei.EventHandlerType.GenericTypeArguments[0]; + var et = new EventTemplate(this, i++, ei.Name, TemplateDataType.FromType(argType)); + et.EventInfo = ei; + + if (annotationAttr != null) + et.Expansion = annotationAttr.Annotation; + + if (listenableAttr != null) + et.Listenable = true; + + events.Add(et); } + } - var arguments = args.Select(x => new ArgumentTemplate() - { - Name = x.Name, - Type = TemplateDataType.FromType(x.ParameterType), - ParameterInfo = x - }) - .ToArray(); + i = 0; + foreach (MethodInfo mi in methodsInfo) + { + var privateAttr = mi.GetCustomAttribute(true); + if (privateAttr == null) + { + var annotationAttr = mi.GetCustomAttribute(true); - var ft = new FunctionTemplate(this, i++, mi.Name, arguments, returnType);// mi.ReturnType == typeof(void)); + var returnType = TemplateDataType.FromType(mi.ReturnType); - if (annotationAttr != null) - ft.Expansion = annotationAttr.Annotation; - else - ft.Expansion = "(" + String.Join(",", mi.GetParameters().Where(x => x.ParameterType != typeof(DistributedConnection)).Select(x => "[" + x.ParameterType.Name + "] " + x.Name)) + ") -> " + mi.ReturnType.Name; + var args = mi.GetParameters(); - ft.MethodInfo = mi; - functions.Add(ft); + if (args.Length > 0) + { + if (args.Last().ParameterType == typeof(DistributedConnection)) + args = args.Take(args.Count() - 1).ToArray(); + } + + var arguments = args.Select(x => new ArgumentTemplate() + { + Name = x.Name, + Type = TemplateDataType.FromType(x.ParameterType), + ParameterInfo = x + }) + .ToArray(); + + var ft = new FunctionTemplate(this, i++, mi.Name, arguments, returnType);// mi.ReturnType == typeof(void)); + + if (annotationAttr != null) + ft.Expansion = annotationAttr.Annotation; + else + ft.Expansion = "(" + String.Join(",", mi.GetParameters().Where(x => x.ParameterType != typeof(DistributedConnection)).Select(x => "[" + x.ParameterType.Name + "] " + x.Name)) + ") -> " + mi.ReturnType.Name; + + ft.MethodInfo = mi; + functions.Add(ft); + } } } } @@ -422,65 +449,68 @@ namespace Esiur.Resource.Template } } - i = 0; - - foreach (var ei in eventsInfo) + if (templateType == TemplateType.Resource) { - var publicAttr = ei.GetCustomAttribute(true); - if (publicAttr != null) + i = 0; + + foreach (var ei in eventsInfo) { - var annotationAttr = ei.GetCustomAttribute(true); - var listenableAttr = ei.GetCustomAttribute(true); - - var argType = ei.EventHandlerType.GenericTypeArguments[0]; - - var et = new EventTemplate(this, i++, ei.Name, TemplateDataType.FromType(argType)); - et.EventInfo = ei; - - if (annotationAttr != null) - et.Expansion = annotationAttr.Annotation; - - if (listenableAttr != null) - et.Listenable = true; - - events.Add(et); - } - } - - i = 0; - foreach (MethodInfo mi in methodsInfo) - { - var publicAttr = mi.GetCustomAttribute(true); - if (publicAttr != null) - { - var annotationAttr = mi.GetCustomAttribute(true); - var returnType = TemplateDataType.FromType(mi.ReturnType); - - var args = mi.GetParameters(); - - if (args.Length > 0) + var publicAttr = ei.GetCustomAttribute(true); + if (publicAttr != null) { - if (args.Last().ParameterType == typeof(DistributedConnection)) - args = args.Take(args.Count() - 1).ToArray(); + var annotationAttr = ei.GetCustomAttribute(true); + var listenableAttr = ei.GetCustomAttribute(true); + + var argType = ei.EventHandlerType.GenericTypeArguments[0]; + + var et = new EventTemplate(this, i++, ei.Name, TemplateDataType.FromType(argType)); + et.EventInfo = ei; + + if (annotationAttr != null) + et.Expansion = annotationAttr.Annotation; + + if (listenableAttr != null) + et.Listenable = true; + + events.Add(et); } + } - var arguments = args.Select(x => new ArgumentTemplate() - { - Name = x.Name, - Type = TemplateDataType.FromType(x.ParameterType), - ParameterInfo = x - }) - .ToArray(); + i = 0; + foreach (MethodInfo mi in methodsInfo) + { + var publicAttr = mi.GetCustomAttribute(true); + if (publicAttr != null) + { + var annotationAttr = mi.GetCustomAttribute(true); + var returnType = TemplateDataType.FromType(mi.ReturnType); - var ft = new FunctionTemplate(this, i++, mi.Name, arguments, returnType);// mi.ReturnType == typeof(void)); + var args = mi.GetParameters(); - if (annotationAttr != null) - ft.Expansion = annotationAttr.Annotation; - else - ft.Expansion = "(" + String.Join(",", mi.GetParameters().Where(x=>x.ParameterType != typeof(DistributedConnection)).Select(x=> "[" + x.ParameterType.Name + "] " + x.Name)) + ") -> " + mi.ReturnType.Name; + if (args.Length > 0) + { + if (args.Last().ParameterType == typeof(DistributedConnection)) + args = args.Take(args.Count() - 1).ToArray(); + } - ft.MethodInfo = mi; - functions.Add(ft); + var arguments = args.Select(x => new ArgumentTemplate() + { + Name = x.Name, + Type = TemplateDataType.FromType(x.ParameterType), + ParameterInfo = x + }) + .ToArray(); + + var ft = new FunctionTemplate(this, i++, mi.Name, arguments, returnType);// mi.ReturnType == typeof(void)); + + if (annotationAttr != null) + ft.Expansion = annotationAttr.Annotation; + else + ft.Expansion = "(" + String.Join(",", mi.GetParameters().Where(x => x.ParameterType != typeof(DistributedConnection)).Select(x => "[" + x.ParameterType.Name + "] " + x.Name)) + ") -> " + mi.ReturnType.Name; + + ft.MethodInfo = mi; + functions.Add(ft); + } } } } @@ -497,7 +527,8 @@ namespace Esiur.Resource.Template // bake it binarily var b = new BinaryList(); - b.AddGuid(classId) + b.AddUInt8((byte)templateType) + .AddGuid(classId) .AddUInt8((byte)className.Length) .AddString(className) .AddInt32(version) @@ -533,6 +564,8 @@ namespace Esiur.Resource.Template var od = new ResourceTemplate(); od.content = data.Clip(offset, contentLength); + od.templateType = (TemplateType)data[offset++]; + od.classId = data.GetGuid(offset); offset += 16; od.className = data.GetString(offset + 1, data[offset]); diff --git a/Esiur/Resource/Template/TemplateDataType.cs b/Esiur/Resource/Template/TemplateDataType.cs index 068bf55..7ceb11a 100644 --- a/Esiur/Resource/Template/TemplateDataType.cs +++ b/Esiur/Resource/Template/TemplateDataType.cs @@ -10,7 +10,7 @@ namespace Esiur.Resource.Template { public DataType Type { get; set; } //public string TypeName { get; set; } - public ResourceTemplate TypeTemplate => TypeGuid == null ? null : Warehouse.GetTemplate((Guid)TypeGuid); + public ResourceTemplate TypeTemplate => TypeGuid == null ? null : Warehouse.GetTemplateByClassId((Guid)TypeGuid); public Guid? TypeGuid { get; set; } //public TemplateDataType(DataType type, string typeName) @@ -51,20 +51,14 @@ namespace Esiur.Resource.Template _ when t == typeof(IResource) => DataType.Void, // Dynamic resource (unspecified type) _ when typeof(Structure).IsAssignableFrom(t) || t == typeof(ExpandoObject) => DataType.Structure, _ when Codec.ImplementsInterface(t, typeof(IResource)) => DataType.Resource, + _ when Codec.ImplementsInterface(t, typeof(IRecord)) => DataType.Record, _ => DataType.Void }; - //string tn = dt switch - //{ - // DataType.Resource => t.FullName, - // DataType.Structure when t != typeof(Structure) => t.FullName, - // _ => null - //}; - Guid? typeGuid = null; - if (dt == DataType.Resource) + if (dt == DataType.Resource || dt == DataType.Record) typeGuid = ResourceTemplate.GetTypeGuid(t); if (type.IsArray) @@ -80,11 +74,9 @@ namespace Esiur.Resource.Template public byte[] Compose() { if (Type == DataType.Resource || - Type == DataType.ResourceArray)//|| - //Type == DataType.DistributedResource || - //Type == DataType.DistributedResourceArray || - //Type == DataType.Structure || - //Type == DataType.StructureArray) + Type == DataType.ResourceArray || + Type == DataType.Record || + Type == DataType.RecordArray) { var guid = DC.ToBytes((Guid)TypeGuid); return new BinaryList() @@ -102,11 +94,9 @@ namespace Esiur.Resource.Template { var type = (DataType)data[offset++]; if (type == DataType.Resource || - type == DataType.ResourceArray)//|| - // type == DataType.DistributedResource || - // type == DataType.DistributedResourceArray)// || - // type == DataType.Structure || - // type == DataType.StructureArray) + type == DataType.ResourceArray || + type == DataType.Record || + type == DataType.RecordArray) { var guid = data.GetGuid(offset); return (17, new TemplateDataType() { Type = type, TypeGuid = guid }); diff --git a/Esiur/Resource/Template/TemplateType.cs b/Esiur/Resource/Template/TemplateType.cs new file mode 100644 index 0000000..58d39a7 --- /dev/null +++ b/Esiur/Resource/Template/TemplateType.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Esiur.Resource.Template +{ + public enum TemplateType:byte + { + Resource, + Record, + } +} diff --git a/Esiur/Resource/Warehouse.cs b/Esiur/Resource/Warehouse.cs index d7ee258..4712f88 100644 --- a/Esiur/Resource/Warehouse.cs +++ b/Esiur/Resource/Warehouse.cs @@ -54,6 +54,7 @@ namespace Esiur.Resource static uint resourceCounter = 0; static KeyList templates = new KeyList(); + static KeyList wrapperTemplates = new KeyList(); static bool warehouseIsOpen = false; @@ -120,8 +121,14 @@ namespace Esiur.Resource var generatedType = assembly.GetType("Esiur.Generated"); if (generatedType != null) { - var types = (Type[])generatedType.GetProperty("Types").GetValue(null); - foreach (var t in types) + var resourceTypes = (Type[])generatedType.GetProperty("Resources").GetValue(null); + foreach (var t in resourceTypes) + { + PutTemplate(new ResourceTemplate(t), true); + } + + var recordTypes = (Type[])generatedType.GetProperty("Records").GetValue(null); + foreach (var t in recordTypes) { PutTemplate(new ResourceTemplate(t)); } @@ -730,9 +737,14 @@ namespace Esiur.Resource /// Put a resource template in the templates warehouse. /// /// Resource template. - public static void PutTemplate(ResourceTemplate template) + public static void PutTemplate(ResourceTemplate template, bool wrapper = false) { - if (!templates.ContainsKey(template.ClassId)) + if (wrapper) + { + if (!wrapperTemplates.ContainsKey(template.ClassId)) + wrapperTemplates.Add(template.ClassId, template); + } + else if (!templates.ContainsKey(template.ClassId)) templates.Add(template.ClassId, template); } @@ -742,18 +754,19 @@ namespace Esiur.Resource /// /// .Net type. /// Resource template. - public static ResourceTemplate GetTemplate(Type type) + public static ResourceTemplate GetTemplateByType(Type type) { - if (!Codec.ImplementsInterface(type, typeof(IResource))) + if (!(Codec.ImplementsInterface(type, typeof(IResource)) + || Codec.ImplementsInterface(type, typeof(IRecord)))) return null; var baseType = ResourceProxy.GetBaseType(type); - if (baseType == typeof(IResource) ) + if (baseType == typeof(IResource) + || baseType == typeof(IRecord)) return null; - // loaded ? foreach (var t in templates.Values) if (t.ClassName == baseType.FullName) @@ -770,10 +783,16 @@ namespace Esiur.Resource /// /// Class Id. /// Resource template. - public static ResourceTemplate GetTemplate(Guid classId) + public static ResourceTemplate GetTemplateByClassId(Guid classId, bool wrapper = false) { - if (templates.ContainsKey(classId)) + if (wrapper) + { + if (wrapperTemplates.ContainsKey(classId)) + return wrapperTemplates[classId]; + } + else if (templates.ContainsKey(classId)) return templates[classId]; + return null; } @@ -782,7 +801,7 @@ namespace Esiur.Resource /// /// Class name. /// Resource template. - public static AsyncReply GetTemplate(string className) + public static AsyncReply GetTemplateByClassName(string className) { foreach (var t in templates.Values) if (t.ClassName == className) diff --git a/Esiur/Tools/Esiur.psd1 b/Esiur/Tools/Esiur.psd1 new file mode 100644 index 0000000..06ef1a2 Binary files /dev/null and b/Esiur/Tools/Esiur.psd1 differ diff --git a/Esiur/Tools/Esiur.psm1 b/Esiur/Tools/Esiur.psm1 new file mode 100644 index 0000000..7f1a4d2 --- /dev/null +++ b/Esiur/Tools/Esiur.psm1 @@ -0,0 +1,115 @@ +function Get-Template($url, $dir, $username, $password) +{ + + $lib = Resolve-Path -Path "$($PSScriptRoot)\..\lib\netstandard2.0\Esiur.dll" + #write-host "Lib is at $($lib)" + + $assembly = [Reflection.Assembly]::LoadFile($lib) + $tempPath = [Esiur.Proxy.TemplateGenerator]::GetTemplate($url, $dir, $username,$password); + + $startupProject = GetStartupProject + + $activeConfiguration = $startupProject.ConfigurationManager.ActiveConfiguration + if ($activeConfiguration -eq $null) + { + throw "Unable to read project configuration settings of project '$($startupProject.ProjectName)' for the " + + 'active solution configuration. Try closing Package Manager Console and restarting Visual Studio. If the ' + + 'problem persists, use Help > Send Feedback > Report a Problem.' + } + + + $startupProject.ProjectItems.AddFromDirectory($tempPath) | Out-Null +} + +function GetStartupProject($name, $fallbackProject) +{ + if ($name) + { + return Get-Project $name + } + + $startupProjectPaths = $DTE.Solution.SolutionBuild.StartupProjects + if ($startupProjectPaths) + { + if ($startupProjectPaths.Length -eq 1) + { + $startupProjectPath = $startupProjectPaths[0] + if (![IO.Path]::IsPathRooted($startupProjectPath)) + { + $solutionPath = Split-Path (GetProperty $DTE.Solution.Properties 'Path') + $startupProjectPath = [IO.Path]::GetFullPath((Join-Path $solutionPath $startupProjectPath)) + } + + $startupProject = GetSolutionProjects | ?{ + try + { + $fullName = $_.FullName + } + catch [NotImplementedException] + { + return $false + } + + if ($fullName -and $fullName.EndsWith('\')) + { + $fullName = $fullName.Substring(0, $fullName.Length - 1) + } + + return $fullName -eq $startupProjectPath + } + if ($startupProject) + { + return $startupProject + } + + Write-Warning "Unable to resolve startup project '$startupProjectPath'." + } + else + { + Write-Warning 'Multiple startup projects set.' + } + } + else + { + Write-Warning 'No startup project set.' + } + + Write-Warning "Using project '$($fallbackProject.ProjectName)' as the startup project." + + return $fallbackProject +} + +function GetProperty($properties, $propertyName) +{ + try + { + return $properties.Item($propertyName).Value + } + catch + { + return $null + } +} + +function GetSolutionProjects() +{ + $projects = New-Object 'System.Collections.Stack' + + $DTE.Solution.Projects | %{ + $projects.Push($_) + } + + while ($projects.Count) + { + $project = $projects.Pop(); + + <# yield return #> $project + + if ($project.ProjectItems) + { + $project.ProjectItems | ?{ $_.SubProject } | %{ + $projects.Push($_.SubProject) + } + } + } +} \ No newline at end of file diff --git a/Esiur/Tools/init.ps1 b/Esiur/Tools/init.ps1 new file mode 100644 index 0000000..fa97933 --- /dev/null +++ b/Esiur/Tools/init.ps1 @@ -0,0 +1,36 @@ + +param($installPath, $toolsPath, $package, $project) + +# NB: Not set for scripts in PowerShell 2.0 +if (!$PSScriptRoot) +{ + $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent +} + +if ($PSVersionTable.PSVersion -lt '3.0') +{ + # Import a "dummy" module that contains matching functions that throw on PS2 + Import-Module (Join-Path $PSScriptRoot 'Esiur.psd1') -DisableNameChecking + + return +} + +$importedModule = Get-Module 'Esiur' +$moduleToImport = Test-ModuleManifest (Join-Path $PSScriptRoot 'Esiur.psd1') +$import = $true +if ($importedModule) +{ + if ($importedModule.Version -le $moduleToImport.Version) + { + Remove-Module 'Esiur' + } + else + { + $import = $false + } +} + +if ($import) +{ + Import-Module $moduleToImport -DisableNameChecking +}