Browse Source

為下一版的解耦做準備

0x0001F36D 5 years ago
parent
commit
abb9976589

+ 4 - 2
readme.md

@@ -8,12 +8,14 @@
     > Line 的非官方(透過逆向工程得到) API 在使用上非常非常難開發,
     > 有大量文件及我個人及前輩測試發現很容易有封鎖的可能,
     > 尤其是語音通話這部分。
-- IoT 控制 (MQTT)
+- IoT 控制
     > 以前開發過,這部分問題比較少。
+- Spotify
+    > 以前開發過,問題可能會比較少。
 - Uber Eats 訂餐
     > 還沒研究,原本想串 Food Panda 不過沒有找到逆向出來的 API 能參考,Uber Eats 是已經有找到相對應的非官方 API 了。
 - 備忘錄
-    > 我個人都用 Any.do (無官方API)。
+    >  Any.do (無官方API)。
 - Trello
     > 上面大部分跑過後才會考慮加入這部分的整合功能。
 - Uber

+ 13 - 0
src/Yuuna.Contracts/Modules/IModuleMetadata.cs

@@ -0,0 +1,13 @@
+// Author: Orlys
+// Github: https://github.com/Orlys
+
+namespace Yuuna.Contracts.Modules
+{
+    public interface IModuleMetadata
+    {
+        string Author { get; set; }
+        string Description { get; set; }
+        string Name { get; }
+        string Url { get; set; }
+    }
+}

+ 16 - 76
src/Yuuna.Contracts/Modules/ModuleBase.cs

@@ -5,110 +5,52 @@ namespace Yuuna.Contracts.Modules
 {
     using System;
     using System.Collections.Immutable;
-    using System.Reflection;
     using System.Runtime.Loader;
     using Yuuna.Contracts.Optimization;
     using Yuuna.Contracts.Patterns;
     using Yuuna.Contracts.Semantics;
     using Yuuna.Contracts.TextSegmention;
-
-    /// <summary>
-    /// 模組狀態。
-    /// </summary>
-    public enum ModuleStatus
-    {
-        /// <summary>
-        /// 未初始化。
-        /// </summary>
-        Uninitialized,
-        /// <summary>
-        /// 初始化失敗。
-        /// </summary>
-        FailToInitialize,
-        /// <summary>
-        /// 初始化完成。
-        /// </summary>
-        Initialized,
-    }
-
-    internal interface IModule
-    {
-        IPatternSet Patterns { get; }
-    }
-
     public abstract class ModuleBase 
     {
-
-        protected virtual string ModuleName { get; }
-
-        private readonly PatternFactory _patternfactory;
-
-        internal IPatternSet Patterns => this._patternfactory;
+        /// <summary>
+        /// 中繼資料。
+        /// </summary> 
+        public ModuleMetadataAttribute Metadata { get; }
 
         public ModuleBase()
         {
-            this._patternfactory = new PatternFactory(this);
-            this.ModuleName = this.GetType().Name;
-            this.Status = ModuleStatus.Uninitialized;
+            this.Metadata = ModuleMetadataAttribute.GetMetadata(this.GetType());
         }
 
-        /// <summary>
-        /// 模組名稱。
-        /// </summary>
-        public string Name
-        {
-            get
-            {
-                var t = this.GetType();
-                var getMethod = t.GetProperty(nameof(this.ModuleName), (BindingFlags)52).GetGetMethod(true);
-                if (!getMethod.GetBaseDefinition().Equals(getMethod))
-                {
-                    try
-                    {
-                        var test = this.ModuleName;
-                        if (!string.IsNullOrWhiteSpace(test))
-                        {
-                            return test;
-                        }
-                    }
-                    catch
-                    {
-                    }
-                    return t.Name;
-                }
-                else
-                    return this.ModuleName;
-            }
-        }
-
-        /// <summary>
-        /// 表示模組是否已初始化。
-        /// </summary>
-        public ModuleStatus Status { get; private set; }
+        public bool Initialized { get; private set; }
 
         /// <summary>
         /// 初始化模組
         /// </summary>
         /// <param name="textSegmenter">分詞器</param>
         /// <param name="groupManager">群組管理</param>
-        internal void Initialize(ITextSegmenter textSegmenter, IGroupManager groupManager)
+        internal void Initialize(ITextSegmenter textSegmenter, IGroupManager groupManager, out IPatternSet patterns)
         {
-            if (this.Status.Equals(ModuleStatus.Uninitialized))
+            patterns = null;
+            if (!this.Initialized)
             {
                 try
                 {
-                    this.BuildPatterns(groupManager, this._patternfactory);
+                    patterns = new PatternFactory(this);
+                    this.BuildPatterns(groupManager, patterns as IPatternBuilder);
                     textSegmenter.Load(groupManager);
-                    this.Status = ModuleStatus.Initialized;
                     this.AfterInitialize();
+                    this.Initialized = true;
+                    this.Patterns = patterns;
                 }
-                catch //(Exception e)
+                catch (Exception e)
                 {
-                    this.Status = ModuleStatus.FailToInitialize;
+                    throw new TypeInitializationException(this.GetType().FullName, e);
                 }
             }
         }
 
+        internal IPatternSet Patterns { get; private set; }
 
         /// <summary>
         /// 在初始化後引發。
@@ -123,7 +65,5 @@ namespace Yuuna.Contracts.Modules
         /// <param name="g"></param>
         /// <param name="p"></param>
         protected abstract void BuildPatterns(IGroupManager g, IPatternBuilder p);
-
-
     }
 }

+ 97 - 0
src/Yuuna.Contracts/Modules/ModuleMetadataAttribute.cs

@@ -0,0 +1,97 @@
+// Author: Orlys
+// Github: https://github.com/Orlys
+
+namespace Yuuna.Contracts.Modules
+{
+    using System;
+    using System.Reflection;
+
+    [AttributeUsage(AttributeTargets.Class)]
+    public sealed class ModuleMetadataAttribute : Attribute, IModuleMetadata
+    {
+        public ModuleMetadataAttribute(string name)
+        {
+            this.Name = name;
+        }
+
+        public string Name { get; private set; }
+        public string Author { get; set; }
+        public string Url { get; set; }
+        public string Description { get; set; }
+
+        internal static ModuleMetadataAttribute GetMetadata(Type type)
+        {
+            var meta = type.GetCustomAttribute<ModuleMetadataAttribute>();
+            if (meta is null)
+                meta = new ModuleMetadataAttribute(type.Name);
+            else if (string.IsNullOrWhiteSpace(meta.Name))
+                meta.Name = type.Name;
+            return meta;
+        }
+    }
+
+    //public abstract class ModuleBase 
+    //{
+    //    private readonly PatternFactory _patternfactory;
+
+    //    /// <summary>
+    //    /// 中繼資料。
+    //    /// </summary> 
+    //    public ModuleMetadataAttribute Metadata { get; }
+
+    //    internal IPatternSet Patterns => this._patternfactory;
+
+    //    public ModuleBase()
+    //    {
+    //        this.Status = ModuleStatus.Uninitialized;
+    //        this._patternfactory = new PatternFactory(this);
+    //        this.Metadata = ModuleMetadataAttribute.GetMetadata(this.GetType());
+    //    }
+
+
+    //    /// <summary>
+    //    /// 表示模組是否已初始化。
+    //    /// </summary>
+    //    public ModuleStatus Status { get; private set; }
+
+    //    /// <summary>
+    //    /// 初始化模組
+    //    /// </summary>
+    //    /// <param name="textSegmenter">分詞器</param>
+    //    /// <param name="groupManager">群組管理</param>
+    //    internal void Initialize(ITextSegmenter textSegmenter, IGroupManager groupManager)
+    //    {
+    //        if (this.Status.Equals(ModuleStatus.Uninitialized))
+    //        {
+    //            try
+    //            {
+    //                this.BuildPatterns(groupManager, this._patternfactory);
+    //                textSegmenter.Load(groupManager);
+    //                this.Status = ModuleStatus.Initialized;
+    //                this.AfterInitialize();
+    //            }
+    //            catch //(Exception e)
+    //            {
+    //                this.Status = ModuleStatus.FailToInitialize;
+    //            }
+    //        }
+    //    }
+
+
+    //    /// <summary>
+    //    /// 在初始化後引發。
+    //    /// </summary>
+    //    protected virtual void AfterInitialize()
+    //    {
+    //    }
+
+    //    /// <summary>
+    //    /// 建立模式規則。
+    //    /// </summary>
+    //    /// <param name="g"></param>
+    //    /// <param name="p"></param>
+    //    protected abstract void BuildPatterns(IGroupManager g, IPatternBuilder p);
+
+
+    //}
+}

+ 5 - 6
src/Yuuna/Actor.cs

@@ -85,7 +85,7 @@ namespace Yuuna
         private Antonym[] _antonyms;
         private ITextSegmenter _segmenter;
         private IStrategy _strategy;
-        private IReadOnlyList<ModuleProxy> _moduleProxies;
+        private IReadOnlyList<ModuleCoupler> _moduleProxies;
         private readonly Stack<Match> _session;
         private readonly object _lock = new object();
         private volatile bool _initialized;
@@ -94,11 +94,11 @@ namespace Yuuna
         {
             this._session = new Stack<Match>();
         }
-        public Actor(ITextSegmenter segmenter, IEnumerable<Response> canResponses, IStrategy strategy, IReadOnlyList<ModuleProxy> moduleProxies) : this()
+        public Actor(ITextSegmenter segmenter, IEnumerable<Response> canResponses, IStrategy strategy, IReadOnlyList<ModuleCoupler> moduleProxies) : this()
         {
             this.Initialize(segmenter, canResponses, strategy, moduleProxies);
         }
-        public void Initialize(ITextSegmenter segmenter, IEnumerable<Response> canResponses, IStrategy strategy, IReadOnlyList<ModuleProxy> moduleProxies)
+        public void Initialize(ITextSegmenter segmenter, IEnumerable<Response> canResponses, IStrategy strategy, IReadOnlyList<ModuleCoupler> moduleProxies)
         {
             if (!this._initialized)
                 lock (this._lock)
@@ -120,7 +120,7 @@ namespace Yuuna
                     foreach (var plugin in this._moduleProxies)
                     {
                         plugin.Initialize(this._segmenter, new GroupManager());
-                        Debug.WriteLine("已載入模組: " + plugin.Type.AssemblyQualifiedName);
+                        Debug.WriteLine("已載入模組: " + plugin.Metadata.Name);
                     }
                     this._initialized = true;
                 }
@@ -161,7 +161,6 @@ namespace Yuuna
                     {
                         if (antonym.Judge(text, out var type))
                         {
-
                             if (type.Equals(Antonym.TypeKinds.Positive))
                             {
                                 single.Pattern.Owner.Patterns.TryGet(single.Pattern, out var bag);
@@ -193,7 +192,7 @@ namespace Yuuna
 
 
 
-                var alternative = this._strategy.FindBest(this._moduleProxies.Select(x => x.PatternSet).ToArray(), cutted);
+                var alternative = this._strategy.FindBest(this._moduleProxies.Select(x => x.Patterns).ToArray(), cutted);
                 //Debug.WriteLine(alternative.Status);
                 switch (alternative.Status)
                 {

+ 2 - 1
src/Yuuna/EntryPoint.cs

@@ -23,7 +23,8 @@ namespace Yuuna
     { 
         public static async Task Main(string[] args)
         {
-            await WebHost.RunAsync(); 
+            var k = WebHost.RunAsync(); 
+            await k;
         }
     }
 }

+ 21 - 0
src/Yuuna/Plugins/CollectibleLoader.cs

@@ -0,0 +1,21 @@
+// Author: Orlys
+// Github: https://github.com/Orlys
+
+namespace Yuuna
+{
+    using System.Runtime.Loader;
+    using System.Reflection;
+
+    public sealed class CollectibleLoader : AssemblyLoadContext
+    {
+        internal CollectibleLoader() : base(true)
+        {
+
+        }
+
+        protected override Assembly Load(AssemblyName assemblyName)
+        {
+            return null;
+        }
+    }
+}

+ 68 - 0
src/Yuuna/Plugins/ModuleCoupler.cs

@@ -0,0 +1,68 @@
+// Author: Orlys
+// Github: https://github.com/Orlys
+
+namespace Yuuna
+{
+    using System;
+    using System.Collections.Generic;
+    using System.IO;
+    using System.Reflection;
+    using Yuuna.Contracts.Modules;
+    using Yuuna.Contracts.Patterns;
+    using Yuuna.Contracts.TextSegmention;
+    using Yuuna.Contracts.Semantics;
+
+    public sealed class ModuleCoupler : IDisposable
+    {
+        private WeakReference<ModuleBase> _module;
+        private CollectibleLoader _loader;
+        internal ModuleCoupler(IEnumerable<FileInfo> dlls)
+        {
+            this._loader = new CollectibleLoader();
+            foreach (var dll in dlls)
+            {
+                using (var fs = dll.OpenRead())
+                {
+                    var asm = this._loader.LoadFromStream(fs);
+                    foreach (var t in asm.GetTypes())
+                    {
+                        if (t.IsSubclassOf(typeof(ModuleBase)))
+                        {
+                            this._module = new WeakReference<ModuleBase>(Activator.CreateInstance(t) as ModuleBase);
+                            if (this._module.TryGetTarget(out var m))
+                            {
+                                this.Metadata = m.Metadata;
+                            }
+                            break;
+                        }
+                    }
+
+                }
+            }
+        }
+
+        public IPatternSet Patterns { get; private set; }
+        public IModuleMetadata Metadata { get; }
+
+        public bool Initialize(ITextSegmenter s, IGroupManager g)
+        {
+            if (this._module is null)
+                return false;
+
+            if (this._module.TryGetTarget(out var m))
+            {
+                m.Initialize(s, g, out var v);
+                this.Patterns = v;
+                return true;
+            }
+
+            return false;
+        }
+
+        public void Dispose()
+        {
+            this._loader.Unload();
+            this._loader = null;
+        }
+    }
+}

+ 11 - 12
src/Yuuna/Plugins/ModuleManager.cs

@@ -6,14 +6,12 @@ namespace Yuuna
     using System;
     using System.Collections.Generic;
     using System.Linq;
-    using Yuuna.Contracts.Modules;
     using System.Runtime.Loader;
     using System.IO;
     using System.Linq.Expressions;
     using System.Reflection;
      
-
-    public class ModuleManager
+    public sealed class ModuleManager
     {
         private static volatile ModuleManager s_inst;
         private static object s_lock = new object();
@@ -21,11 +19,11 @@ namespace Yuuna
         {
             get
             {
-                if(s_inst is null)
+                if (s_inst is null)
                 {
                     lock (s_lock)
                     {
-                        if(s_inst is null)
+                        if (s_inst is null)
                         {
                             s_inst = new ModuleManager();
                         }
@@ -38,12 +36,12 @@ namespace Yuuna
 
         private const string PATH = "./modules";
 
-        private readonly List< ModuleProxy> _modules;
+        private readonly List<ModuleCoupler> _modules;
         private readonly DirectoryInfo _modulesFolder;
 
         private ModuleManager()
         {
-            this._modules = new List<ModuleProxy>();
+            this._modules = new List<ModuleCoupler>();
             this._modulesFolder = new DirectoryInfo(PATH);
             if (!this._modulesFolder.Exists)
             {
@@ -55,14 +53,14 @@ namespace Yuuna
             }
         }
 
-        public IReadOnlyList<ModuleProxy> Modules => this._modules;
+        public IReadOnlyList<ModuleCoupler> Modules => this._modules;
 
         private void Scan()
         {
             foreach (var moduleFolder in this._modulesFolder.EnumerateDirectories())
             {
-                if (ModuleProxy.TryCreate(moduleFolder, out var domain))
-                    this._modules.Add(domain);
+                var n = new ModuleCoupler(moduleFolder.EnumerateFiles("*.dll"));
+                this._modules.Add(n);
             }
         }
 
@@ -70,9 +68,10 @@ namespace Yuuna
         {
             foreach (var m in this._modules)
             {
-                m.Unload();
-                (m as IDisposable)?.Dispose();
+                m.Dispose();
             }
+            GC.Collect();
+            GC.WaitForPendingFinalizers();
             this._modules.Clear();
         }
 

+ 0 - 127
src/Yuuna/Plugins/ModuleProxy.cs

@@ -1,127 +0,0 @@
-// Author: Orlys
-// Github: https://github.com/Orlys
-
-namespace Yuuna
-{
-    using System;
-    using Yuuna.Contracts.Modules;
-    using Yuuna.Contracts.TextSegmention;
-    using System.Runtime.Loader;
-    using Yuuna.Contracts.Semantics;
-    using System.Linq.Expressions;
-    using System.IO;
-    using System.Linq;
-    using System.Runtime.CompilerServices;
-    using Yuuna.Contracts.Patterns;
-
-    public sealed class ModuleProxy : IDisposable
-    { 
-        private volatile ModuleBase _inst;
-        private volatile AssemblyLoadContext _loader; 
-        private DirectoryInfo _moduleFolder;
-        private readonly object _lock = new object();
-        public bool Proxied => this._inst is null;
-
-        public string Name => this._inst?.Name;
-
-        public Type Type => this._inst?.GetType();
-
-        private ModuleProxy(DirectoryInfo moduleFolder, AssemblyLoadContext loader, ModuleBase inst)
-        {
-            this._moduleFolder = moduleFolder;
-            this._loader = loader;
-            this._inst = inst; 
-        }
-
-        public IPatternSet PatternSet => this._inst.Patterns;
-
-        public static bool TryCreate(DirectoryInfo moduleFolder, out ModuleProxy domain)
-        {
-            if( LoadSingleModuleType(moduleFolder, out var loader, out var inst))
-            {
-                domain = new ModuleProxy(moduleFolder, loader, inst);
-                return true;
-            }
-            domain = null;
-            return false;
-        }
-         
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private static bool LoadSingleModuleType(DirectoryInfo moduleFolder, out AssemblyLoadContext loader, out ModuleBase inst)
-        {
-            loader = new AssemblyLoadContext(moduleFolder.Name, true);
-
-            //var refs = new HashSet<AssemblyName>();
-            foreach (var file in moduleFolder.EnumerateFiles("*.dll", SearchOption.AllDirectories))
-            {
-                try
-                {
-                    /*var a = */
-                    loader.LoadFromAssemblyPath(file.FullName);
-                    //foreach (var refA in a.GetReferencedAssemblies()) 
-                    //    refs.Add(refA); 
-                }
-                catch
-                {
-                }
-            }
-
-            var type  = loader.Assemblies
-                .SelectMany(x => x.GetTypes())
-                .SingleOrDefault(x =>
-                    x.IsSubclassOf(typeof(ModuleBase)) &&
-                    x.IsPublic &&
-                    !x.IsAbstract);
-
-            inst = null;
-            if (type is null)
-            {
-                loader.Unload();
-                loader = null;
-            }
-            else
-            {
-                try
-                {
-                    inst = Activator.CreateInstance(type) as ModuleBase;
-                }
-                catch  
-                { 
-                } 
-            }
-            return inst is ModuleBase;
-        }
-
-        internal void Initialize(ITextSegmenter textSegmenter, IGroupManager groupManager)
-        {
-            this._inst.Initialize(textSegmenter, groupManager);
-        } 
-
-        public void Reload()
-        {
-            if(LoadSingleModuleType(this._moduleFolder, out var loader, out var inst))
-            {
-                lock (this._lock)
-                {
-                    this.Unload();
-                    this._loader = loader;
-                    this._inst = inst;
-                }
-            }
-        }
-
-        public void Unload()
-        {
-            (this._inst as IDisposable)?.Dispose();
-            this._inst = null;
-
-            this._loader?.Unload();
-            this._loader = null; 
-        }
-
-        void IDisposable.Dispose()
-        {
-            this.Unload();
-        }
-    }
-}

+ 0 - 1
src/Yuuna/Yuuna.csproj

@@ -11,7 +11,6 @@
 
   <ItemGroup>
     <Folder Include="Interaction\AspNetCore\Mvc\Views\" />
-    <Folder Include="Management\" />
     <Folder Include="Properties\" />
   </ItemGroup>