detect/WebViewControl.Avalonia/AssemblyCache.cs

99 lines
4.3 KiB
C#
Raw Normal View History

2024-11-13 17:09:15 +08:00
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
namespace WebViewControl {
internal class AssemblyCache {
private object SyncRoot { get; } = new object();
// We now allow to load multiple versions of the same assembly, which means that resource urls can
// (optionally) specify the version. We don't force the version to be specified to maintain backwards
// compatibility, and thus for each assembly name we two entries in the dictionary: with and without a version.
// Note that no guarantee is provided about which version is resolved if there are multiple loaded assemblies
// with the same name and no specific version is provided.
// This, consumer apps are encouraged to include the version in the resource url
private IDictionary<(string AssemblyName, Version AssemblyVersion), Assembly> assemblies;
private bool newAssembliesLoaded = true;
internal Assembly ResolveResourceAssembly(Uri resourceUrl, bool failOnMissingAssembly) {
if (assemblies == null) {
lock (SyncRoot) {
if (assemblies == null) {
assemblies = new Dictionary<(string, Version), Assembly>();
AppDomain.CurrentDomain.AssemblyLoad += delegate { newAssembliesLoaded = true; };
}
}
}
var (assemblyName, assemblyVersion) = ResourceUrl.GetEmbeddedResourceAssemblyNameAndVersion(resourceUrl);
var assembly = GetAssemblyByNameAndVersion(assemblyName, assemblyVersion);
if (assembly == null) {
if (newAssembliesLoaded) {
lock (SyncRoot) {
if (newAssembliesLoaded) {
// add loaded assemblies to cache
newAssembliesLoaded = false;
foreach (var domainAssembly in AppDomain.CurrentDomain.GetAssemblies()) {
AddOrReplace(domainAssembly);
}
}
}
}
assembly = GetAssemblyByNameAndVersion(assemblyName, assemblyVersion);
if (assembly == null) {
try {
// try loading the assembly from a file named AssemblyName.dll (or AssemblyName-AssemblyVersion.dll if
// a version was provided)
var fileName = $"{assemblyName}{(assemblyVersion == null ? "" : $"-{assemblyVersion}")}.dll";
var assemblyPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);
assembly = AssemblyLoader.LoadAssembly(assemblyPath);
} catch (IOException) {
// ignore
}
if (assembly != null) {
lock (SyncRoot) {
AddOrReplace(assembly);
}
}
}
}
if (failOnMissingAssembly && assembly == null) {
throw new InvalidOperationException("Could not find assembly for: " + resourceUrl);
}
return assembly;
}
private void AddOrReplace(Assembly assembly) {
var identity = assembly.GetName();
var assemblyName = identity.Name;
if (assemblyName == null) {
return;
}
// add two entries, with and without the version.
// for the null-version entry, keep the assembly with the highest version
var version = identity.Version;
if (!assemblies.TryGetValue((assemblyName, null), out var nullVersionAssembly) ||
(nullVersionAssembly.GetName().Version is { } previousVersion && previousVersion < version)) {
assemblies[(assemblyName, null)] = assembly;
}
assemblies[(assemblyName, version)] = assembly;
}
private Assembly GetAssemblyByNameAndVersion(string assemblyName, Version assemblyVersion) {
lock (SyncRoot) {
assemblies.TryGetValue((assemblyName, assemblyVersion), out var assembly);
return assembly;
}
}
}
}