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:
Renaud Paquay
2010-06-11 17:12:27 -07:00
parent e7615d6eb7
commit 75efa67b61
11 changed files with 168 additions and 187 deletions

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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;
}
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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" />