AssetLoader.cs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. // Copyright (c) The Avalonia Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using System;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Reflection;
  8. using Avalonia.Platform;
  9. namespace Avalonia.Shared.PlatformSupport
  10. {
  11. /// <summary>
  12. /// Loads assets compiled into the application binary.
  13. /// </summary>
  14. public class AssetLoader : IAssetLoader
  15. {
  16. private static readonly Dictionary<string, AssemblyDescriptor> AssemblyNameCache
  17. = new Dictionary<string, AssemblyDescriptor>();
  18. private AssemblyDescriptor _defaultAssembly;
  19. /// <summary>
  20. /// Initializes a new instance of the <see cref="AssetLoader"/> class.
  21. /// </summary>
  22. /// <param name="assembly">
  23. /// The default assembly from which to load assets for which no assembly is specified.
  24. /// </param>
  25. public AssetLoader(Assembly assembly = null)
  26. {
  27. if (assembly == null)
  28. assembly = Assembly.GetEntryAssembly();
  29. if (assembly != null)
  30. _defaultAssembly = new AssemblyDescriptor(assembly);
  31. }
  32. /// <summary>
  33. /// Sets the default assembly from which to load assets for which no assembly is specified.
  34. /// </summary>
  35. /// <param name="assembly">The default assembly.</param>
  36. public void SetDefaultAssembly(Assembly assembly)
  37. {
  38. _defaultAssembly = new AssemblyDescriptor(assembly);
  39. }
  40. /// <summary>
  41. /// Checks if an asset with the specified URI exists.
  42. /// </summary>
  43. /// <param name="uri">The URI.</param>
  44. /// <param name="baseUri">
  45. /// A base URI to use if <paramref name="uri"/> is relative.
  46. /// </param>
  47. /// <returns>True if the asset could be found; otherwise false.</returns>
  48. public bool Exists(Uri uri, Uri baseUri = null)
  49. {
  50. return GetAsset(uri, baseUri) != null;
  51. }
  52. /// <summary>
  53. /// Opens the resource with the requested URI.
  54. /// </summary>
  55. /// <param name="uri">The URI.</param>
  56. /// <param name="baseUri">
  57. /// A base URI to use if <paramref name="uri"/> is relative.
  58. /// </param>
  59. /// <returns>A stream containing the resource contents.</returns>
  60. /// <exception cref="FileNotFoundException">
  61. /// The resource was not found.
  62. /// </exception>
  63. public Stream Open(Uri uri, Uri baseUri = null)
  64. {
  65. var asset = GetAsset(uri, baseUri);
  66. if (asset == null)
  67. {
  68. throw new FileNotFoundException($"The resource {uri} could not be found.");
  69. }
  70. return asset.GetStream();
  71. }
  72. private IAssetDescriptor GetAsset(Uri uri, Uri baseUri)
  73. {
  74. if (!uri.IsAbsoluteUri || uri.Scheme == "resm")
  75. {
  76. var asm = GetAssembly(uri) ?? GetAssembly(baseUri) ?? _defaultAssembly;
  77. if (asm == null)
  78. {
  79. throw new ArgumentException(
  80. "No default assembly, entry assembly or explicit assembly specified; " +
  81. "don't know where to look up for the resource, try specifiyng assembly explicitly.");
  82. }
  83. IAssetDescriptor rv;
  84. var resourceKey = uri.AbsolutePath;
  85. asm.Resources.TryGetValue(resourceKey, out rv);
  86. return rv;
  87. }
  88. throw new ArgumentException($"Invalid uri, see https://github.com/AvaloniaUI/Avalonia/issues/282#issuecomment-166982104", nameof(uri));
  89. }
  90. private AssemblyDescriptor GetAssembly(Uri uri)
  91. {
  92. if (uri != null)
  93. {
  94. var qs = ParseQueryString(uri);
  95. if (qs.TryGetValue("assembly", out var assemblyName))
  96. {
  97. return GetAssembly(assemblyName);
  98. }
  99. }
  100. return null;
  101. }
  102. private AssemblyDescriptor GetAssembly(string name)
  103. {
  104. if (name == null)
  105. {
  106. return _defaultAssembly;
  107. }
  108. if (!AssemblyNameCache.TryGetValue(name, out var rv))
  109. {
  110. var loadedAssemblies = AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetLoadedAssemblies();
  111. var match = loadedAssemblies.FirstOrDefault(a => a.GetName().Name == name);
  112. if (match != null)
  113. {
  114. AssemblyNameCache[name] = rv = new AssemblyDescriptor(match);
  115. }
  116. else
  117. {
  118. // iOS does not support loading assemblies dynamically!
  119. //
  120. #if NETCOREAPP1_0
  121. AssemblyNameCache[name] = rv = new AssemblyDescriptor(Assembly.Load(new AssemblyName(name)));
  122. #elif __IOS__
  123. throw new InvalidOperationException(
  124. $"Assembly {name} needs to be referenced and explicitly loaded before loading resources");
  125. #else
  126. AssemblyNameCache[name] = rv = new AssemblyDescriptor(Assembly.Load(name));
  127. #endif
  128. }
  129. }
  130. return rv;
  131. }
  132. private Dictionary<string, string> ParseQueryString(Uri uri)
  133. {
  134. return uri.Query.TrimStart('?')
  135. .Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries)
  136. .Select(p => p.Split('='))
  137. .ToDictionary(p => p[0], p => p[1]);
  138. }
  139. private interface IAssetDescriptor
  140. {
  141. Stream GetStream();
  142. }
  143. private class AssemblyResourceDescriptor : IAssetDescriptor
  144. {
  145. private readonly Assembly _asm;
  146. private readonly string _name;
  147. public AssemblyResourceDescriptor(Assembly asm, string name)
  148. {
  149. _asm = asm;
  150. _name = name;
  151. }
  152. public Stream GetStream()
  153. {
  154. return _asm.GetManifestResourceStream(_name);
  155. }
  156. }
  157. private class AssemblyDescriptor
  158. {
  159. public AssemblyDescriptor(Assembly assembly)
  160. {
  161. Assembly = assembly;
  162. if (assembly != null)
  163. {
  164. Resources = assembly.GetManifestResourceNames()
  165. .ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n));
  166. Name = assembly.GetName().Name;
  167. }
  168. }
  169. public Assembly Assembly { get; }
  170. public Dictionary<string, IAssetDescriptor> Resources { get; }
  171. public string Name { get; }
  172. }
  173. }
  174. }