Try your search with a different keyword or use * as a wildcard.
using System;
using System.Reflection;
using System.Text;
using Newtonsoft.Json;
using Nop.Core;
using Nop.Core.Infrastructure;
namespace Nop.Services.Plugins;
///
/// Represents an information about plugins
///
public partial class PluginsInfo : IPluginsInfo
{
#region Fields
protected const string OBSOLETE_FIELD = "Obsolete field, using only for compatibility";
protected List _installedPluginNames = new();
protected IList _installedPlugins = new List();
protected readonly INopFileProvider _fileProvider;
#endregion
#region Ctor
public PluginsInfo(INopFileProvider fileProvider)
{
_fileProvider = fileProvider ?? CommonHelper.DefaultFileProvider;
}
#endregion
#region Utilities
///
/// Get system names of installed plugins from obsolete file
///
///
/// A task that represents the asynchronous operation
/// The task result contains the list of plugin system names
///
protected virtual IList GetObsoleteInstalledPluginNames()
{
//check whether file exists
var filePath = _fileProvider.MapPath(NopPluginDefaults.InstalledPluginsFilePath);
if (!_fileProvider.FileExists(filePath))
{
//if not, try to parse the file that was used in previous nopCommerce versions
filePath = _fileProvider.MapPath(NopPluginDefaults.ObsoleteInstalledPluginsFilePath);
if (!_fileProvider.FileExists(filePath))
return new List();
//get plugin system names from the old txt file
var pluginSystemNames = new List();
using (var reader = new StringReader(_fileProvider.ReadAllText(filePath, Encoding.UTF8)))
{
string pluginName;
while ((pluginName = reader.ReadLine()) != null)
if (!string.IsNullOrWhiteSpace(pluginName))
pluginSystemNames.Add(pluginName.Trim());
}
//and delete the old one
_fileProvider.DeleteFile(filePath);
return pluginSystemNames;
}
var text = _fileProvider.ReadAllText(filePath, Encoding.UTF8);
if (string.IsNullOrEmpty(text))
return new List();
//delete the old file
_fileProvider.DeleteFile(filePath);
//get plugin system names from the JSON file
return JsonConvert.DeserializeObject>(text);
}
///
/// Deserialize PluginInfo from json
///
/// Json data of PluginInfo
/// True if data are loaded, otherwise False
protected virtual void DeserializePluginInfo(string json)
{
if (string.IsNullOrEmpty(json))
return;
var pluginsInfo = JsonConvert.DeserializeObject(json);
if (pluginsInfo == null)
return;
InstalledPluginNames = pluginsInfo.InstalledPluginNames;
InstalledPlugins = pluginsInfo.InstalledPlugins;
PluginNamesToUninstall = pluginsInfo.PluginNamesToUninstall;
PluginNamesToDelete = pluginsInfo.PluginNamesToDelete;
PluginNamesToInstall = pluginsInfo.PluginNamesToInstall;
}
///
/// Check whether the directory is a plugin directory
///
/// Directory name
/// Result of check
protected bool IsPluginDirectory(string directoryName)
{
if (string.IsNullOrEmpty(directoryName))
return false;
//get parent directory
var parent = _fileProvider.GetParentDirectory(directoryName);
if (string.IsNullOrEmpty(parent))
return false;
//directory is directly in plugins directory
if (!_fileProvider.GetDirectoryNameOnly(parent).Equals(NopPluginDefaults.PathName, StringComparison.InvariantCultureIgnoreCase))
return false;
return true;
}
///
/// Get list of description files-plugin descriptors pairs
///
/// Plugin directory name
/// Original and parsed description files
protected IList<(string DescriptionFile, PluginDescriptor PluginDescriptor)> GetDescriptionFilesAndDescriptors(string directoryName)
{
ArgumentException.ThrowIfNullOrEmpty(directoryName);
var result = new List<(string DescriptionFile, PluginDescriptor PluginDescriptor)>();
//try to find description files in the plugin directory
var files = _fileProvider.GetFiles(directoryName, NopPluginDefaults.DescriptionFileName, false);
//populate result list
foreach (var descriptionFile in files)
{
//skip files that are not in the plugin directory
if (!IsPluginDirectory(_fileProvider.GetDirectoryName(descriptionFile)))
continue;
//load plugin descriptor from the file
var text = _fileProvider.ReadAllText(descriptionFile, Encoding.UTF8);
var pluginDescriptor = PluginDescriptor.GetPluginDescriptorFromText(text);
result.Add((descriptionFile, pluginDescriptor));
}
//sort list by display order. NOTE: Lowest DisplayOrder will be first i.e 0 , 1, 1, 1, 5, 10
//it's required: https://www.nopcommerce.com/boards/topic/17455/load-plugins-based-on-their-displayorder-on-startup
result = result.OrderBy(item => item.PluginDescriptor.DisplayOrder).ToList();
return result;
}
#endregion
#region Methods
///
/// Get plugins info
///
///
/// The true if data are loaded, otherwise False
///
public virtual void LoadPluginInfo()
{
//check whether plugins info file exists
var filePath = _fileProvider.MapPath(NopPluginDefaults.PluginsInfoFilePath);
if (!_fileProvider.FileExists(filePath))
{
//file doesn't exist, so try to get only installed plugin names from the obsolete file
_installedPluginNames.AddRange(GetObsoleteInstalledPluginNames());
//and save info into a new file if need
if (_installedPluginNames.Any())
Save();
}
//try to get plugin info from the JSON file
var text = _fileProvider.FileExists(filePath)
? _fileProvider.ReadAllText(filePath, Encoding.UTF8)
: string.Empty;
DeserializePluginInfo(text);
var pluginDescriptors = new List<(PluginDescriptor pluginDescriptor, bool needToDeploy)>();
var incompatiblePlugins = new Dictionary();
//ensure plugins directory is created
var pluginsDirectory = _fileProvider.MapPath(NopPluginDefaults.Path);
_fileProvider.CreateDirectory(pluginsDirectory);
//load plugin descriptors from the plugin directory
foreach (var item in GetDescriptionFilesAndDescriptors(pluginsDirectory))
{
var descriptionFile = item.DescriptionFile;
var pluginDescriptor = item.PluginDescriptor;
//skip descriptor of plugin that is going to be deleted
if (PluginNamesToDelete.Contains(pluginDescriptor.SystemName))
continue;
//ensure that plugin is compatible with the current version
if (!pluginDescriptor.SupportedVersions.Contains(NopVersion.CURRENT_VERSION, StringComparer.InvariantCultureIgnoreCase))
{
incompatiblePlugins.Add(pluginDescriptor.SystemName, PluginIncompatibleType.NotCompatibleWithCurrentVersion);
continue;
}
//some more validation
if (string.IsNullOrEmpty(pluginDescriptor.SystemName?.Trim()))
throw new Exception($"A plugin '{descriptionFile}' has no system name. Try assigning the plugin a unique name and recompiling.");
if (pluginDescriptors.Any(p => p.pluginDescriptor.Equals(pluginDescriptor)))
throw new Exception($"A plugin with '{pluginDescriptor.SystemName}' system name is already defined");
//set 'Installed' property
pluginDescriptor.Installed = InstalledPlugins.Select(pd => pd.SystemName)
.Any(pluginName => pluginName.Equals(pluginDescriptor.SystemName, StringComparison.InvariantCultureIgnoreCase));
try
{
//try to get plugin directory
var pluginDirectory = _fileProvider.GetDirectoryName(descriptionFile);
if (string.IsNullOrEmpty(pluginDirectory))
throw new Exception($"Directory cannot be resolved for '{_fileProvider.GetFileName(descriptionFile)}' description file");
//get list of all library files in the plugin directory (not in the bin one)
pluginDescriptor.PluginFiles = _fileProvider.GetFiles(pluginDirectory, "*.dll", false)
.Where(file => IsPluginDirectory(_fileProvider.GetDirectoryName(file)))
.ToList();
//try to find a main plugin assembly file
var mainPluginFile = pluginDescriptor.PluginFiles.FirstOrDefault(file =>
{
var fileName = _fileProvider.GetFileName(file);
return fileName.Equals(pluginDescriptor.AssemblyFileName, StringComparison.InvariantCultureIgnoreCase);
});
//file with the specified name not found
if (mainPluginFile == null)
{
//so plugin is incompatible
incompatiblePlugins.Add(pluginDescriptor.SystemName, PluginIncompatibleType.MainAssemblyNotFound);
continue;
}
var pluginName = pluginDescriptor.SystemName;
//if it's found, set it as original assembly file
pluginDescriptor.OriginalAssemblyFile = mainPluginFile;
//need to deploy if plugin is already installed
var needToDeploy = InstalledPlugins.Select(pd => pd.SystemName).Contains(pluginName);
//also, deploy if the plugin is only going to be installed now
needToDeploy = needToDeploy || PluginNamesToInstall.Any(pluginInfo => pluginInfo.SystemName.Equals(pluginName));
//finally, exclude from deploying the plugin that is going to be deleted
needToDeploy = needToDeploy && !PluginNamesToDelete.Contains(pluginName);
//mark plugin as successfully deployed
pluginDescriptors.Add((pluginDescriptor, needToDeploy));
}
catch (ReflectionTypeLoadException exception)
{
//get all loader exceptions
var error = exception.LoaderExceptions.Aggregate($"Plugin '{pluginDescriptor.FriendlyName}'. ",
(message, nextMessage) => $"{message}{nextMessage?.Message ?? string.Empty}{Environment.NewLine}");
throw new Exception(error, exception);
}
catch (Exception exception)
{
//add a plugin name, this way we can easily identify a problematic plugin
throw new Exception($"Plugin '{pluginDescriptor.FriendlyName}'. {exception.Message}", exception);
}
}
IncompatiblePlugins = incompatiblePlugins;
PluginDescriptors = pluginDescriptors;
}
///
/// Save plugins info to the file
///
/// A task that represents the asynchronous operation
public virtual async Task SaveAsync()
{
//save the file
var filePath = _fileProvider.MapPath(NopPluginDefaults.PluginsInfoFilePath);
var text = JsonConvert.SerializeObject(this, Formatting.Indented);
await _fileProvider.WriteAllTextAsync(filePath, text, Encoding.UTF8);
}
///
/// Save plugins info to the file
///
public virtual void Save()
{
//save the file
var filePath = _fileProvider.MapPath(NopPluginDefaults.PluginsInfoFilePath);
var text = JsonConvert.SerializeObject(this, Formatting.Indented);
_fileProvider.WriteAllText(filePath, text, Encoding.UTF8);
}
///
/// Create copy from another instance of IPluginsInfo interface
///
/// Plugins info
public virtual void CopyFrom(IPluginsInfo pluginsInfo)
{
InstalledPlugins = pluginsInfo.InstalledPlugins?.ToList() ?? new List();
PluginNamesToUninstall = pluginsInfo.PluginNamesToUninstall?.ToList() ?? new List();
PluginNamesToDelete = pluginsInfo.PluginNamesToDelete?.ToList() ?? new List();
PluginNamesToInstall = pluginsInfo.PluginNamesToInstall?.ToList() ??
new List<(string SystemName, Guid? CustomerGuid)>();
AssemblyLoadedCollision = pluginsInfo.AssemblyLoadedCollision?.ToList();
PluginDescriptors = pluginsInfo.PluginDescriptors;
IncompatiblePlugins = pluginsInfo.IncompatiblePlugins?.ToDictionary(item => item.Key, item => item.Value);
}
#endregion
#region Properties
///
/// Gets or sets the list of all installed plugin names
///
public virtual IList InstalledPluginNames
{
get
{
if (_installedPlugins.Any())
_installedPluginNames.Clear();
return _installedPluginNames.Any() ? _installedPluginNames : [OBSOLETE_FIELD];
}
set
{
if (value?.Any() ?? false)
_installedPluginNames = value.ToList();
}
}
///
/// Gets or sets the list of all installed plugin
///
public virtual IList InstalledPlugins
{
get
{
if ((_installedPlugins?.Any() ?? false) || !_installedPluginNames.Any())
return _installedPlugins;
if (PluginDescriptors?.Any() ?? false)
_installedPlugins = PluginDescriptors
.Where(pd => _installedPluginNames.Any(pn =>
pn.Equals(pd.pluginDescriptor.SystemName, StringComparison.InvariantCultureIgnoreCase)))
.Select(pd => pd.pluginDescriptor as PluginDescriptorBaseInfo).ToList();
else
return _installedPluginNames
.Where(name => !name.Equals(OBSOLETE_FIELD, StringComparison.InvariantCultureIgnoreCase))
.Select(systemName => new PluginDescriptorBaseInfo { SystemName = systemName }).ToList();
return _installedPlugins;
}
set => _installedPlugins = value;
}
///
/// Gets or sets the list of plugin names which will be uninstalled
///
public virtual IList PluginNamesToUninstall { get; set; } = new List();
///
/// Gets or sets the list of plugin names which will be deleted
///
public virtual IList PluginNamesToDelete { get; set; } = new List();
///
/// Gets or sets the list of plugin names which will be installed
///
public virtual IList<(string SystemName, Guid? CustomerGuid)> PluginNamesToInstall { get; set; } =
new List<(string SystemName, Guid? CustomerGuid)>();
///
/// Gets or sets the list of plugin which are not compatible with the current version
///
///
/// Key - the system name of plugin.
/// Value - the reason of incompatibility.
///
[JsonIgnore]
public virtual IDictionary IncompatiblePlugins { get; set; }
///
/// Gets or sets the list of assembly loaded collisions
///
[JsonIgnore]
public virtual IList AssemblyLoadedCollision { get; set; }
///
/// Gets or sets a collection of plugin descriptors of all deployed plugins
///
[JsonIgnore]
public virtual IList<(PluginDescriptor pluginDescriptor, bool needToDeploy)> PluginDescriptors { get; set; }
#endregion
}