mirror of
https://github.com/esiur/esiur-dotnet.git
synced 2025-06-26 21:13:13 +00:00
Rename
This commit is contained in:
@ -1,47 +0,0 @@
|
||||
/*
|
||||
|
||||
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 Esiur.Data;
|
||||
using Esiur.Core;
|
||||
|
||||
namespace Esiur.Resource
|
||||
{
|
||||
public delegate bool QueryFilter<T>(T value);
|
||||
|
||||
public interface IResource : IDestructible
|
||||
{
|
||||
|
||||
AsyncReply<bool> Trigger(ResourceTrigger trigger);
|
||||
|
||||
Instance Instance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
/*
|
||||
|
||||
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.Resource.Template;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Esiur.Resource
|
||||
{
|
||||
public interface IStore:IResource
|
||||
{
|
||||
AsyncReply<IResource> Get(string path);//, Func<IResource, bool> filter = null);
|
||||
//AsyncReply<IResource> Retrieve(uint iid);
|
||||
bool Put(IResource resource);
|
||||
string Link(IResource resource);
|
||||
bool Record(IResource resource, string propertyName, object value, ulong age, DateTime dateTime);
|
||||
bool Modify(IResource resource, string propertyName, object value, ulong age, DateTime dateTime);
|
||||
bool Remove(IResource resource);
|
||||
|
||||
|
||||
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<PropertyTemplate, PropertyValue[]>> GetRecord(IResource resource, ulong fromAge, ulong toAge);
|
||||
// AsyncReply<KeyList<PropertyTemplate, PropertyValue[]>> GetRecordByDate(IResource resource, DateTime fromDate, DateTime toDate);
|
||||
|
||||
AsyncReply<KeyList<PropertyTemplate, PropertyValue[]>> GetRecord(IResource resource, DateTime fromDate, DateTime toDate);
|
||||
}
|
||||
}
|
@ -1,919 +0,0 @@
|
||||
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.Net.IIP;
|
||||
using Esiur.Misc;
|
||||
using Esiur.Security.Permissions;
|
||||
using Esiur.Resource.Template;
|
||||
using Esiur.Security.Authority;
|
||||
using Esiur.Proxy;
|
||||
using Esiur.Core;
|
||||
|
||||
namespace Esiur.Resource
|
||||
{
|
||||
public class Instance
|
||||
{
|
||||
string name;
|
||||
|
||||
//IQueryable<IResource> children;//
|
||||
//AutoList<IResource, Instance> children;// = new AutoList<IResource, Instance>();
|
||||
WeakReference<IResource> resource;
|
||||
IStore store;
|
||||
//AutoList<IResource, Instance> parents;// = new AutoList<IResource>();
|
||||
//bool inherit;
|
||||
ResourceTemplate template;
|
||||
|
||||
AutoList<IPermissionsManager, Instance> managers;// = new AutoList<IPermissionManager, Instance>();
|
||||
|
||||
|
||||
public delegate void ResourceModifiedEvent(IResource resource, string propertyName, object newValue);
|
||||
//public delegate void ResourceEventOccurredEvent(IResource resource, string eventName, string[] users, DistributedConnection[] connections, object[] args);
|
||||
|
||||
public delegate void ResourceEventOccurredEvent(IResource resource, object issuer, Session[] receivers, string eventName, object[] args);
|
||||
|
||||
public delegate void ResourceDestroyedEvent(IResource resource);
|
||||
|
||||
public event ResourceModifiedEvent ResourceModified;
|
||||
public event ResourceEventOccurredEvent ResourceEventOccurred;
|
||||
public event ResourceDestroyedEvent ResourceDestroyed;
|
||||
|
||||
bool loading = false;
|
||||
|
||||
KeyList<string, object> attributes;
|
||||
|
||||
List<ulong> ages = new List<ulong>();
|
||||
List<DateTime> modificationDates = new List<DateTime>();
|
||||
private ulong instanceAge;
|
||||
private DateTime instanceModificationDate;
|
||||
|
||||
uint id;
|
||||
|
||||
|
||||
/// <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)
|
||||
{
|
||||
if (attributes == null)
|
||||
this.attributes.Clear();
|
||||
else
|
||||
{
|
||||
foreach (var attr in attributes)
|
||||
this.attributes.Remove(attr);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Structure GetAttributes(string[] attributes = null)
|
||||
{
|
||||
var st = new Structure();
|
||||
|
||||
if (attributes == null)
|
||||
{
|
||||
var clone = this.attributes.Keys.ToList();
|
||||
clone.Add("managers");
|
||||
attributes = clone.ToArray();// this.attributes.Keys.ToList().Add("managers");
|
||||
}
|
||||
|
||||
foreach (var attr in attributes)
|
||||
{
|
||||
if (attr == "name")
|
||||
st["name"] = this.name;
|
||||
else if (attr == "managers")
|
||||
{
|
||||
var mngrs = new List<Structure>();
|
||||
|
||||
foreach (var manager in this.managers)
|
||||
mngrs.Add(new Structure()
|
||||
{
|
||||
["type"] = manager.GetType().FullName + "," + manager.GetType().GetTypeInfo().Assembly.GetName().Name,
|
||||
["settings"] = manager.Settings
|
||||
});
|
||||
|
||||
st["managers"] = mngrs.ToArray();
|
||||
}
|
||||
else if (attr == "parents")
|
||||
{
|
||||
//st["parents"] = parents.ToArray();
|
||||
}
|
||||
else if (attr == "children")
|
||||
{
|
||||
//st["children"] = children.ToArray();
|
||||
}
|
||||
else if (attr == "childrenCount")
|
||||
{
|
||||
//st["childrenCount"] = children.Count;
|
||||
}
|
||||
else if (attr == "type")
|
||||
{
|
||||
st["type"] = resource.GetType().FullName;
|
||||
}
|
||||
else
|
||||
st[attr] = this.attributes[attr];
|
||||
}
|
||||
|
||||
return st;
|
||||
}
|
||||
|
||||
public bool SetAttributes(Structure attributes, bool clearAttributes = false)
|
||||
{
|
||||
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 = 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 = 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 = template.GetPropertyTemplateByName(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.Info.PropertyType == typeof(DistributedPropertyContext))
|
||||
return false;
|
||||
|
||||
|
||||
if (pt.Info.CanWrite)
|
||||
{
|
||||
try
|
||||
{
|
||||
loading = true;
|
||||
|
||||
pt.Info.SetValue(res, DC.CastConvert(value, pt.Info.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>
|
||||
/// 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.template.GetPropertyTemplateByIndex(i);
|
||||
if (pt != null)
|
||||
{
|
||||
var pv = properties[i];
|
||||
LoadProperty(pt.Name, pv.Age, pv.Date, pv.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Export all properties with ResourceProperty attributed as bytes array.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public PropertyValue[] Serialize()
|
||||
{
|
||||
List<PropertyValue> props = new List<PropertyValue>();
|
||||
|
||||
foreach (var pt in template.Properties)
|
||||
{
|
||||
/*
|
||||
#if NETSTANDARD
|
||||
var pi = resource.GetType().GetTypeInfo().GetProperty(pt.Name);
|
||||
#else
|
||||
var pi = resource.GetType().GetProperty(pt.Name);
|
||||
#endif
|
||||
*/
|
||||
|
||||
//if (pt.Serilize)
|
||||
//{
|
||||
IResource res;
|
||||
if (resource.TryGetTarget(out res))
|
||||
{
|
||||
var rt = pt.Serilize ? pt.Info.GetValue(res, null) : null;
|
||||
props.Add(new PropertyValue(rt, ages[pt.Index], modificationDates[pt.Index]));
|
||||
}
|
||||
//}
|
||||
}
|
||||
|
||||
return props.ToArray();
|
||||
}
|
||||
/*
|
||||
public bool Deserialize(byte[] data, uint offset, uint length)
|
||||
{
|
||||
|
||||
var props = Codec.ParseValues(data, offset, length);
|
||||
Deserialize(props);
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
/*
|
||||
public byte[] Serialize(bool includeLength = false, DistributedConnection sender = null)
|
||||
{
|
||||
|
||||
//var bl = new BinaryList();
|
||||
List<object> props = new List<object>();
|
||||
|
||||
foreach (var pt in template.Properties)
|
||||
{
|
||||
|
||||
var pi = resource.GetType().GetProperty(pt.Name);
|
||||
|
||||
var rt = pi.GetValue(resource, null);
|
||||
|
||||
// this is a cool hack to let the property know the sender
|
||||
if (rt is Func<DistributedConnection, object>)
|
||||
rt = (rt as Func<DistributedConnection, object>)(sender);
|
||||
|
||||
props.Add(rt);
|
||||
|
||||
}
|
||||
|
||||
if (includeLength)
|
||||
{
|
||||
return Codec.Compose(props.ToArray(), false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var rt = Codec.Compose(props.ToArray(), false);
|
||||
return DC.Clip(rt, 4, (uint)(rt.Length - 4));
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] StorageSerialize()
|
||||
{
|
||||
|
||||
var props = new List<object>();
|
||||
|
||||
foreach(var pt in template.Properties)
|
||||
{
|
||||
if (!pt.Storable)
|
||||
continue;
|
||||
|
||||
var pi = resource.GetType().GetProperty(pt.Name);
|
||||
|
||||
if (!pi.CanWrite)
|
||||
continue;
|
||||
|
||||
var rt = pi.GetValue(resource, null);
|
||||
|
||||
props.Add(rt);
|
||||
|
||||
}
|
||||
|
||||
return Codec.Compose(props.ToArray(), false);
|
||||
}
|
||||
*/
|
||||
|
||||
/// <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(PropertyTemplate 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.Storage == StorageMode.NonVolatile)
|
||||
{
|
||||
store.Modify(res, pt.Name, value, ages[pt.Index], now);
|
||||
}
|
||||
else if (pt.Storage == StorageMode.Recordable)
|
||||
{
|
||||
store.Record(res, pt.Name, value, ages[pt.Index], now);
|
||||
}
|
||||
|
||||
ResourceModified?.Invoke(res, pt.Name, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 (GetPropertyValue(propertyName, out value))
|
||||
{
|
||||
var pt = template.GetPropertyTemplateByName(propertyName);
|
||||
EmitModification(pt, value);
|
||||
}
|
||||
}
|
||||
|
||||
// internal void EmitResourceEvent(string name, string[] users, DistributedConnection[] connections, object[] args)
|
||||
|
||||
internal void EmitResourceEvent(object issuer, Session[] receivers, string name, object[] args)
|
||||
{
|
||||
IResource res;
|
||||
if (this.resource.TryGetTarget(out res))
|
||||
{
|
||||
|
||||
ResourceEventOccurred?.Invoke(res, issuer, receivers, name, args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 GetPropertyValue(string name, out object value)
|
||||
{
|
||||
/*
|
||||
#if NETSTANDARD
|
||||
PropertyInfo pi = resource.GetType().GetTypeInfo().GetProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
|
||||
|
||||
#else
|
||||
PropertyInfo pi = resource.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
|
||||
#endif
|
||||
*/
|
||||
|
||||
var pt = template.GetPropertyTemplateByName(name);
|
||||
|
||||
if (pt != null && pt.Info != null)
|
||||
{
|
||||
/*
|
||||
#if NETSTANDARD
|
||||
object[] ca = pi.GetCustomAttributes(typeof(ResourceProperty), false).ToArray();
|
||||
|
||||
#else
|
||||
object[] ca = pi.GetCustomAttributes(typeof(ResourceProperty), false);
|
||||
#endif
|
||||
|
||||
if (ca.Length > 0)
|
||||
{
|
||||
value = pi.GetValue(resource, null);
|
||||
//if (value is Func<IManager, object>)
|
||||
// value = (value as Func<IManager, object>)(sender);
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
|
||||
IResource res;
|
||||
if (resource.TryGetTarget(out res))
|
||||
value = pt.Info.GetValue(res, null);
|
||||
else
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
public bool Inherit
|
||||
{
|
||||
get { return inherit; }
|
||||
}*/
|
||||
|
||||
/// <summary>
|
||||
/// List of parents.
|
||||
/// </summary>
|
||||
//public AutoList<IResource, Instance> Parents => parents;
|
||||
|
||||
/// <summary>
|
||||
/// Store responsible for creating and keeping the resource.
|
||||
/// </summary>
|
||||
public IStore Store
|
||||
{
|
||||
get { return store; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of children.
|
||||
/// </summary>
|
||||
// public AutoList<IResource, Instance> Children => children;
|
||||
|
||||
/// <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.Link(res);
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public AsyncBag<T> Children<T>(string name = null) where T : IResource
|
||||
{
|
||||
IResource res;
|
||||
if (this.resource.TryGetTarget(out res))
|
||||
{
|
||||
//if (!(store is null))
|
||||
return store.Children<T>(res, name);
|
||||
//else
|
||||
// return (res as IStore).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>(null);
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.store != null)
|
||||
return this.store.Link(this.resource);
|
||||
else
|
||||
{
|
||||
var l = new List<string>();
|
||||
//l.Add(name);
|
||||
|
||||
var p = this.resource; // parents.First();
|
||||
|
||||
while (true)
|
||||
{
|
||||
l.Insert(0, p.Instance.name);
|
||||
|
||||
if (p.Instance.parents.Count == 0)
|
||||
break;
|
||||
|
||||
p = p.Instance.parents.First();
|
||||
}
|
||||
|
||||
return String.Join("/", l.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
*
|
||||
*/
|
||||
|
||||
/// <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 template describes the properties, functions and events of the resource.
|
||||
/// </summary>
|
||||
public ResourceTemplate Template
|
||||
{
|
||||
get { return template; }
|
||||
|
||||
/*
|
||||
internal set
|
||||
{
|
||||
template = Warehouse.GetTemplate(resource.GetType());
|
||||
|
||||
// set ages
|
||||
for (byte i = 0; i < template.Properties.Length; i++)
|
||||
{
|
||||
ages.Add(0);
|
||||
modificationDates.Add(DateTime.MinValue);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/// <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, MemberTemplate member, object inquirer = null)
|
||||
{
|
||||
IResource res;
|
||||
if (this.resource.TryGetTarget(out res))
|
||||
{
|
||||
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;
|
||||
|
||||
/// <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(uint id, string name, IResource resource, IStore store, ResourceTemplate customTemplate = null, ulong age = 0)
|
||||
{
|
||||
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 (customTemplate != null)
|
||||
this.template = customTemplate;
|
||||
else
|
||||
this.template = Warehouse.GetTemplate(resource.GetType());
|
||||
|
||||
// set ages
|
||||
for (byte i = 0; i < template.Properties.Length; i++)
|
||||
{
|
||||
ages.Add(0);
|
||||
modificationDates.Add(DateTime.MinValue);
|
||||
}
|
||||
|
||||
// connect events
|
||||
Type t = ResourceProxy.GetBaseType(resource);
|
||||
|
||||
#if NETSTANDARD
|
||||
var events = t.GetTypeInfo().GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
|
||||
|
||||
#else
|
||||
var events = t.GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
|
||||
#endif
|
||||
|
||||
foreach (var evt in events)
|
||||
{
|
||||
//if (evt.EventHandlerType != typeof(ResourceEventHanlder))
|
||||
// continue;
|
||||
|
||||
|
||||
if (evt.EventHandlerType == typeof(ResourceEventHanlder))
|
||||
{
|
||||
var ca = (ResourceEvent[])evt.GetCustomAttributes(typeof(ResourceEvent), true);
|
||||
if (ca.Length == 0)
|
||||
continue;
|
||||
|
||||
ResourceEventHanlder proxyDelegate = (args) => EmitResourceEvent(null, null, evt.Name, args);
|
||||
evt.AddEventHandler(resource, proxyDelegate);
|
||||
|
||||
}
|
||||
else if (evt.EventHandlerType == typeof(CustomResourceEventHanlder))
|
||||
{
|
||||
var ca = (ResourceEvent[])evt.GetCustomAttributes(typeof(ResourceEvent), true);
|
||||
if (ca.Length == 0)
|
||||
continue;
|
||||
|
||||
CustomResourceEventHanlder proxyDelegate = (issuer, receivers, args) => EmitResourceEvent(issuer, receivers, evt.Name, args);
|
||||
evt.AddEventHandler(resource, proxyDelegate);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
else if (evt.EventHandlerType == typeof(CustomUsersEventHanlder))
|
||||
{
|
||||
var ca = (ResourceEvent[])evt.GetCustomAttributes(typeof(ResourceEvent), true);
|
||||
if (ca.Length == 0)
|
||||
continue;
|
||||
|
||||
CustomUsersEventHanlder proxyDelegate = (users, args) => EmitResourceEvent(evt.Name, users, null, args);
|
||||
evt.AddEventHandler(resource, proxyDelegate);
|
||||
}
|
||||
else if (evt.EventHandlerType == typeof(CustomConnectionsEventHanlder))
|
||||
{
|
||||
var ca = (ResourceEvent[])evt.GetCustomAttributes(typeof(ResourceEvent), true);
|
||||
if (ca.Length == 0)
|
||||
continue;
|
||||
|
||||
CustomConnectionsEventHanlder proxyDelegate = (connections, args) => EmitResourceEvent(evt.Name, null, connections, args);
|
||||
evt.AddEventHandler(resource, proxyDelegate);
|
||||
}
|
||||
else if (evt.EventHandlerType == typeof(CustomReceiversEventHanlder))
|
||||
{
|
||||
var ca = (ResourceEvent[])evt.GetCustomAttributes(typeof(ResourceEvent), true);
|
||||
if (ca.Length == 0)
|
||||
continue;
|
||||
|
||||
CustomReceiversEventHanlder proxyDelegate = (users, connections, args) => EmitResourceEvent(evt.Name, users, connections, args);
|
||||
evt.AddEventHandler(resource, proxyDelegate);
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//IQueryable<IResource> Children => store.GetChildren(this);
|
||||
|
||||
|
||||
/*
|
||||
* private void Children_OnRemoved(Instance parent, IResource value)
|
||||
{
|
||||
value.Instance.parents.Remove(resource);
|
||||
}
|
||||
|
||||
private void Children_OnAdd(Instance parent, IResource value)
|
||||
{
|
||||
if (!value.Instance.parents.Contains(resource))
|
||||
value.Instance.parents.Add(resource);
|
||||
}
|
||||
|
||||
private void Parents_OnRemoved(Instance parent, IResource value)
|
||||
{
|
||||
value.Instance.children.Remove(resource);
|
||||
}
|
||||
|
||||
private void Parents_OnAdd(Instance parent, IResource value)
|
||||
{
|
||||
if (!value.Instance.children.Contains(resource))
|
||||
value.Instance.children.Add(resource);
|
||||
}
|
||||
*/
|
||||
|
||||
private void Resource_OnDestroy(object sender)
|
||||
{
|
||||
ResourceDestroyed?.Invoke((IResource)sender);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
/*
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public virtual bool Create()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
~Resource()
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
/*
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
[AttributeUsage(AttributeTargets.Event)]
|
||||
public class ResourceEvent : System.Attribute
|
||||
{
|
||||
|
||||
string expansion;
|
||||
|
||||
public string Expansion
|
||||
{
|
||||
get
|
||||
{
|
||||
return expansion;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public ResourceEvent(string expansion = null)
|
||||
{
|
||||
this.expansion = expansion;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
/*
|
||||
|
||||
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.Net.IIP;
|
||||
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 void ResourceEventHanlder(params object[] args);
|
||||
// public delegate void CustomUsersEventHanlder(string[] usernames, params object[] args);
|
||||
//public delegate void CustomReceiversEventHanlder(DistributedConnection[] connections, params object[] args);
|
||||
//public delegate void CustomInquirerEventHanlder(object inquirer, params object[] args);
|
||||
|
||||
public delegate void CustomResourceEventHanlder(object issuer, Session[] receivers, params object[] args);
|
||||
|
||||
// public delegate void CustomReceiversEventHanlder(string[] usernames, DistributedConnection[] connections, params object[] args);
|
||||
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
/*
|
||||
|
||||
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
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class ResourceFunction : System.Attribute
|
||||
{
|
||||
private string expansion = null;
|
||||
|
||||
public string Expansion
|
||||
{
|
||||
get
|
||||
{
|
||||
return expansion;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public ResourceFunction(string expansion = null)
|
||||
{
|
||||
this.expansion = expansion;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
/*
|
||||
|
||||
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.Resource.Template;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Esiur.Resource
|
||||
{
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class ResourceProperty : System.Attribute
|
||||
{
|
||||
bool serialize;
|
||||
string readExpansion;
|
||||
string writeExpansion;
|
||||
// bool recordable;
|
||||
//bool storable;
|
||||
|
||||
//public bool Recordable => recordable;
|
||||
|
||||
//public bool Storable => storable;
|
||||
StorageMode storage;
|
||||
|
||||
public StorageMode Storage => storage;
|
||||
|
||||
public bool Serialize => serialize;
|
||||
|
||||
public string ReadExpansion
|
||||
{
|
||||
get
|
||||
{
|
||||
return readExpansion;
|
||||
}
|
||||
}
|
||||
|
||||
public string WriteExpansion
|
||||
{
|
||||
get
|
||||
{
|
||||
return writeExpansion;
|
||||
}
|
||||
}
|
||||
|
||||
public ResourceProperty(StorageMode storage = StorageMode.NonVolatile, bool serialize = true, string readExpansion = null, string writeExpansion = null)
|
||||
{
|
||||
this.readExpansion = readExpansion;
|
||||
this.writeExpansion = writeExpansion;
|
||||
this.storage = storage;
|
||||
this.serialize = serialize;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/*
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
/*
|
||||
|
||||
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;
|
||||
DataType type;
|
||||
|
||||
public Storable()
|
||||
{
|
||||
type = DataType.Void;
|
||||
}
|
||||
|
||||
public DeserializerFunction Deserializer
|
||||
{
|
||||
get { return deserializer; }
|
||||
}
|
||||
|
||||
public SerializerFunction Serializer
|
||||
{
|
||||
get { return serializer; }
|
||||
}
|
||||
|
||||
public Storable(DataType type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Storable(DataType type, SerializerFunction serializer, DeserializerFunction deserializer)
|
||||
{
|
||||
this.type = type;
|
||||
this.serializer = serializer;
|
||||
this.deserializer = deserializer;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Resource
|
||||
{
|
||||
public enum StorageMode : byte
|
||||
{
|
||||
NonVolatile,
|
||||
Volatile,
|
||||
Recordable
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
using Esiur.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Esiur.Resource.Template
|
||||
{
|
||||
public class EventTemplate : MemberTemplate
|
||||
{
|
||||
public string Expansion
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public override byte[] Compose()
|
||||
{
|
||||
var name = base.Compose();
|
||||
|
||||
if (Expansion != null)
|
||||
{
|
||||
var exp = DC.ToBytes(Expansion);
|
||||
return new BinaryList()
|
||||
.AddUInt8(0x50)
|
||||
.AddInt32(exp.Length)
|
||||
.AddUInt8Array(exp)
|
||||
.AddUInt8((byte)name.Length)
|
||||
.AddUInt8Array(name)
|
||||
.ToArray();
|
||||
}
|
||||
else
|
||||
return new BinaryList()
|
||||
.AddUInt8(0x40)
|
||||
.AddUInt8((byte)name.Length)
|
||||
.AddUInt8Array(name)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
|
||||
public EventTemplate(ResourceTemplate template, byte index, string name, string expansion)
|
||||
:base(template, MemberType.Property, index, name)
|
||||
{
|
||||
this.Expansion = expansion;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
using Esiur.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Esiur.Resource.Template
|
||||
{
|
||||
public class FunctionTemplate : MemberTemplate
|
||||
{
|
||||
|
||||
public string Expansion
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool IsVoid
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
public override byte[] Compose()
|
||||
{
|
||||
var name = base.Compose();
|
||||
|
||||
if (Expansion != null)
|
||||
{
|
||||
var exp = DC.ToBytes(Expansion);
|
||||
return new BinaryList().AddUInt8((byte)(0x10 | (IsVoid ? 0x8 : 0x0)))
|
||||
.AddUInt8((byte)name.Length)
|
||||
.AddUInt8Array(name)
|
||||
.AddInt32(exp.Length)
|
||||
.AddUInt8Array(exp)
|
||||
.ToArray();
|
||||
}
|
||||
else
|
||||
return new BinaryList().AddUInt8((byte)(IsVoid ? 0x8 : 0x0))
|
||||
.AddUInt8((byte)name.Length)
|
||||
.AddUInt8Array(name)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
|
||||
public FunctionTemplate(ResourceTemplate template, byte index, string name,bool isVoid, string expansion)
|
||||
:base(template, MemberType.Property, index, name)
|
||||
{
|
||||
this.IsVoid = isVoid;
|
||||
this.Expansion = expansion;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
using Esiur.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Esiur.Resource.Template
|
||||
{
|
||||
public class MemberTemplate
|
||||
{
|
||||
public enum MemberType
|
||||
{
|
||||
Function = 0,
|
||||
Property = 1,
|
||||
Event = 2,
|
||||
}
|
||||
|
||||
public byte Index => index;
|
||||
public string Name => name;
|
||||
public MemberType Type => type;
|
||||
|
||||
ResourceTemplate template;
|
||||
string name;
|
||||
MemberType type;
|
||||
byte index;
|
||||
|
||||
public ResourceTemplate Template => template;
|
||||
|
||||
public MemberTemplate(ResourceTemplate template, MemberType type, byte index, string name)
|
||||
{
|
||||
this.template = template;
|
||||
this.type = type;
|
||||
this.index = index;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public string Fullname => template.ClassName + "." + Name;
|
||||
|
||||
public virtual byte[] Compose()
|
||||
{
|
||||
return DC.ToBytes(Name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,129 +0,0 @@
|
||||
using Esiur.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Esiur.Resource.Template
|
||||
{
|
||||
public class PropertyTemplate : MemberTemplate
|
||||
{
|
||||
public enum PropertyPermission:byte
|
||||
{
|
||||
Read = 1,
|
||||
Write,
|
||||
ReadWrite
|
||||
}
|
||||
|
||||
|
||||
public PropertyInfo Info
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool Serilize
|
||||
{
|
||||
get;set;
|
||||
}
|
||||
//bool ReadOnly;
|
||||
//IIPTypes::DataType ReturnType;
|
||||
public PropertyPermission Permission {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/*
|
||||
public bool Recordable
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}*/
|
||||
|
||||
public StorageMode Storage
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string ReadExpansion
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string WriteExpansion
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/*
|
||||
public bool Storable
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}*/
|
||||
|
||||
|
||||
public override byte[] Compose()
|
||||
{
|
||||
var name = base.Compose();
|
||||
var pv = ((byte)(Permission) << 1) | (Storage == StorageMode.Recordable ? 1 : 0);
|
||||
|
||||
if (WriteExpansion != null && ReadExpansion != null)
|
||||
{
|
||||
var rexp = DC.ToBytes(ReadExpansion);
|
||||
var wexp = DC.ToBytes(WriteExpansion);
|
||||
return new BinaryList()
|
||||
.AddUInt8((byte)(0x38 | pv))
|
||||
.AddUInt8((byte)name.Length)
|
||||
.AddUInt8Array(name)
|
||||
.AddInt32(wexp.Length)
|
||||
.AddUInt8Array(wexp)
|
||||
.AddInt32(rexp.Length)
|
||||
.AddUInt8Array(rexp)
|
||||
.ToArray();
|
||||
}
|
||||
else if (WriteExpansion != null)
|
||||
{
|
||||
var wexp = DC.ToBytes(WriteExpansion);
|
||||
return new BinaryList()
|
||||
.AddUInt8((byte)(0x30 | pv))
|
||||
.AddUInt8((byte)name.Length)
|
||||
.AddUInt8Array(name)
|
||||
.AddInt32(wexp.Length)
|
||||
.AddUInt8Array(wexp)
|
||||
.ToArray();
|
||||
}
|
||||
else if (ReadExpansion != null)
|
||||
{
|
||||
var rexp = DC.ToBytes(ReadExpansion);
|
||||
return new BinaryList()
|
||||
.AddUInt8((byte)(0x28 | pv))
|
||||
.AddUInt8((byte)name.Length)
|
||||
.AddUInt8Array(name)
|
||||
.AddInt32(rexp.Length)
|
||||
.AddUInt8Array(rexp)
|
||||
.ToArray();
|
||||
}
|
||||
else
|
||||
return new BinaryList()
|
||||
.AddUInt8((byte)(0x20 | pv))
|
||||
.AddUInt8((byte)name.Length)
|
||||
.AddUInt8Array(name)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public PropertyTemplate(ResourceTemplate template, byte index, string name, string read, string write, StorageMode storage)
|
||||
:base(template, MemberType.Property, index, name)
|
||||
{
|
||||
//this.Recordable = recordable;
|
||||
this.Storage = storage;
|
||||
this.ReadExpansion = read;
|
||||
this.WriteExpansion = write;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,365 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
using Esiur.Misc;
|
||||
using Esiur.Data;
|
||||
using Esiur.Core;
|
||||
using System.Security.Cryptography;
|
||||
using Esiur.Proxy;
|
||||
|
||||
namespace Esiur.Resource.Template
|
||||
{
|
||||
public class ResourceTemplate
|
||||
{
|
||||
Guid classId;
|
||||
string className;
|
||||
List<MemberTemplate> members = new List<MemberTemplate>();
|
||||
List<FunctionTemplate> functions = new List<FunctionTemplate>();
|
||||
List<EventTemplate> events = new List<EventTemplate>();
|
||||
List<PropertyTemplate> properties = new List<PropertyTemplate>();
|
||||
int version;
|
||||
//bool isReady;
|
||||
|
||||
byte[] content;
|
||||
|
||||
public byte[] Content
|
||||
{
|
||||
get { return content; }
|
||||
}
|
||||
|
||||
public MemberTemplate GetMemberTemplate(MemberInfo member)
|
||||
{
|
||||
if (member is MethodInfo)
|
||||
return GetFunctionTemplateByName(member.Name);
|
||||
else if (member is EventInfo)
|
||||
return GetEventTemplateByName(member.Name);
|
||||
else if (member is PropertyInfo)
|
||||
return GetPropertyTemplateByName(member.Name);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public EventTemplate GetEventTemplateByName(string eventName)
|
||||
{
|
||||
foreach (var i in events)
|
||||
if (i.Name == eventName)
|
||||
return i;
|
||||
return null;
|
||||
}
|
||||
|
||||
public EventTemplate GetEventTemplateByIndex(byte index)
|
||||
{
|
||||
foreach (var i in events)
|
||||
if (i.Index == index)
|
||||
return i;
|
||||
return null;
|
||||
}
|
||||
|
||||
public FunctionTemplate GetFunctionTemplateByName(string functionName)
|
||||
{
|
||||
foreach (var i in functions)
|
||||
if (i.Name == functionName)
|
||||
return i;
|
||||
return null;
|
||||
}
|
||||
public FunctionTemplate GetFunctionTemplateByIndex(byte index)
|
||||
{
|
||||
foreach (var i in functions)
|
||||
if (i.Index == index)
|
||||
return i;
|
||||
return null;
|
||||
}
|
||||
|
||||
public PropertyTemplate GetPropertyTemplateByIndex(byte index)
|
||||
{
|
||||
foreach (var i in properties)
|
||||
if (i.Index == index)
|
||||
return i;
|
||||
return null;
|
||||
}
|
||||
|
||||
public PropertyTemplate GetPropertyTemplateByName(string propertyName)
|
||||
{
|
||||
foreach (var i in properties)
|
||||
if (i.Name == propertyName)
|
||||
return i;
|
||||
return null;
|
||||
}
|
||||
|
||||
public Guid ClassId
|
||||
{
|
||||
get { return classId; }
|
||||
}
|
||||
public string ClassName
|
||||
{
|
||||
get { return className; }
|
||||
}
|
||||
|
||||
public MemberTemplate[] Methods
|
||||
{
|
||||
get{return members.ToArray();}
|
||||
}
|
||||
|
||||
public FunctionTemplate[] Functions
|
||||
{
|
||||
get { return functions.ToArray(); }
|
||||
}
|
||||
|
||||
public EventTemplate[] Events
|
||||
{
|
||||
get { return events.ToArray(); }
|
||||
}
|
||||
|
||||
public PropertyTemplate[] Properties
|
||||
{
|
||||
get { return properties.ToArray(); }
|
||||
}
|
||||
|
||||
|
||||
|
||||
public ResourceTemplate()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
public ResourceTemplate(Type type)
|
||||
{
|
||||
|
||||
type = ResourceProxy.GetBaseType(type);
|
||||
|
||||
// set guid
|
||||
|
||||
var typeName = Encoding.UTF8.GetBytes(type.FullName);
|
||||
var hash = SHA256.Create().ComputeHash(typeName).Clip(0, 16);
|
||||
|
||||
classId = new Guid(hash);
|
||||
className = type.FullName;
|
||||
|
||||
|
||||
#if NETSTANDARD
|
||||
PropertyInfo[] propsInfo = type.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
|
||||
EventInfo[] eventsInfo = type.GetTypeInfo().GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
|
||||
MethodInfo[] methodsInfo = type.GetTypeInfo().GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
|
||||
|
||||
#else
|
||||
PropertyInfo[] propsInfo = type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
|
||||
EventInfo[] eventsInfo = type.GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
|
||||
MethodInfo[] methodsInfo = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
|
||||
#endif
|
||||
|
||||
//byte currentIndex = 0;
|
||||
|
||||
byte i = 0;
|
||||
|
||||
foreach (var pi in propsInfo)
|
||||
{
|
||||
var ps = (ResourceProperty[])pi.GetCustomAttributes(typeof(ResourceProperty), true);
|
||||
if (ps.Length > 0)
|
||||
{
|
||||
var pt = new PropertyTemplate(this, i++, pi.Name, ps[0].ReadExpansion, ps[0].WriteExpansion, ps[0].Storage);
|
||||
pt.Info = pi;
|
||||
pt.Serilize = ps[0].Serialize;
|
||||
properties.Add(pt);
|
||||
}
|
||||
}
|
||||
|
||||
i = 0;
|
||||
|
||||
foreach (var ei in eventsInfo)
|
||||
{
|
||||
var es = (ResourceEvent[])ei.GetCustomAttributes(typeof(ResourceEvent), true);
|
||||
if (es.Length > 0)
|
||||
{
|
||||
var et = new EventTemplate(this, i++, ei.Name, es[0].Expansion);
|
||||
events.Add(et);
|
||||
}
|
||||
}
|
||||
|
||||
i = 0;
|
||||
foreach (MethodInfo mi in methodsInfo)
|
||||
{
|
||||
var fs = (ResourceFunction[])mi.GetCustomAttributes(typeof(ResourceFunction), true);
|
||||
if (fs.Length > 0)
|
||||
{
|
||||
var ft = new FunctionTemplate(this, i++, mi.Name, mi.ReturnType == typeof(void), fs[0].Expansion);
|
||||
functions.Add(ft);
|
||||
}
|
||||
}
|
||||
|
||||
// append signals
|
||||
for (i = 0; i < events.Count; i++)
|
||||
members.Add(events[i]);
|
||||
// append slots
|
||||
for (i = 0; i < functions.Count; i++)
|
||||
members.Add(functions[i]);
|
||||
// append properties
|
||||
for (i = 0; i < properties.Count; i++)
|
||||
members.Add(properties[i]);
|
||||
|
||||
// bake it binarily
|
||||
var b = new BinaryList();
|
||||
b.AddGuid(classId)
|
||||
.AddUInt8((byte)className.Length)
|
||||
.AddString(className)
|
||||
.AddInt32(version)
|
||||
.AddUInt16((ushort)members.Count);
|
||||
|
||||
|
||||
foreach (var ft in functions)
|
||||
b.AddUInt8Array(ft.Compose());
|
||||
foreach (var pt in properties)
|
||||
b.AddUInt8Array(pt.Compose());
|
||||
foreach (var et in events)
|
||||
b.AddUInt8Array(et.Compose());
|
||||
|
||||
content = b.ToArray();
|
||||
}
|
||||
|
||||
public static ResourceTemplate Parse(byte[] data)
|
||||
{
|
||||
return Parse(data, 0, (uint)data.Length);
|
||||
}
|
||||
|
||||
|
||||
public static ResourceTemplate Parse(byte[] data, uint offset, uint contentLength)
|
||||
{
|
||||
|
||||
uint ends = offset + contentLength;
|
||||
|
||||
uint oOffset = offset;
|
||||
|
||||
// start parsing...
|
||||
|
||||
var od = new ResourceTemplate();
|
||||
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;
|
||||
|
||||
byte functionIndex = 0;
|
||||
byte propertyIndex = 0;
|
||||
byte eventIndex = 0;
|
||||
|
||||
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 isVoid = ((data[offset++] & 0x08) == 0x08);
|
||||
var name = data.GetString(offset + 1, data[offset]);
|
||||
offset += (uint)data[offset] + 1;
|
||||
|
||||
if (hasExpansion) // expansion ?
|
||||
{
|
||||
var cs = data.GetUInt32(offset);
|
||||
offset += 4;
|
||||
expansion = data.GetString(offset, cs);
|
||||
offset += cs;
|
||||
}
|
||||
|
||||
var ft = new FunctionTemplate(od, functionIndex++, name, isVoid, 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;
|
||||
|
||||
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, readExpansion, writeExpansion, recordable ? StorageMode.Recordable : StorageMode.Volatile);
|
||||
|
||||
od.properties.Add(pt);
|
||||
}
|
||||
else if (type == 2) // Event
|
||||
{
|
||||
|
||||
string expansion = null;
|
||||
var hasExpansion = ((data[offset++] & 0x10) == 0x10);
|
||||
|
||||
var name = data.GetString(offset + 1, data[offset]);// Encoding.ASCII.GetString(data, (int)offset + 1, (int)data[offset]);
|
||||
offset += (uint)data[offset] + 1;
|
||||
|
||||
if (hasExpansion) // expansion ?
|
||||
{
|
||||
var cs = data.GetUInt32(offset);
|
||||
offset += 4;
|
||||
expansion = data.GetString(offset, cs);
|
||||
offset += cs;
|
||||
}
|
||||
|
||||
var et = new EventTemplate(od, eventIndex++, name, expansion);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,548 +0,0 @@
|
||||
/*
|
||||
|
||||
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.Proxy;
|
||||
using Esiur.Resource.Template;
|
||||
using Esiur.Security.Permissions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Esiur.Net.IIP;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Esiur.Resource
|
||||
{
|
||||
// Centeral Resource Issuer
|
||||
public static class Warehouse
|
||||
{
|
||||
//static byte prefixCounter;
|
||||
|
||||
static AutoList<IStore, Instance> stores = new AutoList<IStore, Instance>(null);
|
||||
static Dictionary<uint, WeakReference<IResource>> resources = new Dictionary<uint, WeakReference<IResource>>();
|
||||
static uint resourceCounter = 0;
|
||||
|
||||
static KeyList<Guid, ResourceTemplate> templates = new KeyList<Guid, ResourceTemplate>();
|
||||
|
||||
static bool warehouseIsOpen = false;
|
||||
|
||||
public delegate void StoreConnectedEvent(IStore store, string name);
|
||||
public delegate void StoreDisconnectedEvent(IStore store);
|
||||
|
||||
public static event StoreConnectedEvent StoreConnected;
|
||||
public static event StoreDisconnectedEvent StoreDisconnected;
|
||||
|
||||
public static KeyList<string, Func<IStore>> Protocols { get; } = getSupportedProtocols();
|
||||
|
||||
|
||||
static KeyList<string, Func<IStore>> getSupportedProtocols()
|
||||
{
|
||||
var rt = new KeyList<string, Func<IStore>>();
|
||||
rt.Add("iip", () => new DistributedConnection());
|
||||
return rt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a store by its name.
|
||||
/// </summary>
|
||||
/// <param name="name">Store instance name</param>
|
||||
/// <returns></returns>
|
||||
public static IStore GetStore(string name)
|
||||
{
|
||||
foreach (var s in stores)
|
||||
if (s.Instance.Name == name)
|
||||
return s as IStore;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a resource by instance Id.
|
||||
/// </summary>
|
||||
/// <param name="id">Instance Id</param>
|
||||
/// <returns></returns>
|
||||
public static 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open the warehouse.
|
||||
/// This function issues the initialize trigger to all stores and resources.
|
||||
/// </summary>
|
||||
/// <returns>True, if no problem occurred.</returns>
|
||||
public static AsyncReply<bool> Open()
|
||||
{
|
||||
var bag = new AsyncBag<bool>();
|
||||
|
||||
foreach (var store in stores)
|
||||
bag.Add(store.Trigger(ResourceTrigger.Initialize));
|
||||
|
||||
|
||||
bag.Seal();
|
||||
|
||||
var rt = new AsyncReply<bool>();
|
||||
bag.Then((x) =>
|
||||
{
|
||||
foreach (var b in x)
|
||||
if (!b)
|
||||
{
|
||||
rt.Trigger(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var rBag = new AsyncBag<bool>();
|
||||
foreach (var rk in resources)
|
||||
{
|
||||
IResource r;
|
||||
if (rk.Value.TryGetTarget(out r))
|
||||
rBag.Add(r.Trigger(ResourceTrigger.SystemInitialized));
|
||||
}
|
||||
|
||||
rBag.Seal();
|
||||
|
||||
rBag.Then(y =>
|
||||
{
|
||||
foreach (var b in y)
|
||||
if (!b)
|
||||
{
|
||||
rt.Trigger(false);
|
||||
return;
|
||||
}
|
||||
|
||||
rt.Trigger(true);
|
||||
warehouseIsOpen = true;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
return rt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close the warehouse.
|
||||
/// This function issues terminate trigger to all resources and stores.
|
||||
/// </summary>
|
||||
/// <returns>True, if no problem occurred.</returns>
|
||||
public static 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.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.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;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
private static IResource[] QureyIn(string[] path, int index, IEnumerable<IResource> resources)// AutoList<IResource, Instance> resources)
|
||||
{
|
||||
var rt = new List<IResource>();
|
||||
|
||||
if (index == path.Length - 1)
|
||||
{
|
||||
if (path[index] == "")
|
||||
foreach (IResource child in resources)
|
||||
rt.Add(child);
|
||||
else
|
||||
foreach (IResource child in resources)
|
||||
if (child.Instance.Name == path[index])
|
||||
rt.Add(child);
|
||||
}
|
||||
else
|
||||
foreach (IResource child in resources)
|
||||
if (child.Instance.Name == path[index])
|
||||
rt.AddRange(QureyIn(path, index+1, child.Instance.Children<IResource>()));
|
||||
|
||||
return rt.ToArray();
|
||||
}
|
||||
|
||||
public static AsyncReply<IResource[]> Query(string path)
|
||||
{
|
||||
if (path == null || path == "")
|
||||
{
|
||||
var roots = stores.Where(s => s.Instance.Parents<IResource>().Count() == 0).ToArray();
|
||||
return new AsyncReply<IResource[]>(roots);
|
||||
}
|
||||
else
|
||||
{
|
||||
var rt = new AsyncReply<IResource[]>();
|
||||
Get(path).Then(x =>
|
||||
{
|
||||
var p = path.Split('/');
|
||||
|
||||
if (x == null)
|
||||
{
|
||||
rt.Trigger(QureyIn(p, 0, stores));
|
||||
}
|
||||
else
|
||||
{
|
||||
var ar = QureyIn(p, 0, stores).Where(r => r != x).ToList();
|
||||
ar.Insert(0, x);
|
||||
rt.Trigger(ar.ToArray());
|
||||
}
|
||||
});
|
||||
|
||||
return rt;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
public static async Task<IResource[]> Query(string path)
|
||||
{
|
||||
var rt = new AsyncReply<IResource[]>();
|
||||
|
||||
var p = path.Trim().Split('/');
|
||||
IResource resource;
|
||||
|
||||
foreach (var store in stores)
|
||||
if (p[0] == store.Instance.Name)
|
||||
{
|
||||
|
||||
if (p.Length == 1)
|
||||
return new IResource[] { store };
|
||||
|
||||
var res = await store.Get(String.Join("/", p.Skip(1).ToArray()));
|
||||
if (res != null)
|
||||
return new IResource[] { res };
|
||||
|
||||
|
||||
resource = store;
|
||||
for (var i = 1; i < p.Length; i++)
|
||||
{
|
||||
var children = await resource.Instance.Children<IResource>(p[i]);
|
||||
if (children.Length > 0)
|
||||
{
|
||||
if (i == p.Length - 1)
|
||||
return children;
|
||||
else
|
||||
resource = children[0];
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
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 static AsyncReply<IResource> Get(string path, object attributes = null, IResource parent = null, IPermissionsManager manager = null)
|
||||
{
|
||||
var rt = new AsyncReply<IResource>();
|
||||
|
||||
// Should we create a new store ?
|
||||
|
||||
if (path.Contains("://"))
|
||||
{
|
||||
var url = path.Split(new string[] { "://" }, 2, StringSplitOptions.None);
|
||||
var hostname = url[1].Split(new char[] { '/' }, 2)[0];
|
||||
var pathname = string.Join("/", url[1].Split(new char[] { '/' }).Skip(1));
|
||||
|
||||
|
||||
if (Protocols.ContainsKey(url[0]))
|
||||
{
|
||||
var handler = Protocols[url[0]];
|
||||
|
||||
var store = handler();
|
||||
Put(store, hostname, null, parent, null, 0, manager, attributes);
|
||||
|
||||
|
||||
store.Trigger(ResourceTrigger.Open).Then(x => {
|
||||
|
||||
warehouseIsOpen = true;
|
||||
|
||||
if (pathname.Length > 0 && pathname != "")
|
||||
store.Get(pathname).Then(r => {
|
||||
rt.Trigger(r);
|
||||
}).Error(e => rt.TriggerError(e));
|
||||
else
|
||||
rt.Trigger(store);
|
||||
}).Error(e => {
|
||||
rt.TriggerError(e);
|
||||
Warehouse.Remove(store);
|
||||
});
|
||||
|
||||
return rt;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Query(path).ContinueWith(rs =>
|
||||
{
|
||||
// rt.TriggerError(new Exception());
|
||||
if (rs.Result != null && rs.Result.Length > 0)
|
||||
rt.Trigger(rs.Result[0]);
|
||||
else
|
||||
rt.Trigger(null);
|
||||
});
|
||||
|
||||
return rt;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Put a resource in the warehouse.
|
||||
/// </summary>
|
||||
/// <param name="resource">Resource instance.</param>
|
||||
/// <param name="name">Resource name.</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 static void Put(IResource resource, string name, IStore store = null, IResource parent = null, ResourceTemplate customTemplate = null, ulong age = 0, IPermissionsManager manager = null, object attributes = null)
|
||||
{
|
||||
|
||||
if (store == null)
|
||||
{
|
||||
// assign parent as a store
|
||||
if (parent is IStore)
|
||||
store = (IStore)parent;
|
||||
// assign parent's store as a store
|
||||
else if (parent != null)
|
||||
store = parent.Instance.Store;
|
||||
// assign self as a store (root store)
|
||||
else if (resource is IStore)
|
||||
{
|
||||
store = (IStore)resource;
|
||||
stores.Add(resource as IStore);
|
||||
}
|
||||
else
|
||||
throw new Exception("Can't find a store for the resource.");
|
||||
}
|
||||
|
||||
resource.Instance = new Instance(resourceCounter++, name, resource, store, customTemplate, age);
|
||||
|
||||
if (attributes != null)
|
||||
resource.Instance.SetAttributes(Structure.FromObject(attributes));
|
||||
|
||||
if (manager != null)
|
||||
resource.Instance.Managers.Add(manager);
|
||||
|
||||
if (store == parent)
|
||||
parent = null;
|
||||
|
||||
|
||||
/*
|
||||
if (parent == null)
|
||||
{
|
||||
if (!(resource is IStore))
|
||||
store.Instance.Children.Add(resource);
|
||||
}
|
||||
else
|
||||
parent.Instance.Children.Add(resource);
|
||||
*/
|
||||
|
||||
|
||||
if (resource is IStore)
|
||||
StoreConnected?.Invoke(resource as IStore, name);
|
||||
//else
|
||||
store.Put(resource);
|
||||
|
||||
|
||||
if (parent != null)
|
||||
{
|
||||
parent.Instance.Store.AddChild(parent, resource);
|
||||
store.AddParent(resource, parent);
|
||||
//store.AddChild(parent, resource);
|
||||
|
||||
}
|
||||
|
||||
resources.Add(resource.Instance.Id, new WeakReference<IResource>(resource));
|
||||
|
||||
if (warehouseIsOpen)
|
||||
resource.Trigger(ResourceTrigger.Initialize);
|
||||
|
||||
}
|
||||
|
||||
public static T New<T>(string name, IStore store = null, IResource parent = null, IPermissionsManager manager = null, Structure attributes = null)
|
||||
where T:IResource
|
||||
{
|
||||
var type = ResourceProxy.GetProxy<T>();
|
||||
var res = Activator.CreateInstance(type) as IResource;
|
||||
Put(res, name, store, parent, null, 0, manager, attributes);
|
||||
return (T)res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Put a resource template in the templates warehouse.
|
||||
/// </summary>
|
||||
/// <param name="template">Resource template.</param>
|
||||
public static void PutTemplate(ResourceTemplate template)
|
||||
{
|
||||
if (!templates.ContainsKey(template.ClassId))
|
||||
templates.Add(template.ClassId, template);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get a template by type from the templates warehouse. If not in the warehouse, a new ResourceTemplate is created and added to the warehouse.
|
||||
/// </summary>
|
||||
/// <param name="type">.Net type.</param>
|
||||
/// <returns>Resource template.</returns>
|
||||
public static ResourceTemplate GetTemplate(Type type)
|
||||
{
|
||||
// loaded ?
|
||||
foreach (var t in templates.Values)
|
||||
if (t.ClassName == type.FullName)
|
||||
return t;
|
||||
|
||||
var template = new ResourceTemplate(type);
|
||||
templates.Add(template.ClassId, template);
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a template by class Id from the templates warehouse. If not in the warehouse, a new ResourceTemplate is created and added to the warehouse.
|
||||
/// </summary>
|
||||
/// <param name="classId">Class Id.</param>
|
||||
/// <returns>Resource template.</returns>
|
||||
public static AsyncReply<ResourceTemplate> GetTemplate(Guid classId)
|
||||
{
|
||||
if (templates.ContainsKey(classId))
|
||||
return new AsyncReply<ResourceTemplate>(templates[classId]);
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a template by class name from the templates warehouse. If not in the warehouse, a new ResourceTemplate is created and added to the warehouse.
|
||||
/// </summary>
|
||||
/// <param name="className">Class name.</param>
|
||||
/// <returns>Resource template.</returns>
|
||||
public static AsyncReply<ResourceTemplate> GetTemplate(string className)
|
||||
{
|
||||
foreach (var t in templates.Values)
|
||||
if (t.ClassName == className)
|
||||
return new AsyncReply<ResourceTemplate>(t);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool Remove(IResource resource)
|
||||
{
|
||||
|
||||
if (resource.Instance == null)
|
||||
return false;
|
||||
|
||||
if (resources.ContainsKey(resource.Instance.Id))
|
||||
resources.Remove(resource.Instance.Id);
|
||||
else
|
||||
return false;
|
||||
|
||||
if (resource is IStore)
|
||||
{
|
||||
stores.Remove(resource as IStore);
|
||||
|
||||
// remove all objects associated with the store
|
||||
var toBeRemoved = resources.Values.Where(x => {
|
||||
IResource r;
|
||||
return x.TryGetTarget(out r) && r.Instance.Store == resource;
|
||||
}).ToArray();
|
||||
|
||||
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();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user