// Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using Avalonia.Platform; namespace Avalonia.Shared.PlatformSupport { /// /// Loads assets compiled into the application binary. /// public class AssetLoader : IAssetLoader { private static readonly Dictionary AssemblyNameCache = new Dictionary(); private AssemblyDescriptor _defaultAssembly; /// /// Initializes a new instance of the class. /// /// /// The default assembly from which to load assets for which no assembly is specified. /// public AssetLoader(Assembly assembly = null) { if (assembly == null) assembly = Assembly.GetEntryAssembly(); if (assembly != null) _defaultAssembly = 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) { _defaultAssembly = 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 GetAsset(uri, baseUri) != null; } /// /// Opens the resource with the requested URI. /// /// The URI. /// /// A base URI to use if is relative. /// /// A stream containing the resource contents. /// /// The resource was not found. /// public Stream Open(Uri uri, Uri baseUri = null) { var asset = GetAsset(uri, baseUri); if (asset == null) { throw new FileNotFoundException($"The resource {uri} could not be found."); } return asset.GetStream(); } private IAssetDescriptor GetAsset(Uri uri, Uri baseUri) { if (!uri.IsAbsoluteUri || uri.Scheme == "resm") { var asm = GetAssembly(uri) ?? GetAssembly(baseUri) ?? _defaultAssembly; if (asm == null) { throw new ArgumentException( "No default assembly, entry assembly or explicit assembly specified; " + "don't know where to look up for the resource, try specifiyng assembly explicitly."); } IAssetDescriptor rv; var resourceKey = uri.AbsolutePath; asm.Resources.TryGetValue(resourceKey, out rv); return rv; } throw new ArgumentException($"Invalid uri, see https://github.com/AvaloniaUI/Avalonia/issues/282#issuecomment-166982104", nameof(uri)); } private AssemblyDescriptor GetAssembly(Uri uri) { if (uri != null) { var qs = ParseQueryString(uri); if (qs.TryGetValue("assembly", out var assemblyName)) { return GetAssembly(assemblyName); } } return null; } private AssemblyDescriptor GetAssembly(string name) { if (name == null) { return _defaultAssembly; } if (!AssemblyNameCache.TryGetValue(name, out var rv)) { var loadedAssemblies = AvaloniaLocator.Current.GetService().GetLoadedAssemblies(); var match = loadedAssemblies.FirstOrDefault(a => a.GetName().Name == name); if (match != null) { AssemblyNameCache[name] = rv = new AssemblyDescriptor(match); } else { // iOS does not support loading assemblies dynamically! // #if NETCOREAPP1_0 AssemblyNameCache[name] = rv = new AssemblyDescriptor(Assembly.Load(new AssemblyName(name))); #elif __IOS__ throw new InvalidOperationException( $"Assembly {name} needs to be referenced and explicitly loaded before loading resources"); #else AssemblyNameCache[name] = rv = new AssemblyDescriptor(Assembly.Load(name)); #endif } } return rv; } private Dictionary ParseQueryString(Uri uri) { return uri.Query.TrimStart('?') .Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries) .Select(p => p.Split('=')) .ToDictionary(p => p[0], p => p[1]); } private interface IAssetDescriptor { Stream GetStream(); } private class AssemblyResourceDescriptor : IAssetDescriptor { private readonly Assembly _asm; private readonly string _name; public AssemblyResourceDescriptor(Assembly asm, string name) { _asm = asm; _name = name; } public Stream GetStream() { return _asm.GetManifestResourceStream(_name); } } private class AssemblyDescriptor { public AssemblyDescriptor(Assembly assembly) { Assembly = assembly; if (assembly != null) { Resources = assembly.GetManifestResourceNames() .ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n)); Name = assembly.GetName().Name; } } public Assembly Assembly { get; } public Dictionary Resources { get; } public string Name { get; } } } }