chr
2026-04-05 fe750b791d5b517cc4e9bc8e99a9a75139a0cfba
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
 
namespace OpenTap.Package
{
    /// <summary>
    /// Defined stages of a package. Used by <see cref="ICustomPackageAction"/> to implement actions to a stage.
    /// </summary>
    public enum PackageActionStage
    {
        /// <summary>
        /// Package install stage (e.g. running tap package install)
        /// </summary>
        Install,
        /// <summary>
        /// Package uninstall stage (e.g. running tap package uninstall)
        /// </summary>
        Uninstall,
        /// <summary>
        /// Package create stage (e.g. running tap package create)
        /// </summary>
        Create
    }
 
    /// <summary>
    /// Custom data elements in package.xml inside File elements, to be used for custom actions by <see cref="ICustomPackageAction"/> at predefined stages (<see cref="PackageActionStage"/>)
    /// </summary>
    public interface ICustomPackageData : ITapPlugin
    {
    }
 
    /// <summary>
    /// Argument for <see cref="ICustomPackageAction.Execute"/>.
    /// </summary>
    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
    }
 
 
    /// <summary>
    /// Custom actions for <see cref="ICustomPackageData"/> inside File element in package.xml files, to be executed at predefined stages (<see cref="PackageActionStage"/>)
    /// </summary>
    public interface ICustomPackageAction : ITapPlugin
    {
        /// <summary>
        /// The order of the action. Actions are executed in the order of lowest to highest.
        /// </summary>
        /// <returns></returns>
        int Order();
 
        /// <summary>
        /// At which stage the action should be executed
        /// </summary>
        PackageActionStage ActionStage { get; }
 
        /// <summary>
        /// Runs this custom action on a package. This is called after any normal operations associated with the given stage.
        /// </summary>
        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<ICustomPackageAction>()
                .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<Type> failedToLoadPlugins = new HashSet<Type>();
        internal static List<ICustomPackageData> GetAllData()
        {
            var packageData = new List<ICustomPackageData>();
 
            var plugins = PluginManager.GetPlugins<ICustomPackageData>();
            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;
        }
    }
 
    /// <summary>
    /// Placeholder object that represents an unrecognized XML element under the File element in a package definition xml file (package.xml).
    /// </summary>
    public class MissingPackageData : ICustomPackageData
    {
        /// <summary>
        /// Default Constructor.
        /// </summary>
        public MissingPackageData()
        {
 
        }
 
        /// <summary>
        /// Constructs a MissingPackageData given the unrecognized XML element.
        /// </summary>
        /// <param name="xmlElement"></param>
        public MissingPackageData(XElement xmlElement)
        {
            XmlElement = xmlElement ?? throw new ArgumentNullException(nameof(xmlElement));
        }
 
        /// <summary>
        /// The unrecognized XML element represented by this object.
        /// </summary>
        /// <value></value>
        public XElement XmlElement { get; set; }
 
        /// <summary>
        /// Returns the line in which the unrecognized XML element appears in the package definition xml file (package.xml).
        /// </summary>
        /// <returns></returns>
        public string GetLine()
        {
            if (XmlElement is IXmlLineInfo lineInfo && lineInfo.HasLineInfo())
                return lineInfo.LineNumber.ToString();
            else
                return "";
        }
 
        /// <summary>
        /// Queries the PluginManager to try to find a ICustomPackageData plugin that fits this XML element.
        /// </summary>
        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;
        }
    }
    
    /// <summary>
    /// Extension methods to help manage ICustomPackageData on PackageFile objects.
    /// </summary>
    public static class PackageFileExtensions
    {
        /// <summary>
        /// Returns if a specific custom data type is attached to the <see cref="PackageFile"/>.
        /// </summary>
        /// <typeparam name="T">The type that inherits from <see cref="ICustomPackageData"/></typeparam>
        /// <param name="file"></param>
        /// <returns>True if <see cref="PackageFile"/> has elements of specified custom types</returns>
        public static bool HasCustomData<T>(this PackageFile file) where T : ICustomPackageData
        {
            return file.CustomData.Any(s => s is T);
        }
 
        /// <summary>
        /// Returns all elements attached to the <see cref="PackageFile"/> of the specified custom data type.
        /// </summary>
        /// <typeparam name="T">The type that inherits from <see cref="ICustomPackageData"/></typeparam>
        /// <param name="file"></param>
        /// <returns>List of <see cref="ICustomPackageData"/></returns>
        public static IEnumerable<T> GetCustomData<T>(this PackageFile file) where T : ICustomPackageData
        {
            return file.CustomData.OfType<T>();
        }
 
        /// <summary>
        /// Removes all elements of a specific custom type that are attached to the <see cref="PackageFile"/>.
        /// </summary>
        /// <typeparam name="T">The type that inherits from <see cref="ICustomPackageData"/></typeparam>
        /// <param name="file"></param>
        public static void RemoveCustomData<T>(this PackageFile file) where T : ICustomPackageData
        {
            file.CustomData.RemoveIf(s => s is T);
        }
    }
 
}