mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-12-03 12:03:51 +08:00
Make extension loading a 2 pass process
First pass is "probing": any loader who thinks it can load a given extension returns a descriptor for it. The descriptor contains information about how old the extension is. The extension manager sorts the descriptor list return by probing all loaders, and picks the most recent entry. During the 2nd pass, each loader is called again, with the candidate extension entry as argument. This allows each loader to be notified that, if they don't load an extension, they might still need to cleanup some state related to previous loading. In essence, this is implementing a complete state machine of loaders and extension "active/inactive" states/transitions. --HG-- branch : dev
This commit is contained in:
@@ -50,8 +50,12 @@ namespace Orchard.Tests.Environment.Extensions {
|
||||
get { return 1; }
|
||||
}
|
||||
|
||||
public ExtensionEntry Load(ExtensionDescriptor descriptor) {
|
||||
return new ExtensionEntry { Descriptor = descriptor, ExportedTypes = new[] { typeof(Alpha), typeof(Beta), typeof(Phi) } };
|
||||
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||
return new ExtensionProbeEntry { Descriptor = descriptor, Loader = this };
|
||||
}
|
||||
|
||||
public ExtensionEntry Load(ExtensionProbeEntry entry) {
|
||||
return new ExtensionEntry { Descriptor = entry.Descriptor, ExportedTypes = new[] { typeof(Alpha), typeof(Beta), typeof(Phi) } };
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -20,10 +20,7 @@ namespace Orchard.Environment.Extensions {
|
||||
public Localizer T { get; set; }
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public ExtensionManager(
|
||||
IEnumerable<IExtensionFolders> folders,
|
||||
IEnumerable<IExtensionLoader> loaders
|
||||
) {
|
||||
public ExtensionManager(IEnumerable<IExtensionFolders> folders, IEnumerable<IExtensionLoader> loaders) {
|
||||
_folders = folders;
|
||||
_loaders = loaders.OrderBy(x => x.Order);
|
||||
T = NullLocalizer.Instance;
|
||||
@@ -163,13 +160,21 @@ namespace Orchard.Environment.Extensions {
|
||||
}
|
||||
|
||||
private ExtensionEntry BuildEntry(ExtensionDescriptor descriptor) {
|
||||
foreach (var loader in _loaders) {
|
||||
var entry = loader.Load(descriptor);
|
||||
if (entry != null)
|
||||
return entry;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
var loaders = _loaders.ToList();
|
||||
|
||||
var moreRecentEntry = loaders
|
||||
.Select(loader => loader.Probe(descriptor))
|
||||
.Where(entry => entry != null)
|
||||
.OrderByDescending( entry => entry.LastModificationTimeUtc)
|
||||
.FirstOrDefault();
|
||||
|
||||
ExtensionEntry result = null;
|
||||
foreach (var loader in loaders) {
|
||||
ExtensionEntry entry = loader.Load(moreRecentEntry);
|
||||
if (entry != null && result == null)
|
||||
result = entry;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,14 +7,25 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
public class AreaExtensionLoader : IExtensionLoader {
|
||||
public int Order { get { return 50; } }
|
||||
|
||||
public ExtensionEntry Load(ExtensionDescriptor descriptor) {
|
||||
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||
if (descriptor.Location == "~/Areas") {
|
||||
return new ExtensionProbeEntry {
|
||||
Descriptor = descriptor,
|
||||
Loader = this,
|
||||
LastModificationTimeUtc = DateTime.MinValue,
|
||||
VirtualPath = "~/Areas/" + descriptor.Name,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ExtensionEntry Load(ExtensionProbeEntry entry) {
|
||||
if (entry.Loader == this) {
|
||||
var assembly = Assembly.Load("Orchard.Web");
|
||||
return new ExtensionEntry {
|
||||
Descriptor = descriptor,
|
||||
Descriptor = entry.Descriptor,
|
||||
Assembly = assembly,
|
||||
ExportedTypes = assembly.GetExportedTypes().Where(x => IsTypeFromModule(x, descriptor))
|
||||
ExportedTypes = assembly.GetExportedTypes().Where(x => IsTypeFromModule(x, entry.Descriptor))
|
||||
};
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -10,13 +10,25 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
public class CoreExtensionLoader : IExtensionLoader {
|
||||
public int Order { get { return 10; } }
|
||||
|
||||
public ExtensionEntry Load(ExtensionDescriptor descriptor) {
|
||||
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||
if (descriptor.Location == "~/Core") {
|
||||
return new ExtensionProbeEntry {
|
||||
Descriptor = descriptor,
|
||||
LastModificationTimeUtc = DateTime.MinValue,
|
||||
Loader = this,
|
||||
VirtualPath = "~/Core/" + descriptor.Name
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ExtensionEntry Load(ExtensionProbeEntry entry) {
|
||||
if (entry.Loader == this) {
|
||||
var assembly = Assembly.Load("Orchard.Core");
|
||||
return new ExtensionEntry {
|
||||
Descriptor = descriptor,
|
||||
Descriptor = entry.Descriptor,
|
||||
Assembly = assembly,
|
||||
ExportedTypes = assembly.GetExportedTypes().Where(x => IsTypeFromModule(x, descriptor))
|
||||
ExportedTypes = assembly.GetExportedTypes().Where(x => IsTypeFromModule(x, entry.Descriptor))
|
||||
};
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
using Orchard.FileSystems.Dependencies;
|
||||
using Orchard.FileSystems.VirtualPath;
|
||||
@@ -19,24 +20,34 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
|
||||
public int Order { get { return 100; } }
|
||||
|
||||
public ExtensionEntry Load(ExtensionDescriptor descriptor) {
|
||||
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||
string projectPath = _virtualPathProvider.Combine(descriptor.Location, descriptor.Name,
|
||||
descriptor.Name + ".csproj");
|
||||
if (!_virtualPathProvider.FileExists(projectPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var assembly = _buildManager.GetCompiledAssembly(projectPath);
|
||||
|
||||
if (_hostEnvironment.IsFullTrust) {
|
||||
_dependenciesFolder.StoreBuildProviderAssembly(descriptor.Name, projectPath, assembly);
|
||||
}
|
||||
|
||||
return new ExtensionEntry {
|
||||
return new ExtensionProbeEntry {
|
||||
Descriptor = descriptor,
|
||||
Assembly = assembly,
|
||||
ExportedTypes = assembly.GetExportedTypes(),
|
||||
LastModificationTimeUtc = File.GetLastWriteTimeUtc(_virtualPathProvider.MapPath(projectPath)),
|
||||
Loader = this,
|
||||
VirtualPath = projectPath
|
||||
};
|
||||
}
|
||||
|
||||
public ExtensionEntry Load(ExtensionProbeEntry entry) {
|
||||
if (entry.Loader == this) {
|
||||
var assembly = _buildManager.GetCompiledAssembly(entry.VirtualPath);
|
||||
|
||||
_dependenciesFolder.StoreBuildProviderAssembly(entry.Descriptor.Name, entry.VirtualPath, assembly);
|
||||
|
||||
return new ExtensionEntry {
|
||||
Descriptor = entry.Descriptor,
|
||||
Assembly = assembly,
|
||||
ExportedTypes = assembly.GetExportedTypes(),
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,17 @@
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
using System;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
|
||||
namespace Orchard.Environment.Extensions.Loaders {
|
||||
public interface IExtensionLoader {
|
||||
int Order { get; }
|
||||
ExtensionEntry Load(ExtensionDescriptor descriptor);
|
||||
ExtensionProbeEntry Probe(ExtensionDescriptor descriptor);
|
||||
ExtensionEntry Load(ExtensionProbeEntry descriptor);
|
||||
}
|
||||
|
||||
public class ExtensionProbeEntry {
|
||||
public ExtensionDescriptor Descriptor { get; set; }
|
||||
public IExtensionLoader Loader { get; set; }
|
||||
public string VirtualPath { get; set; }
|
||||
public DateTime LastModificationTimeUtc { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Orchard.Environment.Extensions.Loaders {
|
||||
/// <summary>
|
||||
/// Expose a read-only stream as the concatenation of a list of read-only streams
|
||||
/// </summary>
|
||||
public class MergedReadOnlyStreams : Stream {
|
||||
private class StreamDescriptor {
|
||||
public int Index { get; set; }
|
||||
public Stream Stream { get; set; }
|
||||
public long Offset { get; set; }
|
||||
public long Length { get; set; }
|
||||
public long Limit { get { return Offset + Length; } }
|
||||
}
|
||||
|
||||
private readonly List<StreamDescriptor> _streams;
|
||||
private long _position;
|
||||
|
||||
public MergedReadOnlyStreams(params Stream[] streams) {
|
||||
_streams = CreateDescritors(streams).ToList();
|
||||
}
|
||||
|
||||
private static IEnumerable<StreamDescriptor> CreateDescritors(params Stream[] streams) {
|
||||
long offset = 0;
|
||||
int index = 0;
|
||||
foreach (var stream in streams) {
|
||||
yield return new StreamDescriptor {
|
||||
Stream = stream,
|
||||
Index = index,
|
||||
Length = stream.Length,
|
||||
Offset = offset
|
||||
};
|
||||
|
||||
offset += stream.Length;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
public override void Flush() {
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) {
|
||||
switch (origin) {
|
||||
case SeekOrigin.Begin:
|
||||
_position = offset;
|
||||
break;
|
||||
case SeekOrigin.Current:
|
||||
_position += offset;
|
||||
break;
|
||||
case SeekOrigin.End:
|
||||
_position = Length + offset;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException("origin");
|
||||
}
|
||||
return _position;
|
||||
}
|
||||
|
||||
public override void SetLength(long value) {
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count) {
|
||||
int totalRead = 0;
|
||||
while (count > 0) {
|
||||
// Find stream for current position (might fail if end of all streams)
|
||||
var descriptor = GetDescriptor(_position);
|
||||
if (descriptor == null)
|
||||
break;
|
||||
|
||||
// Read bytes from the current stream
|
||||
int read = descriptor.Stream.Read(buffer, offset, count);
|
||||
if (read == 0)
|
||||
break;
|
||||
|
||||
_position += read;
|
||||
totalRead += read;
|
||||
count -= read;
|
||||
offset += read;
|
||||
}
|
||||
return totalRead;
|
||||
}
|
||||
|
||||
private StreamDescriptor GetDescriptor(long position) {
|
||||
return _streams.SingleOrDefault(stream => stream.Offset <= position && position < stream.Limit);
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) {
|
||||
}
|
||||
|
||||
public override bool CanRead {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override bool CanSeek {
|
||||
get { return _streams.All(d => d.Stream.CanSeek); }
|
||||
}
|
||||
|
||||
public override bool CanWrite {
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override long Length {
|
||||
get { return _streams.Aggregate(0L, (prev, desc) => prev + desc.Length); }
|
||||
}
|
||||
|
||||
public override long Position {
|
||||
get { return _position; }
|
||||
set {
|
||||
if (!CanSeek)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
_position = Position;
|
||||
|
||||
// Update the position of all streams
|
||||
foreach(var desc in _streams) {
|
||||
if (_position < desc.Offset) desc.Stream.Position = 0;
|
||||
else if (_position > desc.Limit) desc.Stream.Position = desc.Stream.Length;
|
||||
else desc.Stream.Position = _position - desc.Offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
using System.IO;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
using Orchard.FileSystems.Dependencies;
|
||||
using Orchard.FileSystems.VirtualPath;
|
||||
|
||||
@@ -18,23 +19,35 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
|
||||
public int Order { get { return 30; } }
|
||||
|
||||
public ExtensionEntry Load(ExtensionDescriptor descriptor) {
|
||||
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||
var extensionPath = _virtualPathProvider.Combine(descriptor.Location, descriptor.Name, "bin",
|
||||
descriptor.Name + ".dll");
|
||||
if (!_virtualPathProvider.FileExists(extensionPath))
|
||||
return null;
|
||||
|
||||
_folder.StorePrecompiledAssembly(descriptor.Name, extensionPath);
|
||||
|
||||
var assembly = _folder.LoadAssembly(descriptor.Name);
|
||||
if (assembly == null)
|
||||
return null;
|
||||
|
||||
return new ExtensionEntry {
|
||||
return new ExtensionProbeEntry {
|
||||
Descriptor = descriptor,
|
||||
Assembly = assembly,
|
||||
ExportedTypes = assembly.GetExportedTypes()
|
||||
LastModificationTimeUtc = File.GetLastWriteTimeUtc(_virtualPathProvider.MapPath(extensionPath)),
|
||||
Loader = this,
|
||||
VirtualPath = extensionPath
|
||||
};
|
||||
}
|
||||
|
||||
public ExtensionEntry Load(ExtensionProbeEntry entry) {
|
||||
if (entry.Loader == this) {
|
||||
_folder.StorePrecompiledAssembly(entry.Descriptor.Name, entry.VirtualPath);
|
||||
|
||||
var assembly = _folder.LoadAssembly(entry.Descriptor.Name);
|
||||
if (assembly == null)
|
||||
return null;
|
||||
|
||||
return new ExtensionEntry {
|
||||
Descriptor = entry.Descriptor,
|
||||
Assembly = assembly,
|
||||
ExportedTypes = assembly.GetExportedTypes()
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
using System.IO;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
using Orchard.FileSystems.Dependencies;
|
||||
using Orchard.FileSystems.VirtualPath;
|
||||
|
||||
namespace Orchard.Environment.Extensions.Loaders {
|
||||
/// <summary>
|
||||
@@ -8,23 +10,41 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
/// </summary>
|
||||
public class ProbingExtensionLoader : IExtensionLoader {
|
||||
private readonly IDependenciesFolder _folder;
|
||||
private readonly IVirtualPathProvider _virtualPathProvider;
|
||||
|
||||
public ProbingExtensionLoader(IDependenciesFolder folder) {
|
||||
public ProbingExtensionLoader(IDependenciesFolder folder, IVirtualPathProvider virtualPathProvider) {
|
||||
_folder = folder;
|
||||
}
|
||||
|
||||
public int Order { get { return 40; } }
|
||||
|
||||
public ExtensionEntry Load(ExtensionDescriptor descriptor) {
|
||||
var assembly = _folder.LoadAssembly(descriptor.Name);
|
||||
if (assembly == null)
|
||||
return null;
|
||||
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||
var desc = _folder.GetDescriptor(descriptor.Name);
|
||||
if (desc != null) {
|
||||
return new ExtensionProbeEntry {
|
||||
Descriptor = descriptor,
|
||||
LastModificationTimeUtc = File.GetLastWriteTimeUtc(desc.FileName),
|
||||
Loader = this,
|
||||
VirtualPath = desc.VirtualPath
|
||||
};
|
||||
}
|
||||
|
||||
return new ExtensionEntry {
|
||||
Descriptor = descriptor,
|
||||
Assembly = assembly,
|
||||
ExportedTypes = assembly.GetExportedTypes()
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
public ExtensionEntry Load(ExtensionProbeEntry entry) {
|
||||
if (entry.Loader == this) {
|
||||
var assembly = _folder.LoadAssembly(entry.Descriptor.Name);
|
||||
if (assembly == null)
|
||||
return null;
|
||||
|
||||
return new ExtensionEntry {
|
||||
Descriptor = entry.Descriptor,
|
||||
Assembly = assembly,
|
||||
ExportedTypes = assembly.GetExportedTypes()
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Web.Compilation;
|
||||
using System.Web.Hosting;
|
||||
@@ -17,7 +18,7 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
_dependenciesFolder = dependenciesFolder;
|
||||
}
|
||||
|
||||
public ExtensionEntry Load(ExtensionDescriptor descriptor) {
|
||||
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||
if (HostingEnvironment.IsHosted == false)
|
||||
return null;
|
||||
|
||||
@@ -28,13 +29,34 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
if (assembly == null)
|
||||
return null;
|
||||
|
||||
_dependenciesFolder.StoreReferencedAssembly(descriptor.Name);
|
||||
|
||||
return new ExtensionEntry {
|
||||
return new ExtensionProbeEntry {
|
||||
Descriptor = descriptor,
|
||||
Assembly = assembly,
|
||||
ExportedTypes = assembly.GetExportedTypes()
|
||||
LastModificationTimeUtc = File.GetLastWriteTimeUtc(assembly.Location),
|
||||
Loader = this,
|
||||
VirtualPath = "~/bin/" + descriptor.Name
|
||||
};
|
||||
}
|
||||
|
||||
public ExtensionEntry Load(ExtensionProbeEntry entry) {
|
||||
if (HostingEnvironment.IsHosted == false)
|
||||
return null;
|
||||
|
||||
if (entry.Loader == this) {
|
||||
|
||||
var assembly = BuildManager.GetReferencedAssemblies()
|
||||
.OfType<Assembly>()
|
||||
.FirstOrDefault(x => x.GetName().Name == entry.Descriptor.Name);
|
||||
|
||||
_dependenciesFolder.StoreReferencedAssembly(entry.Descriptor.Name);
|
||||
|
||||
return new ExtensionEntry {
|
||||
Descriptor = entry.Descriptor,
|
||||
Assembly = assembly,
|
||||
ExportedTypes = assembly.GetExportedTypes()
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,7 +352,6 @@
|
||||
<Compile Include="Data\DataModule.cs" />
|
||||
<Compile Include="Data\Orderable.cs" />
|
||||
<Compile Include="Environment\DefaultOrchardShell.cs" />
|
||||
<Compile Include="Environment\Extensions\Loaders\MergedReadOnlyStreams.cs" />
|
||||
<Compile Include="FileSystems\Dependencies\DefaultDependenciesFolder.cs" />
|
||||
<Compile Include="FileSystems\VirtualPath\DefaultVirtualPathMonitor.cs" />
|
||||
<Compile Include="FileSystems\VirtualPath\DefaultVirtualPathProvider.cs" />
|
||||
|
||||
Reference in New Issue
Block a user