mirror of
https://github.com/duplicati/duplicati.git
synced 2026-05-06 23:29:31 -04:00
249 lines
10 KiB
C#
249 lines
10 KiB
C#
// Copyright (C) 2024, The Duplicati Team
|
|
// https://duplicati.com, hello@duplicati.com
|
|
//
|
|
// 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.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using Duplicati.Library.AutoUpdater;
|
|
using Duplicati.Library.Interface;
|
|
|
|
namespace Duplicati.Library.DynamicLoader
|
|
{
|
|
/// <summary>
|
|
/// This class supports dynamic loading of instances of a given interface
|
|
/// </summary>
|
|
/// <typeparam name="T">The interface that the class loads</typeparam>
|
|
internal abstract class DynamicLoader<T> where T : class, IDynamicModule
|
|
{
|
|
/// <summary>
|
|
/// The tag used for logging
|
|
/// </summary>
|
|
private static readonly string LOGTAG = Logging.Log.LogTagFromType<DynamicLoader<T>>();
|
|
|
|
/// <summary>
|
|
/// A lock used to guarantee threadsafe access to the interface lookup table
|
|
/// </summary>
|
|
protected readonly object m_lock = new object();
|
|
|
|
/// <summary>
|
|
/// A cached list of interfaces
|
|
/// </summary>
|
|
protected Dictionary<string, T> m_interfaces;
|
|
|
|
/// <summary>
|
|
/// List of supported commands
|
|
/// </summary>
|
|
protected Dictionary<Type, IReadOnlyList<ICommandLineArgument>> m_supportedCommands = new();
|
|
|
|
/// <summary>
|
|
/// Function to extract the key value from the interface
|
|
/// </summary>
|
|
/// <param name="item">The interface to extract the key from</param>
|
|
/// <returns>The key for the interface</returns>
|
|
protected abstract string GetInterfaceKey(T item);
|
|
|
|
/// <summary>
|
|
/// Gets a list of subfolders to search for interfaces
|
|
/// </summary>
|
|
protected abstract string[] Subfolders { get; }
|
|
|
|
/// <summary>
|
|
/// The list of statically included modules
|
|
/// </summary>
|
|
protected abstract IEnumerable<T> BuiltInModules { get; }
|
|
|
|
/// <summary>
|
|
/// Construct a new instance of the dynamic loader,
|
|
/// does not load anything
|
|
/// </summary>
|
|
protected DynamicLoader()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides threadsafe doublelocking loading of the interface table
|
|
/// </summary>
|
|
protected void LoadInterfaces()
|
|
{
|
|
if (m_interfaces == null)
|
|
lock (m_lock)
|
|
if (m_interfaces == null)
|
|
{
|
|
var interfaces = new Dictionary<string, T>();
|
|
// When loading, inject the built-ins first, so they can be replaced by subfolder matches
|
|
foreach (T b in BuiltInModules)
|
|
interfaces[GetInterfaceKey(b)] = b;
|
|
|
|
// When loading, the subfolder matches are placed last in the
|
|
// resulting list, and thus applied last to the lookup,
|
|
// meaning that they can replace the built-in versions
|
|
foreach (T b in FindInterfaceImplementors(Subfolders))
|
|
interfaces[GetInterfaceKey(b)] = b;
|
|
|
|
m_interfaces = interfaces;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Searches the base folder of this dll for classes in other assemblies that
|
|
/// implements a certain interface, and has a default constructor
|
|
/// </summary>
|
|
/// <param name="additionalfolders">Any additional folders besides the assembly path to search in</param>
|
|
/// <returns>A list of instanciated classes which implements the interface</returns>
|
|
private IEnumerable<T> FindInterfaceImplementors(string[] additionalfolders)
|
|
{
|
|
var interfaces = new List<T>();
|
|
|
|
// Search in these folders for modules
|
|
var root_paths = new[] {
|
|
Path.Combine(UpdaterManager.INSTALLATIONDIR, "modules"),
|
|
Path.Combine(
|
|
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) ?? string.Empty,
|
|
AutoUpdateSettings.AppName,
|
|
"modules"
|
|
),
|
|
Environment.GetEnvironmentVariable($"{AutoUpdateSettings.AppName}_MODULE_PATH")
|
|
}
|
|
.Where(x => !string.IsNullOrEmpty(x) && Directory.Exists(x));
|
|
|
|
// In each folder we look, search only in the subfolders containing the modules
|
|
var files = new List<string>();
|
|
foreach (var path in root_paths)
|
|
{
|
|
if (additionalfolders != null)
|
|
foreach (string s in additionalfolders)
|
|
{
|
|
string subpath = System.IO.Path.Combine(path, s);
|
|
if (System.IO.Directory.Exists(subpath))
|
|
files.AddRange(System.IO.Directory.GetFiles(subpath, "*.dll"));
|
|
}
|
|
}
|
|
|
|
foreach (string s in files)
|
|
{
|
|
try
|
|
{
|
|
//NOTE: This is pretty nifty, due to the use of assembly redirect and LoadFile, we can
|
|
// actually end up loading multiple versions of the same file (if it is present).
|
|
//Since the lookup dictionary applies the modules in the order returned
|
|
// and the subfolders are probed last, a module in the subfolder
|
|
// will take the place of a stock module, if both use same key
|
|
Assembly asm = Assembly.LoadFile(s);
|
|
if (asm != Assembly.GetExecutingAssembly())
|
|
{
|
|
foreach (Type t in asm.GetExportedTypes())
|
|
try
|
|
{
|
|
if (typeof(T).IsAssignableFrom(t) && t != typeof(T))
|
|
{
|
|
//TODO: Figure out how to support types with no default constructors
|
|
if (t.GetConstructor(Type.EmptyTypes) != null)
|
|
{
|
|
T i = Activator.CreateInstance(t) as T;
|
|
if (i != null)
|
|
interfaces.Add(i);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ex = GetActualException(ex);
|
|
Duplicati.Library.Logging.Log.WriteWarningMessage(LOGTAG, "SoftError", ex, Strings.DynamicLoader.DynamicTypeLoadError(t.FullName, s, ex.Message));
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ex = GetActualException(ex);
|
|
// Since this is locating the assemblies that have the proper interface, it isn't an error to not.
|
|
// This was loading the log with errors about additional DLL's that are not plugins and do not have manifests.
|
|
Duplicati.Library.Logging.Log.WriteExplicitMessage(LOGTAG, "HardError", ex, Strings.DynamicLoader.DynamicAssemblyLoadError(s, ex.Message));
|
|
}
|
|
}
|
|
|
|
return interfaces;
|
|
}
|
|
|
|
private Exception GetActualException(Exception ex)
|
|
{
|
|
if (ex is TargetInvocationException)
|
|
ex = (ex as TargetInvocationException).InnerException;
|
|
return ex;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a list of loaded interfaces
|
|
/// </summary>
|
|
public T[] Interfaces
|
|
{
|
|
get
|
|
{
|
|
LoadInterfaces();
|
|
lock (m_lock)
|
|
return new List<T>(m_interfaces.Values).ToArray();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a list of the keys of loaded interfaces
|
|
/// </summary>
|
|
public string[] Keys
|
|
{
|
|
get
|
|
{
|
|
LoadInterfaces();
|
|
lock (m_lock)
|
|
return new List<string>(m_interfaces.Keys).ToArray();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the supported commands from the item, applying caching
|
|
/// </summary>
|
|
/// <param name="item">The item to get the supported commands for</param>
|
|
/// <returns>The list of supported commands</returns>
|
|
protected IEnumerable<ICommandLineArgument> GetSupportedCommandsCached(T item)
|
|
{
|
|
var type = item.GetType();
|
|
lock (m_lock)
|
|
{
|
|
if (m_supportedCommands.TryGetValue(type, out var commands))
|
|
return commands;
|
|
|
|
return m_supportedCommands[type] = item.SupportedCommands.ToList().AsReadOnly();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper method to register a module
|
|
/// </summary>
|
|
/// <param name="module">The module to register</param>
|
|
public void AddModule(T module)
|
|
{
|
|
LoadInterfaces();
|
|
lock (m_lock)
|
|
m_interfaces[GetInterfaceKey(module)] = module;
|
|
}
|
|
}
|
|
}
|