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); } } }