using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Kruzya.TelegramBot.Core { public sealed class Core { /// /// The Core API version. /// public UInt32 ApiVersion => 1; /// /// The array that contains all modules instances. /// public Module[] Modules => _modules.ToArray(); /// /// The current bot configuration. /// public IConfiguration Configuration => _configuration; #region Private properties /// /// The array that contains all modules instances. /// private List _modules = new List(); /// /// The current bot configuration. /// private IConfiguration _configuration; /// /// The all directories for loading our module assemblies. /// private List _directories { get { // For start, load all assemblies in required directory. // And register in internal temporary array. var modulesDirectory = new List(new string[] { Path.Join(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "modules"), Path.Join(Environment.CurrentDirectory, "modules") }); // Also lookup load directories from config. modulesDirectory.AddRange(_configuration.GetSection("ModuleDirectories").GetChildren().Select(q => q.Value) .ToList()); return modulesDirectory; } } #endregion public Core(IConfiguration configuration) { _configuration = configuration; HookAppDomain(); Start(); } /// /// Initializes all required submodules. /// private void Start() { var types = new List(); foreach (var directory in _directories) { if (!Directory.Exists(directory)) { continue; } foreach (var file in Directory.EnumerateFiles(directory, "*.dll", SearchOption.AllDirectories)) { var assemblyTypes = LoadAssembly(file); if (assemblyTypes == null) { continue; } types.AddRange(assemblyTypes); } } // Instantiate them. foreach (var module in types) { EnsureLoaded(module); } } /// /// Loads the assembly and appends all Module types to list. /// /// The full path to assembly file /// if success, null otherwise. private IEnumerable LoadAssembly(string path) { Assembly assembly; try { assembly = Assembly.LoadFile(path); } catch (Exception) { // don't load any invalid module. return null; } return assembly.GetExportedTypes().Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Module))); } /// /// Ensures if module is loaded and started. Note that call can change module event chain. /// /// The Module type. /// Module instance. public T EnsureLoaded() where T : Module { return (T) EnsureLoaded(typeof(T)); } /// /// Ensures if module is loaded and started. Note that call can change module event chain. /// /// The Module type. /// Module instance. /// The given type is not extends the Module class public Module EnsureLoaded(Type moduleType) { if (!moduleType.IsSubclassOf(typeof(Module))) { throw new Exception("Received incorrect module type"); } var module = _modules.FirstOrDefault(module => module.GetType() == moduleType); if (module == null) { module = (Module) Activator.CreateInstance(moduleType, this); Console.WriteLine($"Started module {module.GetType().Name}"); _modules.Add(module); } return module; } private void HookAppDomain() { var appDomain = AppDomain.CurrentDomain; appDomain.AssemblyResolve += delegate(object? sender, ResolveEventArgs args) { var basePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); string assemblyPath = Path.Combine(basePath, new AssemblyName(args.Name).Name + ".dll"); if (!File.Exists(assemblyPath)) { return null; } return Assembly.LoadFrom(assemblyPath); }; } } }