#region Disclaimer / License
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
#endregion
using System;
using System.Collections.Generic;
using System.Text;
namespace Duplicati.Library.DynamicLoader
{
///
/// This class supports dynamic loading of instances of a given interface
///
/// The interface that the class loads
internal abstract class DynamicLoader where T : class
{
///
/// The tag used for logging
///
private static readonly string LOGTAG = Logging.Log.LogTagFromType>();
///
/// A lock used to guarantee threadsafe access to the interface lookup table
///
protected readonly object m_lock = new object();
///
/// A cached list of interfaces
///
protected Dictionary m_interfaces;
///
/// Function to extract the key value from the interface
///
/// The interface to extract the key from
/// The key for the interface
protected abstract string GetInterfaceKey(T item);
///
/// Gets a list of subfolders to search for interfaces
///
protected abstract string[] Subfolders { get; }
///
/// Construcst a new instance of the dynamic loader,
/// does not load anything
///
protected DynamicLoader()
{
}
///
/// Provides threadsafe doublelocking loading of the interface table
///
protected void LoadInterfaces()
{
if (m_interfaces == null)
lock (m_lock)
if (m_interfaces == null)
{
Dictionary interfaces = new Dictionary();
//When loading, the subfolder matches are places last in the
// resulting list, and thus applied last to the lookup,
// meaning that they can replace the stock versions
foreach (T b in FindInterfaceImplementors(Subfolders))
interfaces[GetInterfaceKey(b)] = b;
m_interfaces = interfaces;
}
}
///
/// Searches the base folder of this dll for classes in other assemblies that
/// implements a certain interface, and has a default constructor
///
/// Any additional folders besides the assembly path to search in
/// A list of instanciated classes which implements the interface
private List FindInterfaceImplementors(string[] additionalfolders)
{
List interfaces = new List();
string path = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
List files = new List();
files.AddRange(System.IO.Directory.GetFiles(path, "*.dll"));
//We can override with subfolders
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
System.Reflection.Assembly asm = System.Reflection.Assembly.LoadFile(s);
if (asm != System.Reflection.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)
{
Duplicati.Library.Logging.Log.WriteWarningMessage(LOGTAG, "SoftError", ex, Strings.DynamicLoader.DynamicTypeLoadError(t.FullName, s, ex.Message));
}
}
}
catch(Exception 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;
}
///
/// Gets a list of loaded interfaces
///
public T[] Interfaces
{
get
{
LoadInterfaces();
lock(m_lock)
return new List(m_interfaces.Values).ToArray();
}
}
///
/// Gets a list of the keys of loaded interfaces
///
public string[] Keys
{
get
{
LoadInterfaces();
lock (m_lock)
return new List(m_interfaces.Keys).ToArray();
}
}
}
}