<commit_before>﻿#region LICENSE

// The contents of this file are subject to the Common Public Attribution
// License Version 1.0. (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
// https://github.com/NiclasOlofsson/MiNET/blob/master/LICENSE. 
// The License is based on the Mozilla Public License Version 1.1, but Sections 14 
// and 15 have been added to cover use of software over a computer network and 
// provide for limited attribution for the Original Developer. In addition, Exhibit A has 
// been modified to be consistent with Exhibit B.
// 
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
// the specific language governing rights and limitations under the License.
// 
// The Original Code is MiNET.
// 
// The Original Developer is the Initial Developer.  The Initial Developer of
// the Original Code is <NAME>.
// 
// All portions of the code written by <NAME> are Copyright (c) 2014-2018 <NAME>. 
// All Rights Reserved.

#endregion

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using log4net;
using MiNET.Net;
using MiNET.Plugins.Attributes;
using MiNET.Utils;
using MiNET.Worlds;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;

namespace MiNET.Plugins
{
	public class PluginManager
	{
		private static readonly ILog Log = LogManager.GetLogger(typeof(MiNetServer));

		private readonly List<object> _plugins = new List<object>();
		private readonly Dictionary<MethodInfo, PacketHandlerAttribute> _packetHandlerDictionary = new Dictionary<MethodInfo, PacketHandlerAttribute>();
		private readonly Dictionary<MethodInfo, PacketHandlerAttribute> _packetSendHandlerDictionary = new Dictionary<MethodInfo, PacketHandlerAttribute>();
		private readonly Dictionary<MethodInfo, CommandAttribute> _pluginCommands = new Dictionary<MethodInfo, CommandAttribute>();

		public List<object> Plugins
		{
			get { return _plugins; }
		}

		public CommandSet Commands { get; set; } = new CommandSet();

		private string _currentPath = null;

		internal void LoadPlugins()
		{
			if (Config.GetProperty("PluginDisabled", false)) return;

			// Default it is the directory we are executing, and below.
			string pluginDirectoryPaths = Path.GetDirectoryName(new Uri(Assembly.GetEntryAssembly().CodeBase).LocalPath);
			pluginDirectoryPaths = Config.GetProperty("PluginDirectory", pluginDirectoryPaths);
			//HACK: Make it possible to define multiple PATH;PATH;PATH

			foreach (string dirPath in pluginDirectoryPaths.Split(new char[] {';'}, StringSplitOptions.RemoveEmptyEntries))
			{
				if (dirPath == null) continue;

				string pluginDirectory = Path.GetFullPath(dirPath);

				Log.Debug($"Looking for plugin assemblies in directory {pluginDirectory}");

				if (!Directory.Exists(pluginDirectory)) continue;

				_currentPath = pluginDirectory;

				AppDomain currentDomain = AppDomain.CurrentDomain;
				currentDomain.AssemblyResolve += MyResolveEventHandler;

				List<string> pluginPaths = new List<string>();

				pluginPaths.AddRange(Directory.GetFiles(pluginDirectory, "*.dll", SearchOption.AllDirectories));
				pluginPaths.AddRange(Directory.GetFiles(pluginDirectory, "*.exe", SearchOption.AllDirectories));
				pluginPaths.ForEach(path => Log.Debug($"Looking for plugins in assembly {path}"));

				foreach (string pluginPath in pluginPaths)
				{
					Assembly newAssembly = Assembly.LoadFile(pluginPath);

					try
					{
						Type[] types = newAssembly.GetExportedTypes();
						foreach (Type type in types)
						{
							try
							{
								// If no PluginAttribute and does not implement IPlugin interface, not a valid plugin
								if (!type.IsDefined(typeof(PluginAttribute), true) && !typeof(IPlugin).IsAssignableFrom(type)) continue;

								// If plugin is already loaded don't load it again
								if (_plugins.Any(l => l.GetType().AssemblyQualifiedName == type.AssemblyQualifiedName))
								{
									Log.Error($"Tried to load duplicate plugin: {type}");
									continue;
								}

								if (type.IsDefined(typeof(PluginAttribute), true))
								{
									PluginAttribute pluginAttribute = Attribute.GetCustomAttribute(type, typeof(PluginAttribute), true) as PluginAttribute;
									if (pluginAttribute != null)
									{
										if (!Config.GetProperty(pluginAttribute.PluginName + ".Enabled", true)) continue;
									}
								}
								var ctor = type.GetConstructor(Type.EmptyTypes);
								if (ctor != null)
								{
									var plugin = ctor.Invoke(null);
									_plugins.Add(plugin);
									LoadCommands(type);
									Commands = GenerateCommandSet(_pluginCommands.Keys.ToArray());
									LoadPacketHandlers(type);
									Log.Debug($"Loaded plugin {type}");
								}
							}
							catch (Exception ex)
							{
								Log.WarnFormat("Failed loading plugin type {0} as a plugin.", type);
								Log.Debug("Plugin loader caught exception, but is moving on.", ex);
							}
						}
					}
					catch (Exception e)
					{
						Log.WarnFormat("Failed loading exported types for assembly {0} as a plugin.", newAssembly.FullName);
						Log.Debug("Plugin loader caught exception, but is moving on.", e);
					}
				}
			}

			DebugPrintCommands();
		}

		public event ResolveEventHandler AssemblyResolve;

		private Assembly MyResolveEventHandler(object sender, ResolveEventArgs args)
		{
			var assembly = AssemblyResolve?.Invoke(sender, args);

			if (assembly != null) return assembly;
			if (_currentPath == null) return null;

			try
			{
				AssemblyName name = new AssemblyName(args.Name);
				string assemblyPath = _currentPath + "\\" + name.Name + ".dll";
				return Assembly.LoadFile(assemblyPath);
			}
			catch (Exception)
			{
				try
				{
					AssemblyName name = new AssemblyName(args.Name);
					string assemblyPath = _currentPath + "\\" + name.Name + ".exe";
					return Assembly.LoadFile(assemblyPath);
				}
				catch (Exception)
				{
					return Assembly.LoadFile(args.Name + ".dll");
				}<commit_msg>Plugins can be loaded via code directly, instead of via config. Not tested, so have fun and report bugs if you find it.<commit_after>﻿#region LICENSE

// The contents of this file are subject to the Common Public Attribution
// License Version 1.0. (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
// https://github.com/NiclasOlofsson/MiNET/blob/master/LICENSE. 
// The License is based on the Mozilla Public License Version 1.1, but Sections 14 
// and 15 have been added to cover use of software over a computer network and 
// provide for limited attribution for the Original Developer. In addition, Exhibit A has 
// been modified to be consistent with Exhibit B.
// 
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
// the specific language governing rights and limitations under the License.
// 
// The Original Code is MiNET.
// 
// The Original Developer is the Initial Developer.  The Initial Developer of
// the Original Code is <NAME>.
// 
// All portions of the code written by <NAME> are Copyright (c) 2014-2018 <NAME>. 
// All Rights Reserved.

#endregion

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using log4net;
using MiNET.Net;
using MiNET.Plugins.Attributes;
using MiNET.Utils;
using MiNET.Worlds;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;

namespace MiNET.Plugins
{
	public class PluginManager
	{
		private static readonly ILog Log = LogManager.GetLogger(typeof(MiNetServer));

		private readonly List<object> _plugins = new List<object>();
		private readonly Dictionary<MethodInfo, PacketHandlerAttribute> _packetHandlerDictionary = new Dictionary<MethodInfo, PacketHandlerAttribute>();
		private readonly Dictionary<MethodInfo, PacketHandlerAttribute> _packetSendHandlerDictionary = new Dictionary<MethodInfo, PacketHandlerAttribute>();
		private readonly Dictionary<MethodInfo, CommandAttribute> _pluginCommands = new Dictionary<MethodInfo, CommandAttribute>();

		public List<object> Plugins
		{
			get { return _plugins; }
		}

		public CommandSet Commands { get; set; } = new CommandSet();

		private string _currentPath = null;

		internal void LoadPlugins()
		{
			if (Config.GetProperty("PluginDisabled", false)) return;

			// Default it is the directory we are executing, and below.
			string pluginDirectoryPaths = Path.GetDirectoryName(new Uri(Assembly.GetEntryAssembly().CodeBase).LocalPath);
			pluginDirectoryPaths = Config.GetProperty("PluginDirectory", pluginDirectoryPaths);
			//HACK: Make it possible to define multiple PATH;PATH;PATH

			foreach (string dirPath in pluginDirectoryPaths.Split(new char[] {';'}, StringSplitOptions.RemoveEmptyEntries))
			{
				if (dirPath == null) continue;

				string pluginDirectory = Path.GetFullPath(dirPath);

				Log.Debug($"Looking for plugin assemblies in directory {pluginDirectory}");

				if (!Directory.Exists(pluginDirectory)) continue;

				_currentPath = pluginDirectory;

				AppDomain currentDomain = AppDomain.CurrentDomain;
				currentDomain.AssemblyResolve += MyResolveEventHandler;

				List<string> pluginPaths = new List<string>();

				pluginPaths.AddRange(Directory.GetFiles(pluginDirectory, "*.dll", SearchOption.AllDirectories));
				pluginPaths.AddRange(Directory.GetFiles(pluginDirectory, "*.exe", SearchOption.AllDirectories));
				pluginPaths.ForEach(path => Log.Debug($"Looking for plugins in assembly {path}"));

				foreach (string pluginPath in pluginPaths)
				{
					Assembly newAssembly = Assembly.LoadFile(pluginPath);

					try
					{
						Type[] types = newAssembly.GetExportedTypes();
						foreach (Type type in types)
						{
							try
							{
								// If no PluginAttribute and does not implement IPlugin interface, not a valid plugin
								if (!type.IsDefined(typeof(PluginAttribute), true) && !typeof(IPlugin).IsAssignableFrom(type)) continue;

								// If plugin is already loaded don't load it again
								if (_plugins.Any(l => l.GetType().AssemblyQualifiedName == type.AssemblyQualifiedName))
								{
									Log.Error($"Tried to load duplicate plugin: {type}");
									continue;
								}

								if (type.IsDefined(typeof(PluginAttribute), true))
								{
									PluginAttribute pluginAttribute = Attribute.GetCustomAttribute(type, typeof(PluginAttribute), true) as PluginAttribute;
									if (pluginAttribute != null)
									{
										if (!Config.GetProperty(pluginAttribute.PluginName + ".Enabled", true)) continue;
									}
								}
								var ctor = type.GetConstructor(Type.EmptyTypes);
								if (ctor != null)
								{
									var plugin = ctor.Invoke(null);
									LoadPlugin(plugin, type);
								}
							}
							catch (Exception ex)
							{
								Log.WarnFormat("Failed loading plugin type {0} as a plugin.", type);
								Log.Debug("Plugin loader caught exception, but is moving on.", ex);
							}
						}
					}
					catch (Exception e)
					{
						Log.WarnFormat("Failed loading exported types for assembly {0} as a plugin.", newAssembly.FullName);
						Log.Debug("Plugin loader caught exception, but is moving on.", e);
					}
				}
			}

			DebugPrintCommands();
		}

		public void LoadPlugin(object plugin)
		{
			Type type = plugin.GetType();

			if (_plugins.Any(l => l.GetType().AssemblyQualifiedName == type.AssemblyQualifiedName))
			{
				Log.Error($"Tried to load duplicate plugin: {type}");
				return;
			}

			if (type.IsDefined(typeof(PluginAttribute), true))
			{
				PluginAttribute pluginAttribute = Attribute.GetCustomAttribute(type, typeof(PluginAttribute), true) as PluginAttribute;
				if (pluginAttribute != null)
				{
					if (!Config.GetProperty(pluginAttribute.PluginName + ".Enabled", true))
						return;
				}
			}

			LoadPlugin(plugin, type);
		}

		private void LoadPlugin(object plugin, Type type)
		{
			_plugins.Add(plugin);
			LoadCommands(type);
			Commands = GenerateCommandSet(_pluginCommands.Keys.ToArray());
			LoadPacketHandlers(type);
			Log.Debug($"Loaded plugin {type}");
		}

		public event ResolveEventHandler AssemblyResolve;

		private Assembly MyResolveEventHandler(object sender, ResolveEventArgs args)
		{
			var assembly = AssemblyResolve?.Invoke(sender, args);

			if (assembly != null) return assembly;
			if (_currentPath == null) return null;

			try
			{
				AssemblyName name = new AssemblyName(args.Name);
				string assemblyPath = _currentPath + "\\" + name.Name + ".dll";
				return Assembly.LoadFile(assemblyPath);
			}
			catch (Exception)
			{
				try
				{
					AssemblyName name = new AssemblyName(args.Name);
					string assemblyPath = _currentPath + "\\" + name.Name + ".exe";
					return Assembly.LoadFile(assemblyPath);
				}
				catch (Exception)
				{
					return Assembly.LoadFile(args.Name + ".dll");
				}
{'fraction_non_alphanumeric': 0.10057578703357511, 'fraction_numerical': 0.0028415463994616017, 'mean_word_length': 3.57543619568936, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 36, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7307554', 'n_tokens_mistral': 4544, 'n_tokens_neox': 3527, 'n_words': 1122}