using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
namespace OpenTap.Package
{
///
/// Defined stages of a package. Used by to implement actions to a stage.
///
public enum PackageActionStage
{
///
/// Package install stage (e.g. running tap package install)
///
Install,
///
/// Package uninstall stage (e.g. running tap package uninstall)
///
Uninstall,
///
/// Package create stage (e.g. running tap package create)
///
Create
}
///
/// Custom data elements in package.xml inside File elements, to be used for custom actions by at predefined stages ()
///
public interface ICustomPackageData : ITapPlugin
{
}
///
/// Argument for .
///
public class CustomPackageActionArgs
{
#pragma warning disable 1591 // TODO: Add XML Comments in this file, then remove this
public CustomPackageActionArgs(string temporaryDirectory, bool forceAction)
{
TemporaryDirectory = temporaryDirectory;
ForceAction = forceAction;
}
public string TemporaryDirectory { get; }
public bool ForceAction { get; } = false;
#pragma warning restore 1591 // TODO: Add XML Comments in this file, then remove this
}
///
/// Custom actions for inside File element in package.xml files, to be executed at predefined stages ()
///
public interface ICustomPackageAction : ITapPlugin
{
///
/// The order of the action. Actions are executed in the order of lowest to highest.
///
///
int Order();
///
/// At which stage the action should be executed
///
PackageActionStage ActionStage { get; }
///
/// Runs this custom action on a package. This is called after any normal operations associated with the given stage.
///
bool Execute(PackageDef package, CustomPackageActionArgs customActionArgs);
}
internal static class CustomPackageActionHelper
{
static TraceSource log = OpenTap.Log.CreateSource("Package");
internal static void RunCustomActions(PackageDef package, PackageActionStage stage, CustomPackageActionArgs args)
{
var customActions = TypeData.GetDerivedTypes()
.Where(s => s.CanCreateInstance)
.TrySelect(s => s.CreateInstance() as ICustomPackageAction, (ex, s) =>
{
log.Warning($"Failed to instantiate type '{s.Name}'.");
log.Debug(ex);
})
.Where(s => s != null)
.Where(w => w.ActionStage == stage)
.OrderBy(p => p.Order())
.ToList();
if (customActions.Count == 0)
{
log.Debug($"Found no custom actions to run at '{stage.ToString().ToLower()}' stage.");
return;
}
log.Debug($"Available custom actions for '{stage.ToString().ToLower()}' stage. ({customActions.Count} actions: {string.Join(", ", customActions.Select(s => s.ToString()))})");
foreach (ICustomPackageAction action in customActions)
{
Stopwatch timer = Stopwatch.StartNew();
try
{
if (action.Execute(package, args))
{
log.Info(timer, $"Package action {action.GetType().Name} completed");
continue;
}
}
catch (Exception ex)
{
log.Warning(timer, $"Package action {action.ToString()} failed", ex);
throw;
}
}
}
static HashSet failedToLoadPlugins = new HashSet();
internal static List GetAllData()
{
var packageData = new List();
var plugins = PluginManager.GetPlugins();
foreach (var plugin in plugins)
{
try
{
if (failedToLoadPlugins.Contains(plugin))
continue;
ICustomPackageData customData = (ICustomPackageData)Activator.CreateInstance(plugin);
packageData.Add(customData);
}
catch (Exception ex)
{
failedToLoadPlugins.Add(plugin);
log.Warning($"Failed to instantiate {plugin}. Skipping plugin.");
log.Debug(ex);
}
}
return packageData;
}
}
///
/// Placeholder object that represents an unrecognized XML element under the File element in a package definition xml file (package.xml).
///
public class MissingPackageData : ICustomPackageData
{
///
/// Default Constructor.
///
public MissingPackageData()
{
}
///
/// Constructs a MissingPackageData given the unrecognized XML element.
///
///
public MissingPackageData(XElement xmlElement)
{
XmlElement = xmlElement ?? throw new ArgumentNullException(nameof(xmlElement));
}
///
/// The unrecognized XML element represented by this object.
///
///
public XElement XmlElement { get; set; }
///
/// Returns the line in which the unrecognized XML element appears in the package definition xml file (package.xml).
///
///
public string GetLine()
{
if (XmlElement is IXmlLineInfo lineInfo && lineInfo.HasLineInfo())
return lineInfo.LineNumber.ToString();
else
return "";
}
///
/// Queries the PluginManager to try to find a ICustomPackageData plugin that fits this XML element.
///
public bool TryResolve(out ICustomPackageData customPackageData)
{
customPackageData = this;
var handlingPlugins = CustomPackageActionHelper.GetAllData().Where(s => s.GetType().GetDisplayAttribute().Name == XmlElement.Name.LocalName).ToList();
if (handlingPlugins != null && handlingPlugins.Count() > 0)
{
ICustomPackageData p = handlingPlugins.FirstOrDefault();
if (XmlElement.HasAttributes || !XmlElement.IsEmpty) { }
new TapSerializer().Deserialize(XmlElement, o => p = (ICustomPackageData)o, p.GetType());
customPackageData = p;
return true;
}
return false;
}
}
///
/// Extension methods to help manage ICustomPackageData on PackageFile objects.
///
public static class PackageFileExtensions
{
///
/// Returns if a specific custom data type is attached to the .
///
/// The type that inherits from
///
/// True if has elements of specified custom types
public static bool HasCustomData(this PackageFile file) where T : ICustomPackageData
{
return file.CustomData.Any(s => s is T);
}
///
/// Returns all elements attached to the of the specified custom data type.
///
/// The type that inherits from
///
/// List of
public static IEnumerable GetCustomData(this PackageFile file) where T : ICustomPackageData
{
return file.CustomData.OfType();
}
///
/// Removes all elements of a specific custom type that are attached to the .
///
/// The type that inherits from
///
public static void RemoveCustomData(this PackageFile file) where T : ICustomPackageData
{
file.CustomData.RemoveIf(s => s is T);
}
}
}