2
0
mirror of https://github.com/esiur/esiur-dotnet.git synced 2026-04-04 12:28:21 +00:00
This commit is contained in:
2026-04-04 04:31:30 +03:00
parent 1339604bc5
commit 5f73cf7af7
298 changed files with 100 additions and 501 deletions

View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource;
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Event | AttributeTargets.Parameter, AllowMultiple = true)]
public class AnnotationAttribute : Attribute
{
public readonly string? Key;
public readonly string Value;
public AnnotationAttribute(string annotation)
{
Key = "";
Value = annotation;
}
public AnnotationAttribute(string key, string value)
{
Key = key;
Value = value;
}
//public AnnotationAttribute(params string[] annotations)
//{
// this.Annotation = String.Join("\n", annotations);
//}
}

View File

@@ -0,0 +1,40 @@
/*
Copyright (c) 2020 Ahmed Kh. Zamil
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Esiur.Resource;
[AttributeUsage(AttributeTargets.Property)]
public class AttributeAttribute : System.Attribute
{
public string Name { get; set; }
public AttributeAttribute(string name = null)
{
this.Name = name;
}
}

View File

@@ -0,0 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource;
public delegate void CustomEventOccurredEvent(CustomEventOccurredInfo info);

View File

@@ -0,0 +1,27 @@
using Esiur.Data.Types;
using Esiur.Security.Authority;
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource;
public class CustomEventOccurredInfo
{
public readonly EventDef EventDef;
public readonly IResource Resource;
public readonly object Value;
public readonly object Issuer;
public readonly Func<Session, bool> Receivers;
public string Name => EventDef.Name;
public CustomEventOccurredInfo(IResource resource, EventDef eventDef, Func<Session, bool> receivers, object issuer, object value)
{
Resource = resource;
EventDef = eventDef;
Receivers = receivers;
Issuer = issuer;
Value = value;
}
}

View File

@@ -0,0 +1,8 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource
{
public delegate void EventOccurredEvent(EventOccurredInfo info);
}

View File

@@ -0,0 +1,26 @@
using Esiur.Data.Types;
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource
{
public class EventOccurredInfo
{
public readonly EventDef Definition;
public string Name => Definition.Name;
public readonly IResource Resource;
public readonly object Value;
public EventOccurredInfo(IResource resource, EventDef eventDef, object value)
{
Resource = resource;
Value = value;
Definition = eventDef;
}
}
}

View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Linq;
namespace Esiur.Resource;
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Event | AttributeTargets.Class | AttributeTargets.Enum)]
public class ExportAttribute : Attribute
{
public string Name { get; private set; } = null;
public PropertyPermission? Permission { get; private set; }
public ExportAttribute()
{
}
public ExportAttribute(string name)
{
Name = name;
}
public ExportAttribute(PropertyPermission permission)
{
Permission = permission;
}
public ExportAttribute(string name, PropertyPermission permission)
{
Name = name;
Permission = permission;
}
}

View File

@@ -0,0 +1,43 @@
/*
Copyright (c) 2017 Ahmed Kh. Zamil
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
using Esiur.Core;
using System.Text.Json.Serialization;
using System.ComponentModel.DataAnnotations.Schema;
#nullable enable
namespace Esiur.Resource;
public delegate bool QueryFilter<T>(T value);
public interface IResource : IDestructible
{
AsyncReply<bool> Trigger(ResourceTrigger trigger);
[NotMapped]
[JsonIgnore]
Instance? Instance { get; set; }
}

View File

@@ -0,0 +1,77 @@
/*
Copyright (c) 2017 Ahmed Kh. Zamil
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
using Esiur.Data;
using Esiur.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Esiur.Security.Permissions;
using Esiur.Security.Authority;
using Esiur.Data.Types;
namespace Esiur.Resource;
public interface IStore : IResource
{
AsyncReply<IResource> Get(string path);
AsyncReply<bool> Put(IResource resource, string path);
string Link(IResource resource);
//bool Record(IResource resource, string propertyName, object value, ulong? age, DateTime? dateTime);
bool Modify(IResource resource, PropertyDef propertyDef, object value, ulong? age, DateTime? dateTime);
AsyncReply<bool> Remove(IResource resource);
AsyncReply<bool> Remove(string path);
AsyncReply<bool> Move(IResource resource, string newPath);
//bool RemoveAttributes(IResource resource, string[] attributes = null);
//Structure GetAttributes(IResource resource, string[] attributes = null);
//bool SetAttributes(IResource resource, Structure attributes, bool clearAttributes = false);
//AsyncReply<bool> AddChild(IResource parent, IResource child);
//AsyncReply<bool> RemoveChild(IResource parent, IResource child);
//AsyncReply<bool> AddParent(IResource child, IResource parent);
//AsyncReply<bool> RemoveParent(IResource child, IResource parent);
AsyncBag<T> Children<T>(IResource resource, string name) where T : IResource;
AsyncBag<T> Parents<T>(IResource resource, string name) where T : IResource;
//AsyncReply<PropertyValue[]> GetPropertyRecord(IResource resource, string propertyName, ulong fromAge, ulong toAge);
//AsyncReply<PropertyValue[]> GetPropertyRecordByDate(IResource resource, string propertyName, DateTime fromDate, DateTime toDate);
//AsyncReply<KeyList<PropertyDef, PropertyValue[]>> GetRecord(IResource resource, ulong fromAge, ulong toAge);
// AsyncReply<KeyList<PropertyDef, PropertyValue[]>> GetRecordByDate(IResource resource, DateTime fromDate, DateTime toDate);
//AsyncReply<KeyList<PropertyDef, PropertyValue[]>> GetRecord(IResource resource, DateTime fromDate, DateTime toDate);
}

View File

@@ -0,0 +1,9 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource;
public class IgnoreAttribute : Attribute
{
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource;
[AttributeUsage(AttributeTargets.Class)]
public class ImportAttribute : Attribute
{
public ImportAttribute(params string[] urls)
{
}
}

View File

@@ -0,0 +1,846 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Esiur.Data;
using System.Runtime.CompilerServices;
using System.Reflection;
using Esiur.Misc;
using Esiur.Security.Permissions;
using Esiur.Security.Authority;
using Esiur.Proxy;
using Esiur.Core;
using System.Text.Json;
using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection.Emit;
using Esiur.Data.Types;
using Esiur.Protocol;
namespace Esiur.Resource;
[NotMapped]
public class Instance
{
string name;
// public int IntVal { get; set; }
WeakReference<IResource> resource;
IStore store;
TypeDef definition;
AutoList<IPermissionsManager, Instance> managers;
public event PropertyModifiedEvent PropertyModified;
public event EventOccurredEvent EventOccurred;
public event CustomEventOccurredEvent CustomEventOccurred;
public event ResourceDestroyedEvent Destroyed;
bool loading = false;
//KeyList<string, object> attributes;
List<ulong?> ages = new();
List<DateTime?> modificationDates = new();
private ulong instanceAge;
private byte hops;
private DateTime instanceModificationDate;
uint id;
public KeyList<string, object> Variables { get; } = new KeyList<string, object>();
/// <summary>
/// Instance attributes are custom properties associated with the instance, a place to store information by IStore.
/// </summary>
//public KeyList<string, object> Attributes
//{
// get
// {
// return attributes;
// }
//}
public override string ToString()
{
return name + " (" + Link + ")";
}
public bool RemoveAttributes(string[] attributes = null)
{
return false;
/*
IResource res;
if (!resource.TryGetTarget(out res))
return false;
return store.RemoveAttributes(res, attributes);
*/
/*
if (attributes == null)
this.attributes.Clear();
else
{
foreach (var attr in attributes)
this.attributes.Remove(attr);
}
return true;
*/
}
public Map<string, object> GetAttributes(string[] attributes = null)
{
// @TODO
var rt = new Map<string, object>();
if (attributes != null)
{
for (var i = 0; i < attributes.Length; i++)
{
var at = definition.GetAttributeDef(attributes[i]);
if (at != null)
{
}
}
}
return rt;
}
public bool SetAttributes(Map<string, object> attributes, bool clearAttributes = false)
{
// @ TODO
IResource res;
if (resource.TryGetTarget(out res))
{
foreach (var kv in attributes)
{
var at = definition.GetAttributeDef(kv.Key);
if (at != null)
if (at.PropertyInfo.CanWrite)
at.PropertyInfo.SetValue(res, RuntimeCaster.Cast(kv.Value, at.PropertyInfo.PropertyType));
}
}
return true;
/*
try
{
if (clearAttributes)
this.attributes.Clear();
foreach (var attr in attributes)
if (attr.Key == "name")
this.name = attr.Value as string;
else if (attr.Key == "managers")
{
this.managers.Clear();
var mngrs = attr.Value as object[];
foreach (var mngr in mngrs)
{
var m = mngr as Structure;
var type = Type.GetType(m["type"] as string);
if (Codec.ImplementsInterface(type, typeof(IPermissionsManager)))
{
var settings = m["settings"] as Structure;
var manager = Activator.CreateInstance(type) as IPermissionsManager;
IResource res;
if (this.resource.TryGetTarget(out res))
{
manager.Initialize(settings, res);
this.managers.Add(manager);
}
}
else
return false;
}
}
else
{
this.attributes[attr.Key] = attr.Value;
}
}
catch
{
return false;
}
return true;
*/
}
/*
public Structure GetAttributes()
{
var st = new Structure();
foreach (var a in attributes.Keys)
st[a] = attributes[a];
st["name"] = name;
var mngrs = new List<Structure>();
foreach (var manager in managers)
{
var mngr = new Structure();
mngr["settings"] = manager.Settings;
mngr["type"] = manager.GetType().FullName;
mngrs.Add(mngr);
}
st["managers"] = mngrs;
return st;
}*/
/// <summary>
/// Get the age of a given property index.
/// </summary>
/// <param name="index">Zero-based property index.</param>
/// <returns>Age.</returns>
public ulong? GetAge(byte index)
{
if (index < ages.Count)
return ages[index];
else
return 0;
}
/// <summary>
/// Set the age of a property.
/// </summary>
/// <param name="index">Zero-based property index.</param>
/// <param name="value">Age.</param>
public void SetAge(byte index, ulong? value)
{
if (index < ages.Count)
{
ages[index] = value;
if (value > instanceAge)
instanceAge = (ulong)value;
}
}
/// <summary>
/// Set the modification date of a property.
/// </summary>
/// <param name="index">Zero-based property index.</param>
/// <param name="value">Modification date.</param>
public void SetModificationDate(byte index, DateTime? value)
{
if (index < modificationDates.Count)
{
modificationDates[index] = value;
if (value > instanceModificationDate)
instanceModificationDate = (DateTime)value;
}
}
/// <summary>
/// Get modification date of a specific property.
/// </summary>
/// <param name="index">Zero-based property index</param>
/// <returns>Modification date.</returns>
public DateTime? GetModificationDate(byte index)
{
if (index < modificationDates.Count)
return modificationDates[index];
else
return DateTime.MinValue;
}
/// <summary>
/// Load property value (used by stores)
/// </summary>
/// <param name="name">Property name</param>
/// <param name="age">Property age</param>
/// <param name="value">Property value</param>
/// <returns></returns>
public bool LoadProperty(string name, ulong? age, DateTime? modificationDate, object value)
{
IResource res;
if (!resource.TryGetTarget(out res))
return false;
var pt = definition.GetPropertyDefByName(name);
if (pt == null)
return false;
/*
#if NETSTANDARD
var pi = resource.GetType().GetTypeInfo().GetProperty(name, new[] { resource.GetType() });
#else
var pi = resource.GetType().GetProperty(pt.Name);
#endif
*/
if (pt.PropertyInfo.PropertyType.IsGenericType
&& pt.PropertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(PropertyContext<>))
return false;
if (pt.PropertyInfo.CanWrite)
{
try
{
loading = true;
pt.PropertyInfo.SetValue(res, RuntimeCaster.Cast(value, pt.PropertyInfo.PropertyType));
}
catch (Exception ex)
{
//Console.WriteLine(resource.ToString() + " " + name);
Global.Log(ex);
}
loading = false;
}
SetAge(pt.Index, age);
SetModificationDate(pt.Index, modificationDate);
return true;
}
/// <summary>
/// Age of the instance, incremented by 1 in every modification.
/// </summary>
public ulong Age
{
get { return instanceAge; }
internal set { instanceAge = value; }
}
/// <summary>
/// Number of nodes to reach the original resource.
/// </summary>
public byte Hops
{
get { return hops; }
internal set { hops = value; }
}
/// <summary>
/// Last modification date.
/// </summary>
public DateTime? ModificationDate
{
get
{
return instanceModificationDate;
}
}
/// <summary>
/// Instance Id.
/// </summary>
public uint Id
{
get { return id; }
}
/// <summary>
/// Import properties from bytes array.
/// </summary>
/// <param name="properties"></param>
/// <returns></returns>
public bool Deserialize(PropertyValue[] properties)
{
for (byte i = 0; i < properties.Length; i++)
{
var pt = this.definition.GetPropertyDefByIndex(i);
if (pt != null)
{
var pv = properties[i];
LoadProperty(pt.Name, pv.Age, pv.Date, pv.Value);
}
}
return true;
}
public string ToJson()
{
IResource res;
if (resource.TryGetTarget(out res))
return JsonSerializer.Serialize(res, Global.SerializeOptions);
else
return null;
}
/// <summary>
/// Export all properties with ResourceProperty attributed as bytes array.
/// </summary>
/// <returns></returns>
public PropertyValue[] Serialize()
{
IResource res;
if (!resource.TryGetTarget(out res))
throw new Exception("Resource no longer available.");
if (res is IDynamicResource dynamicResource)
return dynamicResource.SerializeResource();
var props = new List<PropertyValue>();
foreach (var pt in definition.Properties)
{
var rt = pt.PropertyInfo.GetValue(res, null);
props.Add(new PropertyValue(rt, ages[pt.Index], modificationDates[pt.Index]));
}
return props.ToArray();
}
/// <summary>
/// Export all properties with ResourceProperty attributed as bytes array after a specific age.
/// </summary>
/// <returns></returns>
public Map<byte, PropertyValue> SerializeAfter(ulong age = 0)
{
IResource res;
if (!resource.TryGetTarget(out res))
throw new Exception("Resource no longer available.");
if (res is IDynamicResource dynamicResource)
return dynamicResource.SerializeResourceAfter(age);
var props = new Map<byte, PropertyValue>();
foreach (var pt in definition.Properties)
{
if (res.Instance.GetAge(pt.Index) > age)
{
var rt = pt.PropertyInfo.GetValue(res, null);
props.Add(pt.Index,
new PropertyValue(rt,
ages[pt.Index],
modificationDates[pt.Index]));
}
}
return props;
}
/// <summary>
/// If True, the instance can be stored to disk.
/// </summary>
/// <returns></returns>
public bool IsStorable()
{
#if NETSTANDARD
var attrs = resource.GetType().GetTypeInfo().GetCustomAttributes(typeof(Storable), true).ToArray();
#else
var attrs = resource.GetType().GetCustomAttributes(typeof(Storable), true);
#endif
return attrs.Length > 0;
}
internal void EmitModification(PropertyDef pt, object value)
{
IResource res;
if (this.resource.TryGetTarget(out res))
{
instanceAge++;
var now = DateTime.UtcNow;
ages[pt.Index] = instanceAge;
modificationDates[pt.Index] = now;
//if (pt.HasHistory)
//{
// store.Record(res, pt.Name, value, ages[pt.Index], now);
//}
//else //if (pt.Storage == StorageMode.Recordable)
//{
store.Modify(res, pt, value, ages[pt.Index], now);
//}
//ResourceModified?.Invoke(res, pt.Name, value);
PropertyModified?.Invoke(new PropertyModificationInfo(res, pt, value, instanceAge));
}
}
/// <summary>
/// Notify listeners that a property was modified.
/// </summary>
/// <param name="propertyName"></param>
/// <param name="newValue"></param>
/// <param name="oldValue"></param>
public void Modified([CallerMemberName] string propertyName = "")
{
if (loading)
return;
object value;
if (TryGetPropertyValue(propertyName, out value))
{
var pt = definition.GetPropertyDefByName(propertyName);
EmitModification(pt, value);
}
}
// internal void EmitResourceEvent(string name, string[] users, EpConnection[] connections, object[] args)
internal void EmitCustomResourceEvent(object issuer, Func<Session, bool> receivers, EventDef eventDef, object value)
{
IResource res;
if (this.resource.TryGetTarget(out res))
{
CustomEventOccurred?.Invoke(new CustomEventOccurredInfo(res, eventDef, receivers, issuer, value));
}
}
internal void EmitResourceEvent(EventDef eventDef, object value)
{
IResource res;
if (this.resource.TryGetTarget(out res))
{
EventOccurred?.Invoke(new EventOccurredInfo(res, eventDef, value));
}
}
internal void EmitResourceEventByIndex(byte eventIndex, object value)
{
IResource res;
if (this.resource.TryGetTarget(out res))
{
var eventDef = definition.GetEventDefByIndex(eventIndex);
EventOccurred?.Invoke(new EventOccurredInfo(res, eventDef, value));
}
}
internal void EmitCustomResourceEventByIndex(object issuer, Func<Session, bool> receivers, byte eventIndex, object value)
{
IResource res;
if (this.resource.TryGetTarget(out res))
{
var eventDef = definition.GetEventDefByIndex(eventIndex);
CustomEventOccurred?.Invoke(new CustomEventOccurredInfo(res, eventDef, receivers, issuer, value));
}
}
/// <summary>
/// Get the value of a given property by name.
/// </summary>
/// <param name="name">Property name</param>
/// <param name="value">Output value</param>
/// <returns>True, if the resource has the property.</returns>
public bool TryGetPropertyValue(string name, out object value)
{
var pt = definition.GetPropertyDefByName(name);
IResource res;
if (resource.TryGetTarget(out res))
{
if (res is IDynamicResource dynamicResource)
{
value = dynamicResource.GetResourceProperty(pt.Index);
return true;
}
else if (pt != null && pt.PropertyInfo != null)
{
value = pt.PropertyInfo.GetValue(res, null);
return true;
}
}
value = null;
return false;
}
public object GetPropertyValueOrDefault(string name, object defaultValue = null)
{
object value;
if (TryGetPropertyValue(name, out value))
return value;
else
return defaultValue;
}
/// <summary>
/// Store responsible for creating and keeping the resource.
/// </summary>
public IStore Store
{
get { return store; }
}
public bool IsDestroyed { get; private set; }
/// <summary>
/// The unique and permanent link to the resource.
/// </summary>
public string Link
{
get
{
IResource res;
if (this.resource.TryGetTarget(out res))
{
if (res == res.Instance.store)
return name; // root store
else
return store.Instance.name + "/" + store.Link(res);
}
else
return null;
}
}
public AsyncBag<T> Children<T>(string name = null) where T : IResource
{
IResource res;
if (this.resource.TryGetTarget(out res))
{
return store.Children<T>(res, name);
}
else
return new AsyncBag<T>(null);
}
public AsyncBag<T> Parents<T>(string name = null) where T : IResource
{
IResource res;
if (this.resource.TryGetTarget(out res))
{
return store.Parents<T>(res, name);
}
else
return new AsyncBag<T>(default(T[]));
}
/// <summary>
/// Instance name.
/// </summary>
public string Name
{
get { return name; }
set { name = value; }
}
/// <summary>
/// Resource managed by this instance.
/// </summary>
public IResource Resource
{
get
{
IResource res;
if (this.resource.TryGetTarget(out res))
{
return res;
}
else
return null;
}
}
/// <summary>
/// Resource TypeDef describes the properties, functions and events of the resource.
/// </summary>
public TypeDef Definition
{
get { return definition; }
}
/// <summary>
/// Check for permission.
/// </summary>
/// <param name="session">Caller sessions.</param>
/// <param name="action">Action type</param>
/// <param name="member">Function, property or event to check for permission.</param>
/// <param name="inquirer">Permission inquirer.</param>
/// <returns>Ruling.</returns>
public Ruling Applicable(Session session, ActionType action, MemberDef member, object inquirer = null)
{
IResource res;
if (this.resource.TryGetTarget(out res))
{
//return store.Applicable(res, session, action, member, inquirer);
foreach (IPermissionsManager manager in managers)
{
var r = manager.Applicable(res, session, action, member, inquirer);
if (r != Ruling.DontCare)
return r;
}
}
return Ruling.DontCare;
}
/// <summary>
/// Execution managers.
/// </summary>
public AutoList<IPermissionsManager, Instance> Managers => managers;
public readonly Warehouse Warehouse;
/// <summary>
/// Create new instance.
/// </summary>
/// <param name="id">Instance Id.</param>
/// <param name="name">Name of the instance.</param>
/// <param name="resource">Resource to manage.</param>
/// <param name="store">Store responsible for the resource.</param>
public Instance(Warehouse warehouse, uint id, string name, IResource resource, IStore store, ulong age = 0)
{
this.Warehouse = warehouse;
this.store = store;
this.resource = new WeakReference<IResource>(resource);
this.id = id;
this.name = name ?? "";
this.instanceAge = age;
//this.attributes = new KeyList<string, object>(this);
//children = new AutoList<IResource, Instance>(this);
//parents = new AutoList<IResource, Instance>(this);
managers = new AutoList<IPermissionsManager, Instance>(this);
//children.OnAdd += Children_OnAdd;
//children.OnRemoved += Children_OnRemoved;
//parents.OnAdd += Parents_OnAdd;
//parents.OnRemoved += Parents_OnRemoved;
resource.OnDestroy += Resource_OnDestroy;
if (resource is IDynamicResource dynamicResource)
{
this.definition = dynamicResource.ResourceDefinition;
}
else
{
this.definition = Warehouse.GetTypeDefByType(resource.GetType());
}
// set ages
for (byte i = 0; i < definition.Properties.Length; i++)
{
ages.Add(0);
modificationDates.Add(DateTime.MinValue);
}
// connect events
if (!(resource is EpResource))
{
Type t = ResourceProxy.GetBaseType(resource);
var events = t.GetTypeInfo().GetEvents(BindingFlags.Public | BindingFlags.Instance);
var emitEventByIndexMethod = GetType().GetMethod("EmitResourceEventByIndex", BindingFlags.Instance | BindingFlags.NonPublic);
var emitCustomEventByIndexMethod = GetType().GetMethod("EmitCustomResourceEventByIndex", BindingFlags.Instance | BindingFlags.NonPublic);
foreach (var evt in definition.Events)
{
if (evt.EventInfo == null)
continue;
var eventGenericType = evt.EventInfo.EventHandlerType.GetGenericTypeDefinition();
if (eventGenericType == typeof(ResourceEventHandler<>))
{
var dm = new DynamicMethod("_", null,
new Type[] { typeof(Instance), evt.EventInfo.EventHandlerType.GenericTypeArguments[0] },
typeof(Instance).Module, true);
var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldc_I4, (int)evt.Index);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Box, evt.EventInfo.EventHandlerType.GenericTypeArguments[0]);
il.Emit(OpCodes.Callvirt, emitEventByIndexMethod);
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ret);
var proxyDelegate = dm.CreateDelegate(evt.EventInfo.EventHandlerType, this);
//ResourceEventHandler<object> proxyDelegate = new ResourceEventHandler<object>((args) => EmitResourceEvent(evt, args));
evt.EventInfo.AddEventHandler(resource, proxyDelegate);
}
else if (eventGenericType == typeof(CustomResourceEventHandler<>))
{
var dm = new DynamicMethod("_", null,
new Type[] { typeof(Instance), typeof(object), typeof(Func<Session, bool>),
evt.EventInfo.EventHandlerType.GenericTypeArguments[0] },
typeof(Instance).Module, true);
var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Ldc_I4, (int)evt.Index);
il.Emit(OpCodes.Ldarg_3);
il.Emit(OpCodes.Box, evt.EventInfo.EventHandlerType.GenericTypeArguments[0]);
il.Emit(OpCodes.Callvirt, emitCustomEventByIndexMethod);
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ret);
var proxyDelegate = dm.CreateDelegate(evt.EventInfo.EventHandlerType, this);
evt.EventInfo.AddEventHandler(resource, proxyDelegate);
}
}
}
}
private void Resource_OnDestroy(object sender)
{
IsDestroyed = true;
Destroyed?.Invoke((IResource)sender);
}
}

View File

@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Text;
using Esiur.Data.Types;
using Esiur.Resource;
namespace Esiur.Resource;
public struct PropertyModificationInfo
{
public readonly IResource Resource;
public readonly PropertyDef PropertyDef;
public string Name => PropertyDef.Name;
public readonly ulong Age;
public object Value;
public PropertyModificationInfo(IResource resource, PropertyDef propertyDef, object value, ulong age)
{
Resource = resource;
PropertyDef = propertyDef;
Age = age;
Value = value;
}
}

View File

@@ -0,0 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource;
public delegate void PropertyModifiedEvent(PropertyModificationInfo data);

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource
{
public enum PropertyPermission : byte
{
Read = 1,
Write,
ReadWrite,
}
}

View File

@@ -0,0 +1,60 @@
/*
Copyright (c) 2017 Ahmed Kh. Zamil
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.Text;
using Esiur.Core;
namespace Esiur.Resource;
public class Resource : IResource
{
public Instance Instance { get; set; }
public event DestroyedEvent OnDestroy;
public virtual void Destroy()
{
OnDestroy?.Invoke(this);
}
public virtual AsyncReply<bool> Trigger(ResourceTrigger trigger)
{
if (trigger == ResourceTrigger.Initialize)
return new AsyncReply<bool>(this.Create());
else
return new AsyncReply<bool>(true);
}
protected virtual bool Create()
{
return true;
}
~Resource()
{
Destroy();
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource;
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class ResourceAttribute : Attribute
{
public ResourceAttribute()
{
}
}

View File

@@ -0,0 +1,8 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource
{
public delegate void ResourceDestroyedEvent(IResource resource);
}

View File

@@ -0,0 +1,45 @@
/*
Copyright (c) 2017 Ahmed Kh. Zamil
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
using Esiur.Data;
using Esiur.Core;
using Esiur.Security.Authority;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Esiur.Resource;
//public delegate R DCovariant<out R>();
public delegate void ResourceEventHandler<in T>(T argument);//where T : class;
// public delegate void CustomUsersEventHanlder(string[] usernames, params object[] args);
//public delegate void CustomReceiversEventHanlder(EpConnection[] connections, params object[] args);
//public delegate void CustomInquirerEventHanlder(object inquirer, params object[] args);
public delegate void CustomResourceEventHandler<in T>(object issuer, Func<Session, bool> receivers, T argument);// object issuer, Session[] receivers, params object[] args);
// public delegate void CustomReceiversEventHanlder(string[] usernames, EpConnection[] connections, params object[] args);

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
namespace Esiur.Resource;
public class ResourceQuery : IQueryable<IResource>
{
public Type ElementType => throw new NotImplementedException();
public Expression Expression => throw new NotImplementedException();
public IQueryProvider Provider => throw new NotImplementedException();
public IEnumerator<IResource> GetEnumerator()
{
throw new NotImplementedException();
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}

View File

@@ -0,0 +1,41 @@
/*
Copyright (c) 2017 Ahmed Kh. Zamil
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Esiur.Resource;
public enum ResourceTrigger : int
{
Open = 0,
Initialize,
Terminate,
Configure,
SystemInitialized,
SystemTerminated,
SystemReload,
}

View File

@@ -0,0 +1,71 @@
/*
Copyright (c) 2017 Ahmed Kh. Zamil
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
using Esiur.Data;
using Esiur.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Esiur.Resource;
[AttributeUsage(AttributeTargets.All)]
public class Storable : global::System.Attribute
{
public delegate object SerializerFunction(object value);
public delegate object DeserializerFunction(object data);
SerializerFunction serializer;
DeserializerFunction deserializer;
Tru dataType;
public Storable()
{
//dataType = = DataType.Void;
}
public DeserializerFunction Deserializer
{
get { return deserializer; }
}
public SerializerFunction Serializer
{
get { return serializer; }
}
public Storable(Tru type)
{
this.dataType = type;
}
public Storable(Tru type, SerializerFunction serializer, DeserializerFunction deserializer)
{
this.dataType = type;
this.serializer = serializer;
this.deserializer = deserializer;
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource;
[AttributeUsage(AttributeTargets.Property)]
public class StorageAttribute : Attribute
{
public StorageMode Mode { get; set; }
public StorageAttribute(StorageMode mode)
{
Mode = mode;
}
}

View File

@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource;
public enum StorageMode : byte
{
NonVolatile,
Volatile,
History
}

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Text;
using Esiur.Core;
using Esiur.Data;
using Esiur.Data.Types;
namespace Esiur.Resource;
public abstract class Store<T> : IStore where T : IResource
{
public Instance Instance { get; set; }
public event DestroyedEvent OnDestroy;
public abstract AsyncBag<T1> Children<T1>(IResource resource, string name) where T1 : IResource;
public virtual void Destroy()
{
OnDestroy?.Invoke(this);
}
public abstract AsyncReply<IResource> Get(string path);
public abstract AsyncReply<KeyList<PropertyDef, PropertyValue[]>> GetRecord(IResource resource, DateTime fromDate, DateTime toDate);
public abstract string Link(IResource resource);
public abstract bool Modify(IResource resource, PropertyDef propertyDef, object value, ulong? age, DateTime? dateTime);
public abstract AsyncReply<bool> Put(IResource resource, string path);
public abstract bool Record(IResource resource, string propertyName, object value, ulong? age, DateTime? dateTime);
public abstract AsyncReply<bool> Trigger(ResourceTrigger trigger);
//public async AsyncReply<T> New(string name = null, object attributes = null, object properties = null)
//{
// var resource = await Warehouse.New<T>(name, this, null, null, attributes, properties);
// resource.Instance.Managers.AddRange(this.Instance.Managers.ToArray());
// return resource;
//}
public abstract AsyncReply<bool> Remove(IResource resource);
public abstract AsyncReply<bool> Remove(string path);
public abstract AsyncReply<bool> Move(IResource resource, string newPath);
public abstract AsyncBag<T1> Parents<T1>(IResource resource, string name) where T1 : IResource;
}

View File

@@ -0,0 +1,40 @@
/*
Copyright (c) 2021 Ahmed Kh. Zamil
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Esiur.Resource;
[AttributeUsage(AttributeTargets.Event)]
public class SubscribableAttribute : System.Attribute
{
public SubscribableAttribute()
{
}
}

View File

@@ -0,0 +1,19 @@
using Esiur.Data;
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum)]
public class TypeIdAttribute : Attribute
{
public Uuid Id { get; private set; }
public TypeIdAttribute(string id)
{
var data = DC.FromHex(id, null);
Id = new Uuid(data);
}
}
}

View File

@@ -0,0 +1,668 @@
/*
Copyright (c) 2017 Ahmed Kh. Zamil
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
using Esiur.Core;
using Esiur.Data;
using Esiur.Data.Types;
using Esiur.Misc;
using Esiur.Net.Packets;
using Esiur.Protocol;
using Esiur.Proxy;
using Esiur.Security.Permissions;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Esiur.Resource;
// Central Resources Manager
public class Warehouse
{
public static Warehouse Default = new Warehouse();
//static byte prefixCounter;
//static AutoList<IStore, Instance> stores = new AutoList<IStore, Instance>(null);
ConcurrentDictionary<uint, WeakReference<IResource>> resources = new ConcurrentDictionary<uint, WeakReference<IResource>>();
ConcurrentDictionary<IStore, List<WeakReference<IResource>>> stores = new ConcurrentDictionary<IStore, List<WeakReference<IResource>>>();
uint resourceCounter = 0;
KeyList<TypeDefKind, KeyList<Uuid, TypeDef>> typeDefs
= new KeyList<TypeDefKind, KeyList<Uuid, TypeDef>>()
{
[TypeDefKind.Resource] = new KeyList<Uuid, TypeDef>(),
[TypeDefKind.Record] = new KeyList<Uuid, TypeDef>(),
[TypeDefKind.Enum] = new KeyList<Uuid, TypeDef>(),
};
bool warehouseIsOpen = false;
public delegate void StoreEvent(IStore store);
public event StoreEvent StoreConnected;
public event StoreEvent StoreDisconnected;
public delegate AsyncReply<IStore> ProtocolInstance(string name, object properties);
public KeyList<string, ProtocolInstance> Protocols { get; } = new KeyList<string, ProtocolInstance>();
private Regex urlRegex = new Regex(@"^(?:([\S]*)://([^/]*)/?)");
public Warehouse()
{
Protocols.Add("EP",
async (name, attributes)
=> await New<EpConnection>(name, null, attributes));
new TypeDef(typeof(EpAuthPacketIAuthHeader), this);
new TypeDef(typeof(EpAuthPacketIAuthDestination), this);
new TypeDef(typeof(EpAuthPacketIAuthFormat), this);
}
/// <summary>
/// Get a store by its name.
/// </summary>
/// <param name="name">Store instance name</param>
/// <returns></returns>
public IStore GetStore(string name)
{
foreach (var s in stores)
if (s.Key.Instance.Name == name)
return s.Key;
return null;
}
public WeakReference<IResource>[] Resources => resources.Values.ToArray();
/// <summary>
/// Get a resource by instance Id.
/// </summary>
/// <param name="id">Instance Id</param>
/// <returns></returns>
public AsyncReply<IResource> GetById(uint id)
{
if (resources.ContainsKey(id))
{
IResource r;
if (resources[id].TryGetTarget(out r))
return new AsyncReply<IResource>(r);
else
return new AsyncReply<IResource>(null);
}
else
return new AsyncReply<IResource>(null);
}
void LoadGenerated()
{
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
var generatedType = assembly.GetType("Esiur.Generated");
if (generatedType != null)
{
var resourceTypes = (Type[])generatedType.GetProperty("Resources").GetValue(null);
foreach (var t in resourceTypes)
{
RegisterTypeDef(new TypeDef(t));
}
var recordTypes = (Type[])generatedType.GetProperty("Records").GetValue(null);
foreach (var t in recordTypes)
{
RegisterTypeDef(new TypeDef(t));
}
var enumsTypes = (Type[])generatedType.GetProperty("Enums").GetValue(null);
foreach (var t in enumsTypes)
{
RegisterTypeDef(new TypeDef(t));
}
}
}
}
/// <summary>
/// Open the warehouse.
/// This function issues the initialize trigger to all stores and resources.
/// </summary>
/// <returns>True, if no problem occurred.</returns>
public async AsyncReply<bool> Open()
{
if (warehouseIsOpen)
return false;
// Load generated models
LoadGenerated();
warehouseIsOpen = true;
var resSnap = resources.Select(x =>
{
IResource r;
if (x.Value.TryGetTarget(out r))
return r;
else
return null;
}).Where(r => r != null).ToArray();
foreach (var r in resSnap)
{
//IResource r;
//if (rk.Value.TryGetTarget(out r))
//{
var rt = await r.Trigger(ResourceTrigger.Initialize);
//if (!rt)
// return false;
if (!rt)
{
Global.Log("Warehouse", LogType.Warning, $"Resource failed at Initialize {r.Instance.Name} [{r.Instance.Definition.Name}]");
}
//}
}
foreach (var r in resSnap)
{
var rt = await r.Trigger(ResourceTrigger.SystemInitialized);
if (!rt)
{
Global.Log("Warehouse", LogType.Warning, $"Resource failed at SystemInitialized {r.Instance.Name} [{r.Instance.Definition.Name}]");
}
}
return true;
}
/// <summary>
/// Close the warehouse.
/// This function issues terminate trigger to all resources and stores.
/// </summary>
/// <returns>True, if no problem occurred.</returns>
public AsyncReply<bool> Close()
{
var bag = new AsyncBag<bool>();
foreach (var resource in resources.Values)
{
IResource r;
if (resource.TryGetTarget(out r))
{
if (!(r is IStore))
bag.Add(r.Trigger(ResourceTrigger.Terminate));
}
}
foreach (var store in stores)
bag.Add(store.Key.Trigger(ResourceTrigger.Terminate));
foreach (var resource in resources.Values)
{
IResource r;
if (resource.TryGetTarget(out r))
{
if (!(r is IStore))
bag.Add(r.Trigger(ResourceTrigger.SystemTerminated));
}
}
foreach (var store in stores)
bag.Add(store.Key.Trigger(ResourceTrigger.SystemTerminated));
bag.Seal();
var rt = new AsyncReply<bool>();
bag.Then((x) =>
{
foreach (var b in x)
if (!b)
{
rt.Trigger(false);
return;
}
rt.Trigger(true);
});
return rt;
}
public async AsyncReply<IResource> Query(string path)
{
var p = path.Trim().TrimStart('/').Split('/');
foreach (var store in stores.Keys)
{
if (p[0] == store.Instance.Name)
{
if (p.Length == 1)
return store;
var res = await store.Get(String.Join("/", p.Skip(1).ToArray()));
if (res != null)
return res;
return null;
}
}
return null;
}
/// <summary>
/// Get a resource by its path.
/// Resource path is sperated by '/' character, e.g. "system/http".
/// </summary>
/// <param name="path"></param>
/// <returns>Resource instance.</returns>
public async AsyncReply<T> Get<T>(string path, object attributes = null, IResource parent = null, IPermissionsManager manager = null)
where T : IResource
{
if (urlRegex.IsMatch(path))
{
var url = urlRegex.Split(path);
if (Protocols.ContainsKey(url[1]))
{
if (!warehouseIsOpen)
await Open();
var handler = Protocols[url[1]];
var store = await handler(url[2], attributes);
try
{
if (url[3].Length > 0 && url[3] != "")
return (T)await store.Get(url[3]);
else
return (T)store;
}
catch (Exception ex)
{
Remove(store);
throw ex;
}
}
}
var res = await Query(path);
if (res == null)
return default(T);
else
return (T)res;
}
/// <summary>
/// Put a resource in the warehouse.
/// </summary>
/// <param name="name">Resource name.</param>
/// <param name="resource">Resource instance.</param>
/// <param name="store">IStore that manages the resource. Can be null if the resource is a store.</param>
/// <param name="parent">Parent resource. if not presented the store becomes the parent for the resource.</param>
public async AsyncReply<T> Put<T>(string path, T resource, ulong age = 0, IPermissionsManager manager = null, object attributes = null) where T : IResource
{
if (resource.Instance != null)
throw new Exception("Resource already initialized.");
if (string.IsNullOrEmpty(path))
throw new ArgumentNullException("Invalid path.");
var location = path.TrimStart('/').Split('/');
IStore store = null;
//IResource parent = null;
var instanceName = location.Last();
if (location.Length == 1)
{
if (!(resource is IStore))
throw new Exception("Resource is not a store, root level path is not allowed.");
store = (IStore)resource;
}
else
{
// get parent
var parent = await Get<IResource>(string.Join("/", location.Take(location.Length - 1)));
if (parent == null)
throw new Exception("Can't find parent");
store = parent.Instance.Store;// GetStore(location[0]);
//if (store == null)
// throw new Exception("Store not found.");
}
var resourceReference = new WeakReference<IResource>(resource);
resource.Instance = new Instance(this, resourceCounter++, instanceName, resource, store, age);
if (attributes != null)
if (attributes is Map<string, object> attrs)
resource.Instance.SetAttributes(attrs);
else
resource.Instance.SetAttributes(Map<string, object>.FromObject(attributes));
try
{
if (resource is IStore)
stores.TryAdd(resource as IStore, new List<WeakReference<IResource>>());
else if ((IResource)resource != store)
{
if (!await store.Put(resource, string.Join("/", location.Skip(1).ToArray())))
throw new Exception("Store failed to put the resource.");
}
var t = resource.GetType();
Global.Counters["T-" + t.Namespace + "." + t.Name]++;
resources.TryAdd(resource.Instance.Id, resourceReference);
if (warehouseIsOpen)
{
await resource.Trigger(ResourceTrigger.Initialize);
if (resource is IStore)
await resource.Trigger(ResourceTrigger.Open);
}
if (resource is IStore)
StoreConnected?.Invoke(resource as IStore);
}
catch (Exception ex)
{
Remove(resource);
throw ex;
}
return resource;
}
public T Create<T>(object properties = null)
{
return (T)Create(typeof(T), properties);
}
public IResource Create(Type type, object properties = null)
{
type = ResourceProxy.GetProxy(type);
var res = Activator.CreateInstance(type) as IResource;
if (properties != null)
{
if (properties is Map<byte, object> map)
{
var typeDef = GetTypeDefByType(type);
foreach (var kvp in map)
typeDef.GetPropertyDefByIndex(kvp.Key).PropertyInfo.SetValue(res, kvp.Value);
}
else
{
var ps = Map<string, object>.FromObject(properties);
foreach (var p in ps)
{
var pi = type.GetProperty(p.Key, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (pi != null)
{
if (pi.CanWrite)
{
try
{
pi.SetValue(res, p.Value);
}
catch (Exception ex)
{
Global.Log(ex);
}
}
}
else
{
var fi = type.GetField(p.Key, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (fi != null)
{
try
{
fi.SetValue(res, p.Value);
}
catch (Exception ex)
{
Global.Log(ex);
}
}
}
}
}
}
return res;
}
public async AsyncReply<IResource> New(Type type, string path, IPermissionsManager manager = null, object attributes = null, object properties = null)
{
var res = Create(type, properties);
return await Put(path, res, 0, manager, attributes);
}
public async AsyncReply<T> New<T>(string path, IPermissionsManager manager = null, object attributes = null, object properties = null)
where T : IResource
{
return (T)(await New(typeof(T), path, manager, attributes, properties));
}
/// <summary>
/// Put a resource schema in the schemas warehouse.
/// </summary>
/// <param name="typeDef">Resource type definition.</param>
public void RegisterTypeDef(TypeDef typeDef)
{
if (typeDefs[typeDef.Kind].ContainsKey(typeDef.Id))
throw new Exception($"TypeDef with same class Id already exists. {typeDefs[typeDef.Kind][typeDef.Id].Name} -> {typeDef.Name}");
typeDefs[typeDef.Kind][typeDef.Id] = typeDef;
}
/// <summary>
/// Get a TypeDef by type from the warehouse. If not in the warehouse, a new TypeDef is created and added to the warehouse.
/// </summary>
/// <param name="type">.Net type.</param>
/// <returns>Resource TypeDef.</returns>
public TypeDef GetTypeDefByType(Type type)
{
if (!(type.IsClass || type.IsEnum))
return null;
var baseType = ResourceProxy.GetBaseType(type);
if (baseType == typeof(IResource)
|| baseType == typeof(IRecord))
return null;
TypeDefKind typeDefKind;
if (Codec.ImplementsInterface(type, typeof(IResource)))
typeDefKind = TypeDefKind.Resource;
else if (Codec.ImplementsInterface(type, typeof(IRecord)))
typeDefKind = TypeDefKind.Record;
else if (type.IsEnum)
typeDefKind = TypeDefKind.Enum;
else
return null;
var typeDef = typeDefs[typeDefKind].Values.FirstOrDefault(x => x.DefinedType == baseType);
if (typeDef != null)
return typeDef;
// create new TypeDef for type
typeDef = new TypeDef(baseType, this);
TypeDef.GetDependencies(typeDef, this);
return typeDef;
}
/// <summary>
/// Get a TypeDef by TypeId from the warehouse. If not in the warehouse, a new TypeDef is created and added to the warehouse.
/// </summary>
/// <param name="typeId">typeId.</param>
/// <returns>TypeDef.</returns>
public TypeDef GetTypeDefById(Uuid typeId, TypeDefKind? typeDefKind = null)
{
if (typeDefKind == null)
{
// look into resources
var typeDef = typeDefs[TypeDefKind.Resource][typeId];
if (typeDef != null)
return typeDef;
// look into records
typeDef = typeDefs[TypeDefKind.Record][typeId];
if (typeDef != null)
return typeDef;
// look into enums
typeDef = typeDefs[TypeDefKind.Enum][typeId];
return typeDef;
}
else
return typeDefs[typeDefKind.Value][typeId];
}
/// <summary>
/// Get a TypeDef by type name . If not in the warehouse, a new TypeDef is created and added to the warehouse.
/// </summary>
/// <param name="typeName">Class full name.</param>
/// <returns>TypeDef.</returns>
public TypeDef GetTypeDefByName(string typeName, TypeDefKind? typeDefKind = null)
{
if (typeDefKind == null)
{
// look into resources
var typeDef = typeDefs[TypeDefKind.Resource].Values.FirstOrDefault(x => x.Name == typeName);
if (typeDef != null)
return typeDef;
// look into records
typeDef = typeDefs[TypeDefKind.Record].Values.FirstOrDefault(x => x.Name == typeName);
if (typeDef != null)
return typeDef;
// look into enums
typeDef = typeDefs[TypeDefKind.Enum].Values.FirstOrDefault(x => x.Name == typeName);
return typeDef;
}
else
{
return typeDefs[typeDefKind.Value].Values.FirstOrDefault(x => x.Name == typeName);
}
}
public bool Remove(IResource resource)
{
if (resource.Instance == null)
return false;
WeakReference<IResource> resourceReference;
if (resources.ContainsKey(resource.Instance.Id))
resources.TryRemove(resource.Instance.Id, out resourceReference);
else
return false;
if (resource != resource.Instance.Store)
{
List<WeakReference<IResource>> list;
if (stores.TryGetValue(resource.Instance.Store, out list))
{
lock (((ICollection)list).SyncRoot)
list.Remove(resourceReference);
}
}
if (resource is IStore)
{
var store = resource as IStore;
List<WeakReference<IResource>> toBeRemoved;
stores.TryRemove(store, out toBeRemoved);
foreach (var o in toBeRemoved)
{
IResource r;
if (o.TryGetTarget(out r))
Remove(r);
}
StoreDisconnected?.Invoke(resource as IStore);
}
if (resource.Instance.Store != null)
resource.Instance.Store.Remove(resource);
resource.Destroy();
resource.Instance = null;
return true;
}
}