mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 19:54:57 +08:00
work on extension loading
* Add a "ExtensionLoaderCoordinator" class * Rework the way loaders are notified of extensions being loaded, (de)activated, etc. --HG-- branch : dev
This commit is contained in:
@@ -43,23 +43,19 @@ namespace Orchard.Tests.Environment.Extensions {
|
||||
}
|
||||
}
|
||||
|
||||
public class StubLoaders : IExtensionLoader {
|
||||
public class StubLoaders : ExtensionLoaderBase {
|
||||
#region Implementation of IExtensionLoader
|
||||
|
||||
public int Order {
|
||||
public override int Order {
|
||||
get { return 1; }
|
||||
}
|
||||
|
||||
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||
public override 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) } };
|
||||
}
|
||||
|
||||
public void Monitor(ExtensionDescriptor descriptor, Action<IVolatileToken> monitor) {
|
||||
throw new NotImplementedException();
|
||||
public override ExtensionEntry Load(ExtensionDescriptor descriptor) {
|
||||
return new ExtensionEntry { Descriptor = descriptor, ExportedTypes = new[] { typeof(Alpha), typeof(Beta), typeof(Phi) } };
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@@ -143,10 +143,6 @@
|
||||
<Project>{17F86780-9A1F-4AA1-86F1-875EEC2730C7}</Project>
|
||||
<Name>Orchard.Modules</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="Modules\Orchard.MultiTenancy\Orchard.MultiTenancy.csproj">
|
||||
<Project>{72457126-E118-4171-A08F-9A709EE4B7FC}</Project>
|
||||
<Name>Orchard.MultiTenancy</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="Modules\Orchard.Pages\Orchard.Pages.csproj">
|
||||
<Project>{4A9C04A6-0986-4A92-A610-5F59FF273FB9}</Project>
|
||||
<Name>Orchard.Pages</Name>
|
||||
|
@@ -23,6 +23,7 @@ namespace Orchard.Environment {
|
||||
private readonly IRunningShellTable _runningShellTable;
|
||||
private readonly IProcessingEngine _processingEngine;
|
||||
private readonly IExtensionManager _extensionManager;
|
||||
private readonly IExtensionLoaderCoordinator _extensionLoaderCoordinator;
|
||||
private readonly ICacheManager _cacheManager;
|
||||
|
||||
private IEnumerable<ShellContext> _current;
|
||||
@@ -33,6 +34,7 @@ namespace Orchard.Environment {
|
||||
IRunningShellTable runningShellTable,
|
||||
IProcessingEngine processingEngine,
|
||||
IExtensionManager extensionManager,
|
||||
IExtensionLoaderCoordinator extensionLoaderCoordinator,
|
||||
ICacheManager cacheManager,
|
||||
ControllerBuilder controllerBuilder) {
|
||||
_shellSettingsManager = shellSettingsManager;
|
||||
@@ -40,6 +42,7 @@ namespace Orchard.Environment {
|
||||
_runningShellTable = runningShellTable;
|
||||
_processingEngine = processingEngine;
|
||||
_extensionManager = extensionManager;
|
||||
_extensionLoaderCoordinator = extensionLoaderCoordinator;
|
||||
_cacheManager = cacheManager;
|
||||
_controllerBuilder = controllerBuilder;
|
||||
Logger = NullLogger.Instance;
|
||||
@@ -78,10 +81,17 @@ namespace Orchard.Environment {
|
||||
}
|
||||
|
||||
IEnumerable<ShellContext> BuildCurrent() {
|
||||
if (_current == null) {
|
||||
lock (this) {
|
||||
return _current ?? (_current = CreateAndActivate().ToArray());
|
||||
if (_current == null) {
|
||||
SetupExtensions();
|
||||
MonitorExtensions();
|
||||
_current = CreateAndActivate().ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
return _current;
|
||||
}
|
||||
|
||||
IEnumerable<ShellContext> CreateAndActivate() {
|
||||
var allSettings = _shellSettingsManager.LoadSettings();
|
||||
@@ -119,13 +129,21 @@ namespace Orchard.Environment {
|
||||
return _shellContextFactory.CreateShellContext(settings);
|
||||
}
|
||||
|
||||
protected virtual void BeginRequest() {
|
||||
private void SetupExtensions() {
|
||||
_extensionLoaderCoordinator.SetupExtensions();
|
||||
}
|
||||
|
||||
private void MonitorExtensions() {
|
||||
_cacheManager.Get("OrchardHost_Extensions",
|
||||
ctx => {
|
||||
_extensionManager.Monitor(ctx.Monitor);
|
||||
_extensionLoaderCoordinator.MonitorExtensions(ctx.Monitor);
|
||||
_current = null;
|
||||
return "";
|
||||
});
|
||||
}
|
||||
|
||||
protected virtual void BeginRequest() {
|
||||
MonitorExtensions();
|
||||
BuildCurrent();
|
||||
}
|
||||
|
||||
|
200
src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs
Normal file
200
src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs
Normal file
@@ -0,0 +1,200 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Orchard.Caching;
|
||||
using Orchard.Environment.Extensions.Loaders;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
using Orchard.FileSystems.Dependencies;
|
||||
using Orchard.FileSystems.VirtualPath;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Logging;
|
||||
using Orchard.Services;
|
||||
|
||||
namespace Orchard.Environment.Extensions {
|
||||
public class ExtensionLoaderCoordinator : IExtensionLoaderCoordinator {
|
||||
private readonly IDependenciesFolder _dependenciesFolder;
|
||||
private readonly IExtensionManager _extensionManager;
|
||||
private readonly IEnumerable<IExtensionLoader> _loaders;
|
||||
private readonly IVirtualPathProvider _virtualPathProvider;
|
||||
private readonly IClock _clock;
|
||||
|
||||
public ExtensionLoaderCoordinator(
|
||||
IDependenciesFolder dependenciesFolder,
|
||||
IExtensionManager extensionManager,
|
||||
IEnumerable<IExtensionLoader> loaders,
|
||||
IVirtualPathProvider virtualPathProvider,
|
||||
IClock clock) {
|
||||
_dependenciesFolder = dependenciesFolder;
|
||||
_extensionManager = extensionManager;
|
||||
_loaders = loaders.OrderBy(l => l.Order);
|
||||
_virtualPathProvider = virtualPathProvider;
|
||||
_clock = clock;
|
||||
T = NullLocalizer.Instance;
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
|
||||
public Localizer T { get; set; }
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public void SetupExtensions() {
|
||||
Logger.Information("Loading extensions.");
|
||||
|
||||
var extensions = _extensionManager.AvailableExtensions().Where(d => d.ExtensionType == "Module").ToList();
|
||||
var existingDependencies = _dependenciesFolder.LoadDescriptors().ToList();
|
||||
|
||||
var sameExtensions = extensions.Where(e => existingDependencies.Any(e2 => e2.Name == e.Name)).ToList();
|
||||
var deletedDependencies = existingDependencies.Where(e => !extensions.Any(e2 => e2.Name == e.Name)).ToList();
|
||||
var newExtensions = extensions.Except(sameExtensions).ToList();
|
||||
|
||||
var ctx = new ExtensionLoadingContext { DependenciesFolder = _dependenciesFolder };
|
||||
|
||||
// Notify all loaders about extensions removed from the web site
|
||||
foreach (var dependency in deletedDependencies) {
|
||||
Logger.Information("Extension {0} has been removed from site", dependency.Name);
|
||||
foreach (var loader in _loaders) {
|
||||
if (dependency.LoaderName == loader.Name) {
|
||||
loader.ExtensionRemoved(ctx, dependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For all existing extensions in the site, ask each loader if they can
|
||||
// load that extension.
|
||||
var newDependencies = new List<DependencyDescriptor>();
|
||||
foreach (var extension in extensions) {
|
||||
bool isNewExtension = newExtensions.Any(e => e.Name == extension.Name);
|
||||
ProcessExtension(ctx, extension, isNewExtension, existingDependencies, newDependencies);
|
||||
}
|
||||
|
||||
// Execute all the work need by "ctx"
|
||||
ProcessContextCommands(ctx);
|
||||
|
||||
// And finally save the new entries in the dependencies folder
|
||||
_dependenciesFolder.StoreDescriptors(newDependencies);
|
||||
Logger.Information("Done loading extensions.");
|
||||
}
|
||||
|
||||
private void ProcessExtension(
|
||||
ExtensionLoadingContext ctx,
|
||||
ExtensionDescriptor extension,
|
||||
bool isNewExtension,
|
||||
IEnumerable<DependencyDescriptor> existingDependencies,
|
||||
List<DependencyDescriptor> newDependencies) {
|
||||
|
||||
var extensionProbes = _loaders
|
||||
.Select(loader => loader.Probe(extension))
|
||||
.Where(probe => probe != null)
|
||||
.OrderByDescending(probe => probe.LastModificationTimeUtc)
|
||||
.ThenBy(probe => probe.Loader.Order)
|
||||
.ToList();
|
||||
|
||||
|
||||
if (Logger.IsEnabled(LogLevel.Debug)) {
|
||||
Logger.Debug("Loaders for extension \"{0}\": ", extension.Name);
|
||||
foreach (var probe in extensionProbes) {
|
||||
Logger.Debug(" Loader: {0}", probe.Loader.Name);
|
||||
Logger.Debug(" VirtualPath: {0}", probe.VirtualPath);
|
||||
Logger.Debug(" DateTimeUtc: {0}", probe.LastModificationTimeUtc);
|
||||
}
|
||||
}
|
||||
|
||||
var activatedExtension = extensionProbes.FirstOrDefault();
|
||||
var previousDependency = existingDependencies.Where(d => d.Name == extension.Name).FirstOrDefault();
|
||||
|
||||
if (activatedExtension == null) {
|
||||
Logger.Warning("No loader found for extension {0}!", extension.Name);
|
||||
}
|
||||
|
||||
foreach (var loader in _loaders) {
|
||||
if (activatedExtension != null && activatedExtension.Loader.Name == loader.Name) {
|
||||
Logger.Information("Activating extension \"{0}\" with loader \"{1}\"", activatedExtension.Descriptor.Name, loader.Name);
|
||||
loader.ExtensionActivated(ctx, isNewExtension, extension);
|
||||
}
|
||||
else if (previousDependency != null && previousDependency.LoaderName == loader.Name) {
|
||||
Logger.Information("Deactivating extension \"{0}\" from loader \"{1}\"", previousDependency.Name, loader.Name);
|
||||
loader.ExtensionDeactivated(ctx, isNewExtension, extension);
|
||||
}
|
||||
}
|
||||
|
||||
if (activatedExtension != null) {
|
||||
newDependencies.Add(new DependencyDescriptor {
|
||||
Name = extension.Name,
|
||||
LoaderName = activatedExtension.Loader.Name,
|
||||
VirtualPath = activatedExtension.VirtualPath
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessContextCommands(ExtensionLoadingContext ctx) {
|
||||
Logger.Information("Executing list of operations needed for loading extensions...");
|
||||
foreach (var fileName in ctx.FilesToDelete) {
|
||||
Logger.Information("Deleting file \"{0}\"", fileName);
|
||||
File.Delete(fileName);
|
||||
}
|
||||
foreach (var entry in ctx.FilesToCopy) {
|
||||
Logger.Information("Copying file from \"{0}\" to \"{1}\"", entry.Key, entry.Value);
|
||||
MakeDestinationFileNameAvailable(entry.Value);
|
||||
File.Copy(entry.Key, entry.Value);
|
||||
}
|
||||
foreach (var entry in ctx.FilesToRename) {
|
||||
Logger.Information("Moving file from \"{0}\" to \"{1}\"", entry.Key, entry.Value);
|
||||
MakeDestinationFileNameAvailable(entry.Value);
|
||||
File.Move(entry.Key, entry.Value);
|
||||
}
|
||||
|
||||
if (ctx.RestartAppDomain || ctx.ResetSiteCompilation) {
|
||||
if (ctx.RestartAppDomain)
|
||||
Logger.Information("AppDomain restart required.");
|
||||
|
||||
if (ctx.ResetSiteCompilation)
|
||||
Logger.Information("Reset site compilation state required.");
|
||||
|
||||
// Touch web.config
|
||||
File.SetLastWriteTimeUtc(_virtualPathProvider.MapPath("~/web.config"), _clock.UtcNow);
|
||||
}
|
||||
}
|
||||
|
||||
private void MakeDestinationFileNameAvailable(string destinationFileName) {
|
||||
// Try deleting the destination first
|
||||
try {
|
||||
File.Delete(destinationFileName);
|
||||
} catch {
|
||||
// We land here if the file is in use, for example. Let's move on.
|
||||
}
|
||||
|
||||
// If destination doesn't exist, we are good
|
||||
if (!File.Exists(destinationFileName))
|
||||
return;
|
||||
|
||||
// Try renaming destination to a unique filename
|
||||
const string extension = "deleted";
|
||||
for (int i = 0; i < 100; i++) {
|
||||
var newExtension = (i == 0 ? extension : string.Format("{0}{1}", extension, i));
|
||||
var newFileName = Path.ChangeExtension(destinationFileName, newExtension);
|
||||
try {
|
||||
File.Delete(newFileName);
|
||||
File.Move(destinationFileName, newFileName);
|
||||
|
||||
// If successful, we are done...
|
||||
return;
|
||||
}
|
||||
catch (Exception) {
|
||||
// We need to try with another extension
|
||||
}
|
||||
}
|
||||
|
||||
// Everything failed, throw an exception
|
||||
throw new OrchardException(T("Unable to make room for file {0} in dependencies folder: too many conflicts.", destinationFileName).Text);
|
||||
}
|
||||
|
||||
public void MonitorExtensions(Action<IVolatileToken> monitor) {
|
||||
var extensions = _extensionManager.AvailableExtensions().Where(d => d.ExtensionType == "Module").ToList();
|
||||
foreach (var extension in extensions) {
|
||||
foreach (var loader in _loaders) {
|
||||
loader.Monitor(extension, monitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
using System.Collections.Generic;
|
||||
using Orchard.FileSystems.Dependencies;
|
||||
|
||||
namespace Orchard.Environment.Extensions {
|
||||
public class ExtensionLoadingContext {
|
||||
public ExtensionLoadingContext() {
|
||||
FilesToDelete = new HashSet<string>();
|
||||
FilesToCopy = new Dictionary<string, string>();
|
||||
FilesToRename = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
public IDependenciesFolder DependenciesFolder { get; set; }
|
||||
public HashSet<string> FilesToDelete { get; set; }
|
||||
public Dictionary<string, string> FilesToCopy { get; set; }
|
||||
public Dictionary<string, string> FilesToRename { get; set; }
|
||||
public bool RestartAppDomain { get; set; }
|
||||
public bool ResetSiteCompilation { get; set; }
|
||||
}
|
||||
}
|
@@ -4,7 +4,6 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using Orchard.Caching;
|
||||
using Orchard.Environment.Extensions.Folders;
|
||||
using Orchard.Environment.Extensions.Helpers;
|
||||
using Orchard.Environment.Extensions.Loaders;
|
||||
@@ -154,33 +153,13 @@ namespace Orchard.Environment.Extensions {
|
||||
Directory.Delete(targetFolder, true);
|
||||
}
|
||||
|
||||
public void Monitor(Action<IVolatileToken> monitor) {
|
||||
foreach (var descriptor in AvailableExtensions()) {
|
||||
if (string.Equals(descriptor.ExtensionType, "Module", StringComparison.OrdinalIgnoreCase)) {
|
||||
foreach (var loader in _loaders) {
|
||||
loader.Monitor(descriptor, monitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ExtensionEntry BuildEntry(ExtensionDescriptor descriptor) {
|
||||
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;
|
||||
foreach (var loader in _loaders) {
|
||||
ExtensionEntry entry = loader.Load(descriptor);
|
||||
if (entry != null)
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
using Orchard.Caching;
|
||||
|
||||
namespace Orchard.Environment.Extensions {
|
||||
public interface IExtensionLoaderCoordinator {
|
||||
void SetupExtensions();
|
||||
void MonitorExtensions(Action<IVolatileToken> monitor);
|
||||
}
|
||||
}
|
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Web;
|
||||
using Orchard.Caching;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
|
||||
namespace Orchard.Environment.Extensions {
|
||||
@@ -10,6 +8,5 @@ namespace Orchard.Environment.Extensions {
|
||||
IEnumerable<Feature> LoadFeatures(IEnumerable<FeatureDescriptor> featureDescriptors);
|
||||
void InstallExtension(string extensionType, HttpPostedFileBase extensionBundle);
|
||||
void UninstallExtension(string extensionType, string extensionName);
|
||||
void Monitor(Action<IVolatileToken> monitor);
|
||||
}
|
||||
}
|
@@ -1,29 +1,21 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Orchard.Caching;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
using Orchard.FileSystems.Dependencies;
|
||||
using Orchard.FileSystems.VirtualPath;
|
||||
|
||||
namespace Orchard.Environment.Extensions.Loaders {
|
||||
public class AreaExtensionLoader : IExtensionLoader {
|
||||
public class AreaExtensionLoader : ExtensionLoaderBase {
|
||||
private readonly IDependenciesFolder _dependenciesFolder;
|
||||
private readonly IVirtualPathProvider _virtualPathProvider;
|
||||
|
||||
public AreaExtensionLoader(IDependenciesFolder dependenciesFolder, IVirtualPathProvider virtualPathProvider) {
|
||||
public AreaExtensionLoader(IDependenciesFolder dependenciesFolder) {
|
||||
_dependenciesFolder = dependenciesFolder;
|
||||
_virtualPathProvider = virtualPathProvider;
|
||||
}
|
||||
|
||||
public int Order { get { return 50; } }
|
||||
public override int Order { get { return 50; } }
|
||||
|
||||
public void Monitor(ExtensionDescriptor descriptor, Action<IVolatileToken> monitor) {
|
||||
// We don't need to monitor anything since we are loaded
|
||||
// from the application assembly itself.
|
||||
}
|
||||
|
||||
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||
public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||
if (descriptor.Location == "~/Areas") {
|
||||
return new ExtensionProbeEntry {
|
||||
Descriptor = descriptor,
|
||||
@@ -35,25 +27,20 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
return null;
|
||||
}
|
||||
|
||||
public ExtensionEntry Load(ExtensionProbeEntry entry) {
|
||||
if (entry.Loader == this) {
|
||||
public override ExtensionEntry Load(ExtensionDescriptor descriptor) {
|
||||
var dependency = _dependenciesFolder.GetDescriptor(descriptor.Name);
|
||||
if (dependency != null && dependency.LoaderName == this.Name) {
|
||||
|
||||
var assembly = Assembly.Load("Orchard.Web");
|
||||
|
||||
_dependenciesFolder.Store(new DependencyDescriptor { ModuleName = entry.Descriptor.Name, LoaderName = this.GetType().FullName});
|
||||
|
||||
return new ExtensionEntry {
|
||||
Descriptor = entry.Descriptor,
|
||||
Descriptor = descriptor,
|
||||
Assembly = assembly,
|
||||
ExportedTypes = assembly.GetExportedTypes().Where(x => IsTypeFromModule(x, entry.Descriptor))
|
||||
ExportedTypes = assembly.GetExportedTypes().Where(x => IsTypeFromModule(x, descriptor))
|
||||
};
|
||||
}
|
||||
else {
|
||||
// If the extension is not loaded by us, there is no cached state
|
||||
// we need to invalidate
|
||||
_dependenciesFolder.Remove(entry.Descriptor.Name, this.GetType().FullName);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsTypeFromModule(Type type, ExtensionDescriptor descriptor) {
|
||||
return (type.Namespace + ".").StartsWith("Orchard.Web.Areas." + descriptor.Name + ".");
|
||||
|
@@ -10,23 +10,16 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
/// <summary>
|
||||
/// Load an extension by looking into specific namespaces of the "Orchard.Core" assembly
|
||||
/// </summary>
|
||||
public class CoreExtensionLoader : IExtensionLoader {
|
||||
public class CoreExtensionLoader : ExtensionLoaderBase {
|
||||
private readonly IDependenciesFolder _dependenciesFolder;
|
||||
private readonly IVirtualPathProvider _virtualPathProvider;
|
||||
|
||||
public CoreExtensionLoader(IDependenciesFolder dependenciesFolder, IVirtualPathProvider virtualPathProvider) {
|
||||
public CoreExtensionLoader(IDependenciesFolder dependenciesFolder) {
|
||||
_dependenciesFolder = dependenciesFolder;
|
||||
_virtualPathProvider = virtualPathProvider;
|
||||
}
|
||||
|
||||
public int Order { get { return 10; } }
|
||||
public override int Order { get { return 10; } }
|
||||
|
||||
public void Monitor(ExtensionDescriptor descriptor, Action<IVolatileToken> monitor) {
|
||||
// We don't need to monitor anything since we are loaded
|
||||
// from the core assembly.
|
||||
}
|
||||
|
||||
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||
public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||
if (descriptor.Location == "~/Core") {
|
||||
return new ExtensionProbeEntry {
|
||||
Descriptor = descriptor,
|
||||
@@ -38,25 +31,20 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
return null;
|
||||
}
|
||||
|
||||
public ExtensionEntry Load(ExtensionProbeEntry entry) {
|
||||
if (entry.Loader == this) {
|
||||
public override ExtensionEntry Load(ExtensionDescriptor descriptor) {
|
||||
var dependency = _dependenciesFolder.GetDescriptor(descriptor.Name);
|
||||
if (dependency != null && dependency.LoaderName == this.Name) {
|
||||
|
||||
var assembly = Assembly.Load("Orchard.Core");
|
||||
|
||||
_dependenciesFolder.Store(new DependencyDescriptor { ModuleName = entry.Descriptor.Name, LoaderName = this.GetType().FullName });
|
||||
|
||||
return new ExtensionEntry {
|
||||
Descriptor = entry.Descriptor,
|
||||
Descriptor = descriptor,
|
||||
Assembly = assembly,
|
||||
ExportedTypes = assembly.GetExportedTypes().Where(x => IsTypeFromModule(x, entry.Descriptor))
|
||||
ExportedTypes = assembly.GetExportedTypes().Where(x => IsTypeFromModule(x, descriptor))
|
||||
};
|
||||
}
|
||||
else {
|
||||
// If the extension is not loaded by us, there is no cached state
|
||||
// we need to invalidate
|
||||
_dependenciesFolder.Remove(entry.Descriptor.Name, this.GetType().FullName);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsTypeFromModule(Type type, ExtensionDescriptor descriptor) {
|
||||
return (type.Namespace + ".").StartsWith("Orchard.Core." + descriptor.Name + ".");
|
||||
|
@@ -4,36 +4,60 @@ using Orchard.Caching;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
using Orchard.FileSystems.Dependencies;
|
||||
using Orchard.FileSystems.VirtualPath;
|
||||
using Orchard.Logging;
|
||||
|
||||
namespace Orchard.Environment.Extensions.Loaders {
|
||||
public class DynamicExtensionLoader : IExtensionLoader {
|
||||
private readonly IHostEnvironment _hostEnvironment;
|
||||
public class DynamicExtensionLoader : ExtensionLoaderBase {
|
||||
private readonly IBuildManager _buildManager;
|
||||
private readonly IVirtualPathProvider _virtualPathProvider;
|
||||
private readonly IVirtualPathMonitor _virtualPathMonitor;
|
||||
private readonly IDependenciesFolder _dependenciesFolder;
|
||||
|
||||
public DynamicExtensionLoader(IHostEnvironment hostEnvironment,
|
||||
public DynamicExtensionLoader(
|
||||
IBuildManager buildManager,
|
||||
IVirtualPathProvider virtualPathProvider,
|
||||
IVirtualPathMonitor virtualPathMonitor,
|
||||
IDependenciesFolder dependenciesFolder) {
|
||||
_hostEnvironment = hostEnvironment;
|
||||
|
||||
_buildManager = buildManager;
|
||||
_virtualPathProvider = virtualPathProvider;
|
||||
_virtualPathMonitor = virtualPathMonitor;
|
||||
_dependenciesFolder = dependenciesFolder;
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
|
||||
public int Order { get { return 100; } }
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public void Monitor(ExtensionDescriptor descriptor, Action<IVolatileToken> monitor) {
|
||||
public override int Order { get { return 100; } }
|
||||
|
||||
public override string GetAssemblyDirective(DependencyDescriptor dependency) {
|
||||
return string.Format("<%@ Assembly Src=\"{0}\"%>", dependency.VirtualPath);
|
||||
}
|
||||
|
||||
public override void Monitor(ExtensionDescriptor descriptor, Action<IVolatileToken> monitor) {
|
||||
// We need to monitor the path to the ".csproj" file.
|
||||
string projectPath = GetProjectPath(descriptor);
|
||||
if (projectPath != null)
|
||||
if (projectPath != null) {
|
||||
Logger.Information("Monitoring virtual path {0}", projectPath);
|
||||
monitor(_virtualPathMonitor.WhenPathChanges(projectPath));
|
||||
}
|
||||
}
|
||||
|
||||
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||
public override void ExtensionRemoved(ExtensionLoadingContext ctx, DependencyDescriptor dependency) {
|
||||
// Since a dynamic assembly is not active anymore, we need to notify ASP.NET
|
||||
// that a new site compilation is needed (since ascx files may be referencing
|
||||
// this now removed extension).
|
||||
ctx.ResetSiteCompilation = true;
|
||||
}
|
||||
|
||||
public override void ExtensionDeactivated(ExtensionLoadingContext ctx, bool isNewExtension, ExtensionDescriptor extension) {
|
||||
// Since a dynamic assembly is not active anymore, we need to notify ASP.NET
|
||||
// that a new site compilation is needed (since ascx files may be referencing
|
||||
// this now removed extension).
|
||||
ctx.ResetSiteCompilation = true;
|
||||
}
|
||||
|
||||
public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||
string projectPath = GetProjectPath(descriptor);
|
||||
if (projectPath == null)
|
||||
return null;
|
||||
@@ -46,6 +70,21 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
};
|
||||
}
|
||||
|
||||
public override ExtensionEntry Load(ExtensionDescriptor descriptor) {
|
||||
var dependency = _dependenciesFolder.GetDescriptor(descriptor.Name);
|
||||
if (dependency != null && dependency.LoaderName == this.Name) {
|
||||
|
||||
var assembly = _buildManager.GetCompiledAssembly(dependency.VirtualPath);
|
||||
|
||||
return new ExtensionEntry {
|
||||
Descriptor = descriptor,
|
||||
Assembly = assembly,
|
||||
ExportedTypes = assembly.GetExportedTypes(),
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private string GetProjectPath(ExtensionDescriptor descriptor) {
|
||||
string projectPath = _virtualPathProvider.Combine(descriptor.Location, descriptor.Name,
|
||||
descriptor.Name + ".csproj");
|
||||
@@ -56,33 +95,5 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
|
||||
return projectPath;
|
||||
}
|
||||
|
||||
|
||||
public ExtensionEntry Load(ExtensionProbeEntry entry) {
|
||||
if (entry.Loader == this) {
|
||||
var assembly = _buildManager.GetCompiledAssembly(entry.VirtualPath);
|
||||
|
||||
_dependenciesFolder.Store(new DependencyDescriptor {
|
||||
ModuleName = entry.Descriptor.Name,
|
||||
LoaderName = this.GetType().FullName,
|
||||
VirtualPath = entry.VirtualPath,
|
||||
FileName = assembly.Location
|
||||
});
|
||||
|
||||
return new ExtensionEntry {
|
||||
Descriptor = entry.Descriptor,
|
||||
Assembly = assembly,
|
||||
ExportedTypes = assembly.GetExportedTypes(),
|
||||
};
|
||||
}
|
||||
else {
|
||||
// If the extension is not loaded by us, there is some cached state we need to invalidate
|
||||
// 1) The webforms views which have been compiled with ".csproj" assembly source
|
||||
// 2) The modules which contains features which depend on us
|
||||
//TODO
|
||||
_dependenciesFolder.Remove(entry.Descriptor.Name, this.GetType().FullName);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Orchard.Caching;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
using Orchard.FileSystems.Dependencies;
|
||||
|
||||
namespace Orchard.Environment.Extensions.Loaders {
|
||||
public abstract class ExtensionLoaderBase : IExtensionLoader {
|
||||
public abstract int Order { get; }
|
||||
public string Name { get { return this.GetType().Name; } }
|
||||
|
||||
public abstract ExtensionProbeEntry Probe(ExtensionDescriptor descriptor);
|
||||
public abstract ExtensionEntry Load(ExtensionDescriptor descriptor);
|
||||
|
||||
public virtual void ExtensionActivated(ExtensionLoadingContext ctx, bool isNewExtension, ExtensionDescriptor extension) {}
|
||||
public virtual void ExtensionDeactivated(ExtensionLoadingContext ctx, bool isNewExtension, ExtensionDescriptor extension){}
|
||||
public virtual void ExtensionRemoved(ExtensionLoadingContext ctx, DependencyDescriptor dependency) { }
|
||||
public virtual void Monitor(ExtensionDescriptor extension, Action<IVolatileToken> monitor) { }
|
||||
public virtual string GetAssemblyDirective(DependencyDescriptor dependency) { return null; }
|
||||
|
||||
public static bool FileIsNewer(string sourceFileName, string destinationFileName) {
|
||||
if (!File.Exists(destinationFileName))
|
||||
return true;
|
||||
|
||||
return File.GetLastWriteTimeUtc(sourceFileName) > File.GetLastWriteTimeUtc(destinationFileName);
|
||||
}
|
||||
|
||||
public static bool IsAssemblyLoaded(string moduleName) {
|
||||
return AppDomain.CurrentDomain.GetAssemblies().Any(a => a.GetName().Name == moduleName);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,19 +1,29 @@
|
||||
using System;
|
||||
using Orchard.Caching;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
using Orchard.FileSystems.Dependencies;
|
||||
|
||||
namespace Orchard.Environment.Extensions.Loaders {
|
||||
public interface IExtensionLoader {
|
||||
int Order { get; }
|
||||
ExtensionProbeEntry Probe(ExtensionDescriptor descriptor);
|
||||
ExtensionEntry Load(ExtensionProbeEntry descriptor);
|
||||
void Monitor(ExtensionDescriptor descriptor, Action<IVolatileToken> monitor);
|
||||
}
|
||||
|
||||
public class ExtensionProbeEntry {
|
||||
public ExtensionDescriptor Descriptor { get; set; }
|
||||
public IExtensionLoader Loader { get; set; }
|
||||
public string VirtualPath { get; set; }
|
||||
public DateTime LastModificationTimeUtc { get; set; }
|
||||
}
|
||||
|
||||
public interface IExtensionLoader {
|
||||
int Order { get; }
|
||||
string Name { get; }
|
||||
|
||||
ExtensionProbeEntry Probe(ExtensionDescriptor descriptor);
|
||||
ExtensionEntry Load(ExtensionDescriptor descriptor);
|
||||
|
||||
void ExtensionActivated(ExtensionLoadingContext ctx, bool isNewExtension, ExtensionDescriptor extension);
|
||||
void ExtensionDeactivated(ExtensionLoadingContext ctx, bool isNewExtension, ExtensionDescriptor extension);
|
||||
void ExtensionRemoved(ExtensionLoadingContext ctx, DependencyDescriptor dependency);
|
||||
|
||||
void Monitor(ExtensionDescriptor extension, Action<IVolatileToken> monitor);
|
||||
|
||||
string GetAssemblyDirective(DependencyDescriptor dependency);
|
||||
}
|
||||
}
|
@@ -1,16 +1,18 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Orchard.Caching;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
using Orchard.FileSystems.Dependencies;
|
||||
using Orchard.FileSystems.VirtualPath;
|
||||
using Orchard.Logging;
|
||||
|
||||
namespace Orchard.Environment.Extensions.Loaders {
|
||||
/// <summary>
|
||||
/// Load an extension by looking into the "bin" subdirectory of an
|
||||
/// extension directory.
|
||||
/// </summary>
|
||||
public class PrecompiledExtensionLoader : IExtensionLoader {
|
||||
public class PrecompiledExtensionLoader : ExtensionLoaderBase {
|
||||
private readonly IDependenciesFolder _dependenciesFolder;
|
||||
private readonly IVirtualPathProvider _virtualPathProvider;
|
||||
private readonly IVirtualPathMonitor _virtualPathMonitor;
|
||||
@@ -19,26 +21,64 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
_dependenciesFolder = dependenciesFolder;
|
||||
_virtualPathProvider = virtualPathProvider;
|
||||
_virtualPathMonitor = virtualPathMonitor;
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
|
||||
public int Order { get { return 30; } }
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public void Monitor(ExtensionDescriptor descriptor, Action<IVolatileToken> monitor) {
|
||||
public override int Order { get { return 30; } }
|
||||
|
||||
public override string GetAssemblyDirective(DependencyDescriptor dependency) {
|
||||
return string.Format("<%@ Assembly Name=\"{0}\"%>", dependency.Name);
|
||||
}
|
||||
|
||||
public override void ExtensionRemoved(ExtensionLoadingContext ctx, DependencyDescriptor dependency) {
|
||||
var assemblyFileName = _dependenciesFolder.GetProbingAssemblyPhysicalFileName(dependency.Name);
|
||||
if (File.Exists(assemblyFileName)) {
|
||||
ctx.FilesToDelete.Add(assemblyFileName);
|
||||
|
||||
// We need to restart the appDomain if the assembly is loaded
|
||||
if (IsAssemblyLoaded(dependency.Name)) {
|
||||
Logger.Information("Extension removed: Setting AppDomain for restart because assembly {0} is loaded", dependency.Name);
|
||||
ctx.RestartAppDomain = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExtensionActivated(ExtensionLoadingContext ctx, bool isNewExtension, ExtensionDescriptor extension) {
|
||||
string sourceFileName = _virtualPathProvider.MapPath(GetAssemblyPath(extension));
|
||||
string destinationFileName = _dependenciesFolder.GetProbingAssemblyPhysicalFileName(extension.Name);
|
||||
if (FileIsNewer(sourceFileName, destinationFileName)) {
|
||||
ctx.FilesToCopy.Add(sourceFileName, destinationFileName);
|
||||
// We need to restart the appDomain if the assembly is loaded
|
||||
if (IsAssemblyLoaded(extension.Name)) {
|
||||
Logger.Information("Extension activated: Setting AppDomain for restart because assembly {0} is loaded", extension.Name);
|
||||
ctx.RestartAppDomain = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExtensionDeactivated(ExtensionLoadingContext ctx, bool isNewExtension, ExtensionDescriptor extension) {
|
||||
var assemblyFileName = _dependenciesFolder.GetProbingAssemblyPhysicalFileName(extension.Name);
|
||||
if (File.Exists(assemblyFileName)) {
|
||||
ctx.FilesToDelete.Add(assemblyFileName);
|
||||
// We need to restart the appDomain if the assembly is loaded
|
||||
if (IsAssemblyLoaded(extension.Name)) {
|
||||
Logger.Information("Extension deactivated: Setting AppDomain for restart because assembly {0} is loaded", extension.Name);
|
||||
ctx.RestartAppDomain = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Monitor(ExtensionDescriptor descriptor, Action<IVolatileToken> monitor) {
|
||||
string assemblyPath = GetAssemblyPath(descriptor);
|
||||
if (assemblyPath != null)
|
||||
if (assemblyPath != null) {
|
||||
Logger.Information("Monitoring virtual path \"{0}\"", assemblyPath);
|
||||
monitor(_virtualPathMonitor.WhenPathChanges(assemblyPath));
|
||||
}
|
||||
|
||||
public string GetAssemblyPath(ExtensionDescriptor descriptor) {
|
||||
var assemblyPath = _virtualPathProvider.Combine(descriptor.Location, descriptor.Name, "bin",
|
||||
descriptor.Name + ".dll");
|
||||
if (!_virtualPathProvider.FileExists(assemblyPath))
|
||||
return null;
|
||||
|
||||
return assemblyPath;
|
||||
}
|
||||
|
||||
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||
public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||
var assemblyPath = GetAssemblyPath(descriptor);
|
||||
if (assemblyPath == null)
|
||||
return null;
|
||||
@@ -51,28 +91,30 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
};
|
||||
}
|
||||
|
||||
public ExtensionEntry Load(ExtensionProbeEntry entry) {
|
||||
if (entry.Loader == this) {
|
||||
_dependenciesFolder.StorePrecompiledAssembly(entry.Descriptor.Name, entry.VirtualPath, this.GetType().FullName);
|
||||
public override ExtensionEntry Load(ExtensionDescriptor descriptor) {
|
||||
var dependency = _dependenciesFolder.GetDescriptor(descriptor.Name);
|
||||
if (dependency != null && dependency.LoaderName == this.Name) {
|
||||
|
||||
var assembly = _dependenciesFolder.LoadAssembly(entry.Descriptor.Name);
|
||||
var assembly = _dependenciesFolder.GetProbingAssembly(descriptor.Name);
|
||||
if (assembly == null)
|
||||
return null;
|
||||
|
||||
return new ExtensionEntry {
|
||||
Descriptor = entry.Descriptor,
|
||||
Assembly = assembly,
|
||||
ExportedTypes = assembly.GetExportedTypes()
|
||||
Descriptor = descriptor,
|
||||
Assembly = assembly.Assembly(),
|
||||
ExportedTypes = assembly.Assembly().GetExportedTypes()
|
||||
};
|
||||
}
|
||||
else {
|
||||
// If the extension is not loaded by us, there is some cached state we need to invalidate
|
||||
// 1) The webforms views which have been compiled with ".csproj" assembly source
|
||||
// 2) The modules which contains features which depend on us
|
||||
//TODO
|
||||
_dependenciesFolder.Remove(entry.Descriptor.Name, this.GetType().FullName);
|
||||
return null;
|
||||
}
|
||||
|
||||
public string GetAssemblyPath(ExtensionDescriptor descriptor) {
|
||||
var assemblyPath = _virtualPathProvider.Combine(descriptor.Location, descriptor.Name, "bin",
|
||||
descriptor.Name + ".dll");
|
||||
if (!_virtualPathProvider.FileExists(assemblyPath))
|
||||
return null;
|
||||
|
||||
return assemblyPath;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,32 +1,62 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Orchard.Caching;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
using Orchard.FileSystems.Dependencies;
|
||||
using Orchard.FileSystems.VirtualPath;
|
||||
using Orchard.Logging;
|
||||
|
||||
namespace Orchard.Environment.Extensions.Loaders {
|
||||
/// <summary>
|
||||
/// Load an extension using the "Assembly.Load" method if the
|
||||
/// file can be found in the "App_Data/Dependencies" folder.
|
||||
/// </summary>
|
||||
public class ProbingExtensionLoader : IExtensionLoader {
|
||||
public class ProbingExtensionLoader : ExtensionLoaderBase {
|
||||
private readonly IDependenciesFolder _dependenciesFolder;
|
||||
private readonly IVirtualPathProvider _virtualPathProvider;
|
||||
|
||||
public ProbingExtensionLoader(IDependenciesFolder dependenciesFolder) {
|
||||
public ProbingExtensionLoader(IDependenciesFolder dependenciesFolder, IVirtualPathProvider virtualPathProvider) {
|
||||
_dependenciesFolder = dependenciesFolder;
|
||||
_virtualPathProvider = virtualPathProvider;
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
|
||||
public int Order { get { return 40; } }
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public void Monitor(ExtensionDescriptor descriptor, Action<IVolatileToken> monitor) {
|
||||
// We don't monitor assemblies loaded from this probing directory,
|
||||
// because they are just a copy of the assemblies from the module
|
||||
// bin directory.
|
||||
public override int Order { get { return 40; } }
|
||||
|
||||
public override string GetAssemblyDirective(DependencyDescriptor dependency) {
|
||||
return string.Format("<%@ Assembly Name=\"{0}\"%>", dependency.Name);
|
||||
}
|
||||
|
||||
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||
if (!_dependenciesFolder.HasPrecompiledAssembly(descriptor.Name))
|
||||
public override void ExtensionRemoved(ExtensionLoadingContext ctx, DependencyDescriptor dependency) {
|
||||
var assemblyFileName = _dependenciesFolder.GetProbingAssemblyPhysicalFileName(dependency.Name);
|
||||
if (File.Exists(assemblyFileName)) {
|
||||
ctx.FilesToDelete.Add(assemblyFileName);
|
||||
// We need to restart the appDomain if the assembly is loaded
|
||||
if (IsAssemblyLoaded(dependency.Name)) {
|
||||
Logger.Information("Extension removed: Setting AppDomain for restart because assembly {0} is loaded", dependency.Name);
|
||||
ctx.RestartAppDomain = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExtensionDeactivated(ExtensionLoadingContext ctx, bool isNewExtension, ExtensionDescriptor extension) {
|
||||
var assemblyFileName = _dependenciesFolder.GetProbingAssemblyPhysicalFileName(extension.Name);
|
||||
if (File.Exists(assemblyFileName)) {
|
||||
ctx.FilesToDelete.Add(assemblyFileName);
|
||||
// We need to restart the appDomain if the assembly is loaded
|
||||
if (IsAssemblyLoaded(extension.Name)) {
|
||||
Logger.Information("Extension deactivated: Setting AppDomain for restart because assembly {0} is loaded", extension.Name);
|
||||
ctx.RestartAppDomain = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||
var probingAssembly = _dependenciesFolder.GetProbingAssembly(descriptor.Name);
|
||||
if (probingAssembly == null)
|
||||
return null;
|
||||
|
||||
var desc = _dependenciesFolder.GetDescriptor(descriptor.Name);
|
||||
@@ -35,33 +65,27 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
|
||||
return new ExtensionProbeEntry {
|
||||
Descriptor = descriptor,
|
||||
LastModificationTimeUtc = File.GetLastWriteTimeUtc(desc.FileName),
|
||||
LastModificationTimeUtc = probingAssembly.LastWriteTimeUtc(),
|
||||
Loader = this,
|
||||
VirtualPath = desc.VirtualPath
|
||||
};
|
||||
}
|
||||
|
||||
public ExtensionEntry Load(ExtensionProbeEntry entry) {
|
||||
if (entry.Loader == this) {
|
||||
var assembly = _dependenciesFolder.LoadAssembly(entry.Descriptor.Name);
|
||||
public override ExtensionEntry Load(ExtensionDescriptor descriptor) {
|
||||
var dependency = _dependenciesFolder.GetDescriptor(descriptor.Name);
|
||||
if (dependency != null && dependency.LoaderName == this.Name) {
|
||||
|
||||
var assembly = _dependenciesFolder.GetProbingAssembly(descriptor.Name);
|
||||
if (assembly == null)
|
||||
return null;
|
||||
|
||||
return new ExtensionEntry {
|
||||
Descriptor = entry.Descriptor,
|
||||
Assembly = assembly,
|
||||
ExportedTypes = assembly.GetExportedTypes()
|
||||
Descriptor = descriptor,
|
||||
Assembly = assembly.Assembly(),
|
||||
ExportedTypes = assembly.Assembly().GetExportedTypes()
|
||||
};
|
||||
}
|
||||
else {
|
||||
// If the extension is not loaded by us, there is some cached state we need to invalidate
|
||||
// 1) The webforms views which have been compiled with "Assembly Name=""" directive
|
||||
// 2) The modules which contains features which depend on us
|
||||
// 3) The binary from the App_Data directory
|
||||
//TODO
|
||||
_dependenciesFolder.Remove(entry.Descriptor.Name, this.GetType().FullName);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,32 +1,35 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Web.Compilation;
|
||||
using System.Web.Hosting;
|
||||
using Orchard.Caching;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
using Orchard.FileSystems.Dependencies;
|
||||
using Orchard.FileSystems.VirtualPath;
|
||||
|
||||
namespace Orchard.Environment.Extensions.Loaders {
|
||||
/// <summary>
|
||||
/// Load an extension by looking through the BuildManager referenced assemblies
|
||||
/// </summary>
|
||||
public class ReferencedExtensionLoader : IExtensionLoader {
|
||||
public class ReferencedExtensionLoader : ExtensionLoaderBase {
|
||||
private readonly IDependenciesFolder _dependenciesFolder;
|
||||
private readonly IVirtualPathProvider _virtualPathProvider;
|
||||
|
||||
public ReferencedExtensionLoader(IDependenciesFolder dependenciesFolder) {
|
||||
public ReferencedExtensionLoader(IDependenciesFolder dependenciesFolder, IVirtualPathProvider virtualPathProvider) {
|
||||
_dependenciesFolder = dependenciesFolder;
|
||||
_virtualPathProvider = virtualPathProvider;
|
||||
}
|
||||
|
||||
public int Order { get { return 20; } }
|
||||
public override int Order { get { return 20; } }
|
||||
|
||||
public void Monitor(ExtensionDescriptor descriptor, Action<IVolatileToken> monitor) {
|
||||
// We don't monitor assemblies loaded from the "~/bin" directory,
|
||||
// because they are monitored by the ASP.NET runtime.
|
||||
public override void ExtensionDeactivated(ExtensionLoadingContext ctx, bool isNewExtension, ExtensionDescriptor extension) {
|
||||
var assemblyPath = _virtualPathProvider.Combine("~/bin", extension.Name + ".dll");
|
||||
if (_virtualPathProvider.FileExists(assemblyPath)) {
|
||||
ctx.FilesToDelete.Add(_virtualPathProvider.MapPath(assemblyPath));
|
||||
}
|
||||
}
|
||||
|
||||
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||
public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||
if (HostingEnvironment.IsHosted == false)
|
||||
return null;
|
||||
|
||||
@@ -45,36 +48,24 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
};
|
||||
}
|
||||
|
||||
public ExtensionEntry Load(ExtensionProbeEntry entry) {
|
||||
public override ExtensionEntry Load(ExtensionDescriptor descriptor) {
|
||||
if (HostingEnvironment.IsHosted == false)
|
||||
return null;
|
||||
|
||||
if (entry.Loader == this) {
|
||||
var dependency = _dependenciesFolder.GetDescriptor(descriptor.Name);
|
||||
if (dependency != null && dependency.LoaderName == this.Name) {
|
||||
|
||||
var assembly = BuildManager.GetReferencedAssemblies()
|
||||
.OfType<Assembly>()
|
||||
.FirstOrDefault(x => x.GetName().Name == entry.Descriptor.Name);
|
||||
|
||||
_dependenciesFolder.Store(new DependencyDescriptor {
|
||||
ModuleName = entry.Descriptor.Name,
|
||||
LoaderName = this.GetType().FullName,
|
||||
VirtualPath = entry.VirtualPath,
|
||||
FileName = assembly.Location
|
||||
});
|
||||
|
||||
.FirstOrDefault(x => x.GetName().Name == descriptor.Name);
|
||||
|
||||
return new ExtensionEntry {
|
||||
Descriptor = entry.Descriptor,
|
||||
Descriptor = descriptor,
|
||||
Assembly = assembly,
|
||||
ExportedTypes = assembly.GetExportedTypes()
|
||||
};
|
||||
}
|
||||
else {
|
||||
// If the extension is not loaded by us, there is no cached state
|
||||
// we need to invalidate
|
||||
_dependenciesFolder.Remove(entry.Descriptor.Name, this.GetType().FullName);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -57,6 +57,7 @@ namespace Orchard.Environment {
|
||||
.As<ICompositionStrategy>()
|
||||
.SingleInstance();
|
||||
{
|
||||
builder.RegisterType<ExtensionLoaderCoordinator>().As<IExtensionLoaderCoordinator>().SingleInstance();
|
||||
builder.RegisterType<ExtensionManager>().As<IExtensionManager>().SingleInstance();
|
||||
{
|
||||
builder.RegisterType<ModuleFolders>().As<IExtensionFolders>()
|
||||
|
@@ -6,11 +6,12 @@ using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using Orchard.Caching;
|
||||
using Orchard.FileSystems.AppData;
|
||||
using Orchard.Localization;
|
||||
|
||||
namespace Orchard.FileSystems.Dependencies {
|
||||
public class DefaultDependenciesFolder : IDependenciesFolder {
|
||||
private readonly string _basePath = "Dependencies";
|
||||
private readonly string _persistanceFileName = "dependencies.xml";
|
||||
private const string BasePath = "Dependencies";
|
||||
private const string FileName = "dependencies.xml";
|
||||
private readonly ICacheManager _cacheManager;
|
||||
private readonly IAppDataFolder _appDataFolder;
|
||||
private readonly InvalidationToken _writeThroughToken;
|
||||
@@ -19,11 +20,14 @@ namespace Orchard.FileSystems.Dependencies {
|
||||
_cacheManager = cacheManager;
|
||||
_appDataFolder = appDataFolder;
|
||||
_writeThroughToken = new InvalidationToken();
|
||||
T = NullLocalizer.Instance;
|
||||
}
|
||||
|
||||
public Localizer T { get; set; }
|
||||
|
||||
private string PersistencePath {
|
||||
get {
|
||||
return _appDataFolder.Combine(_basePath, _persistanceFileName);
|
||||
return _appDataFolder.Combine(BasePath, FileName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,106 +35,47 @@ namespace Orchard.FileSystems.Dependencies {
|
||||
get {
|
||||
return _cacheManager.Get(PersistencePath,
|
||||
ctx => {
|
||||
_appDataFolder.CreateDirectory(_basePath);
|
||||
_appDataFolder.CreateDirectory(BasePath);
|
||||
|
||||
ctx.Monitor(_appDataFolder.WhenPathChanges(ctx.Key));
|
||||
ctx.Monitor(_writeThroughToken);
|
||||
|
||||
_appDataFolder.CreateDirectory(_basePath);
|
||||
_appDataFolder.CreateDirectory(BasePath);
|
||||
return ReadDependencies(ctx.Key).ToList();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class InvalidationToken : IVolatileToken {
|
||||
public bool IsCurrent { get; set; }
|
||||
public IEnumerable<DependencyDescriptor> LoadDescriptors() {
|
||||
return Descriptors;
|
||||
}
|
||||
|
||||
public void StorePrecompiledAssembly(string moduleModuleName, string virtualPath, string loaderName) {
|
||||
_appDataFolder.CreateDirectory(_basePath);
|
||||
|
||||
// Only store assembly if it's more recent that what we have stored already (if anything)
|
||||
var assemblyFileName = _appDataFolder.MapPath(virtualPath);
|
||||
if (IsNewerAssembly(moduleModuleName, assemblyFileName)) {
|
||||
var destinationFileName = Path.GetFileName(assemblyFileName);
|
||||
var destinationPath = _appDataFolder.MapPath(_appDataFolder.Combine(_basePath, destinationFileName));
|
||||
File.Copy(assemblyFileName, destinationPath, true);
|
||||
|
||||
StoreDepencyInformation(new DependencyDescriptor {
|
||||
ModuleName = moduleModuleName,
|
||||
LoaderName = loaderName,
|
||||
VirtualPath = virtualPath,
|
||||
FileName = destinationFileName
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(string moduleName, string loaderName) {
|
||||
Func<DependencyDescriptor, bool> predicate = (d => d.ModuleName == moduleName && d.LoaderName == loaderName);
|
||||
if (Descriptors.Any(predicate)) {
|
||||
var newDescriptors = Descriptors.Where(e => !predicate(e));
|
||||
|
||||
WriteDependencies(PersistencePath, newDescriptors);
|
||||
}
|
||||
}
|
||||
|
||||
public void Store(DependencyDescriptor descriptor) {
|
||||
StoreDepencyInformation(descriptor);
|
||||
public void StoreDescriptors(IEnumerable<DependencyDescriptor> dependencyDescriptors) {
|
||||
WriteDependencies(PersistencePath, dependencyDescriptors);
|
||||
}
|
||||
|
||||
public DependencyDescriptor GetDescriptor(string moduleName) {
|
||||
return Descriptors.SingleOrDefault(d => d.ModuleName == moduleName);
|
||||
return Descriptors.SingleOrDefault(d => d.Name == moduleName);
|
||||
}
|
||||
|
||||
private bool IsNewerAssembly(string moduleName, string assemblyFileName) {
|
||||
var dependency = Descriptors.SingleOrDefault(d => d.ModuleName == moduleName);
|
||||
if (dependency == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var existingFileName = _appDataFolder.MapPath(_appDataFolder.Combine(_basePath, dependency.FileName));
|
||||
if (!File.Exists(existingFileName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (File.GetLastWriteTimeUtc(existingFileName) < File.GetLastWriteTimeUtc(assemblyFileName));
|
||||
}
|
||||
|
||||
private void StoreDepencyInformation(DependencyDescriptor descriptor) {
|
||||
var dependencies = Descriptors.ToList();
|
||||
int index = dependencies.FindIndex(d => d.ModuleName == descriptor.ModuleName);
|
||||
if (index < 0) {
|
||||
dependencies.Add(descriptor);
|
||||
}
|
||||
else {
|
||||
dependencies[index] = descriptor;
|
||||
}
|
||||
|
||||
WriteDependencies(PersistencePath, dependencies);
|
||||
}
|
||||
|
||||
public Assembly LoadAssembly(string moduleName) {
|
||||
_appDataFolder.CreateDirectory(_basePath);
|
||||
|
||||
var dependency = Descriptors.SingleOrDefault(d => d.ModuleName == moduleName);
|
||||
if (dependency == null)
|
||||
public ProbingAssembly GetProbingAssembly(string moduleName) {
|
||||
var path = PrecompiledAssemblyPath(moduleName);
|
||||
if (!_appDataFolder.FileExists(path))
|
||||
return null;
|
||||
|
||||
if (!_appDataFolder.FileExists(_appDataFolder.Combine(_basePath, dependency.FileName)))
|
||||
return null;
|
||||
|
||||
return Assembly.Load(Path.GetFileNameWithoutExtension(dependency.FileName));
|
||||
return new ProbingAssembly {
|
||||
Path = path,
|
||||
Assembly = () => Assembly.Load(moduleName),
|
||||
LastWriteTimeUtc = () => File.GetLastWriteTimeUtc(_appDataFolder.MapPath(path)),
|
||||
};
|
||||
}
|
||||
|
||||
public bool HasPrecompiledAssembly(string moduleName) {
|
||||
var dependency = Descriptors.SingleOrDefault(d => d.ModuleName == moduleName);
|
||||
if (dependency == null)
|
||||
return false;
|
||||
public string GetProbingAssemblyPhysicalFileName(string moduleName) {
|
||||
return _appDataFolder.MapPath(PrecompiledAssemblyPath(moduleName));
|
||||
}
|
||||
|
||||
if (!_appDataFolder.FileExists(_appDataFolder.Combine(_basePath, dependency.FileName)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
private string PrecompiledAssemblyPath(string moduleName) {
|
||||
return _appDataFolder.Combine(BasePath, moduleName + ".dll");
|
||||
}
|
||||
|
||||
private IEnumerable<DependencyDescriptor> ReadDependencies(string persistancePath) {
|
||||
@@ -146,9 +91,8 @@ namespace Orchard.FileSystems.Dependencies {
|
||||
.Elements(ns("Dependencies"))
|
||||
.Elements(ns("Dependency"))
|
||||
.Select(e => new DependencyDescriptor {
|
||||
ModuleName = elem(e, "ModuleName"),
|
||||
Name = elem(e, "ModuleName"),
|
||||
VirtualPath = elem(e, "VirtualPath"),
|
||||
FileName = elem(e, "FileName"),
|
||||
LoaderName = elem(e, "LoaderName")
|
||||
})
|
||||
.ToList();
|
||||
@@ -161,10 +105,9 @@ namespace Orchard.FileSystems.Dependencies {
|
||||
var document = new XDocument();
|
||||
document.Add(new XElement(ns("Dependencies")));
|
||||
var elements = dependencies.Select(d => new XElement("Dependency",
|
||||
new XElement(ns("ModuleName"), d.ModuleName),
|
||||
new XElement(ns("ModuleName"), d.Name),
|
||||
new XElement(ns("VirtualPath"), d.VirtualPath),
|
||||
new XElement(ns("LoaderName"), d.LoaderName),
|
||||
new XElement(ns("FileName"), d.FileName)));
|
||||
new XElement(ns("LoaderName"), d.LoaderName)));
|
||||
document.Root.Add(elements);
|
||||
|
||||
using (var stream = _appDataFolder.CreateFile(persistancePath)) {
|
||||
@@ -174,5 +117,9 @@ namespace Orchard.FileSystems.Dependencies {
|
||||
// Ensure cache is invalidated right away, not waiting for file change notification to happen
|
||||
_writeThroughToken.IsCurrent = false;
|
||||
}
|
||||
|
||||
private class InvalidationToken : IVolatileToken {
|
||||
public bool IsCurrent { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,20 +1,27 @@
|
||||
using System.Reflection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Orchard.Caching;
|
||||
|
||||
namespace Orchard.FileSystems.Dependencies {
|
||||
public class DependencyDescriptor {
|
||||
public string ModuleName { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string LoaderName { get; set; }
|
||||
public string VirtualPath { get; set; }
|
||||
public string FileName { get; set; }
|
||||
}
|
||||
|
||||
public class ProbingAssembly {
|
||||
public string Path { get; set; }
|
||||
public Func<DateTime> LastWriteTimeUtc { get; set; }
|
||||
public Func<Assembly> Assembly { get; set; }
|
||||
}
|
||||
|
||||
public interface IDependenciesFolder : IVolatileProvider {
|
||||
void Store(DependencyDescriptor descriptor);
|
||||
void StorePrecompiledAssembly(string moduleName, string virtualPath, string loaderName);
|
||||
void Remove(string moduleName, string loaderName);
|
||||
DependencyDescriptor GetDescriptor(string moduleName);
|
||||
bool HasPrecompiledAssembly(string moduleName);
|
||||
Assembly LoadAssembly(string moduleName);
|
||||
IEnumerable<DependencyDescriptor> LoadDescriptors();
|
||||
void StoreDescriptors(IEnumerable<DependencyDescriptor> dependencyDescriptors);
|
||||
|
||||
ProbingAssembly GetProbingAssembly(string moduleName);
|
||||
string GetProbingAssemblyPhysicalFileName(string moduleName);
|
||||
}
|
||||
}
|
||||
|
@@ -1,16 +1,15 @@
|
||||
using System.IO;
|
||||
using System.Web.Hosting;
|
||||
using Orchard.Environment.Extensions.Loaders;
|
||||
|
||||
namespace Orchard.FileSystems.Dependencies {
|
||||
public class WebFormsExtensionsVirtualFile : VirtualFile {
|
||||
private readonly DependencyDescriptor _dependencyDescriptor;
|
||||
private readonly VirtualFile _actualFile;
|
||||
private readonly string _assemblyDirective;
|
||||
|
||||
public WebFormsExtensionsVirtualFile(string virtualPath, DependencyDescriptor dependencyDescriptor, VirtualFile actualFile)
|
||||
public WebFormsExtensionsVirtualFile(string virtualPath, VirtualFile actualFile, string assemblyDirective)
|
||||
: base(virtualPath) {
|
||||
_dependencyDescriptor = dependencyDescriptor;
|
||||
_actualFile = actualFile;
|
||||
_assemblyDirective = assemblyDirective;
|
||||
}
|
||||
|
||||
public override string Name {
|
||||
@@ -37,7 +36,7 @@ namespace Orchard.FileSystems.Dependencies {
|
||||
for (var line = reader.ReadLine(); line != null; line = reader.ReadLine()) {
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(line) && !assemblyDirectiveAdded) {
|
||||
line += GetAssemblyDirective();
|
||||
line += _assemblyDirective;
|
||||
assemblyDirectiveAdded = true;
|
||||
}
|
||||
|
||||
@@ -49,14 +48,5 @@ namespace Orchard.FileSystems.Dependencies {
|
||||
return new MemoryStream(memoryStream.GetBuffer(), 0, length);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetAssemblyDirective() {
|
||||
if (_dependencyDescriptor.LoaderName == typeof(DynamicExtensionLoader).FullName) {
|
||||
return string.Format("<%@ Assembly Src=\"{0}\"%>", _dependencyDescriptor.VirtualPath);
|
||||
}
|
||||
else {
|
||||
return string.Format("<%@ Assembly Name=\"{0}\"%>", _dependencyDescriptor.ModuleName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,16 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web.Hosting;
|
||||
using Orchard.Environment.Extensions.Loaders;
|
||||
using Orchard.FileSystems.VirtualPath;
|
||||
|
||||
namespace Orchard.FileSystems.Dependencies {
|
||||
public class WebFormsExtensionsVirtualPathProvider : VirtualPathProvider, ICustomVirtualPathProvider {
|
||||
private readonly IDependenciesFolder _dependenciesFolder;
|
||||
private readonly IEnumerable<IExtensionLoader> _loaders;
|
||||
private readonly string[] _prefixes = { "~/Modules/", "/Modules/" };
|
||||
private readonly string[] _extensions = { ".ascx", ".aspx", ".master" };
|
||||
|
||||
public WebFormsExtensionsVirtualPathProvider(IDependenciesFolder dependenciesFolder) {
|
||||
public WebFormsExtensionsVirtualPathProvider(IDependenciesFolder dependenciesFolder, IEnumerable<IExtensionLoader> loaders) {
|
||||
_dependenciesFolder = dependenciesFolder;
|
||||
_loaders = loaders;
|
||||
}
|
||||
|
||||
public override bool DirectoryExists(string virtualDir) {
|
||||
@@ -24,27 +28,35 @@ namespace Orchard.FileSystems.Dependencies {
|
||||
public override VirtualFile GetFile(string virtualPath) {
|
||||
var actualFile = Previous.GetFile(virtualPath);
|
||||
|
||||
return GetCustomVirtualFile(virtualPath, actualFile) ?? actualFile;
|
||||
}
|
||||
|
||||
private VirtualFile GetCustomVirtualFile(string virtualPath, VirtualFile actualFile) {
|
||||
var prefix = PrefixMatch(virtualPath, _prefixes);
|
||||
if (prefix == null)
|
||||
return actualFile;
|
||||
return null;
|
||||
|
||||
var extension = ExtensionMatch(virtualPath, _extensions);
|
||||
if (extension == null)
|
||||
return actualFile;
|
||||
return null;
|
||||
|
||||
var moduleName = ModuleMatch(virtualPath, prefix);
|
||||
if (moduleName == null)
|
||||
return actualFile;
|
||||
return null;
|
||||
|
||||
// It looks like we have a module name. Is this one of this modules
|
||||
// with its assembly stored in the "App_Data/Dependencies" folder?
|
||||
var dependencyDescriptor = _dependenciesFolder.GetDescriptor(moduleName);
|
||||
if (dependencyDescriptor == null)
|
||||
return actualFile;
|
||||
return null;
|
||||
|
||||
// Yes: we need to wrap the VirtualFile to add the <%@ Assembly Name=".."%> directive
|
||||
// in the content.
|
||||
return new WebFormsExtensionsVirtualFile(virtualPath, dependencyDescriptor, actualFile);
|
||||
var loader = _loaders.Where(l => l.Name == dependencyDescriptor.LoaderName).FirstOrDefault();
|
||||
if (loader == null)
|
||||
return null;
|
||||
|
||||
var directive = loader.GetAssemblyDirective(dependencyDescriptor);
|
||||
if (string.IsNullOrEmpty(directive))
|
||||
return null;
|
||||
|
||||
return new WebFormsExtensionsVirtualFile(virtualPath, actualFile, directive);
|
||||
}
|
||||
|
||||
private string ModuleMatch(string virtualPath, string prefix) {
|
||||
|
@@ -353,6 +353,10 @@
|
||||
<Compile Include="Data\DataModule.cs" />
|
||||
<Compile Include="Data\Orderable.cs" />
|
||||
<Compile Include="Environment\DefaultOrchardShell.cs" />
|
||||
<Compile Include="Environment\Extensions\ExtensionLoaderCoordinator.cs" />
|
||||
<Compile Include="Environment\Extensions\ExtensionLoadingContext.cs" />
|
||||
<Compile Include="Environment\Extensions\IExtensionLoaderCoordinator.cs" />
|
||||
<Compile Include="Environment\Extensions\Loaders\ExtensionLoaderBase.cs" />
|
||||
<Compile Include="FileSystems\AppData\IAppDataFolderRoot.cs" />
|
||||
<Compile Include="FileSystems\Dependencies\DefaultDependenciesFolder.cs" />
|
||||
<Compile Include="FileSystems\VirtualPath\DefaultVirtualPathMonitor.cs" />
|
||||
|
Reference in New Issue
Block a user