From 810558140f9ca3541b7709f2791add0b6db817e1 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 29 Apr 2023 23:25:05 -0400 Subject: [PATCH 1/2] Introduce static AssetLoader --- src/Avalonia.Base/Platform/AssetLoader.cs | 295 ++---------------- .../Platform/StandardAssetLoader.cs | 256 +++++++++++++++ .../StandardRuntimePlatformServices.cs | 2 +- .../AssetLoaderTests.cs | 23 +- .../Styling/ResourceBenchmarks.cs | 2 +- .../Xaml/StyleIncludeTests.cs | 2 +- tests/Avalonia.RenderTests/TestBase.cs | 2 +- tests/Avalonia.UnitTests/TestServices.cs | 8 +- 8 files changed, 304 insertions(+), 286 deletions(-) create mode 100644 src/Avalonia.Base/Platform/StandardAssetLoader.cs diff --git a/src/Avalonia.Base/Platform/AssetLoader.cs b/src/Avalonia.Base/Platform/AssetLoader.cs index 7df446e854..854610f1c9 100644 --- a/src/Avalonia.Base/Platform/AssetLoader.cs +++ b/src/Avalonia.Base/Platform/AssetLoader.cs @@ -1,281 +1,48 @@ -using System; +using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Linq; using System.Reflection; -#if !BUILDTASK -using Avalonia.Platform.Internal; -using Avalonia.Utilities; -#endif -namespace Avalonia.Platform -{ - /// - /// Loads assets compiled into the application binary. - /// - public class AssetLoader +namespace Avalonia.Platform; + #if !BUILDTASK - : IAssetLoader +/// #endif - { +public static class AssetLoader +{ #if !BUILDTASK - private static IAssemblyDescriptorResolver s_assemblyDescriptorResolver = new AssemblyDescriptorResolver(); - - private AssemblyDescriptor? _defaultResmAssembly; - - /// - /// Introduced for tests. - /// - internal static void SetAssemblyDescriptorResolver(IAssemblyDescriptorResolver resolver) => - s_assemblyDescriptorResolver = resolver; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The default assembly from which to load resm: assets for which no assembly is specified. - /// - public AssetLoader(Assembly? assembly = null) - { - if (assembly == null) - assembly = Assembly.GetEntryAssembly(); - if (assembly != null) - _defaultResmAssembly = new AssemblyDescriptor(assembly); - } - - /// - /// Sets the default assembly from which to load assets for which no assembly is specified. - /// - /// The default assembly. - public void SetDefaultAssembly(Assembly assembly) - { - _defaultResmAssembly = new AssemblyDescriptor(assembly); - } - - /// - /// Checks if an asset with the specified URI exists. - /// - /// The URI. - /// - /// A base URI to use if is relative. - /// - /// True if the asset could be found; otherwise false. - public bool Exists(Uri uri, Uri? baseUri = null) - { - return TryGetAsset(uri, baseUri, out _); - } - - /// - /// Opens the asset with the requested URI. - /// - /// The URI. - /// - /// A base URI to use if is relative. - /// - /// A stream containing the asset contents. - /// - /// The asset could not be found. - /// - public Stream Open(Uri uri, Uri? baseUri = null) => OpenAndGetAssembly(uri, baseUri).Item1; - - /// - /// Opens the asset with the requested URI and returns the asset stream and the - /// assembly containing the asset. - /// - /// The URI. - /// - /// A base URI to use if is relative. - /// - /// - /// The stream containing the resource contents together with the assembly. - /// - /// - /// The asset could not be found. - /// - public (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri? baseUri = null) - { - if (TryGetAsset(uri, baseUri, out var assetDescriptor)) - { - return (assetDescriptor.GetStream(), assetDescriptor.Assembly); - } - - throw new FileNotFoundException($"The resource {uri} could not be found."); - } - - public Assembly? GetAssembly(Uri uri, Uri? baseUri) - { - if (!uri.IsAbsoluteUri && baseUri != null) - { - uri = new Uri(baseUri, uri); - } - - if (TryGetAssembly(uri, out var assemblyDescriptor)) - { - return assemblyDescriptor.Assembly; - } - - return null; - } - - /// - /// Gets all assets of a folder and subfolders that match specified uri. - /// - /// The URI. - /// Base URI that is used if is relative. - /// All matching assets as a tuple of the absolute path to the asset and the assembly containing the asset - public IEnumerable GetAssets(Uri uri, Uri? baseUri) - { - if (uri.IsAbsoluteResm()) - { - if (!TryGetAssembly(uri, out var assembly)) - { - assembly = _defaultResmAssembly; - } - - return assembly?.Resources? - .Where(x => x.Key.Contains(uri.GetUnescapeAbsolutePath())) - .Select(x => new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ?? - Enumerable.Empty(); - } - - uri = uri.EnsureAbsolute(baseUri); - - if (uri.IsAvares()) - { - if (!TryGetResAsmAndPath(uri, out var assembly, out var path)) - { - return Enumerable.Empty(); - } + private static IAssetLoader GetAssetLoader() => AvaloniaLocator.Current.GetRequiredService(); - if (assembly?.AvaloniaResources == null) - { - return Enumerable.Empty(); - } + /// + public static void SetDefaultAssembly(Assembly assembly) => GetAssetLoader().SetDefaultAssembly(assembly); - if (path.Length > 0 && path[path.Length - 1] != '/') - { - path += '/'; - } + /// + public static bool Exists(Uri uri, Uri? baseUri = null) => GetAssetLoader().Exists(uri, baseUri); - return assembly.AvaloniaResources - .Where(r => r.Key.StartsWith(path, StringComparison.Ordinal)) - .Select(x => new Uri($"avares://{assembly.Name}{x.Key}")); - } + /// + public static Stream Open(Uri uri, Uri? baseUri = null) => GetAssetLoader().Open(uri, baseUri); - return Enumerable.Empty(); - } + /// + public static (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri? baseUri = null) + => GetAssetLoader().OpenAndGetAssembly(uri, baseUri); - private bool TryGetAsset(Uri uri, Uri? baseUri, [NotNullWhen(true)] out IAssetDescriptor? assetDescriptor) - { - assetDescriptor = null; + /// + public static Assembly? GetAssembly(Uri uri, Uri? baseUri = null) + => GetAssetLoader().GetAssembly(uri, baseUri); - if (uri.IsAbsoluteResm()) - { - if (!TryGetAssembly(uri, out var assembly) && !TryGetAssembly(baseUri, out assembly)) - { - assembly = _defaultResmAssembly; - } - - if (assembly?.Resources != null) - { - var resourceKey = uri.AbsolutePath; - - if (assembly.Resources.TryGetValue(resourceKey, out assetDescriptor)) - { - return true; - } - } - } - - uri = uri.EnsureAbsolute(baseUri); - - if (uri.IsAvares()) - { - if (TryGetResAsmAndPath(uri, out var assembly, out var path)) - { - if (assembly.AvaloniaResources == null) - { - return false; - } - - if (assembly.AvaloniaResources.TryGetValue(path, out assetDescriptor)) - { - return true; - } - } - } - - return false; - } - - private static bool TryGetResAsmAndPath(Uri uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly, out string path) - { - path = uri.GetUnescapeAbsolutePath(); - - if (TryLoadAssembly(uri.Authority, out assembly)) - { - return true; - } - - return false; - } - - private static bool TryGetAssembly(Uri? uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly) - { - assembly = null; - - if (uri != null) - { - if (!uri.IsAbsoluteUri) - { - return false; - } - - if (uri.IsAvares() && TryGetResAsmAndPath(uri, out assembly, out _)) - { - return true; - } - - if (uri.IsResm()) - { - var assemblyName = uri.GetAssemblyNameFromQuery(); - - if (assemblyName.Length > 0 && TryLoadAssembly(assemblyName, out assembly)) - { - return true; - } - } - } - - return false; - } - - private static bool TryLoadAssembly(string assemblyName, [NotNullWhen(true)] out IAssemblyDescriptor? assembly) - { - assembly = null; - - try - { - assembly = s_assemblyDescriptorResolver.GetAssembly(assemblyName); - - return true; - } - catch (Exception) { } - - return false; - } + /// + public static IEnumerable GetAssets(Uri uri, Uri? baseUri) + => GetAssetLoader().GetAssets(uri, baseUri); #endif - public static void RegisterResUriParsers() - { - if (!UriParser.IsKnownScheme("avares")) - UriParser.Register(new GenericUriParser( - GenericUriParserOptions.GenericAuthority | - GenericUriParserOptions.NoUserInfo | - GenericUriParserOptions.NoPort | - GenericUriParserOptions.NoQuery | - GenericUriParserOptions.NoFragment), "avares", -1); - } + internal static void RegisterResUriParsers() + { + if (!UriParser.IsKnownScheme("avares")) + UriParser.Register(new GenericUriParser( + GenericUriParserOptions.GenericAuthority | + GenericUriParserOptions.NoUserInfo | + GenericUriParserOptions.NoPort | + GenericUriParserOptions.NoQuery | + GenericUriParserOptions.NoFragment), "avares", -1); } } diff --git a/src/Avalonia.Base/Platform/StandardAssetLoader.cs b/src/Avalonia.Base/Platform/StandardAssetLoader.cs new file mode 100644 index 0000000000..387f77f59b --- /dev/null +++ b/src/Avalonia.Base/Platform/StandardAssetLoader.cs @@ -0,0 +1,256 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Reflection; +using Avalonia.Platform.Internal; +using Avalonia.Utilities; + +namespace Avalonia.Platform +{ + /// + /// Loads assets compiled into the application binary. + /// + internal class StandardAssetLoader : IAssetLoader + { + private readonly IAssemblyDescriptorResolver _assemblyDescriptorResolver; + private AssemblyDescriptor? _defaultResmAssembly; + + public StandardAssetLoader(IAssemblyDescriptorResolver resolver, Assembly? assembly = null) + { + if (assembly == null) + assembly = Assembly.GetEntryAssembly(); + if (assembly != null) + _defaultResmAssembly = new AssemblyDescriptor(assembly); + _assemblyDescriptorResolver = resolver; + } + + public StandardAssetLoader(Assembly? assembly = null) : this(new AssemblyDescriptorResolver(), assembly) + { + + } + + /// + /// Sets the default assembly from which to load assets for which no assembly is specified. + /// + /// The default assembly. + public void SetDefaultAssembly(Assembly assembly) + { + _defaultResmAssembly = new AssemblyDescriptor(assembly); + } + + /// + /// Checks if an asset with the specified URI exists. + /// + /// The URI. + /// + /// A base URI to use if is relative. + /// + /// True if the asset could be found; otherwise false. + public bool Exists(Uri uri, Uri? baseUri = null) + { + return TryGetAsset(uri, baseUri, out _); + } + + /// + /// Opens the asset with the requested URI. + /// + /// The URI. + /// + /// A base URI to use if is relative. + /// + /// A stream containing the asset contents. + /// + /// The asset could not be found. + /// + public Stream Open(Uri uri, Uri? baseUri = null) => OpenAndGetAssembly(uri, baseUri).Item1; + + /// + /// Opens the asset with the requested URI and returns the asset stream and the + /// assembly containing the asset. + /// + /// The URI. + /// + /// A base URI to use if is relative. + /// + /// + /// The stream containing the resource contents together with the assembly. + /// + /// + /// The asset could not be found. + /// + public (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri? baseUri = null) + { + if (TryGetAsset(uri, baseUri, out var assetDescriptor)) + { + return (assetDescriptor.GetStream(), assetDescriptor.Assembly); + } + + throw new FileNotFoundException($"The resource {uri} could not be found."); + } + + public Assembly? GetAssembly(Uri uri, Uri? baseUri) + { + if (!uri.IsAbsoluteUri && baseUri != null) + { + uri = new Uri(baseUri, uri); + } + + if (TryGetAssembly(uri, out var assemblyDescriptor)) + { + return assemblyDescriptor.Assembly; + } + + return null; + } + + /// + /// Gets all assets of a folder and subfolders that match specified uri. + /// + /// The URI. + /// Base URI that is used if is relative. + /// All matching assets as a tuple of the absolute path to the asset and the assembly containing the asset + public IEnumerable GetAssets(Uri uri, Uri? baseUri) + { + if (uri.IsAbsoluteResm()) + { + if (!TryGetAssembly(uri, out var assembly)) + { + assembly = _defaultResmAssembly; + } + + return assembly?.Resources? + .Where(x => x.Key.Contains(uri.GetUnescapeAbsolutePath())) + .Select(x => new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ?? + Enumerable.Empty(); + } + + uri = uri.EnsureAbsolute(baseUri); + + if (uri.IsAvares()) + { + if (!TryGetResAsmAndPath(uri, out var assembly, out var path)) + { + return Enumerable.Empty(); + } + + if (assembly?.AvaloniaResources == null) + { + return Enumerable.Empty(); + } + + if (path.Length > 0 && path[path.Length - 1] != '/') + { + path += '/'; + } + + return assembly.AvaloniaResources + .Where(r => r.Key.StartsWith(path, StringComparison.Ordinal)) + .Select(x => new Uri($"avares://{assembly.Name}{x.Key}")); + } + + return Enumerable.Empty(); + } + + private bool TryGetAsset(Uri uri, Uri? baseUri, [NotNullWhen(true)] out IAssetDescriptor? assetDescriptor) + { + assetDescriptor = null; + + if (uri.IsAbsoluteResm()) + { + if (!TryGetAssembly(uri, out var assembly) && !TryGetAssembly(baseUri, out assembly)) + { + assembly = _defaultResmAssembly; + } + + if (assembly?.Resources != null) + { + var resourceKey = uri.AbsolutePath; + + if (assembly.Resources.TryGetValue(resourceKey, out assetDescriptor)) + { + return true; + } + } + } + + uri = uri.EnsureAbsolute(baseUri); + + if (uri.IsAvares()) + { + if (TryGetResAsmAndPath(uri, out var assembly, out var path)) + { + if (assembly.AvaloniaResources == null) + { + return false; + } + + if (assembly.AvaloniaResources.TryGetValue(path, out assetDescriptor)) + { + return true; + } + } + } + + return false; + } + + private bool TryGetResAsmAndPath(Uri uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly, out string path) + { + path = uri.GetUnescapeAbsolutePath(); + + if (TryLoadAssembly(uri.Authority, out assembly)) + { + return true; + } + + return false; + } + + private bool TryGetAssembly(Uri? uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly) + { + assembly = null; + + if (uri != null) + { + if (!uri.IsAbsoluteUri) + { + return false; + } + + if (uri.IsAvares() && TryGetResAsmAndPath(uri, out assembly, out _)) + { + return true; + } + + if (uri.IsResm()) + { + var assemblyName = uri.GetAssemblyNameFromQuery(); + + if (assemblyName.Length > 0 && TryLoadAssembly(assemblyName, out assembly)) + { + return true; + } + } + } + + return false; + } + + private bool TryLoadAssembly(string assemblyName, [NotNullWhen(true)] out IAssemblyDescriptor? assembly) + { + assembly = null; + + try + { + assembly = _assemblyDescriptorResolver.GetAssembly(assemblyName); + + return true; + } + catch (Exception) { } + + return false; + } + } +} diff --git a/src/Avalonia.Base/Platform/StandardRuntimePlatformServices.cs b/src/Avalonia.Base/Platform/StandardRuntimePlatformServices.cs index 0a36b4c9dd..800d9b390f 100644 --- a/src/Avalonia.Base/Platform/StandardRuntimePlatformServices.cs +++ b/src/Avalonia.Base/Platform/StandardRuntimePlatformServices.cs @@ -14,7 +14,7 @@ namespace Avalonia.Platform AssetLoader.RegisterResUriParsers(); AvaloniaLocator.CurrentMutable .Bind().ToConstant(standardPlatform) - .Bind().ToConstant(new AssetLoader(assembly)) + .Bind().ToConstant(new StandardAssetLoader(assembly)) .Bind().ToConstant( #if NET6_0_OR_GREATER new Net6Loader() diff --git a/tests/Avalonia.Base.UnitTests/AssetLoaderTests.cs b/tests/Avalonia.Base.UnitTests/AssetLoaderTests.cs index 894b6578e3..d840ea171e 100644 --- a/tests/Avalonia.Base.UnitTests/AssetLoaderTests.cs +++ b/tests/Avalonia.Base.UnitTests/AssetLoaderTests.cs @@ -7,8 +7,10 @@ using Xunit; namespace Avalonia.Base.UnitTests; -public class AssetLoaderTests : IDisposable +public class AssetLoaderTests { + private IAssemblyDescriptorResolver _resolver; + public class MockAssembly : Assembly { } private const string AssemblyNameWithWhitespace = "Awesome Library"; @@ -17,22 +19,20 @@ public class AssetLoaderTests : IDisposable public AssetLoaderTests() { - var resolver = Mock.Of(); + _resolver = Mock.Of(); var descriptor = CreateAssemblyDescriptor(AssemblyNameWithWhitespace); - Mock.Get(resolver).Setup(x => x.GetAssembly(AssemblyNameWithWhitespace)).Returns(descriptor); + Mock.Get(_resolver).Setup(x => x.GetAssembly(AssemblyNameWithWhitespace)).Returns(descriptor); descriptor = CreateAssemblyDescriptor(AssemblyNameWithNonAscii); - Mock.Get(resolver).Setup(x => x.GetAssembly(AssemblyNameWithNonAscii)).Returns(descriptor); - - AssetLoader.SetAssemblyDescriptorResolver(resolver); + Mock.Get(_resolver).Setup(x => x.GetAssembly(AssemblyNameWithNonAscii)).Returns(descriptor); } [Fact] public void AssemblyName_With_Whitespace_Should_Load_Resm() { var uri = new Uri($"resm:Avalonia.Base.UnitTests.Assets.something?assembly={AssemblyNameWithWhitespace}"); - var loader = new AssetLoader(); + var loader = new StandardAssetLoader(_resolver); var assemblyActual = loader.GetAssembly(uri, null); @@ -43,7 +43,7 @@ public class AssetLoaderTests : IDisposable public void AssemblyName_With_Non_ASCII_Should_Load_Avares() { var uri = new Uri($"avares://{AssemblyNameWithNonAscii}/Assets/something"); - var loader = new AssetLoader(); + var loader = new StandardAssetLoader(_resolver); var assemblyActual = loader.GetAssembly(uri, null); @@ -54,7 +54,7 @@ public class AssetLoaderTests : IDisposable public void Invalid_AssemblyName_Should_Yield_Empty_Enumerable() { var uri = new Uri($"avares://InvalidAssembly"); - var loader = new AssetLoader(); + var loader = new StandardAssetLoader(_resolver); var assemblyActual = loader.GetAssets(uri, null); @@ -71,9 +71,4 @@ public class AssetLoaderTests : IDisposable Mock.Get(descriptor).Setup(x => x.Assembly).Returns(assembly); return descriptor; } - - public void Dispose() - { - AssetLoader.SetAssemblyDescriptorResolver(new AssemblyDescriptorResolver()); - } } diff --git a/tests/Avalonia.Benchmarks/Styling/ResourceBenchmarks.cs b/tests/Avalonia.Benchmarks/Styling/ResourceBenchmarks.cs index b044bcde59..a32f98e462 100644 --- a/tests/Avalonia.Benchmarks/Styling/ResourceBenchmarks.cs +++ b/tests/Avalonia.Benchmarks/Styling/ResourceBenchmarks.cs @@ -17,7 +17,7 @@ namespace Avalonia.Benchmarks.Styling private static IDisposable CreateApp() { var services = new TestServices( - assetLoader: new AssetLoader(), + assetLoader: new StandardAssetLoader(), globalClock: new MockGlobalClock(), platform: new AppBuilder().RuntimePlatform, renderInterface: new MockPlatformRenderInterface(), diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs index 5d6d4a78e4..67e808221e 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs @@ -282,7 +282,7 @@ public class StyleIncludeTests public void StyleInclude_From_CodeBehind_Resolves_Compiled() { using var locatorScope = AvaloniaLocator.EnterScope(); - AvaloniaLocator.CurrentMutable.BindToSelf(new AssetLoader(GetType().Assembly)); + AvaloniaLocator.CurrentMutable.BindToSelf(new StandardAssetLoader(GetType().Assembly)); var sp = new TestServiceProvider(); var styleInclude = new StyleInclude(sp) diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs index 4732099d60..a925b4e60c 100644 --- a/tests/Avalonia.RenderTests/TestBase.cs +++ b/tests/Avalonia.RenderTests/TestBase.cs @@ -45,7 +45,7 @@ namespace Avalonia.Direct2D1.RenderTests private static readonly TestDispatcherImpl threadingInterface = new TestDispatcherImpl(); - private static readonly IAssetLoader assetLoader = new AssetLoader(); + private static readonly IAssetLoader assetLoader = new StandardAssetLoader(); static TestBase() { diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index 800abbc2c7..9b95e71d8c 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -20,7 +20,7 @@ namespace Avalonia.UnitTests public class TestServices { public static readonly TestServices StyledWindow = new TestServices( - assetLoader: new AssetLoader(), + assetLoader: new StandardAssetLoader(), platform: new StandardRuntimePlatform(), renderInterface: new MockPlatformRenderInterface(), standardCursorFactory: Mock.Of(), @@ -31,7 +31,7 @@ namespace Avalonia.UnitTests windowingPlatform: new MockWindowingPlatform()); public static readonly TestServices MockPlatformRenderInterface = new TestServices( - assetLoader: new AssetLoader(), + assetLoader: new StandardAssetLoader(), renderInterface: new MockPlatformRenderInterface(), fontManagerImpl: new MockFontManagerImpl(), textShaperImpl: new MockTextShaperImpl()); @@ -50,13 +50,13 @@ namespace Avalonia.UnitTests keyboardDevice: () => new KeyboardDevice(), keyboardNavigation: new KeyboardNavigationHandler(), inputManager: new InputManager(), - assetLoader: new AssetLoader(), + assetLoader: new StandardAssetLoader(), renderInterface: new MockPlatformRenderInterface(), fontManagerImpl: new MockFontManagerImpl(), textShaperImpl: new MockTextShaperImpl()); public static readonly TestServices TextServices = new TestServices( - assetLoader: new AssetLoader(), + assetLoader: new StandardAssetLoader(), renderInterface: new MockPlatformRenderInterface(), fontManagerImpl: new HarfBuzzFontManagerImpl(), textShaperImpl: new HarfBuzzTextShaperImpl()); From ae3931faa70b9b408cfe00906da01fe3449dcc86 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 29 Apr 2023 23:29:37 -0400 Subject: [PATCH 2/2] Since git decided to mark StandardAssetLoader file as a new, why not fix formatting in here anyway --- .../Platform/StandardAssetLoader.cs | 345 +++++++++--------- 1 file changed, 172 insertions(+), 173 deletions(-) diff --git a/src/Avalonia.Base/Platform/StandardAssetLoader.cs b/src/Avalonia.Base/Platform/StandardAssetLoader.cs index 387f77f59b..118e57c7af 100644 --- a/src/Avalonia.Base/Platform/StandardAssetLoader.cs +++ b/src/Avalonia.Base/Platform/StandardAssetLoader.cs @@ -7,250 +7,249 @@ using System.Reflection; using Avalonia.Platform.Internal; using Avalonia.Utilities; -namespace Avalonia.Platform +namespace Avalonia.Platform; + +/// +/// Loads assets compiled into the application binary. +/// +internal class StandardAssetLoader : IAssetLoader { + private readonly IAssemblyDescriptorResolver _assemblyDescriptorResolver; + private AssemblyDescriptor? _defaultResmAssembly; + + public StandardAssetLoader(IAssemblyDescriptorResolver resolver, Assembly? assembly = null) + { + if (assembly == null) + assembly = Assembly.GetEntryAssembly(); + if (assembly != null) + _defaultResmAssembly = new AssemblyDescriptor(assembly); + _assemblyDescriptorResolver = resolver; + } + + public StandardAssetLoader(Assembly? assembly = null) : this(new AssemblyDescriptorResolver(), assembly) + { + + } + /// - /// Loads assets compiled into the application binary. + /// Sets the default assembly from which to load assets for which no assembly is specified. /// - internal class StandardAssetLoader : IAssetLoader + /// The default assembly. + public void SetDefaultAssembly(Assembly assembly) { - private readonly IAssemblyDescriptorResolver _assemblyDescriptorResolver; - private AssemblyDescriptor? _defaultResmAssembly; + _defaultResmAssembly = new AssemblyDescriptor(assembly); + } - public StandardAssetLoader(IAssemblyDescriptorResolver resolver, Assembly? assembly = null) - { - if (assembly == null) - assembly = Assembly.GetEntryAssembly(); - if (assembly != null) - _defaultResmAssembly = new AssemblyDescriptor(assembly); - _assemblyDescriptorResolver = resolver; - } + /// + /// Checks if an asset with the specified URI exists. + /// + /// The URI. + /// + /// A base URI to use if is relative. + /// + /// True if the asset could be found; otherwise false. + public bool Exists(Uri uri, Uri? baseUri = null) + { + return TryGetAsset(uri, baseUri, out _); + } - public StandardAssetLoader(Assembly? assembly = null) : this(new AssemblyDescriptorResolver(), assembly) + /// + /// Opens the asset with the requested URI. + /// + /// The URI. + /// + /// A base URI to use if is relative. + /// + /// A stream containing the asset contents. + /// + /// The asset could not be found. + /// + public Stream Open(Uri uri, Uri? baseUri = null) => OpenAndGetAssembly(uri, baseUri).Item1; + + /// + /// Opens the asset with the requested URI and returns the asset stream and the + /// assembly containing the asset. + /// + /// The URI. + /// + /// A base URI to use if is relative. + /// + /// + /// The stream containing the resource contents together with the assembly. + /// + /// + /// The asset could not be found. + /// + public (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri? baseUri = null) + { + if (TryGetAsset(uri, baseUri, out var assetDescriptor)) { - + return (assetDescriptor.GetStream(), assetDescriptor.Assembly); } - /// - /// Sets the default assembly from which to load assets for which no assembly is specified. - /// - /// The default assembly. - public void SetDefaultAssembly(Assembly assembly) + throw new FileNotFoundException($"The resource {uri} could not be found."); + } + + public Assembly? GetAssembly(Uri uri, Uri? baseUri) + { + if (!uri.IsAbsoluteUri && baseUri != null) { - _defaultResmAssembly = new AssemblyDescriptor(assembly); + uri = new Uri(baseUri, uri); } - /// - /// Checks if an asset with the specified URI exists. - /// - /// The URI. - /// - /// A base URI to use if is relative. - /// - /// True if the asset could be found; otherwise false. - public bool Exists(Uri uri, Uri? baseUri = null) + if (TryGetAssembly(uri, out var assemblyDescriptor)) { - return TryGetAsset(uri, baseUri, out _); + return assemblyDescriptor.Assembly; } - /// - /// Opens the asset with the requested URI. - /// - /// The URI. - /// - /// A base URI to use if is relative. - /// - /// A stream containing the asset contents. - /// - /// The asset could not be found. - /// - public Stream Open(Uri uri, Uri? baseUri = null) => OpenAndGetAssembly(uri, baseUri).Item1; - - /// - /// Opens the asset with the requested URI and returns the asset stream and the - /// assembly containing the asset. - /// - /// The URI. - /// - /// A base URI to use if is relative. - /// - /// - /// The stream containing the resource contents together with the assembly. - /// - /// - /// The asset could not be found. - /// - public (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri? baseUri = null) + return null; + } + + /// + /// Gets all assets of a folder and subfolders that match specified uri. + /// + /// The URI. + /// Base URI that is used if is relative. + /// All matching assets as a tuple of the absolute path to the asset and the assembly containing the asset + public IEnumerable GetAssets(Uri uri, Uri? baseUri) + { + if (uri.IsAbsoluteResm()) { - if (TryGetAsset(uri, baseUri, out var assetDescriptor)) + if (!TryGetAssembly(uri, out var assembly)) { - return (assetDescriptor.GetStream(), assetDescriptor.Assembly); + assembly = _defaultResmAssembly; } - throw new FileNotFoundException($"The resource {uri} could not be found."); + return assembly?.Resources? + .Where(x => x.Key.Contains(uri.GetUnescapeAbsolutePath())) + .Select(x => new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ?? + Enumerable.Empty(); } - public Assembly? GetAssembly(Uri uri, Uri? baseUri) + uri = uri.EnsureAbsolute(baseUri); + + if (uri.IsAvares()) { - if (!uri.IsAbsoluteUri && baseUri != null) + if (!TryGetResAsmAndPath(uri, out var assembly, out var path)) { - uri = new Uri(baseUri, uri); + return Enumerable.Empty(); } - if (TryGetAssembly(uri, out var assemblyDescriptor)) + if (assembly?.AvaloniaResources == null) { - return assemblyDescriptor.Assembly; + return Enumerable.Empty(); } - return null; + if (path.Length > 0 && path[path.Length - 1] != '/') + { + path += '/'; + } + + return assembly.AvaloniaResources + .Where(r => r.Key.StartsWith(path, StringComparison.Ordinal)) + .Select(x => new Uri($"avares://{assembly.Name}{x.Key}")); } - /// - /// Gets all assets of a folder and subfolders that match specified uri. - /// - /// The URI. - /// Base URI that is used if is relative. - /// All matching assets as a tuple of the absolute path to the asset and the assembly containing the asset - public IEnumerable GetAssets(Uri uri, Uri? baseUri) + return Enumerable.Empty(); + } + + private bool TryGetAsset(Uri uri, Uri? baseUri, [NotNullWhen(true)] out IAssetDescriptor? assetDescriptor) + { + assetDescriptor = null; + + if (uri.IsAbsoluteResm()) { - if (uri.IsAbsoluteResm()) + if (!TryGetAssembly(uri, out var assembly) && !TryGetAssembly(baseUri, out assembly)) { - if (!TryGetAssembly(uri, out var assembly)) - { - assembly = _defaultResmAssembly; - } - - return assembly?.Resources? - .Where(x => x.Key.Contains(uri.GetUnescapeAbsolutePath())) - .Select(x => new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ?? - Enumerable.Empty(); + assembly = _defaultResmAssembly; } - uri = uri.EnsureAbsolute(baseUri); - - if (uri.IsAvares()) + if (assembly?.Resources != null) { - if (!TryGetResAsmAndPath(uri, out var assembly, out var path)) - { - return Enumerable.Empty(); - } - - if (assembly?.AvaloniaResources == null) - { - return Enumerable.Empty(); - } + var resourceKey = uri.AbsolutePath; - if (path.Length > 0 && path[path.Length - 1] != '/') + if (assembly.Resources.TryGetValue(resourceKey, out assetDescriptor)) { - path += '/'; + return true; } - - return assembly.AvaloniaResources - .Where(r => r.Key.StartsWith(path, StringComparison.Ordinal)) - .Select(x => new Uri($"avares://{assembly.Name}{x.Key}")); } - - return Enumerable.Empty(); } - private bool TryGetAsset(Uri uri, Uri? baseUri, [NotNullWhen(true)] out IAssetDescriptor? assetDescriptor) - { - assetDescriptor = null; + uri = uri.EnsureAbsolute(baseUri); - if (uri.IsAbsoluteResm()) + if (uri.IsAvares()) + { + if (TryGetResAsmAndPath(uri, out var assembly, out var path)) { - if (!TryGetAssembly(uri, out var assembly) && !TryGetAssembly(baseUri, out assembly)) + if (assembly.AvaloniaResources == null) { - assembly = _defaultResmAssembly; + return false; } - if (assembly?.Resources != null) + if (assembly.AvaloniaResources.TryGetValue(path, out assetDescriptor)) { - var resourceKey = uri.AbsolutePath; - - if (assembly.Resources.TryGetValue(resourceKey, out assetDescriptor)) - { - return true; - } + return true; } } + } - uri = uri.EnsureAbsolute(baseUri); + return false; + } - if (uri.IsAvares()) - { - if (TryGetResAsmAndPath(uri, out var assembly, out var path)) - { - if (assembly.AvaloniaResources == null) - { - return false; - } - - if (assembly.AvaloniaResources.TryGetValue(path, out assetDescriptor)) - { - return true; - } - } - } + private bool TryGetResAsmAndPath(Uri uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly, out string path) + { + path = uri.GetUnescapeAbsolutePath(); - return false; + if (TryLoadAssembly(uri.Authority, out assembly)) + { + return true; } - private bool TryGetResAsmAndPath(Uri uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly, out string path) + return false; + } + + private bool TryGetAssembly(Uri? uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly) + { + assembly = null; + + if (uri != null) { - path = uri.GetUnescapeAbsolutePath(); + if (!uri.IsAbsoluteUri) + { + return false; + } - if (TryLoadAssembly(uri.Authority, out assembly)) + if (uri.IsAvares() && TryGetResAsmAndPath(uri, out assembly, out _)) { return true; } - return false; - } - - private bool TryGetAssembly(Uri? uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly) - { - assembly = null; - - if (uri != null) + if (uri.IsResm()) { - if (!uri.IsAbsoluteUri) - { - return false; - } + var assemblyName = uri.GetAssemblyNameFromQuery(); - if (uri.IsAvares() && TryGetResAsmAndPath(uri, out assembly, out _)) + if (assemblyName.Length > 0 && TryLoadAssembly(assemblyName, out assembly)) { return true; } - - if (uri.IsResm()) - { - var assemblyName = uri.GetAssemblyNameFromQuery(); - - if (assemblyName.Length > 0 && TryLoadAssembly(assemblyName, out assembly)) - { - return true; - } - } } - - return false; } - private bool TryLoadAssembly(string assemblyName, [NotNullWhen(true)] out IAssemblyDescriptor? assembly) - { - assembly = null; + return false; + } - try - { - assembly = _assemblyDescriptorResolver.GetAssembly(assemblyName); + private bool TryLoadAssembly(string assemblyName, [NotNullWhen(true)] out IAssemblyDescriptor? assembly) + { + assembly = null; - return true; - } - catch (Exception) { } + try + { + assembly = _assemblyDescriptorResolver.GetAssembly(assemblyName); - return false; + return true; } + catch (Exception) { } + + return false; } }