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:
Renaud Paquay
2010-06-14 09:01:31 -07:00
parent ad54ffdbb2
commit cd3a537396
22 changed files with 605 additions and 344 deletions

View File

@@ -43,23 +43,19 @@ namespace Orchard.Tests.Environment.Extensions {
} }
} }
public class StubLoaders : IExtensionLoader { public class StubLoaders : ExtensionLoaderBase {
#region Implementation of IExtensionLoader #region Implementation of IExtensionLoader
public int Order { public override int Order {
get { return 1; } get { return 1; }
} }
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) { public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
return new ExtensionProbeEntry { Descriptor = descriptor, Loader = this }; return new ExtensionProbeEntry { Descriptor = descriptor, Loader = this };
} }
public ExtensionEntry Load(ExtensionProbeEntry entry) { public override ExtensionEntry Load(ExtensionDescriptor descriptor) {
return new ExtensionEntry { Descriptor = entry.Descriptor, ExportedTypes = new[] { typeof(Alpha), typeof(Beta), typeof(Phi) } }; return new ExtensionEntry { Descriptor = descriptor, ExportedTypes = new[] { typeof(Alpha), typeof(Beta), typeof(Phi) } };
}
public void Monitor(ExtensionDescriptor descriptor, Action<IVolatileToken> monitor) {
throw new NotImplementedException();
} }
#endregion #endregion

View File

@@ -143,10 +143,6 @@
<Project>{17F86780-9A1F-4AA1-86F1-875EEC2730C7}</Project> <Project>{17F86780-9A1F-4AA1-86F1-875EEC2730C7}</Project>
<Name>Orchard.Modules</Name> <Name>Orchard.Modules</Name>
</ProjectReference> </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"> <ProjectReference Include="Modules\Orchard.Pages\Orchard.Pages.csproj">
<Project>{4A9C04A6-0986-4A92-A610-5F59FF273FB9}</Project> <Project>{4A9C04A6-0986-4A92-A610-5F59FF273FB9}</Project>
<Name>Orchard.Pages</Name> <Name>Orchard.Pages</Name>

View File

@@ -23,6 +23,7 @@ namespace Orchard.Environment {
private readonly IRunningShellTable _runningShellTable; private readonly IRunningShellTable _runningShellTable;
private readonly IProcessingEngine _processingEngine; private readonly IProcessingEngine _processingEngine;
private readonly IExtensionManager _extensionManager; private readonly IExtensionManager _extensionManager;
private readonly IExtensionLoaderCoordinator _extensionLoaderCoordinator;
private readonly ICacheManager _cacheManager; private readonly ICacheManager _cacheManager;
private IEnumerable<ShellContext> _current; private IEnumerable<ShellContext> _current;
@@ -33,6 +34,7 @@ namespace Orchard.Environment {
IRunningShellTable runningShellTable, IRunningShellTable runningShellTable,
IProcessingEngine processingEngine, IProcessingEngine processingEngine,
IExtensionManager extensionManager, IExtensionManager extensionManager,
IExtensionLoaderCoordinator extensionLoaderCoordinator,
ICacheManager cacheManager, ICacheManager cacheManager,
ControllerBuilder controllerBuilder) { ControllerBuilder controllerBuilder) {
_shellSettingsManager = shellSettingsManager; _shellSettingsManager = shellSettingsManager;
@@ -40,6 +42,7 @@ namespace Orchard.Environment {
_runningShellTable = runningShellTable; _runningShellTable = runningShellTable;
_processingEngine = processingEngine; _processingEngine = processingEngine;
_extensionManager = extensionManager; _extensionManager = extensionManager;
_extensionLoaderCoordinator = extensionLoaderCoordinator;
_cacheManager = cacheManager; _cacheManager = cacheManager;
_controllerBuilder = controllerBuilder; _controllerBuilder = controllerBuilder;
Logger = NullLogger.Instance; Logger = NullLogger.Instance;
@@ -78,10 +81,17 @@ namespace Orchard.Environment {
} }
IEnumerable<ShellContext> BuildCurrent() { IEnumerable<ShellContext> BuildCurrent() {
if (_current == null) {
lock (this) { lock (this) {
return _current ?? (_current = CreateAndActivate().ToArray()); if (_current == null) {
SetupExtensions();
MonitorExtensions();
_current = CreateAndActivate().ToArray();
} }
} }
}
return _current;
}
IEnumerable<ShellContext> CreateAndActivate() { IEnumerable<ShellContext> CreateAndActivate() {
var allSettings = _shellSettingsManager.LoadSettings(); var allSettings = _shellSettingsManager.LoadSettings();
@@ -119,13 +129,21 @@ namespace Orchard.Environment {
return _shellContextFactory.CreateShellContext(settings); return _shellContextFactory.CreateShellContext(settings);
} }
protected virtual void BeginRequest() { private void SetupExtensions() {
_extensionLoaderCoordinator.SetupExtensions();
}
private void MonitorExtensions() {
_cacheManager.Get("OrchardHost_Extensions", _cacheManager.Get("OrchardHost_Extensions",
ctx => { ctx => {
_extensionManager.Monitor(ctx.Monitor); _extensionLoaderCoordinator.MonitorExtensions(ctx.Monitor);
_current = null; _current = null;
return ""; return "";
}); });
}
protected virtual void BeginRequest() {
MonitorExtensions();
BuildCurrent(); BuildCurrent();
} }

View 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);
}
}
}
}
}

View File

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

View File

@@ -4,7 +4,6 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Web; using System.Web;
using ICSharpCode.SharpZipLib.Zip; using ICSharpCode.SharpZipLib.Zip;
using Orchard.Caching;
using Orchard.Environment.Extensions.Folders; using Orchard.Environment.Extensions.Folders;
using Orchard.Environment.Extensions.Helpers; using Orchard.Environment.Extensions.Helpers;
using Orchard.Environment.Extensions.Loaders; using Orchard.Environment.Extensions.Loaders;
@@ -154,33 +153,13 @@ namespace Orchard.Environment.Extensions {
Directory.Delete(targetFolder, true); 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) { private ExtensionEntry BuildEntry(ExtensionDescriptor descriptor) {
var loaders = _loaders.ToList(); foreach (var loader in _loaders) {
ExtensionEntry entry = loader.Load(descriptor);
var moreRecentEntry = loaders if (entry != null)
.Select(loader => loader.Probe(descriptor)) return entry;
.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 null;
return result;
} }
} }
} }

View File

@@ -0,0 +1,9 @@
using System;
using Orchard.Caching;
namespace Orchard.Environment.Extensions {
public interface IExtensionLoaderCoordinator {
void SetupExtensions();
void MonitorExtensions(Action<IVolatileToken> monitor);
}
}

View File

@@ -1,7 +1,5 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Web; using System.Web;
using Orchard.Caching;
using Orchard.Environment.Extensions.Models; using Orchard.Environment.Extensions.Models;
namespace Orchard.Environment.Extensions { namespace Orchard.Environment.Extensions {
@@ -10,6 +8,5 @@ namespace Orchard.Environment.Extensions {
IEnumerable<Feature> LoadFeatures(IEnumerable<FeatureDescriptor> featureDescriptors); IEnumerable<Feature> LoadFeatures(IEnumerable<FeatureDescriptor> featureDescriptors);
void InstallExtension(string extensionType, HttpPostedFileBase extensionBundle); void InstallExtension(string extensionType, HttpPostedFileBase extensionBundle);
void UninstallExtension(string extensionType, string extensionName); void UninstallExtension(string extensionType, string extensionName);
void Monitor(Action<IVolatileToken> monitor);
} }
} }

View File

@@ -1,29 +1,21 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Orchard.Caching;
using Orchard.Environment.Extensions.Models; using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.Dependencies; using Orchard.FileSystems.Dependencies;
using Orchard.FileSystems.VirtualPath; using Orchard.FileSystems.VirtualPath;
namespace Orchard.Environment.Extensions.Loaders { namespace Orchard.Environment.Extensions.Loaders {
public class AreaExtensionLoader : IExtensionLoader { public class AreaExtensionLoader : ExtensionLoaderBase {
private readonly IDependenciesFolder _dependenciesFolder; private readonly IDependenciesFolder _dependenciesFolder;
private readonly IVirtualPathProvider _virtualPathProvider;
public AreaExtensionLoader(IDependenciesFolder dependenciesFolder, IVirtualPathProvider virtualPathProvider) { public AreaExtensionLoader(IDependenciesFolder dependenciesFolder) {
_dependenciesFolder = 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) { public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
// We don't need to monitor anything since we are loaded
// from the application assembly itself.
}
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
if (descriptor.Location == "~/Areas") { if (descriptor.Location == "~/Areas") {
return new ExtensionProbeEntry { return new ExtensionProbeEntry {
Descriptor = descriptor, Descriptor = descriptor,
@@ -35,25 +27,20 @@ namespace Orchard.Environment.Extensions.Loaders {
return null; return null;
} }
public ExtensionEntry Load(ExtensionProbeEntry entry) { public override ExtensionEntry Load(ExtensionDescriptor descriptor) {
if (entry.Loader == this) { var dependency = _dependenciesFolder.GetDescriptor(descriptor.Name);
if (dependency != null && dependency.LoaderName == this.Name) {
var assembly = Assembly.Load("Orchard.Web"); var assembly = Assembly.Load("Orchard.Web");
_dependenciesFolder.Store(new DependencyDescriptor { ModuleName = entry.Descriptor.Name, LoaderName = this.GetType().FullName});
return new ExtensionEntry { return new ExtensionEntry {
Descriptor = entry.Descriptor, Descriptor = descriptor,
Assembly = assembly, 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; return null;
} }
}
private static bool IsTypeFromModule(Type type, ExtensionDescriptor descriptor) { private static bool IsTypeFromModule(Type type, ExtensionDescriptor descriptor) {
return (type.Namespace + ".").StartsWith("Orchard.Web.Areas." + descriptor.Name + "."); return (type.Namespace + ".").StartsWith("Orchard.Web.Areas." + descriptor.Name + ".");

View File

@@ -10,23 +10,16 @@ namespace Orchard.Environment.Extensions.Loaders {
/// <summary> /// <summary>
/// Load an extension by looking into specific namespaces of the "Orchard.Core" assembly /// Load an extension by looking into specific namespaces of the "Orchard.Core" assembly
/// </summary> /// </summary>
public class CoreExtensionLoader : IExtensionLoader { public class CoreExtensionLoader : ExtensionLoaderBase {
private readonly IDependenciesFolder _dependenciesFolder; private readonly IDependenciesFolder _dependenciesFolder;
private readonly IVirtualPathProvider _virtualPathProvider;
public CoreExtensionLoader(IDependenciesFolder dependenciesFolder, IVirtualPathProvider virtualPathProvider) { public CoreExtensionLoader(IDependenciesFolder dependenciesFolder) {
_dependenciesFolder = 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) { public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
// We don't need to monitor anything since we are loaded
// from the core assembly.
}
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
if (descriptor.Location == "~/Core") { if (descriptor.Location == "~/Core") {
return new ExtensionProbeEntry { return new ExtensionProbeEntry {
Descriptor = descriptor, Descriptor = descriptor,
@@ -38,25 +31,20 @@ namespace Orchard.Environment.Extensions.Loaders {
return null; return null;
} }
public ExtensionEntry Load(ExtensionProbeEntry entry) { public override ExtensionEntry Load(ExtensionDescriptor descriptor) {
if (entry.Loader == this) { var dependency = _dependenciesFolder.GetDescriptor(descriptor.Name);
if (dependency != null && dependency.LoaderName == this.Name) {
var assembly = Assembly.Load("Orchard.Core"); var assembly = Assembly.Load("Orchard.Core");
_dependenciesFolder.Store(new DependencyDescriptor { ModuleName = entry.Descriptor.Name, LoaderName = this.GetType().FullName });
return new ExtensionEntry { return new ExtensionEntry {
Descriptor = entry.Descriptor, Descriptor = descriptor,
Assembly = assembly, 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; return null;
} }
}
private static bool IsTypeFromModule(Type type, ExtensionDescriptor descriptor) { private static bool IsTypeFromModule(Type type, ExtensionDescriptor descriptor) {
return (type.Namespace + ".").StartsWith("Orchard.Core." + descriptor.Name + "."); return (type.Namespace + ".").StartsWith("Orchard.Core." + descriptor.Name + ".");

View File

@@ -4,36 +4,60 @@ using Orchard.Caching;
using Orchard.Environment.Extensions.Models; using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.Dependencies; using Orchard.FileSystems.Dependencies;
using Orchard.FileSystems.VirtualPath; using Orchard.FileSystems.VirtualPath;
using Orchard.Logging;
namespace Orchard.Environment.Extensions.Loaders { namespace Orchard.Environment.Extensions.Loaders {
public class DynamicExtensionLoader : IExtensionLoader { public class DynamicExtensionLoader : ExtensionLoaderBase {
private readonly IHostEnvironment _hostEnvironment;
private readonly IBuildManager _buildManager; private readonly IBuildManager _buildManager;
private readonly IVirtualPathProvider _virtualPathProvider; private readonly IVirtualPathProvider _virtualPathProvider;
private readonly IVirtualPathMonitor _virtualPathMonitor; private readonly IVirtualPathMonitor _virtualPathMonitor;
private readonly IDependenciesFolder _dependenciesFolder; private readonly IDependenciesFolder _dependenciesFolder;
public DynamicExtensionLoader(IHostEnvironment hostEnvironment, public DynamicExtensionLoader(
IBuildManager buildManager, IBuildManager buildManager,
IVirtualPathProvider virtualPathProvider, IVirtualPathProvider virtualPathProvider,
IVirtualPathMonitor virtualPathMonitor, IVirtualPathMonitor virtualPathMonitor,
IDependenciesFolder dependenciesFolder) { IDependenciesFolder dependenciesFolder) {
_hostEnvironment = hostEnvironment;
_buildManager = buildManager; _buildManager = buildManager;
_virtualPathProvider = virtualPathProvider; _virtualPathProvider = virtualPathProvider;
_virtualPathMonitor = virtualPathMonitor; _virtualPathMonitor = virtualPathMonitor;
_dependenciesFolder = dependenciesFolder; _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); string projectPath = GetProjectPath(descriptor);
if (projectPath != null) if (projectPath != null) {
Logger.Information("Monitoring virtual path {0}", projectPath);
monitor(_virtualPathMonitor.WhenPathChanges(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); string projectPath = GetProjectPath(descriptor);
if (projectPath == null) if (projectPath == null)
return 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) { private string GetProjectPath(ExtensionDescriptor descriptor) {
string projectPath = _virtualPathProvider.Combine(descriptor.Location, descriptor.Name, string projectPath = _virtualPathProvider.Combine(descriptor.Location, descriptor.Name,
descriptor.Name + ".csproj"); descriptor.Name + ".csproj");
@@ -56,33 +95,5 @@ namespace Orchard.Environment.Extensions.Loaders {
return projectPath; 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;
}
}
} }
} }

View File

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

View File

@@ -1,19 +1,29 @@
using System; using System;
using Orchard.Caching; using Orchard.Caching;
using Orchard.Environment.Extensions.Models; using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.Dependencies;
namespace Orchard.Environment.Extensions.Loaders { 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 class ExtensionProbeEntry {
public ExtensionDescriptor Descriptor { get; set; } public ExtensionDescriptor Descriptor { get; set; }
public IExtensionLoader Loader { get; set; } public IExtensionLoader Loader { get; set; }
public string VirtualPath { get; set; } public string VirtualPath { get; set; }
public DateTime LastModificationTimeUtc { 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);
}
} }

View File

@@ -1,16 +1,18 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using Orchard.Caching; using Orchard.Caching;
using Orchard.Environment.Extensions.Models; using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.Dependencies; using Orchard.FileSystems.Dependencies;
using Orchard.FileSystems.VirtualPath; using Orchard.FileSystems.VirtualPath;
using Orchard.Logging;
namespace Orchard.Environment.Extensions.Loaders { namespace Orchard.Environment.Extensions.Loaders {
/// <summary> /// <summary>
/// Load an extension by looking into the "bin" subdirectory of an /// Load an extension by looking into the "bin" subdirectory of an
/// extension directory. /// extension directory.
/// </summary> /// </summary>
public class PrecompiledExtensionLoader : IExtensionLoader { public class PrecompiledExtensionLoader : ExtensionLoaderBase {
private readonly IDependenciesFolder _dependenciesFolder; private readonly IDependenciesFolder _dependenciesFolder;
private readonly IVirtualPathProvider _virtualPathProvider; private readonly IVirtualPathProvider _virtualPathProvider;
private readonly IVirtualPathMonitor _virtualPathMonitor; private readonly IVirtualPathMonitor _virtualPathMonitor;
@@ -19,26 +21,64 @@ namespace Orchard.Environment.Extensions.Loaders {
_dependenciesFolder = dependenciesFolder; _dependenciesFolder = dependenciesFolder;
_virtualPathProvider = virtualPathProvider; _virtualPathProvider = virtualPathProvider;
_virtualPathMonitor = virtualPathMonitor; _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); string assemblyPath = GetAssemblyPath(descriptor);
if (assemblyPath != null) if (assemblyPath != null) {
Logger.Information("Monitoring virtual path \"{0}\"", assemblyPath);
monitor(_virtualPathMonitor.WhenPathChanges(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); var assemblyPath = GetAssemblyPath(descriptor);
if (assemblyPath == null) if (assemblyPath == null)
return null; return null;
@@ -51,28 +91,30 @@ namespace Orchard.Environment.Extensions.Loaders {
}; };
} }
public ExtensionEntry Load(ExtensionProbeEntry entry) { public override ExtensionEntry Load(ExtensionDescriptor descriptor) {
if (entry.Loader == this) { var dependency = _dependenciesFolder.GetDescriptor(descriptor.Name);
_dependenciesFolder.StorePrecompiledAssembly(entry.Descriptor.Name, entry.VirtualPath, this.GetType().FullName); if (dependency != null && dependency.LoaderName == this.Name) {
var assembly = _dependenciesFolder.LoadAssembly(entry.Descriptor.Name); var assembly = _dependenciesFolder.GetProbingAssembly(descriptor.Name);
if (assembly == null) if (assembly == null)
return null; return null;
return new ExtensionEntry { return new ExtensionEntry {
Descriptor = entry.Descriptor, Descriptor = descriptor,
Assembly = assembly, Assembly = assembly.Assembly(),
ExportedTypes = assembly.GetExportedTypes() 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; 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;
} }
} }
} }

View File

@@ -1,32 +1,62 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using Orchard.Caching; using Orchard.Caching;
using Orchard.Environment.Extensions.Models; using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.Dependencies; using Orchard.FileSystems.Dependencies;
using Orchard.FileSystems.VirtualPath; using Orchard.FileSystems.VirtualPath;
using Orchard.Logging;
namespace Orchard.Environment.Extensions.Loaders { namespace Orchard.Environment.Extensions.Loaders {
/// <summary> /// <summary>
/// Load an extension using the "Assembly.Load" method if the /// Load an extension using the "Assembly.Load" method if the
/// file can be found in the "App_Data/Dependencies" folder. /// file can be found in the "App_Data/Dependencies" folder.
/// </summary> /// </summary>
public class ProbingExtensionLoader : IExtensionLoader { public class ProbingExtensionLoader : ExtensionLoaderBase {
private readonly IDependenciesFolder _dependenciesFolder; private readonly IDependenciesFolder _dependenciesFolder;
private readonly IVirtualPathProvider _virtualPathProvider;
public ProbingExtensionLoader(IDependenciesFolder dependenciesFolder) { public ProbingExtensionLoader(IDependenciesFolder dependenciesFolder, IVirtualPathProvider virtualPathProvider) {
_dependenciesFolder = dependenciesFolder; _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) { public override int Order { get { return 40; } }
// We don't monitor assemblies loaded from this probing directory,
// because they are just a copy of the assemblies from the module public override string GetAssemblyDirective(DependencyDescriptor dependency) {
// bin directory. return string.Format("<%@ Assembly Name=\"{0}\"%>", dependency.Name);
} }
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) { public override void ExtensionRemoved(ExtensionLoadingContext ctx, DependencyDescriptor dependency) {
if (!_dependenciesFolder.HasPrecompiledAssembly(descriptor.Name)) 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; return null;
var desc = _dependenciesFolder.GetDescriptor(descriptor.Name); var desc = _dependenciesFolder.GetDescriptor(descriptor.Name);
@@ -35,33 +65,27 @@ namespace Orchard.Environment.Extensions.Loaders {
return new ExtensionProbeEntry { return new ExtensionProbeEntry {
Descriptor = descriptor, Descriptor = descriptor,
LastModificationTimeUtc = File.GetLastWriteTimeUtc(desc.FileName), LastModificationTimeUtc = probingAssembly.LastWriteTimeUtc(),
Loader = this, Loader = this,
VirtualPath = desc.VirtualPath VirtualPath = desc.VirtualPath
}; };
} }
public ExtensionEntry Load(ExtensionProbeEntry entry) { public override ExtensionEntry Load(ExtensionDescriptor descriptor) {
if (entry.Loader == this) { var dependency = _dependenciesFolder.GetDescriptor(descriptor.Name);
var assembly = _dependenciesFolder.LoadAssembly(entry.Descriptor.Name); if (dependency != null && dependency.LoaderName == this.Name) {
var assembly = _dependenciesFolder.GetProbingAssembly(descriptor.Name);
if (assembly == null) if (assembly == null)
return null; return null;
return new ExtensionEntry { return new ExtensionEntry {
Descriptor = entry.Descriptor, Descriptor = descriptor,
Assembly = assembly, Assembly = assembly.Assembly(),
ExportedTypes = assembly.GetExportedTypes() 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; return null;
} }
} }
} }
}

View File

@@ -1,32 +1,35 @@
using System; using System.IO;
using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Web.Compilation; using System.Web.Compilation;
using System.Web.Hosting; using System.Web.Hosting;
using Orchard.Caching;
using Orchard.Environment.Extensions.Models; using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.Dependencies; using Orchard.FileSystems.Dependencies;
using Orchard.FileSystems.VirtualPath;
namespace Orchard.Environment.Extensions.Loaders { namespace Orchard.Environment.Extensions.Loaders {
/// <summary> /// <summary>
/// Load an extension by looking through the BuildManager referenced assemblies /// Load an extension by looking through the BuildManager referenced assemblies
/// </summary> /// </summary>
public class ReferencedExtensionLoader : IExtensionLoader { public class ReferencedExtensionLoader : ExtensionLoaderBase {
private readonly IDependenciesFolder _dependenciesFolder; private readonly IDependenciesFolder _dependenciesFolder;
private readonly IVirtualPathProvider _virtualPathProvider;
public ReferencedExtensionLoader(IDependenciesFolder dependenciesFolder) { public ReferencedExtensionLoader(IDependenciesFolder dependenciesFolder, IVirtualPathProvider virtualPathProvider) {
_dependenciesFolder = dependenciesFolder; _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) { public override void ExtensionDeactivated(ExtensionLoadingContext ctx, bool isNewExtension, ExtensionDescriptor extension) {
// We don't monitor assemblies loaded from the "~/bin" directory, var assemblyPath = _virtualPathProvider.Combine("~/bin", extension.Name + ".dll");
// because they are monitored by the ASP.NET runtime. 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) if (HostingEnvironment.IsHosted == false)
return null; 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) if (HostingEnvironment.IsHosted == false)
return null; return null;
if (entry.Loader == this) { var dependency = _dependenciesFolder.GetDescriptor(descriptor.Name);
if (dependency != null && dependency.LoaderName == this.Name) {
var assembly = BuildManager.GetReferencedAssemblies() var assembly = BuildManager.GetReferencedAssemblies()
.OfType<Assembly>() .OfType<Assembly>()
.FirstOrDefault(x => x.GetName().Name == entry.Descriptor.Name); .FirstOrDefault(x => x.GetName().Name == descriptor.Name);
_dependenciesFolder.Store(new DependencyDescriptor {
ModuleName = entry.Descriptor.Name,
LoaderName = this.GetType().FullName,
VirtualPath = entry.VirtualPath,
FileName = assembly.Location
});
return new ExtensionEntry { return new ExtensionEntry {
Descriptor = entry.Descriptor, Descriptor = descriptor,
Assembly = assembly, Assembly = assembly,
ExportedTypes = assembly.GetExportedTypes() 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; return null;
} }
} }
} }
}

View File

@@ -57,6 +57,7 @@ namespace Orchard.Environment {
.As<ICompositionStrategy>() .As<ICompositionStrategy>()
.SingleInstance(); .SingleInstance();
{ {
builder.RegisterType<ExtensionLoaderCoordinator>().As<IExtensionLoaderCoordinator>().SingleInstance();
builder.RegisterType<ExtensionManager>().As<IExtensionManager>().SingleInstance(); builder.RegisterType<ExtensionManager>().As<IExtensionManager>().SingleInstance();
{ {
builder.RegisterType<ModuleFolders>().As<IExtensionFolders>() builder.RegisterType<ModuleFolders>().As<IExtensionFolders>()

View File

@@ -6,11 +6,12 @@ using System.Reflection;
using System.Xml.Linq; using System.Xml.Linq;
using Orchard.Caching; using Orchard.Caching;
using Orchard.FileSystems.AppData; using Orchard.FileSystems.AppData;
using Orchard.Localization;
namespace Orchard.FileSystems.Dependencies { namespace Orchard.FileSystems.Dependencies {
public class DefaultDependenciesFolder : IDependenciesFolder { public class DefaultDependenciesFolder : IDependenciesFolder {
private readonly string _basePath = "Dependencies"; private const string BasePath = "Dependencies";
private readonly string _persistanceFileName = "dependencies.xml"; private const string FileName = "dependencies.xml";
private readonly ICacheManager _cacheManager; private readonly ICacheManager _cacheManager;
private readonly IAppDataFolder _appDataFolder; private readonly IAppDataFolder _appDataFolder;
private readonly InvalidationToken _writeThroughToken; private readonly InvalidationToken _writeThroughToken;
@@ -19,11 +20,14 @@ namespace Orchard.FileSystems.Dependencies {
_cacheManager = cacheManager; _cacheManager = cacheManager;
_appDataFolder = appDataFolder; _appDataFolder = appDataFolder;
_writeThroughToken = new InvalidationToken(); _writeThroughToken = new InvalidationToken();
T = NullLocalizer.Instance;
} }
public Localizer T { get; set; }
private string PersistencePath { private string PersistencePath {
get { get {
return _appDataFolder.Combine(_basePath, _persistanceFileName); return _appDataFolder.Combine(BasePath, FileName);
} }
} }
@@ -31,106 +35,47 @@ namespace Orchard.FileSystems.Dependencies {
get { get {
return _cacheManager.Get(PersistencePath, return _cacheManager.Get(PersistencePath,
ctx => { ctx => {
_appDataFolder.CreateDirectory(_basePath); _appDataFolder.CreateDirectory(BasePath);
ctx.Monitor(_appDataFolder.WhenPathChanges(ctx.Key)); ctx.Monitor(_appDataFolder.WhenPathChanges(ctx.Key));
ctx.Monitor(_writeThroughToken); ctx.Monitor(_writeThroughToken);
_appDataFolder.CreateDirectory(_basePath); _appDataFolder.CreateDirectory(BasePath);
return ReadDependencies(ctx.Key).ToList(); return ReadDependencies(ctx.Key).ToList();
}); });
} }
} }
public class InvalidationToken : IVolatileToken { public IEnumerable<DependencyDescriptor> LoadDescriptors() {
public bool IsCurrent { get; set; } return Descriptors;
} }
public void StorePrecompiledAssembly(string moduleModuleName, string virtualPath, string loaderName) { public void StoreDescriptors(IEnumerable<DependencyDescriptor> dependencyDescriptors) {
_appDataFolder.CreateDirectory(_basePath); WriteDependencies(PersistencePath, dependencyDescriptors);
// 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 DependencyDescriptor GetDescriptor(string moduleName) { 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) { public ProbingAssembly GetProbingAssembly(string moduleName) {
var dependency = Descriptors.SingleOrDefault(d => d.ModuleName == moduleName); var path = PrecompiledAssemblyPath(moduleName);
if (dependency == null) { if (!_appDataFolder.FileExists(path))
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)
return null; return null;
if (!_appDataFolder.FileExists(_appDataFolder.Combine(_basePath, dependency.FileName))) return new ProbingAssembly {
return null; Path = path,
Assembly = () => Assembly.Load(moduleName),
return Assembly.Load(Path.GetFileNameWithoutExtension(dependency.FileName)); LastWriteTimeUtc = () => File.GetLastWriteTimeUtc(_appDataFolder.MapPath(path)),
};
} }
public bool HasPrecompiledAssembly(string moduleName) { public string GetProbingAssemblyPhysicalFileName(string moduleName) {
var dependency = Descriptors.SingleOrDefault(d => d.ModuleName == moduleName); return _appDataFolder.MapPath(PrecompiledAssemblyPath(moduleName));
if (dependency == null) }
return false;
if (!_appDataFolder.FileExists(_appDataFolder.Combine(_basePath, dependency.FileName))) private string PrecompiledAssemblyPath(string moduleName) {
return false; return _appDataFolder.Combine(BasePath, moduleName + ".dll");
return true;
} }
private IEnumerable<DependencyDescriptor> ReadDependencies(string persistancePath) { private IEnumerable<DependencyDescriptor> ReadDependencies(string persistancePath) {
@@ -146,9 +91,8 @@ namespace Orchard.FileSystems.Dependencies {
.Elements(ns("Dependencies")) .Elements(ns("Dependencies"))
.Elements(ns("Dependency")) .Elements(ns("Dependency"))
.Select(e => new DependencyDescriptor { .Select(e => new DependencyDescriptor {
ModuleName = elem(e, "ModuleName"), Name = elem(e, "ModuleName"),
VirtualPath = elem(e, "VirtualPath"), VirtualPath = elem(e, "VirtualPath"),
FileName = elem(e, "FileName"),
LoaderName = elem(e, "LoaderName") LoaderName = elem(e, "LoaderName")
}) })
.ToList(); .ToList();
@@ -161,10 +105,9 @@ namespace Orchard.FileSystems.Dependencies {
var document = new XDocument(); var document = new XDocument();
document.Add(new XElement(ns("Dependencies"))); document.Add(new XElement(ns("Dependencies")));
var elements = dependencies.Select(d => new XElement("Dependency", 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("VirtualPath"), d.VirtualPath),
new XElement(ns("LoaderName"), d.LoaderName), new XElement(ns("LoaderName"), d.LoaderName)));
new XElement(ns("FileName"), d.FileName)));
document.Root.Add(elements); document.Root.Add(elements);
using (var stream = _appDataFolder.CreateFile(persistancePath)) { 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 // Ensure cache is invalidated right away, not waiting for file change notification to happen
_writeThroughToken.IsCurrent = false; _writeThroughToken.IsCurrent = false;
} }
private class InvalidationToken : IVolatileToken {
public bool IsCurrent { get; set; }
}
} }
} }

View File

@@ -1,20 +1,27 @@
using System.Reflection; using System;
using System.Collections.Generic;
using System.Reflection;
using Orchard.Caching; using Orchard.Caching;
namespace Orchard.FileSystems.Dependencies { namespace Orchard.FileSystems.Dependencies {
public class DependencyDescriptor { public class DependencyDescriptor {
public string ModuleName { get; set; } public string Name { get; set; }
public string LoaderName { get; set; } public string LoaderName { get; set; }
public string VirtualPath { 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 { 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); DependencyDescriptor GetDescriptor(string moduleName);
bool HasPrecompiledAssembly(string moduleName); IEnumerable<DependencyDescriptor> LoadDescriptors();
Assembly LoadAssembly(string moduleName); void StoreDescriptors(IEnumerable<DependencyDescriptor> dependencyDescriptors);
ProbingAssembly GetProbingAssembly(string moduleName);
string GetProbingAssemblyPhysicalFileName(string moduleName);
} }
} }

View File

@@ -1,16 +1,15 @@
using System.IO; using System.IO;
using System.Web.Hosting; using System.Web.Hosting;
using Orchard.Environment.Extensions.Loaders;
namespace Orchard.FileSystems.Dependencies { namespace Orchard.FileSystems.Dependencies {
public class WebFormsExtensionsVirtualFile : VirtualFile { public class WebFormsExtensionsVirtualFile : VirtualFile {
private readonly DependencyDescriptor _dependencyDescriptor;
private readonly VirtualFile _actualFile; 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) { : base(virtualPath) {
_dependencyDescriptor = dependencyDescriptor;
_actualFile = actualFile; _actualFile = actualFile;
_assemblyDirective = assemblyDirective;
} }
public override string Name { public override string Name {
@@ -37,7 +36,7 @@ namespace Orchard.FileSystems.Dependencies {
for (var line = reader.ReadLine(); line != null; line = reader.ReadLine()) { for (var line = reader.ReadLine(); line != null; line = reader.ReadLine()) {
if (!string.IsNullOrWhiteSpace(line) && !assemblyDirectiveAdded) { if (!string.IsNullOrWhiteSpace(line) && !assemblyDirectiveAdded) {
line += GetAssemblyDirective(); line += _assemblyDirective;
assemblyDirectiveAdded = true; assemblyDirectiveAdded = true;
} }
@@ -49,14 +48,5 @@ namespace Orchard.FileSystems.Dependencies {
return new MemoryStream(memoryStream.GetBuffer(), 0, length); 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);
}
}
} }
} }

View File

@@ -1,16 +1,20 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Web.Hosting; using System.Web.Hosting;
using Orchard.Environment.Extensions.Loaders;
using Orchard.FileSystems.VirtualPath; using Orchard.FileSystems.VirtualPath;
namespace Orchard.FileSystems.Dependencies { namespace Orchard.FileSystems.Dependencies {
public class WebFormsExtensionsVirtualPathProvider : VirtualPathProvider, ICustomVirtualPathProvider { public class WebFormsExtensionsVirtualPathProvider : VirtualPathProvider, ICustomVirtualPathProvider {
private readonly IDependenciesFolder _dependenciesFolder; private readonly IDependenciesFolder _dependenciesFolder;
private readonly IEnumerable<IExtensionLoader> _loaders;
private readonly string[] _prefixes = { "~/Modules/", "/Modules/" }; private readonly string[] _prefixes = { "~/Modules/", "/Modules/" };
private readonly string[] _extensions = { ".ascx", ".aspx", ".master" }; private readonly string[] _extensions = { ".ascx", ".aspx", ".master" };
public WebFormsExtensionsVirtualPathProvider(IDependenciesFolder dependenciesFolder) { public WebFormsExtensionsVirtualPathProvider(IDependenciesFolder dependenciesFolder, IEnumerable<IExtensionLoader> loaders) {
_dependenciesFolder = dependenciesFolder; _dependenciesFolder = dependenciesFolder;
_loaders = loaders;
} }
public override bool DirectoryExists(string virtualDir) { public override bool DirectoryExists(string virtualDir) {
@@ -24,27 +28,35 @@ namespace Orchard.FileSystems.Dependencies {
public override VirtualFile GetFile(string virtualPath) { public override VirtualFile GetFile(string virtualPath) {
var actualFile = Previous.GetFile(virtualPath); var actualFile = Previous.GetFile(virtualPath);
return GetCustomVirtualFile(virtualPath, actualFile) ?? actualFile;
}
private VirtualFile GetCustomVirtualFile(string virtualPath, VirtualFile actualFile) {
var prefix = PrefixMatch(virtualPath, _prefixes); var prefix = PrefixMatch(virtualPath, _prefixes);
if (prefix == null) if (prefix == null)
return actualFile; return null;
var extension = ExtensionMatch(virtualPath, _extensions); var extension = ExtensionMatch(virtualPath, _extensions);
if (extension == null) if (extension == null)
return actualFile; return null;
var moduleName = ModuleMatch(virtualPath, prefix); var moduleName = ModuleMatch(virtualPath, prefix);
if (moduleName == null) 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); var dependencyDescriptor = _dependenciesFolder.GetDescriptor(moduleName);
if (dependencyDescriptor == null) if (dependencyDescriptor == null)
return actualFile; return null;
// Yes: we need to wrap the VirtualFile to add the <%@ Assembly Name=".."%> directive var loader = _loaders.Where(l => l.Name == dependencyDescriptor.LoaderName).FirstOrDefault();
// in the content. if (loader == null)
return new WebFormsExtensionsVirtualFile(virtualPath, dependencyDescriptor, actualFile); 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) { private string ModuleMatch(string virtualPath, string prefix) {

View File

@@ -353,6 +353,10 @@
<Compile Include="Data\DataModule.cs" /> <Compile Include="Data\DataModule.cs" />
<Compile Include="Data\Orderable.cs" /> <Compile Include="Data\Orderable.cs" />
<Compile Include="Environment\DefaultOrchardShell.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\AppData\IAppDataFolderRoot.cs" />
<Compile Include="FileSystems\Dependencies\DefaultDependenciesFolder.cs" /> <Compile Include="FileSystems\Dependencies\DefaultDependenciesFolder.cs" />
<Compile Include="FileSystems\VirtualPath\DefaultVirtualPathMonitor.cs" /> <Compile Include="FileSystems\VirtualPath\DefaultVirtualPathMonitor.cs" />