From 674fe6ecb8743aba5a51d2254b1ea317d712c41b Mon Sep 17 00:00:00 2001 From: Andre Rodrigues Date: Fri, 15 Apr 2011 16:44:12 -0700 Subject: [PATCH 001/139] #17745: Decoding Nuget paths. --HG-- branch : 1.x --- .../Orchard.Tests.Modules.csproj | 7 +- .../Services/FileBasedProjectSystemTests.cs | 103 ++++++++++++++++++ .../Packaging/Services/Hello.World.csproj.txt | 30 +---- .../Packaging/Services/HelloDriver.cs.txt | 9 -- .../Services/FileBaseProjectSystem.cs | 28 ++--- 5 files changed, 114 insertions(+), 63 deletions(-) create mode 100644 src/Orchard.Tests.Modules/Packaging/Services/FileBasedProjectSystemTests.cs delete mode 100644 src/Orchard.Tests.Modules/Packaging/Services/HelloDriver.cs.txt diff --git a/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj b/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj index dfacdb755..2caea26f6 100644 --- a/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj +++ b/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj @@ -149,6 +149,7 @@ + @@ -162,7 +163,6 @@ - @@ -296,12 +296,7 @@ - - - - - diff --git a/src/Orchard.Tests.Modules/Packaging/Services/FileBasedProjectSystemTests.cs b/src/Orchard.Tests.Modules/Packaging/Services/FileBasedProjectSystemTests.cs new file mode 100644 index 000000000..902d45dde --- /dev/null +++ b/src/Orchard.Tests.Modules/Packaging/Services/FileBasedProjectSystemTests.cs @@ -0,0 +1,103 @@ +using System.IO; +using Autofac; +using Moq; +using NuGet; +using NUnit.Framework; +using Orchard.Caching; +using Orchard.Environment; +using Orchard.Environment.Extensions; +using Orchard.Environment.Extensions.Models; +using Orchard.FileSystems.VirtualPath; +using Orchard.FileSystems.WebSite; +using Orchard.Packaging.Services; +using Orchard.Tests.Stubs; +using Orchard.UI.Notify; +using IPackageBuilder = Orchard.Packaging.Services.IPackageBuilder; +using PackageBuilder = Orchard.Packaging.Services.PackageBuilder; + +namespace Orchard.Tests.Modules.Packaging.Services { + [TestFixture] + public class FileBasedProjectSystemTests : ContainerTestBase { + private const string PackageIdentifier = "Hello.World"; + + private readonly string _basePath = Path.Combine(Path.GetTempPath(), "PackageInstallerTests"); + + private Mock _mockedVirtualPathProvider; + + protected override void Register(ContainerBuilder builder) { + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterInstance(new Mock().Object); + builder.RegisterType().As(); + + _mockedVirtualPathProvider = new Mock(); + builder.RegisterInstance(_mockedVirtualPathProvider.Object).As(); + builder.RegisterType().As(); + builder.RegisterType().As() + .As().InstancePerLifetimeScope(); + } + + [SetUp] + public override void Init() { + base.Init(); + + if (Directory.Exists(_basePath)) { + Directory.Delete(_basePath, true); + } + + Directory.CreateDirectory(_basePath); + } + + [TestFixtureTearDown] + public void Clean() { + if (Directory.Exists(_basePath)) { + Directory.Delete(_basePath, true); + } + } + + private Stream BuildHelloWorld(IPackageBuilder packageBuilder) { + // add some content because NuGet requires it + var folder = _container.Resolve(); + using (var sourceStream = GetType().Assembly.GetManifestResourceStream(GetType(), "Hello.World.csproj.txt")) { + folder.AddFile("~/Modules/Hello.World/Hello.World.csproj", new StreamReader(sourceStream).ReadToEnd()); + } + + return packageBuilder.BuildPackage(new ExtensionDescriptor { + ExtensionType = DefaultExtensionTypes.Module, + Id = PackageIdentifier, + Version = "1.0", + Description = "a", + Author = "b" + }); + } + + [Test] + public void ValidPathsTest() { + IPackageBuilder packageBuilder = _container.Resolve(); + Stream stream = BuildHelloWorld(packageBuilder); + + string filename = Path.Combine(_basePath, "package.nupkg"); + using (var fileStream = File.Create(filename)) { + stream.CopyTo(fileStream); + } + + ZipPackage zipPackage = new ZipPackage(filename); + IPackageInstaller packageInstaller = _container.Resolve(); + + _mockedVirtualPathProvider.Setup(v => v.MapPath(It.IsAny())) + .Returns(path => Path.Combine(_basePath, path.Replace("~\\", ""))); + + _mockedVirtualPathProvider.Setup(v => v.Combine(It.IsAny())) + .Returns(Path.Combine); + + PackageInfo packageInfo = packageInstaller.Install(zipPackage, _basePath, _basePath); + Assert.That(packageInfo, Is.Not.Null); + Assert.That(Directory.Exists(Path.Combine(_basePath, "Modules/Hello.World"))); + Assert.That(File.Exists(Path.Combine(_basePath, "Modules/Hello.World/Hello.World.csproj"))); + Assert.That(!File.Exists(Path.Combine(_basePath, "Modules/Hello.World/Service%References/SomeReference.cs"))); + Assert.That(File.Exists(Path.Combine(_basePath, "Modules/Hello.World/Service References/SomeReference.cs"))); + } + } +} diff --git a/src/Orchard.Tests.Modules/Packaging/Services/Hello.World.csproj.txt b/src/Orchard.Tests.Modules/Packaging/Services/Hello.World.csproj.txt index 94b77eec2..ef5172a59 100644 --- a/src/Orchard.Tests.Modules/Packaging/Services/Hello.World.csproj.txt +++ b/src/Orchard.Tests.Modules/Packaging/Services/Hello.World.csproj.txt @@ -70,37 +70,9 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/src/Orchard.Tests.Modules/Packaging/Services/HelloDriver.cs.txt b/src/Orchard.Tests.Modules/Packaging/Services/HelloDriver.cs.txt deleted file mode 100644 index a376f809f..000000000 --- a/src/Orchard.Tests.Modules/Packaging/Services/HelloDriver.cs.txt +++ /dev/null @@ -1,9 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Orchard.Tests.Modules.Packaging { - class HelloDriver { - } -} diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Services/FileBaseProjectSystem.cs b/src/Orchard.Web/Modules/Orchard.Packaging/Services/FileBaseProjectSystem.cs index d9f711d5f..35ef0693d 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Services/FileBaseProjectSystem.cs +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Services/FileBaseProjectSystem.cs @@ -47,7 +47,7 @@ namespace Orchard.Packaging.Services { } public string GetFullPath(string path) { - return Path.Combine(Root, path); + return Path.Combine(Root, Uri.UnescapeDataString(path)); } protected virtual string GetReferencePath(string name) { @@ -55,12 +55,12 @@ namespace Orchard.Packaging.Services { } public void AddFile(string path, Stream stream) { - EnsureDirectory(Path.GetDirectoryName(path)); + string fullPath = GetFullPath(path); + EnsureDirectory(Path.GetDirectoryName(fullPath)); - using (Stream outputStream = File.Create(GetFullPath(path))) { + using (Stream outputStream = File.Create(fullPath)) { stream.CopyTo(outputStream); } - } public void DeleteFile(string path) { @@ -88,9 +88,7 @@ namespace Orchard.Packaging.Services { path = GetFullPath(path); Directory.Delete(path, recursive); } - catch (DirectoryNotFoundException) { - - } + catch (DirectoryNotFoundException) {} } public void AddReference(string referencePath, Stream stream) { @@ -125,12 +123,8 @@ namespace Orchard.Packaging.Services { return Directory.EnumerateFiles(path, filter) .Select(MakeRelativePath); } - catch (UnauthorizedAccessException) { - - } - catch (DirectoryNotFoundException) { - - } + catch (UnauthorizedAccessException) {} + catch (DirectoryNotFoundException) {} return Enumerable.Empty(); } @@ -144,12 +138,8 @@ namespace Orchard.Packaging.Services { return Directory.EnumerateDirectories(path) .Select(MakeRelativePath); } - catch (UnauthorizedAccessException) { - - } - catch (DirectoryNotFoundException) { - - } + catch (UnauthorizedAccessException) {} + catch (DirectoryNotFoundException) {} return Enumerable.Empty(); } From accc1c34d2f03f6edce6fea1ba78ce0b01ea7cfb Mon Sep 17 00:00:00 2001 From: Andre Rodrigues Date: Thu, 28 Apr 2011 13:25:03 -0700 Subject: [PATCH 002/139] #17776: Making gallery feed Context ignore unknown properties. --HG-- branch : 1.x --- .../Orchard.Packaging/Services/PackagingSourceManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackagingSourceManager.cs b/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackagingSourceManager.cs index ffa80b183..5adc82fbb 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackagingSourceManager.cs +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackagingSourceManager.cs @@ -72,7 +72,7 @@ namespace Orchard.Packaging.Services { return (packagingSource == null ? GetSources() : new[] {packagingSource}) .SelectMany( source => { - var galleryFeedContext = new GalleryFeedContext(new Uri(source.FeedUrl)); + var galleryFeedContext = new GalleryFeedContext(new Uri(source.FeedUrl)) { IgnoreMissingProperties = true }; IQueryable packages = includeScreenshots ? galleryFeedContext.Packages.Expand("Screenshots") : galleryFeedContext.Packages; @@ -95,7 +95,7 @@ namespace Orchard.Packaging.Services { public int GetExtensionCount(PackagingSource packagingSource = null, Func, IQueryable> query = null) { return (packagingSource == null ? GetSources() : new[] { packagingSource }) .Sum( source => { - var galleryFeedContext = new GalleryFeedContext(new Uri(source.FeedUrl)); + var galleryFeedContext = new GalleryFeedContext(new Uri(source.FeedUrl)) { IgnoreMissingProperties = true }; IQueryable packages = galleryFeedContext.Packages; if (query != null) { From 657c762d84193178dfc2a02b30a9302817be60cd Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Fri, 29 Apr 2011 07:42:18 -0700 Subject: [PATCH 003/139] #17752 Fixing indexing failure on content deletiong Work Items: 17752 --HG-- branch : 1.x --- .../Indexing/IndexingTaskExecutorTests.cs | 20 +++++++++++++++++++ .../Services/IndexingTaskExecutor.cs | 11 +++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/Orchard.Tests.Modules/Indexing/IndexingTaskExecutorTests.cs b/src/Orchard.Tests.Modules/Indexing/IndexingTaskExecutorTests.cs index 62772909c..b57053c27 100644 --- a/src/Orchard.Tests.Modules/Indexing/IndexingTaskExecutorTests.cs +++ b/src/Orchard.Tests.Modules/Indexing/IndexingTaskExecutorTests.cs @@ -211,11 +211,31 @@ namespace Orchard.Tests.Modules.Indexing { Assert.That(_provider.NumDocs(IndexName), Is.EqualTo(2)); _contentManager.Unpublish(content.ContentItem); + _contentManager.Flush(); while (_indexTaskExecutor.UpdateIndexBatch(IndexName)) {} Assert.That(_provider.NumDocs(IndexName), Is.EqualTo(1)); } + [Test] + public void ShouldUpdateTheIndexWhenContentIsDeleted() { + _contentManager.Create(ThingDriver.ContentTypeName).Text = "Lorem ipsum"; + + while (_indexTaskExecutor.UpdateIndexBatch(IndexName)) { } + Assert.That(_provider.NumDocs(IndexName), Is.EqualTo(1)); + + var content = _contentManager.Create(ThingDriver.ContentTypeName); + content.Text = "Lorem ipsum"; + + while (_indexTaskExecutor.UpdateIndexBatch(IndexName)) { } + Assert.That(_provider.NumDocs(IndexName), Is.EqualTo(2)); + + _contentManager.Remove(content.ContentItem); + _contentManager.Flush(); + + while (_indexTaskExecutor.UpdateIndexBatch(IndexName)) { } + Assert.That(_provider.NumDocs(IndexName), Is.EqualTo(1)); + } [Test] public void ShouldIndexAllContentOverTheLoopSize() { diff --git a/src/Orchard.Web/Modules/Orchard.Indexing/Services/IndexingTaskExecutor.cs b/src/Orchard.Web/Modules/Orchard.Indexing/Services/IndexingTaskExecutor.cs index 9526e1f69..e0cf28360 100644 --- a/src/Orchard.Web/Modules/Orchard.Indexing/Services/IndexingTaskExecutor.cs +++ b/src/Orchard.Web/Modules/Orchard.Indexing/Services/IndexingTaskExecutor.cs @@ -173,21 +173,22 @@ namespace Orchard.Indexing.Services { Logger.Information("Updating index"); _indexingStatus = IndexingStatus.Updating; - var contentItems = _taskRepository + var indexingTasks = _taskRepository .Fetch(x => x.Id > indexSettings.LastIndexedId) .OrderBy(x => x.Id) .Take(ContentItemsPerLoop) .GroupBy(x => x.ContentItemRecord.Id) - .Select(group => new {TaskId = group.Max(task => task.Id), ContentItem = _contentManager.Get(group.Key, VersionOptions.Published)}) + .Select(group => new {TaskId = group.Max(task => task.Id), Id = group.Key, ContentItem = _contentManager.Get(group.Key, VersionOptions.Published)}) .OrderBy(x => x.TaskId) .ToArray(); - foreach (var item in contentItems) { + foreach (var item in indexingTasks) { try { + // item.ContentItem can be null if the content item has been deleted IDocumentIndex documentIndex = ExtractDocumentIndex(item.ContentItem); if (documentIndex == null) { - deleteFromIndex.Add(item.ContentItem.Id); + deleteFromIndex.Add(item.Id); } else if (documentIndex.IsDirty) { addToIndex.Add(documentIndex); @@ -196,7 +197,7 @@ namespace Orchard.Indexing.Services { indexSettings.LastIndexedId = item.TaskId; } catch (Exception ex) { - Logger.Warning(ex, "Unable to index content item #{0} during update", item.ContentItem.Id); + Logger.Warning(ex, "Unable to index content item #{0} during update", item.Id); } } } From 63e2c3475ff5c5ac6acc04bc1b9a4e56f556971f Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Sat, 30 Apr 2011 19:47:29 -0700 Subject: [PATCH 004/139] Don't lose exception callstack if error during startup --HG-- branch : 1.x extra : transplant_source : %B6%B7%FDp%C1%16G%D4%16%9E%F7%85%99%E50%B9%F8%0C%CC%D1 --- src/Orchard.Startup/Starter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Orchard.Startup/Starter.cs b/src/Orchard.Startup/Starter.cs index 6394e3459..2527a4051 100644 --- a/src/Orchard.Startup/Starter.cs +++ b/src/Orchard.Startup/Starter.cs @@ -17,7 +17,7 @@ namespace Orchard.WarmupStarter { // so we need to simulate a "restart". var error = _error; LaunchStartupThread(registrations); - throw error; + throw new ApplicationException("Error during Orchard startup", error); } // Only notify if the host has started up From 0dabbb53229089ef83f1ba18b52bac7aa8230316 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Sat, 30 Apr 2011 19:50:32 -0700 Subject: [PATCH 005/139] PERF: Don't monitor files if component have been disabled --HG-- branch : 1.x extra : transplant_source : %7D%07%23%F0%0ED%E7%B6%00%BA%DA%5C%CBv%24z%07%1B%F2%B6 --- .../Environment/Extensions/Loaders/DynamicExtensionLoader.cs | 3 +++ .../Extensions/Loaders/PrecompiledExtensionLoader.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs index 5061427cc..5e04c0cb9 100644 --- a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs @@ -67,6 +67,9 @@ namespace Orchard.Environment.Extensions.Loaders { } public override void Monitor(ExtensionDescriptor descriptor, Action monitor) { + if (Disabled) + return; + // Monitor .csproj and all .cs files string projectPath = GetProjectPath(descriptor); if (projectPath != null) { diff --git a/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs index 3d3e12f25..8c79f899c 100644 --- a/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs @@ -122,6 +122,9 @@ namespace Orchard.Environment.Extensions.Loaders { } public override void Monitor(ExtensionDescriptor descriptor, Action monitor) { + if (Disabled) + return; + // If the assembly exists, monitor it string assemblyPath = GetAssemblyPath(descriptor); if (assemblyPath != null) { From 8e7550cda9ed7ab1c067e0d887d1480b22af8d9c Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Sun, 1 May 2011 12:44:42 -0700 Subject: [PATCH 006/139] PERF: Use app relative virtual path for file monitoring This is so that the IVirtualPathMonitor implementation can properly cache virtual paths in a single entry. This decreases the number of entries by one or two order of magnitude when complex solutions are loaded (i.e. modules with lot of references to other modules). --HG-- branch : 1.x extra : transplant_source : %11%21n%FA%5Dy%E9%83%E3%86%86%D0%8Ba%FF%98%5C%8D/%22 --- .../Loaders/DynamicExtensionLoader.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs index 5e04c0cb9..f63ac0331 100644 --- a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs @@ -76,8 +76,9 @@ namespace Orchard.Environment.Extensions.Loaders { foreach (var path in GetDependencies(projectPath)) { Logger.Information("Monitoring virtual path \"{0}\"", path); - monitor(_virtualPathMonitor.WhenPathChanges(path)); - _reloadWorkaround.Monitor(_virtualPathMonitor.WhenPathChanges(path)); + var token = _virtualPathMonitor.WhenPathChanges(path); + monitor(token); + _reloadWorkaround.Monitor(token); } } } @@ -182,7 +183,7 @@ namespace Orchard.Environment.Extensions.Loaders { } protected IEnumerable GetDependencies(string projectPath) { - HashSet dependencies = new HashSet { projectPath }; + var dependencies = new HashSet { projectPath }; AddDependencies(projectPath, dependencies); @@ -205,6 +206,18 @@ namespace Orchard.Environment.Extensions.Loaders { ? _virtualPathProvider.GetProjectReferenceVirtualPath(projectPath, referenceDescriptor.SimpleName, referenceDescriptor.Path) : _virtualPathProvider.Combine(basePath, referenceDescriptor.Path); + // Normalize the virtual path (avoid ".." in the path name) + if (!string.IsNullOrEmpty(path)) { + try { + path = _virtualPathProvider.ToAppRelative(path); + } + catch (Exception e) { + // The initial path might have been invalid (e.g. path indicates a path outside the application root) + Logger.Information(e, "Path '{0}' cannot be made app relative", path); + path = null; + } + } + // Attempt to reference the project / library file if (!string.IsNullOrEmpty(path) && !currentSet.Contains(path) && _virtualPathProvider.TryFileExists(path)) { currentSet.Add(path); From 8c67c9f3863660d737efb87193faa7c3d331547e Mon Sep 17 00:00:00 2001 From: Andre Rodrigues Date: Mon, 2 May 2011 14:47:19 -0700 Subject: [PATCH 007/139] #17775: Checking if a theme exists before applying it. --HG-- branch : 1.x --- .../Controllers/AdminController.cs | 160 +++++++++--------- .../Orchard.Themes/Models/ThemeEntry.cs | 4 +- 2 files changed, 85 insertions(+), 79 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.Themes/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.Themes/Controllers/AdminController.cs index f6e971a86..8af36a415 100644 --- a/src/Orchard.Web/Modules/Orchard.Themes/Controllers/AdminController.cs +++ b/src/Orchard.Web/Modules/Orchard.Themes/Controllers/AdminController.cs @@ -66,80 +66,77 @@ namespace Orchard.Themes.Controllers { public ILogger Logger { get; set; } public ActionResult Index() { - try { - bool installThemes = _featureManager.GetEnabledFeatures().FirstOrDefault(f => f.Id == "PackagingServices") != null; + bool installThemes = _featureManager.GetEnabledFeatures().FirstOrDefault(f => f.Id == "PackagingServices") != null; - var featuresThatNeedUpdate = _dataMigrationManager.GetFeaturesThatNeedUpdate(); - ThemeEntry currentTheme = new ThemeEntry(_siteThemeService.GetSiteTheme()); - IEnumerable themes = _extensionManager.AvailableExtensions() - .Where(extensionDescriptor => { - bool hidden = false; - string tags = extensionDescriptor.Tags; - if (tags != null) { - hidden = tags.Split(',').Any(t => t.Trim().Equals("hidden", StringComparison.OrdinalIgnoreCase)); + var featuresThatNeedUpdate = _dataMigrationManager.GetFeaturesThatNeedUpdate(); + ThemeEntry currentTheme = new ThemeEntry(_siteThemeService.GetSiteTheme()); + IEnumerable themes = _extensionManager.AvailableExtensions() + .Where(extensionDescriptor => { + bool hidden = false; + string tags = extensionDescriptor.Tags; + if (tags != null) { + hidden = tags.Split(',').Any(t => t.Trim().Equals("hidden", StringComparison.OrdinalIgnoreCase)); + } + + return !hidden && + DefaultExtensionTypes.IsTheme(extensionDescriptor.ExtensionType) && + !currentTheme.Descriptor.Id.Equals(extensionDescriptor.Id); + }) + .Select(extensionDescriptor => { + ThemeEntry themeEntry = new ThemeEntry(extensionDescriptor) { + NeedsUpdate = featuresThatNeedUpdate.Contains(extensionDescriptor.Id), + IsRecentlyInstalled = _themeService.IsRecentlyInstalled(extensionDescriptor), + Enabled = _shellDescriptor.Features.Any(sf => sf.Name == extensionDescriptor.Id), + CanUninstall = installThemes + }; + + if (_extensionDisplayEventHandler != null) { + foreach (string notification in _extensionDisplayEventHandler.Displaying(themeEntry.Descriptor, ControllerContext.RequestContext)) + { + themeEntry.Notifications.Add(notification); } + } - return !hidden && - DefaultExtensionTypes.IsTheme(extensionDescriptor.ExtensionType) && - !currentTheme.Descriptor.Id.Equals(extensionDescriptor.Id); - }) - .Select(extensionDescriptor => { - ThemeEntry themeEntry = new ThemeEntry(extensionDescriptor) { - NeedsUpdate = featuresThatNeedUpdate.Contains(extensionDescriptor.Id), - IsRecentlyInstalled = _themeService.IsRecentlyInstalled(extensionDescriptor), - Enabled = _shellDescriptor.Features.Any(sf => sf.Name == extensionDescriptor.Id), - CanUninstall = installThemes - }; + return themeEntry; + }) + .ToArray(); - if (_extensionDisplayEventHandler != null) { - foreach (string notification in _extensionDisplayEventHandler.Displaying(themeEntry.Descriptor, ControllerContext.RequestContext)) - { - themeEntry.Notifications.Add(notification); - } - } - - return themeEntry; - }) - .ToArray(); - - return View(new ThemesIndexViewModel { - CurrentTheme = currentTheme, - InstallThemes = installThemes, - Themes = themes - }); - } catch (Exception exception) { - this.Error(exception, T("Listing themes failed: {0}", exception.Message), Logger, Services.Notifier); - - return View(new ThemesIndexViewModel()); - } + return View(new ThemesIndexViewModel { + CurrentTheme = currentTheme, + InstallThemes = installThemes, + Themes = themes + }); } [HttpPost, FormValueAbsent("submit.Apply"), FormValueAbsent("submit.Cancel")] public ActionResult Preview(string themeName, string returnUrl) { - try { - if (!Services.Authorizer.Authorize(Permissions.ApplyTheme, T("Couldn't preview the current theme"))) - return new HttpUnauthorizedResult(); + if (!Services.Authorizer.Authorize(Permissions.ApplyTheme, T("Couldn't preview the current theme"))) + return new HttpUnauthorizedResult(); + if (_extensionManager.AvailableExtensions() + .FirstOrDefault(extension => DefaultExtensionTypes.IsTheme(extension.ExtensionType) && extension.Name.Equals(themeName)) == null) { + + Services.Notifier.Error(T("Theme {0} was not found", themeName)); + } else { _themeService.EnableThemeFeatures(themeName); _previewTheme.SetPreviewTheme(themeName); - - return this.RedirectLocal(returnUrl, "~/"); - } catch (Exception exception) { - this.Error(exception, T("Previewing theme failed: {0}", exception.Message), Logger, Services.Notifier); - - return RedirectToAction("Index"); } + + return this.RedirectLocal(returnUrl, "~/"); } [HttpPost, ActionName("Preview"), FormValueRequired("submit.Apply")] public ActionResult ApplyPreview(string themeName, string returnUrl) { - try { - if (!Services.Authorizer.Authorize(Permissions.ApplyTheme, T("Couldn't preview the current theme"))) - return new HttpUnauthorizedResult(); + if (!Services.Authorizer.Authorize(Permissions.ApplyTheme, T("Couldn't preview the current theme"))) + return new HttpUnauthorizedResult(); + + if (_extensionManager.AvailableExtensions() + .FirstOrDefault(extension => DefaultExtensionTypes.IsTheme(extension.ExtensionType) && extension.Name.Equals(themeName)) == null) { + + Services.Notifier.Error(T("Theme {0} was not found", themeName)); + } else { _previewTheme.SetPreviewTheme(null); _siteThemeService.SetSiteTheme(themeName); - } catch (Exception exception) { - this.Error(exception, T("Previewing theme failed: {0}", exception.Message), Logger, Services.Notifier); } return RedirectToAction("Index"); @@ -147,53 +144,60 @@ namespace Orchard.Themes.Controllers { [HttpPost, ActionName("Preview"), FormValueRequired("submit.Cancel")] public ActionResult CancelPreview(string returnUrl) { - try { - if (!Services.Authorizer.Authorize(Permissions.ApplyTheme, T("Couldn't preview the current theme"))) - return new HttpUnauthorizedResult(); - _previewTheme.SetPreviewTheme(null); - } catch (Exception exception) { - this.Error(exception, T("Previewing theme failed: {0}", exception.Message), Logger, Services.Notifier); - } + if (!Services.Authorizer.Authorize(Permissions.ApplyTheme, T("Couldn't preview the current theme"))) + return new HttpUnauthorizedResult(); + + _previewTheme.SetPreviewTheme(null); + return RedirectToAction("Index"); } [HttpPost] public ActionResult Enable(string themeName) { - try { - if (!Services.Authorizer.Authorize(Permissions.ApplyTheme, T("Couldn't enable the theme"))) - return new HttpUnauthorizedResult(); + if (!Services.Authorizer.Authorize(Permissions.ApplyTheme, T("Couldn't enable the theme"))) + return new HttpUnauthorizedResult(); + if (_extensionManager.AvailableExtensions() + .FirstOrDefault(extension => DefaultExtensionTypes.IsTheme(extension.ExtensionType) && extension.Name.Equals(themeName)) == null) { + + Services.Notifier.Error(T("Theme {0} was not found", themeName)); + } else { _themeService.EnableThemeFeatures(themeName); - } catch (Exception exception) { - this.Error(exception, T("Enabling theme failed: {0}", exception.Message), Logger, Services.Notifier); } + return RedirectToAction("Index"); } [HttpPost] public ActionResult Disable(string themeName) { - try { - if (!Services.Authorizer.Authorize(Permissions.ApplyTheme, T("Couldn't disable the current theme"))) - return new HttpUnauthorizedResult(); + if (!Services.Authorizer.Authorize(Permissions.ApplyTheme, T("Couldn't disable the current theme"))) + return new HttpUnauthorizedResult(); + if (_extensionManager.AvailableExtensions() + .FirstOrDefault(extension => DefaultExtensionTypes.IsTheme(extension.ExtensionType) && extension.Name.Equals(themeName)) == null) { + + Services.Notifier.Error(T("Theme {0} was not found", themeName)); + } else { _themeService.DisableThemeFeatures(themeName); - } catch (Exception exception) { - this.Error(exception, T("Disabling theme failed: {0}", exception.Message), Logger, Services.Notifier); } + return RedirectToAction("Index"); } [HttpPost] public ActionResult Activate(string themeName) { - try { - if (!Services.Authorizer.Authorize(Permissions.ApplyTheme, T("Couldn't set the current theme"))) - return new HttpUnauthorizedResult(); + if (!Services.Authorizer.Authorize(Permissions.ApplyTheme, T("Couldn't set the current theme"))) + return new HttpUnauthorizedResult(); + if (_extensionManager.AvailableExtensions() + .FirstOrDefault(extension => DefaultExtensionTypes.IsTheme(extension.ExtensionType) && extension.Name.Equals(themeName)) == null) { + + Services.Notifier.Error(T("Theme {0} was not found", themeName)); + } else { _themeService.EnableThemeFeatures(themeName); _siteThemeService.SetSiteTheme(themeName); - } catch (Exception exception) { - this.Error(exception, T("Activating theme failed: {0}", exception.Message), Logger, Services.Notifier); } + return RedirectToAction("Index"); } diff --git a/src/Orchard.Web/Modules/Orchard.Themes/Models/ThemeEntry.cs b/src/Orchard.Web/Modules/Orchard.Themes/Models/ThemeEntry.cs index 07a942f77..9739d6181 100644 --- a/src/Orchard.Web/Modules/Orchard.Themes/Models/ThemeEntry.cs +++ b/src/Orchard.Web/Modules/Orchard.Themes/Models/ThemeEntry.cs @@ -9,7 +9,9 @@ namespace Orchard.Themes.Models { /// /// Default constructor. /// - public ThemeEntry() {} + public ThemeEntry() { + Notifications = new List(); + } /// /// Instantiates a theme based on an extension descriptor. From 0b4bf6276d5406ebe09a256a196a56d1be665a77 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 4 May 2011 17:48:37 -0700 Subject: [PATCH 008/139] PERF: Improve performance of DefaultVirtualPathMonitor Decrease constant cost of implementation: * Don't check for file/directory existence. It is much more expensive to systematically check for file existence through the VPP than catching the occasional exception. * Don't eagerly create ASP.NET cache entries when there are already existing entries. This is so if there are multiple calls for the same virtual path, we don't end up calling an expensive API when there is no need. --HG-- branch : 1.x extra : transplant_source : %C8%C3t%5C%B3%DA%C4%98%E8%13%23%D1B%91%AF2%B7%01%E7%CE --- .../VirtualPath/DefaultVirtualPathMonitor.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathMonitor.cs b/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathMonitor.cs index f4a20bac3..b5a5db7ef 100644 --- a/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathMonitor.cs +++ b/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathMonitor.cs @@ -8,6 +8,7 @@ using Orchard.Logging; using Orchard.Services; namespace Orchard.FileSystems.VirtualPath { + public class DefaultVirtualPathMonitor : IVirtualPathMonitor { private readonly Thunk _thunk; private readonly string _prefix = Guid.NewGuid().ToString("n"); @@ -23,26 +24,18 @@ namespace Orchard.FileSystems.VirtualPath { public ILogger Logger { get; set; } public IVolatileToken WhenPathChanges(string virtualPath) { + var token = BindToken(virtualPath); try { - var token = BindToken(virtualPath); - - if (!HostingEnvironment.VirtualPathProvider.DirectoryExists(virtualPath) - && !HostingEnvironment.VirtualPathProvider.FileExists(virtualPath)) { - // if trying to monitor a directory or file inside a directory which doesn't exist - // monitor first existing parent directory - return new Token(virtualPath); - } - BindSignal(virtualPath); - return token; } catch (HttpException e) { // This exception happens if trying to monitor a directory or file // inside a directory which doesn't exist - Logger.Warning(e, "Error monitor file changes on virtual path '{0}'", virtualPath); - // Fix this to monitor first existing parent directory. - return new Token(virtualPath); + Logger.Warning(e, "Error monitoring file changes on virtual path '{0}'", virtualPath); + + //TODO: Return a token monitoring first existing parent directory. } + return token; } private Token BindToken(string virtualPath) { @@ -81,13 +74,20 @@ namespace Orchard.FileSystems.VirtualPath { } private void BindSignal(string virtualPath, CacheItemRemovedCallback callback) { + string key = _prefix + virtualPath; + + //PERF: Don't add in the cache if already present. Creating a "CacheDependency" + // object (below) is actually quite expensive. + if (HostingEnvironment.Cache.Get(key) != null) + return; + var cacheDependency = HostingEnvironment.VirtualPathProvider.GetCacheDependency( virtualPath, new[] { virtualPath }, _clock.UtcNow); HostingEnvironment.Cache.Add( - _prefix + virtualPath, + key, virtualPath, cacheDependency, Cache.NoAbsoluteExpiration, From fe41a08e8eebf12454fb50afde75c798a46ca718 Mon Sep 17 00:00:00 2001 From: Andre Rodrigues Date: Sun, 8 May 2011 23:12:59 -0700 Subject: [PATCH 009/139] #17742: Making package installation work even if recipes module / feature is not enabled. --HG-- branch : 1.x --- .../PackagingServicesController.cs | 42 +++++++++++++------ .../InstallModuleDetails.cshtml | 2 +- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Controllers/PackagingServicesController.cs b/src/Orchard.Web/Modules/Orchard.Packaging/Controllers/PackagingServicesController.cs index a438f58ef..c02c09e80 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Controllers/PackagingServicesController.cs +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Controllers/PackagingServicesController.cs @@ -37,6 +37,17 @@ namespace Orchard.Packaging.Controllers { private readonly IRecipeHarvester _recipeHarvester; private readonly IRecipeManager _recipeManager; + public PackagingServicesController( + ShellSettings shellSettings, + IPackageManager packageManager, + IPackagingSourceManager packagingSourceManager, + INotifier notifier, + IAppDataFolderRoot appDataFolderRoot, + IOrchardServices services, + IModuleService moduleService) + : this(shellSettings, packageManager, packagingSourceManager, notifier, appDataFolderRoot, services, moduleService, null, null) { + } + public PackagingServicesController( ShellSettings shellSettings, IPackageManager packageManager, @@ -163,11 +174,14 @@ namespace Orchard.Packaging.Controllers { FeatureDescriptor = featureDescriptor }).ToList(); - List recipes = _recipeHarvester.HarvestRecipes(extensionDescriptor.Id) - .Select(recipe => new PackagingInstallRecipeViewModel { - Execute = false, // by default no recipes are executed - Recipe = recipe - }).ToList(); + List recipes = null; + if (_recipeHarvester != null) { + recipes = _recipeHarvester.HarvestRecipes(extensionDescriptor.Id) + .Select(recipe => new PackagingInstallRecipeViewModel { + Execute = false, // by default no recipes are executed + Recipe = recipe + }).ToList(); + } if (features.Count > 0) { return View("InstallModuleDetails", new PackagingInstallViewModel { @@ -187,15 +201,17 @@ namespace Orchard.Packaging.Controllers { return new HttpUnauthorizedResult(); try { - IEnumerable recipes = _recipeHarvester.HarvestRecipes(packagingInstallViewModel.ExtensionDescriptor.Id) - .Where(loadedRecipe => packagingInstallViewModel.Recipes.FirstOrDefault(recipeViewModel => recipeViewModel.Execute && recipeViewModel.Recipe.Name.Equals(loadedRecipe.Name)) != null); + if (_recipeHarvester != null && _recipeManager != null) { + IEnumerable recipes = _recipeHarvester.HarvestRecipes(packagingInstallViewModel.ExtensionDescriptor.Id) + .Where(loadedRecipe => packagingInstallViewModel.Recipes.FirstOrDefault(recipeViewModel => recipeViewModel.Execute && recipeViewModel.Recipe.Name.Equals(loadedRecipe.Name)) != null); - foreach (Recipe recipe in recipes) { - try { - _recipeManager.Execute(recipe); - } - catch { - Services.Notifier.Error(T("Recipes contains {0} unsuported module installation steps.", recipe.Name)); + foreach (Recipe recipe in recipes) { + try { + _recipeManager.Execute(recipe); + } + catch { + Services.Notifier.Error(T("Recipes contains {0} unsuported module installation steps.", recipe.Name)); + } } } diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Views/PackagingServices/InstallModuleDetails.cshtml b/src/Orchard.Web/Modules/Orchard.Packaging/Views/PackagingServices/InstallModuleDetails.cshtml index 361379f56..c55188e80 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Views/PackagingServices/InstallModuleDetails.cshtml +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Views/PackagingServices/InstallModuleDetails.cshtml @@ -13,7 +13,7 @@ @Html.HiddenFor(m => m.ExtensionDescriptor.Id) - if (Model.Recipes.Count > 0) { + if (Model.Recipes != null && Model.Recipes.Count > 0) {
@T("Pick the recipes you want to run").ToString() From d9f313e0ef333f170ea2662c5ce79cfea8c654e7 Mon Sep 17 00:00:00 2001 From: Andre Rodrigues Date: Sun, 8 May 2011 23:28:07 -0700 Subject: [PATCH 010/139] #17716: Specifying area in the action link. --HG-- branch : 1.x --- .../Orchard.Packaging/Events/ExtensionDisplayEventHandler.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Events/ExtensionDisplayEventHandler.cs b/src/Orchard.Web/Modules/Orchard.Packaging/Events/ExtensionDisplayEventHandler.cs index a619d76dc..7e41b372a 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Events/ExtensionDisplayEventHandler.cs +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Events/ExtensionDisplayEventHandler.cs @@ -45,7 +45,8 @@ namespace Orchard.Packaging.Events { urlHelper.Action(DefaultExtensionTypes.IsTheme(updatePackageEntry.ExtensionsDescriptor.ExtensionType) ? "ThemesUpdates" : "ModulesUpdates", - "GalleryUpdates")).ToString(); + "GalleryUpdates", + new { Area = "Orchard.Packaging" })).ToString(); } } } From b059bdfa6efdf43e506e5d5b64e9efb7d6985467 Mon Sep 17 00:00:00 2001 From: Nathan Heskew Date: Mon, 9 May 2011 10:39:51 -0700 Subject: [PATCH 011/139] Grabbing the latest TinyMCE (3.4.2) in order to get a bugfix for adding script tags in IE9 work item: 17783 --HG-- branch : 1.x --- .../Modules/TinyMce/Scripts/langs/en.js | 79 +- .../plugins/autoresize/editor_plugin.js | 2 +- .../plugins/autoresize/editor_plugin_src.js | 232 +- .../plugins/fullscreen/editor_plugin.js | 2 +- .../plugins/fullscreen/editor_plugin_src.js | 12 +- .../plugins/searchreplace/editor_plugin.js | 2 +- .../searchreplace/editor_plugin_src.js | 4 + .../plugins/searchreplace/js/searchreplace.js | 24 +- .../plugins/searchreplace/searchreplace.htm | 33 +- .../TinyMce/Scripts/themes/advanced/about.htm | 8 +- .../Scripts/themes/advanced/anchor.htm | 10 +- .../Scripts/themes/advanced/charmap.htm | 79 +- .../Scripts/themes/advanced/color_picker.htm | 19 +- .../themes/advanced/editor_template.js | 2 +- .../themes/advanced/editor_template_src.js | 213 +- .../TinyMce/Scripts/themes/advanced/image.htm | 102 +- .../Scripts/themes/advanced/js/about.js | 1 + .../Scripts/themes/advanced/js/anchor.js | 5 + .../Scripts/themes/advanced/js/charmap.js | 32 +- .../themes/advanced/js/color_picker.js | 140 +- .../Scripts/themes/advanced/js/image.js | 4 +- .../Scripts/themes/advanced/js/link.js | 11 +- .../themes/advanced/js/source_editor.js | 2 +- .../Scripts/themes/advanced/langs/en.js | 10 +- .../Scripts/themes/advanced/langs/en_dlg.js | 9 +- .../TinyMce/Scripts/themes/advanced/link.htm | 55 +- .../themes/advanced/skins/default/content.css | 11 + .../themes/advanced/skins/default/ui.css | 8 +- .../themes/advanced/skins/o2k7/content.css | 10 + .../themes/advanced/skins/o2k7/dialog.css | 1 + .../Scripts/themes/advanced/skins/o2k7/ui.css | 9 +- .../themes/advanced/skins/o2k7/ui_black.css | 2 +- .../themes/advanced/skins/o2k7/ui_silver.css | 2 +- .../Scripts/themes/advanced/source_editor.htm | 6 +- .../Scripts/themes/simple/editor_template.js | 2 +- .../themes/simple/editor_template_src.js | 3 +- .../Modules/TinyMce/Scripts/tiny_mce.js | 2 +- .../Modules/TinyMce/Scripts/tiny_mce_popup.js | 2 +- .../Modules/TinyMce/Scripts/tiny_mce_src.js | 6256 +++++++++++------ .../TinyMce/Scripts/utils/form_utils.js | 18 +- .../Modules/TinyMce/Scripts/utils/mctabs.js | 105 +- .../Modules/TinyMce/Scripts/utils/validate.js | 38 +- 42 files changed, 4817 insertions(+), 2750 deletions(-) diff --git a/src/Orchard.Web/Modules/TinyMce/Scripts/langs/en.js b/src/Orchard.Web/Modules/TinyMce/Scripts/langs/en.js index 8519b4de7..8a80d46b1 100644 --- a/src/Orchard.Web/Modules/TinyMce/Scripts/langs/en.js +++ b/src/Orchard.Web/Modules/TinyMce/Scripts/langs/en.js @@ -12,9 +12,54 @@ not_set:"-- Not set --", clipboard_msg:"Copy/Cut/Paste is not available in Mozilla and Firefox.\nDo you want more information about this issue?", clipboard_no_support:"Currently not supported by your browser, use keyboard shortcuts instead.", popup_blocked:"Sorry, but we have noticed that your popup-blocker has disabled a window that provides application functionality. You will need to disable popup blocking on this site in order to fully utilize this tool.", -invalid_data:"Error: Invalid values entered, these are marked in red.", +invalid_data:"{#field} is invalid", +invalid_data_number:"{#field} must be a number", +invalid_data_min:"{#field} must be a number greater than {#min}", +invalid_data_size:"{#field} must be a number or percentage", more_colors:"More colors" }, +colors:{ +'000000':'Black', +'993300':'Burnt orange', +'333300':'Dark olive', +'003300':'Dark green', +'003366':'Dark azure', +'000080':'Navy Blue', +'333399':'Indigo', +'333333':'Very dark gray', +'800000':'Maroon', +'FF6600':'Orange', +'808000':'Olive', +'008000':'Green', +'008080':'Teal', +'0000FF':'Blue', +'666699':'Grayish blue', +'808080':'Gray', +'FF0000':'Red', +'FF9900':'Amber', +'99CC00':'Yellow green', +'339966':'Sea green', +'33CCCC':'Turquoise', +'3366FF':'Royal blue', +'800080':'Purple', +'999999':'Medium gray', +'FF00FF':'Magenta', +'FFCC00':'Gold', +'FFFF00':'Yellow', +'00FF00':'Lime', +'00FFFF':'Aqua', +'00CCFF':'Sky blue', +'993366':'Brown', +'C0C0C0':'Silver', +'FF99CC':'Pink', +'FFCC99':'Peach', +'FFFF99':'Light yellow', +'CCFFCC':'Pale green', +'CCFFFF':'Pale cyan', +'99CCFF':'Light sky blue', +'CC99FF':'Plum', +'FFFFFF':'White' +}, contextmenu:{ align:"Alignment", left:"Left", @@ -90,7 +135,9 @@ desc:"Edit CSS Style" paste:{ paste_text_desc:"Paste as Plain Text", paste_word_desc:"Paste from Word", -selectall_desc:"Select All" +selectall_desc:"Select All", +plaintext_mode_sticky:"Paste is now in plain text mode. Click again to toggle back to regular paste mode. After you paste something you will be returned to regular paste mode.", +plaintext_mode:"Paste is now in plain text mode. Click again to toggle back to regular paste mode." }, paste_dlg:{ text_title:"Use CTRL+V on your keyboard to paste the text into the window.", @@ -120,7 +167,9 @@ col:"Column", cell:"Cell" }, autosave:{ -unload_msg:"The changes you made will be lost if you navigate away from this page." +unload_msg:"The changes you made will be lost if you navigate away from this page.", +restore_content:"Restore auto-saved content.", +warning_message:"If you restore the saved content, you will lose all the content that is currently in the editor.\n\nAre you sure you want to restore the saved content?." }, fullscreen:{ desc:"Toggle fullscreen mode" @@ -147,8 +196,28 @@ langs:"Languages", wait:"Please wait...", sug:"Suggestions", no_sug:"No suggestions", -no_mpell:"No misspellings found." +no_mpell:"No misspellings found.", +learn_word:"Learn word" }, pagebreak:{ desc:"Insert page break." -}}}); \ No newline at end of file +}, +advlist:{ +types:"Types", +def:"Default", +lower_alpha:"Lower alpha", +lower_greek:"Lower greek", +lower_roman:"Lower roman", +upper_alpha:"Upper alpha", +upper_roman:"Upper roman", +circle:"Circle", +disc:"Disc", +square:"Square" +}, +aria:{ +rich_text_area:"Rich Text Area" +}, +wordcount:{ +words: 'Words: ' +} +}}); \ No newline at end of file diff --git a/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/autoresize/editor_plugin.js b/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/autoresize/editor_plugin.js index 08bd75ffe..10687a921 100644 --- a/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/autoresize/editor_plugin.js +++ b/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/autoresize/editor_plugin.js @@ -1 +1 @@ -(function(){tinymce.create("tinymce.plugins.AutoResizePlugin",{init:function(a){var b=true,d=this;if(a.getParam("fullscreen_is_enabled"))return;function c(){var i=a.getDoc(),h=i.body,g=i.documentElement,f=tinymce.DOM,e=d.autoresize_min_height,c;if((tinymce.isIE?h.scrollWidth:g.offsetWidth)==0)return;c=tinymce.isIE?h.scrollHeight:g.offsetHeight;if(c>d.autoresize_min_height)e=c;f.setStyle(f.get(a.id+"_ifr"),"height",e+"px");if(d.throbbing){a.setProgressState(false);a.setProgressState(b)}}d.editor=a;d.autoresize_min_height=a.getElement().offsetHeight;a.onChange.add(c);a.onSetContent.add(c);a.onPaste.add(c);a.onKeyUp.add(c);a.onPostRender.add(c);if(a.getParam("autoresize_on_init",b)){a.onInit.add(function(a){a.setProgressState(b);d.throbbing=b;a.getBody().style.overflowY="hidden"});a.onLoadContent.add(function(a){c();setTimeout(function(){c();a.setProgressState(false);d.throbbing=false},1250)})}a.addCommand("mceAutoResize",c)},getInfo:function(){return{longname:"Auto Resize",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autoresize",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("autoresize",tinymce.plugins.AutoResizePlugin)})() \ No newline at end of file +(function(){tinymce.create("tinymce.plugins.AutoResizePlugin",{init:function(a,c){var d=this,e=0;if(a.getParam("fullscreen_is_enabled")){return}function b(){var i=a.getDoc(),f=i.body,k=i.documentElement,h=tinymce.DOM,j=d.autoresize_min_height,g;g=tinymce.isIE?f.scrollHeight:k.offsetHeight;g=d.bottom_margin+g;if(g>d.autoresize_min_height){j=g}if(j!==e){h.setStyle(h.get(a.id+"_ifr"),"height",j+"px");e=j}if(d.throbbing){a.setProgressState(false);a.setProgressState(true)}}d.editor=a;d.autoresize_min_height=a.getElement().offsetHeight;d.bottom_margin=parseInt(a.getParam("autoresize_bottom_margin",50));a.onChange.add(b);a.onSetContent.add(b);a.onPaste.add(b);a.onKeyUp.add(b);a.onPostRender.add(b);if(a.getParam("autoresize_on_init",true)){a.onInit.add(function(g,f){g.setProgressState(true);d.throbbing=true;g.getBody().style.overflowY="hidden"});a.onLoadContent.add(function(g,f){b();setTimeout(function(){b();g.setProgressState(false);d.throbbing=false},1250)})}a.addCommand("mceAutoResize",b)},getInfo:function(){return{longname:"Auto Resize",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autoresize",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("autoresize",tinymce.plugins.AutoResizePlugin)})(); \ No newline at end of file diff --git a/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/autoresize/editor_plugin_src.js b/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/autoresize/editor_plugin_src.js index abe5835ba..50af21aac 100644 --- a/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/autoresize/editor_plugin_src.js +++ b/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/autoresize/editor_plugin_src.js @@ -8,117 +8,121 @@ * Contributing: http://tinymce.moxiecode.com/contributing */ -(function () { - /** - * Auto Resize - * - * This plugin automatically resizes the content area to fit its content height. - * It will retain a minimum height, which is the height of the content area when - * it's initialized. - */ - tinymce.create('tinymce.plugins.AutoResizePlugin', { - /** - * Initializes the plugin, this will be executed after the plugin has been created. - * This call is done before the editor instance has finished it's initialization so use the onInit event - * of the editor instance to intercept that event. - * - * @param {tinymce.Editor} ed Editor instance that the plugin is initialized in. - * @param {string} url Absolute URL to where the plugin is located. - */ - init: function (ed, url) { - var t = this; - - if (ed.getParam('fullscreen_is_enabled')) - return; - - /** - * This method gets executed each time the editor needs to resize. - */ - function resize() { - var d = ed.getDoc(), b = d.body, de = d.documentElement, DOM = tinymce.DOM, resizeHeight = t.autoresize_min_height, myHeight; - - // Don't resize if we have no width - a fix for http://orchard.codeplex.com/workitem/16853 - if ((tinymce.isIE ? b.scrollWidth : de.offsetWidth) == 0) { - return; - } - - // Get height differently depending on the browser used - myHeight = tinymce.isIE ? b.scrollHeight : de.offsetHeight; - - // Don't make it smaller than the minimum height - if (myHeight > t.autoresize_min_height) - resizeHeight = myHeight; - - // Resize content element - DOM.setStyle(DOM.get(ed.id + '_ifr'), 'height', resizeHeight + 'px'); - - // if we're throbbing, we'll re-throb to match the new size - if (t.throbbing) { - ed.setProgressState(false); - ed.setProgressState(true); - } - }; - - t.editor = ed; - - // Define minimum height - t.autoresize_min_height = ed.getElement().offsetHeight; - - // Add appropriate listeners for resizing content area - ed.onChange.add(resize); - ed.onSetContent.add(resize); - ed.onPaste.add(resize); - ed.onKeyUp.add(resize); - ed.onPostRender.add(resize); - - if (ed.getParam('autoresize_on_init', true)) { - // Things to do when the editor is ready - ed.onInit.add(function (ed, l) { - // Show throbber until content area is resized properly - ed.setProgressState(true); - t.throbbing = true; - - // Hide scrollbars - ed.getBody().style.overflowY = "hidden"; - }); - - ed.onLoadContent.add(function (ed, l) { - resize(); - - // Because the content area resizes when its content CSS loads, - // and we can't easily add a listener to its onload event, - // we'll just trigger a resize after a short loading period - setTimeout(function () { - resize(); - - // Disable throbber - ed.setProgressState(false); - t.throbbing = false; - }, 1250); - }); - } - - // Register the command so that it can be invoked by using tinyMCE.activeEditor.execCommand('mceExample'); - ed.addCommand('mceAutoResize', resize); - }, - - /** - * Returns information about the plugin as a name/value array. - * The current keys are longname, author, authorurl, infourl and version. - * - * @return {Object} Name/value array containing information about the plugin. - */ - getInfo: function () { - return { - longname: 'Auto Resize', - author: 'Moxiecode Systems AB', - authorurl: 'http://tinymce.moxiecode.com', - infourl: 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autoresize', - version: tinymce.majorVersion + "." + tinymce.minorVersion - }; - } - }); - - // Register plugin - tinymce.PluginManager.add('autoresize', tinymce.plugins.AutoResizePlugin); -})(); \ No newline at end of file +(function() { + /** + * Auto Resize + * + * This plugin automatically resizes the content area to fit its content height. + * It will retain a minimum height, which is the height of the content area when + * it's initialized. + */ + tinymce.create('tinymce.plugins.AutoResizePlugin', { + /** + * Initializes the plugin, this will be executed after the plugin has been created. + * This call is done before the editor instance has finished it's initialization so use the onInit event + * of the editor instance to intercept that event. + * + * @param {tinymce.Editor} ed Editor instance that the plugin is initialized in. + * @param {string} url Absolute URL to where the plugin is located. + */ + init : function(ed, url) { + var t = this, oldSize = 0; + + if (ed.getParam('fullscreen_is_enabled')) + return; + + /** + * This method gets executed each time the editor needs to resize. + */ + function resize() { + var d = ed.getDoc(), b = d.body, de = d.documentElement, DOM = tinymce.DOM, resizeHeight = t.autoresize_min_height, myHeight; + + // Get height differently depending on the browser used + myHeight = tinymce.isIE ? b.scrollHeight : de.offsetHeight; + + // Bottom margin + myHeight = t.bottom_margin + myHeight; + + // Don't make it smaller than the minimum height + if (myHeight > t.autoresize_min_height) + resizeHeight = myHeight; + + // Resize content element + if ( resizeHeight !== oldSize ) { + DOM.setStyle(DOM.get(ed.id + '_ifr'), 'height', resizeHeight + 'px'); + oldSize = resizeHeight; + } + + // if we're throbbing, we'll re-throb to match the new size + if (t.throbbing) { + ed.setProgressState(false); + ed.setProgressState(true); + } + }; + + t.editor = ed; + + // Define minimum height + t.autoresize_min_height = ed.getElement().offsetHeight; + + // Add margin at the bottom for better UX + t.bottom_margin = parseInt( ed.getParam('autoresize_bottom_margin', 50) ); + + // Add appropriate listeners for resizing content area + ed.onChange.add(resize); + ed.onSetContent.add(resize); + ed.onPaste.add(resize); + ed.onKeyUp.add(resize); + ed.onPostRender.add(resize); + + if (ed.getParam('autoresize_on_init', true)) { + // Things to do when the editor is ready + ed.onInit.add(function(ed, l) { + // Show throbber until content area is resized properly + ed.setProgressState(true); + t.throbbing = true; + + // Hide scrollbars + ed.getBody().style.overflowY = "hidden"; + }); + + ed.onLoadContent.add(function(ed, l) { + resize(); + + // Because the content area resizes when its content CSS loads, + // and we can't easily add a listener to its onload event, + // we'll just trigger a resize after a short loading period + setTimeout(function() { + resize(); + + // Disable throbber + ed.setProgressState(false); + t.throbbing = false; + }, 1250); + }); + } + + // Register the command so that it can be invoked by using tinyMCE.activeEditor.execCommand('mceExample'); + ed.addCommand('mceAutoResize', resize); + }, + + /** + * Returns information about the plugin as a name/value array. + * The current keys are longname, author, authorurl, infourl and version. + * + * @return {Object} Name/value array containing information about the plugin. + */ + getInfo : function() { + return { + longname : 'Auto Resize', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autoresize', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('autoresize', tinymce.plugins.AutoResizePlugin); +})(); diff --git a/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/fullscreen/editor_plugin.js b/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/fullscreen/editor_plugin.js index 4437bd266..6eae3ec84 100644 --- a/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/fullscreen/editor_plugin.js +++ b/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/fullscreen/editor_plugin.js @@ -1 +1 @@ -(function(){var a=tinymce.DOM;tinymce.create("tinymce.plugins.FullScreenPlugin",{init:function(c,d){var e=this,f={},b;e.editor=c;c.addCommand("mceFullScreen",function(){var h,i=a.doc.documentElement;if(c.getParam("fullscreen_is_enabled")){if(c.getParam("fullscreen_new_window")){closeFullscreen()}else{a.win.setTimeout(function(){tinymce.dom.Event.remove(a.win,"resize",e.resizeFunc);tinyMCE.get(c.getParam("fullscreen_editor_id")).setContent(c.getContent({format:"raw"}),{format:"raw"});tinyMCE.remove(c);a.remove("mce_fullscreen_container");i.style.overflow=c.getParam("fullscreen_html_overflow");a.setStyle(a.doc.body,"overflow",c.getParam("fullscreen_overflow"));a.win.scrollTo(c.getParam("fullscreen_scrollx"),c.getParam("fullscreen_scrolly"));tinyMCE.settings=tinyMCE.oldSettings},10)}return}if(c.getParam("fullscreen_new_window")){h=a.win.open(d+"/fullscreen.htm","mceFullScreenPopup","fullscreen=yes,menubar=no,toolbar=no,scrollbars=no,resizable=yes,left=0,top=0,width="+screen.availWidth+",height="+screen.availHeight);try{h.resizeTo(screen.availWidth,screen.availHeight)}catch(g){}}else{tinyMCE.oldSettings=tinyMCE.settings;f.fullscreen_overflow=a.getStyle(a.doc.body,"overflow",1)||"auto";f.fullscreen_html_overflow=a.getStyle(i,"overflow",1);b=a.getViewPort();f.fullscreen_scrollx=b.x;f.fullscreen_scrolly=b.y;if(tinymce.isOpera&&f.fullscreen_overflow=="visible"){f.fullscreen_overflow="auto"}if(tinymce.isIE&&f.fullscreen_overflow=="scroll"){f.fullscreen_overflow="auto"}if(tinymce.isIE&&(f.fullscreen_html_overflow=="visible"||f.fullscreen_html_overflow=="scroll")){f.fullscreen_html_overflow="auto"}if(f.fullscreen_overflow=="0px"){f.fullscreen_overflow=""}a.setStyle(a.doc.body,"overflow","hidden");i.style.overflow="hidden";b=a.getViewPort();a.win.scrollTo(0,0);if(tinymce.isIE){b.h-=1}n=a.add(a.doc.body,"div",{id:"mce_fullscreen_container",style:"position:"+(tinymce.isIE6||(tinymce.isIE&&!a.boxModel)?"absolute":"fixed")+";top:0;left:0;width:"+b.w+"px;height:"+b.h+"px;z-index:200000;"});a.add(n,"div",{id:"mce_fullscreen"});tinymce.each(c.settings,function(j,k){f[k]=j});f.id="mce_fullscreen";f.width=n.clientWidth;f.height=n.clientHeight-15;f.fullscreen_is_enabled=true;f.fullscreen_editor_id=c.id;f.theme_advanced_resizing=false;f.save_onsavecallback=function(){c.setContent(tinyMCE.get(f.id).getContent({format:"raw"}),{format:"raw"});c.execCommand("mceSave")};tinymce.each(c.getParam("fullscreen_settings"),function(l,j){f[j]=l});if(f.theme_advanced_toolbar_location==="external"){f.theme_advanced_toolbar_location="top"}e.fullscreenEditor=new tinymce.Editor("mce_fullscreen",f);e.fullscreenEditor.onInit.add(function(){e.fullscreenEditor.setContent(c.getContent());e.fullscreenEditor.focus()});e.fullscreenEditor.render();e.fullscreenElement=new tinymce.dom.Element("mce_fullscreen_container");e.fullscreenElement.update();e.resizeFunc=tinymce.dom.Event.add(a.win,"resize",function(){var m=tinymce.DOM.getViewPort(),k=e.fullscreenEditor,j,l;j=k.dom.getSize(k.getContainer().firstChild);l=k.dom.getSize(k.getContainer().getElementsByTagName("iframe")[0]);k.theme.resizeTo(m.w-j.w+l.w,m.h-j.h+l.h)})}});c.addButton("fullscreen",{title:"fullscreen.desc",cmd:"mceFullScreen"});c.onNodeChange.add(function(h,g){g.setActive("fullscreen",h.getParam("fullscreen_is_enabled"))})},getInfo:function(){return{longname:"Fullscreen",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/fullscreen",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("fullscreen",tinymce.plugins.FullScreenPlugin)})(); \ No newline at end of file +(function(){var a=tinymce.DOM;tinymce.create("tinymce.plugins.FullScreenPlugin",{init:function(d,e){var f=this,g={},c,b;f.editor=d;d.addCommand("mceFullScreen",function(){var i,j=a.doc.documentElement;if(d.getParam("fullscreen_is_enabled")){if(d.getParam("fullscreen_new_window")){closeFullscreen()}else{a.win.setTimeout(function(){tinymce.dom.Event.remove(a.win,"resize",f.resizeFunc);tinyMCE.get(d.getParam("fullscreen_editor_id")).setContent(d.getContent({format:"raw"}),{format:"raw"});tinyMCE.remove(d);a.remove("mce_fullscreen_container");j.style.overflow=d.getParam("fullscreen_html_overflow");a.setStyle(a.doc.body,"overflow",d.getParam("fullscreen_overflow"));a.win.scrollTo(d.getParam("fullscreen_scrollx"),d.getParam("fullscreen_scrolly"));tinyMCE.settings=tinyMCE.oldSettings},10)}return}if(d.getParam("fullscreen_new_window")){i=a.win.open(e+"/fullscreen.htm","mceFullScreenPopup","fullscreen=yes,menubar=no,toolbar=no,scrollbars=no,resizable=yes,left=0,top=0,width="+screen.availWidth+",height="+screen.availHeight);try{i.resizeTo(screen.availWidth,screen.availHeight)}catch(h){}}else{tinyMCE.oldSettings=tinyMCE.settings;g.fullscreen_overflow=a.getStyle(a.doc.body,"overflow",1)||"auto";g.fullscreen_html_overflow=a.getStyle(j,"overflow",1);c=a.getViewPort();g.fullscreen_scrollx=c.x;g.fullscreen_scrolly=c.y;if(tinymce.isOpera&&g.fullscreen_overflow=="visible"){g.fullscreen_overflow="auto"}if(tinymce.isIE&&g.fullscreen_overflow=="scroll"){g.fullscreen_overflow="auto"}if(tinymce.isIE&&(g.fullscreen_html_overflow=="visible"||g.fullscreen_html_overflow=="scroll")){g.fullscreen_html_overflow="auto"}if(g.fullscreen_overflow=="0px"){g.fullscreen_overflow=""}a.setStyle(a.doc.body,"overflow","hidden");j.style.overflow="hidden";c=a.getViewPort();a.win.scrollTo(0,0);if(tinymce.isIE){c.h-=1}if(tinymce.isIE6){b="absolute;top:"+c.y}else{b="fixed;top:0"}n=a.add(a.doc.body,"div",{id:"mce_fullscreen_container",style:"position:"+b+";left:0;width:"+c.w+"px;height:"+c.h+"px;z-index:200000;"});a.add(n,"div",{id:"mce_fullscreen"});tinymce.each(d.settings,function(k,l){g[l]=k});g.id="mce_fullscreen";g.width=n.clientWidth;g.height=n.clientHeight-15;g.fullscreen_is_enabled=true;g.fullscreen_editor_id=d.id;g.theme_advanced_resizing=false;g.save_onsavecallback=function(){d.setContent(tinyMCE.get(g.id).getContent({format:"raw"}),{format:"raw"});d.execCommand("mceSave")};tinymce.each(d.getParam("fullscreen_settings"),function(m,l){g[l]=m});if(g.theme_advanced_toolbar_location==="external"){g.theme_advanced_toolbar_location="top"}f.fullscreenEditor=new tinymce.Editor("mce_fullscreen",g);f.fullscreenEditor.onInit.add(function(){f.fullscreenEditor.setContent(d.getContent());f.fullscreenEditor.focus()});f.fullscreenEditor.render();f.fullscreenElement=new tinymce.dom.Element("mce_fullscreen_container");f.fullscreenElement.update();f.resizeFunc=tinymce.dom.Event.add(a.win,"resize",function(){var o=tinymce.DOM.getViewPort(),l=f.fullscreenEditor,k,m;k=l.dom.getSize(l.getContainer().firstChild);m=l.dom.getSize(l.getContainer().getElementsByTagName("iframe")[0]);l.theme.resizeTo(o.w-k.w+m.w,o.h-k.h+m.h)})}});d.addButton("fullscreen",{title:"fullscreen.desc",cmd:"mceFullScreen"});d.onNodeChange.add(function(i,h){h.setActive("fullscreen",i.getParam("fullscreen_is_enabled"))})},getInfo:function(){return{longname:"Fullscreen",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/fullscreen",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("fullscreen",tinymce.plugins.FullScreenPlugin)})(); \ No newline at end of file diff --git a/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/fullscreen/editor_plugin_src.js b/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/fullscreen/editor_plugin_src.js index 0efd9bbe5..3477c86c9 100644 --- a/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/fullscreen/editor_plugin_src.js +++ b/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/fullscreen/editor_plugin_src.js @@ -13,7 +13,7 @@ tinymce.create('tinymce.plugins.FullScreenPlugin', { init : function(ed, url) { - var t = this, s = {}, vp; + var t = this, s = {}, vp, posCss; t.editor = ed; @@ -78,7 +78,15 @@ if (tinymce.isIE) vp.h -= 1; - n = DOM.add(DOM.doc.body, 'div', {id : 'mce_fullscreen_container', style : 'position:' + (tinymce.isIE6 || (tinymce.isIE && !DOM.boxModel) ? 'absolute' : 'fixed') + ';top:0;left:0;width:' + vp.w + 'px;height:' + vp.h + 'px;z-index:200000;'}); + // Use fixed position if it exists + if (tinymce.isIE6) + posCss = 'absolute;top:' + vp.y; + else + posCss = 'fixed;top:0'; + + n = DOM.add(DOM.doc.body, 'div', { + id : 'mce_fullscreen_container', + style : 'position:' + posCss + ';left:0;width:' + vp.w + 'px;height:' + vp.h + 'px;z-index:200000;'}); DOM.add(n, 'div', {id : 'mce_fullscreen'}); tinymce.each(ed.settings, function(v, n) { diff --git a/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/searchreplace/editor_plugin.js b/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/searchreplace/editor_plugin.js index cd9c985b7..165bc12df 100644 --- a/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/searchreplace/editor_plugin.js +++ b/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/searchreplace/editor_plugin.js @@ -1 +1 @@ -(function(){tinymce.create("tinymce.plugins.SearchReplacePlugin",{init:function(a,c){function b(d){a.windowManager.open({file:c+"/searchreplace.htm",width:420+parseInt(a.getLang("searchreplace.delta_width",0)),height:170+parseInt(a.getLang("searchreplace.delta_height",0)),inline:1,auto_focus:0},{mode:d,search_string:a.selection.getContent({format:"text"}),plugin_url:c})}a.addCommand("mceSearch",function(){b("search")});a.addCommand("mceReplace",function(){b("replace")});a.addButton("search",{title:"searchreplace.search_desc",cmd:"mceSearch"});a.addButton("replace",{title:"searchreplace.replace_desc",cmd:"mceReplace"});a.addShortcut("ctrl+f","searchreplace.search_desc","mceSearch")},getInfo:function(){return{longname:"Search/Replace",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/searchreplace",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("searchreplace",tinymce.plugins.SearchReplacePlugin)})(); \ No newline at end of file +(function(){tinymce.create("tinymce.plugins.SearchReplacePlugin",{init:function(a,c){function b(d){window.focus();a.windowManager.open({file:c+"/searchreplace.htm",width:420+parseInt(a.getLang("searchreplace.delta_width",0)),height:170+parseInt(a.getLang("searchreplace.delta_height",0)),inline:1,auto_focus:0},{mode:d,search_string:a.selection.getContent({format:"text"}),plugin_url:c})}a.addCommand("mceSearch",function(){b("search")});a.addCommand("mceReplace",function(){b("replace")});a.addButton("search",{title:"searchreplace.search_desc",cmd:"mceSearch"});a.addButton("replace",{title:"searchreplace.replace_desc",cmd:"mceReplace"});a.addShortcut("ctrl+f","searchreplace.search_desc","mceSearch")},getInfo:function(){return{longname:"Search/Replace",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/searchreplace",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("searchreplace",tinymce.plugins.SearchReplacePlugin)})(); \ No newline at end of file diff --git a/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/searchreplace/editor_plugin_src.js b/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/searchreplace/editor_plugin_src.js index 1433a06a4..4c87e8fa7 100644 --- a/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/searchreplace/editor_plugin_src.js +++ b/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/searchreplace/editor_plugin_src.js @@ -12,6 +12,10 @@ tinymce.create('tinymce.plugins.SearchReplacePlugin', { init : function(ed, url) { function open(m) { + // Keep IE from writing out the f/r character to the editor + // instance while initializing a new dialog. See: #3131190 + window.focus(); + ed.windowManager.open({ file : url + '/searchreplace.htm', width : 420 + parseInt(ed.getLang('searchreplace.delta_width', 0)), diff --git a/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/searchreplace/js/searchreplace.js b/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/searchreplace/js/searchreplace.js index 0137ba0fd..80284b9f3 100644 --- a/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/searchreplace/js/searchreplace.js +++ b/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/searchreplace/js/searchreplace.js @@ -2,14 +2,18 @@ tinyMCEPopup.requireLangPack(); var SearchReplaceDialog = { init : function(ed) { - var f = document.forms[0], m = tinyMCEPopup.getWindowArg("mode"); + var t = this, f = document.forms[0], m = tinyMCEPopup.getWindowArg("mode"); - this.switchMode(m); + t.switchMode(m); f[m + '_panel_searchstring'].value = tinyMCEPopup.getWindowArg("search_string"); // Focus input field f[m + '_panel_searchstring'].focus(); + + mcTabs.onChange.add(function(tab_id, panel_id) { + t.switchMode(tab_id.substring(0, tab_id.indexOf('_'))); + }); }, switchMode : function(m) { @@ -51,16 +55,14 @@ var SearchReplaceDialog = { function fix() { // Correct Firefox graphics glitches + // TODO: Verify if this is actually needed any more, maybe it was for very old FF versions? r = se.getRng().cloneRange(); ed.getDoc().execCommand('SelectAll', false, null); se.setRng(r); }; function replace() { - if (tinymce.isIE) - ed.selection.getRng().duplicate().pasteHTML(rs); // Needs to be duplicated due to selection bug in IE - else - ed.getDoc().execCommand('InsertHTML', false, rs); + ed.selection.setContent(rs); // Needs to be duplicated due to selection bug in IE }; // IE flags @@ -74,6 +76,9 @@ var SearchReplaceDialog = { ed.selection.collapse(true); if (tinymce.isIE) { + ed.focus(); + r = ed.getDoc().selection.createRange(); + while (r.findText(s, b ? -1 : 1, fl)) { r.scrollIntoView(); r.select(); @@ -110,15 +115,14 @@ var SearchReplaceDialog = { se.collapse(b); r = se.getRng(); - if (tinymce.isIE) { - r = ed.getDoc().selection.createRange(); - } - // Whats the point if (!s) return; if (tinymce.isIE) { + ed.focus(); + r = ed.getDoc().selection.createRange(); + if (r.findText(s, b ? -1 : 1, fl)) { r.scrollIntoView(); r.select(); diff --git a/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/searchreplace/searchreplace.htm b/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/searchreplace/searchreplace.htm index d0424cfc9..5a22d8aa4 100644 --- a/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/searchreplace/searchreplace.htm +++ b/src/Orchard.Web/Modules/TinyMce/Scripts/plugins/searchreplace/searchreplace.htm @@ -8,27 +8,28 @@ - + +
- +
- +
- - - +
+ + @@ -39,7 +40,7 @@ "}else{e+=""}if(f&&j.ListBox){if(f.Button||f.SplitButton){e+=b.createHTML("td",{"class":"mceToolbarStart"},b.createHTML("span",null,""))}}}g="mceToolbarEnd";if(j.Button){g+=" mceToolbarEndButton"}else{if(j.SplitButton){g+=" mceToolbarEndSplitButton"}else{if(j.ListBox){g+=" mceToolbarEndListBox"}}}e+=b.createHTML("td",{"class":g},b.createHTML("span",null,""));return b.createHTML("table",{id:l.id,"class":"mceToolbar"+(m["class"]?" "+m["class"]:""),cellpadding:"0",cellspacing:"0",align:l.settings.align||""},""+e+"")}});(function(b){var a=b.util.Dispatcher,c=b.each;b.create("tinymce.AddOnManager",{AddOnManager:function(){var d=this;d.items=[];d.urls={};d.lookup={};d.onAdd=new a(d)},get:function(d){return this.lookup[d]},requireLangPack:function(e){var d=b.settings;if(d&&d.language){b.ScriptLoader.add(this.urls[e]+"/langs/"+d.language+".js")}},add:function(e,d){this.items.push(d);this.lookup[e]=d;this.onAdd.dispatch(this,e,d);return d},load:function(h,e,d,g){var f=this;if(f.urls[h]){return}if(e.indexOf("/")!=0&&e.indexOf("://")==-1){e=b.baseURL+"/"+e}f.urls[h]=e.substring(0,e.lastIndexOf("/"));if(!f.lookup[h]){b.ScriptLoader.add(e,d,g)}}});b.PluginManager=new b.AddOnManager();b.ThemeManager=new b.AddOnManager()}(tinymce));(function(j){var g=j.each,d=j.extend,k=j.DOM,i=j.dom.Event,f=j.ThemeManager,b=j.PluginManager,e=j.explode,h=j.util.Dispatcher,a,c=0;j.documentBaseURL=window.location.href.replace(/[\?#].*$/,"").replace(/[\/\\][^\/]+$/,"");if(!/[\/\\]$/.test(j.documentBaseURL)){j.documentBaseURL+="/"}j.baseURL=new j.util.URI(j.documentBaseURL).toAbsolute(j.baseURL);j.baseURI=new j.util.URI(j.baseURL);j.onBeforeUnload=new h(j);i.add(window,"beforeunload",function(l){j.onBeforeUnload.dispatch(j,l)});j.onAddEditor=new h(j);j.onRemoveEditor=new h(j);j.EditorManager=d(j,{editors:[],i18n:{},activeEditor:null,init:function(q){var n=this,p,l=j.ScriptLoader,u,o=[],m;function r(x,y,t){var v=x[y];if(!v){return}if(j.is(v,"string")){t=v.replace(/\.\w+$/,"");t=t?j.resolve(t):0;v=j.resolve(v)}return v.apply(t||this,Array.prototype.slice.call(arguments,2))}q=d({theme:"simple",language:"en"},q);n.settings=q;i.add(document,"init",function(){var s,v;r(q,"onpageload");switch(q.mode){case"exact":s=q.elements||"";if(s.length>0){g(e(s),function(x){if(k.get(x)){m=new j.Editor(x,q);o.push(m);m.render(1)}else{g(document.forms,function(y){g(y.elements,function(z){if(z.name===x){x="mce_editor_"+c++;k.setAttrib(z,"id",x);m=new j.Editor(x,q);o.push(m);m.render(1)}})})}})}break;case"textareas":case"specific_textareas":function t(y,x){return x.constructor===RegExp?x.test(y.className):k.hasClass(y,x)}g(k.select("textarea"),function(x){if(q.editor_deselector&&t(x,q.editor_deselector)){return}if(!q.editor_selector||t(x,q.editor_selector)){u=k.get(x.name);if(!x.id&&!u){x.id=x.name}if(!x.id||n.get(x.id)){x.id=k.uniqueId()}m=new j.Editor(x.id,q);o.push(m);m.render(1)}});break}if(q.oninit){s=v=0;g(o,function(x){v++;if(!x.initialized){x.onInit.add(function(){s++;if(s==v){r(q,"oninit")}})}else{s++}if(s==v){r(q,"oninit")}})}})},get:function(l){if(l===a){return this.editors}return this.editors[l]},getInstanceById:function(l){return this.get(l)},add:function(m){var l=this,n=l.editors;n[m.id]=m;n.push(m);l._setActive(m);l.onAddEditor.dispatch(l,m);return m},remove:function(n){var m=this,l,o=m.editors;if(!o[n.id]){return null}delete o[n.id];for(l=0;l':"",visual_table_class:"mceItemTable",visual:1,font_size_style_values:"xx-small,x-small,small,medium,large,x-large,xx-large",apply_source_formatting:1,directionality:"ltr",forced_root_block:"p",valid_elements:"@[id|class|style|title|dir';if(F.document_base_url!=m.documentBaseURL){E.iframeHTML+=''}E.iframeHTML+='';if(m.relaxedDomain){E.iframeHTML+=''; + t.iframeHTML += ''; + + // Firefox 2 doesn't load stylesheets correctly this way + if (!isGecko || !/Firefox\/2/.test(navigator.userAgent)) { + for (i = 0; i < t.contentCSS.length; i++) + t.iframeHTML += ''; + + t.contentCSS = []; + } bi = s.body_id || 'tinymce'; if (bi.indexOf('=') != -1) { @@ -9559,19 +10888,18 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { t.iframeHTML += ''; // Domain relaxing enabled, then set document domain - if (tinymce.relaxedDomain) { + if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) { // We need to write the contents here in IE since multiple writes messes up refresh button and back button - if (isIE || (tinymce.isOpera && parseFloat(opera.version()) >= 9.5)) - u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()'; - else if (tinymce.isOpera) - u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";document.close();ed.setupIframe();})()'; + u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()'; } // Create iframe - n = DOM.add(o.iframeContainer, 'iframe', { + // TODO: ACC add the appropriate description on this. + n = DOM.add(o.iframeContainer, 'iframe', { id : t.id + "_ifr", src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7 - frameBorder : '0', + frameBorder : '0', + title : s.aria_label, style : { width : '100%', height : h @@ -9581,8 +10909,9 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { t.contentAreaContainer = o.iframeContainer; DOM.get(o.editorContainer).style.display = t.orgDisplay; DOM.get(t.id).style.display = 'none'; + DOM.setAttrib(t.id, 'aria-hidden', true); - if (!isIE || !tinymce.relaxedDomain) + if (!tinymce.relaxedDomain || !u) t.setupIframe(); e = n = o = null; // Cleanup @@ -9596,6 +10925,9 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { d.open(); d.write(t.iframeHTML); d.close(); + + if (tinymce.relaxedDomain) + d.domain = tinymce.relaxedDomain; } // Design mode needs to be added here Ctrl+A will fail otherwise @@ -9621,6 +10953,8 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { DOM.show(b); } + t.schema = new tinymce.html.Schema(s); + t.dom = new tinymce.dom.DOMUtils(t.getDoc(), { keep_values : true, url_converter : t.convertURL, @@ -9629,16 +10963,77 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { class_filter : s.class_filter, update_styles : 1, fix_ie_paragraphs : 1, - valid_styles : s.valid_styles + schema : t.schema }); - t.schema = new tinymce.dom.Schema(); + t.parser = new tinymce.html.DomParser(s, t.schema); - t.serializer = new tinymce.dom.Serializer(extend(s, { - valid_elements : s.verify_html === false ? '*[*]' : s.valid_elements, - dom : t.dom, - schema : t.schema - })); + // Force anchor names closed + t.parser.addAttributeFilter('name', function(nodes, name) { + var i = nodes.length, sibling, prevSibling, parent, node; + + while (i--) { + node = nodes[i]; + if (node.name === 'a' && node.firstChild) { + parent = node.parent; + + // Move children after current node + sibling = node.lastChild; + do { + prevSibling = sibling.prev; + parent.insert(sibling, node); + sibling = prevSibling; + } while (sibling); + } + } + }); + + // Convert src and href into data-mce-src, data-mce-href and data-mce-style + t.parser.addAttributeFilter('src,href,style', function(nodes, name) { + var i = nodes.length, node, dom = t.dom, value; + + while (i--) { + node = nodes[i]; + value = node.attr(name); + + if (name === "style") + node.attr('data-mce-style', dom.serializeStyle(dom.parseStyle(value), node.name)); + else + node.attr('data-mce-' + name, t.convertURL(value, name, node.name)); + } + }); + + // Keep scripts from executing + t.parser.addNodeFilter('script', function(nodes, name) { + var i = nodes.length; + + while (i--) + nodes[i].attr('type', 'mce-text/javascript'); + }); + + t.parser.addNodeFilter('#cdata', function(nodes, name) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + node.type = 8; + node.name = '#comment'; + node.value = '[CDATA[' + node.value + ']]'; + } + }); + + t.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) { + var i = nodes.length, node, nonEmptyElements = t.schema.getNonEmptyElements(); + + while (i--) { + node = nodes[i]; + + if (node.isEmpty(nonEmptyElements)) + node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true; + } + }); + + t.serializer = new tinymce.dom.Serializer(s, t.dom, t.schema); t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer); @@ -9648,18 +11043,18 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { t.formatter.register({ alignleft : [ {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}}, - {selector : 'img,table', styles : {'float' : 'left'}} + {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}} ], aligncenter : [ {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}}, - {selector : 'img', styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}}, - {selector : 'table', styles : {marginLeft : 'auto', marginRight : 'auto'}} + {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}}, + {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}} ], alignright : [ {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}}, - {selector : 'img,table', styles : {'float' : 'right'}} + {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}} ], alignfull : [ @@ -9667,33 +11062,35 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { ], bold : [ - {inline : 'strong'}, + {inline : 'strong', remove : 'all'}, {inline : 'span', styles : {fontWeight : 'bold'}}, - {inline : 'b'} + {inline : 'b', remove : 'all'} ], italic : [ - {inline : 'em'}, + {inline : 'em', remove : 'all'}, {inline : 'span', styles : {fontStyle : 'italic'}}, - {inline : 'i'} + {inline : 'i', remove : 'all'} ], underline : [ {inline : 'span', styles : {textDecoration : 'underline'}, exact : true}, - {inline : 'u'} + {inline : 'u', remove : 'all'} ], strikethrough : [ {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true}, - {inline : 'u'} + {inline : 'strike', remove : 'all'} ], - forecolor : {inline : 'span', styles : {color : '%value'}}, - hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}}, + forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false}, + hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false}, fontname : {inline : 'span', styles : {fontFamily : '%value'}}, fontsize : {inline : 'span', styles : {fontSize : '%value'}}, fontsize_class : {inline : 'span', attributes : {'class' : '%value'}}, blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'}, + subscript : {inline : 'sub'}, + superscript : {inline : 'sup'}, removeformat : [ {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true}, @@ -9714,7 +11111,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { // Pass through t.undoManager.onAdd.add(function(um, l) { - if (!l.initial) + if (um.hasUndo()) return t.onChange.dispatch(t, l, um); }); @@ -9758,29 +11155,6 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { if (s.nowrap) t.getBody().style.whiteSpace = "nowrap"; - if (s.custom_elements) { - function handleCustom(ed, o) { - each(explode(s.custom_elements), function(v) { - var n; - - if (v.indexOf('~') === 0) { - v = v.substring(1); - n = 'span'; - } else - n = 'div'; - - o.content = o.content.replace(new RegExp('<(' + v + ')([^>]*)>', 'g'), '<' + n + ' _mce_name="$1"$2>'); - o.content = o.content.replace(new RegExp('', 'g'), ''); - }); - }; - - t.onBeforeSetContent.add(handleCustom); - t.onPostProcess.add(function(ed, o) { - if (o.set) - handleCustom(ed, o); - }); - } - if (s.handle_node_change_callback) { t.onNodeChange.add(function(ed, cm, n) { t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed()); @@ -9802,6 +11176,18 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }); } + if (s.protect) { + t.onBeforeSetContent.add(function(ed, o) { + if (s.protect) { + each(s.protect, function(pattern) { + o.content = o.content.replace(pattern, function(str) { + return ''; + }); + }); + } + }); + } + if (s.convert_newlines_to_brs) { t.onBeforeSetContent.add(function(ed, o) { if (o.initial) @@ -9809,12 +11195,6 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }); } - if (s.fix_nesting && isIE) { - t.onBeforeSetContent.add(function(ed, o) { - o.content = t._fixNesting(o.content); - }); - } - if (s.preformatted) { t.onPostProcess.add(function(ed, o) { o.content = o.content.replace(/^\s*/, ''); @@ -9908,7 +11288,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { var pn = n.parentNode; if (ed.dom.isBlock(pn) && pn.lastChild === n) - ed.dom.add(pn, 'br', {'_mce_bogus' : 1}); + ed.dom.add(pn, 'br', {'data-mce-bogus' : 1}); }); }; @@ -9937,8 +11317,9 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { if (t.removed) return; - t.load({initial : true, format : (s.cleanup_on_startup ? 'html' : 'raw')}); + t.load({initial : true, format : 'html'}); t.startContent = t.getContent({format : 'raw'}); + t.undoManager.add(); t.initialized = true; t.onInit.dispatch(t); @@ -9948,11 +11329,9 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { t.nodeChanged({initial : 1}); // Load specified content CSS last - if (s.content_css) { - tinymce.each(explode(s.content_css), function(u) { - t.dom.loadCSS(t.documentBaseURI.toAbsolute(u)); - }); - } + each(t.contentCSS, function(u) { + t.dom.loadCSS(u); + }); // Handle auto focus if (s.auto_focus) { @@ -10068,7 +11447,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }, nodeChanged : function(o) { - var t = this, s = t.selection, n = (isIE ? s.getNode() : s.getStart()) || t.getBody(); + var t = this, s = t.selection, n = s.getStart() || t.getBody(); // Fix for bug #1896577 it seems that this can not be fired while the editor is loading if (t.initialized) { @@ -10101,16 +11480,16 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { t.buttons[n] = s; }, - addCommand : function(n, f, s) { - this.execCommands[n] = {func : f, scope : s || this}; + addCommand : function(name, callback, scope) { + this.execCommands[name] = {func : callback, scope : scope || this}; }, - addQueryStateHandler : function(n, f, s) { - this.queryStateCommands[n] = {func : f, scope : s || this}; + addQueryStateHandler : function(name, callback, scope) { + this.queryStateCommands[name] = {func : callback, scope : scope || this}; }, - addQueryValueHandler : function(n, f, s) { - this.queryValueCommands[n] = {func : f, scope : s || this}; + addQueryValueHandler : function(name, callback, scope) { + this.queryValueCommands[name] = {func : callback, scope : scope || this}; }, addShortcut : function(pa, desc, cmd_func, sc) { @@ -10213,12 +11592,6 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { return true; } - // Execute global commands - if (tinymce.GlobalCommands.execCommand(t, cmd, ui, val)) { - t.onExecCommand.dispatch(t, cmd, ui, val, a); - return true; - } - // Editor commands if (t.editorCommands.execCommand(cmd, ui, val)) { t.onExecCommand.dispatch(t, cmd, ui, val, a); @@ -10350,7 +11723,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { // Add undo level will trigger onchange event if (!o.no_events) { - t.undoManager.typing = 0; + t.undoManager.typing = false; t.undoManager.add(); } @@ -10382,66 +11755,77 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { return h; }, - setContent : function(h, o) { - var t = this; + setContent : function(content, args) { + var self = this, rootNode, body = self.getBody(); - o = o || {}; - o.format = o.format || 'html'; - o.set = true; - o.content = h; + // Setup args object + args = args || {}; + args.format = args.format || 'html'; + args.set = true; + args.content = content; - if (!o.no_events) - t.onBeforeSetContent.dispatch(t, o); + // Do preprocessing + if (!args.no_events) + self.onBeforeSetContent.dispatch(self, args); + + content = args.content; // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content // It will also be impossible to place the caret in the editor unless there is a BR element present - if (!tinymce.isIE && (h.length === 0 || /^\s+$/.test(h))) { - o.content = t.dom.setHTML(t.getBody(), '
'); - o.format = 'raw'; + if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) { + body.innerHTML = '
'; + return; } - o.content = t.dom.setHTML(t.getBody(), tinymce.trim(o.content)); - - if (o.format != 'raw' && t.settings.cleanup) { - o.getInner = true; - o.content = t.dom.setHTML(t.getBody(), t.serializer.serialize(t.getBody(), o)); + // Parse and serialize the html + if (args.format !== 'raw') { + content = new tinymce.html.Serializer({}, self.schema).serialize( + self.parser.parse(content) + ); } - if (!o.no_events) - t.onSetContent.dispatch(t, o); + // Set the new cleaned contents to the editor + args.content = tinymce.trim(content); + self.dom.setHTML(body, args.content); - return o.content; + // Do post processing + if (!args.no_events) + self.onSetContent.dispatch(self, args); + + return args.content; }, - getContent : function(o) { - var t = this, h; + getContent : function(args) { + var self = this, content; - o = o || {}; - o.format = o.format || 'html'; - o.get = true; + // Setup args object + args = args || {}; + args.format = args.format || 'html'; + args.get = true; - if (!o.no_events) - t.onBeforeGetContent.dispatch(t, o); + // Do preprocessing + if (!args.no_events) + self.onBeforeGetContent.dispatch(self, args); - if (o.format != 'raw' && t.settings.cleanup) { - o.getInner = true; - h = t.serializer.serialize(t.getBody(), o); - } else - h = t.getBody().innerHTML; + // Get raw contents or by default the cleaned contents + if (args.format == 'raw') + content = self.getBody().innerHTML; + else + content = self.serializer.serialize(self.getBody(), args); - h = h.replace(/^\s*|\s*$/g, ''); - o.content = h; + args.content = tinymce.trim(content); - if (!o.no_events) - t.onGetContent.dispatch(t, o); + // Do post processing + if (!args.no_events) + self.onGetContent.dispatch(self, args); - return o.content; + return args.content; }, isDirty : function() { - var t = this; + var self = this; - return tinymce.trim(t.startContent) != tinymce.trim(t.getContent({format : 'raw', no_events : 1})) && !t.isNotDirty; + return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty; }, getContainer : function() { @@ -10651,16 +12035,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { each(lo, function(v, k) { switch (k) { case 'contextmenu': - if (tinymce.isOpera) { - // Fake contextmenu on Opera - dom.bind(t.getBody(), 'mousedown', function(e) { - if (e.ctrlKey) { - e.fakeType = 'contextmenu'; - eventHandler(e); - } - }); - } else - dom.bind(t.getBody(), k, eventHandler); + dom.bind(t.getDoc(), k, eventHandler); break; case 'paste': @@ -10692,7 +12067,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { e = e.target; - if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('_mce_src'))) + if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('data-mce-src'))) e.src = t.documentBaseURI.toAbsolute(v); }); } @@ -10879,25 +12254,19 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }); t.onKeyDown.add(function(ed, e) { + var sel; + switch (e.keyCode) { case 8: + sel = t.getDoc().selection; + // Fix IE control + backspace browser bug - if (t.selection.getRng().item) { - ed.dom.remove(t.selection.getRng().item(0)); + if (sel.createRange && sel.createRange().item) { + ed.dom.remove(sel.createRange().item(0)); return Event.cancel(e); } } }); - - /*if (t.dom.boxModel) { - t.getBody().style.height = '100%'; - - Event.add(t.getWin(), 'resize', function(e) { - var docElm = t.getDoc().documentElement; - - docElm.style.height = (docElm.offsetHeight - 10) + 'px'; - }); - }*/ } if (tinymce.isOpera) { @@ -10909,7 +12278,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { // Add custom undo/redo handlers if (s.custom_undo_redo) { function addUndo() { - t.undoManager.typing = 0; + t.undoManager.typing = false; t.undoManager.add(); }; @@ -10918,24 +12287,48 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { addUndo(); }); + // Add undo level when contents is drag/dropped within the editor + t.dom.bind(t.dom.getRoot(), 'dragend', function(e) { + addUndo(); + }); + t.onKeyUp.add(function(ed, e) { + var rng, parent, bookmark; + + // Fix for bug #3168, to remove odd ".." nodes from the DOM we need to get/set the HTML of the parent node. + if (isIE && e.keyCode == 8) { + rng = t.selection.getRng(); + if (rng.parentElement) { + parent = rng.parentElement(); + bookmark = t.selection.getBookmark(); + parent.innerHTML = parent.innerHTML; + t.selection.moveToBookmark(bookmark); + } + } + if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey) addUndo(); }); t.onKeyDown.add(function(ed, e) { - var rng, parent, bookmark; + var rng, parent, bookmark, keyCode = e.keyCode; // IE has a really odd bug where the DOM might include an node that doesn't have // a proper structure. If you try to access nodeValue it would throw an illegal value exception. // This seems to only happen when you delete contents and it seems to be avoidable if you refresh the element // after you delete contents from it. See: #3008923 - if (isIE && e.keyCode == 46) { + if (isIE && keyCode == 46) { rng = t.selection.getRng(); if (rng.parentElement) { parent = rng.parentElement(); + if (!t.undoManager.typing) { + t.undoManager.beforeChange(); + t.undoManager.typing = true; + t.undoManager.add(); + } + // Select next word when ctrl key is used in combo with delete if (e.ctrlKey) { rng.moveEnd('word', 1); @@ -10966,17 +12359,24 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { } } - // Is caracter positon keys - if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45) { + // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter + if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45) { + // Add position before enter key is pressed, used by IE since it still uses the default browser behavior + // Todo: Remove this once we normalize enter behavior on IE + if (tinymce.isIE && keyCode == 13) + t.undoManager.beforeChange(); + if (t.undoManager.typing) addUndo(); return; } - if (!t.undoManager.typing) { + // If key isn't shift,ctrl,alt,capslock,metakey + if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !t.undoManager.typing) { + t.undoManager.beforeChange(); t.undoManager.add(); - t.undoManager.typing = 1; + t.undoManager.typing = true; } }); @@ -10985,6 +12385,53 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { addUndo(); }); } + + // Bug fix for FireFox keeping styles from end of selection instead of start. + if (tinymce.isGecko) { + function getAttributeApplyFunction() { + var template = t.dom.getAttribs(t.selection.getStart().cloneNode(false)); + + return function() { + var target = t.selection.getStart(); + t.dom.removeAllAttribs(target); + each(template, function(attr) { + target.setAttributeNode(attr.cloneNode(true)); + }); + }; + } + + function isSelectionAcrossElements() { + var s = t.selection; + + return !s.isCollapsed() && s.getStart() != s.getEnd(); + } + + t.onKeyPress.add(function(ed, e) { + var applyAttributes; + + if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) { + applyAttributes = getAttributeApplyFunction(); + t.getDoc().execCommand('delete', false, null); + applyAttributes(); + + return Event.cancel(e); + } + }); + + t.dom.bind(t.getDoc(), 'cut', function(e) { + var applyAttributes; + + if (isSelectionAcrossElements()) { + applyAttributes = getAttributeApplyFunction(); + t.onKeyUp.addToTop(Event.cancel, Event); + + setTimeout(function() { + applyAttributes(); + t.onKeyUp.remove(Event.cancel, Event); + }, 0); + } + }); + } }, _isHidden : function() { @@ -10996,57 +12443,6 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { // Weird, wheres that cursor selection? s = this.selection.getSel(); return (!s || !s.rangeCount || s.rangeCount == 0); - }, - - // Fix for bug #1867292 - _fixNesting : function(s) { - var d = [], i; - - s = s.replace(/<(\/)?([^\s>]+)[^>]*?>/g, function(a, b, c) { - var e; - - // Handle end element - if (b === '/') { - if (!d.length) - return ''; - - if (c !== d[d.length - 1].tag) { - for (i=d.length - 1; i>=0; i--) { - if (d[i].tag === c) { - d[i].close = 1; - break; - } - } - - return ''; - } else { - d.pop(); - - if (d.length && d[d.length - 1].close) { - a = a + ''; - d.pop(); - } - } - } else { - // Ignore these - if (/^(br|hr|input|meta|img|link|param)$/i.test(c)) - return a; - - // Ignore closed ones - if (/\/>$/.test(a)) - return a; - - d.push({tag : c}); // Push start element - } - - return a; - }); - - // End all open tags - for (i=d.length - 1; i>=0; i--) - s += ''; - - return s; } }); })(tinymce); @@ -11193,6 +12589,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }); toggleFormat('align' + align); + execCommand('mceRepaint'); }, // Override list commands to fix WebKit bug @@ -11218,7 +12615,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }, // Override commands to use the text formatter engine - 'Bold,Italic,Underline,Strikethrough' : function(command) { + 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { toggleFormat(command); }, @@ -11291,7 +12688,115 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }, mceInsertContent : function(command, ui, value) { - selection.setContent(value); + var caretNode, rng, rootNode, parent, node, rng, nodeRect, viewPortRect, args; + + function findSuitableCaretNode(node, root_node, next) { + var walker = new tinymce.dom.TreeWalker(next ? node.nextSibling : node.previousSibling, root_node); + + while ((node = walker.current())) { + if ((node.nodeType == 3 && tinymce.trim(node.nodeValue).length) || node.nodeName == 'BR' || node.nodeName == 'IMG') + return node; + + if (next) + walker.next(); + else + walker.prev(); + } + }; + + args = {content: value, format: 'html'}; + selection.onBeforeSetContent.dispatch(selection, args); + value = args.content; + + // Add caret at end of contents if it's missing + if (value.indexOf('{$caret}') == -1) + value += '{$caret}'; + + // Set the content at selection to a span and replace it's contents with the value + selection.setContent('\uFEFF', {no_events : false}); + dom.setOuterHTML('__mce', value.replace(/\{\$caret\}/, '\uFEFF')); + + caretNode = dom.select('#__mce')[0]; + rootNode = dom.getRoot(); + + // Move the caret into the last suitable location within the previous sibling if it's a block since the block might be split + if (caretNode.previousSibling && dom.isBlock(caretNode.previousSibling) || caretNode.parentNode == rootNode) { + node = findSuitableCaretNode(caretNode, rootNode); + if (node) { + if (node.nodeName == 'BR') + node.parentNode.insertBefore(caretNode, node); + else + dom.insertAfter(caretNode, node); + } + } + + // Find caret root parent and clean it up using the serializer to avoid nesting + while (caretNode) { + if (caretNode === rootNode) { + // Clean up the parent element by parsing and serializing it + // This will remove invalid elements/attributes and fix nesting issues + dom.setOuterHTML(parent, + new tinymce.html.Serializer({}, editor.schema).serialize( + editor.parser.parse(dom.getOuterHTML(parent)) + ) + ); + + break; + } + + parent = caretNode; + caretNode = caretNode.parentNode; + } + + // Find caret after cleanup and move selection to that location + caretNode = dom.select('#__mce')[0]; + if (caretNode) { + node = findSuitableCaretNode(caretNode, rootNode) || findSuitableCaretNode(caretNode, rootNode, true); + dom.remove(caretNode); + + if (node) { + rng = dom.createRng(); + + if (node.nodeType == 3) { + rng.setStart(node, node.length); + rng.setEnd(node, node.length); + } else { + if (node.nodeName == 'BR') { + rng.setStartBefore(node); + rng.setEndBefore(node); + } else { + rng.setStartAfter(node); + rng.setEndAfter(node); + } + } + + selection.setRng(rng); + + // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well + if (!tinymce.isIE) { + node = dom.create('span', null, '\u00a0'); + rng.insertNode(node); + nodeRect = dom.getRect(node); + viewPortRect = dom.getViewPort(editor.getWin()); + + // Check if node is out side the viewport if it is then scroll to it + if ((nodeRect.y > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) || + (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) { + editor.getBody().scrollLeft = nodeRect.x; + editor.getBody().scrollTop = nodeRect.y; + } + + dom.remove(node); + } + + // Make sure that the selection is collapsed after we removed the node fixes a WebKit bug + // where WebKit would place the endContainer/endOffset at a different location than the startContainer/startOffset + selection.collapse(true); + } + } + + selection.onSetContent.dispatch(selection, args); + editor.addVisual(); }, mceInsertRawHTML : function(command, ui, value) { @@ -11346,7 +12851,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }, InsertHorizontalRule : function() { - selection.setContent('
'); + editor.execCommand('mceInsertContent', false, '
'); }, mceToggleVisualAid : function() { @@ -11355,18 +12860,36 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }, mceReplaceContent : function(command, ui, value) { - selection.setContent(value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'}))); + editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'}))); }, mceInsertLink : function(command, ui, value) { - var link = dom.getParent(selection.getNode(), 'a'); + var link = dom.getParent(selection.getNode(), 'a'), img, floatVal; if (tinymce.is(value, 'string')) value = {href : value}; + // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here. + value.href = value.href.replace(' ', '%20'); + if (!link) { + // WebKit can't create links on float images for some odd reason so just remove it and restore it later + if (tinymce.isWebKit) { + img = dom.getParent(selection.getNode(), 'img'); + + if (img) { + floatVal = img.style.cssFloat; + img.style.cssFloat = null; + } + } + execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);'); - each(dom.select('a[href=javascript:mctmp(0);]'), function(link) { + + // Restore float value + if (floatVal) + img.style.cssFloat = floatVal; + + each(dom.select("a[href='javascript:mctmp(0);']"), function(link) { dom.setAttribs(link, value); }); } else { @@ -11394,7 +12917,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { return isFormatMatch('align' + command.substring(7)); }, - 'Bold,Italic,Underline,Strikethrough' : function(command) { + 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { return isFormatMatch(command); }, @@ -11451,6 +12974,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { } }; })(tinymce); + (function(tinymce) { var Dispatcher = tinymce.util.Dispatcher; @@ -11462,12 +12986,20 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }; return self = { - typing : 0, + typing : false, onAdd : new Dispatcher(self), + onUndo : new Dispatcher(self), + onRedo : new Dispatcher(self), + beforeChange : function() { + // Set before bookmark on previous level + if (data[index]) + data[index].beforeBookmark = editor.selection.getBookmark(2, true); + }, + add : function(level) { var i, settings = editor.settings, lastLevel; @@ -11476,10 +13008,8 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { // Add undo level if needed lastLevel = data[index]; - if (lastLevel && lastLevel.content == level.content) { - if (index > 0 || data.length == 1) - return null; - } + if (lastLevel && lastLevel.content == level.content) + return null; // Time to compress if (settings.custom_undo_redo_levels) { @@ -11496,13 +13026,8 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { level.bookmark = editor.selection.getBookmark(2, true); // Crop array if needed - if (index < data.length - 1) { - // Treat first level as initial - if (index == 0) - data = []; - else - data.length = index + 1; - } + if (index < data.length - 1) + data.length = index + 1; data.push(level); index = data.length - 1; @@ -11518,14 +13043,14 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { if (self.typing) { self.add(); - self.typing = 0; + self.typing = false; } if (index > 0) { level = data[--index]; editor.setContent(level.content, {format : 'raw'}); - editor.selection.moveToBookmark(level.bookmark); + editor.selection.moveToBookmark(level.beforeBookmark); self.onUndo.dispatch(self, level); } @@ -11550,15 +13075,16 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { clear : function() { data = []; - index = self.typing = 0; + index = 0; + self.typing = false; }, hasUndo : function() { - return index > 0 || self.typing; + return index > 0 || this.typing; }, hasRedo : function() { - return index < data.length - 1; + return index < data.length - 1 && !this.typing; } }; }; @@ -11607,24 +13133,15 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { return rng2.cloneContents().textContent.length == 0; }; - function isEmpty(n) { - n = n.innerHTML; - - n = n.replace(/<(img|hr|table|input|select|textarea)[ \>]/gi, '-'); // Keep these convert them to - chars - n = n.replace(/<[^>]+>/g, ''); // Remove all tags - - return n.replace(/[ \u00a0\t\r\n]+/g, '') == ''; - }; - function splitList(selection, dom, li) { var listBlock, block; - if (isEmpty(li)) { + if (dom.isEmpty(li)) { listBlock = dom.getParent(li, 'ul,ol'); if (!dom.getParent(listBlock.parentNode, 'ul,ol')) { dom.split(listBlock, li); - block = dom.create('p', 0, '
'); + block = dom.create('p', 0, '
'); dom.replace(block, li); selection.select(block, 1); } @@ -11646,33 +13163,16 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { ed.onPreInit.add(t.setup, t); - t.reOpera = new RegExp('(\\u00a0| | )<\/' + elm + '>', 'gi'); - t.rePadd = new RegExp(']+)><\\\/p>|]+)\\\/>|]+)>\\s+<\\\/p>|

<\\\/p>||

\\s+<\\\/p>'.replace(/p/g, elm), 'gi'); - t.reNbsp2BR1 = new RegExp(']+)>[\\s\\u00a0]+<\\\/p>|

[\\s\\u00a0]+<\\\/p>'.replace(/p/g, elm), 'gi'); - t.reNbsp2BR2 = new RegExp('<%p()([^>]+)>( | )<\\\/%p>|<%p>( | )<\\\/%p>'.replace(/%p/g, elm), 'gi'); - t.reBR2Nbsp = new RegExp(']+)>\\s*
\\s*<\\\/p>|

\\s*
\\s*<\\\/p>'.replace(/p/g, elm), 'gi'); - - function padd(ed, o) { - if (isOpera) - o.content = o.content.replace(t.reOpera, ''); - - o.content = tinymce._replace(t.rePadd, '<' + elm + '$1$2$3$4$5$6>\u00a0', o.content); - - if (!isIE && !isOpera && o.set) { - // Use   instead of BR in padded paragraphs - o.content = o.content.replace(t.reNbsp2BR1, '<' + elm + '$1$2>
'); - o.content = o.content.replace(t.reNbsp2BR2, '<' + elm + '$1$2>
'); - } else - o.content = tinymce._replace(t.reBR2Nbsp, '<' + elm + '$1$2>\u00a0', o.content); - }; - - ed.onBeforeSetContent.add(padd); - ed.onPostProcess.add(padd); - if (s.forced_root_block) { ed.onInit.add(t.forceRoots, t); ed.onSetContent.add(t.forceRoots, t); ed.onBeforeGetContent.add(t.forceRoots, t); + ed.onExecCommand.add(function(ed, cmd) { + if (cmd == 'mceInsertContent') { + t.forceRoots(); + ed.nodeChanged(); + } + }); } }, @@ -11746,6 +13246,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { parent.innerHTML = '\uFEFF'; selection.select(parent, 1); + selection.collapse(true); ed.getDoc().execCommand('Delete', false, null); t._previousFormats = 0; } @@ -11800,21 +13301,6 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }); } - // Padd empty inline elements within block elements - // For example:

becomes

 

- ed.onPreProcess.add(function(ed, o) { - each(dom.select('p,h1,h2,h3,h4,h5,h6,div', o.node), function(p) { - if (isEmpty(p)) { - each(dom.select('span,em,strong,b,i', o.node), function(n) { - if (!n.hasChildNodes()) { - n.appendChild(ed.getDoc().createTextNode('\u00a0')); - return FALSE; // Break the loop one padding is enough - } - }); - } - }); - }); - // IE specific fixes if (isIE) { // Replaces IE:s auto generated paragraphs with the specified element name @@ -11875,7 +13361,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { nx = nl[i]; // Ignore internal elements - if (nx.nodeType === 1 && nx.getAttribute('_mce_type')) { + if (nx.nodeType === 1 && nx.getAttribute('data-mce-type')) { bl = null; continue; } @@ -12003,6 +13489,8 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body; var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car; + ed.undoManager.beforeChange(); + // If root blocks are forced then use Operas default behavior since it's really good // Removed due to bug: #1853816 // if (se.forced_root_block && isOpera) @@ -12179,7 +13667,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { aft.innerHTML = aft.firstChild.innerHTML; // Padd empty blocks - if (isEmpty(bef)) + if (dom.isEmpty(bef)) bef.innerHTML = '
'; function appendStyles(e, en) { @@ -12206,14 +13694,14 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { nn = nn.appendChild(nl[i]); // Padd most inner style element - nl[0].innerHTML = isOpera ? ' ' : '
'; // Extra space for Opera so that the caret can move there + nl[0].innerHTML = isOpera ? '\u00a0' : '
'; // Extra space for Opera so that the caret can move there return nl[0]; // Move caret to most inner element } else - e.innerHTML = isOpera ? ' ' : '
'; // Extra space for Opera so that the caret can move there + e.innerHTML = isOpera ? '\u00a0' : '
'; // Extra space for Opera so that the caret can move there }; // Fill empty afterblook with current style - if (isEmpty(aft)) + if (dom.isEmpty(aft)) car = appendStyles(aft, en); // Opera needs this one backwards for older versions @@ -12242,14 +13730,20 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs y = ed.dom.getPos(aft).y; - ch = aft.clientHeight; + //ch = aft.clientHeight; // Is element within viewport - if (y < vp.y || y + ch > vp.y + vp.h) { + if (y < vp.y || y + 25 > vp.y + vp.h) { ed.getWin().scrollTo(0, y < vp.y ? y : y - vp.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks - //console.debug('SCROLL!', 'vp.y: ' + vp.y, 'y' + y, 'vp.h' + vp.h, 'clientHeight' + aft.clientHeight, 'yyy: ' + (y < vp.y ? y : y - vp.h + aft.clientHeight)); + + /*console.debug( + 'Element: y=' + y + ', h=' + ch + ', ' + + 'Viewport: y=' + vp.y + ", h=" + vp.h + ', bottom=' + (vp.y + vp.h) + );*/ } + ed.undoManager.add(); + return FALSE; }, @@ -12464,7 +13958,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { c = new tinymce.ui.NativeListBox(id, s); else { cls = cc || t._cls.listbox || tinymce.ui.ListBox; - c = new cls(id, s); + c = new cls(id, s, ed); } t.controls[id] = c; @@ -12519,7 +14013,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { if (s.menu_button) { cls = cc || t._cls.menubutton || tinymce.ui.MenuButton; - c = new cls(id, s); + c = new cls(id, s, ed); ed.onMouseDown.add(c.hideMenu, c); } else { cls = t._cls.button || tinymce.ui.Button; @@ -12566,7 +14060,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { id = t.prefix + id; cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton; - c = t.add(new cls(id, s)); + c = t.add(new cls(id, s, ed)); ed.onMouseDown.add(c.hideMenu, c); return c; @@ -12606,7 +14100,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { id = t.prefix + id; cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton; - c = new cls(id, s); + c = new cls(id, s, ed); ed.onMouseDown.add(c.hideMenu, c); // Remove the menu element when the editor is removed @@ -12638,13 +14132,25 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { id = t.prefix + id; cls = cc || t._cls.toolbar || tinymce.ui.Toolbar; - c = new cls(id, s); + c = new cls(id, s, t.editor); if (t.get(id)) return null; return t.add(c); }, + + createToolbarGroup : function(id, s, cc) { + var c, t = this, cls; + id = t.prefix + id; + cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup; + c = new cls(id, s, t.editor); + + if (t.get(id)) + return null; + + return t.add(c); + }, createSeparator : function(cc) { var cls = cc || this._cls.separator || tinymce.ui.Separator; @@ -12781,53 +14287,6 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { } }); }(tinymce)); -(function(tinymce) { - function CommandManager() { - var execCommands = {}, queryStateCommands = {}, queryValueCommands = {}; - - function add(collection, cmd, func, scope) { - if (typeof(cmd) == 'string') - cmd = [cmd]; - - tinymce.each(cmd, function(cmd) { - collection[cmd.toLowerCase()] = {func : func, scope : scope}; - }); - }; - - tinymce.extend(this, { - add : function(cmd, func, scope) { - add(execCommands, cmd, func, scope); - }, - - addQueryStateHandler : function(cmd, func, scope) { - add(queryStateCommands, cmd, func, scope); - }, - - addQueryValueHandler : function(cmd, func, scope) { - add(queryValueCommands, cmd, func, scope); - }, - - execCommand : function(scope, cmd, ui, value, args) { - if (cmd = execCommands[cmd.toLowerCase()]) { - if (cmd.func.call(scope || cmd.scope, ui, value, args) !== false) - return true; - } - }, - - queryCommandValue : function() { - if (cmd = queryValueCommands[cmd.toLowerCase()]) - return cmd.func.call(scope || cmd.scope, ui, value, args); - }, - - queryCommandState : function() { - if (cmd = queryStateCommands[cmd.toLowerCase()]) - return cmd.func.call(scope || cmd.scope, ui, value, args); - } - }); - }; - - tinymce.GlobalCommands = new CommandManager(); -})(tinymce); (function(tinymce) { tinymce.Formatter = function(ed) { var formats = {}, @@ -12836,7 +14295,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { selection = ed.selection, TreeWalker = tinymce.dom.TreeWalker, rangeUtils = new tinymce.dom.RangeUtils(dom), - isValid = ed.schema.isValid, + isValid = ed.schema.isValidChild, isBlock = dom.isBlock, forcedRootBlock = ed.settings.forced_root_block, nodeIndex = dom.nodeIndex, @@ -12905,8 +14364,31 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { } }; + var getTextDecoration = function(node) { + var decoration; + + ed.dom.getParent(node, function(n) { + decoration = ed.dom.getStyle(n, 'text-decoration'); + return decoration && decoration !== 'none'; + }); + + return decoration; + }; + + var processUnderlineAndColor = function(node) { + var textDecoration; + if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) { + textDecoration = getTextDecoration(node.parentNode); + if (ed.dom.getStyle(node, 'color') && textDecoration) { + ed.dom.setStyle(node, 'text-decoration', textDecoration); + } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) { + ed.dom.setStyle(node, 'text-decoration', null); + } + } + }; + function apply(name, vars, node) { - var formatList = get(name), format = formatList[0], bookmark, rng, i; + var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed(); function moveStart(rng) { var container = rng.startContainer, @@ -12997,6 +14479,11 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { if (format.selector) { // Look for matching formats each(formatList, function(format) { + // Check collapsed state if it exists + if ('collapsed' in format && format.collapsed !== isCollapsed) { + return; + } + if (dom.is(node, format.selector) && !isCaretNode(node)) { setElementFormat(node, format); found = true; @@ -13011,7 +14498,8 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { } // Is it valid to wrap this item - if (isValid(wrapName, nodeName) && isValid(parentName, wrapName)) { + if (isValid(wrapName, nodeName) && isValid(parentName, wrapName) && + !(node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279)) { // Start wrapping if (!currentWrapElm) { // Wrap the node @@ -13036,6 +14524,30 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { each(nodes, process); }); + // Wrap links inside as well, for example color inside a link when the wrapper is around the link + if (format.wrap_links === false) { + each(newWrappers, function(node) { + function process(node) { + var i, currentWrapElm, children; + + if (node.nodeName === 'A') { + currentWrapElm = wrapElm.cloneNode(FALSE); + newWrappers.push(currentWrapElm); + + children = tinymce.grep(node.childNodes); + for (i = 0; i < children.length; i++) + currentWrapElm.appendChild(children[i]); + + node.appendChild(currentWrapElm); + } + + each(tinymce.grep(node.childNodes), process); + }; + + process(node); + }); + } + // Cleanup each(newWrappers, function(node) { var childCount; @@ -13075,8 +14587,9 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { childCount = getChildCount(node); - // Remove empty nodes - if (childCount === 0) { + // Remove empty nodes but only if there is multiple wrappers and they are not block + // elements so never remove single

since that would remove the currrent empty block element where the caret is at + if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) { dom.remove(node, 1); return; } @@ -13092,6 +14605,19 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { // this: text // will become: text each(dom.select(format.inline, node), function(child) { + var parent; + + // When wrap_links is set to false we don't want + // to remove the format on children within links + if (format.wrap_links === false) { + parent = child.parentNode; + + do { + if (parent.nodeName === 'A') + return; + } while (parent = parent.parentNode); + } + removeFormat(format, vars, child, format.exact ? child : null); }); }); @@ -13132,11 +14658,20 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { applyRngStyle(expandRng(rng, formatList)); } else { - if (!selection.isCollapsed() || !format.inline) { + if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { + // Obtain selection node before selection is unselected by applyRngStyle() + var curSelNode = ed.selection.getNode(); + // Apply formatting to selection bookmark = selection.getBookmark(); applyRngStyle(expandRng(selection.getRng(TRUE), formatList)); + // Colored nodes should be underlined so that the color of the underline matches the text color. + if (format.styles && (format.styles.color || format.styles.textDecoration)) { + tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes'); + processUnderlineAndColor(curSelNode); + } + selection.moveToBookmark(bookmark); selection.setRng(moveStart(selection.getRng(TRUE))); ed.nodeChanged(); @@ -13300,8 +14835,8 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { if (startContainer != endContainer) { // Wrap start/end nodes in span element since these might be cloned/moved - startContainer = wrap(startContainer, 'span', {id : '_start', _mce_type : 'bookmark'}); - endContainer = wrap(endContainer, 'span', {id : '_end', _mce_type : 'bookmark'}); + startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'}); + endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'}); // Split start/end splitToFormatRoot(startContainer); @@ -13324,6 +14859,11 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { rangeUtils.walk(rng, function(nodes) { each(nodes, function(node) { process(node); + + // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined. + if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') { + removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node); + } }); }); }; @@ -13337,7 +14877,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { return; } - if (!selection.isCollapsed() || !format.inline) { + if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { bookmark = selection.getBookmark(); removeRngStyle(selection.getRng(TRUE)); selection.moveToBookmark(bookmark); @@ -13353,7 +14893,9 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }; function toggle(name, vars, node) { - if (match(name, vars, node)) + var fmt = get(name); + + if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0]['toggle'])) remove(name, vars, node); else apply(name, vars, node); @@ -13617,7 +15159,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { var startContainer = rng.startContainer, startOffset = rng.startOffset, endContainer = rng.endContainer, - endOffset = rng.endOffset, sibling, lastIdx; + endOffset = rng.endOffset, sibling, lastIdx, leaf; // This function walks up the tree if there is no siblings before/after the node function findParentContainer(container, child_name, sibling_name, root) { @@ -13647,6 +15189,19 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { return container; }; + // This function walks down the tree to find the leaf at the selection. + // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node. + function findLeaf(node, offset) { + if (offset === undefined) + offset = node.nodeType === 3 ? node.length : node.childNodes.length; + while (node && node.hasChildNodes()) { + node = node.childNodes[offset]; + if (node) + offset = node.nodeType === 3 ? node.length : node.childNodes.length; + } + return { node: node, offset: offset }; + } + // If index based start position then resolve it if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { lastIdx = startContainer.childNodes.length - 1; @@ -13672,12 +15227,36 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { if (isBookmarkNode(startContainer)) startContainer = startContainer.nextSibling || startContainer; - if (isBookmarkNode(endContainer.parentNode)) + if (isBookmarkNode(endContainer.parentNode)) { + endOffset = dom.nodeIndex(endContainer); endContainer = endContainer.parentNode; + } - if (isBookmarkNode(endContainer)) - endContainer = endContainer.previousSibling || endContainer; + if (isBookmarkNode(endContainer) && endContainer.previousSibling) { + endContainer = endContainer.previousSibling; + endOffset = endContainer.length; + } + if (format[0].inline) { + // Avoid applying formatting to a trailing space. + leaf = findLeaf(endContainer, endOffset); + if (leaf.node) { + while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) + leaf = findLeaf(leaf.node.previousSibling); + + if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 && + leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') { + + if (leaf.offset > 1) { + endContainer = leaf.node; + endContainer.splitText(leaf.offset - 1); + } else if (leaf.node.previousSibling) { + endContainer = leaf.node.previousSibling; + } + } + } + } + // Move start/end point up the tree if the leaves are sharp and if we are in different containers // Example * becomes !: !

*texttext*

! // This will reduce the number of wrapper elements that needs to be created @@ -13690,7 +15269,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { // Expand start/end container to matching selector if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { function findSelectorEndPoint(container, sibling_name) { - var parents, i, y; + var parents, i, y, curFormat; if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name]) container = container[sibling_name]; @@ -13698,7 +15277,13 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { parents = getParents(container); for (i = 0; i < parents.length; i++) { for (y = 0; y < format.length; y++) { - if (dom.is(parents[i], format[y].selector)) + curFormat = format[y]; + + // If collapsed state is set then skip formats that doesn't match that + if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) + continue; + + if (dom.is(parents[i], curFormat.selector)) return parents[i]; } } @@ -13808,7 +15393,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { // Remove style attribute if it's empty if (stylesModified && dom.getAttrib(node, 'style') == '') { node.removeAttribute('style'); - node.removeAttribute('_mce_style'); + node.removeAttribute('data-mce-style'); } // Remove attributes @@ -13849,7 +15434,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { // Remove mce prefixed attributes if (MCE_ATTR_RE.test(name)) - node.removeAttribute('_mce_' + name); + node.removeAttribute('data-mce-' + name); node.removeAttribute(name); } @@ -13934,7 +15519,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }; function isBookmarkNode(node) { - return node && node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark'; + return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark'; }; function mergeSiblings(prev, next) { @@ -14005,7 +15590,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { if (prev && next) { function findElementSibling(node, sibling_name) { for (sibling = node; sibling; sibling = sibling[sibling_name]) { - if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling)) + if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) return node; if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) @@ -14082,6 +15667,10 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { // Apply pending formats each(pendingFormats.apply.reverse(), function(item) { apply(item.name, item.vars, caret_node); + + // Colored nodes should be underlined so that the color of the underline matches the text color. + if (item.name === 'forecolor' && item.vars.value) + processUnderlineAndColor(caret_node.parentNode); }); // Remove pending formats @@ -14213,6 +15802,7 @@ tinymce.onAddEditor.add(function(tinymce, ed) { }; ed.onPreProcess.add(convert); + ed.onSetContent.add(convert); ed.onInit.add(function() { ed.selection.onSetContent.add(convert); diff --git a/src/Orchard.Web/Modules/TinyMce/Scripts/utils/form_utils.js b/src/Orchard.Web/Modules/TinyMce/Scripts/utils/form_utils.js index 2617a26ed..59da01399 100644 --- a/src/Orchard.Web/Modules/TinyMce/Scripts/utils/form_utils.js +++ b/src/Orchard.Web/Modules/TinyMce/Scripts/utils/form_utils.js @@ -11,10 +11,14 @@ var themeBaseURL = tinyMCEPopup.editor.baseURI.toAbsolute('themes/' + tinyMCEPopup.getParam("theme")); function getColorPickerHTML(id, target_form_element) { - var h = ""; + var h = "", dom = tinyMCEPopup.dom; - h += ''; - h += ' '; + if (label = dom.select('label[for=' + target_form_element + ']')[0]) { + label.id = label.id || dom.uniqueId(); + } + + h += ''; + h += ' '; return h; } @@ -67,6 +71,9 @@ function selectByValue(form_obj, field_name, value, add_custom, ignore_case) { if (!form_obj || !form_obj.elements[field_name]) return; + if (!value) + value = ""; + var sel = form_obj.elements[field_name]; var found = false; @@ -171,7 +178,7 @@ function convertHexToRGB(col) { } function trimSize(size) { - return size.replace(/([0-9\.]+)px|(%|in|cm|mm|em|ex|pt|pc)/, '$1$2'); + return size.replace(/([0-9\.]+)(px|%|in|cm|mm|em|ex|pt|pc)/i, '$1$2'); } function getCSSSize(size) { @@ -183,6 +190,9 @@ function getCSSSize(size) { // Add px if (/^[0-9]+$/.test(size)) size += 'px'; + // Sanity check, IE doesn't like broken values + else if (!(/^[0-9\.]+(px|%|in|cm|mm|em|ex|pt|pc)$/i.test(size))) + return ""; return size; } diff --git a/src/Orchard.Web/Modules/TinyMce/Scripts/utils/mctabs.js b/src/Orchard.Web/Modules/TinyMce/Scripts/utils/mctabs.js index 825d4c143..458ec86da 100644 --- a/src/Orchard.Web/Modules/TinyMce/Scripts/utils/mctabs.js +++ b/src/Orchard.Web/Modules/TinyMce/Scripts/utils/mctabs.js @@ -10,6 +10,7 @@ function MCTabs() { this.settings = []; + this.onChange = tinyMCEPopup.editor.windowManager.createInstance('tinymce.util.Dispatcher'); }; MCTabs.prototype.init = function(settings) { @@ -28,26 +29,62 @@ MCTabs.prototype.getParam = function(name, default_value) { return value; }; -MCTabs.prototype.displayTab = function(tab_id, panel_id) { - var panelElm, panelContainerElm, tabElm, tabContainerElm, selectionClass, nodes, i; +MCTabs.prototype.showTab =function(tab){ + tab.className = 'current'; + tab.setAttribute("aria-selected", true); + tab.setAttribute("aria-expanded", true); + tab.tabIndex = 0; +}; + +MCTabs.prototype.hideTab =function(tab){ + var t=this; + + tab.className = ''; + tab.setAttribute("aria-selected", false); + tab.setAttribute("aria-expanded", false); + tab.tabIndex = -1; +}; + +MCTabs.prototype.showPanel = function(panel) { + panel.className = 'current'; + panel.setAttribute("aria-hidden", false); +}; + +MCTabs.prototype.hidePanel = function(panel) { + panel.className = 'panel'; + panel.setAttribute("aria-hidden", true); +}; + +MCTabs.prototype.getPanelForTab = function(tabElm) { + return tinyMCEPopup.dom.getAttrib(tabElm, "aria-controls"); +}; + +MCTabs.prototype.displayTab = function(tab_id, panel_id, avoid_focus) { + var panelElm, panelContainerElm, tabElm, tabContainerElm, selectionClass, nodes, i, t = this; + + tabElm = document.getElementById(tab_id); + + if (panel_id === undefined) { + panel_id = t.getPanelForTab(tabElm); + } panelElm= document.getElementById(panel_id); panelContainerElm = panelElm ? panelElm.parentNode : null; - tabElm = document.getElementById(tab_id); tabContainerElm = tabElm ? tabElm.parentNode : null; - selectionClass = this.getParam('selection_class', 'current'); + selectionClass = t.getParam('selection_class', 'current'); if (tabElm && tabContainerElm) { nodes = tabContainerElm.childNodes; // Hide all other tabs for (i = 0; i < nodes.length; i++) { - if (nodes[i].nodeName == "LI") - nodes[i].className = ''; + if (nodes[i].nodeName == "LI") { + t.hideTab(nodes[i]); + } } // Show selected tab - tabElm.className = 'current'; + t.showTab(tabElm); } if (panelElm && panelContainerElm) { @@ -56,11 +93,15 @@ MCTabs.prototype.displayTab = function(tab_id, panel_id) { // Hide all other panels for (i = 0; i < nodes.length; i++) { if (nodes[i].nodeName == "DIV") - nodes[i].className = 'panel'; + t.hidePanel(nodes[i]); + } + + if (!avoid_focus) { + tabElm.focus(); } // Show selected panel - panelElm.className = 'current'; + t.showPanel(panelElm); } }; @@ -73,5 +114,49 @@ MCTabs.prototype.getAnchor = function() { return ""; }; -// Global instance + +//Global instance var mcTabs = new MCTabs(); + +tinyMCEPopup.onInit.add(function() { + var tinymce = tinyMCEPopup.getWin().tinymce, dom = tinyMCEPopup.dom, each = tinymce.each; + + each(dom.select('div.tabs'), function(tabContainerElm) { + var keyNav; + + dom.setAttrib(tabContainerElm, "role", "tablist"); + + var items = tinyMCEPopup.dom.select('li', tabContainerElm); + var action = function(id) { + mcTabs.displayTab(id, mcTabs.getPanelForTab(id)); + mcTabs.onChange.dispatch(id); + }; + + each(items, function(item) { + dom.setAttrib(item, 'role', 'tab'); + dom.bind(item, 'click', function(evt) { + action(item.id); + }); + }); + + dom.bind(dom.getRoot(), 'keydown', function(evt) { + if (evt.keyCode === 9 && evt.ctrlKey && !evt.altKey) { // Tab + keyNav.moveFocus(evt.shiftKey ? -1 : 1); + tinymce.dom.Event.cancel(evt); + } + }); + + each(dom.select('a', tabContainerElm), function(a) { + dom.setAttrib(a, 'tabindex', '-1'); + }); + + keyNav = tinyMCEPopup.editor.windowManager.createInstance('tinymce.ui.KeyboardNavigation', { + root: tabContainerElm, + items: items, + onAction: action, + actOnFocus: true, + enableLeftRight: true, + enableUpDown: true + }, tinyMCEPopup.dom); + }); +}); \ No newline at end of file diff --git a/src/Orchard.Web/Modules/TinyMce/Scripts/utils/validate.js b/src/Orchard.Web/Modules/TinyMce/Scripts/utils/validate.js index a6fcf9701..27cbfab81 100644 --- a/src/Orchard.Web/Modules/TinyMce/Scripts/utils/validate.js +++ b/src/Orchard.Web/Modules/TinyMce/Scripts/utils/validate.js @@ -32,7 +32,7 @@ var Validator = { }, isSize : function(s) { - return this.test(s, '^[0-9]+(%|in|cm|mm|em|ex|pt|pc|px)?$'); + return this.test(s, '^[0-9.]+(%|in|cm|mm|em|ex|pt|pc|px)?$'); }, isId : function(s) { @@ -96,8 +96,10 @@ var AutoValidator = { var i, nl, s = this.settings, c = 0; nl = this.tags(f, 'label'); - for (i=0; i Date: Mon, 9 May 2011 11:01:15 -0700 Subject: [PATCH 012/139] Making the rest of the copy on the admin dashboard localizable. work item: 17724 --HG-- branch : 1.x --- .../Core/Dashboard/Views/Admin/Index.cshtml | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/Orchard.Web/Core/Dashboard/Views/Admin/Index.cshtml b/src/Orchard.Web/Core/Dashboard/Views/Admin/Index.cshtml index da1561f46..23389b51b 100644 --- a/src/Orchard.Web/Core/Dashboard/Views/Admin/Index.cshtml +++ b/src/Orchard.Web/Core/Dashboard/Views/Admin/Index.cshtml @@ -1,32 +1,36 @@ @model dynamic - @{ Layout.Title = T("Welcome to Orchard").ToString(); }
-

Get up and running.

-

Start by exploring the menu on the left and familiarize yourself with Orchard. - As for the basics, we suggest @Html.ActionLink(T("changing the theme").ToString(), "Index", "Admin", new { area = "Orchard.Themes" }, null), @Html.ActionLink(T("adding some pages").ToString(), "Create", "Admin", new { area = "Contents", id = "Page" }, null), @Html.ActionLink(T("setup up a blog").ToString(), "Create", "BlogAdmin", new { area = "Orchard.Blogs" }, null), - and @Html.ActionLink(T("configuring basic settings").ToString(), "Index", "Admin", new { area = "Settings", groupInfoId = "Index" }, null).

+

@T("Get up and running")

+

@T(@"Start by exploring the menu on the left and familiarize yourself with Orchard. As for the basics, we suggest changing the theme, adding some pages, setup up a blog, and configuring basic settings.", + Url.Action("Index", "Admin", new { area = "Orchard.Themes" }), + Url.Action("Create", "Admin", new { area = "Contents", id = "Page" }), + Url.Action("Create", "BlogAdmin", new { area = "Orchard.Blogs" }), + Url.Action("Index", "Admin", new { area = "Settings", groupInfoId = "Index" }))

- -

Change the way your site works and looks with @Html.ActionLink(T("themes").ToString(), "Themes", "Gallery", new { area = "Orchard.Packaging" }, null) and @Html.ActionLink(T("modules").ToString(), "Modules", "Gallery", new { area = "Orchard.Packaging" }, null). There’s plenty to choose from in the Orchard Gallery. We’re always adding things, so be sure to check back often to see what’s new.

+ +

@T(@"Change the way your site works and looks with themes and modules. There’s plenty to choose from in the Orchard Gallery. We’re always adding things, so be sure to check back often to see what’s new.", + Url.Action("Themes", "Gallery", new { area = "Orchard.Packaging" }), + Url.Action("Modules", "Gallery", new { area = "Orchard.Packaging" })) +

-

Read the Docs.

-

Are you ready to go deeper and become an Orchard expert? Take a look at the Orchard Documentation Wiki to learn about how everything connects together and what makes Orchard tick.

+

@T("Read the Docs")

+

@T(@"Are you ready to go deeper and become an Orchard expert? Take a look at the Orchard Documentation Wiki to learn about how everything connects together and what makes Orchard tick.")

-

Make friends.

-

Find friends that share your interest of Orchard. There are a couple ways that you can discuss and get connected to the project including mailing lists, forums and IRC.

+

@T("Make friends")

+

@T(@"Find friends that share your interest of Orchard. There are a couple ways that you can discuss and get connected to the project including mailing lists, forums and IRC.")

-

Contribute back.

-

Help grow Orchard. We encourage contributions of all sorts, including code submissions, documentation, translations, feature recommendations, and more.Here are some ways to give back to the project.

+

@T("Contribute back")

+

@T(@"Help grow Orchard. We encourage contributions of all sorts, including code submissions, documentation, translations, feature recommendations, and more.Here are some ways to give back to the project.")

-

@T("Stay up to date.")

+

@T("Stay up to date")

From 03ddb80526e69a39106a7b91c8f43c19efeb89b9 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Mon, 9 May 2011 11:03:20 -0700 Subject: [PATCH 013/139] PERF: Cache result of parsing .csproj files --HG-- branch : 1.x extra : transplant_source : %02%96%9A%B7%91%05%E1%1EF%03%5D%5C%EB%A0%95Q%13%F6B%9C --- .../Loaders/DynamicExtensionLoaderTests.cs | 59 ++++++++++--------- .../Compilers/DefaultExtensionCompiler.cs | 4 +- .../Compilers/DefaultProjectFileParser.cs | 28 ++++++++- .../Compilers/IProjectFileParser.cs | 1 + .../Loaders/DynamicExtensionLoader.cs | 8 +-- 5 files changed, 62 insertions(+), 38 deletions(-) diff --git a/src/Orchard.Tests/Environment/Loaders/DynamicExtensionLoaderTests.cs b/src/Orchard.Tests/Environment/Loaders/DynamicExtensionLoaderTests.cs index 96db8bf53..16ea81a77 100644 --- a/src/Orchard.Tests/Environment/Loaders/DynamicExtensionLoaderTests.cs +++ b/src/Orchard.Tests/Environment/Loaders/DynamicExtensionLoaderTests.cs @@ -53,10 +53,10 @@ namespace Orchard.Tests.Environment.Loaders { DynamicExtensionLoaderAccessor extensionLoader = _container.Resolve(); StubFileSystem stubFileSystem = _container.Resolve(); - StubFileSystem.FileEntry fileEntry = stubFileSystem.CreateFileEntry("orchard.a.csjproj"); + StubFileSystem.FileEntry fileEntry = stubFileSystem.CreateFileEntry("orchard.a.csproj"); // Create duplicate source files (invalid situation in reality but easy enough to test) - _mockedStubProjectFileParser.Setup(stubProjectFileParser => stubProjectFileParser.Parse(It.IsAny())).Returns( + _mockedStubProjectFileParser.Setup(stubProjectFileParser => stubProjectFileParser.Parse(It.IsAny())).Returns( new ProjectFileDescriptor { SourceFilenames = new[] { fileName1, fileName2, fileName1 } }); // duplicate file IEnumerable dependencies = extensionLoader.GetDependenciesAccessor(fileEntry.Name); @@ -76,14 +76,15 @@ namespace Orchard.Tests.Environment.Loaders { DynamicExtensionLoaderAccessor extensionLoader = _container.Resolve(); StubFileSystem stubFileSystem = _container.Resolve(); - StubFileSystem.FileEntry fileEntry = stubFileSystem.CreateFileEntry("orchard.a.csjproj"); - StubFileSystem.FileEntry fileEntry2 = stubFileSystem.CreateFileEntry("orchard.b.csjproj"); - StubFileSystem.FileEntry fileEntry3 = stubFileSystem.CreateFileEntry("orchard.c.csjproj"); + StubFileSystem.FileEntry fileEntry = stubFileSystem.CreateFileEntry("orchard.a.csproj"); + StubFileSystem.FileEntry fileEntry2 = stubFileSystem.CreateFileEntry("orchard.b.csproj"); + StubFileSystem.FileEntry fileEntry3 = stubFileSystem.CreateFileEntry("orchard.c.csproj"); // Project a reference b and c which share a file in common // Result for project a - _mockedStubProjectFileParser.Setup(stubProjectFileParser => stubProjectFileParser.Parse(It.Is(stream => ((StubFileSystem.FileEntryReadStream)stream).FileEntry == fileEntry))) + _mockedStubProjectFileParser + .Setup(stubProjectFileParser => stubProjectFileParser.Parse(It.Is(virtualPath => virtualPath == "orchard.a.csproj"))) .Returns( new ProjectFileDescriptor { SourceFilenames = new[] { fileName1, fileName2 }, @@ -96,16 +97,16 @@ namespace Orchard.Tests.Environment.Loaders { }, new ReferenceDescriptor { ReferenceType = ReferenceType.Project, - SimpleName = Path.GetFileNameWithoutExtension(fileEntry2.Name), - FullName = Path.GetFileNameWithoutExtension(fileEntry2.Name), + SimpleName = Path.GetFileNameWithoutExtension(fileEntry3.Name), + FullName = Path.GetFileNameWithoutExtension(fileEntry3.Name), Path = fileEntry3.Name } } }); // Result for project b and c - _mockedStubProjectFileParser.Setup(stubProjectFileParser => stubProjectFileParser.Parse(It.Is(stream => - ((StubFileSystem.FileEntryReadStream)stream).FileEntry == fileEntry2 || ((StubFileSystem.FileEntryReadStream)stream).FileEntry == fileEntry3))) + _mockedStubProjectFileParser + .Setup(stubProjectFileParser => stubProjectFileParser.Parse(It.Is(virtualPath => (virtualPath == "~/orchard.b.csproj" || virtualPath == "~/orchard.c.csproj")))) .Returns( new ProjectFileDescriptor { SourceFilenames = new[] { commonFileName } @@ -116,14 +117,14 @@ namespace Orchard.Tests.Environment.Loaders { Assert.That(dependencies.Count(), Is.EqualTo(6), "6 results should mean no duplicates"); // Project files - Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileEntry.Name)), Is.Not.Null); - Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileEntry2.Name)), Is.Not.Null); - Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileEntry3.Name)), Is.Not.Null); + Assert.That(dependencies.FirstOrDefault(dep => dep.Contains(fileEntry.Name)), Is.Not.Null); + Assert.That(dependencies.FirstOrDefault(dep => dep.Contains(fileEntry2.Name)), Is.Not.Null); + Assert.That(dependencies.FirstOrDefault(dep => dep.Contains(fileEntry3.Name)), Is.Not.Null); // Individual source files - Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileName1)), Is.Not.Null); - Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileName2)), Is.Not.Null); - Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(commonFileName)), Is.Not.Null); + Assert.That(dependencies.FirstOrDefault(dep => dep.Contains(fileName1)), Is.Not.Null); + Assert.That(dependencies.FirstOrDefault(dep => dep.Contains(fileName2)), Is.Not.Null); + Assert.That(dependencies.FirstOrDefault(dep => dep.Contains(commonFileName)), Is.Not.Null); } [Test] @@ -135,8 +136,8 @@ namespace Orchard.Tests.Environment.Loaders { DynamicExtensionLoaderAccessor extensionLoader = _container.Resolve(); StubFileSystem stubFileSystem = _container.Resolve(); - StubFileSystem.FileEntry fileEntry = stubFileSystem.CreateFileEntry("orchard.a.csjproj"); - StubFileSystem.FileEntry fileEntry2 = stubFileSystem.CreateFileEntry("orchard.b.csjproj"); + StubFileSystem.FileEntry fileEntry = stubFileSystem.CreateFileEntry("orchard.a.csproj"); + StubFileSystem.FileEntry fileEntry2 = stubFileSystem.CreateFileEntry("orchard.b.csproj"); StubFileSystem.DirectoryEntry directoryEntry = stubFileSystem.CreateDirectoryEntry("bin"); StubFileSystem.FileEntry fileEntry3 = directoryEntry.CreateFile("orchard.b.dll"); @@ -144,7 +145,8 @@ namespace Orchard.Tests.Environment.Loaders { // Project a reference b and c which share a file in common // Result for project a - _mockedStubProjectFileParser.Setup(stubProjectFileParser => stubProjectFileParser.Parse(It.Is(stream => ((StubFileSystem.FileEntryReadStream)stream).FileEntry == fileEntry))) + _mockedStubProjectFileParser + .Setup(stubProjectFileParser => stubProjectFileParser.Parse(It.Is(virtualPath => virtualPath == "orchard.a.csproj"))) .Returns( new ProjectFileDescriptor { SourceFilenames = new[] { fileName1, fileName2 }, @@ -159,14 +161,15 @@ namespace Orchard.Tests.Environment.Loaders { }); // Result for project b and c - _mockedStubProjectFileParser.Setup(stubProjectFileParser => stubProjectFileParser.Parse(It.Is(stream => - ((StubFileSystem.FileEntryReadStream)stream).FileEntry == fileEntry2))) + _mockedStubProjectFileParser + .Setup(stubProjectFileParser => stubProjectFileParser.Parse(It.Is(virtualPath => virtualPath == "~/orchard.b.csproj"))) .Returns( new ProjectFileDescriptor { SourceFilenames = new[] { commonFileName } }); - _mockedDependenciesFolder.Setup(dependenciesFolder => dependenciesFolder.GetDescriptor(It.Is(moduleName => moduleName == Path.GetDirectoryName(fileEntry2.Name)))) + _mockedDependenciesFolder + .Setup(dependenciesFolder => dependenciesFolder.GetDescriptor(It.Is(moduleName => moduleName == Path.GetDirectoryName(fileEntry2.Name)))) .Returns( new DependencyDescriptor { VirtualPath = Path.Combine(directoryEntry.Name, fileEntry3.Name) @@ -177,14 +180,14 @@ namespace Orchard.Tests.Environment.Loaders { Assert.That(dependencies.Count(), Is.EqualTo(6), "6 results should mean no duplicates"); // Project files - Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileEntry.Name)), Is.Not.Null); - Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileEntry2.Name)), Is.Not.Null); - Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(Path.Combine(directoryEntry.Name, fileEntry3.Name))), Is.Not.Null); + Assert.That(dependencies.FirstOrDefault(dep => dep.Contains(fileEntry.Name)), Is.Not.Null); + Assert.That(dependencies.FirstOrDefault(dep => dep.Contains(fileEntry2.Name)), Is.Not.Null); + Assert.That(dependencies.FirstOrDefault(dep => dep.Contains(Path.Combine(directoryEntry.Name, fileEntry3.Name))), Is.Not.Null); // Individual source files - Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileName1)), Is.Not.Null); - Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileName2)), Is.Not.Null); - Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(commonFileName)), Is.Not.Null); + Assert.That(dependencies.FirstOrDefault(dep => dep.Contains(fileName1)), Is.Not.Null); + Assert.That(dependencies.FirstOrDefault(dep => dep.Contains(fileName2)), Is.Not.Null); + Assert.That(dependencies.FirstOrDefault(dep => dep.Contains(commonFileName)), Is.Not.Null); } internal class DynamicExtensionLoaderAccessor : DynamicExtensionLoader { diff --git a/src/Orchard/Environment/Extensions/Compilers/DefaultExtensionCompiler.cs b/src/Orchard/Environment/Extensions/Compilers/DefaultExtensionCompiler.cs index fcf88493e..7dae2355c 100644 --- a/src/Orchard/Environment/Extensions/Compilers/DefaultExtensionCompiler.cs +++ b/src/Orchard/Environment/Extensions/Compilers/DefaultExtensionCompiler.cs @@ -48,8 +48,7 @@ namespace Orchard.Environment.Extensions.Compilers { return; try { - using (var stream = _virtualPathProvider.OpenFile(context.VirtualPath)) { - var projectFileDescriptor = _projectFileParser.Parse(stream); + var projectFileDescriptor = _projectFileParser.Parse(context.VirtualPath); // Add source files var directory = _virtualPathProvider.GetDirectoryName(context.VirtualPath); @@ -100,7 +99,6 @@ namespace Orchard.Environment.Extensions.Compilers { } } } - } catch (Exception e) { //Note: we need to embed the "e.Message" in the exception text because // ASP.NET build manager "swallows" inner exceptions from this method. diff --git a/src/Orchard/Environment/Extensions/Compilers/DefaultProjectFileParser.cs b/src/Orchard/Environment/Extensions/Compilers/DefaultProjectFileParser.cs index b4048bfd5..1dfbf8e7b 100644 --- a/src/Orchard/Environment/Extensions/Compilers/DefaultProjectFileParser.cs +++ b/src/Orchard/Environment/Extensions/Compilers/DefaultProjectFileParser.cs @@ -3,12 +3,38 @@ using System.IO; using System.Linq; using System.Xml; using System.Xml.Linq; +using Orchard.Caching; +using Orchard.FileSystems.WebSite; namespace Orchard.Environment.Extensions.Compilers { public class DefaultProjectFileParser : IProjectFileParser { + private readonly IWebSiteFolder _webSiteFolder; + private readonly ICacheManager _cacheManager; + + public DefaultProjectFileParser(IWebSiteFolder webSiteFolder, ICacheManager cacheManager) { + _webSiteFolder = webSiteFolder; + _cacheManager = cacheManager; + } + + public ProjectFileDescriptor Parse(string virtualPath) { + return _cacheManager.Get(virtualPath, + ctx => { + ctx.Monitor(_webSiteFolder.WhenPathChanges(virtualPath)); + string content = _webSiteFolder.ReadFile(virtualPath); + using (var reader = new StringReader(content)) { + return Parse(reader); + } + }); + } public ProjectFileDescriptor Parse(Stream stream) { - var document = XDocument.Load(XmlReader.Create(stream)); + using (var reader = new StreamReader(stream)) { + return Parse(reader); + } + } + + public ProjectFileDescriptor Parse(TextReader reader) { + var document = XDocument.Load(XmlReader.Create(reader)); return new ProjectFileDescriptor { AssemblyName = GetAssemblyName(document), SourceFilenames = GetSourceFilenames(document).ToArray(), diff --git a/src/Orchard/Environment/Extensions/Compilers/IProjectFileParser.cs b/src/Orchard/Environment/Extensions/Compilers/IProjectFileParser.cs index 33ea9f1aa..b78589ce3 100644 --- a/src/Orchard/Environment/Extensions/Compilers/IProjectFileParser.cs +++ b/src/Orchard/Environment/Extensions/Compilers/IProjectFileParser.cs @@ -2,6 +2,7 @@ namespace Orchard.Environment.Extensions.Compilers { public interface IProjectFileParser { + ProjectFileDescriptor Parse(string virtualPath); ProjectFileDescriptor Parse(Stream stream); } } \ No newline at end of file diff --git a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs index f63ac0331..465a21f45 100644 --- a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs @@ -101,8 +101,7 @@ namespace Orchard.Environment.Extensions.Loaders { if (projectPath == null) return Enumerable.Empty(); - using (var stream = _virtualPathProvider.OpenFile(projectPath)) { - var projectFile = _projectFileParser.Parse(stream); + var projectFile = _projectFileParser.Parse(projectPath); return projectFile.References.Select(r => new ExtensionReferenceProbeEntry { Descriptor = descriptor, @@ -111,7 +110,6 @@ namespace Orchard.Environment.Extensions.Loaders { VirtualPath = _virtualPathProvider.GetProjectReferenceVirtualPath(projectPath, r.SimpleName, r.Path) }); } - } public override void ReferenceActivated(ExtensionLoadingContext context, ExtensionReferenceProbeEntry referenceEntry) { //Note: This is the same implementation as "PrecompiledExtensionLoader" @@ -193,8 +191,7 @@ namespace Orchard.Environment.Extensions.Loaders { private void AddDependencies(string projectPath, HashSet currentSet) { string basePath = _virtualPathProvider.GetDirectoryName(projectPath); - using (var stream = _virtualPathProvider.OpenFile(projectPath)) { - ProjectFileDescriptor projectFile = _projectFileParser.Parse(stream); + ProjectFileDescriptor projectFile = _projectFileParser.Parse(projectPath); // Add source files currentSet.UnionWith(projectFile.SourceFilenames.Select(f => _virtualPathProvider.Combine(basePath, f))); @@ -236,7 +233,6 @@ namespace Orchard.Environment.Extensions.Loaders { } } } - } private string GetProjectPath(ExtensionDescriptor descriptor) { string projectPath = _virtualPathProvider.Combine(descriptor.Location, descriptor.Id, From 7ba079f40c4cb7aa1f945f0fe2e1fe4a6dc68755 Mon Sep 17 00:00:00 2001 From: Suha Can Date: Mon, 9 May 2011 13:03:57 -0700 Subject: [PATCH 014/139] #17790: Recipe switch for CLI setup is case sensitive --HG-- branch : 1.x --- src/Orchard.Web/Modules/Orchard.Setup/Services/SetupService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupService.cs b/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupService.cs index 26f11e579..628ce21b8 100644 --- a/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupService.cs +++ b/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupService.cs @@ -200,7 +200,7 @@ namespace Orchard.Setup.Services { cultureManager.AddCulture("en-US"); var recipeManager = environment.Resolve(); - executionId = recipeManager.Execute(Recipes().Where(r => r.Name == context.Recipe).FirstOrDefault()); + executionId = recipeManager.Execute(Recipes().Where(r => r.Name.Equals(context.Recipe, StringComparison.OrdinalIgnoreCase)).FirstOrDefault()); //null check: temporary fix for running setup in command line if (HttpContext.Current != null) { From 72b34307be05d7dded39f631c98486685e98b4fe Mon Sep 17 00:00:00 2001 From: Suha Can Date: Mon, 9 May 2011 13:10:51 -0700 Subject: [PATCH 015/139] #17782: Reports Module - Inconsistent Permissions Contributed by ldhertert. --HG-- branch : 1.x --- src/Orchard.Web/Core/Reports/AdminMenu.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Orchard.Web/Core/Reports/AdminMenu.cs b/src/Orchard.Web/Core/Reports/AdminMenu.cs index b8c1a0e76..1a48c91f5 100644 --- a/src/Orchard.Web/Core/Reports/AdminMenu.cs +++ b/src/Orchard.Web/Core/Reports/AdminMenu.cs @@ -11,7 +11,7 @@ namespace Orchard.Core.Reports { builder.AddImageSet("reports") .Add(T("Reports"), "12", menu => menu.Add(T("View"), "0", item => item.Action("Index", "Admin", new { area = "Reports" }) - .Permission(StandardPermissions.AccessAdminPanel))); + .Permission(StandardPermissions.SiteOwner))); } } } \ No newline at end of file From a976b38388fe48e293a9102d94c72e01ae4c1b24 Mon Sep 17 00:00:00 2001 From: Suha Can Date: Mon, 9 May 2011 13:32:25 -0700 Subject: [PATCH 016/139] #17791: List Admin UI uses Content Type name instead of DisplayName in "Add New" button --HG-- branch : 1.x --- .../Modules/Orchard.Lists/Controllers/AdminController.cs | 7 +++++++ .../Modules/Orchard.Lists/Views/Admin/List.cshtml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Orchard.Web/Modules/Orchard.Lists/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.Lists/Controllers/AdminController.cs index f8f607cff..6d63d7c22 100644 --- a/src/Orchard.Web/Modules/Orchard.Lists/Controllers/AdminController.cs +++ b/src/Orchard.Web/Modules/Orchard.Lists/Controllers/AdminController.cs @@ -92,6 +92,12 @@ namespace Lists.Controllers { var list = Shape.List(); list.AddRange(pageOfContentItems.Select(ci => _contentManager.BuildDisplay(ci, "SummaryAdmin"))); + var containerItemContentDisplayName = String.Empty; + if (hasRestriction) + containerItemContentDisplayName = _contentDefinitionManager.GetTypeDefinition(restrictedContentType).DisplayName; + else if (!string.IsNullOrEmpty(model.FilterByContentType)) + containerItemContentDisplayName = _contentDefinitionManager.GetTypeDefinition(model.FilterByContentType).DisplayName; + dynamic viewModel = Shape.ViewModel() .ContentItems(list) .Pager(pagerShape) @@ -100,6 +106,7 @@ namespace Lists.Controllers { .ContainerDisplayName(model.ContainerDisplayName) .HasRestriction(hasRestriction) .ContainerContentType(container.ContentType) + .ContainerItemContentDisplayName(containerItemContentDisplayName) .ContainerItemContentType(hasRestriction ? restrictedContentType : (model.FilterByContentType ?? "")) .OtherLists(_contentManager.Query(VersionOptions.Latest).List() .Select(part => part.ContentItem) diff --git a/src/Orchard.Web/Modules/Orchard.Lists/Views/Admin/List.cshtml b/src/Orchard.Web/Modules/Orchard.Lists/Views/Admin/List.cshtml index 6b915120f..387b4f381 100644 --- a/src/Orchard.Web/Modules/Orchard.Lists/Views/Admin/List.cshtml +++ b/src/Orchard.Web/Modules/Orchard.Lists/Views/Admin/List.cshtml @@ -5,7 +5,7 @@ int containerId = ((int?)Model.ContainerId).GetValueOrDefault(); - string createLinkText = string.IsNullOrEmpty(Model.ContainerItemContentType) ? T("Create New Content").ToString() : T("Create New {0}", Model.ContainerItemContentType).ToString(); + string createLinkText = string.IsNullOrEmpty(Model.ContainerItemContentDisplayName) ? T("Create New Content").ToString() : T("Create New {0}", Model.ContainerItemContentDisplayName).ToString(); Layout.Title = T("Manage {0}", Model.ContainerDisplayName); From 52d5a2695cbb8f9191b9e0ca72853762ee66f886 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Mon, 9 May 2011 13:37:25 -0700 Subject: [PATCH 017/139] #17765: Fixing security notification in Shape Tracing Work Items: 17765 --HG-- branch : 1.x --- .../Orchard.DesignerTools/Controllers/AlternateController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Orchard.Web/Modules/Orchard.DesignerTools/Controllers/AlternateController.cs b/src/Orchard.Web/Modules/Orchard.DesignerTools/Controllers/AlternateController.cs index 81a949b27..8949d7217 100644 --- a/src/Orchard.Web/Modules/Orchard.DesignerTools/Controllers/AlternateController.cs +++ b/src/Orchard.Web/Modules/Orchard.DesignerTools/Controllers/AlternateController.cs @@ -23,7 +23,7 @@ namespace Orchard.DesignerTools.Controllers public Localizer T { get; set; } public ActionResult Create(string template, string alternate, string returnUrl) { - if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not authorized to create templates")) && !Request.IsLocal) + if (!Request.IsLocal && !Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not authorized to create templates"))) return new HttpUnauthorizedResult(); alternate = alternate.Replace("__", "-").Replace("_", "."); From 342fc55a228e94700b25414a502cd5394b9efbc8 Mon Sep 17 00:00:00 2001 From: Andre Rodrigues Date: Mon, 9 May 2011 13:51:59 -0700 Subject: [PATCH 018/139] #17774: Rolling files when locked. Removing unecessary custom XMLConfigurators, reconfiguring and monitoring files in the logger factory only under full trust. --HG-- branch : 1.x --- .../Logging/OrchardFileAppenderTests.cs | 55 ++ .../Orchard.Framework.Tests.csproj | 1 + src/Orchard.Web/Config/log4net.config | 6 +- src/Orchard/Logging/OrchardFileAppender.cs | 62 +- src/Orchard/Logging/OrchardLog4netFactory.cs | 42 +- src/Orchard/Logging/OrchardXmlConfigurator.cs | 308 ------- .../OrchardXmlHierarchyConfigurator.cs | 858 ------------------ src/Orchard/Orchard.Framework.csproj | 2 - 8 files changed, 109 insertions(+), 1225 deletions(-) create mode 100644 src/Orchard.Tests/Logging/OrchardFileAppenderTests.cs delete mode 100644 src/Orchard/Logging/OrchardXmlConfigurator.cs delete mode 100644 src/Orchard/Logging/OrchardXmlHierarchyConfigurator.cs diff --git a/src/Orchard.Tests/Logging/OrchardFileAppenderTests.cs b/src/Orchard.Tests/Logging/OrchardFileAppenderTests.cs new file mode 100644 index 000000000..1e429e208 --- /dev/null +++ b/src/Orchard.Tests/Logging/OrchardFileAppenderTests.cs @@ -0,0 +1,55 @@ +using System; +using Moq; +using Moq.Protected; +using NUnit.Framework; +using Orchard.Logging; + +namespace Orchard.Tests.Logging { + [TestFixture] + public class OrchardFileAppenderTests { + [Test] + public void AddSuffixTest() { + const string filename = "Orchard-debug"; + const string filenameAlternative1 = "Orchard-debug-1"; + const string filenameAlternative2 = "Orchard-debug-2"; + + string filenameUsed = string.Empty; + + Mock firstOrchardFileAppenderMock = new Mock(); + StubOrchardFileAppender firstOrchardFileAppender = firstOrchardFileAppenderMock.Object; + + // Regular filename should be used if nothing is locked + firstOrchardFileAppenderMock.Protected().Setup("BaseOpenFile", ItExpr.Is(file => file.Equals(filename)), ItExpr.IsAny()).Callback( + (file, append) => filenameUsed = file); + + firstOrchardFileAppender.OpenFileStub(filename, true); + + Assert.That(filenameUsed, Is.EqualTo(filename)); + + // Alternative 1 should be used if regular filename is locked + + firstOrchardFileAppenderMock.Protected().Setup("BaseOpenFile", ItExpr.Is(file => file.Equals(filename)), ItExpr.IsAny()).Throws(new Exception()); + firstOrchardFileAppenderMock.Protected().Setup("BaseOpenFile", ItExpr.Is(file => file.Equals(filenameAlternative1)), ItExpr.IsAny()).Callback( + (file, append) => filenameUsed = file); + + firstOrchardFileAppender.OpenFileStub(filename, true); + + Assert.That(filenameUsed, Is.EqualTo(filenameAlternative1)); + + // make alternative 1 also throw exception to make sure alternative 2 is used. + firstOrchardFileAppenderMock.Protected().Setup("BaseOpenFile", ItExpr.Is(file => file.Equals(filenameAlternative1)), ItExpr.IsAny()).Throws(new Exception()); + firstOrchardFileAppenderMock.Protected().Setup("BaseOpenFile", ItExpr.Is(file => file.Equals(filenameAlternative2)), ItExpr.IsAny()).Callback( + (file, append) => filenameUsed = file); + + firstOrchardFileAppender.OpenFileStub(filename, true); + + Assert.That(filenameUsed, Is.EqualTo(filenameAlternative2)); + } + + public class StubOrchardFileAppender : OrchardFileAppender { + public void OpenFileStub(string fileName, bool append) { + base.OpenFile(fileName, append); + } + } + } +} diff --git a/src/Orchard.Tests/Orchard.Framework.Tests.csproj b/src/Orchard.Tests/Orchard.Framework.Tests.csproj index 4027f9a8b..5acf3f855 100644 --- a/src/Orchard.Tests/Orchard.Framework.Tests.csproj +++ b/src/Orchard.Tests/Orchard.Framework.Tests.csproj @@ -255,6 +255,7 @@ + diff --git a/src/Orchard.Web/Config/log4net.config b/src/Orchard.Web/Config/log4net.config index 29afd8b06..8b57d7da7 100644 --- a/src/Orchard.Web/Config/log4net.config +++ b/src/Orchard.Web/Config/log4net.config @@ -53,8 +53,7 @@ - - + @@ -72,8 +71,7 @@ - - + diff --git a/src/Orchard/Logging/OrchardFileAppender.cs b/src/Orchard/Logging/OrchardFileAppender.cs index eb1429fa4..ddde96623 100644 --- a/src/Orchard/Logging/OrchardFileAppender.cs +++ b/src/Orchard/Logging/OrchardFileAppender.cs @@ -1,27 +1,51 @@ -using log4net.Appender; -using log4net.Util; +using System.Collections.Generic; +using log4net.Appender; namespace Orchard.Logging { public class OrchardFileAppender : RollingFileAppender { - public enum RollingStyleFrequencyMode { - Once, - Continuous + /// + /// Dictionary of already known suffixes (based on previous attempts) for a given filename. + /// + private static readonly Dictionary _suffixes = new Dictionary(); + + private const int Retries = 50; + + /// + /// Opens the log file adding an incremental suffix to the filename if required due to an openning failure (usually, locking). + /// + /// The filename as specified in the configuration file. + /// Boolean flag indicating weather the log file should be appended if it already exists. + protected override void OpenFile(string fileName, bool append) { + lock (this) { + bool fileOpened = false; + string currentFilename = fileName; + + if (!_suffixes.ContainsKey(fileName)) { + _suffixes[fileName] = 0; + } + + int newSuffix = _suffixes[fileName]; + + for (int i = 1; !fileOpened && i <= Retries; i++) { + try { + if (newSuffix > 0) { + currentFilename = string.Format("{0}-{1}", fileName, newSuffix); + } + + BaseOpenFile(currentFilename, append); + + fileOpened = true; + } catch { + newSuffix = _suffixes[fileName] + i; + } + } + + _suffixes[fileName] = newSuffix; + } } - public RollingStyleFrequencyMode RollingStyleFrequency { get; set; } - - public bool RollSize { - get { return (RollingStyle == RollingMode.Composite || RollingStyle == RollingMode.Size); } - } - - protected override void AdjustFileBeforeAppend() { - if (RollingStyle == RollingMode.Size || - RollingStyleFrequency == RollingStyleFrequencyMode.Continuous) { - base.AdjustFileBeforeAppend(); - } - else if (RollSize && ((File != null) && (((CountingQuietTextWriter) QuietWriter).Count >= MaxFileSize))) { - RollOverSize(); - } + protected virtual void BaseOpenFile(string fileName, bool append) { + base.OpenFile(fileName, append); } } } diff --git a/src/Orchard/Logging/OrchardLog4netFactory.cs b/src/Orchard/Logging/OrchardLog4netFactory.cs index 4a5dd45d4..043651b00 100644 --- a/src/Orchard/Logging/OrchardLog4netFactory.cs +++ b/src/Orchard/Logging/OrchardLog4netFactory.cs @@ -1,48 +1,22 @@ using System; using System.Configuration; -using System.IO; -using System.Web; -using System.Web.Hosting; - using Castle.Core.Logging; - using log4net; +using log4net.Config; +using Orchard.Environment; namespace Orchard.Logging { public class OrchardLog4netFactory : AbstractLoggerFactory { - public OrchardLog4netFactory() - : this(ConfigurationManager.AppSettings["log4net.Config"]) { - } + public OrchardLog4netFactory(IHostEnvironment hostEnvironment) + : this(ConfigurationManager.AppSettings["log4net.Config"], hostEnvironment) { } - public OrchardLog4netFactory(String configFilename) { - if (!String.IsNullOrWhiteSpace(configFilename)) { - var mappedConfigFilename = configFilename; - - if (HostingEnvironment.IsHosted) { - if (!VirtualPathUtility.IsAppRelative(mappedConfigFilename)) { - if (!mappedConfigFilename.StartsWith("/")) { - mappedConfigFilename = "~/" + mappedConfigFilename; - } - else { - mappedConfigFilename = "~" + mappedConfigFilename; - } - } - - mappedConfigFilename = HostingEnvironment.MapPath(mappedConfigFilename); - } - - OrchardXmlConfigurator.Configure(mappedConfigFilename); + public OrchardLog4netFactory(string configFilename, IHostEnvironment hostEnvironment) { + if (hostEnvironment.IsFullTrust) { + // Only monitor configuration file in full trust + XmlConfigurator.ConfigureAndWatch(GetConfigFile(configFilename)); } } - /// - /// Configures log4net with a stream containing XML. - /// - /// - public OrchardLog4netFactory(Stream config) { - OrchardXmlConfigurator.Configure(config); - } - public override Castle.Core.Logging.ILogger Create(string name, LoggerLevel level) { throw new NotSupportedException("Logger levels cannot be set at runtime. Please review your configuration file."); } diff --git a/src/Orchard/Logging/OrchardXmlConfigurator.cs b/src/Orchard/Logging/OrchardXmlConfigurator.cs deleted file mode 100644 index 965f389f9..000000000 --- a/src/Orchard/Logging/OrchardXmlConfigurator.cs +++ /dev/null @@ -1,308 +0,0 @@ -using System; -using System.IO; -using System.Reflection; -using System.Xml; - -using log4net; -using log4net.Repository; -using log4net.Repository.Hierarchy; -using log4net.Util; - -namespace Orchard.Logging { - public class OrchardXmlConfigurator { - - /// - /// Private constructor - /// - private OrchardXmlConfigurator() { - } - - /// - /// Configures log4net using the specified configuration file. - /// - /// The name of the XML file to load the configuration from. - /// - /// - /// The configuration file must be valid XML. It must contain - /// at least one element called log4net that holds - /// the log4net configuration data. - /// - /// - /// The log4net configuration file can possible be specified in the application's - /// configuration file (either MyAppName.exe.config for a - /// normal application on Web.config for an ASP.NET application). - /// - /// - /// The first element matching <configuration> will be read as the - /// configuration. If this file is also a .NET .config file then you must specify - /// a configuration section for the log4net element otherwise .NET will - /// complain. Set the type for the section handler to , for example: - /// - /// - ///
- /// - /// - /// - /// - /// The following example configures log4net using a configuration file, of which the - /// location is stored in the application's configuration file : - /// - /// - /// using log4net.Config; - /// using System.IO; - /// using System.Configuration; - /// - /// ... - /// - /// XmlConfigurator.Configure(ConfigurationSettings.AppSettings["log4net-config-file"]); - /// - /// - /// In the .config file, the path to the log4net can be specified like this : - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static void Configure(string configFilename) { - Configure(LogManager.GetRepository(Assembly.GetCallingAssembly()), configFilename); - } - - /// - /// Configures log4net using the specified configuration data stream. - /// - /// A stream to load the XML configuration from. - /// - /// - /// The configuration data must be valid XML. It must contain - /// at least one element called log4net that holds - /// the log4net configuration data. - /// - /// - /// Note that this method will NOT close the stream parameter. - /// - /// - public static void Configure(Stream configStream) { - Configure(LogManager.GetRepository(Assembly.GetCallingAssembly()), configStream); - } - - /// - /// Configures the using the specified configuration - /// file. - /// - /// The repository to configure. - /// The name of the XML file to load the configuration from. - /// - /// - /// The configuration file must be valid XML. It must contain - /// at least one element called log4net that holds - /// the configuration data. - /// - /// - /// The log4net configuration file can possible be specified in the application's - /// configuration file (either MyAppName.exe.config for a - /// normal application on Web.config for an ASP.NET application). - /// - /// - /// The first element matching <configuration> will be read as the - /// configuration. If this file is also a .NET .config file then you must specify - /// a configuration section for the log4net element otherwise .NET will - /// complain. Set the type for the section handler to , for example: - /// - /// - ///
- /// - /// - /// - /// - /// The following example configures log4net using a configuration file, of which the - /// location is stored in the application's configuration file : - /// - /// - /// using log4net.Config; - /// using System.IO; - /// using System.Configuration; - /// - /// ... - /// - /// XmlConfigurator.Configure(ConfigurationSettings.AppSettings["log4net-config-file"]); - /// - /// - /// In the .config file, the path to the log4net can be specified like this : - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static void Configure(ILoggerRepository repository, string configFilename) { - LogLog.Debug("XmlConfigurator: configuring repository [" + repository.Name + "] using file [" + configFilename + "]"); - - if (String.IsNullOrWhiteSpace(configFilename)) { - LogLog.Error("XmlConfigurator: Configure called with null 'configFilename' parameter"); - } - else { - // Have to use File.Exists() rather than configFile.Exists() - // because configFile.Exists() caches the value, not what we want. - if (File.Exists(configFilename)) { - // Open the file for reading - FileStream fs = null; - - // Try hard to open the file - for (var retry = 5; --retry >= 0; ) { - try { - fs = new FileStream(configFilename, FileMode.Open, FileAccess.Read, FileShare.Read); - break; - } - catch (IOException ex) { - if (retry == 0) { - LogLog.Error("XmlConfigurator: Failed to open XML config file [" + configFilename + "]", ex); - - // The stream cannot be valid - fs = null; - } - - System.Threading.Thread.Sleep(250); - } - } - - if (fs != null) { - try { - // Load the configuration from the stream - Configure(repository, fs); - } - finally { - // Force the file closed whatever happens - fs.Close(); - } - } - } - else { - LogLog.Debug("XmlConfigurator: config file [" + configFilename + "] not found. Configuration unchanged."); - } - } - } - - /// - /// Configures the using the specified configuration - /// file. - /// - /// The repository to configure. - /// The stream to load the XML configuration from. - /// - /// - /// The configuration data must be valid XML. It must contain - /// at least one element called log4net that holds - /// the configuration data. - /// - /// - /// Note that this method will NOT close the stream parameter. - /// - /// - public static void Configure(ILoggerRepository repository, Stream configStream) { - LogLog.Debug("XmlConfigurator: configuring repository [" + repository.Name + "] using stream"); - - if (configStream == null) { - LogLog.Error("XmlConfigurator: Configure called with null 'configStream' parameter"); - } - else { - // Load the config file into a document - var doc = new XmlDocument(); - try { - // Create a text reader for the file stream - var xmlReader = new XmlTextReader(configStream) { DtdProcessing = DtdProcessing.Parse }; - - // Specify that the reader should not perform validation - var settings = new XmlReaderSettings { ValidationType = ValidationType.None }; - - // load the data into the document - doc.Load(xmlReader); - } - catch (Exception ex) { - LogLog.Error("XmlConfigurator: Error while loading XML configuration", ex); - - // The document is invalid - doc = null; - } - - if (doc != null) { - LogLog.Debug("XmlConfigurator: loading XML configuration"); - - // Configure using the 'log4net' element - var configNodeList = doc.GetElementsByTagName("log4net"); - if (configNodeList.Count == 0) { - LogLog.Debug("XmlConfigurator: XML configuration does not contain a element. Configuration Aborted."); - } - else if (configNodeList.Count > 1) { - LogLog.Error("XmlConfigurator: XML configuration contains [" + configNodeList.Count + "] elements. Only one is allowed. Configuration Aborted."); - } - else { - ConfigureFromXml(repository, configNodeList[0] as XmlElement); - } - } - } - } - - /// - /// Configures the specified repository using a log4net element. - /// - /// The hierarchy to configure. - /// The element to parse. - /// - /// - /// Loads the log4net configuration from the XML element - /// supplied as . - /// - /// - /// This method is ultimately called by one of the Configure methods - /// to load the configuration from an . - /// - /// - private static void ConfigureFromXml(ILoggerRepository repository, XmlElement element) { - if (element == null) { - LogLog.Error("XmlConfigurator: ConfigureFromXml called with null 'element' parameter"); - } - else if (repository == null) { - LogLog.Error("XmlConfigurator: ConfigureFromXml called with null 'repository' parameter"); - } - else { - LogLog.Debug("XmlConfigurator: Configuring Repository [" + repository.Name + "]"); - - // - // Since we're not reinventing the whole Hierarchy class from log4net to add out optimizations to XmlRepositoryConfigurator - // we've to check the neccessary casts here to be able to complete the configuration. - // - - // Needed to fire configuration changed event - var repositorySkeleton = repository as LoggerRepositorySkeleton; - - // Needed to XmlHierarchyConfigurator - var hierarchy = repository as Hierarchy; - - if (repositorySkeleton == null || hierarchy == null) { - LogLog.Warn("XmlConfigurator: Repository [" + repository + "] does not support the XmlConfigurator"); - } - else { - var configurator = new OrchardXmlHierarchyConfigurator(hierarchy); - - // Copy the xml data into the root of a new document - // this isolates the xml config data from the rest of - // the document - var newDoc = new XmlDocument(); - var newElement = (XmlElement)newDoc.AppendChild(newDoc.ImportNode(element, true)); - - // Pass the configurator the config element - configurator.Configure(newElement); - - repositorySkeleton.RaiseConfigurationChanged(EventArgs.Empty); - } - } - } - } -} diff --git a/src/Orchard/Logging/OrchardXmlHierarchyConfigurator.cs b/src/Orchard/Logging/OrchardXmlHierarchyConfigurator.cs deleted file mode 100644 index 8b4370432..000000000 --- a/src/Orchard/Logging/OrchardXmlHierarchyConfigurator.cs +++ /dev/null @@ -1,858 +0,0 @@ -using System; -using System.Collections; -using System.Globalization; -using System.Linq; -using System.Reflection; -using System.Xml; -using log4net.Appender; -using log4net.Core; -using log4net.ObjectRenderer; -using log4net.Repository.Hierarchy; -using log4net.Util; - -namespace Orchard.Logging { - /// - /// Initializes the log4net environment using an XML DOM. - /// - /// - /// - /// Configures a using an XML DOM. - /// - /// - /// Nicko Cadell - /// Gert Driesen - public class OrchardXmlHierarchyConfigurator { - private enum ConfigUpdateMode { - Merge, - Overwrite - } - - // String constants used while parsing the XML data - private const string ConfigurationTag = "log4net"; - private const string RendererTag = "renderer"; - private const string AppenderTag = "appender"; - private const string AppenderRefTag = "appender-ref"; - private const string ParamTag = "param"; - - // TODO: Deprecate use of category tags - private const string CategoryTag = "category"; - // TODO: Deprecate use of priority tag - private const string PriorityTag = "priority"; - - private const string LoggerTag = "logger"; - private const string NameAttr = "name"; - private const string TypeAttr = "type"; - private const string ValueAttr = "value"; - private const string RootTag = "root"; - private const string LevelTag = "level"; - private const string RefAttr = "ref"; - private const string AdditivityAttr = "additivity"; - private const string ThresholdAttr = "threshold"; - private const string ConfigDebugAttr = "configDebug"; - private const string InternalDebugAttr = "debug"; - private const string ConfigUpdateModeAttr = "update"; - private const string RenderingTypeAttr = "renderingClass"; - private const string RenderedTypeAttr = "renderedClass"; - - // flag used on the level element - private const string Inherited = "inherited"; - - /// - /// key: appenderName, value: appender. - /// - private Hashtable _appenderBag; - - /// - /// The Hierarchy being configured. - /// - private readonly Hierarchy _hierarchy; - - /// - /// The snapshot of the environment variables at configuration time, or null if an error has occured during querying them. - /// - private IDictionary _environmentVariables; - - /// - /// Construct the configurator for a hierarchy - /// - /// The hierarchy to build. - /// - /// - /// Initializes a new instance of the class - /// with the specified . - /// - /// - public OrchardXmlHierarchyConfigurator(Hierarchy hierarchy) { - _hierarchy = hierarchy; - _appenderBag = new Hashtable(); - } - - /// - /// Configure the hierarchy by parsing a DOM tree of XML elements. - /// - /// The root element to parse. - /// - /// - /// Configure the hierarchy by parsing a DOM tree of XML elements. - /// - /// - public void Configure(XmlElement element) { - if (element == null || _hierarchy == null) { - return; - } - - var rootElementName = element.LocalName; - - if (rootElementName != ConfigurationTag) { - LogLog.Error("XmlHierarchyConfigurator: Xml element is - not a <" + ConfigurationTag + "> element."); - return; - } - - if (!LogLog.InternalDebugging) { - // Look for a debug attribute to enable internal debug - var debugAttribute = element.GetAttribute(InternalDebugAttr); - LogLog.Debug("XmlHierarchyConfigurator: " + InternalDebugAttr + " attribute [" + debugAttribute + "]."); - - if (debugAttribute.Length > 0 && debugAttribute != "null") { - LogLog.InternalDebugging = OptionConverter.ToBoolean(debugAttribute, true); - } - else { - LogLog.Debug("XmlHierarchyConfigurator: Ignoring " + InternalDebugAttr + " attribute."); - } - - var confDebug = element.GetAttribute(ConfigDebugAttr); - if (confDebug.Length > 0 && confDebug != "null") { - LogLog.Warn("XmlHierarchyConfigurator: The \"" + ConfigDebugAttr + "\" attribute is deprecated."); - LogLog.Warn("XmlHierarchyConfigurator: Use the \"" + InternalDebugAttr + "\" attribute instead."); - LogLog.InternalDebugging = OptionConverter.ToBoolean(confDebug, true); - } - } - - // Default mode is merge - var configUpdateMode = ConfigUpdateMode.Merge; - - // Look for the config update attribute - var configUpdateModeAttribute = element.GetAttribute(ConfigUpdateModeAttr); - if (!String.IsNullOrEmpty(configUpdateModeAttribute)) { - // Parse the attribute - try { - configUpdateMode = (ConfigUpdateMode)OptionConverter.ConvertStringTo(typeof(ConfigUpdateMode), configUpdateModeAttribute); - } - catch { - LogLog.Error("XmlHierarchyConfigurator: Invalid " + ConfigUpdateModeAttr + " attribute value [" + configUpdateModeAttribute + "]"); - } - } - - // IMPL: The IFormatProvider argument to Enum.ToString() is deprecated in .NET 2.0 - LogLog.Debug("XmlHierarchyConfigurator: Configuration update mode [" + configUpdateMode + "]."); - - // Only reset configuration if overwrite flag specified - if (configUpdateMode == ConfigUpdateMode.Overwrite) { - // Reset to original unset configuration - _hierarchy.ResetConfiguration(); - LogLog.Debug("XmlHierarchyConfigurator: Configuration reset before reading config."); - } - - // The Log4netFactory/OrchardXmlConfigurator/OrchardXmlHierarchyConfigurator can be refactored later - // so we call HostEnvironment.IsFullTrust - if (AppDomain.CurrentDomain.IsHomogenous && AppDomain.CurrentDomain.IsFullyTrusted) - _environmentVariables = System.Environment.GetEnvironmentVariables(); - else { - _environmentVariables = null; - } - - /* Building Appender objects, placing them in a local namespace - for future reference */ - - /* Process all the top level elements */ - - foreach (XmlNode currentNode in element.ChildNodes) { - if (currentNode.NodeType == XmlNodeType.Element) { - var currentElement = (XmlElement)currentNode; - - if (currentElement.LocalName == LoggerTag) { - ParseLogger(currentElement); - } - else if (currentElement.LocalName == CategoryTag) { - // TODO: deprecated use of category - ParseLogger(currentElement); - } - else if (currentElement.LocalName == RootTag) { - ParseRoot(currentElement); - } - else if (currentElement.LocalName == RendererTag) { - ParseRenderer(currentElement); - } - else if (currentElement.LocalName == AppenderTag) { - // We ignore appenders in this pass. They will - // be found and loaded if they are referenced. - } - else { - // Read the param tags and set properties on the hierarchy - SetParameter(currentElement, _hierarchy); - } - } - } - - // Lastly set the hierarchy threshold - string thresholdStr = element.GetAttribute(ThresholdAttr); - LogLog.Debug("XmlHierarchyConfigurator: Hierarchy Threshold [" + thresholdStr + "]"); - if (thresholdStr.Length > 0 && thresholdStr != "null") { - var thresholdLevel = (Level)ConvertStringTo(typeof(Level), thresholdStr); - if (thresholdLevel != null) { - _hierarchy.Threshold = thresholdLevel; - } - else { - LogLog.Warn("XmlHierarchyConfigurator: Unable to set hierarchy threshold using value [" + thresholdStr + "] (with acceptable conversion types)"); - } - } - - // Done reading config - } - - /// - /// Parse appenders by IDREF. - /// - /// The appender ref element. - /// The instance of the appender that the ref refers to. - /// - /// - /// Parse an XML element that represents an appender and return - /// the appender. - /// - /// - protected IAppender FindAppenderByReference(XmlElement appenderRef) { - var appenderName = appenderRef.GetAttribute(RefAttr); - - IAppender appender = (IAppender)_appenderBag[appenderName]; - if (appender != null) { - return appender; - } - // Find the element with that id - XmlElement element = null; - - if (!String.IsNullOrEmpty(appenderName)) { - foreach (XmlElement curAppenderElement in appenderRef.OwnerDocument.GetElementsByTagName(AppenderTag)) { - if (curAppenderElement.GetAttribute("name") != appenderName) { - continue; - } - element = curAppenderElement; - break; - } - } - - if (element == null) { - LogLog.Error("XmlHierarchyConfigurator: No appender named [" + appenderName + "] could be found."); - return null; - } - appender = ParseAppender(element); - if (appender != null) { - _appenderBag[appenderName] = appender; - } - return appender; - } - - /// - /// Parses an appender element. - /// - /// The appender element. - /// The appender instance or null when parsing failed. - /// - /// - /// Parse an XML element that represents an appender and return - /// the appender instance. - /// - /// - protected IAppender ParseAppender(XmlElement appenderElement) { - var appenderName = appenderElement.GetAttribute(NameAttr); - var typeName = appenderElement.GetAttribute(TypeAttr); - - LogLog.Debug("XmlHierarchyConfigurator: Loading Appender [" + appenderName + "] type: [" + typeName + "]"); - try { - var appender = (IAppender)Activator.CreateInstance(SystemInfo.GetTypeFromString(typeName, true, true)); - appender.Name = appenderName; - - foreach (XmlNode currentNode in appenderElement.ChildNodes) { - /* We're only interested in Elements */ - if (currentNode.NodeType == XmlNodeType.Element) { - var currentElement = (XmlElement)currentNode; - - // Look for the appender ref tag - if (currentElement.LocalName == AppenderRefTag) { - var refName = currentElement.GetAttribute(RefAttr); - - var appenderContainer = appender as IAppenderAttachable; - if (appenderContainer != null) { - LogLog.Debug("XmlHierarchyConfigurator: Attaching appender named [" + refName + "] to appender named [" + appender.Name + "]."); - - var referencedAppender = FindAppenderByReference(currentElement); - if (referencedAppender != null) { - appenderContainer.AddAppender(referencedAppender); - } - } - else { - LogLog.Error("XmlHierarchyConfigurator: Requesting attachment of appender named [" + refName + "] to appender named [" + appender.Name + "] which does not implement log4net.Core.IAppenderAttachable."); - } - } - else { - // For all other tags we use standard set param method - SetParameter(currentElement, appender); - } - } - } - - var optionHandler = appender as IOptionHandler; - if (optionHandler != null) { - optionHandler.ActivateOptions(); - } - - LogLog.Debug("XmlHierarchyConfigurator: Created Appender [" + appenderName + "]"); - return appender; - } - catch (Exception ex) { - // Yes, it's ugly. But all exceptions point to the same problem: we can't create an Appender - - LogLog.Error("XmlHierarchyConfigurator: Could not create Appender [" + appenderName + "] of type [" + typeName + "]. Reported error follows.", ex); - return null; - } - } - - /// - /// Parses a logger element. - /// - /// The logger element. - /// - /// - /// Parse an XML element that represents a logger. - /// - /// - protected void ParseLogger(XmlElement loggerElement) { - // Create a new log4net.Logger object from the element. - var loggerName = loggerElement.GetAttribute(NameAttr); - - LogLog.Debug("XmlHierarchyConfigurator: Retrieving an instance of log4net.Repository.Logger for logger [" + loggerName + "]."); - var log = _hierarchy.GetLogger(loggerName) as Logger; - - // Setting up a logger needs to be an atomic operation, in order - // to protect potential log operations while logger - // configuration is in progress. - if (log == null) { - return; - } - lock (log) { - var additivity = OptionConverter.ToBoolean(loggerElement.GetAttribute(AdditivityAttr), true); - - LogLog.Debug("XmlHierarchyConfigurator: Setting [" + log.Name + "] additivity to [" + additivity + "]."); - log.Additivity = additivity; - ParseChildrenOfLoggerElement(loggerElement, log, false); - } - } - - /// - /// Parses the root logger element. - /// - /// The root element. - /// - /// - /// Parse an XML element that represents the root logger. - /// - /// - protected void ParseRoot(XmlElement rootElement) { - var root = _hierarchy.Root; - // logger configuration needs to be atomic - lock (root) { - ParseChildrenOfLoggerElement(rootElement, root, true); - } - } - - /// - /// Parses the children of a logger element. - /// - /// The category element. - /// The logger instance. - /// Flag to indicate if the logger is the root logger. - /// - /// - /// Parse the child elements of a <logger> element. - /// - /// - protected void ParseChildrenOfLoggerElement(XmlElement catElement, Logger log, bool isRoot) { - // Remove all existing appenders from log. They will be - // reconstructed if need be. - log.RemoveAllAppenders(); - - foreach (XmlNode currentNode in catElement.ChildNodes) { - if (currentNode.NodeType == XmlNodeType.Element) { - var currentElement = (XmlElement)currentNode; - - if (currentElement.LocalName == AppenderRefTag) { - var appender = FindAppenderByReference(currentElement); - var refName = currentElement.GetAttribute(RefAttr); - if (appender != null) { - LogLog.Debug("XmlHierarchyConfigurator: Adding appender named [" + refName + "] to logger [" + log.Name + "]."); - log.AddAppender(appender); - } - else { - LogLog.Error("XmlHierarchyConfigurator: Appender named [" + refName + "] not found."); - } - } - else if (currentElement.LocalName == LevelTag || currentElement.LocalName == PriorityTag) { - ParseLevel(currentElement, log, isRoot); - } - else { - SetParameter(currentElement, log); - } - } - } - - var optionHandler = log as IOptionHandler; - if (optionHandler != null) { - optionHandler.ActivateOptions(); - } - } - - /// - /// Parses an object renderer. - /// - /// The renderer element. - /// - /// - /// Parse an XML element that represents a renderer. - /// - /// - protected void ParseRenderer(XmlElement element) { - var renderingClassName = element.GetAttribute(RenderingTypeAttr); - var renderedClassName = element.GetAttribute(RenderedTypeAttr); - - LogLog.Debug("XmlHierarchyConfigurator: Rendering class [" + renderingClassName + "], Rendered class [" + renderedClassName + "]."); - var renderer = (IObjectRenderer)OptionConverter.InstantiateByClassName(renderingClassName, typeof(IObjectRenderer), null); - if (renderer == null) { - LogLog.Error("XmlHierarchyConfigurator: Could not instantiate renderer [" + renderingClassName + "]."); - return; - } - try { - _hierarchy.RendererMap.Put(SystemInfo.GetTypeFromString(renderedClassName, true, true), renderer); - } - catch (Exception e) { - LogLog.Error("XmlHierarchyConfigurator: Could not find class [" + renderedClassName + "].", e); - } - } - - /// - /// Parses a level element. - /// - /// The level element. - /// The logger object to set the level on. - /// Flag to indicate if the logger is the root logger. - /// - /// - /// Parse an XML element that represents a level. - /// - /// - protected void ParseLevel(XmlElement element, Logger log, bool isRoot) { - var loggerName = log.Name; - if (isRoot) { - loggerName = "root"; - } - - var levelStr = element.GetAttribute(ValueAttr); - LogLog.Debug("XmlHierarchyConfigurator: Logger [" + loggerName + "] Level string is [" + levelStr + "]."); - - if (Inherited == levelStr) { - if (isRoot) { - LogLog.Error("XmlHierarchyConfigurator: Root level cannot be inherited. Ignoring directive."); - } - else { - LogLog.Debug("XmlHierarchyConfigurator: Logger [" + loggerName + "] level set to inherit from parent."); - log.Level = null; - } - } - else { - log.Level = log.Hierarchy.LevelMap[levelStr]; - if (log.Level == null) { - LogLog.Error("XmlHierarchyConfigurator: Undefined level [" + levelStr + "] on Logger [" + loggerName + "]."); - } - else { - LogLog.Debug("XmlHierarchyConfigurator: Logger [" + loggerName + "] level set to [name=\"" + log.Level.Name + "\",value=" + log.Level.Value + "]."); - } - } - } - - /// - /// Sets a parameter on an object. - /// - /// The parameter element. - /// The object to set the parameter on. - /// - /// The parameter name must correspond to a writable property - /// on the object. The value of the parameter is a string, - /// therefore this function will attempt to set a string - /// property first. If unable to set a string property it - /// will inspect the property and its argument type. It will - /// attempt to call a static method called Parse on the - /// type of the property. This method will take a single - /// string argument and return a value that can be used to - /// set the property. - /// - protected void SetParameter(XmlElement element, object target) { - // Get the property name - var name = element.GetAttribute(NameAttr); - - // If the name attribute does not exist then use the name of the element - if (element.LocalName != ParamTag || String.IsNullOrEmpty(name)) { - name = element.LocalName; - } - - // Look for the property on the target object - var targetType = target.GetType(); - Type propertyType = null; - - MethodInfo methInfo = null; - - // Try to find a writable property - var propInfo = targetType.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.IgnoreCase); - if (propInfo != null && propInfo.CanWrite) { - // found a property - propertyType = propInfo.PropertyType; - } - else { - propInfo = null; - - // look for a method with the signature Add(type) - methInfo = FindMethodInfo(targetType, name); - - if (methInfo != null) { - propertyType = methInfo.GetParameters()[0].ParameterType; - } - } - - if (propertyType == null) { - LogLog.Error("XmlHierarchyConfigurator: Cannot find Property [" + name + "] to set object on [" + target + "]"); - } - else { - string propertyValue = null; - - if (element.GetAttributeNode(ValueAttr) != null) { - propertyValue = element.GetAttribute(ValueAttr); - } - else if (element.HasChildNodes) { - // Concatenate the CDATA and Text nodes together - foreach (XmlNode childNode in element.ChildNodes) { - if (childNode.NodeType == XmlNodeType.CDATA || childNode.NodeType == XmlNodeType.Text) { - if (propertyValue == null) { - propertyValue = childNode.InnerText; - } - else { - propertyValue += childNode.InnerText; - } - } - } - } - - if (propertyValue != null) { - if (_environmentVariables != null) { - // Expand environment variables in the string. - propertyValue = OptionConverter.SubstituteVariables(propertyValue, _environmentVariables); - } - - Type parsedObjectConversionTargetType = null; - - // Check if a specific subtype is specified on the element using the 'type' attribute - var subTypeString = element.GetAttribute(TypeAttr); - if (!String.IsNullOrEmpty(subTypeString)) { - // Read the explicit subtype - try { - var subType = SystemInfo.GetTypeFromString(subTypeString, true, true); - - LogLog.Debug("XmlHierarchyConfigurator: Parameter [" + name + "] specified subtype [" + subType.FullName + "]"); - - if (!propertyType.IsAssignableFrom(subType)) { - // Check if there is an appropriate type converter - if (OptionConverter.CanConvertTypeTo(subType, propertyType)) { - // Must re-convert to the real property type - parsedObjectConversionTargetType = propertyType; - - // Use sub type as intermediary type - propertyType = subType; - } - else { - LogLog.Error("XmlHierarchyConfigurator: Subtype [" + subType.FullName + "] set on [" + name + "] is not a subclass of property type [" + propertyType.FullName + "] and there are no acceptable type conversions."); - } - } - else { - // The subtype specified is found and is actually a subtype of the property - // type, therefore we can switch to using this type. - propertyType = subType; - } - } - catch (Exception ex) { - LogLog.Error("XmlHierarchyConfigurator: Failed to find type [" + subTypeString + "] set on [" + name + "]", ex); - } - } - - // Now try to convert the string value to an acceptable type - // to pass to this property. - - var convertedValue = ConvertStringTo(propertyType, propertyValue); - - // Check if we need to do an additional conversion - if (convertedValue != null && parsedObjectConversionTargetType != null) { - LogLog.Debug("XmlHierarchyConfigurator: Performing additional conversion of value from [" + convertedValue.GetType().Name + "] to [" + parsedObjectConversionTargetType.Name + "]"); - convertedValue = OptionConverter.ConvertTypeTo(convertedValue, parsedObjectConversionTargetType); - } - - if (convertedValue != null) { - if (propInfo != null) { - // Got a converted result - LogLog.Debug("XmlHierarchyConfigurator: Setting Property [" + propInfo.Name + "] to " + convertedValue.GetType().Name + " value [" + convertedValue + "]"); - - try { - // Pass to the property - propInfo.SetValue(target, convertedValue, BindingFlags.SetProperty, null, null, CultureInfo.InvariantCulture); - } - catch (TargetInvocationException targetInvocationEx) { - LogLog.Error("XmlHierarchyConfigurator: Failed to set parameter [" + propInfo.Name + "] on object [" + target + "] using value [" + convertedValue + "]", targetInvocationEx.InnerException); - } - } - else if (methInfo != null) { - // Got a converted result - LogLog.Debug("XmlHierarchyConfigurator: Setting Collection Property [" + methInfo.Name + "] to " + convertedValue.GetType().Name + " value [" + convertedValue + "]"); - - try { - // Pass to the property - methInfo.Invoke(target, BindingFlags.InvokeMethod, null, new[] { convertedValue }, CultureInfo.InvariantCulture); - } - catch (TargetInvocationException targetInvocationEx) { - LogLog.Error("XmlHierarchyConfigurator: Failed to set parameter [" + name + "] on object [" + target + "] using value [" + convertedValue + "]", targetInvocationEx.InnerException); - } - } - } - else { - LogLog.Warn("XmlHierarchyConfigurator: Unable to set property [" + name + "] on object [" + target + "] using value [" + propertyValue + "] (with acceptable conversion types)"); - } - } - else { - object createdObject; - - if (propertyType == typeof(string) && !HasAttributesOrElements(element)) { - // If the property is a string and the element is empty (no attributes - // or child elements) then we special case the object value to an empty string. - // This is necessary because while the String is a class it does not have - // a default constructor that creates an empty string, which is the behavior - // we are trying to simulate and would be expected from CreateObjectFromXml - createdObject = ""; - } - else { - // No value specified - Type defaultObjectType = null; - if (IsTypeConstructible(propertyType)) { - defaultObjectType = propertyType; - } - - createdObject = CreateObjectFromXml(element, defaultObjectType, propertyType); - } - - if (createdObject == null) { - LogLog.Error("XmlHierarchyConfigurator: Failed to create object to set param: " + name); - } - else { - if (propInfo != null) { - // Got a converted result - LogLog.Debug("XmlHierarchyConfigurator: Setting Property [" + propInfo.Name + "] to object [" + createdObject + "]"); - - try { - // Pass to the property - propInfo.SetValue(target, createdObject, BindingFlags.SetProperty, null, null, CultureInfo.InvariantCulture); - } - catch (TargetInvocationException targetInvocationEx) { - LogLog.Error("XmlHierarchyConfigurator: Failed to set parameter [" + propInfo.Name + "] on object [" + target + "] using value [" + createdObject + "]", targetInvocationEx.InnerException); - } - } - else if (methInfo != null) { - // Got a converted result - LogLog.Debug("XmlHierarchyConfigurator: Setting Collection Property [" + methInfo.Name + "] to object [" + createdObject + "]"); - - try { - // Pass to the property - methInfo.Invoke(target, BindingFlags.InvokeMethod, null, new[] { createdObject }, CultureInfo.InvariantCulture); - } - catch (TargetInvocationException targetInvocationEx) { - LogLog.Error("XmlHierarchyConfigurator: Failed to set parameter [" + methInfo.Name + "] on object [" + target + "] using value [" + createdObject + "]", targetInvocationEx.InnerException); - } - } - } - } - } - } - - /// - /// Test if an element has no attributes or child elements - /// - /// the element to inspect - /// true if the element has any attributes or child elements, false otherwise - private static bool HasAttributesOrElements(XmlElement element) { - return element.ChildNodes.Cast().Any(node => node.NodeType == XmlNodeType.Attribute || node.NodeType == XmlNodeType.Element); - } - - /// - /// Test if a is constructible with Activator.CreateInstance. - /// - /// the type to inspect - /// true if the type is creatable using a default constructor, false otherwise - private static bool IsTypeConstructible(Type type) { - if (type.IsClass && !type.IsAbstract) { - var defaultConstructor = type.GetConstructor(new Type[0]); - if (defaultConstructor != null && !defaultConstructor.IsAbstract && !defaultConstructor.IsPrivate) { - return true; - } - } - return false; - } - - /// - /// Look for a method on the that matches the supplied - /// - /// the type that has the method - /// the name of the method - /// the method info found - /// - /// - /// The method must be a public instance method on the . - /// The method must be named or "Add" followed by . - /// The method must take a single parameter. - /// - /// - private static MethodInfo FindMethodInfo(Type targetType, string name) { - var requiredMethodNameA = name; - var requiredMethodNameB = "Add" + name; - - var methods = targetType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - - foreach (var methInfo in methods) { - if (!methInfo.IsStatic) { - if (string.Compare(methInfo.Name, requiredMethodNameA, true, CultureInfo.InvariantCulture) == 0 || - string.Compare(methInfo.Name, requiredMethodNameB, true, CultureInfo.InvariantCulture) == 0) { - // Found matching method name - - // Look for version with one arg only - var methParams = methInfo.GetParameters(); - if (methParams.Length == 1) { - return methInfo; - } - } - } - } - return null; - } - - /// - /// Converts a string value to a target type. - /// - /// The type of object to convert the string to. - /// The string value to use as the value of the object. - /// - /// - /// An object of type with value or - /// null when the conversion could not be performed. - /// - /// - protected object ConvertStringTo(Type type, string value) { - // Hack to allow use of Level in property - if (typeof(Level) == type) { - // Property wants a level - var levelValue = _hierarchy.LevelMap[value]; - - if (levelValue == null) { - LogLog.Error("XmlHierarchyConfigurator: Unknown Level Specified [" + value + "]"); - } - - return levelValue; - } - return OptionConverter.ConvertStringTo(type, value); - } - - /// - /// Creates an object as specified in XML. - /// - /// The XML element that contains the definition of the object. - /// The object type to use if not explicitly specified. - /// The type that the returned object must be or must inherit from. - /// The object or null - /// - /// - /// Parse an XML element and create an object instance based on the configuration - /// data. - /// - /// - /// The type of the instance may be specified in the XML. If not - /// specified then the is used - /// as the type. However the type is specified it must support the - /// type. - /// - /// - protected object CreateObjectFromXml(XmlElement element, Type defaultTargetType, Type typeConstraint) { - Type objectType; - - // Get the object type - var objectTypeString = element.GetAttribute(TypeAttr); - if (String.IsNullOrEmpty(objectTypeString)) { - if (defaultTargetType == null) { - LogLog.Error("XmlHierarchyConfigurator: Object type not specified. Cannot create object of type [" + typeConstraint.FullName + "]. Missing Value or Type."); - return null; - } - // Use the default object type - objectType = defaultTargetType; - } - else { - // Read the explicit object type - try { - objectType = SystemInfo.GetTypeFromString(objectTypeString, true, true); - } - catch (Exception ex) { - LogLog.Error("XmlHierarchyConfigurator: Failed to find type [" + objectTypeString + "]", ex); - return null; - } - } - - var requiresConversion = false; - - // Got the object type. Check that it meets the typeConstraint - if (typeConstraint != null) { - if (!typeConstraint.IsAssignableFrom(objectType)) { - // Check if there is an appropriate type converter - if (OptionConverter.CanConvertTypeTo(objectType, typeConstraint)) { - requiresConversion = true; - } - else { - LogLog.Error("XmlHierarchyConfigurator: Object type [" + objectType.FullName + "] is not assignable to type [" + typeConstraint.FullName + "]. There are no acceptable type conversions."); - return null; - } - } - } - - // Create using the default constructor - object createdObject = null; - try { - createdObject = Activator.CreateInstance(objectType); - } - catch (Exception createInstanceEx) { - LogLog.Error("XmlHierarchyConfigurator: Failed to construct object of type [" + objectType.FullName + "] Exception: " + createInstanceEx); - } - - // Set any params on object - foreach (var currentNode in element.ChildNodes.Cast().Where(currentNode => currentNode.NodeType == XmlNodeType.Element)) { - SetParameter((XmlElement)currentNode, createdObject); - } - - // Check if we need to call ActivateOptions - var optionHandler = createdObject as IOptionHandler; - if (optionHandler != null) { - optionHandler.ActivateOptions(); - } - - // Ok object should be initialized - - return requiresConversion ? OptionConverter.ConvertTypeTo(createdObject, typeConstraint) : createdObject; - } - } -} diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index b05405759..35b429556 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -200,8 +200,6 @@ - - From 4bd3157fe99237a31b10de39af780686ae9d41c6 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Mon, 9 May 2011 14:13:05 -0700 Subject: [PATCH 019/139] #17793: Removing Orchard.Web.cspoj from msdeploy Work Items: 17793 --HG-- branch : 1.x --- Orchard.proj | 10 +- src/Orchard.sln | 13 - .../MSBuild.Orchard.Tasks.Tests.csproj | 125 -------- .../Properties/AssemblyInfo.cs | 34 --- .../StageProjectAlterationTests.cs | 104 ------- .../TestData/ExtraFiles.xml | 16 - .../TestData/ProjectReferences.xml | 23 -- .../TestData/SimpleWebProject.xml | 287 ------------------ .../TestData/TestDataFiles.cs | 49 --- .../MSBuild.Orchard.Tasks.csproj | 1 - .../StageProjectAlteration.cs | 219 ------------- 11 files changed, 1 insertion(+), 880 deletions(-) delete mode 100644 src/Tools/MSBuild.Orchard.Tasks.Tests/MSBuild.Orchard.Tasks.Tests.csproj delete mode 100644 src/Tools/MSBuild.Orchard.Tasks.Tests/Properties/AssemblyInfo.cs delete mode 100644 src/Tools/MSBuild.Orchard.Tasks.Tests/StageProjectAlterationTests.cs delete mode 100644 src/Tools/MSBuild.Orchard.Tasks.Tests/TestData/ExtraFiles.xml delete mode 100644 src/Tools/MSBuild.Orchard.Tasks.Tests/TestData/ProjectReferences.xml delete mode 100644 src/Tools/MSBuild.Orchard.Tasks.Tests/TestData/SimpleWebProject.xml delete mode 100644 src/Tools/MSBuild.Orchard.Tasks.Tests/TestData/TestDataFiles.cs delete mode 100644 src/Tools/MSBuild.Orchard.Tasks/StageProjectAlteration.cs diff --git a/Orchard.proj b/Orchard.proj index 4dccfea02..c604796b4 100644 --- a/Orchard.proj +++ b/Orchard.proj @@ -140,7 +140,6 @@ - @@ -155,7 +154,7 @@ - + @@ -205,13 +204,6 @@ - - - - - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {4AB4B5B6-277E-4FF6-B69B-7AE9E16D2A56} - Library - Properties - MSBuild.Orchard.Tasks.Tests - MSBuild.Orchard.Tasks.Tests - v4.0 - 512 - - - 3.5 - - false - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - true - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - x86 - ..\..\OrchardBasicCorrectness.ruleset - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - AllRules.ruleset - - - - - - False - ..\..\..\lib\moq\Moq.dll - - - False - ..\..\..\lib\nunit\nunit.framework.dll - - - - 3.5 - - - 3.5 - - - 3.5 - - - - - - - - - - - - {5E5E7A21-C7B2-44D8-8593-2F9541AE041D} - MSBuild.Orchard.Tasks - - - - - - - - - - - - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - true - - - False - Windows Installer 3.1 - true - - - - - \ No newline at end of file diff --git a/src/Tools/MSBuild.Orchard.Tasks.Tests/Properties/AssemblyInfo.cs b/src/Tools/MSBuild.Orchard.Tasks.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index 432a92500..000000000 --- a/src/Tools/MSBuild.Orchard.Tasks.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("MSBuild.Orchard.Tasks.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyProduct("Orchard")] -[assembly: AssemblyCopyright("Copyright © Outercurve Foundation 2009")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("db4cb512-5c00-44c9-9173-7ede47af1967")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.20")] -[assembly: AssemblyFileVersion("1.0.20")] diff --git a/src/Tools/MSBuild.Orchard.Tasks.Tests/StageProjectAlterationTests.cs b/src/Tools/MSBuild.Orchard.Tasks.Tests/StageProjectAlterationTests.cs deleted file mode 100644 index 6387b1e1b..000000000 --- a/src/Tools/MSBuild.Orchard.Tasks.Tests/StageProjectAlterationTests.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System.Linq; -using System.Xml.Linq; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; -using Moq; -using MSBuild.Orchard.Tasks.Tests.TestData; -using NUnit.Framework; - -namespace MSBuild.Orchard.Tasks.Tests { - [TestFixture] - public class StageProjectAlterationTests { - private TestDataFiles _testDataFiles; - private StageProjectAlteration _task; - private string _xmlns = "http://schemas.microsoft.com/developer/msbuild/2003"; - - [SetUp] - public void Init() { - _testDataFiles = new TestDataFiles(); - _task = new StageProjectAlteration(); - - var engine = new Mock(); - _task.BuildEngine = engine.Object; - } - - [TearDown] - public void Term() { - _testDataFiles.Dispose(); - _testDataFiles = null; - } - - [Test] - public void ClassShouldBeCallable() { - var result = _task.Execute(); - Assert.That(result, Is.False); - } - - [Test] - public void ProjectFileNameMustExist() { - _task.ProjectFileName = "no-such-file.csproj"; - var result = _task.Execute(); - Assert.That(result, Is.False); - - _task.ProjectFileName = _testDataFiles.Get("SimpleWebProject.xml"); - result = _task.Execute(); - Assert.That(result, Is.True); - } - - [Test] - public void ProjectReferencesMustChange() { - _task.ProjectFileName = _testDataFiles.Get("ProjectReferences.xml"); - var before = XDocument.Load(_task.ProjectFileName); - var result = _task.Execute(); - var after = XDocument.Load(_task.ProjectFileName); - - Assert.That(result, Is.True); - - var beforeProjectReferences = before - .Elements(XName.Get("Project", _xmlns)) - .Elements(XName.Get("ItemGroup", _xmlns)) - .Elements(XName.Get("ProjectReference", _xmlns)); - - Assert.That(beforeProjectReferences.Count(), Is.Not.EqualTo(0)); - - var afterProjectReferences = after - .Elements(XName.Get("Project", _xmlns)) - .Elements(XName.Get("ItemGroup", _xmlns)) - .Elements(XName.Get("ProjectReference", _xmlns)); - - Assert.That(afterProjectReferences.Count(), Is.EqualTo(0)); - } - - [Test] - public void ExtraFilesAreDetected() { - _task.ProjectFileName = _testDataFiles.Get("ExtraFiles.xml"); - var result = _task.Execute(); - Assert.That(result, Is.True); - - Assert.That(_task.ExtraFiles.Count(), Is.EqualTo(4)); - } - - [Test] - public void ContentFilesAreDetected() { - _task.ProjectFileName = _testDataFiles.Get("ExtraFiles.xml"); - _task.AddContentFiles = new[] { - new TaskItem("newfile.xml"), - new TaskItem("another\\newfile.txt") - }; - var result = _task.Execute(); - Assert.That(result, Is.True); - - var after = XDocument.Load(_task.ProjectFileName); - var afterIncludes = after - .Elements(XName.Get("Project", _xmlns)) - .Elements(XName.Get("ItemGroup", _xmlns)) - .Elements(XName.Get("Content", _xmlns)) - .Attributes("Include") - .Select(attr => (string)attr) - .ToArray(); - - Assert.That(afterIncludes, Has.Some.EqualTo("newfile.xml")); - Assert.That(afterIncludes, Has.Some.EqualTo("another\\newfile.txt")); - } - } -} diff --git a/src/Tools/MSBuild.Orchard.Tasks.Tests/TestData/ExtraFiles.xml b/src/Tools/MSBuild.Orchard.Tasks.Tests/TestData/ExtraFiles.xml deleted file mode 100644 index f9e08e3b4..000000000 --- a/src/Tools/MSBuild.Orchard.Tasks.Tests/TestData/ExtraFiles.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - Global.asax - - - - - - - - - - - - diff --git a/src/Tools/MSBuild.Orchard.Tasks.Tests/TestData/ProjectReferences.xml b/src/Tools/MSBuild.Orchard.Tasks.Tests/TestData/ProjectReferences.xml deleted file mode 100644 index 9736a10de..000000000 --- a/src/Tools/MSBuild.Orchard.Tasks.Tests/TestData/ProjectReferences.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - False - ..\..\lib\autofac\Autofac.dll - - - False - ..\..\lib\autofac\Autofac.Integration.Web.dll - - - - - - {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6} - Orchard - - - {9916839C-39FC-4CEB-A5AF-89CA7E87119F} - Orchard.Core - - - diff --git a/src/Tools/MSBuild.Orchard.Tasks.Tests/TestData/SimpleWebProject.xml b/src/Tools/MSBuild.Orchard.Tasks.Tests/TestData/SimpleWebProject.xml deleted file mode 100644 index 4c3a29ed4..000000000 --- a/src/Tools/MSBuild.Orchard.Tasks.Tests/TestData/SimpleWebProject.xml +++ /dev/null @@ -1,287 +0,0 @@ - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {50B779EA-EC00-4699-84C0-03B395C365D2} - {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - Orchard.Web - Orchard.Web - v3.5 - true - - - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\ - TRACE - prompt - 4 - - - - False - ..\..\lib\autofac\Autofac.dll - - - False - ..\..\lib\autofac\Autofac.Integration.Web.dll - - - False - ..\..\lib\fluentnhibernate\NHibernate.ByteCode.Castle.dll - - - - - 3.5 - - - 3.5 - - - 3.5 - - - False - ..\..\lib\aspnetmvc\System.Web.Mvc.dll - True - - - 3.5 - - - - - - - - - - - - - - - Global.asax - - - - - - - - - - - - {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6} - Orchard - - - {9916839C-39FC-4CEB-A5AF-89CA7E87119F} - Orchard.Core - - - {63FBD4D9-E1DA-4A7B-AA6A-D6074FE50867} - Orchard.Blogs - - - {14C049FD-B35B-415A-A824-87F26B26E7FD} - Orchard.Comments - - - {67C1D3AF-A0EC-46B2-BAE1-DF1DA8E0B890} - Orchard.Experimental - - - {D9A7B330-CD22-4DA1-A95A-8DE1982AD8EB} - Orchard.Media - - - {D10AD48F-407D-4DB5-A328-173EC7CB010F} - Orchard.Roles - - - {8C7FCBC2-E6E1-405E-BFB5-D8D9E67A09C4} - Orchard.Setup - - - {5D0F00F0-26C9-4785-AD61-B85710C60EB0} - Orchard.Tags - - - {79AED36E-ABD0-4747-93D3-8722B042454B} - Orchard.Users - - - {17C44253-65A2-4597-98C7-16EE576824B6} - Orchard.Sandbox - - - {954CA994-D204-468B-9D69-51F6AD3E1C29} - TinyMce - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $(ProjectDir)\..\Manifests - - - - - - - - - - - - False - False - 30320 - / - - - False - False - - - False - - - - - \ No newline at end of file diff --git a/src/Tools/MSBuild.Orchard.Tasks.Tests/TestData/TestDataFiles.cs b/src/Tools/MSBuild.Orchard.Tasks.Tests/TestData/TestDataFiles.cs deleted file mode 100644 index 6f192a7b7..000000000 --- a/src/Tools/MSBuild.Orchard.Tasks.Tests/TestData/TestDataFiles.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace MSBuild.Orchard.Tasks.Tests.TestData { - public class TestDataFiles : IDisposable { - List _entries = new List(); - private readonly string _tempPath; - - public TestDataFiles() { - _tempPath = Path.GetTempFileName(); - File.Delete(_tempPath); - Directory.CreateDirectory(_tempPath); - } - - public void Dispose() { - Directory.Delete(_tempPath, true); - } - - public string Get(string name) { - if (!_entries.Any(entry => entry.Name == name)) { - var type = GetType(); - var fullPath = Path.Combine(_tempPath, name); - using (var inputStream = type.Assembly.GetManifestResourceStream(type, name)) { - if (inputStream == null) - throw new ApplicationException("Tests data not found"); - - using (var outputStream = new FileStream(fullPath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) { - var buffer = new byte[8192]; - for (; ; ) { - var size = inputStream.Read(buffer, 0, buffer.Length); - if (size < 1) - break; - outputStream.Write(buffer, 0, size); - } - } - } - _entries.Add(new Entry { Name = name, FullPath = fullPath }); - } - return _entries.Single(entry => entry.Name == name).FullPath; - } - - class Entry { - public string Name { get; set; } - public string FullPath { get; set; } - } - } -} diff --git a/src/Tools/MSBuild.Orchard.Tasks/MSBuild.Orchard.Tasks.csproj b/src/Tools/MSBuild.Orchard.Tasks/MSBuild.Orchard.Tasks.csproj index bad5c6697..b7d9d3162 100644 --- a/src/Tools/MSBuild.Orchard.Tasks/MSBuild.Orchard.Tasks.csproj +++ b/src/Tools/MSBuild.Orchard.Tasks/MSBuild.Orchard.Tasks.csproj @@ -70,7 +70,6 @@ - diff --git a/src/Tools/MSBuild.Orchard.Tasks/StageProjectAlteration.cs b/src/Tools/MSBuild.Orchard.Tasks/StageProjectAlteration.cs deleted file mode 100644 index b4903407c..000000000 --- a/src/Tools/MSBuild.Orchard.Tasks/StageProjectAlteration.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Xml.Linq; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -namespace MSBuild.Orchard.Tasks { - public class StageProjectAlteration : Task { - public string ProjectFileName { get; set; } - - public ITaskItem[] AddContentFiles { get; set; } - - [Output] - public ITaskItem[] ExtraFiles { get; set; } - - - - public override bool Execute() { - Log.LogMessage("Altering \"{0}\"", ProjectFileName); - - var context = new Context(this); - if (context.LoadProject() && - context.ChangeProjectReferencesToFileReferences() && - context.ChangeLibraryReferencesToFileReferences() && - context.FindExtraFiles() && - context.AddContentFiles() && - context.SaveProject()) { - - Log.LogMessage("Stage project altered successfully"); - return true; - } - - Log.LogWarning("Stage project alteration failed"); - return false; - } - - class Context { - private readonly StageProjectAlteration _task; - XDocument _document; - - private const string Xmlns = "http://schemas.microsoft.com/developer/msbuild/2003"; - private static readonly XName Project = XName.Get("Project", Xmlns); - private static readonly XName ItemGroup = XName.Get("ItemGroup", Xmlns); - private static readonly XName ProjectReference = XName.Get("ProjectReference", Xmlns); - private static readonly XName Reference = XName.Get("Reference", Xmlns); - private static readonly XName Name = XName.Get("Name", Xmlns); - private static readonly XName Include = XName.Get("Include"); - private static readonly XName HintPath = XName.Get("HintPath", Xmlns); - private static readonly XName SpecificVersion = XName.Get("SpecificVersion", Xmlns); - private static readonly XName Content = XName.Get("Content", Xmlns); - private static readonly XName Compile = XName.Get("Compile", Xmlns); - private static readonly XName None = XName.Get("None", Xmlns); - - public Context(StageProjectAlteration task) { - _task = task; - } - - public bool LoadProject() { - try { - _document = XDocument.Load(_task.ProjectFileName); - return true; - } - catch (Exception) { - _task.Log.LogError("Unable to load project file"); - return false; - } - } - - public bool SaveProject() { - _document.Save(_task.ProjectFileName); - return true; - } - - public bool ChangeProjectReferencesToFileReferences() { - var projectReferences = _document - .Elements(Project) - .Elements(ItemGroup) - .Elements(ProjectReference); - - var referenceItemGroup = _document - .Elements(Project) - .Elements(ItemGroup) - .FirstOrDefault(elt => elt.Elements(Reference).Any()); - - if (referenceItemGroup == null) { - referenceItemGroup = new XElement(ItemGroup); - _document.Root.Add(referenceItemGroup); - } - - foreach (var projectReferenceName in projectReferences.Elements(Name)) { - string oldHintPath = (projectReferenceName.Parent.Element(HintPath) ?? new XElement(HintPath)).Value; - string newHintPath = string.Format("bin\\{0}.dll", (string)projectReferenceName); - var reference = new XElement( - Reference, - new XAttribute(Include, (string)projectReferenceName), - new XElement(SpecificVersion, "False"), - new XElement(HintPath, newHintPath)); - referenceItemGroup.Add(reference); - - _task.Log.LogMessage("Project reference \"{0}\": HintPath changed from \"{1}\" to \"{2}\"", - (string)projectReferenceName, oldHintPath, newHintPath); - } - - foreach (var projectReference in projectReferences.ToArray()) { - projectReference.Remove(); - } - - return true; - } - - public bool ChangeLibraryReferencesToFileReferences() { - var libraryReferences = _document - .Elements(Project) - .Elements(ItemGroup) - .Elements(Reference); - - var referenceItemGroup = _document - .Elements(Project) - .Elements(ItemGroup) - .FirstOrDefault(elt => elt.Elements(Reference).Any()); - - if (referenceItemGroup == null) { - referenceItemGroup = new XElement(ItemGroup); - _document.Root.Add(referenceItemGroup); - } - - List elementsToRemove = new List(); - foreach (var hintPathElement in libraryReferences.Elements(HintPath)) { - string oldHintPath = hintPathElement.Value; - - if (!oldHintPath.StartsWith("..\\..\\lib\\")) - continue; - - elementsToRemove.Add(hintPathElement.Parent); - // Need to change the hint path from - // ..\\..\\lib\\\\.dll - // to - // bin\\.dll - string assemblyFileName = Path.GetFileName(oldHintPath); - string newHintPath = Path.Combine("bin", assemblyFileName); - var reference = new XElement( - Reference, - new XAttribute(Include, hintPathElement.Parent.Attribute(Include).Value), - new XElement(SpecificVersion, "False"), - new XElement(HintPath, newHintPath)); - referenceItemGroup.Add(reference); - - _task.Log.LogMessage("Assembly (library) Reference \"{0}\": HintPath changed from \"{1}\" to \"{2}\"", - hintPathElement.Parent.Attribute(Include).Value, oldHintPath, newHintPath); - } - - foreach (var reference in elementsToRemove) { - reference.Remove(); - } - - return true; - } - - public bool FindExtraFiles() { - var extraFiles = _document - .Elements(Project) - .Elements(ItemGroup) - .Elements().Where(elt => elt.Name == Compile || elt.Name == None) - .Attributes(Include) - .Select(attr => (string)attr); - - _task.ExtraFiles = extraFiles - .Select(file => { - _task.Log.LogMessage("Detected extra file \"{0}\"", file); - var item = new TaskItem(file); - item.SetMetadata("RecursiveDir", Path.GetDirectoryName(file)); - return item; - }) - .ToArray(); - - - return true; - } - - public bool AddContentFiles() { - var existingContent = _document - .Elements(Project) - .Elements(ItemGroup) - .Elements(Content) - .Attributes(Include) - .Select(attr => (string)attr); - - var contentItemGroup = _document - .Elements(Project) - .Elements(ItemGroup) - .FirstOrDefault(elt => elt.Elements(Content).Any()); - - if (contentItemGroup == null) { - contentItemGroup = new XElement(ItemGroup); - _document.Root.Add(contentItemGroup); - } - - if (_task.AddContentFiles != null) { - foreach (var addContent in _task.AddContentFiles) { - if (existingContent.Contains(addContent.ItemSpec)) { - // don't add more than once - continue; - } - _task.Log.LogMessage("Adding Content file \"{0}\"", addContent.ItemSpec); - - var content = new XElement( - Content, - new XAttribute(Include, addContent.ItemSpec)); - contentItemGroup.Add(content); - } - } - - return true; - } - } - } -} From 797a7a2261c5d2f8cef145533f85b88dd94ba385 Mon Sep 17 00:00:00 2001 From: Andre Rodrigues Date: Mon, 9 May 2011 14:16:04 -0700 Subject: [PATCH 020/139] Adding null check for logging config file name. Using complete filename for logging file suffix dictionary. Adding logging message on log file attempt fail. --HG-- branch : 1.x --- src/Orchard.Tests/Caching/CacheScopeTests.cs | 8 ++++---- src/Orchard/Logging/OrchardFileAppender.cs | 14 +++++++++----- src/Orchard/Logging/OrchardLog4netFactory.cs | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Orchard.Tests/Caching/CacheScopeTests.cs b/src/Orchard.Tests/Caching/CacheScopeTests.cs index 3e3606959..54cc56193 100644 --- a/src/Orchard.Tests/Caching/CacheScopeTests.cs +++ b/src/Orchard.Tests/Caching/CacheScopeTests.cs @@ -30,14 +30,14 @@ namespace Orchard.Tests.Caching { [Test] public void ComponentsAtHostLevelHaveAccessToCache() { var alpha = _hostContainer.Resolve(); - Assert.That(alpha.CacheManager, Is.Not.Null); + Assert.That(alpha.CacheManager, Is.Not.Null); } [Test] public void HostLevelHasAccessToGlobalVolatileProviders() { - Assert.That(_hostContainer.Resolve(), Is.Not.Null); - Assert.That(_hostContainer.Resolve(), Is.Not.Null); - Assert.That(_hostContainer.Resolve(), Is.Not.Null); + Assert.That(_hostContainer.Resolve(), Is.Not.Null); + Assert.That(_hostContainer.Resolve(), Is.Not.Null); + Assert.That(_hostContainer.Resolve(), Is.Not.Null); } } diff --git a/src/Orchard/Logging/OrchardFileAppender.cs b/src/Orchard/Logging/OrchardFileAppender.cs index ddde96623..08a8fae6f 100644 --- a/src/Orchard/Logging/OrchardFileAppender.cs +++ b/src/Orchard/Logging/OrchardFileAppender.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using log4net.Appender; +using log4net.Util; namespace Orchard.Logging { public class OrchardFileAppender : RollingFileAppender { @@ -18,13 +19,14 @@ namespace Orchard.Logging { protected override void OpenFile(string fileName, bool append) { lock (this) { bool fileOpened = false; + string completeFilename = GetNextOutputFileName(fileName); string currentFilename = fileName; - if (!_suffixes.ContainsKey(fileName)) { - _suffixes[fileName] = 0; + if (!_suffixes.ContainsKey(completeFilename)) { + _suffixes[completeFilename] = 0; } - int newSuffix = _suffixes[fileName]; + int newSuffix = _suffixes[completeFilename]; for (int i = 1; !fileOpened && i <= Retries; i++) { try { @@ -36,11 +38,13 @@ namespace Orchard.Logging { fileOpened = true; } catch { - newSuffix = _suffixes[fileName] + i; + newSuffix = _suffixes[completeFilename] + i; + + LogLog.Error(string.Format("OrchardFileAppender: Failed to open [{0}]. Attempting [{1}-{2}] instead.", fileName, fileName, newSuffix)); } } - _suffixes[fileName] = newSuffix; + _suffixes[completeFilename] = newSuffix; } } diff --git a/src/Orchard/Logging/OrchardLog4netFactory.cs b/src/Orchard/Logging/OrchardLog4netFactory.cs index 043651b00..51e75ed4b 100644 --- a/src/Orchard/Logging/OrchardLog4netFactory.cs +++ b/src/Orchard/Logging/OrchardLog4netFactory.cs @@ -11,7 +11,7 @@ namespace Orchard.Logging { : this(ConfigurationManager.AppSettings["log4net.Config"], hostEnvironment) { } public OrchardLog4netFactory(string configFilename, IHostEnvironment hostEnvironment) { - if (hostEnvironment.IsFullTrust) { + if (!string.IsNullOrWhiteSpace(configFilename) && hostEnvironment.IsFullTrust) { // Only monitor configuration file in full trust XmlConfigurator.ConfigureAndWatch(GetConfigFile(configFilename)); } From 8d1b1d6325aac25c447b57a33d07d4e1609f07b8 Mon Sep 17 00:00:00 2001 From: Suha Can Date: Mon, 9 May 2011 14:43:28 -0700 Subject: [PATCH 021/139] #17755: Export data should download file. --HG-- branch : 1.x --- .../Orchard.ImportExport/Controllers/AdminController.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Orchard.Web/Modules/Orchard.ImportExport/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.ImportExport/Controllers/AdminController.cs index 63bbec125..d62989d04 100644 --- a/src/Orchard.Web/Modules/Orchard.ImportExport/Controllers/AdminController.cs +++ b/src/Orchard.Web/Modules/Orchard.ImportExport/Controllers/AdminController.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using System.Web.Mvc; using Orchard.ContentManagement.MetaData; using Orchard.ImportExport.Services; @@ -77,7 +78,7 @@ namespace Orchard.ImportExport.Controllers { var exportFilePath = _importExportService.Export(contentTypesToExport, exportOptions); Services.Notifier.Information(T("Your export file has been created at {0}", exportFilePath)); - return RedirectToAction("Export"); + return File(exportFilePath, "text/xml", "export.xml"); } catch (Exception exception) { Services.Notifier.Error(T("Export failed: {0}", exception.Message)); From 6cc4a3dc91c053427ca2f2f216b6c133464f24e4 Mon Sep 17 00:00:00 2001 From: Andre Rodrigues Date: Mon, 9 May 2011 15:32:34 -0700 Subject: [PATCH 022/139] Setting logging to quiet mode on unit test to avoid CI server failure. --HG-- branch : 1.x --- src/Orchard.Tests/Logging/OrchardFileAppenderTests.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Orchard.Tests/Logging/OrchardFileAppenderTests.cs b/src/Orchard.Tests/Logging/OrchardFileAppenderTests.cs index 1e429e208..ad389db47 100644 --- a/src/Orchard.Tests/Logging/OrchardFileAppenderTests.cs +++ b/src/Orchard.Tests/Logging/OrchardFileAppenderTests.cs @@ -1,4 +1,5 @@ using System; +using log4net.Util; using Moq; using Moq.Protected; using NUnit.Framework; @@ -15,6 +16,9 @@ namespace Orchard.Tests.Logging { string filenameUsed = string.Empty; + // Set logging to quiet mode + LogLog.QuietMode = true; + Mock firstOrchardFileAppenderMock = new Mock(); StubOrchardFileAppender firstOrchardFileAppender = firstOrchardFileAppenderMock.Object; From eddf71bcd14f6ddec9fa7e80eefa0eec5cfa753f Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Mon, 9 May 2011 16:40:21 -0700 Subject: [PATCH 023/139] #17764: Core Shapes can't be overriden Work Items: 17764 --HG-- branch : 1.x --- .../DisplayManagement/Descriptors/DefaultShapeTableManager.cs | 2 +- .../DisplayManagement/Descriptors/ShapeAlterationBuilder.cs | 2 ++ src/Orchard/DisplayManagement/Descriptors/ShapeDescriptor.cs | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Orchard/DisplayManagement/Descriptors/DefaultShapeTableManager.cs b/src/Orchard/DisplayManagement/Descriptors/DefaultShapeTableManager.cs index 078fee6d6..8186bcbe4 100644 --- a/src/Orchard/DisplayManagement/Descriptors/DefaultShapeTableManager.cs +++ b/src/Orchard/DisplayManagement/Descriptors/DefaultShapeTableManager.cs @@ -45,7 +45,7 @@ namespace Orchard.DisplayManagement.Descriptors { (descriptor, alteration) => { alteration.Alter(descriptor); return descriptor; - })); + })).ToList(); return new ShapeTable { Descriptors = descriptors.ToDictionary(sd => sd.ShapeType, StringComparer.OrdinalIgnoreCase), diff --git a/src/Orchard/DisplayManagement/Descriptors/ShapeAlterationBuilder.cs b/src/Orchard/DisplayManagement/Descriptors/ShapeAlterationBuilder.cs index 42db230e1..bc91d60a3 100644 --- a/src/Orchard/DisplayManagement/Descriptors/ShapeAlterationBuilder.cs +++ b/src/Orchard/DisplayManagement/Descriptors/ShapeAlterationBuilder.cs @@ -55,6 +55,8 @@ namespace Orchard.DisplayManagement.Descriptors { return target(displayContext); } }; + + // ShapeDescriptor.Bindings is a case insensitive dictionary descriptor.Bindings[_bindingName] = binding; }); diff --git a/src/Orchard/DisplayManagement/Descriptors/ShapeDescriptor.cs b/src/Orchard/DisplayManagement/Descriptors/ShapeDescriptor.cs index 24232dd12..1c0f4c3db 100644 --- a/src/Orchard/DisplayManagement/Descriptors/ShapeDescriptor.cs +++ b/src/Orchard/DisplayManagement/Descriptors/ShapeDescriptor.cs @@ -12,7 +12,7 @@ namespace Orchard.DisplayManagement.Descriptors { Displaying = Enumerable.Empty>(); Displayed = Enumerable.Empty>(); Wrappers = new List(); - Bindings = new Dictionary(); + Bindings = new Dictionary(StringComparer.OrdinalIgnoreCase); Placement = ctx => new PlacementInfo {Location = DefaultPlacement}; } From 5bbd017475521811111e91f075d615a976db1301 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Mon, 9 May 2011 17:27:30 -0700 Subject: [PATCH 024/139] #17612: Fixing validation message in Warmup module Work Items: 17612 --HG-- branch : 1.x --- .../Modules/Orchard.Warmup/Controllers/AdminController.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.Warmup/Controllers/AdminController.cs index 17f2a967c..07e23012c 100644 --- a/src/Orchard.Web/Modules/Orchard.Warmup/Controllers/AdminController.cs +++ b/src/Orchard.Web/Modules/Orchard.Warmup/Controllers/AdminController.cs @@ -63,7 +63,10 @@ namespace Orchard.Warmup.Controllers { } } - Services.Notifier.Information(T("Warmup updated successfully.")); + if (ModelState.IsValid) { + Services.Notifier.Information(T("Warmup updated successfully.")); + } + return View(warmupPart); } From 954dd2ef90ac7d715aa55cf0407321c49fdf0ff4 Mon Sep 17 00:00:00 2001 From: Andre Rodrigues Date: Mon, 9 May 2011 20:36:56 -0700 Subject: [PATCH 025/139] Clearing suffixes dictionary when a threshold is hit to avoid memory starvation. Adding further method header comments. --HG-- branch : 1.x --- src/Orchard/Logging/OrchardFileAppender.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Orchard/Logging/OrchardFileAppender.cs b/src/Orchard/Logging/OrchardFileAppender.cs index 08a8fae6f..1af9d2d79 100644 --- a/src/Orchard/Logging/OrchardFileAppender.cs +++ b/src/Orchard/Logging/OrchardFileAppender.cs @@ -9,8 +9,16 @@ namespace Orchard.Logging { /// private static readonly Dictionary _suffixes = new Dictionary(); + /// + /// The number of suffix attempts that will be made on each OpenFile method call. + /// private const int Retries = 50; + /// + /// Maximum number of suffixes recorded before a cleanup happens to recycle memory. + /// + private const int MaxSuffixes = 100; + /// /// Opens the log file adding an incremental suffix to the filename if required due to an openning failure (usually, locking). /// @@ -22,6 +30,10 @@ namespace Orchard.Logging { string completeFilename = GetNextOutputFileName(fileName); string currentFilename = fileName; + if (_suffixes.Count > MaxSuffixes) { + _suffixes.Clear(); + } + if (!_suffixes.ContainsKey(completeFilename)) { _suffixes[completeFilename] = 0; } @@ -48,6 +60,11 @@ namespace Orchard.Logging { } } + /// + /// Calls the base class OpenFile method. Allows this method to be mocked. + /// + /// The filename as specified in the configuration file. + /// Boolean flag indicating weather the log file should be appended if it already exists. protected virtual void BaseOpenFile(string fileName, bool append) { base.OpenFile(fileName, append); } From ff3aa4b53518c1698e1664cf1ebacb695f720a7a Mon Sep 17 00:00:00 2001 From: Andre Rodrigues Date: Mon, 9 May 2011 21:41:31 -0700 Subject: [PATCH 026/139] #17813: Replacing theme name by theme id. --HG-- branch : 1.x --- .../Controllers/AdminController.cs | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.Themes/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.Themes/Controllers/AdminController.cs index 8af36a415..5d39bb074 100644 --- a/src/Orchard.Web/Modules/Orchard.Themes/Controllers/AdminController.cs +++ b/src/Orchard.Web/Modules/Orchard.Themes/Controllers/AdminController.cs @@ -109,34 +109,34 @@ namespace Orchard.Themes.Controllers { } [HttpPost, FormValueAbsent("submit.Apply"), FormValueAbsent("submit.Cancel")] - public ActionResult Preview(string themeName, string returnUrl) { + public ActionResult Preview(string themeId, string returnUrl) { if (!Services.Authorizer.Authorize(Permissions.ApplyTheme, T("Couldn't preview the current theme"))) return new HttpUnauthorizedResult(); if (_extensionManager.AvailableExtensions() - .FirstOrDefault(extension => DefaultExtensionTypes.IsTheme(extension.ExtensionType) && extension.Name.Equals(themeName)) == null) { + .FirstOrDefault(extension => DefaultExtensionTypes.IsTheme(extension.ExtensionType) && extension.Id.Equals(themeId)) == null) { - Services.Notifier.Error(T("Theme {0} was not found", themeName)); + Services.Notifier.Error(T("Theme {0} was not found", themeId)); } else { - _themeService.EnableThemeFeatures(themeName); - _previewTheme.SetPreviewTheme(themeName); + _themeService.EnableThemeFeatures(themeId); + _previewTheme.SetPreviewTheme(themeId); } return this.RedirectLocal(returnUrl, "~/"); } [HttpPost, ActionName("Preview"), FormValueRequired("submit.Apply")] - public ActionResult ApplyPreview(string themeName, string returnUrl) { + public ActionResult ApplyPreview(string themeId, string returnUrl) { if (!Services.Authorizer.Authorize(Permissions.ApplyTheme, T("Couldn't preview the current theme"))) return new HttpUnauthorizedResult(); if (_extensionManager.AvailableExtensions() - .FirstOrDefault(extension => DefaultExtensionTypes.IsTheme(extension.ExtensionType) && extension.Name.Equals(themeName)) == null) { + .FirstOrDefault(extension => DefaultExtensionTypes.IsTheme(extension.ExtensionType) && extension.Id.Equals(themeId)) == null) { - Services.Notifier.Error(T("Theme {0} was not found", themeName)); + Services.Notifier.Error(T("Theme {0} was not found", themeId)); } else { _previewTheme.SetPreviewTheme(null); - _siteThemeService.SetSiteTheme(themeName); + _siteThemeService.SetSiteTheme(themeId); } return RedirectToAction("Index"); @@ -153,68 +153,68 @@ namespace Orchard.Themes.Controllers { } [HttpPost] - public ActionResult Enable(string themeName) { + public ActionResult Enable(string themeId) { if (!Services.Authorizer.Authorize(Permissions.ApplyTheme, T("Couldn't enable the theme"))) return new HttpUnauthorizedResult(); if (_extensionManager.AvailableExtensions() - .FirstOrDefault(extension => DefaultExtensionTypes.IsTheme(extension.ExtensionType) && extension.Name.Equals(themeName)) == null) { + .FirstOrDefault(extension => DefaultExtensionTypes.IsTheme(extension.ExtensionType) && extension.Id.Equals(themeId)) == null) { - Services.Notifier.Error(T("Theme {0} was not found", themeName)); + Services.Notifier.Error(T("Theme {0} was not found", themeId)); } else { - _themeService.EnableThemeFeatures(themeName); + _themeService.EnableThemeFeatures(themeId); } return RedirectToAction("Index"); } [HttpPost] - public ActionResult Disable(string themeName) { + public ActionResult Disable(string themeId) { if (!Services.Authorizer.Authorize(Permissions.ApplyTheme, T("Couldn't disable the current theme"))) return new HttpUnauthorizedResult(); if (_extensionManager.AvailableExtensions() - .FirstOrDefault(extension => DefaultExtensionTypes.IsTheme(extension.ExtensionType) && extension.Name.Equals(themeName)) == null) { + .FirstOrDefault(extension => DefaultExtensionTypes.IsTheme(extension.ExtensionType) && extension.Id.Equals(themeId)) == null) { - Services.Notifier.Error(T("Theme {0} was not found", themeName)); + Services.Notifier.Error(T("Theme {0} was not found", themeId)); } else { - _themeService.DisableThemeFeatures(themeName); + _themeService.DisableThemeFeatures(themeId); } return RedirectToAction("Index"); } [HttpPost] - public ActionResult Activate(string themeName) { + public ActionResult Activate(string themeId) { if (!Services.Authorizer.Authorize(Permissions.ApplyTheme, T("Couldn't set the current theme"))) return new HttpUnauthorizedResult(); if (_extensionManager.AvailableExtensions() - .FirstOrDefault(extension => DefaultExtensionTypes.IsTheme(extension.ExtensionType) && extension.Name.Equals(themeName)) == null) { + .FirstOrDefault(extension => DefaultExtensionTypes.IsTheme(extension.ExtensionType) && extension.Id.Equals(themeId)) == null) { - Services.Notifier.Error(T("Theme {0} was not found", themeName)); + Services.Notifier.Error(T("Theme {0} was not found", themeId)); } else { - _themeService.EnableThemeFeatures(themeName); - _siteThemeService.SetSiteTheme(themeName); + _themeService.EnableThemeFeatures(themeId); + _siteThemeService.SetSiteTheme(themeId); } return RedirectToAction("Index"); } [HttpPost] - public ActionResult Update(string themeName) { + public ActionResult Update(string themeId) { if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Couldn't update theme"))) return new HttpUnauthorizedResult(); - if (string.IsNullOrEmpty(themeName)) + if (string.IsNullOrEmpty(themeId)) return HttpNotFound(); try { - _reportsCoordinator.Register("Data Migration", "Upgrade " + themeName, "Orchard installation"); - _dataMigrationManager.Update(themeName); - Services.Notifier.Information(T("The theme {0} was updated succesfuly", themeName)); + _reportsCoordinator.Register("Data Migration", "Upgrade " + themeId, "Orchard installation"); + _dataMigrationManager.Update(themeId); + Services.Notifier.Information(T("The theme {0} was updated succesfuly", themeId)); } catch (Exception exception) { - this.Error(exception, T("An error occured while updating the theme {0}: {1}", themeName, exception.Message), Logger, Services.Notifier); + this.Error(exception, T("An error occured while updating the theme {0}: {1}", themeId, exception.Message), Logger, Services.Notifier); } return RedirectToAction("Index"); From 4e742d28b3af391a807d8567578c816fcc9a7467 Mon Sep 17 00:00:00 2001 From: Andre Rodrigues Date: Mon, 9 May 2011 22:06:55 -0700 Subject: [PATCH 027/139] #17813: Replacing theme name by theme id (missing file). --HG-- branch : 1.x --- .../Modules/Orchard.Themes/Views/ThemeEntry.cshtml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.Themes/Views/ThemeEntry.cshtml b/src/Orchard.Web/Modules/Orchard.Themes/Views/ThemeEntry.cshtml index 24b6f2526..9be67fd69 100644 --- a/src/Orchard.Web/Modules/Orchard.Themes/Views/ThemeEntry.cshtml +++ b/src/Orchard.Web/Modules/Orchard.Themes/Views/ThemeEntry.cshtml @@ -9,12 +9,12 @@ @Html.Image(Href(Html.ThemePath((ExtensionDescriptor) Model.ContentPart.Descriptor, "/Theme.png")), Html.Encode((string)Model.ContentPart.Name), null) @using (Html.BeginFormAntiForgeryPost(Url.Action("Activate"), FormMethod.Post, new { @class = "inline" })) { - @Html.Hidden("themeName", (string)Model.ContentPart.Descriptor.Id) + @Html.Hidden("themeId", (string)Model.ContentPart.Descriptor.Id) } @using (Html.BeginFormAntiForgeryPost(Url.Action("Preview"), FormMethod.Post, new { @class = "inline" })) { - @Html.Hidden("themeName", (string)Model.ContentPart.Descriptor.Id) + @Html.Hidden("themeId", (string)Model.ContentPart.Descriptor.Id) } @@ -37,13 +37,13 @@ @if (Model.ContentPart.Enabled) { using (Html.BeginFormAntiForgeryPost(Url.Action("Disable"), FormMethod.Post, new { @class = "inline link" })) { - @Html.Hidden("themeName", (string)Model.ContentPart.Descriptor.Id) + @Html.Hidden("themeId", (string)Model.ContentPart.Descriptor.Id) } } else { using (Html.BeginFormAntiForgeryPost(Url.Action("Enable"), FormMethod.Post, new { @class = "inline link" })) { - @Html.Hidden("themeName", (string)Model.ContentPart.Descriptor.Id) + @Html.Hidden("themeId", (string)Model.ContentPart.Descriptor.Id) } } @@ -51,7 +51,7 @@ @if (Model.ContentPart.NeedsUpdate) { | using (Html.BeginFormAntiForgeryPost(Url.Action("Update"), FormMethod.Post, new { @class = "inline link" })) { - @Html.Hidden("themeName", (string)Model.ContentPart.Descriptor.Id) + @Html.Hidden("themeId", (string)Model.ContentPart.Descriptor.Id)
} } From 2ebf723fc8dbc4f3c00892a69d62aa0b05de9b71 Mon Sep 17 00:00:00 2001 From: Nathan Heskew Date: Tue, 10 May 2011 16:09:13 -0700 Subject: [PATCH 028/139] Getting PublishLater to actually publish later. work item: 17802 --HG-- branch : 1.x --- .../Orchard.PublishLater/Handlers/PublishingTaskHandler.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Orchard.Web/Modules/Orchard.PublishLater/Handlers/PublishingTaskHandler.cs b/src/Orchard.Web/Modules/Orchard.PublishLater/Handlers/PublishingTaskHandler.cs index abf14ea21..cd13080c3 100644 --- a/src/Orchard.Web/Modules/Orchard.PublishLater/Handlers/PublishingTaskHandler.cs +++ b/src/Orchard.Web/Modules/Orchard.PublishLater/Handlers/PublishingTaskHandler.cs @@ -7,9 +7,11 @@ namespace Orchard.PublishLater.Handlers { [UsedImplicitly] public class PublishingTaskHandler : IScheduledTaskHandler { private readonly IContentManager _contentManager; + private readonly IOrchardServices _orchardServices; public PublishingTaskHandler(IContentManager contentManager, IOrchardServices orchardServices) { _contentManager = contentManager; + _orchardServices = orchardServices; Logger = NullLogger.Instance; } @@ -23,6 +25,7 @@ namespace Orchard.PublishLater.Handlers { context.Task.ScheduledUtc); _contentManager.Publish(context.Task.ContentItem); + _orchardServices.ContentManager.Flush(); } } } From 123e4681f3e856151877f291b7d2fc8937718896 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Tue, 10 May 2011 20:49:17 -0700 Subject: [PATCH 029/139] Fixing Json formatting in Shape Tracing --HG-- branch : 1.x --- .../Orchard.DesignerTools/Services/ShapeTracingFactory.cs | 2 +- .../Orchard.DesignerTools/Views/ShapeTracingMeta.cshtml | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.DesignerTools/Services/ShapeTracingFactory.cs b/src/Orchard.Web/Modules/Orchard.DesignerTools/Services/ShapeTracingFactory.cs index 2f3b05b0f..6b93ab57a 100644 --- a/src/Orchard.Web/Modules/Orchard.DesignerTools/Services/ShapeTracingFactory.cs +++ b/src/Orchard.Web/Modules/Orchard.DesignerTools/Services/ShapeTracingFactory.cs @@ -190,7 +190,7 @@ namespace Orchard.DesignerTools.Services { } } - private static string FormatJsonValue(string value) { + public static string FormatJsonValue(string value) { // replace " by \" in json strings return value.Replace(@"\", @"\\").Replace("\"", @"\""").Replace("\r\n", @"\n").Replace("\r", @"\n").Replace("\n", @"\n"); } diff --git a/src/Orchard.Web/Modules/Orchard.DesignerTools/Views/ShapeTracingMeta.cshtml b/src/Orchard.Web/Modules/Orchard.DesignerTools/Views/ShapeTracingMeta.cshtml index 4616422b1..87bcf05d5 100644 --- a/src/Orchard.Web/Modules/Orchard.DesignerTools/Views/ShapeTracingMeta.cshtml +++ b/src/Orchard.Web/Modules/Orchard.DesignerTools/Views/ShapeTracingMeta.cshtml @@ -1,4 +1,5 @@ @using Orchard.Utility.Extensions; +@using Orchard.DesignerTools.Services; @functions { string FormatShapeName(string shape) { @@ -59,14 +60,14 @@ shapeTracingMetadataHost[@Model.ShapeId].shape = { } ], - html: '@RemoveEmptyLines(RemoveBeacons(Display(Model.ChildContent).ToString())).Replace(Environment.NewLine, "\\n")', - templateContent: '@(String.IsNullOrWhiteSpace((string)Model.TemplateContent) ? @T("Content not available as coming from source code.") : @Model.TemplateContent.Replace(Environment.NewLine, "\\n"))', + html: '@ShapeTracingFactory.FormatJsonValue(RemoveEmptyLines(RemoveBeacons(Display(Model.ChildContent).ToString())))', + templateContent: '@(ShapeTracingFactory.FormatJsonValue(String.IsNullOrWhiteSpace((string)Model.TemplateContent) ? @T("Content not available as coming from source code.").ToString() : (string)Model.TemplateContent))', model: { @(new MvcHtmlString((string)@Model.Dump)) } }; @if (!String.IsNullOrEmpty((string)Model.PlacementSource) && (WorkContext.HttpContext.Items[(string)Model.PlacementSource] == null)) { WorkContext.HttpContext.Items[(string)Model.PlacementSource] = new object(); -shapeTracingMetadataHost.placement['@Model.PlacementSource.ToString()'] = '@Model.PlacementContent.Replace(Environment.NewLine, "\\n")'; +shapeTracingMetadataHost.placement['@Model.PlacementSource.ToString()'] = '@ShapeTracingFactory.FormatJsonValue((string)Model.PlacementContent)'; } From 755df9e9130c72cadc480c8e4cc200f01d51e487 Mon Sep 17 00:00:00 2001 From: Andre Rodrigues Date: Wed, 11 May 2011 14:08:22 -0700 Subject: [PATCH 030/139] #17772: Making creation date editable on blog posts and pages by making it a setting on the common part --HG-- branch : 1.x --- .../Core/Common/Drivers/CommonPartDriver.cs | 72 +++++++++++++++++-- src/Orchard.Web/Core/Common/Placement.info | 1 + .../Core/Common/ResourceManifest.cs | 9 +++ .../Core/Common/Settings/CommonSettings.cs | 32 +++++++++ src/Orchard.Web/Core/Common/Styles/Web.config | 21 ++++++ .../Common/Styles/orchard-common-datetime.css | 14 ++++ .../ViewModels/CreatedUtcEditorViewModel.cs | 6 ++ .../CommonTypePartSettings.cshtml | 6 ++ .../Parts.Common.CreatedUtc.cshtml | 41 +++++++++++ src/Orchard.Web/Core/Orchard.Core.csproj | 15 ++++ .../Modules/Orchard.Blogs/Migrations.cs | 3 +- .../Orchard.Experimental/Migrations.cs | 25 +++---- .../Modules/Orchard.Pages/Migrations.cs | 5 +- 13 files changed, 225 insertions(+), 25 deletions(-) create mode 100644 src/Orchard.Web/Core/Common/ResourceManifest.cs create mode 100644 src/Orchard.Web/Core/Common/Settings/CommonSettings.cs create mode 100644 src/Orchard.Web/Core/Common/Styles/Web.config create mode 100644 src/Orchard.Web/Core/Common/Styles/orchard-common-datetime.css create mode 100644 src/Orchard.Web/Core/Common/ViewModels/CreatedUtcEditorViewModel.cs create mode 100644 src/Orchard.Web/Core/Common/Views/DefinitionTemplates/CommonTypePartSettings.cshtml create mode 100644 src/Orchard.Web/Core/Common/Views/EditorTemplates/Parts.Common.CreatedUtc.cshtml diff --git a/src/Orchard.Web/Core/Common/Drivers/CommonPartDriver.cs b/src/Orchard.Web/Core/Common/Drivers/CommonPartDriver.cs index d7e7670f5..0d2ee80ea 100644 --- a/src/Orchard.Web/Core/Common/Drivers/CommonPartDriver.cs +++ b/src/Orchard.Web/Core/Common/Drivers/CommonPartDriver.cs @@ -1,8 +1,12 @@ -using System.Xml; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml; using Orchard.ContentManagement; using Orchard.ContentManagement.Drivers; using Orchard.ContentManagement.Handlers; using Orchard.Core.Common.Models; +using Orchard.Core.Common.Settings; using Orchard.Core.Common.ViewModels; using Orchard.Localization; using Orchard.Security; @@ -16,6 +20,9 @@ namespace Orchard.Core.Common.Drivers { private readonly IMembershipService _membershipService; private readonly IClock _clock; + private const string DatePattern = "M/d/yyyy"; + private const string TimePattern = "h:mm tt"; + public CommonPartDriver( IOrchardServices services, IContentManager contentManager, @@ -51,9 +58,7 @@ namespace Orchard.Core.Common.Drivers { } protected override DriverResult Editor(CommonPart part, dynamic shapeHelper) { - return Combined( - OwnerEditor(part, null, shapeHelper), - ContainerEditor(part, null, shapeHelper)); + return BuildEditor(part, null, shapeHelper); } protected override DriverResult Editor(CommonPart part, IUpdateModel updater, dynamic shapeHelper) { @@ -61,9 +66,22 @@ namespace Orchard.Core.Common.Drivers { part.ModifiedUtc = _clock.UtcNow; part.VersionModifiedUtc = _clock.UtcNow; - return Combined( - OwnerEditor(part, updater, shapeHelper), - ContainerEditor(part, updater, shapeHelper)); + return BuildEditor(part, updater, shapeHelper); + } + + private DriverResult BuildEditor(CommonPart part, IUpdateModel updater, dynamic shapeHelper) { + List parts = new List(); + CommonTypePartSettings commonTypePartSettings = GetTypeSettings(part); + + parts.Add(OwnerEditor(part, updater, shapeHelper)); + + if (commonTypePartSettings.ShowCreatedUtcEditor) { + parts.Add(CreatedUtcEditor(part, updater, shapeHelper)); + } + + parts.Add(ContainerEditor(part, updater, shapeHelper)); + + return Combined(parts.ToArray()); } DriverResult OwnerEditor(CommonPart part, IUpdateModel updater, dynamic shapeHelper) { @@ -95,6 +113,42 @@ namespace Orchard.Core.Common.Drivers { () => shapeHelper.EditorTemplate(TemplateName: "Parts.Common.Owner", Model: model, Prefix: Prefix)); } + DriverResult CreatedUtcEditor(CommonPart part, IUpdateModel updater, dynamic shapeHelper) { + var currentUser = _authenticationService.GetAuthenticatedUser(); + if (!_authorizationService.TryCheckAccess(StandardPermissions.SiteOwner, currentUser, part)) { + return null; + } + + var model = new CreatedUtcEditorViewModel(); + if (part.CreatedUtc != null) { + model.CreatedDate = part.CreatedUtc.Value.ToLocalTime().ToString(DatePattern, CultureInfo.InvariantCulture); + model.CreatedTime = part.CreatedUtc.Value.ToLocalTime().ToString(TimePattern, CultureInfo.InvariantCulture); + } + + if (updater != null) { + updater.TryUpdateModel(model, Prefix, null, null); + + if (!string.IsNullOrWhiteSpace(model.CreatedDate) && !string.IsNullOrWhiteSpace(model.CreatedTime)) { + DateTime createdUtc; + string parseDateTime = String.Concat(model.CreatedDate, " ", model.CreatedTime); + + // use an english culture as it is the one used by jQuery.datepicker by default + if (DateTime.TryParse(parseDateTime, CultureInfo.GetCultureInfo("en-US"), DateTimeStyles.AssumeLocal, out createdUtc)) { + part.CreatedUtc = createdUtc; + } + else { + updater.AddModelError(Prefix, T("{0} is an invalid date and time", parseDateTime)); + } + } + else { + updater.AddModelError(Prefix, T("Both the date and time need to be specified.")); + } + } + + return ContentShape("Parts_Common_CreatedUtc_Edit", + () => shapeHelper.EditorTemplate(TemplateName: "Parts.Common.CreatedUtc", Model: model, Prefix: Prefix)); + } + DriverResult ContainerEditor(CommonPart part, IUpdateModel updater, dynamic shapeHelper) { var currentUser = _authenticationService.GetAuthenticatedUser(); if (!_authorizationService.TryCheckAccess(StandardPermissions.SiteOwner, currentUser, part)) { @@ -124,6 +178,10 @@ namespace Orchard.Core.Common.Drivers { () => shapeHelper.EditorTemplate(TemplateName: "Parts.Common.Container", Model: model, Prefix: Prefix)); } + private static CommonTypePartSettings GetTypeSettings(CommonPart part) { + return part.Settings.GetModel(); + } + protected override void Importing(CommonPart part, ImportContentContext context) { var owner = context.Attribute(part.PartDefinition.Name, "Owner"); if (owner != null) { diff --git a/src/Orchard.Web/Core/Common/Placement.info b/src/Orchard.Web/Core/Common/Placement.info index bffa8434f..97f25b4d9 100644 --- a/src/Orchard.Web/Core/Common/Placement.info +++ b/src/Orchard.Web/Core/Common/Placement.info @@ -12,6 +12,7 @@ + diff --git a/src/Orchard.Web/Core/Common/ResourceManifest.cs b/src/Orchard.Web/Core/Common/ResourceManifest.cs new file mode 100644 index 000000000..8e9a80c4a --- /dev/null +++ b/src/Orchard.Web/Core/Common/ResourceManifest.cs @@ -0,0 +1,9 @@ +using Orchard.UI.Resources; + +namespace Orchard.Core.Common { + public class ResourceManifest : IResourceManifestProvider { + public void BuildManifests(ResourceManifestBuilder builder) { + builder.Add().DefineStyle("Common_DatePicker").SetUrl("orchard-common-datetime.css"); + } + } +} diff --git a/src/Orchard.Web/Core/Common/Settings/CommonSettings.cs b/src/Orchard.Web/Core/Common/Settings/CommonSettings.cs new file mode 100644 index 000000000..410aca381 --- /dev/null +++ b/src/Orchard.Web/Core/Common/Settings/CommonSettings.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using Orchard.ContentManagement; +using Orchard.ContentManagement.MetaData; +using Orchard.ContentManagement.MetaData.Builders; +using Orchard.ContentManagement.MetaData.Models; +using Orchard.ContentManagement.ViewModels; + +namespace Orchard.Core.Common.Settings { + public class CommonTypePartSettings { + public bool ShowCreatedUtcEditor { get; set; } + } + + public class CommonSettingsHooks : ContentDefinitionEditorEventsBase { + public override IEnumerable TypePartEditor(ContentTypePartDefinition definition) { + if (definition.PartDefinition.Name != "CommonPart") + yield break; + + var model = definition.Settings.GetModel(); + yield return DefinitionTemplate(model); + } + + public override IEnumerable TypePartEditorUpdate(ContentTypePartDefinitionBuilder builder, IUpdateModel updateModel) { + if (builder.Name != "CommonPart") + yield break; + + var model = new CommonTypePartSettings(); + updateModel.TryUpdateModel(model, "CommonTypePartSettings", null, null); + builder.WithSetting("CommonTypePartSettings.ShowCreatedUtcEditor", model.ShowCreatedUtcEditor.ToString()); + yield return DefinitionTemplate(model); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Common/Styles/Web.config b/src/Orchard.Web/Core/Common/Styles/Web.config new file mode 100644 index 000000000..0dc62ece6 --- /dev/null +++ b/src/Orchard.Web/Core/Common/Styles/Web.config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/Orchard.Web/Core/Common/Styles/orchard-common-datetime.css b/src/Orchard.Web/Core/Common/Styles/orchard-common-datetime.css new file mode 100644 index 000000000..e31c85fe3 --- /dev/null +++ b/src/Orchard.Web/Core/Common/Styles/orchard-common-datetime.css @@ -0,0 +1,14 @@ +fieldset.createdutc-datetime { + float:left; + clear:none; + white-space: nowrap; + vertical-align: middle; +} +fieldset.createdutc-datetime legend { + display:none; +} +fieldset.createdutc-datetime input { + padding:1px; + text-align:center; + color:#666; +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Common/ViewModels/CreatedUtcEditorViewModel.cs b/src/Orchard.Web/Core/Common/ViewModels/CreatedUtcEditorViewModel.cs new file mode 100644 index 000000000..94c978aaf --- /dev/null +++ b/src/Orchard.Web/Core/Common/ViewModels/CreatedUtcEditorViewModel.cs @@ -0,0 +1,6 @@ +namespace Orchard.Core.Common.ViewModels { + public class CreatedUtcEditorViewModel { + public string CreatedDate { get; set; } + public string CreatedTime { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Common/Views/DefinitionTemplates/CommonTypePartSettings.cshtml b/src/Orchard.Web/Core/Common/Views/DefinitionTemplates/CommonTypePartSettings.cshtml new file mode 100644 index 000000000..467f326bc --- /dev/null +++ b/src/Orchard.Web/Core/Common/Views/DefinitionTemplates/CommonTypePartSettings.cshtml @@ -0,0 +1,6 @@ +@model Orchard.Core.Common.Settings.CommonTypePartSettings +
+ @Html.CheckBoxFor(m => m.ShowCreatedUtcEditor) + + @Html.ValidationMessageFor(m => m.ShowCreatedUtcEditor) +
\ No newline at end of file diff --git a/src/Orchard.Web/Core/Common/Views/EditorTemplates/Parts.Common.CreatedUtc.cshtml b/src/Orchard.Web/Core/Common/Views/EditorTemplates/Parts.Common.CreatedUtc.cshtml new file mode 100644 index 000000000..a86438bf3 --- /dev/null +++ b/src/Orchard.Web/Core/Common/Views/EditorTemplates/Parts.Common.CreatedUtc.cshtml @@ -0,0 +1,41 @@ +@model CreatedUtcEditorViewModel +@using Orchard.Core.Common.ViewModels; +@{ + Script.Require("jQueryUtils_TimePicker"); + Script.Require("jQueryUI_DatePicker"); + Style.Require("Common_DatePicker"); + Style.Require("jQueryUtils_TimePicker"); + Style.Require("jQueryUI_DatePicker"); +} +
+ @Html.LabelFor(m => m.CreatedDate, T("Created On")) + + @Html.EditorFor(m => m.CreatedDate) + + @Html.EditorFor(m => m.CreatedTime) +
+@using(Script.Foot()) { + +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Orchard.Core.csproj b/src/Orchard.Web/Core/Orchard.Core.csproj index d6932ec73..888e99645 100644 --- a/src/Orchard.Web/Core/Orchard.Core.csproj +++ b/src/Orchard.Web/Core/Orchard.Core.csproj @@ -66,7 +66,10 @@ + + + @@ -243,6 +246,7 @@
+ @@ -427,6 +431,17 @@ + + + + + + + + + Designer + + diff --git a/src/Orchard.Web/Core/Orchard.Core.csproj b/src/Orchard.Web/Core/Orchard.Core.csproj index 888e99645..2d2cbd912 100644 --- a/src/Orchard.Web/Core/Orchard.Core.csproj +++ b/src/Orchard.Web/Core/Orchard.Core.csproj @@ -200,6 +200,7 @@ + diff --git a/src/Orchard.Web/Core/Settings/Commands/SiteSettingsCommands.cs b/src/Orchard.Web/Core/Settings/Commands/SiteSettingsCommands.cs new file mode 100644 index 000000000..b0bc56eaf --- /dev/null +++ b/src/Orchard.Web/Core/Settings/Commands/SiteSettingsCommands.cs @@ -0,0 +1,53 @@ +using Orchard.Commands; +using Orchard.ContentManagement; +using Orchard.Core.Settings.Models; +using Orchard.Mvc; +using Orchard.Settings; +using Orchard.Utility.Extensions; + +namespace Orchard.Core.Settings.Commands { + public class SiteSettingsCommands : DefaultOrchardCommandHandler { + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly ISiteService _siteService; + + public SiteSettingsCommands(IHttpContextAccessor httpContextAccessor, ISiteService siteService) { + _httpContextAccessor = httpContextAccessor; + _siteService = siteService; + } + + [OrchardSwitch] + public string BaseUrl { get; set; } + + [OrchardSwitch] + public bool Force { get; set; } + + [CommandName("site setting set baseurl")] + [CommandHelp("site setting set baseurl [/BaseUrl:baseUrl] [/Force:true]\r\n\tSet the 'BaseUrl' site settings. If no base url is provided, " + + "use the current request context heuristic to discover the base url. " + + "If 'Force' is true, set the site base url even if it is already set. " + + "The default behavior is to not override the setting.")] + [OrchardSwitches("BaseUrl")] + public string SetBaseUrl() { + // Don't do anything if set and not forcing + if (Force == false && string.IsNullOrEmpty(_siteService.GetSiteSettings().BaseUrl)) { + Context.Output.WriteLine(T("'BaseUrl' site setting is already set. Use the 'Force' flag to override.")); + return null; + } + + // Retrieve request URL if BaseUrl not provided as a switch value + if (string.IsNullOrEmpty(BaseUrl)) { + if (_httpContextAccessor.Current() == null) { + Context.Output.WriteLine(T("No HTTP request available to determine the base url of the site")); + return null; + } + var request = _httpContextAccessor.Current().Request; + BaseUrl = request.ToApplicationRootUrlString(); + } + + // Update base url + _siteService.GetSiteSettings().As().BaseUrl = BaseUrl; + Context.Output.WriteLine(T("'BaseUrl' site setting set to '{0}'", BaseUrl)); + return null; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Setup/Recipes/blog.recipe.xml b/src/Orchard.Web/Modules/Orchard.Setup/Recipes/blog.recipe.xml index 17c919b6c..7ce6a5c02 100644 --- a/src/Orchard.Web/Modules/Orchard.Setup/Recipes/blog.recipe.xml +++ b/src/Orchard.Web/Modules/Orchard.Setup/Recipes/blog.recipe.xml @@ -14,7 +14,7 @@ TinyMce,Orchard.Media,Orchard.MediaPicker,Orchard.PublishLater, Orchard.jQuery,Orchard.Widgets,Orchard.Widgets.PageLayerHinting, Orchard.Scripting,Orchard.Scripting.Lightweight, - PackagingServices,Orchard.Packaging,Gallery.Updates, + PackagingServices,Orchard.Packaging,Gallery.Updates,Orchard.Warmup, TheThemeMachine" /> @@ -52,5 +52,6 @@ widget create HtmlWidget /Title:"Second Leader Aside" /Zone:"TripelSecond" /Position:"5" /Layer:"TheHomepage" /Identity:"SetupHtmlWidget2" /UseLoremIpsumText:true widget create HtmlWidget /Title:"Third Leader Aside" /Zone:"TripelThird" /Position:"5" /Layer:"TheHomepage" /Identity:"SetupHtmlWidget3" /UseLoremIpsumText:true menuitem create /MenuPosition:"1" /MenuText:"Home" /Url:"" /OnMainMenu:true + site setting set baseurl diff --git a/src/Orchard.Web/Modules/Orchard.Setup/Recipes/default.recipe.xml b/src/Orchard.Web/Modules/Orchard.Setup/Recipes/default.recipe.xml index 2e494b045..870490142 100644 --- a/src/Orchard.Web/Modules/Orchard.Setup/Recipes/default.recipe.xml +++ b/src/Orchard.Web/Modules/Orchard.Setup/Recipes/default.recipe.xml @@ -13,7 +13,7 @@ Orchard.Lists,TinyMce,Orchard.Media,Orchard.MediaPicker,Orchard.PublishLater, Orchard.jQuery,Orchard.Widgets,Orchard.Widgets.PageLayerHinting,Orchard.ContentTypes, Orchard.Scripting,Orchard.Scripting.Lightweight, - PackagingServices,Orchard.Packaging,Gallery.Updates, + PackagingServices,Orchard.Packaging,Gallery.Updates,Orchard.Warmup, TheThemeMachine" /> @@ -48,5 +48,6 @@ widget create HtmlWidget /Title:"Second Leader Aside" /Zone:"TripelSecond" /Position:"5" /Layer:"TheHomepage" /Identity:"SetupHtmlWidget2" /UseLoremIpsumText:true widget create HtmlWidget /Title:"Third Leader Aside" /Zone:"TripelThird" /Position:"5" /Layer:"TheHomepage" /Identity:"SetupHtmlWidget3" /UseLoremIpsumText:true menuitem create /MenuPosition:"1" /MenuText:"Home" /Url:"" /OnMainMenu:true + site setting set baseurl From f7e2804363c7b519f002d8854ec19ced27aa5300 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 25 May 2011 12:05:47 -0700 Subject: [PATCH 048/139] #17869: Fix medium trust issue "Assembly.Location" propery is not allowed in medium trust. Use the virtual path of the assembly file instead. Work Item: 17869 --HG-- branch : 1.x --- .../Extensions/Loaders/ReferencedExtensionLoader.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Orchard/Environment/Extensions/Loaders/ReferencedExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/ReferencedExtensionLoader.cs index f526b6558..e6a06079e 100644 --- a/src/Orchard/Environment/Extensions/Loaders/ReferencedExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/ReferencedExtensionLoader.cs @@ -53,11 +53,13 @@ namespace Orchard.Environment.Extensions.Loaders { if (assembly == null) return null; + var assemblyPath = _virtualPathProvider.Combine("~/bin", descriptor.Id + ".dll"); + return new ExtensionProbeEntry { Descriptor = descriptor, - LastWriteTimeUtc = File.GetLastWriteTimeUtc(assembly.Location), + LastWriteTimeUtc = _virtualPathProvider.GetFileLastWriteTimeUtc(assemblyPath), Loader = this, - VirtualPath = _virtualPathProvider.Combine("~/bin", descriptor.Id + ".dll") + VirtualPath = assemblyPath }; } From eb88b28443dbed463a868edff3fcb654a7517f69 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 25 May 2011 13:28:47 -0700 Subject: [PATCH 049/139] PERF: Decrease # of virtual path dependencies for Razor views Lots of duplicate virtual paths were added, as they came from different sources. --HG-- branch : 1.x --- .../Mvc/ViewEngines/Razor/IRazorCompilationEvents.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Orchard/Mvc/ViewEngines/Razor/IRazorCompilationEvents.cs b/src/Orchard/Mvc/ViewEngines/Razor/IRazorCompilationEvents.cs index 385988df1..302726714 100644 --- a/src/Orchard/Mvc/ViewEngines/Razor/IRazorCompilationEvents.cs +++ b/src/Orchard/Mvc/ViewEngines/Razor/IRazorCompilationEvents.cs @@ -85,9 +85,15 @@ namespace Orchard.Mvc.ViewEngines.Razor { } } - foreach (var virtualDependency in entry.dependencies) { - provider.AddVirtualPathDependency(virtualDependency); - } + } + + //PERF: Ensure each virtual path is present only once in the list of dependencies + var virtualDependencies = entries + .SelectMany(e => e.dependencies) + .Distinct(StringComparer.OrdinalIgnoreCase); + + foreach (var virtualDependency in virtualDependencies) { + provider.AddVirtualPathDependency(virtualDependency); } } From 0ac6091b74a99bff89992ed564caedf1b87dd851 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 25 May 2011 17:36:27 -0700 Subject: [PATCH 050/139] 17875: Too many InvalidOperationExceptions during startup Prevent common cases exception by explicitly rejecting invalid virtual paths. --HG-- branch : 1.x --- .../DefaultVirtualPathProviderTests.cs | 40 ++++++-- .../Loaders/DynamicExtensionLoader.cs | 9 +- .../VirtualPath/DefaultVirtualPathMonitor.cs | 2 +- .../VirtualPath/DefaultVirtualPathProvider.cs | 96 ++++++++++++++----- 4 files changed, 108 insertions(+), 39 deletions(-) diff --git a/src/Orchard.Tests/FileSystems/VirtualPath/DefaultVirtualPathProviderTests.cs b/src/Orchard.Tests/FileSystems/VirtualPath/DefaultVirtualPathProviderTests.cs index 2c38f595e..f0e1300da 100644 --- a/src/Orchard.Tests/FileSystems/VirtualPath/DefaultVirtualPathProviderTests.cs +++ b/src/Orchard.Tests/FileSystems/VirtualPath/DefaultVirtualPathProviderTests.cs @@ -9,13 +9,39 @@ namespace Orchard.Tests.FileSystems.VirtualPath { public void TryFileExistsTest() { StubDefaultVirtualPathProvider defaultVirtualPathProvider = new StubDefaultVirtualPathProvider(); - Assert.That(defaultVirtualPathProvider.TryFileExists("~\\a.txt"), Is.True); - Assert.That(defaultVirtualPathProvider.TryFileExists("~\\..\\a.txt"), Is.False); - Assert.That(defaultVirtualPathProvider.TryFileExists("~\\a\\..\\a.txt"), Is.True); - Assert.That(defaultVirtualPathProvider.TryFileExists("~\\a\\b\\..\\a.txt"), Is.True); - Assert.That(defaultVirtualPathProvider.TryFileExists("~\\a\\b\\..\\..\\a.txt"), Is.True); - Assert.That(defaultVirtualPathProvider.TryFileExists("~\\a\\b\\..\\..\\..\\a.txt"), Is.False); - Assert.That(defaultVirtualPathProvider.TryFileExists("~\\a\\..\\..\\b\\c.txt"), Is.False); + Assert.That(defaultVirtualPathProvider.TryFileExists("~/a.txt"), Is.True); + Assert.That(defaultVirtualPathProvider.TryFileExists("~/../a.txt"), Is.False); + Assert.That(defaultVirtualPathProvider.TryFileExists("~/a/../a.txt"), Is.True); + Assert.That(defaultVirtualPathProvider.TryFileExists("~/a/b/../a.txt"), Is.True); + Assert.That(defaultVirtualPathProvider.TryFileExists("~/a/b/../../a.txt"), Is.True); + Assert.That(defaultVirtualPathProvider.TryFileExists("~/a/b/../../../a.txt"), Is.False); + Assert.That(defaultVirtualPathProvider.TryFileExists("~/a/../../b/c.txt"), Is.False); + } + + [Test] + public void RejectMalformedVirtualPathTests() { + StubDefaultVirtualPathProvider defaultVirtualPathProvider = new StubDefaultVirtualPathProvider(); + + Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("~/a.txt"), Is.False); + Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("/a.txt"), Is.False); + + Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("~/../a.txt"), Is.True); + Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("/../a.txt"), Is.True); + + Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("~/a/../a.txt"), Is.False); + Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("/a/../a.txt"), Is.False); + + Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("~/a/b/../a.txt"), Is.False); + Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("/a/b/../a.txt"), Is.False); + + Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("~/a/b/../../a.txt"), Is.False); + Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("/a/b/../../a.txt"), Is.False); + + Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("~/a/b/../../../a.txt"), Is.True); + Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("/a/b/../../../a.txt"), Is.True); + + Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("~/a/../../b//.txt"), Is.True); + Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("/a/../../b//.txt"), Is.True); } } diff --git a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs index 8d7abcccd..63005e5f9 100644 --- a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs @@ -207,14 +207,7 @@ namespace Orchard.Environment.Extensions.Loaders { // Normalize the virtual path (avoid ".." in the path name) if (!string.IsNullOrEmpty(path)) { - try { - path = _virtualPathProvider.ToAppRelative(path); - } - catch (Exception e) { - // The initial path might have been invalid (e.g. path indicates a path outside the application root) - Logger.Information(e, "Path '{0}' cannot be made app relative", path); - path = null; - } + path = _virtualPathProvider.ToAppRelative(path); } // Attempt to reference the project / library file diff --git a/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathMonitor.cs b/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathMonitor.cs index b5a5db7ef..d6bd4c81d 100644 --- a/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathMonitor.cs +++ b/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathMonitor.cs @@ -31,7 +31,7 @@ namespace Orchard.FileSystems.VirtualPath { catch (HttpException e) { // This exception happens if trying to monitor a directory or file // inside a directory which doesn't exist - Logger.Warning(e, "Error monitoring file changes on virtual path '{0}'", virtualPath); + Logger.Information(e, "Error monitoring file changes on virtual path '{0}'", virtualPath); //TODO: Return a token monitoring first existing parent directory. } diff --git a/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs b/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs index 3678ec65b..cd5fea096 100644 --- a/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs +++ b/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs @@ -4,9 +4,16 @@ using System.IO; using System.Linq; using System.Web; using System.Web.Hosting; +using Orchard.Logging; namespace Orchard.FileSystems.VirtualPath { public class DefaultVirtualPathProvider : IVirtualPathProvider { + public DefaultVirtualPathProvider() { + Logger = NullLogger.Instance; + } + + public ILogger Logger { get; set; } + public virtual string GetDirectoryName(string virtualPath) { return Path.GetDirectoryName(virtualPath).Replace(Path.DirectorySeparatorChar, '/'); } @@ -17,7 +24,7 @@ namespace Orchard.FileSystems.VirtualPath { .GetDirectory(path) .Files .OfType() - .Select(f => ToAppRelative(f.VirtualPath)); + .Select(f => VirtualPathUtility.ToAppRelative(f.VirtualPath)); } public virtual IEnumerable ListDirectories(string path) { @@ -26,7 +33,7 @@ namespace Orchard.FileSystems.VirtualPath { .GetDirectory(path) .Directories .OfType() - .Select(d => ToAppRelative(d.VirtualPath)); + .Select(d => VirtualPathUtility.ToAppRelative(d.VirtualPath)); } public virtual string Combine(params string[] paths) { @@ -34,7 +41,65 @@ namespace Orchard.FileSystems.VirtualPath { } public virtual string ToAppRelative(string virtualPath) { - return VirtualPathUtility.ToAppRelative(virtualPath); + if (RejectMalformedVirtualPath(virtualPath)) + return null; + + try { + string result = VirtualPathUtility.ToAppRelative(virtualPath); + + // In some cases, ToAppRelative doesn't normalize the path. In those cases, + // the path is invalid. + // Example: + // ApplicationPath: /Foo + // VirtualPath : ~/Bar/../Blah/Blah2 + // Result : /Blah/Blah2 <= that is not an app relative path! + if (!result.StartsWith("~/")) { + Logger.Information("Path '{0}' cannot be made app relative: Path returned ('{1}') is not app relative.", virtualPath, result); + return null; + } + return result; + } + catch (Exception e) { + // The initial path might have been invalid (e.g. path indicates a path outside the application root) + Logger.Information(e, "Path '{0}' cannot be made app relative", virtualPath); + return null; + } + } + + /// + /// We want to reject path that contains ".." going outside of the application root. + /// ToAppRelative does that already, but we want to do the same while avoiding exceptions. + /// + /// Note: This method doesn't detect all cases of malformed paths, it merely checks + /// for *some* cases of malformed paths, so this is not a replacement for full virtual path + /// verification through VirtualPathUtilty methods. + /// + public bool RejectMalformedVirtualPath(string virtualPath) { + if (string.IsNullOrEmpty(virtualPath)) + return true; + + if (virtualPath.IndexOf("..") >= 0) { + virtualPath = virtualPath.Replace(Path.DirectorySeparatorChar, '/'); + string rootPrefix = virtualPath.StartsWith("~/") ? "~/" : virtualPath.StartsWith("/") ? "/" : ""; + if (!string.IsNullOrEmpty(rootPrefix)) { + string[] terms = virtualPath.Substring(rootPrefix.Length).Split('/'); + int depth = 0; + foreach (var term in terms) { + if (term == "..") { + if (depth == 0) { + Logger.Information("Path '{0}' cannot be made app relative: Too many '..'", virtualPath); + return true; + } + depth--; + } + else { + depth++; + } + } + } + } + + return false; } public virtual Stream OpenFile(string virtualPath) { @@ -62,29 +127,14 @@ namespace Orchard.FileSystems.VirtualPath { } public virtual bool TryFileExists(string virtualPath) { + if (RejectMalformedVirtualPath(virtualPath)) + return false; + try { - // Check if the path falls outside the root directory of the app - string directoryName = Path.GetDirectoryName(virtualPath); - - int level = 0; - int stringLength = directoryName.Count(); - - for(int i = 0 ; i < stringLength ; i++) { - if (directoryName[i] == '\\') { - if (i < (stringLength - 2) && directoryName[i + 1] == '.' && directoryName[i + 2] == '.') { - level--; - i += 2; - } else level++; - } - - if (level < 0) { - return false; - } - } - return FileExists(virtualPath); } - catch { + catch (Exception e) { + Logger.Information(e, "File '{0}' can not be checked for exitence. Assuming doesn't exist.", virtualPath); return false; } } From 56284c516ad7abd3b599af36e236ed38dbbc9cd9 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 25 May 2011 18:10:51 -0700 Subject: [PATCH 051/139] Fix typo --HG-- branch : 1.x --- .../FileSystems/VirtualPath/DefaultVirtualPathProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs b/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs index cd5fea096..ce545698d 100644 --- a/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs +++ b/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs @@ -134,7 +134,7 @@ namespace Orchard.FileSystems.VirtualPath { return FileExists(virtualPath); } catch (Exception e) { - Logger.Information(e, "File '{0}' can not be checked for exitence. Assuming doesn't exist.", virtualPath); + Logger.Information(e, "File '{0}' can not be checked for existence. Assuming doesn't exist.", virtualPath); return false; } } From 72e787ed3800167f68d3adefe75b83471cc4ac20 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 25 May 2011 19:36:19 -0700 Subject: [PATCH 052/139] Renaming method --HG-- branch : 1.x --- .../DefaultVirtualPathProviderTests.cs | 28 +++++++++---------- .../VirtualPath/DefaultVirtualPathProvider.cs | 7 +++-- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/Orchard.Tests/FileSystems/VirtualPath/DefaultVirtualPathProviderTests.cs b/src/Orchard.Tests/FileSystems/VirtualPath/DefaultVirtualPathProviderTests.cs index f0e1300da..735a8ea6e 100644 --- a/src/Orchard.Tests/FileSystems/VirtualPath/DefaultVirtualPathProviderTests.cs +++ b/src/Orchard.Tests/FileSystems/VirtualPath/DefaultVirtualPathProviderTests.cs @@ -22,26 +22,26 @@ namespace Orchard.Tests.FileSystems.VirtualPath { public void RejectMalformedVirtualPathTests() { StubDefaultVirtualPathProvider defaultVirtualPathProvider = new StubDefaultVirtualPathProvider(); - Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("~/a.txt"), Is.False); - Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("/a.txt"), Is.False); + Assert.That(defaultVirtualPathProvider.IsMalformedVirtualPath("~/a.txt"), Is.False); + Assert.That(defaultVirtualPathProvider.IsMalformedVirtualPath("/a.txt"), Is.False); - Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("~/../a.txt"), Is.True); - Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("/../a.txt"), Is.True); + Assert.That(defaultVirtualPathProvider.IsMalformedVirtualPath("~/../a.txt"), Is.True); + Assert.That(defaultVirtualPathProvider.IsMalformedVirtualPath("/../a.txt"), Is.True); - Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("~/a/../a.txt"), Is.False); - Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("/a/../a.txt"), Is.False); + Assert.That(defaultVirtualPathProvider.IsMalformedVirtualPath("~/a/../a.txt"), Is.False); + Assert.That(defaultVirtualPathProvider.IsMalformedVirtualPath("/a/../a.txt"), Is.False); - Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("~/a/b/../a.txt"), Is.False); - Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("/a/b/../a.txt"), Is.False); + Assert.That(defaultVirtualPathProvider.IsMalformedVirtualPath("~/a/b/../a.txt"), Is.False); + Assert.That(defaultVirtualPathProvider.IsMalformedVirtualPath("/a/b/../a.txt"), Is.False); - Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("~/a/b/../../a.txt"), Is.False); - Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("/a/b/../../a.txt"), Is.False); + Assert.That(defaultVirtualPathProvider.IsMalformedVirtualPath("~/a/b/../../a.txt"), Is.False); + Assert.That(defaultVirtualPathProvider.IsMalformedVirtualPath("/a/b/../../a.txt"), Is.False); - Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("~/a/b/../../../a.txt"), Is.True); - Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("/a/b/../../../a.txt"), Is.True); + Assert.That(defaultVirtualPathProvider.IsMalformedVirtualPath("~/a/b/../../../a.txt"), Is.True); + Assert.That(defaultVirtualPathProvider.IsMalformedVirtualPath("/a/b/../../../a.txt"), Is.True); - Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("~/a/../../b//.txt"), Is.True); - Assert.That(defaultVirtualPathProvider.RejectMalformedVirtualPath("/a/../../b//.txt"), Is.True); + Assert.That(defaultVirtualPathProvider.IsMalformedVirtualPath("~/a/../../b//.txt"), Is.True); + Assert.That(defaultVirtualPathProvider.IsMalformedVirtualPath("/a/../../b//.txt"), Is.True); } } diff --git a/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs b/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs index ce545698d..6b488be7d 100644 --- a/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs +++ b/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs @@ -41,7 +41,7 @@ namespace Orchard.FileSystems.VirtualPath { } public virtual string ToAppRelative(string virtualPath) { - if (RejectMalformedVirtualPath(virtualPath)) + if (IsMalformedVirtualPath(virtualPath)) return null; try { @@ -73,8 +73,9 @@ namespace Orchard.FileSystems.VirtualPath { /// Note: This method doesn't detect all cases of malformed paths, it merely checks /// for *some* cases of malformed paths, so this is not a replacement for full virtual path /// verification through VirtualPathUtilty methods. + /// In other wors, !IsMalformed does *not* imply "IsWellformed". /// - public bool RejectMalformedVirtualPath(string virtualPath) { + public bool IsMalformedVirtualPath(string virtualPath) { if (string.IsNullOrEmpty(virtualPath)) return true; @@ -127,7 +128,7 @@ namespace Orchard.FileSystems.VirtualPath { } public virtual bool TryFileExists(string virtualPath) { - if (RejectMalformedVirtualPath(virtualPath)) + if (IsMalformedVirtualPath(virtualPath)) return false; try { From 6d0b7e1d6105da6dbc7721e7c6ca4c851c4693db Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 25 May 2011 19:49:08 -0700 Subject: [PATCH 053/139] Removing method from interface The method really only makes sense for the DynamicExtensionLoader. It is used only in the DynamicModuleVirtualPathProvider only. --HG-- branch : 1.x --- .../Environment/Extensions/Loaders/DynamicExtensionLoader.cs | 5 +++-- .../Environment/Extensions/Loaders/ExtensionLoaderBase.cs | 4 ---- .../Environment/Extensions/Loaders/IExtensionLoader.cs | 1 - .../Dependencies/DynamicModuleVirtualPathProvider.cs | 2 +- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs index 63005e5f9..61160601e 100644 --- a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs @@ -57,13 +57,14 @@ namespace Orchard.Environment.Extensions.Loaders { return GetDependencies(dependency.VirtualPath); } - public override IEnumerable GetDynamicModuleDependencies(DependencyDescriptor dependency, string virtualPath) { + public IEnumerable GetDynamicModuleDependencies(DependencyDescriptor dependency, string virtualPath) { virtualPath = _virtualPathProvider.ToAppRelative(virtualPath); if (StringComparer.OrdinalIgnoreCase.Equals(virtualPath, dependency.VirtualPath)) { return GetDependencies(virtualPath); } - return base.GetDynamicModuleDependencies(dependency, virtualPath); + + return Enumerable.Empty(); } public override void Monitor(ExtensionDescriptor descriptor, Action monitor) { diff --git a/src/Orchard/Environment/Extensions/Loaders/ExtensionLoaderBase.cs b/src/Orchard/Environment/Extensions/Loaders/ExtensionLoaderBase.cs index be17465b2..7c4c887d5 100644 --- a/src/Orchard/Environment/Extensions/Loaders/ExtensionLoaderBase.cs +++ b/src/Orchard/Environment/Extensions/Loaders/ExtensionLoaderBase.cs @@ -57,9 +57,5 @@ namespace Orchard.Environment.Extensions.Loaders { public virtual IEnumerable GetWebFormVirtualDependencies(DependencyDescriptor dependency) { return Enumerable.Empty(); } - - public virtual IEnumerable GetDynamicModuleDependencies(DependencyDescriptor dependency, string virtualPath) { - return Enumerable.Empty(); - } } } \ No newline at end of file diff --git a/src/Orchard/Environment/Extensions/Loaders/IExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/IExtensionLoader.cs index 9d681c83a..bbdc74739 100644 --- a/src/Orchard/Environment/Extensions/Loaders/IExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/IExtensionLoader.cs @@ -41,6 +41,5 @@ namespace Orchard.Environment.Extensions.Loaders { string GetWebFormAssemblyDirective(DependencyDescriptor dependency); IEnumerable GetWebFormVirtualDependencies(DependencyDescriptor dependency); - IEnumerable GetDynamicModuleDependencies(DependencyDescriptor dependency, string virtualPath); } } \ No newline at end of file diff --git a/src/Orchard/FileSystems/Dependencies/DynamicModuleVirtualPathProvider.cs b/src/Orchard/FileSystems/Dependencies/DynamicModuleVirtualPathProvider.cs index b0dda1f0e..b29175928 100644 --- a/src/Orchard/FileSystems/Dependencies/DynamicModuleVirtualPathProvider.cs +++ b/src/Orchard/FileSystems/Dependencies/DynamicModuleVirtualPathProvider.cs @@ -44,7 +44,7 @@ namespace Orchard.FileSystems.Dependencies { var desc = GetDependencyDescriptor(virtualPath); if (desc != null) { - var loader = _loaders.Where(l => l.Name == desc.LoaderName).FirstOrDefault(); + var loader = _loaders.Where(l => l.Name == desc.LoaderName).FirstOrDefault() as DynamicExtensionLoader; if (loader != null) { var otherDependencies = loader.GetDynamicModuleDependencies(desc, virtualPath); From 314c9cb9a66c81a469017e32ddd3f181671d2a03 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 25 May 2011 20:56:02 -0700 Subject: [PATCH 054/139] Refactoring a couple of method related to dynamic compilation Abstract away WebForms syntax knowledge from extension loader implementations. Rename the corresponding methods to be more semantically correct. --HG-- branch : 1.x --- .../Migrations/SchemaCommandGeneratorTests.cs | 8 +--- .../ExtensionLoaderCoordinatorTests.cs | 8 +--- .../Extensions/ExtensionManagerTests.cs | 8 +--- .../Loaders/DynamicExtensionLoader.cs | 6 +-- .../Extensions/Loaders/ExtensionLoaderBase.cs | 6 +-- .../Extensions/Loaders/IExtensionLoader.cs | 21 +++++++++- .../Loaders/PrecompiledExtensionLoader.cs | 6 +-- .../WebFormsExtensionsVirtualPathProvider.cs | 40 +++++++++++++------ .../Razor/IRazorCompilationEvents.cs | 17 ++++---- 9 files changed, 70 insertions(+), 50 deletions(-) diff --git a/src/Orchard.Tests.Modules/Migrations/SchemaCommandGeneratorTests.cs b/src/Orchard.Tests.Modules/Migrations/SchemaCommandGeneratorTests.cs index 928ce7f7a..9372d4d0a 100644 --- a/src/Orchard.Tests.Modules/Migrations/SchemaCommandGeneratorTests.cs +++ b/src/Orchard.Tests.Modules/Migrations/SchemaCommandGeneratorTests.cs @@ -183,15 +183,11 @@ Features: public void Monitor(ExtensionDescriptor extension, Action monitor) { } - public string GetWebFormAssemblyDirective(DependencyDescriptor dependency) { + public IEnumerable GetCompilationReferences(DependencyDescriptor dependency) { throw new NotImplementedException(); } - public IEnumerable GetWebFormVirtualDependencies(DependencyDescriptor dependency) { - throw new NotImplementedException(); - } - - public IEnumerable GetDynamicModuleDependencies(DependencyDescriptor dependency, string virtualPath) { + public IEnumerable GetVirtualPathDependencies(DependencyDescriptor dependency) { throw new NotImplementedException(); } diff --git a/src/Orchard.Tests/Environment/Extensions/ExtensionLoaderCoordinatorTests.cs b/src/Orchard.Tests/Environment/Extensions/ExtensionLoaderCoordinatorTests.cs index f2bbbf216..457a00288 100644 --- a/src/Orchard.Tests/Environment/Extensions/ExtensionLoaderCoordinatorTests.cs +++ b/src/Orchard.Tests/Environment/Extensions/ExtensionLoaderCoordinatorTests.cs @@ -104,15 +104,11 @@ namespace Orchard.Tests.Environment.Extensions { public void Monitor(ExtensionDescriptor extension, Action monitor) { } - public string GetWebFormAssemblyDirective(DependencyDescriptor dependency) { + public IEnumerable GetCompilationReferences(DependencyDescriptor dependency) { throw new NotImplementedException(); } - public IEnumerable GetWebFormVirtualDependencies(DependencyDescriptor dependency) { - throw new NotImplementedException(); - } - - public IEnumerable GetDynamicModuleDependencies(DependencyDescriptor dependency, string virtualPath) { + public IEnumerable GetVirtualPathDependencies(DependencyDescriptor dependency) { throw new NotImplementedException(); } diff --git a/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs b/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs index 3b79bb3f0..a2d91a123 100644 --- a/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs +++ b/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs @@ -109,15 +109,11 @@ namespace Orchard.Tests.Environment.Extensions { public void Monitor(ExtensionDescriptor extension, Action monitor) { } - public string GetWebFormAssemblyDirective(DependencyDescriptor dependency) { + public IEnumerable GetCompilationReferences(DependencyDescriptor dependency) { throw new NotImplementedException(); } - public IEnumerable GetWebFormVirtualDependencies(DependencyDescriptor dependency) { - throw new NotImplementedException(); - } - - public IEnumerable GetDynamicModuleDependencies(DependencyDescriptor dependency, string virtualPath) { + public IEnumerable GetVirtualPathDependencies(DependencyDescriptor dependency) { throw new NotImplementedException(); } diff --git a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs index 61160601e..7e927f55b 100644 --- a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs @@ -48,11 +48,11 @@ namespace Orchard.Environment.Extensions.Loaders { public override int Order { get { return 100; } } - public override string GetWebFormAssemblyDirective(DependencyDescriptor dependency) { - return string.Format("<%@ Assembly Src=\"{0}\"%>", dependency.VirtualPath); + public override IEnumerable GetCompilationReferences(DependencyDescriptor dependency) { + yield return new ExtensionCompilationReference { BuildProviderTarget = dependency.VirtualPath }; } - public override IEnumerable GetWebFormVirtualDependencies(DependencyDescriptor dependency) { + public override IEnumerable GetVirtualPathDependencies(DependencyDescriptor dependency) { // Return csproj and all .cs files return GetDependencies(dependency.VirtualPath); } diff --git a/src/Orchard/Environment/Extensions/Loaders/ExtensionLoaderBase.cs b/src/Orchard/Environment/Extensions/Loaders/ExtensionLoaderBase.cs index 7c4c887d5..723778c0b 100644 --- a/src/Orchard/Environment/Extensions/Loaders/ExtensionLoaderBase.cs +++ b/src/Orchard/Environment/Extensions/Loaders/ExtensionLoaderBase.cs @@ -50,11 +50,11 @@ namespace Orchard.Environment.Extensions.Loaders { protected abstract ExtensionEntry LoadWorker(ExtensionDescriptor descriptor); - public virtual string GetWebFormAssemblyDirective(DependencyDescriptor dependency) { - return null; + public virtual IEnumerable GetCompilationReferences(DependencyDescriptor dependency) { + return Enumerable.Empty(); } - public virtual IEnumerable GetWebFormVirtualDependencies(DependencyDescriptor dependency) { + public virtual IEnumerable GetVirtualPathDependencies(DependencyDescriptor dependency) { return Enumerable.Empty(); } } diff --git a/src/Orchard/Environment/Extensions/Loaders/IExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/IExtensionLoader.cs index bbdc74739..95edd9707 100644 --- a/src/Orchard/Environment/Extensions/Loaders/IExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/IExtensionLoader.cs @@ -20,6 +20,11 @@ namespace Orchard.Environment.Extensions.Loaders { public string VirtualPath { get; set; } } + public class ExtensionCompilationReference { + public string AssemblyName { get; set; } + public string BuildProviderTarget { get; set; } + } + public interface IExtensionLoader { int Order { get; } string Name { get; } @@ -39,7 +44,19 @@ namespace Orchard.Environment.Extensions.Loaders { void Monitor(ExtensionDescriptor extension, Action monitor); - string GetWebFormAssemblyDirective(DependencyDescriptor dependency); - IEnumerable GetWebFormVirtualDependencies(DependencyDescriptor dependency); + /// + /// Return a list of references required to compile a component (e.g. a Razor or WebForm view) + /// depending on the given module. + /// Each reference can either be an assembly name or a file to pass to the + /// IBuildManager.GetCompiledAssembly() method (e.g. a module .csproj project file). + /// + IEnumerable GetCompilationReferences(DependencyDescriptor dependency); + /// + /// Return the list of dependencies (as virtual path) of the given module. + /// If any of the dependency returned in the list is updated, a component depending + /// on the assembly produced for the module must be re-compiled. + /// For example, Razor or WebForms views needs to be recompiled when a dependency of a module changes. + /// + IEnumerable GetVirtualPathDependencies(DependencyDescriptor dependency); } } \ No newline at end of file diff --git a/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs index 8bac5871b..ce9f0d59a 100644 --- a/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs @@ -40,11 +40,11 @@ namespace Orchard.Environment.Extensions.Loaders { public override int Order { get { return 30; } } - public override string GetWebFormAssemblyDirective(DependencyDescriptor dependency) { - return string.Format("<%@ Assembly Name=\"{0}\"%>", dependency.Name); + public override IEnumerable GetCompilationReferences(DependencyDescriptor dependency) { + yield return new ExtensionCompilationReference { AssemblyName = dependency.Name }; } - public override IEnumerable GetWebFormVirtualDependencies(DependencyDescriptor dependency) { + public override IEnumerable GetVirtualPathDependencies(DependencyDescriptor dependency) { yield return _assemblyProbingFolder.GetAssemblyVirtualPath(dependency.Name); } diff --git a/src/Orchard/FileSystems/Dependencies/WebFormsExtensionsVirtualPathProvider.cs b/src/Orchard/FileSystems/Dependencies/WebFormsExtensionsVirtualPathProvider.cs index 5874da5df..b39381713 100644 --- a/src/Orchard/FileSystems/Dependencies/WebFormsExtensionsVirtualPathProvider.cs +++ b/src/Orchard/FileSystems/Dependencies/WebFormsExtensionsVirtualPathProvider.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Text; using System.Web; using System.Web.Hosting; using Orchard.Environment.Extensions.Loaders; @@ -59,11 +60,11 @@ namespace Orchard.FileSystems.Dependencies { var dependencies = virtualPathDependencies .OfType() - .Concat(file.Loaders.SelectMany(dl => dl.Loader.GetWebFormVirtualDependencies(dl.Descriptor))); + .Concat(file.Loaders.SelectMany(dl => dl.Loader.GetVirtualPathDependencies(dl.Descriptor))); if (Logger.IsEnabled(LogLevel.Debug)) { Logger.Debug("GetFileHash(\"{0}\") - virtual path dependencies:", virtualPath); - foreach(var dependency in dependencies) { + foreach (var dependency in dependencies) { Logger.Debug(" Dependency: \"{0}\"", dependency); } } @@ -129,13 +130,13 @@ namespace Orchard.FileSystems.Dependencies { if (loader == null) return null; - var directive = loader.GetWebFormAssemblyDirective(dependencyDescriptor); - if (string.IsNullOrEmpty(directive)) + var references = loader.GetCompilationReferences(dependencyDescriptor).ToList(); + if (!references.Any()) return null; return new VirtualFileOverride { ModuleName = moduleName, - Directive = directive, + Directive = CreateAssemblyDirectivesString(references), Loaders = new[] { new DependencyLoader { Loader = loader, Descriptor = dependencyDescriptor } } }; } @@ -150,25 +151,40 @@ namespace Orchard.FileSystems.Dependencies { if (extension == null) return null; - var loaders = _loaders + var dependencyLoaders = _loaders .SelectMany(loader => _dependenciesFolder .LoadDescriptors() .Where(d => d.LoaderName == loader.Name), - (loader, desr) => new DependencyLoader { Loader = loader, Descriptor = desr }); + (loader, desr) => new DependencyLoader { Loader = loader, Descriptor = desr }) + .ToList(); - var directive = loaders - .Aggregate("", (s, dl) => s + dl.Loader.GetWebFormAssemblyDirective(dl.Descriptor)); + var references = dependencyLoaders + .SelectMany(dl => dl.Loader.GetCompilationReferences(dl.Descriptor)) + .ToList(); - if (string.IsNullOrEmpty(directive)) + if (!references.Any()) return null; return new VirtualFileOverride { ModuleName = "", - Directive = directive, - Loaders = loaders + Directive = CreateAssemblyDirectivesString(references), + Loaders = dependencyLoaders }; } + private string CreateAssemblyDirectivesString(IEnumerable references) { + var sb = new StringBuilder(); + foreach (var reference in references) { + if (!string.IsNullOrEmpty(reference.AssemblyName)) { + sb.AppendFormat("<%@ Assembly Name=\"{0}\"%>", reference.AssemblyName); + } + if (!string.IsNullOrEmpty(reference.BuildProviderTarget)) { + sb.AppendFormat("<%@ Assembly Src=\"{0}\"%>", reference.BuildProviderTarget); + } + } + return sb.ToString(); + } + private static string ModuleMatch(string virtualPath, string prefix) { var index = virtualPath.IndexOf('/', prefix.Length, virtualPath.Length - prefix.Length); if (index < 0) diff --git a/src/Orchard/Mvc/ViewEngines/Razor/IRazorCompilationEvents.cs b/src/Orchard/Mvc/ViewEngines/Razor/IRazorCompilationEvents.cs index 302726714..6e6b861c5 100644 --- a/src/Orchard/Mvc/ViewEngines/Razor/IRazorCompilationEvents.cs +++ b/src/Orchard/Mvc/ViewEngines/Razor/IRazorCompilationEvents.cs @@ -66,25 +66,24 @@ namespace Orchard.Mvc.ViewEngines.Razor { .Select(loader => new { loader, descriptor, - directive = loader.GetWebFormAssemblyDirective(descriptor), - dependencies = loader.GetWebFormVirtualDependencies(descriptor) + references = loader.GetCompilationReferences(descriptor), + dependencies = loader.GetVirtualPathDependencies(descriptor) })); foreach (var entry in entries) { - if (entry.directive != null) { - if (entry.directive.StartsWith("<%@ Assembly Name=\"")) { - var assembly = _assemblyLoader.Load(entry.descriptor.Name); + foreach (var reference in entry.references) { + if (!string.IsNullOrEmpty(reference.AssemblyName)) { + var assembly = _assemblyLoader.Load(reference.AssemblyName); if (assembly != null) provider.AssemblyBuilder.AddAssemblyReference(assembly); } - else if (entry.directive.StartsWith("<%@ Assembly Src=\"")) { + if (!string.IsNullOrEmpty(reference.BuildProviderTarget)) { // Returned assembly may be null if the .csproj file doesn't containt any .cs file, for example - var assembly = _buildManager.GetCompiledAssembly(entry.descriptor.VirtualPath); + var assembly = _buildManager.GetCompiledAssembly(reference.BuildProviderTarget); if (assembly != null) provider.AssemblyBuilder.AddAssemblyReference(assembly); } } - } //PERF: Ensure each virtual path is present only once in the list of dependencies @@ -99,7 +98,7 @@ namespace Orchard.Mvc.ViewEngines.Razor { private DependencyDescriptor GetModuleDependencyDescriptor(string virtualPath) { var appRelativePath = VirtualPathUtility.ToAppRelative(virtualPath); - var prefix = PrefixMatch(appRelativePath, new [] { "~/Modules/", "~/Core/"}); + var prefix = PrefixMatch(appRelativePath, new[] { "~/Modules/", "~/Core/" }); if (prefix == null) return null; From b20b68c9fdaf051a0b9aa06945d47435ed39ee69 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 25 May 2011 21:35:37 -0700 Subject: [PATCH 055/139] Expose GetFileHash method on IVirtualPathProvider --HG-- branch : 1.x --- .../Descriptors/ShapeTemplateBindingStrategyTests.cs | 8 ++++++++ .../Descriptors/StylesheetBindingStrategyTests.cs | 8 ++++++++ src/Orchard.Tests/Stubs/StubVirtualPathProvider.cs | 8 ++++++++ .../FileSystems/VirtualPath/DefaultVirtualPathProvider.cs | 8 ++++++++ .../FileSystems/VirtualPath/IVirtualPathProvider.cs | 2 ++ 5 files changed, 34 insertions(+) diff --git a/src/Orchard.Tests/DisplayManagement/Descriptors/ShapeTemplateBindingStrategyTests.cs b/src/Orchard.Tests/DisplayManagement/Descriptors/ShapeTemplateBindingStrategyTests.cs index 08df97bea..7dca10877 100644 --- a/src/Orchard.Tests/DisplayManagement/Descriptors/ShapeTemplateBindingStrategyTests.cs +++ b/src/Orchard.Tests/DisplayManagement/Descriptors/ShapeTemplateBindingStrategyTests.cs @@ -80,6 +80,14 @@ namespace Orchard.Tests.DisplayManagement.Descriptors { throw new NotImplementedException(); } + public string GetFileHash(string virtualPath) { + throw new NotImplementedException(); + } + + public string GetFileHash(string virtualPath, IEnumerable dependencies) { + throw new NotImplementedException(); + } + public bool DirectoryExists(string virtualPath) { throw new NotImplementedException(); } diff --git a/src/Orchard.Tests/DisplayManagement/Descriptors/StylesheetBindingStrategyTests.cs b/src/Orchard.Tests/DisplayManagement/Descriptors/StylesheetBindingStrategyTests.cs index 36b6aa069..daddb42d0 100644 --- a/src/Orchard.Tests/DisplayManagement/Descriptors/StylesheetBindingStrategyTests.cs +++ b/src/Orchard.Tests/DisplayManagement/Descriptors/StylesheetBindingStrategyTests.cs @@ -78,6 +78,14 @@ namespace Orchard.Tests.DisplayManagement.Descriptors { throw new NotImplementedException(); } + public string GetFileHash(string virtualPath) { + throw new NotImplementedException(); + } + + public string GetFileHash(string virtualPath, IEnumerable dependencies) { + throw new NotImplementedException(); + } + public bool DirectoryExists(string virtualPath) { throw new NotImplementedException(); } diff --git a/src/Orchard.Tests/Stubs/StubVirtualPathProvider.cs b/src/Orchard.Tests/Stubs/StubVirtualPathProvider.cs index fb8fcbfc0..d01209e7f 100644 --- a/src/Orchard.Tests/Stubs/StubVirtualPathProvider.cs +++ b/src/Orchard.Tests/Stubs/StubVirtualPathProvider.cs @@ -56,6 +56,14 @@ namespace Orchard.Tests.Stubs { return _fileSystem.GetFileEntry(ToFileSystemPath(virtualPath)).LastWriteTimeUtc; } + public string GetFileHash(string virtualPath) { + throw new NotImplementedException(); + } + + public string GetFileHash(string virtualPath, IEnumerable dependencies) { + throw new NotImplementedException(); + } + public bool DirectoryExists(string virtualPath) { return _fileSystem.GetDirectoryEntry(ToFileSystemPath(virtualPath)) != null; } diff --git a/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs b/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs index 6b488be7d..2e0ed315d 100644 --- a/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs +++ b/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs @@ -119,6 +119,14 @@ namespace Orchard.FileSystems.VirtualPath { return File.GetLastWriteTimeUtc(MapPath(virtualPath)); } + public string GetFileHash(string virtualPath) { + return GetFileHash(virtualPath, new[] {virtualPath}); + } + + public string GetFileHash(string virtualPath, IEnumerable dependencies) { + return HostingEnvironment.VirtualPathProvider.GetFileHash(virtualPath, dependencies); + } + public virtual string MapPath(string virtualPath) { return HostingEnvironment.MapPath(virtualPath); } diff --git a/src/Orchard/FileSystems/VirtualPath/IVirtualPathProvider.cs b/src/Orchard/FileSystems/VirtualPath/IVirtualPathProvider.cs index 2459c92b6..e507f0663 100644 --- a/src/Orchard/FileSystems/VirtualPath/IVirtualPathProvider.cs +++ b/src/Orchard/FileSystems/VirtualPath/IVirtualPathProvider.cs @@ -15,6 +15,8 @@ namespace Orchard.FileSystems.VirtualPath { StreamWriter CreateText(string virtualPath); Stream CreateFile(string virtualPath); DateTime GetFileLastWriteTimeUtc(string virtualPath); + string GetFileHash(string virtualPath); + string GetFileHash(string virtualPath, IEnumerable dependencies); bool DirectoryExists(string virtualPath); void CreateDirectory(string virtualPath); From 6c732af595e6e5eb10976df2496c44027ce8f955 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Thu, 26 May 2011 11:05:50 -0700 Subject: [PATCH 056/139] Implement dependency files storing state of modules compilation The file is similar to "dependencies.xml", except it contains file hash of all virtual path stored in the file. This is to ensure that the file content is updated when any file hash changes. This will be used in an upcoming bug fix. --HG-- branch : 1.x --- .../Extensions/ExtensionLoaderCoordinator.cs | 5 +- src/Orchard/Environment/OrchardStarter.cs | 1 + .../DefaultModuleDependenciesManager.cs | 103 ++++++++++++++++++ .../IModuleDependenciesManager.cs | 9 ++ src/Orchard/Orchard.Framework.csproj | 2 + 5 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 src/Orchard/FileSystems/Dependencies/DefaultModuleDependenciesManager.cs create mode 100644 src/Orchard/FileSystems/Dependencies/IModuleDependenciesManager.cs diff --git a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs index 094cdbe04..f61107126 100644 --- a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs +++ b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; -using System.Web.Mvc; using Orchard.Caching; using Orchard.Environment.Extensions.Loaders; using Orchard.Environment.Extensions.Models; @@ -15,6 +14,7 @@ using Orchard.Utility; namespace Orchard.Environment.Extensions { public class ExtensionLoaderCoordinator : IExtensionLoaderCoordinator { private readonly IDependenciesFolder _dependenciesFolder; + private readonly IModuleDependenciesManager _moduleDependenciesManager; private readonly IExtensionManager _extensionManager; private readonly IVirtualPathProvider _virtualPathProvider; private readonly IVirtualPathMonitor _virtualPathMonitor; @@ -24,6 +24,7 @@ namespace Orchard.Environment.Extensions { public ExtensionLoaderCoordinator( IDependenciesFolder dependenciesFolder, + IModuleDependenciesManager moduleDependenciesManager, IExtensionManager extensionManager, IVirtualPathProvider virtualPathProvider, IVirtualPathMonitor virtualPathMonitor, @@ -32,6 +33,7 @@ namespace Orchard.Environment.Extensions { IBuildManager buildManager) { _dependenciesFolder = dependenciesFolder; + _moduleDependenciesManager = moduleDependenciesManager; _extensionManager = extensionManager; _virtualPathProvider = virtualPathProvider; _virtualPathMonitor = virtualPathMonitor; @@ -72,6 +74,7 @@ namespace Orchard.Environment.Extensions { // And finally save the new entries in the dependencies folder _dependenciesFolder.StoreDescriptors(context.NewDependencies); + _moduleDependenciesManager.StoreDependencies(context.NewDependencies); Logger.Information("Done loading extensions..."); diff --git a/src/Orchard/Environment/OrchardStarter.cs b/src/Orchard/Environment/OrchardStarter.cs index 41fd63bda..6f05683d3 100644 --- a/src/Orchard/Environment/OrchardStarter.cs +++ b/src/Orchard/Environment/OrchardStarter.cs @@ -60,6 +60,7 @@ namespace Orchard.Environment { RegisterVolatileProvider(builder); RegisterVolatileProvider(builder); RegisterVolatileProvider(builder); + RegisterVolatileProvider(builder); RegisterVolatileProvider(builder); RegisterVolatileProvider(builder); RegisterVolatileProvider(builder); diff --git a/src/Orchard/FileSystems/Dependencies/DefaultModuleDependenciesManager.cs b/src/Orchard/FileSystems/Dependencies/DefaultModuleDependenciesManager.cs new file mode 100644 index 000000000..8d80b99fa --- /dev/null +++ b/src/Orchard/FileSystems/Dependencies/DefaultModuleDependenciesManager.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; +using Orchard.Caching; +using Orchard.FileSystems.AppData; +using Orchard.FileSystems.VirtualPath; +using Orchard.Logging; + +namespace Orchard.FileSystems.Dependencies { + /// + /// Similar to "Dependencies.xml" file, except we also store "GetFileHash" result for every + /// VirtualPath entry. This is so that if any virtual path reference in the file changes, + /// the file stored by this component will also change. + /// + public class DefaultModuleDependenciesManager : IModuleDependenciesManager { + private readonly IAppDataFolder _appDataFolder; + private readonly IVirtualPathProvider _virtualPathProvider; + private const string BasePath = "Dependencies"; + private const string FileName = "Dependencies.ModuleCompilation.xml"; + + public DefaultModuleDependenciesManager(IAppDataFolder appDataFolder, IVirtualPathProvider virtualPathProvider) { + _appDataFolder = appDataFolder; + _virtualPathProvider = virtualPathProvider; + Logger = NullLogger.Instance; + } + + public ILogger Logger { get; set; } + + private string PersistencePath { + get { return _appDataFolder.Combine(BasePath, FileName); } + } + + public void StoreDependencies(IEnumerable dependencyDescriptors) { + var newDocument = CreateDocument(dependencyDescriptors); + var previousDocument = ReadDocument(PersistencePath); + if (CompareXmlDocuments(newDocument, previousDocument)) { + Logger.Debug("Existing document is identical to new one. Skipping save."); + return; + } + + WriteDocument(PersistencePath, newDocument); + } + + public IEnumerable GetVirtualPathDependencies(DependencyDescriptor descriptor) { + // Currently, we return the same file for every module. An improvement would be to return + // a specific file per module (this would decrease the number of recompilations needed + // when modules change on disk). + yield return _appDataFolder.GetVirtualPath(PersistencePath); + } + + private XDocument CreateDocument(IEnumerable dependencies) { + Func ns = (name => XName.Get(name)); + + var document = new XDocument(); + document.Add(new XElement(ns("Dependencies"))); + var elements = dependencies.Select(d => new XElement("Dependency", + new XElement(ns("ModuleName"), d.Name), + new XElement(ns("LoaderName"), d.LoaderName), + new XElement(ns("VirtualPath"), d.VirtualPath), + new XElement(ns("FileHash"), _virtualPathProvider.GetFileHash(d.VirtualPath)), + new XElement(ns("References"), d.References + .Select(r => new XElement(ns("Reference"), + new XElement(ns("Name"), r.Name), + new XElement(ns("LoaderName"), r.LoaderName), + new XElement(ns("VirtualPath"), r.VirtualPath), + new XElement(ns("FileHash"), _virtualPathProvider.GetFileHash(r.VirtualPath)))).ToArray()))); + + document.Root.Add(elements); + return document; + } + + private void WriteDocument(string persistancePath, XDocument document) { + using (var stream = _appDataFolder.CreateFile(persistancePath)) { + document.Save(stream, SaveOptions.None); + stream.Close(); + } + } + + private XDocument ReadDocument(string persistancePath) { + if (!_appDataFolder.FileExists(persistancePath)) + return new XDocument(); + + try { + using (var stream = _appDataFolder.OpenFile(persistancePath)) { + return XDocument.Load(stream); + } + } + catch(Exception e) { + Logger.Information(e, "Error reading file '{0}'", persistancePath); + return new XDocument(); + } + } + + private bool CompareXmlDocuments(XDocument doc1, XDocument doc2) { + return XNode.DeepEquals(doc1.Root, doc2.Root); + } + + private class InvalidationToken : IVolatileToken { + public bool IsCurrent { get; set; } + } + } +} \ No newline at end of file diff --git a/src/Orchard/FileSystems/Dependencies/IModuleDependenciesManager.cs b/src/Orchard/FileSystems/Dependencies/IModuleDependenciesManager.cs new file mode 100644 index 000000000..b183aebbe --- /dev/null +++ b/src/Orchard/FileSystems/Dependencies/IModuleDependenciesManager.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; +using Orchard.Caching; + +namespace Orchard.FileSystems.Dependencies { + public interface IModuleDependenciesManager : IVolatileProvider { + void StoreDependencies(IEnumerable dependencyDescriptors); + IEnumerable GetVirtualPathDependencies(DependencyDescriptor descriptor); + } +} \ No newline at end of file diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index 314b697ca..82efc56a2 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -191,6 +191,8 @@ + + From 7a4e86e6a533df2a1839f744c42be971d157d383 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Thu, 26 May 2011 11:06:20 -0700 Subject: [PATCH 057/139] Virtual paths are case insensitive. --HG-- branch : 1.x --- .../Environment/Extensions/Loaders/DynamicExtensionLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs index 7e927f55b..cabdcfda5 100644 --- a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs @@ -184,7 +184,7 @@ namespace Orchard.Environment.Extensions.Loaders { } protected IEnumerable GetDependencies(string projectPath) { - var dependencies = new HashSet { projectPath }; + var dependencies = new HashSet(StringComparer.OrdinalIgnoreCase) { projectPath }; AddDependencies(projectPath, dependencies); From 6889ba4328e02c85604e2a364336a46287ff0793 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Thu, 26 May 2011 16:28:28 -0700 Subject: [PATCH 058/139] Perf: Include references from module project files only In particular, files from ~/Core should not be included in the list of dependencies, are Orchard.Core.dll is assumed to be in "~/bin", so any change to it will force a whole site recompilation anyways. --HG-- branch : 1.x --- .../Loaders/DynamicExtensionLoader.cs | 86 ++++++++++--------- 1 file changed, 46 insertions(+), 40 deletions(-) diff --git a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs index cabdcfda5..c75a7d94b 100644 --- a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs @@ -21,6 +21,7 @@ namespace Orchard.Environment.Extensions.Loaders { private readonly IDependenciesFolder _dependenciesFolder; private readonly IProjectFileParser _projectFileParser; private readonly ReloadWorkaround _reloadWorkaround = new ReloadWorkaround(); + private readonly string[] _modulesPrefixes = { "~/Modules/", "~/Themes/" }; public DynamicExtensionLoader( IBuildManager buildManager, @@ -104,13 +105,13 @@ namespace Orchard.Environment.Extensions.Loaders { var projectFile = _projectFileParser.Parse(projectPath); - return projectFile.References.Select(r => new ExtensionReferenceProbeEntry { - Descriptor = descriptor, - Loader = this, - Name = r.SimpleName, - VirtualPath = _virtualPathProvider.GetProjectReferenceVirtualPath(projectPath, r.SimpleName, r.Path) - }); - } + return projectFile.References.Select(r => new ExtensionReferenceProbeEntry { + Descriptor = descriptor, + Loader = this, + Name = r.SimpleName, + VirtualPath = _virtualPathProvider.GetProjectReferenceVirtualPath(projectPath, r.SimpleName, r.Path) + }); + } public override void ReferenceActivated(ExtensionLoadingContext context, ExtensionReferenceProbeEntry referenceEntry) { //Note: This is the same implementation as "PrecompiledExtensionLoader" @@ -184,51 +185,56 @@ namespace Orchard.Environment.Extensions.Loaders { } protected IEnumerable GetDependencies(string projectPath) { - var dependencies = new HashSet(StringComparer.OrdinalIgnoreCase) { projectPath }; - + var dependencies = new HashSet(StringComparer.OrdinalIgnoreCase); AddDependencies(projectPath, dependencies); - return dependencies; } private void AddDependencies(string projectPath, HashSet currentSet) { + // Skip files from locations other than "~/Modules" and "~/Themes" + if (string.IsNullOrEmpty(PrefixMatch(projectPath, _modulesPrefixes))) { + return; + } + + // Add project path + currentSet.Add(projectPath); + + // Add source file paths + var projectFile = _projectFileParser.Parse(projectPath); string basePath = _virtualPathProvider.GetDirectoryName(projectPath); + currentSet.UnionWith(projectFile.SourceFilenames.Select(f => _virtualPathProvider.Combine(basePath, f))); - ProjectFileDescriptor projectFile = _projectFileParser.Parse(projectPath); + // Add Project and Library references + if (projectFile.References != null) { + foreach (ReferenceDescriptor referenceDescriptor in projectFile.References.Where(reference => !string.IsNullOrEmpty(reference.Path))) { + string path = referenceDescriptor.ReferenceType == ReferenceType.Library + ? _virtualPathProvider.GetProjectReferenceVirtualPath(projectPath, referenceDescriptor.SimpleName, referenceDescriptor.Path) + : _virtualPathProvider.Combine(basePath, referenceDescriptor.Path); - // Add source files - currentSet.UnionWith(projectFile.SourceFilenames.Select(f => _virtualPathProvider.Combine(basePath, f))); + // Normalize the virtual path (avoid ".." in the path name) + if (!string.IsNullOrEmpty(path)) { + path = _virtualPathProvider.ToAppRelative(path); + } - // Add Project and Library references - if (projectFile.References != null) { - foreach (ReferenceDescriptor referenceDescriptor in projectFile.References.Where(reference => !string.IsNullOrEmpty(reference.Path))) { - string path = referenceDescriptor.ReferenceType == ReferenceType.Library - ? _virtualPathProvider.GetProjectReferenceVirtualPath(projectPath, referenceDescriptor.SimpleName, referenceDescriptor.Path) - : _virtualPathProvider.Combine(basePath, referenceDescriptor.Path); - - // Normalize the virtual path (avoid ".." in the path name) - if (!string.IsNullOrEmpty(path)) { - path = _virtualPathProvider.ToAppRelative(path); - } - - // Attempt to reference the project / library file - if (!string.IsNullOrEmpty(path) && !currentSet.Contains(path) && _virtualPathProvider.TryFileExists(path)) { - currentSet.Add(path); - - // In case of project, also reference the source files - if (referenceDescriptor.ReferenceType == ReferenceType.Project) { + // Attempt to reference the project / library file + if (!string.IsNullOrEmpty(path) && !currentSet.Contains(path) && _virtualPathProvider.TryFileExists(path)) { + switch (referenceDescriptor.ReferenceType) { + case ReferenceType.Project: AddDependencies(path, currentSet); - - // Try to also reference any pre-built DLL - DependencyDescriptor dependencyDescriptor = _dependenciesFolder.GetDescriptor(_virtualPathProvider.GetDirectoryName(referenceDescriptor.Path)); - if (dependencyDescriptor != null && _virtualPathProvider.TryFileExists(dependencyDescriptor.VirtualPath)) { - currentSet.Add(dependencyDescriptor.VirtualPath); - } - } + break; + case ReferenceType.Library: + currentSet.Add(path); + break; } } } } + } + + private static string PrefixMatch(string virtualPath, params string[] prefixes) { + return prefixes + .FirstOrDefault(p => virtualPath.StartsWith(p, StringComparison.OrdinalIgnoreCase)); + } private string GetProjectPath(ExtensionDescriptor descriptor) { string projectPath = _virtualPathProvider.Combine(descriptor.Location, descriptor.Id, @@ -254,14 +260,14 @@ namespace Orchard.Environment.Extensions.Loaders { private readonly List _tokens = new List(); public void Monitor(IVolatileToken whenProjectFileChanges) { - lock(_tokens) { + lock (_tokens) { _tokens.Add(whenProjectFileChanges); } } public bool AppDomainRestartNeeded { get { - lock(_tokens) { + lock (_tokens) { return _tokens.Any(t => t.IsCurrent == false); } } From 1a30d2492f00367158d7afcbb513ca97f2b5d8ad Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Thu, 26 May 2011 16:40:05 -0700 Subject: [PATCH 059/139] Refactor interaction between Dynamic loader and Dynamic VPP --HG-- branch : 1.x --- .../Loaders/DynamicExtensionLoader.cs | 15 +++------ .../DynamicModuleVirtualPathProvider.cs | 33 ++++++++++--------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs index c75a7d94b..61502f6bf 100644 --- a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs @@ -13,6 +13,8 @@ using Orchard.Utility.Extensions; namespace Orchard.Environment.Extensions.Loaders { public class DynamicExtensionLoader : ExtensionLoaderBase { + public static readonly string[] ExtensionsVirtualPathPrefixes = { "~/Modules/", "~/Themes/" }; + private readonly IBuildManager _buildManager; private readonly IVirtualPathProvider _virtualPathProvider; private readonly IVirtualPathMonitor _virtualPathMonitor; @@ -21,7 +23,6 @@ namespace Orchard.Environment.Extensions.Loaders { private readonly IDependenciesFolder _dependenciesFolder; private readonly IProjectFileParser _projectFileParser; private readonly ReloadWorkaround _reloadWorkaround = new ReloadWorkaround(); - private readonly string[] _modulesPrefixes = { "~/Modules/", "~/Themes/" }; public DynamicExtensionLoader( IBuildManager buildManager, @@ -58,14 +59,8 @@ namespace Orchard.Environment.Extensions.Loaders { return GetDependencies(dependency.VirtualPath); } - public IEnumerable GetDynamicModuleDependencies(DependencyDescriptor dependency, string virtualPath) { - virtualPath = _virtualPathProvider.ToAppRelative(virtualPath); - - if (StringComparer.OrdinalIgnoreCase.Equals(virtualPath, dependency.VirtualPath)) { - return GetDependencies(virtualPath); - } - - return Enumerable.Empty(); + public IEnumerable GetFileHashDependencies(DependencyDescriptor dependency) { + return GetDependencies(dependency.VirtualPath); } public override void Monitor(ExtensionDescriptor descriptor, Action monitor) { @@ -192,7 +187,7 @@ namespace Orchard.Environment.Extensions.Loaders { private void AddDependencies(string projectPath, HashSet currentSet) { // Skip files from locations other than "~/Modules" and "~/Themes" - if (string.IsNullOrEmpty(PrefixMatch(projectPath, _modulesPrefixes))) { + if (string.IsNullOrEmpty(PrefixMatch(projectPath, ExtensionsVirtualPathPrefixes))) { return; } diff --git a/src/Orchard/FileSystems/Dependencies/DynamicModuleVirtualPathProvider.cs b/src/Orchard/FileSystems/Dependencies/DynamicModuleVirtualPathProvider.cs index b29175928..e6d09e643 100644 --- a/src/Orchard/FileSystems/Dependencies/DynamicModuleVirtualPathProvider.cs +++ b/src/Orchard/FileSystems/Dependencies/DynamicModuleVirtualPathProvider.cs @@ -16,7 +16,6 @@ namespace Orchard.FileSystems.Dependencies { public class DynamicModuleVirtualPathProvider : VirtualPathProvider, ICustomVirtualPathProvider { private readonly IDependenciesFolder _dependenciesFolder; private readonly IEnumerable _loaders; - private readonly string[] _modulesPrefixes = { "~/Modules/", "~/Themes/" }; public DynamicModuleVirtualPathProvider(IDependenciesFolder dependenciesFolder, IEnumerable loaders) { _dependenciesFolder = dependenciesFolder; @@ -41,25 +40,30 @@ namespace Orchard.FileSystems.Dependencies { } private string GetFileHashWorker(string virtualPath, IEnumerable virtualPathDependencies) { + virtualPath = VirtualPathUtility.ToAppRelative(virtualPath); + var desc = GetDependencyDescriptor(virtualPath); if (desc != null) { + // We are only interested in ".csproj" files loaded from "DynamicExtensionLoader" + var dynamicExtensionLoader = _loaders.Where(l => l.Name == desc.LoaderName).FirstOrDefault() as DynamicExtensionLoader; + if (dynamicExtensionLoader != null) { - var loader = _loaders.Where(l => l.Name == desc.LoaderName).FirstOrDefault() as DynamicExtensionLoader; - if (loader != null) { + if (virtualPath.Equals(desc.VirtualPath, StringComparison.OrdinalIgnoreCase)) { - var otherDependencies = loader.GetDynamicModuleDependencies(desc, virtualPath); - if (otherDependencies.Any()) { + var otherDependencies = dynamicExtensionLoader.GetFileHashDependencies(desc); + if (otherDependencies.Any()) { - var allDependencies = virtualPathDependencies.OfType().Concat(otherDependencies); + var allDependencies = virtualPathDependencies.OfType().Concat(otherDependencies).ToList(); - if (Logger.IsEnabled(LogLevel.Debug)) { - Logger.Debug("GetFileHash(\"{0}\") - virtual path dependencies:", virtualPath); - foreach (var dependency in allDependencies) { - Logger.Debug(" Dependency: \"{0}\"", dependency); + if (Logger.IsEnabled(LogLevel.Debug)) { + Logger.Debug("GetFileHash(\"{0}\") - virtual path dependencies:", virtualPath); + foreach (var dependency in allDependencies) { + Logger.Debug(" Dependency: \"{0}\"", dependency); + } } - } - return base.GetFileHash(virtualPath, allDependencies); + return base.GetFileHash(virtualPath, allDependencies); + } } } } @@ -71,12 +75,11 @@ namespace Orchard.FileSystems.Dependencies { } private DependencyDescriptor GetDependencyDescriptor(string virtualPath) { - var appRelativePath = VirtualPathUtility.ToAppRelative(virtualPath); - var prefix = PrefixMatch(appRelativePath, _modulesPrefixes); + var prefix = PrefixMatch(virtualPath, DynamicExtensionLoader.ExtensionsVirtualPathPrefixes); if (prefix == null) return null; - var moduleName = ModuleMatch(appRelativePath, prefix); + var moduleName = ModuleMatch(virtualPath, prefix); if (moduleName == null) return null; From 7084ecbbbc728eb1f91c9f9f9c8ac0c78f27ee76 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Thu, 26 May 2011 18:11:51 -0700 Subject: [PATCH 060/139] #17804: Ensure views are recompiled more aggressively When a module changes, it's loader can change, which means the list of dependencies can change to. Instead of trying to track the list of dependencies, we now simply add a dependency on a file on disk which describe the module compilation state. As an aside, this also improves startup performance when there were no changes to a site, since asp.net now only has to check a single file instead of the list of dependencies. --HG-- branch : 1.x --- .../DefaultModuleDependenciesManager.cs | 49 +++++++++++++------ .../WebFormsExtensionsVirtualPathProvider.cs | 8 ++- .../Razor/IRazorCompilationEvents.cs | 5 +- 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/Orchard/FileSystems/Dependencies/DefaultModuleDependenciesManager.cs b/src/Orchard/FileSystems/Dependencies/DefaultModuleDependenciesManager.cs index 8d80b99fa..99a1566cf 100644 --- a/src/Orchard/FileSystems/Dependencies/DefaultModuleDependenciesManager.cs +++ b/src/Orchard/FileSystems/Dependencies/DefaultModuleDependenciesManager.cs @@ -43,10 +43,12 @@ namespace Orchard.FileSystems.Dependencies { } public IEnumerable GetVirtualPathDependencies(DependencyDescriptor descriptor) { - // Currently, we return the same file for every module. An improvement would be to return - // a specific file per module (this would decrease the number of recompilations needed - // when modules change on disk). - yield return _appDataFolder.GetVirtualPath(PersistencePath); + if (IsSupportedLoader(descriptor.LoaderName)) { + // Currently, we return the same file for every module. An improvement would be to return + // a specific file per module (this would decrease the number of recompilations needed + // when modules change on disk). + yield return _appDataFolder.GetVirtualPath(PersistencePath); + } } private XDocument CreateDocument(IEnumerable dependencies) { @@ -54,22 +56,39 @@ 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.Name), - new XElement(ns("LoaderName"), d.LoaderName), - new XElement(ns("VirtualPath"), d.VirtualPath), - new XElement(ns("FileHash"), _virtualPathProvider.GetFileHash(d.VirtualPath)), - new XElement(ns("References"), d.References - .Select(r => new XElement(ns("Reference"), - new XElement(ns("Name"), r.Name), - new XElement(ns("LoaderName"), r.LoaderName), - new XElement(ns("VirtualPath"), r.VirtualPath), - new XElement(ns("FileHash"), _virtualPathProvider.GetFileHash(r.VirtualPath)))).ToArray()))); + var elements = FilterDependencies(dependencies).Select( + d => new XElement("Dependency", + new XElement(ns("ModuleName"), d.Name), + new XElement(ns("LoaderName"), d.LoaderName), + new XElement(ns("VirtualPath"), d.VirtualPath), + new XElement(ns("FileHash"), _virtualPathProvider.GetFileHash(d.VirtualPath)), + new XElement(ns("References"), FilterReferences(d.References) + .Select(r => new XElement(ns("Reference"), + new XElement(ns("Name"), r.Name), + new XElement(ns("LoaderName"), r.LoaderName), + new XElement(ns("VirtualPath"), r.VirtualPath), + new XElement(ns("FileHash"), _virtualPathProvider.GetFileHash(r.VirtualPath)))).ToArray()))); document.Root.Add(elements); return document; } + private IEnumerable FilterDependencies(IEnumerable dependencies) { + return dependencies.Where(dep => IsSupportedLoader(dep.LoaderName)); + } + + private IEnumerable FilterReferences(IEnumerable references) { + return references.Where(dep => IsSupportedLoader(dep.LoaderName)); + } + + private bool IsSupportedLoader(string loaderName) { + //Note: this is hard-coded for now, to avoid adding more responsibilities to the IExtensionLoader + // implementations. + return + loaderName == "DynamicExtensionLoader" || + loaderName == "PrecompiledExtensionLoader"; + } + private void WriteDocument(string persistancePath, XDocument document) { using (var stream = _appDataFolder.CreateFile(persistancePath)) { document.Save(stream, SaveOptions.None); diff --git a/src/Orchard/FileSystems/Dependencies/WebFormsExtensionsVirtualPathProvider.cs b/src/Orchard/FileSystems/Dependencies/WebFormsExtensionsVirtualPathProvider.cs index b39381713..a02a3cf5d 100644 --- a/src/Orchard/FileSystems/Dependencies/WebFormsExtensionsVirtualPathProvider.cs +++ b/src/Orchard/FileSystems/Dependencies/WebFormsExtensionsVirtualPathProvider.cs @@ -21,13 +21,15 @@ namespace Orchard.FileSystems.Dependencies { /// public class WebFormVirtualPathProvider : VirtualPathProvider, ICustomVirtualPathProvider { private readonly IDependenciesFolder _dependenciesFolder; + private readonly IModuleDependenciesManager _moduleDependenciesManager; private readonly IEnumerable _loaders; private readonly string[] _modulesPrefixes = { "~/Modules/" }; private readonly string[] _themesPrefixes = { "~/Themes/" }; private readonly string[] _extensions = { ".ascx", ".aspx", ".master" }; - public WebFormVirtualPathProvider(IDependenciesFolder dependenciesFolder, IEnumerable loaders) { + public WebFormVirtualPathProvider(IDependenciesFolder dependenciesFolder, IModuleDependenciesManager moduleDependenciesManager, IEnumerable loaders) { _dependenciesFolder = dependenciesFolder; + _moduleDependenciesManager = moduleDependenciesManager; _loaders = loaders; Logger = NullLogger.Instance; } @@ -60,7 +62,9 @@ namespace Orchard.FileSystems.Dependencies { var dependencies = virtualPathDependencies .OfType() - .Concat(file.Loaders.SelectMany(dl => dl.Loader.GetVirtualPathDependencies(dl.Descriptor))); + .Concat(file.Loaders.SelectMany(dl => _moduleDependenciesManager.GetVirtualPathDependencies(dl.Descriptor))) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); if (Logger.IsEnabled(LogLevel.Debug)) { Logger.Debug("GetFileHash(\"{0}\") - virtual path dependencies:", virtualPath); diff --git a/src/Orchard/Mvc/ViewEngines/Razor/IRazorCompilationEvents.cs b/src/Orchard/Mvc/ViewEngines/Razor/IRazorCompilationEvents.cs index 6e6b861c5..79fd2cf79 100644 --- a/src/Orchard/Mvc/ViewEngines/Razor/IRazorCompilationEvents.cs +++ b/src/Orchard/Mvc/ViewEngines/Razor/IRazorCompilationEvents.cs @@ -25,17 +25,20 @@ namespace Orchard.Mvc.ViewEngines.Razor { /// public class DefaultRazorCompilationEvents : IRazorCompilationEvents { private readonly IDependenciesFolder _dependenciesFolder; + private readonly IModuleDependenciesManager _moduleDependenciesManager; private readonly IBuildManager _buildManager; private readonly IEnumerable _loaders; private readonly IAssemblyLoader _assemblyLoader; public DefaultRazorCompilationEvents( IDependenciesFolder dependenciesFolder, + IModuleDependenciesManager moduleDependenciesManager, IBuildManager buildManager, IEnumerable loaders, IAssemblyLoader assemblyLoader) { _dependenciesFolder = dependenciesFolder; + _moduleDependenciesManager = moduleDependenciesManager; _buildManager = buildManager; _loaders = loaders; _assemblyLoader = assemblyLoader; @@ -67,7 +70,7 @@ namespace Orchard.Mvc.ViewEngines.Razor { loader, descriptor, references = loader.GetCompilationReferences(descriptor), - dependencies = loader.GetVirtualPathDependencies(descriptor) + dependencies = _moduleDependenciesManager.GetVirtualPathDependencies(descriptor) })); foreach (var entry in entries) { From 852d21fee94fdc5e96f6ea3508e11b4e7de4f8f0 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Thu, 26 May 2011 20:41:02 -0700 Subject: [PATCH 061/139] Fix incorrect condition --HG-- branch : 1.x --- .../Core/Settings/Commands/SiteSettingsCommands.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Orchard.Web/Core/Settings/Commands/SiteSettingsCommands.cs b/src/Orchard.Web/Core/Settings/Commands/SiteSettingsCommands.cs index b0bc56eaf..067f42c3e 100644 --- a/src/Orchard.Web/Core/Settings/Commands/SiteSettingsCommands.cs +++ b/src/Orchard.Web/Core/Settings/Commands/SiteSettingsCommands.cs @@ -29,9 +29,11 @@ namespace Orchard.Core.Settings.Commands { [OrchardSwitches("BaseUrl")] public string SetBaseUrl() { // Don't do anything if set and not forcing - if (Force == false && string.IsNullOrEmpty(_siteService.GetSiteSettings().BaseUrl)) { - Context.Output.WriteLine(T("'BaseUrl' site setting is already set. Use the 'Force' flag to override.")); - return null; + if (!string.IsNullOrEmpty(_siteService.GetSiteSettings().BaseUrl)) { + if (!Force) { + Context.Output.WriteLine(T("'BaseUrl' site setting is already set. Use the 'Force' flag to override.")); + return null; + } } // Retrieve request URL if BaseUrl not provided as a switch value From c51f4ce2de08ef18c76c7da29a37917d5d5c5501 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Fri, 27 May 2011 10:45:28 -0700 Subject: [PATCH 062/139] PERF: Adding more logging calls related to performance tracking Also added a few "Disabled" checks in extension loaders to avoid extra-work when they are disabled. --HG-- branch : 1.x --- .../Extensions/ExtensionManager.cs | 7 ++++- .../Loaders/DynamicExtensionLoader.cs | 31 ++++++++++++++++--- .../Loaders/PrecompiledExtensionLoader.cs | 27 ++++++++++++++-- 3 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/Orchard/Environment/Extensions/ExtensionManager.cs b/src/Orchard/Environment/Extensions/ExtensionManager.cs index a6636bfc3..19e80c12b 100644 --- a/src/Orchard/Environment/Extensions/ExtensionManager.cs +++ b/src/Orchard/Environment/Extensions/ExtensionManager.cs @@ -71,9 +71,14 @@ namespace Orchard.Environment.Extensions { } public IEnumerable LoadFeatures(IEnumerable featureDescriptors) { - return featureDescriptors + Logger.Information("Loading features"); + + var result = featureDescriptors .Select(descriptor => _cacheManager.Get(descriptor.Id, ctx => LoadFeature(descriptor))) .ToArray(); + + Logger.Information("Done loading features"); + return result; } private Feature LoadFeature(FeatureDescriptor featureDescriptor) { diff --git a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs index 61502f6bf..b3d6d23f0 100644 --- a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs @@ -94,18 +94,26 @@ namespace Orchard.Environment.Extensions.Loaders { } public override IEnumerable ProbeReferences(ExtensionDescriptor descriptor) { + if (Disabled) + return Enumerable.Empty(); + + Logger.Information("Probing references for module '{0}'", descriptor.Id); + string projectPath = GetProjectPath(descriptor); if (projectPath == null) return Enumerable.Empty(); var projectFile = _projectFileParser.Parse(projectPath); - return projectFile.References.Select(r => new ExtensionReferenceProbeEntry { + var result = projectFile.References.Select(r => new ExtensionReferenceProbeEntry { Descriptor = descriptor, Loader = this, Name = r.SimpleName, VirtualPath = _virtualPathProvider.GetProjectReferenceVirtualPath(projectPath, r.SimpleName, r.Path) }); + + Logger.Information("Done probing references for module '{0}'", descriptor.Id); + return result; } public override void ReferenceActivated(ExtensionLoadingContext context, ExtensionReferenceProbeEntry referenceEntry) { @@ -132,28 +140,43 @@ namespace Orchard.Environment.Extensions.Loaders { } public override Assembly LoadReference(DependencyReferenceDescriptor reference) { + if (Disabled) + return null; + + Logger.Information("Loading reference '{0}'", reference.Name); + // DynamicExtensionLoader has 2 types of references: assemblies from module bin directory // and .csproj. + Assembly result; if (StringComparer.OrdinalIgnoreCase.Equals(Path.GetExtension(reference.VirtualPath), ".dll")) - return _assemblyProbingFolder.LoadAssembly(reference.Name); + result = _assemblyProbingFolder.LoadAssembly(reference.Name); + else { + result = _buildManager.GetCompiledAssembly(reference.VirtualPath); + } - return _buildManager.GetCompiledAssembly(reference.VirtualPath); + Logger.Information("Done loading reference '{0}'", reference.Name); + return result; } public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) { if (Disabled) return null; + Logger.Information("Probing for module '{0}'", descriptor.Id); + string projectPath = GetProjectPath(descriptor); if (projectPath == null) return null; - return new ExtensionProbeEntry { + var result = new ExtensionProbeEntry { Descriptor = descriptor, LastWriteTimeUtc = GetDependencies(projectPath).Max(f => _virtualPathProvider.GetFileLastWriteTimeUtc(f)), Loader = this, VirtualPath = projectPath }; + + Logger.Information("Done probing for module '{0}'", descriptor.Id); + return result; } protected override ExtensionEntry LoadWorker(ExtensionDescriptor descriptor) { diff --git a/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs index ce9f0d59a..650a43ba3 100644 --- a/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs @@ -144,11 +144,16 @@ namespace Orchard.Environment.Extensions.Loaders { } public override IEnumerable ProbeReferences(ExtensionDescriptor descriptor) { + if (Disabled) + return Enumerable.Empty(); + + Logger.Information("Probing references for module '{0}'", descriptor.Id); + var assemblyPath = GetAssemblyPath(descriptor); if (assemblyPath == null) return Enumerable.Empty(); - return _virtualPathProvider + var result = _virtualPathProvider .ListFiles(_virtualPathProvider.GetDirectoryName(assemblyPath)) .Where(s => StringComparer.OrdinalIgnoreCase.Equals(Path.GetExtension(s), ".dll")) .Where(s => !StringComparer.OrdinalIgnoreCase.Equals(Path.GetFileNameWithoutExtension(s), descriptor.Id)) @@ -159,6 +164,9 @@ namespace Orchard.Environment.Extensions.Loaders { VirtualPath = path } ) .ToList(); + + Logger.Information("Done probing references for module '{0}'", descriptor.Id); + return result; } public override bool IsCompatibleWithModuleReferences(ExtensionDescriptor extension, IEnumerable references) { @@ -176,20 +184,33 @@ namespace Orchard.Environment.Extensions.Loaders { if (Disabled) return null; + Logger.Information("Probing for module '{0}'", descriptor.Id); + var assemblyPath = GetAssemblyPath(descriptor); if (assemblyPath == null) return null; - return new ExtensionProbeEntry { + var result = new ExtensionProbeEntry { Descriptor = descriptor, LastWriteTimeUtc = _virtualPathProvider.GetFileLastWriteTimeUtc(assemblyPath), Loader = this, VirtualPath = assemblyPath }; + + Logger.Information("Done probing for module '{0}'", descriptor.Id); + return result; } public override Assembly LoadReference(DependencyReferenceDescriptor reference) { - return _assemblyProbingFolder.LoadAssembly(reference.Name); + if (Disabled) + return null; + + Logger.Information("Loading reference '{0}'", reference.Name); + + var result = _assemblyProbingFolder.LoadAssembly(reference.Name); + + Logger.Information("Done loading reference '{0}'", reference.Name); + return result; } protected override ExtensionEntry LoadWorker(ExtensionDescriptor descriptor) { From b01a0898a95844d00a564cb818299cdc96abaea8 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Tue, 17 May 2011 13:01:39 -0700 Subject: [PATCH 063/139] Refactor Orchard.WarmupStarter assembly * Remove dependency on Orchard.Framework assembly * Fix issue where a host initialization failure would result in '404' errors. We need to restart the host initialization and make sure new incoming requests are queued. * Fix concurrency issue when multiple requests are pending for the host initialization to finish (only one request would notify of a potentially error, the other ones would return a '404'). --HG-- branch : 1.x extra : transplant_source : %3Dz%E4%ADEq%91%9D%17%D2%10jut%A6%93%09t%7CR --- .../Orchard.WarmupStarter.csproj | 11 +- src/Orchard.Startup/Starter.cs | 116 +++++++++------ src/Orchard.Startup/WarmupHttpModule.cs | 132 ++++++++++-------- src/Orchard.Startup/WarmupUtility.cs | 29 ++++ src/Orchard.Web/Global.asax.cs | 34 ++++- 5 files changed, 207 insertions(+), 115 deletions(-) create mode 100644 src/Orchard.Startup/WarmupUtility.cs diff --git a/src/Orchard.Startup/Orchard.WarmupStarter.csproj b/src/Orchard.Startup/Orchard.WarmupStarter.csproj index 8a18085c4..f3b887c81 100644 --- a/src/Orchard.Startup/Orchard.WarmupStarter.csproj +++ b/src/Orchard.Startup/Orchard.WarmupStarter.csproj @@ -31,23 +31,14 @@ 4 - - ..\..\lib\autofac\Autofac.dll - - - - - - {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6} - Orchard.Framework - + diff --git a/src/Orchard.Web/Modules/Orchard.Blogs/Views/Blog.DeleteButton.cshtml b/src/Orchard.Web/Modules/Orchard.Blogs/Views/Blog.DeleteButton.cshtml new file mode 100644 index 000000000..791c01106 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Blogs/Views/Blog.DeleteButton.cshtml @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file From a6bee4cdab0f01a91e04070293fb7c10e6d423ea Mon Sep 17 00:00:00 2001 From: Andre Rodrigues Date: Wed, 18 May 2011 14:47:33 -0700 Subject: [PATCH 068/139] Addinf fluentnhibernate patches. --HG-- branch : 1.x --- .../fluent-nhibernate_ClasslikeMapBase.patch | 84 +++++++++++++++++++ .../fluent-nhibernate_GenericEnumMapper.patch | 21 +++++ 2 files changed, 105 insertions(+) create mode 100644 lib/fluentnhibernate/fluent-nhibernate_ClasslikeMapBase.patch create mode 100644 lib/fluentnhibernate/fluent-nhibernate_GenericEnumMapper.patch diff --git a/lib/fluentnhibernate/fluent-nhibernate_ClasslikeMapBase.patch b/lib/fluentnhibernate/fluent-nhibernate_ClasslikeMapBase.patch new file mode 100644 index 000000000..759ba3b74 --- /dev/null +++ b/lib/fluentnhibernate/fluent-nhibernate_ClasslikeMapBase.patch @@ -0,0 +1,84 @@ +# HG changeset patch +# User Andre Rodrigues +# Date 1305755030 25200 +# Node ID 9cd8564ec960ed8b634f87249da62a69b506b60f +# Parent 82d19d966b63b14bc6b7e1d76404c0fb0aacf4e0 +Exposing References method to avoid the usage of propertyinfo. + +diff -r 82d19d966b63 -r 9cd8564ec960 src/FluentNHibernate/Mapping/ClasslikeMapBase.cs +--- a/src/FluentNHibernate/Mapping/ClasslikeMapBase.cs Wed May 18 14:42:42 2011 -0700 ++++ b/src/FluentNHibernate/Mapping/ClasslikeMapBase.cs Wed May 18 14:43:50 2011 -0700 +@@ -64,7 +64,11 @@ + + protected virtual ManyToOnePart References(PropertyInfo property, string columnName) + { +- var part = new ManyToOnePart(EntityType, property); ++ return References(property.DeclaringType, property.Name, columnName); ++ } ++ ++ public ManyToOnePart References(Type declaringType, string propertyName, string columnName) { ++ var part = new ManyToOnePart(EntityType, propertyName, declaringType.Name); + + if (columnName != null) + part.Column(columnName); +diff -r 82d19d966b63 -r 9cd8564ec960 src/FluentNHibernate/Mapping/CompositeElementPart.cs +--- a/src/FluentNHibernate/Mapping/CompositeElementPart.cs Wed May 18 14:42:42 2011 -0700 ++++ b/src/FluentNHibernate/Mapping/CompositeElementPart.cs Wed May 18 14:43:50 2011 -0700 +@@ -59,7 +59,7 @@ + + protected virtual ManyToOnePart References(PropertyInfo property, string columnName) + { +- var part = new ManyToOnePart(typeof(T), property); ++ var part = new ManyToOnePart(typeof(T), property.Name, property.DeclaringType.Name); + + if (columnName != null) + part.Column(columnName); +diff -r 82d19d966b63 -r 9cd8564ec960 src/FluentNHibernate/Mapping/ManyToOnePart.cs +--- a/src/FluentNHibernate/Mapping/ManyToOnePart.cs Wed May 18 14:42:42 2011 -0700 ++++ b/src/FluentNHibernate/Mapping/ManyToOnePart.cs Wed May 18 14:43:50 2011 -0700 +@@ -20,12 +20,14 @@ + private readonly AttributeStore attributes = new AttributeStore(); + private readonly AttributeStore columnAttributes = new AttributeStore(); + private readonly Type entity; +- private readonly PropertyInfo property; ++ private readonly string name; ++ private readonly string declaringTypeName; + +- public ManyToOnePart(Type entity, PropertyInfo property) ++ public ManyToOnePart(Type entity, string name, string declaringTypeName) + { + this.entity = entity; +- this.property = property; ++ this.name = name; ++ this.declaringTypeName = declaringTypeName; + access = new AccessStrategyBuilder>(this, value => attributes.Set(x => x.Access, value)); + fetch = new FetchTypeExpression>(this, value => attributes.Set(x => x.Fetch, value)); + cascade = new CascadeExpression>(this, value => attributes.Set(x => x.Cascade, value)); +@@ -37,16 +39,15 @@ + var mapping = new ManyToOneMapping(attributes.CloneInner()); + + mapping.ContainingEntityType = entity; +- mapping.PropertyInfo = property; + + if (!mapping.IsSpecified("Name")) +- mapping.Name = property.Name; ++ mapping.Name = name; + + if (!mapping.IsSpecified("Class")) + mapping.SetDefaultValue(x => x.Class, new TypeReference(typeof(TOther))); + + if (columns.Count == 0) +- mapping.AddDefaultColumn(CreateColumn(property.Name + "_id")); ++ mapping.AddDefaultColumn(CreateColumn(name + "_id")); + + foreach (var column in columns) + { +@@ -124,7 +125,7 @@ + + public ManyToOnePart ForeignKey() + { +- return ForeignKey(string.Format("FK_{0}To{1}", property.DeclaringType.Name, property.Name)); ++ return ForeignKey(string.Format("FK_{0}To{1}", declaringTypeName, name)); + } + + public ManyToOnePart ForeignKey(string foreignKeyName) diff --git a/lib/fluentnhibernate/fluent-nhibernate_GenericEnumMapper.patch b/lib/fluentnhibernate/fluent-nhibernate_GenericEnumMapper.patch new file mode 100644 index 000000000..737d9d372 --- /dev/null +++ b/lib/fluentnhibernate/fluent-nhibernate_GenericEnumMapper.patch @@ -0,0 +1,21 @@ +# HG changeset patch +# User Andre Rodrigues +# Date 1305755041 25200 +# Node ID 48d2b0560a4196a96e906a218208b69ff9db25bd +# Parent 9cd8564ec960ed8b634f87249da62a69b506b60f +Making GenericEnumMapper Serializable. + +diff -r 9cd8564ec960 -r 48d2b0560a41 src/FluentNHibernate/Mapping/GenericEnumMapper.cs +--- a/src/FluentNHibernate/Mapping/GenericEnumMapper.cs Wed May 18 14:43:50 2011 -0700 ++++ b/src/FluentNHibernate/Mapping/GenericEnumMapper.cs Wed May 18 14:44:01 2011 -0700 +@@ -1,7 +1,9 @@ +-using NHibernate.Type; ++using System; ++using NHibernate.Type; + + namespace FluentNHibernate.Mapping + { ++ [Serializable] + public class GenericEnumMapper : EnumStringType + { + public GenericEnumMapper() From 984c11a80f224f822ca48b8d28bb4e74a389b587 Mon Sep 17 00:00:00 2001 From: Andre Rodrigues Date: Fri, 20 May 2011 16:10:34 -0700 Subject: [PATCH 069/139] #17825: Showing created instead of published datetime. --HG-- branch : 1.x --- src/Orchard.Web/Core/Common/Shapes.cs | 8 ++++++-- .../Common/Views/Parts.Common.Metadata.Summary.cshtml | 2 +- .../Core/Common/Views/Parts.Common.Metadata.cshtml | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Orchard.Web/Core/Common/Shapes.cs b/src/Orchard.Web/Core/Common/Shapes.cs index 22344ea63..075ea07eb 100644 --- a/src/Orchard.Web/Core/Common/Shapes.cs +++ b/src/Orchard.Web/Core/Common/Shapes.cs @@ -23,8 +23,12 @@ namespace Orchard.Core.Common { } [Shape] - public IHtmlString PublishedState(HtmlHelper Html, DateTime? dateTimeUtc) { - return Html.DateTime(dateTimeUtc, T("Draft")); + public IHtmlString PublishedState(HtmlHelper html, DateTime createdDateTimeUtc, DateTime? publisheddateTimeUtc) { + if (!publisheddateTimeUtc.HasValue) { + return T("Draft"); + } + + return html.DateTime(createdDateTimeUtc); } [Shape] diff --git a/src/Orchard.Web/Core/Common/Views/Parts.Common.Metadata.Summary.cshtml b/src/Orchard.Web/Core/Common/Views/Parts.Common.Metadata.Summary.cshtml index 5fa305f6b..eb13c1813 100644 --- a/src/Orchard.Web/Core/Common/Views/Parts.Common.Metadata.Summary.cshtml +++ b/src/Orchard.Web/Core/Common/Views/Parts.Common.Metadata.Summary.cshtml @@ -1 +1 @@ -
@Display.PublishedState(dateTimeUtc: Model.ContentPart.PublishedUtc)
\ No newline at end of file +
@Display.PublishedState(createdDateTimeUtc: Model.ContentPart.CreatedUtc, publisheddateTimeUtc: Model.ContentPart.PublishedUtc)
\ No newline at end of file diff --git a/src/Orchard.Web/Core/Common/Views/Parts.Common.Metadata.cshtml b/src/Orchard.Web/Core/Common/Views/Parts.Common.Metadata.cshtml index 5fa305f6b..eb13c1813 100644 --- a/src/Orchard.Web/Core/Common/Views/Parts.Common.Metadata.cshtml +++ b/src/Orchard.Web/Core/Common/Views/Parts.Common.Metadata.cshtml @@ -1 +1 @@ -
@Display.PublishedState(dateTimeUtc: Model.ContentPart.PublishedUtc)
\ No newline at end of file +
@Display.PublishedState(createdDateTimeUtc: Model.ContentPart.CreatedUtc, publisheddateTimeUtc: Model.ContentPart.PublishedUtc)
\ No newline at end of file From e3a3753d53bae4b70f3410e4cf8755f905efca14 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Mon, 23 May 2011 13:25:56 -0700 Subject: [PATCH 070/139] #17841: Fixing page sie selector in admin The - - + @if (totalPageCount > 1 || Model.PageSize == 0 || Model.PageSize > pageSizes.First()) { +
+ + + - -
- } + +
+ } - @T("Showing items {0} - {1} of {2}", (Model.Page - 1) * (int)Model.PageSize + 1, Model.PageSize == 0 ? Model.TotalItemCount : Math.Min(Model.TotalItemCount, (Model.Page) * (int)Model.PageSize), Model.TotalItemCount) + @T("Showing items {0} - {1} of {2}", (Model.Page - 1) * (int)Model.PageSize + 1, Model.PageSize == 0 ? Model.TotalItemCount : Math.Min(Model.TotalItemCount, (Model.Page) * (int)Model.PageSize), Model.TotalItemCount) - @if (totalPageCount > 1) { - routeData["pageSize"] = Model.PageSize; - @tag.StartElement + @if (totalPageCount > 1) { + routeData["pageSize"] = Model.PageSize; + @tag.StartElement - // first - if (firstPage > 1) { - if (routeData.ContainsKey("page")) { - routeData.Remove("page"); + // first + if (firstPage > 1) { + if (routeData.ContainsKey("page")) { + routeData.Remove("page"); + } + +
  • + @Html.ActionLink(T("<<").Text, (string)routeData["action"], (string)routeData["controller"], routeData, null) +
  • } -
  • - @Html.ActionLink(T("<<").Text, (string)routeData["action"], (string)routeData["controller"], routeData, null) -
  • - } - - // previous page - if (Model.Page > 1) { - if (Model.Page == 2 && routeData.ContainsKey("page")) { - routeData.Remove("page"); - } else { - routeData["page"] = Model.Page - 1; - } - -
  • - @Html.ActionLink((string)previousText, (string)routeData["action"], (string)routeData["controller"], routeData, null) -
  • - } - - // page numbers - for (var p = firstPage; p <= lastPage; p++) { -
  • - @if (p == Model.Page) { - @p - } else { - if (p == 1) { + // previous page + if (Model.Page > 1) { + if (Model.Page == 2 && routeData.ContainsKey("page")) { routeData.Remove("page"); } else { - routeData["page"] = p; + routeData["page"] = Model.Page - 1; } - @Html.ActionLink(p.ToString(), (string)routeData["action"], (string)routeData["controller"], routeData, null) + +
  • + @Html.ActionLink((string)previousText, (string)routeData["action"], (string)routeData["controller"], routeData, null) +
  • } - - } - // next page - if (Model.Page < totalPageCount) { - routeData["page"] = Model.Page + 1; + // page numbers + for (var p = firstPage; p <= lastPage; p++) { +
  • + @if (p == Model.Page) { + @p + } else { + if (p == 1) { + routeData.Remove("page"); + } else { + routeData["page"] = p; + } + @Html.ActionLink(p.ToString(), (string)routeData["action"], (string)routeData["controller"], routeData, null) + } +
  • + } -
  • - @Html.ActionLink((string)nextText, (string)routeData["action"], (string)routeData["controller"], routeData, null) -
  • - } + // next page + if (Model.Page < totalPageCount) { + routeData["page"] = Model.Page + 1; - // last page - if (lastPage < totalPageCount) { - routeData["page"] = totalPageCount; +
  • + @Html.ActionLink((string)nextText, (string)routeData["action"], (string)routeData["controller"], routeData, null) +
  • + } -
  • - @Html.ActionLink(T(">>").Text, (string)routeData["action"], (string)routeData["controller"], routeData, null) -
  • - } + // last page + if (lastPage < totalPageCount) { + routeData["page"] = totalPageCount; - @tag.EndElement - } +
  • + @Html.ActionLink(T(">>").Text, (string)routeData["action"], (string)routeData["controller"], routeData, null) +
  • + } - + @tag.EndElement + } + + + } } @using(Script.Foot()) { diff --git a/src/Orchard.Web/Core/Common/Views/Parts.Common.Owner.Edit.cshtml b/src/Orchard.Web/Core/Common/Views/Parts.Common.Owner.Edit.cshtml new file mode 100644 index 000000000..8236e2d9d --- /dev/null +++ b/src/Orchard.Web/Core/Common/Views/Parts.Common.Owner.Edit.cshtml @@ -0,0 +1,9 @@ +@model Orchard.Core.Common.OwnerEditor.OwnerEditorViewModel; +@{ + var OwnerEditor = Model; +} +
    + @Html.LabelFor(m => OwnerEditor.Owner, T("Owner")) + @Html.EditorFor(m => OwnerEditor.Owner) + @Html.ValidationMessageFor(m => OwnerEditor.Owner) +
    diff --git a/src/Orchard.Web/Core/Orchard.Core.csproj b/src/Orchard.Web/Core/Orchard.Core.csproj index 600ef490e..94786e3bb 100644 --- a/src/Orchard.Web/Core/Orchard.Core.csproj +++ b/src/Orchard.Web/Core/Orchard.Core.csproj @@ -62,18 +62,19 @@ - - + + + + - + - - + @@ -148,7 +149,7 @@ - + @@ -328,7 +329,7 @@ - + @@ -435,7 +436,7 @@ - + @@ -443,7 +444,10 @@ - + + + + From 331a36a7031855b541dfbe70e95edee29ab5c411 Mon Sep 17 00:00:00 2001 From: Louis DeJardin Date: Fri, 27 May 2011 19:35:28 -0700 Subject: [PATCH 092/139] Small mistakes in last commit --HG-- branch : 1.x --- src/Orchard.Web/Core/Common/DateEditor/DateEditorHandler.cs | 2 +- src/Orchard.Web/Core/Common/Views/Parts.Common.Date.Edit.cshtml | 2 +- .../Core/Common/Views/Parts.Common.Owner.Edit.cshtml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Orchard.Web/Core/Common/DateEditor/DateEditorHandler.cs b/src/Orchard.Web/Core/Common/DateEditor/DateEditorHandler.cs index 9c7d48465..f31bebdcd 100644 --- a/src/Orchard.Web/Core/Common/DateEditor/DateEditorHandler.cs +++ b/src/Orchard.Web/Core/Common/DateEditor/DateEditorHandler.cs @@ -7,7 +7,7 @@ namespace Orchard.Core.Common.DateEditor { public class DateEditorHandler : ContentHandler { public DateEditorHandler() { OnPublished((context, part) => { - var settings = part.ContentItem.TypeDefinition.Settings.GetModel(); + var settings = part.TypePartDefinition.Settings.GetModel(); if (!settings.ShowDateEditor) { return; } diff --git a/src/Orchard.Web/Core/Common/Views/Parts.Common.Date.Edit.cshtml b/src/Orchard.Web/Core/Common/Views/Parts.Common.Date.Edit.cshtml index edcbfadec..431db9271 100644 --- a/src/Orchard.Web/Core/Common/Views/Parts.Common.Date.Edit.cshtml +++ b/src/Orchard.Web/Core/Common/Views/Parts.Common.Date.Edit.cshtml @@ -1,4 +1,4 @@ -@model Orchard.Core.Common.DateEditor.DateEditorViewModel; +@model Orchard.Core.Common.DateEditor.DateEditorViewModel @{ var DateEditor = Model; Script.Require("jQueryUtils_TimePicker"); diff --git a/src/Orchard.Web/Core/Common/Views/Parts.Common.Owner.Edit.cshtml b/src/Orchard.Web/Core/Common/Views/Parts.Common.Owner.Edit.cshtml index 8236e2d9d..94eebc95b 100644 --- a/src/Orchard.Web/Core/Common/Views/Parts.Common.Owner.Edit.cshtml +++ b/src/Orchard.Web/Core/Common/Views/Parts.Common.Owner.Edit.cshtml @@ -1,4 +1,4 @@ -@model Orchard.Core.Common.OwnerEditor.OwnerEditorViewModel; +@model Orchard.Core.Common.OwnerEditor.OwnerEditorViewModel @{ var OwnerEditor = Model; } From 23afea8a0eca5245a46069e64b8d8464719fa6a3 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Sat, 28 May 2011 19:05:52 -0700 Subject: [PATCH 093/139] PERF: Monitor modules files asynchronously Monitoring virtual dependency files for modules takes requires a lot of file I/O, and can be a significant bottleneck on heavily loaded disk sub-system (5-10 secs). Making this process asynchronous during startup decreases startup time by almost that amount of time. --HG-- branch : 1.x --- src/Orchard/Environment/DefaultOrchardHost.cs | 5 +- .../Extensions/ExtensionLoaderCoordinator.cs | 18 +--- .../ExtensionMonitoringCoordinator.cs | 102 ++++++++++++++++++ .../Extensions/IExtensionLoaderCoordinator.cs | 6 +- .../IExtensionMonitoringCoordinator.cs | 8 ++ src/Orchard/Environment/OrchardStarter.cs | 1 + src/Orchard/Orchard.Framework.csproj | 2 + 7 files changed, 119 insertions(+), 23 deletions(-) create mode 100644 src/Orchard/Environment/Extensions/ExtensionMonitoringCoordinator.cs create mode 100644 src/Orchard/Environment/Extensions/IExtensionMonitoringCoordinator.cs diff --git a/src/Orchard/Environment/DefaultOrchardHost.cs b/src/Orchard/Environment/DefaultOrchardHost.cs index 3f1f8d4a8..bf4be43dd 100644 --- a/src/Orchard/Environment/DefaultOrchardHost.cs +++ b/src/Orchard/Environment/DefaultOrchardHost.cs @@ -19,6 +19,7 @@ namespace Orchard.Environment { private readonly IRunningShellTable _runningShellTable; private readonly IProcessingEngine _processingEngine; private readonly IExtensionLoaderCoordinator _extensionLoaderCoordinator; + private readonly IExtensionMonitoringCoordinator _extensionMonitoringCoordinator; private readonly ICacheManager _cacheManager; private readonly object _syncLock = new object(); @@ -30,6 +31,7 @@ namespace Orchard.Environment { IRunningShellTable runningShellTable, IProcessingEngine processingEngine, IExtensionLoaderCoordinator extensionLoaderCoordinator, + IExtensionMonitoringCoordinator extensionMonitoringCoordinator, ICacheManager cacheManager, IHostLocalRestart hostLocalRestart ) { _shellSettingsManager = shellSettingsManager; @@ -37,6 +39,7 @@ namespace Orchard.Environment { _runningShellTable = runningShellTable; _processingEngine = processingEngine; _extensionLoaderCoordinator = extensionLoaderCoordinator; + _extensionMonitoringCoordinator = extensionMonitoringCoordinator; _cacheManager = cacheManager; _hostLocalRestart = hostLocalRestart; @@ -151,7 +154,7 @@ namespace Orchard.Environment { // on disk, and we need to reload new/updated extensions. _cacheManager.Get("OrchardHost_Extensions", ctx => { - _extensionLoaderCoordinator.MonitorExtensions(ctx.Monitor); + _extensionMonitoringCoordinator.MonitorExtensions(ctx.Monitor); _hostLocalRestart.Monitor(ctx.Monitor); DisposeShellContext(); return ""; diff --git a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs index f61107126..35f94189e 100644 --- a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs +++ b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs @@ -165,7 +165,7 @@ namespace Orchard.Environment.Extensions { if (duplicates.Count() > 0) { var sb = new StringBuilder(); sb.Append(T("There are multiple extensions with the same name installed in this instance of Orchard.\r\n")); - foreach(var dup in duplicates) { + foreach (var dup in duplicates) { sb.Append(T("Extension '{0}' has been found from the following locations: {1}.\r\n", dup.Key, string.Join(", ", dup.Select(e => e.Location + "/" + e.Id)))); } sb.Append(T("This issue can be usually solved by removing or renaming the conflicting extension.")); @@ -300,21 +300,5 @@ namespace Orchard.Environment.Extensions { action(); } } - - public void MonitorExtensions(Action monitor) { - Logger.Information("Start monitoring extension files..."); - // Monitor add/remove of any module/theme - monitor(_virtualPathMonitor.WhenPathChanges("~/Modules")); - monitor(_virtualPathMonitor.WhenPathChanges("~/Themes")); - - // Give loaders a chance to monitor any additional changes - var extensions = _extensionManager.AvailableExtensions().Where(d => DefaultExtensionTypes.IsModule(d.ExtensionType) || DefaultExtensionTypes.IsTheme(d.ExtensionType)).ToList(); - foreach (var extension in extensions) { - foreach (var loader in _loaders) { - loader.Monitor(extension, monitor); - } - } - Logger.Information("Done monitoring extension files..."); - } } } \ No newline at end of file diff --git a/src/Orchard/Environment/Extensions/ExtensionMonitoringCoordinator.cs b/src/Orchard/Environment/Extensions/ExtensionMonitoringCoordinator.cs new file mode 100644 index 000000000..e66bf1191 --- /dev/null +++ b/src/Orchard/Environment/Extensions/ExtensionMonitoringCoordinator.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Orchard.Caching; +using Orchard.Environment.Extensions.Loaders; +using Orchard.Environment.Extensions.Models; +using Orchard.FileSystems.VirtualPath; +using Orchard.Logging; + +namespace Orchard.Environment.Extensions { + public class ExtensionMonitoringCoordinator : IExtensionMonitoringCoordinator { + private readonly IVirtualPathMonitor _virtualPathMonitor; + private readonly IExtensionManager _extensionManager; + private readonly IEnumerable _loaders; + + public ExtensionMonitoringCoordinator(IVirtualPathMonitor virtualPathMonitor, + IExtensionManager extensionManager, + IEnumerable loaders) { + + _virtualPathMonitor = virtualPathMonitor; + _extensionManager = extensionManager; + _loaders = loaders; + + Logger = NullLogger.Instance; + } + + public ILogger Logger { get; set; } + public bool Disabled { get; set; } + + public void MonitorExtensions(Action monitor) { + // We may be disabled by custom host configuration for performance reasons + if (Disabled) + return; + + //PREF: Monitor extensions asynchronously. IsCurrent will be 'true' + // until all tokens are collected by the work function. + var token = new AsyncVolativeToken(MonitorExtensionsWork, Logger); + monitor(token); + token.QueueWorkItem(); + } + + public void MonitorExtensionsWork(Action monitor) { + Logger.Information("Start monitoring extension files..."); + // Monitor add/remove of any module/theme + monitor(_virtualPathMonitor.WhenPathChanges("~/Modules")); + monitor(_virtualPathMonitor.WhenPathChanges("~/Themes")); + + // Give loaders a chance to monitor any additional changes + var extensions = _extensionManager.AvailableExtensions().Where(d => DefaultExtensionTypes.IsModule(d.ExtensionType) || DefaultExtensionTypes.IsTheme(d.ExtensionType)).ToList(); + foreach (var extension in extensions) { + foreach (var loader in _loaders) { + loader.Monitor(extension, monitor); + } + } + Logger.Information("Done monitoring extension files..."); + } + + public class AsyncVolativeToken : IVolatileToken { + private readonly Action> _task; + private readonly List _taskTokens = new List(); + private volatile Exception _taskException; + private volatile bool _isTaskFinished; + + public AsyncVolativeToken(Action> task, ILogger logger) { + _task = task; + Logger = logger; + } + + public ILogger Logger { get; set; } + + public void QueueWorkItem() { + // Start a work item to collect tokens in our internal array + ThreadPool.QueueUserWorkItem((state) => { + try { + _task(token => _taskTokens.Add(token)); + } + catch (Exception e) { + Logger.Error(e, "Error while monitoring extension files. Assuming extensions are not current."); + _taskException = e; + } + finally { + _isTaskFinished = true; + } + }); + } + + public bool IsCurrent { + get { + // We are current until the task has finished + if (_taskException != null) { + return false; + } + if (_isTaskFinished) { + return _taskTokens.All(t => t.IsCurrent); + } + return true; + } + } + } + } +} \ No newline at end of file diff --git a/src/Orchard/Environment/Extensions/IExtensionLoaderCoordinator.cs b/src/Orchard/Environment/Extensions/IExtensionLoaderCoordinator.cs index bcb6a76ab..e8e680931 100644 --- a/src/Orchard/Environment/Extensions/IExtensionLoaderCoordinator.cs +++ b/src/Orchard/Environment/Extensions/IExtensionLoaderCoordinator.cs @@ -1,9 +1,5 @@ -using System; -using Orchard.Caching; - -namespace Orchard.Environment.Extensions { +namespace Orchard.Environment.Extensions { public interface IExtensionLoaderCoordinator { void SetupExtensions(); - void MonitorExtensions(Action monitor); } } \ No newline at end of file diff --git a/src/Orchard/Environment/Extensions/IExtensionMonitoringCoordinator.cs b/src/Orchard/Environment/Extensions/IExtensionMonitoringCoordinator.cs new file mode 100644 index 000000000..800f582e8 --- /dev/null +++ b/src/Orchard/Environment/Extensions/IExtensionMonitoringCoordinator.cs @@ -0,0 +1,8 @@ +using System; +using Orchard.Caching; + +namespace Orchard.Environment.Extensions { + public interface IExtensionMonitoringCoordinator { + void MonitorExtensions(Action monitor); + } +} \ No newline at end of file diff --git a/src/Orchard/Environment/OrchardStarter.cs b/src/Orchard/Environment/OrchardStarter.cs index 6f05683d3..4f4d1c53e 100644 --- a/src/Orchard/Environment/OrchardStarter.cs +++ b/src/Orchard/Environment/OrchardStarter.cs @@ -77,6 +77,7 @@ namespace Orchard.Environment { { builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); { builder.RegisterType().As().SingleInstance() diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index 82efc56a2..379e3a4ac 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -175,6 +175,8 @@ + + From ec7e44db4fb7f8ed4499ab5535dc7992e016175e Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Sat, 28 May 2011 20:44:46 -0700 Subject: [PATCH 094/139] Move last write time computation out of loaders Loaders should only know about dependencies, not about modification dates. This will help centralizing and abstracting away (for performance reason) the algorithm used to pick the correct module to activate (according to last modification dates). --HG-- branch : 1.x --- .../Extensions/ExtensionLoaderCoordinator.cs | 27 ++++++++++++++++--- .../Extensions/ExtensionLoadingContext.cs | 5 ++++ .../Extensions/Loaders/CoreExtensionLoader.cs | 4 +-- .../Loaders/DynamicExtensionLoader.cs | 4 +-- .../Extensions/Loaders/IExtensionLoader.cs | 2 +- .../Loaders/PrecompiledExtensionLoader.cs | 4 +-- .../Loaders/RawThemeExtensionLoader.cs | 5 ++-- .../Loaders/ReferencedExtensionLoader.cs | 5 ++-- 8 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs index 35f94189e..34c22c24f 100644 --- a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs +++ b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs @@ -96,7 +96,7 @@ namespace Orchard.Environment.Extensions { foreach (var probe in extensionProbes) { Logger.Debug(" Loader: {0}", probe.Loader.Name); Logger.Debug(" VirtualPath: {0}", probe.VirtualPath); - Logger.Debug(" DateTimeUtc: {0}", probe.LastWriteTimeUtc); + Logger.Debug(" VirtualPathDependencies: {0}", string.Join(", ", probe.VirtualPathDependencies)); } } @@ -175,12 +175,13 @@ namespace Orchard.Environment.Extensions { var previousDependencies = _dependenciesFolder.LoadDescriptors().ToList(); + var virtualPathModficationDates = new Dictionary(StringComparer.OrdinalIgnoreCase); var availableExtensionsProbes = availableExtensions.SelectMany(extension => _loaders .Select(loader => loader.Probe(extension)) .Where(probe => probe != null)) .GroupBy(e => e.Descriptor.Id) .ToDictionary(g => g.Key, g => g.AsEnumerable() - .OrderByDescending(probe => probe.LastWriteTimeUtc) + .OrderByDescending(probe => GetLatestModificationTimeUtc(virtualPathModficationDates, probe)) .ThenBy(probe => probe.Loader.Order), StringComparer.OrdinalIgnoreCase); var deletedDependencies = previousDependencies @@ -214,10 +215,30 @@ namespace Orchard.Environment.Extensions { DeletedDependencies = deletedDependencies, AvailableExtensionsProbes = availableExtensionsProbes, ReferencesByName = referencesByName, - ReferencesByModule = referencesByModule + ReferencesByModule = referencesByModule, + VirtualPathModficationDates = virtualPathModficationDates, }; } + private DateTime GetLatestModificationTimeUtc(Dictionary virtualPathDependencies, ExtensionProbeEntry probe) { + if (!probe.VirtualPathDependencies.Any()) + return DateTime.MinValue; + + Logger.Information("Retrieving modification dates of dependencies of extension '{0}'", probe.Descriptor.Id); + + var result = probe.VirtualPathDependencies.Max(path => { + DateTime dateTime; + if (!virtualPathDependencies.TryGetValue(path, out dateTime)) { + dateTime = _virtualPathProvider.GetFileLastWriteTimeUtc(path); + virtualPathDependencies.Add(path, dateTime); + } + return dateTime; + }); + + Logger.Information("Done retrieving modification dates of dependencies of extension '{0}'", probe.Descriptor.Id); + return result; + } + IEnumerable ProcessExtensionReferences(ExtensionLoadingContext context, ExtensionProbeEntry activatedExtension) { if (activatedExtension == null) return Enumerable.Empty(); diff --git a/src/Orchard/Environment/Extensions/ExtensionLoadingContext.cs b/src/Orchard/Environment/Extensions/ExtensionLoadingContext.cs index 06a086018..b1e6caa00 100644 --- a/src/Orchard/Environment/Extensions/ExtensionLoadingContext.cs +++ b/src/Orchard/Environment/Extensions/ExtensionLoadingContext.cs @@ -25,6 +25,11 @@ namespace Orchard.Environment.Extensions { public bool RestartAppDomain { get; set; } + /// + /// Keep track of modification date of files (VirtualPath => DateTime) + /// + public IDictionary VirtualPathModficationDates { get; set; } + /// /// List of extensions (modules) present in the system /// diff --git a/src/Orchard/Environment/Extensions/Loaders/CoreExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/CoreExtensionLoader.cs index 5163e81f2..9e6621ddb 100644 --- a/src/Orchard/Environment/Extensions/Loaders/CoreExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/CoreExtensionLoader.cs @@ -31,9 +31,9 @@ namespace Orchard.Environment.Extensions.Loaders { if (descriptor.Location == "~/Core") { return new ExtensionProbeEntry { Descriptor = descriptor, - LastWriteTimeUtc = DateTime.MinValue, Loader = this, - VirtualPath = "~/Core/" + descriptor.Id + VirtualPath = "~/Core/" + descriptor.Id, + VirtualPathDependencies = Enumerable.Empty(), }; } return null; diff --git a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs index b3d6d23f0..0d25afe1c 100644 --- a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs @@ -170,9 +170,9 @@ namespace Orchard.Environment.Extensions.Loaders { var result = new ExtensionProbeEntry { Descriptor = descriptor, - LastWriteTimeUtc = GetDependencies(projectPath).Max(f => _virtualPathProvider.GetFileLastWriteTimeUtc(f)), Loader = this, - VirtualPath = projectPath + VirtualPath = projectPath, + VirtualPathDependencies = GetDependencies(projectPath).ToList(), }; Logger.Information("Done probing for module '{0}'", descriptor.Id); diff --git a/src/Orchard/Environment/Extensions/Loaders/IExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/IExtensionLoader.cs index 95edd9707..eb904148a 100644 --- a/src/Orchard/Environment/Extensions/Loaders/IExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/IExtensionLoader.cs @@ -10,7 +10,7 @@ namespace Orchard.Environment.Extensions.Loaders { public ExtensionDescriptor Descriptor { get; set; } public IExtensionLoader Loader { get; set; } public string VirtualPath { get; set; } - public DateTime LastWriteTimeUtc { get; set; } + public IEnumerable VirtualPathDependencies { get; set; } } public class ExtensionReferenceProbeEntry { diff --git a/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs index 650a43ba3..d50ccfbe2 100644 --- a/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs @@ -192,9 +192,9 @@ namespace Orchard.Environment.Extensions.Loaders { var result = new ExtensionProbeEntry { Descriptor = descriptor, - LastWriteTimeUtc = _virtualPathProvider.GetFileLastWriteTimeUtc(assemblyPath), Loader = this, - VirtualPath = assemblyPath + VirtualPath = assemblyPath, + VirtualPathDependencies = new[] { assemblyPath }, }; Logger.Information("Done probing for module '{0}'", descriptor.Id); diff --git a/src/Orchard/Environment/Extensions/Loaders/RawThemeExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/RawThemeExtensionLoader.cs index 5388d3559..f57894a5a 100644 --- a/src/Orchard/Environment/Extensions/Loaders/RawThemeExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/RawThemeExtensionLoader.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Orchard.Environment.Extensions.Models; using Orchard.FileSystems.Dependencies; using Orchard.FileSystems.VirtualPath; @@ -42,9 +43,9 @@ namespace Orchard.Environment.Extensions.Loaders { return new ExtensionProbeEntry { Descriptor = descriptor, - LastWriteTimeUtc = DateTime.MinValue, Loader = this, - VirtualPath = "~/Theme/" + descriptor.Id + VirtualPath = "~/Theme/" + descriptor.Id, + VirtualPathDependencies = Enumerable.Empty(), }; } return null; diff --git a/src/Orchard/Environment/Extensions/Loaders/ReferencedExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/ReferencedExtensionLoader.cs index e6a06079e..9bee57fd7 100644 --- a/src/Orchard/Environment/Extensions/Loaders/ReferencedExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/ReferencedExtensionLoader.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Linq; using Orchard.Environment.Extensions.Models; using Orchard.FileSystems.Dependencies; using Orchard.FileSystems.VirtualPath; @@ -57,9 +58,9 @@ namespace Orchard.Environment.Extensions.Loaders { return new ExtensionProbeEntry { Descriptor = descriptor, - LastWriteTimeUtc = _virtualPathProvider.GetFileLastWriteTimeUtc(assemblyPath), Loader = this, - VirtualPath = assemblyPath + VirtualPath = assemblyPath, + VirtualPathDependencies = new[] { assemblyPath }, }; } From b97c0f06667a8cbd4f60d71c23916e12c75358a3 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Sat, 28 May 2011 20:45:51 -0700 Subject: [PATCH 095/139] Rename class to be more consistent with similar components --HG-- branch : 1.x rename : src/Orchard/FileSystems/Dependencies/DefaultModuleDependenciesManager.cs => src/Orchard/FileSystems/Dependencies/DefaultExtensionDependenciesManager.cs rename : src/Orchard/FileSystems/Dependencies/IModuleDependenciesManager.cs => src/Orchard/FileSystems/Dependencies/IExtensionDependenciesManager.cs --- .../Environment/Extensions/ExtensionLoaderCoordinator.cs | 8 ++++---- src/Orchard/Environment/OrchardStarter.cs | 2 +- ...sManager.cs => DefaultExtensionDependenciesManager.cs} | 4 ++-- ...denciesManager.cs => IExtensionDependenciesManager.cs} | 2 +- .../Dependencies/WebFormsExtensionsVirtualPathProvider.cs | 8 ++++---- .../Mvc/ViewEngines/Razor/IRazorCompilationEvents.cs | 8 ++++---- src/Orchard/Orchard.Framework.csproj | 4 ++-- 7 files changed, 18 insertions(+), 18 deletions(-) rename src/Orchard/FileSystems/Dependencies/{DefaultModuleDependenciesManager.cs => DefaultExtensionDependenciesManager.cs} (94%) rename src/Orchard/FileSystems/Dependencies/{IModuleDependenciesManager.cs => IExtensionDependenciesManager.cs} (78%) diff --git a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs index 34c22c24f..5ce366676 100644 --- a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs +++ b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs @@ -14,7 +14,7 @@ using Orchard.Utility; namespace Orchard.Environment.Extensions { public class ExtensionLoaderCoordinator : IExtensionLoaderCoordinator { private readonly IDependenciesFolder _dependenciesFolder; - private readonly IModuleDependenciesManager _moduleDependenciesManager; + private readonly IExtensionDependenciesManager _extensionDependenciesManager; private readonly IExtensionManager _extensionManager; private readonly IVirtualPathProvider _virtualPathProvider; private readonly IVirtualPathMonitor _virtualPathMonitor; @@ -24,7 +24,7 @@ namespace Orchard.Environment.Extensions { public ExtensionLoaderCoordinator( IDependenciesFolder dependenciesFolder, - IModuleDependenciesManager moduleDependenciesManager, + IExtensionDependenciesManager extensionDependenciesManager, IExtensionManager extensionManager, IVirtualPathProvider virtualPathProvider, IVirtualPathMonitor virtualPathMonitor, @@ -33,7 +33,7 @@ namespace Orchard.Environment.Extensions { IBuildManager buildManager) { _dependenciesFolder = dependenciesFolder; - _moduleDependenciesManager = moduleDependenciesManager; + _extensionDependenciesManager = extensionDependenciesManager; _extensionManager = extensionManager; _virtualPathProvider = virtualPathProvider; _virtualPathMonitor = virtualPathMonitor; @@ -74,7 +74,7 @@ namespace Orchard.Environment.Extensions { // And finally save the new entries in the dependencies folder _dependenciesFolder.StoreDescriptors(context.NewDependencies); - _moduleDependenciesManager.StoreDependencies(context.NewDependencies); + _extensionDependenciesManager.StoreDependencies(context.NewDependencies); Logger.Information("Done loading extensions..."); diff --git a/src/Orchard/Environment/OrchardStarter.cs b/src/Orchard/Environment/OrchardStarter.cs index 4f4d1c53e..6e94ae1bf 100644 --- a/src/Orchard/Environment/OrchardStarter.cs +++ b/src/Orchard/Environment/OrchardStarter.cs @@ -60,7 +60,7 @@ namespace Orchard.Environment { RegisterVolatileProvider(builder); RegisterVolatileProvider(builder); RegisterVolatileProvider(builder); - RegisterVolatileProvider(builder); + RegisterVolatileProvider(builder); RegisterVolatileProvider(builder); RegisterVolatileProvider(builder); RegisterVolatileProvider(builder); diff --git a/src/Orchard/FileSystems/Dependencies/DefaultModuleDependenciesManager.cs b/src/Orchard/FileSystems/Dependencies/DefaultExtensionDependenciesManager.cs similarity index 94% rename from src/Orchard/FileSystems/Dependencies/DefaultModuleDependenciesManager.cs rename to src/Orchard/FileSystems/Dependencies/DefaultExtensionDependenciesManager.cs index eb1907cf0..c8a646da1 100644 --- a/src/Orchard/FileSystems/Dependencies/DefaultModuleDependenciesManager.cs +++ b/src/Orchard/FileSystems/Dependencies/DefaultExtensionDependenciesManager.cs @@ -13,13 +13,13 @@ namespace Orchard.FileSystems.Dependencies { /// VirtualPath entry. This is so that if any virtual path reference in the file changes, /// the file stored by this component will also change. /// - public class DefaultModuleDependenciesManager : IModuleDependenciesManager { + public class DefaultExtensionDependenciesManager : IExtensionDependenciesManager { private readonly IAppDataFolder _appDataFolder; private readonly IVirtualPathProvider _virtualPathProvider; private const string BasePath = "Dependencies"; private const string FileName = "Dependencies.ModuleCompilation.xml"; - public DefaultModuleDependenciesManager(IAppDataFolder appDataFolder, IVirtualPathProvider virtualPathProvider) { + public DefaultExtensionDependenciesManager(IAppDataFolder appDataFolder, IVirtualPathProvider virtualPathProvider) { _appDataFolder = appDataFolder; _virtualPathProvider = virtualPathProvider; Logger = NullLogger.Instance; diff --git a/src/Orchard/FileSystems/Dependencies/IModuleDependenciesManager.cs b/src/Orchard/FileSystems/Dependencies/IExtensionDependenciesManager.cs similarity index 78% rename from src/Orchard/FileSystems/Dependencies/IModuleDependenciesManager.cs rename to src/Orchard/FileSystems/Dependencies/IExtensionDependenciesManager.cs index b183aebbe..403ab9a28 100644 --- a/src/Orchard/FileSystems/Dependencies/IModuleDependenciesManager.cs +++ b/src/Orchard/FileSystems/Dependencies/IExtensionDependenciesManager.cs @@ -2,7 +2,7 @@ using Orchard.Caching; namespace Orchard.FileSystems.Dependencies { - public interface IModuleDependenciesManager : IVolatileProvider { + public interface IExtensionDependenciesManager : IVolatileProvider { void StoreDependencies(IEnumerable dependencyDescriptors); IEnumerable GetVirtualPathDependencies(DependencyDescriptor descriptor); } diff --git a/src/Orchard/FileSystems/Dependencies/WebFormsExtensionsVirtualPathProvider.cs b/src/Orchard/FileSystems/Dependencies/WebFormsExtensionsVirtualPathProvider.cs index a02a3cf5d..cb6045fe7 100644 --- a/src/Orchard/FileSystems/Dependencies/WebFormsExtensionsVirtualPathProvider.cs +++ b/src/Orchard/FileSystems/Dependencies/WebFormsExtensionsVirtualPathProvider.cs @@ -21,15 +21,15 @@ namespace Orchard.FileSystems.Dependencies { /// public class WebFormVirtualPathProvider : VirtualPathProvider, ICustomVirtualPathProvider { private readonly IDependenciesFolder _dependenciesFolder; - private readonly IModuleDependenciesManager _moduleDependenciesManager; + private readonly IExtensionDependenciesManager _extensionDependenciesManager; private readonly IEnumerable _loaders; private readonly string[] _modulesPrefixes = { "~/Modules/" }; private readonly string[] _themesPrefixes = { "~/Themes/" }; private readonly string[] _extensions = { ".ascx", ".aspx", ".master" }; - public WebFormVirtualPathProvider(IDependenciesFolder dependenciesFolder, IModuleDependenciesManager moduleDependenciesManager, IEnumerable loaders) { + public WebFormVirtualPathProvider(IDependenciesFolder dependenciesFolder, IExtensionDependenciesManager extensionDependenciesManager, IEnumerable loaders) { _dependenciesFolder = dependenciesFolder; - _moduleDependenciesManager = moduleDependenciesManager; + _extensionDependenciesManager = extensionDependenciesManager; _loaders = loaders; Logger = NullLogger.Instance; } @@ -62,7 +62,7 @@ namespace Orchard.FileSystems.Dependencies { var dependencies = virtualPathDependencies .OfType() - .Concat(file.Loaders.SelectMany(dl => _moduleDependenciesManager.GetVirtualPathDependencies(dl.Descriptor))) + .Concat(file.Loaders.SelectMany(dl => _extensionDependenciesManager.GetVirtualPathDependencies(dl.Descriptor))) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); diff --git a/src/Orchard/Mvc/ViewEngines/Razor/IRazorCompilationEvents.cs b/src/Orchard/Mvc/ViewEngines/Razor/IRazorCompilationEvents.cs index 573b3b9db..5577285ab 100644 --- a/src/Orchard/Mvc/ViewEngines/Razor/IRazorCompilationEvents.cs +++ b/src/Orchard/Mvc/ViewEngines/Razor/IRazorCompilationEvents.cs @@ -27,20 +27,20 @@ namespace Orchard.Mvc.ViewEngines.Razor { /// public class DefaultRazorCompilationEvents : IRazorCompilationEvents { private readonly IDependenciesFolder _dependenciesFolder; - private readonly IModuleDependenciesManager _moduleDependenciesManager; + private readonly IExtensionDependenciesManager _extensionDependenciesManager; private readonly IBuildManager _buildManager; private readonly IEnumerable _loaders; private readonly IAssemblyLoader _assemblyLoader; public DefaultRazorCompilationEvents( IDependenciesFolder dependenciesFolder, - IModuleDependenciesManager moduleDependenciesManager, + IExtensionDependenciesManager extensionDependenciesManager, IBuildManager buildManager, IEnumerable loaders, IAssemblyLoader assemblyLoader) { _dependenciesFolder = dependenciesFolder; - _moduleDependenciesManager = moduleDependenciesManager; + _extensionDependenciesManager = extensionDependenciesManager; _buildManager = buildManager; _loaders = loaders; _assemblyLoader = assemblyLoader; @@ -77,7 +77,7 @@ namespace Orchard.Mvc.ViewEngines.Razor { loader, descriptor, references = loader.GetCompilationReferences(descriptor), - dependencies = _moduleDependenciesManager.GetVirtualPathDependencies(descriptor) + dependencies = _extensionDependenciesManager.GetVirtualPathDependencies(descriptor) })); // Add assemblies diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index 379e3a4ac..885e238a8 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -193,8 +193,8 @@ - - + + From 21f1658f48feafd7c5eaef514f88b6c286956ebe Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Sat, 28 May 2011 21:01:39 -0700 Subject: [PATCH 096/139] PERF: During startup, re-use the retrieve file modification dates to compute the file hash to store into the ExtensionDependencies file. This allows skipping doing additional file I/O on dynamic modules when they are activated. --HG-- branch : 1.x --- .../Extensions/ExtensionLoaderCoordinator.cs | 13 ++++++++++++- .../DefaultExtensionDependenciesManager.cs | 14 ++++++-------- .../Dependencies/IExtensionDependenciesManager.cs | 5 +++-- src/Orchard/Utility/Hash.cs | 9 +++++++-- 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs index 5ce366676..b42e25b18 100644 --- a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs +++ b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs @@ -74,7 +74,7 @@ namespace Orchard.Environment.Extensions { // And finally save the new entries in the dependencies folder _dependenciesFolder.StoreDescriptors(context.NewDependencies); - _extensionDependenciesManager.StoreDependencies(context.NewDependencies); + _extensionDependenciesManager.StoreDependencies(context.NewDependencies, path => GetFileHash(context, path)); Logger.Information("Done loading extensions..."); @@ -85,6 +85,17 @@ namespace Orchard.Environment.Extensions { } } + private string GetFileHash(ExtensionLoadingContext context, string path) { + var hash = new Hash(); + hash.AddString(path); + + DateTime dateTime; + if (context.VirtualPathModficationDates.TryGetValue(path, out dateTime)) { + hash.AddDateTime(dateTime); + } + return hash.Value; + } + private void ProcessExtension(ExtensionLoadingContext context, ExtensionDescriptor extension) { var extensionProbes = context.AvailableExtensionsProbes.ContainsKey(extension.Id) ? diff --git a/src/Orchard/FileSystems/Dependencies/DefaultExtensionDependenciesManager.cs b/src/Orchard/FileSystems/Dependencies/DefaultExtensionDependenciesManager.cs index c8a646da1..b08aac42c 100644 --- a/src/Orchard/FileSystems/Dependencies/DefaultExtensionDependenciesManager.cs +++ b/src/Orchard/FileSystems/Dependencies/DefaultExtensionDependenciesManager.cs @@ -15,13 +15,11 @@ namespace Orchard.FileSystems.Dependencies { /// public class DefaultExtensionDependenciesManager : IExtensionDependenciesManager { private readonly IAppDataFolder _appDataFolder; - private readonly IVirtualPathProvider _virtualPathProvider; private const string BasePath = "Dependencies"; private const string FileName = "Dependencies.ModuleCompilation.xml"; - public DefaultExtensionDependenciesManager(IAppDataFolder appDataFolder, IVirtualPathProvider virtualPathProvider) { + public DefaultExtensionDependenciesManager(IAppDataFolder appDataFolder) { _appDataFolder = appDataFolder; - _virtualPathProvider = virtualPathProvider; Logger = NullLogger.Instance; } @@ -31,10 +29,10 @@ namespace Orchard.FileSystems.Dependencies { get { return _appDataFolder.Combine(BasePath, FileName); } } - public void StoreDependencies(IEnumerable dependencyDescriptors) { + public void StoreDependencies(IEnumerable dependencyDescriptors, Func fileHashProvider) { Logger.Information("Storing module dependency file."); - var newDocument = CreateDocument(dependencyDescriptors); + var newDocument = CreateDocument(dependencyDescriptors, fileHashProvider); var previousDocument = ReadDocument(PersistencePath); if (CompareXmlDocuments(newDocument, previousDocument)) { Logger.Debug("Existing document is identical to new one. Skipping save."); @@ -55,7 +53,7 @@ namespace Orchard.FileSystems.Dependencies { } } - private XDocument CreateDocument(IEnumerable dependencies) { + private XDocument CreateDocument(IEnumerable dependencies, Func fileHashProvider) { Func ns = (name => XName.Get(name)); var document = new XDocument(); @@ -65,13 +63,13 @@ namespace Orchard.FileSystems.Dependencies { new XElement(ns("ModuleName"), d.Name), new XElement(ns("LoaderName"), d.LoaderName), new XElement(ns("VirtualPath"), d.VirtualPath), - new XElement(ns("FileHash"), _virtualPathProvider.GetFileHash(d.VirtualPath)), + new XElement(ns("FileHash"), fileHashProvider(d.VirtualPath)), new XElement(ns("References"), FilterReferences(d.References) .Select(r => new XElement(ns("Reference"), new XElement(ns("Name"), r.Name), new XElement(ns("LoaderName"), r.LoaderName), new XElement(ns("VirtualPath"), r.VirtualPath), - new XElement(ns("FileHash"), _virtualPathProvider.GetFileHash(r.VirtualPath)))).ToArray()))); + new XElement(ns("FileHash"), fileHashProvider(r.VirtualPath)))).ToArray()))); document.Root.Add(elements); return document; diff --git a/src/Orchard/FileSystems/Dependencies/IExtensionDependenciesManager.cs b/src/Orchard/FileSystems/Dependencies/IExtensionDependenciesManager.cs index 403ab9a28..21cb8e03f 100644 --- a/src/Orchard/FileSystems/Dependencies/IExtensionDependenciesManager.cs +++ b/src/Orchard/FileSystems/Dependencies/IExtensionDependenciesManager.cs @@ -1,9 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Orchard.Caching; namespace Orchard.FileSystems.Dependencies { public interface IExtensionDependenciesManager : IVolatileProvider { - void StoreDependencies(IEnumerable dependencyDescriptors); + void StoreDependencies(IEnumerable dependencyDescriptors, Func fileHashProvider); IEnumerable GetVirtualPathDependencies(DependencyDescriptor descriptor); } } \ No newline at end of file diff --git a/src/Orchard/Utility/Hash.cs b/src/Orchard/Utility/Hash.cs index 49622d72f..dacfead52 100644 --- a/src/Orchard/Utility/Hash.cs +++ b/src/Orchard/Utility/Hash.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; namespace Orchard.Utility { /// @@ -9,10 +10,10 @@ namespace Orchard.Utility { public class Hash { private long _hash; - public string Value { get { return _hash.ToString(); } } + public string Value { get { return _hash.ToString("x", CultureInfo.InvariantCulture); } } public void AddString(string value) { - if ( string.IsNullOrEmpty(value) ) + if (string.IsNullOrEmpty(value)) return; _hash += value.GetHashCode(); } @@ -21,5 +22,9 @@ namespace Orchard.Utility { AddString(type.AssemblyQualifiedName); AddString(type.FullName); } + + public void AddDateTime(DateTime dateTime) { + _hash += dateTime.ToUniversalTime().ToBinary(); + } } } From 933b625bf0466fbd8e057769b2968daf83fc4b27 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Sat, 28 May 2011 22:16:14 -0700 Subject: [PATCH 097/139] PERF: Re-use cached filehash for dynamic module hash Before recompiling a dynamic module (.csproj), the BuildManager verifies that the file hash (returned from the VPP) has changed from the previous compilation. Instead of returning of the file hash of the .csproj file and all its virtual dependencies (source files and references), use the file hash value stored in the file managed by IExtensionDependenciesManager. This improves startup time by 10+ seconds on file i/o bound machines. --HG-- branch : 1.x --- .../Extensions/ExtensionLoaderCoordinator.cs | 37 ++++++++--- .../Extensions/ExtensionLoadingContext.cs | 4 +- .../DefaultExtensionDependenciesManager.cs | 61 ++++++++++++++++--- .../DynamicModuleVirtualPathProvider.cs | 49 ++++----------- .../IExtensionDependenciesManager.cs | 9 +++ 5 files changed, 104 insertions(+), 56 deletions(-) diff --git a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs index b42e25b18..de7c91b33 100644 --- a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs +++ b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs @@ -85,14 +85,37 @@ namespace Orchard.Environment.Extensions { } } - private string GetFileHash(ExtensionLoadingContext context, string path) { + private string GetFileHash(ExtensionLoadingContext context, string extensionId) { var hash = new Hash(); - hash.AddString(path); + hash.AddString(extensionId); - DateTime dateTime; - if (context.VirtualPathModficationDates.TryGetValue(path, out dateTime)) { - hash.AddDateTime(dateTime); + { + ExtensionProbeEntry extensionProbe; + if (context.ProcessedExtensions.TryGetValue(extensionId, out extensionProbe)) { + if (extensionProbe != null) { + var virtualPathDependencies = extensionProbe.VirtualPathDependencies; + foreach (var virtualpathDependency in virtualPathDependencies) { + DateTime dateTime; + if (context.VirtualPathModficationDates.TryGetValue(virtualpathDependency, out dateTime)) { + hash.AddDateTime(dateTime); + } + } + } + } } + + { + ExtensionReferenceProbeEntry extensionReferenceProbe; + if (context.ProcessedReferences.TryGetValue(extensionId, out extensionReferenceProbe)) { + if (extensionReferenceProbe != null) { + DateTime dateTime; + if (context.VirtualPathModficationDates.TryGetValue(extensionReferenceProbe.VirtualPath, out dateTime)) { + hash.AddDateTime(dateTime); + } + } + } + } + return hash.Value; } @@ -309,8 +332,8 @@ namespace Orchard.Environment.Extensions { // Activate the binary ref if (bestBinaryReference != null) { - if (!context.ProcessedReferences.Contains(bestBinaryReference.Entry.Name)) { - context.ProcessedReferences.Add(bestBinaryReference.Entry.Name); + if (!context.ProcessedReferences.ContainsKey(bestBinaryReference.Entry.Name)) { + context.ProcessedReferences.Add(bestBinaryReference.Entry.Name, bestBinaryReference.Entry); bestBinaryReference.Entry.Loader.ReferenceActivated(context, bestBinaryReference.Entry); } activatedReferences.Add(new DependencyReferenceDescriptor { diff --git a/src/Orchard/Environment/Extensions/ExtensionLoadingContext.cs b/src/Orchard/Environment/Extensions/ExtensionLoadingContext.cs index b1e6caa00..bc5d72068 100644 --- a/src/Orchard/Environment/Extensions/ExtensionLoadingContext.cs +++ b/src/Orchard/Environment/Extensions/ExtensionLoadingContext.cs @@ -9,14 +9,14 @@ namespace Orchard.Environment.Extensions { public class ExtensionLoadingContext { public ExtensionLoadingContext() { ProcessedExtensions = new Dictionary(StringComparer.OrdinalIgnoreCase); - ProcessedReferences = new HashSet(StringComparer.OrdinalIgnoreCase); + ProcessedReferences = new Dictionary(StringComparer.OrdinalIgnoreCase); DeleteActions = new List(); CopyActions = new List(); NewDependencies = new List(); } public IDictionary ProcessedExtensions { get; private set; } - public ISet ProcessedReferences { get; private set; } + public IDictionary ProcessedReferences { get; private set; } public IList NewDependencies { get; private set; } diff --git a/src/Orchard/FileSystems/Dependencies/DefaultExtensionDependenciesManager.cs b/src/Orchard/FileSystems/Dependencies/DefaultExtensionDependenciesManager.cs index b08aac42c..7d6101c0e 100644 --- a/src/Orchard/FileSystems/Dependencies/DefaultExtensionDependenciesManager.cs +++ b/src/Orchard/FileSystems/Dependencies/DefaultExtensionDependenciesManager.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Xml.Linq; using Orchard.Caching; using Orchard.FileSystems.AppData; -using Orchard.FileSystems.VirtualPath; using Orchard.Logging; namespace Orchard.FileSystems.Dependencies { @@ -14,12 +13,17 @@ namespace Orchard.FileSystems.Dependencies { /// the file stored by this component will also change. /// public class DefaultExtensionDependenciesManager : IExtensionDependenciesManager { - private readonly IAppDataFolder _appDataFolder; private const string BasePath = "Dependencies"; private const string FileName = "Dependencies.ModuleCompilation.xml"; + private readonly ICacheManager _cacheManager; + private readonly IAppDataFolder _appDataFolder; + private readonly InvalidationToken _writeThroughToken; - public DefaultExtensionDependenciesManager(IAppDataFolder appDataFolder) { + public DefaultExtensionDependenciesManager(ICacheManager cacheManager, IAppDataFolder appDataFolder) { + _cacheManager = cacheManager; _appDataFolder = appDataFolder; + _writeThroughToken = new InvalidationToken(); + Logger = NullLogger.Instance; } @@ -53,6 +57,23 @@ namespace Orchard.FileSystems.Dependencies { } } + public ActivatedExtensionDescriptor GetDescriptor(string extensionId) { + return LoadDescriptors().FirstOrDefault(d => d.ExtensionId.Equals(extensionId, StringComparison.OrdinalIgnoreCase)); + } + + public IEnumerable LoadDescriptors() { + return _cacheManager.Get(PersistencePath, + ctx => { + _appDataFolder.CreateDirectory(BasePath); + ctx.Monitor(_appDataFolder.WhenPathChanges(ctx.Key)); + + _writeThroughToken.IsCurrent = true; + ctx.Monitor(_writeThroughToken); + + return ReadDescriptors(ctx.Key).ToList(); + }); + } + private XDocument CreateDocument(IEnumerable dependencies, Func fileHashProvider) { Func ns = (name => XName.Get(name)); @@ -60,21 +81,42 @@ namespace Orchard.FileSystems.Dependencies { document.Add(new XElement(ns("Dependencies"))); var elements = FilterDependencies(dependencies).Select( d => new XElement("Dependency", - new XElement(ns("ModuleName"), d.Name), + new XElement(ns("ExtensionId"), d.Name), new XElement(ns("LoaderName"), d.LoaderName), new XElement(ns("VirtualPath"), d.VirtualPath), - new XElement(ns("FileHash"), fileHashProvider(d.VirtualPath)), + new XElement(ns("FileHash"), fileHashProvider(d.Name)), new XElement(ns("References"), FilterReferences(d.References) .Select(r => new XElement(ns("Reference"), - new XElement(ns("Name"), r.Name), + new XElement(ns("ReferenceId"), r.Name), new XElement(ns("LoaderName"), r.LoaderName), new XElement(ns("VirtualPath"), r.VirtualPath), - new XElement(ns("FileHash"), fileHashProvider(r.VirtualPath)))).ToArray()))); + new XElement(ns("FileHash"), fileHashProvider(r.Name)))).ToArray()))); document.Root.Add(elements); return document; } + private IEnumerable ReadDescriptors(string persistancePath) { + Func ns = (name => XName.Get(name)); + Func elem = (e, name) => e.Element(ns(name)).Value; + + XDocument document = ReadDocument(persistancePath); + return document + .Elements(ns("Dependencies")) + .Elements(ns("Dependency")) + .Select(e => new ActivatedExtensionDescriptor { + ExtensionId = elem(e, "ExtensionId"), + VirtualPath = elem(e, "VirtualPath"), + LoaderName = elem(e, "LoaderName"), + FileHash = elem(e, "FileHash"), + //References = e.Elements(ns("References")).Elements(ns("Reference")).Select(r => new DependencyReferenceDescriptor { + // Name = elem(r, "Name"), + // LoaderName = elem(r, "LoaderName"), + // VirtualPath = elem(r, "VirtualPath") + //}) + }).ToList(); + } + private IEnumerable FilterDependencies(IEnumerable dependencies) { return dependencies.Where(dep => IsSupportedLoader(dep.LoaderName)); } @@ -87,11 +129,12 @@ namespace Orchard.FileSystems.Dependencies { //Note: this is hard-coded for now, to avoid adding more responsibilities to the IExtensionLoader // implementations. return - loaderName == "DynamicExtensionLoader" || + loaderName == "DynamicExtensionLoader" || loaderName == "PrecompiledExtensionLoader"; } private void WriteDocument(string persistancePath, XDocument document) { + _writeThroughToken.IsCurrent = false; using (var stream = _appDataFolder.CreateFile(persistancePath)) { document.Save(stream, SaveOptions.None); stream.Close(); @@ -107,7 +150,7 @@ namespace Orchard.FileSystems.Dependencies { return XDocument.Load(stream); } } - catch(Exception e) { + catch (Exception e) { Logger.Information(e, "Error reading file '{0}'", persistancePath); return new XDocument(); } diff --git a/src/Orchard/FileSystems/Dependencies/DynamicModuleVirtualPathProvider.cs b/src/Orchard/FileSystems/Dependencies/DynamicModuleVirtualPathProvider.cs index e6d09e643..ce5715567 100644 --- a/src/Orchard/FileSystems/Dependencies/DynamicModuleVirtualPathProvider.cs +++ b/src/Orchard/FileSystems/Dependencies/DynamicModuleVirtualPathProvider.cs @@ -14,25 +14,17 @@ namespace Orchard.FileSystems.Dependencies { /// served from the "~/Modules" or "~/Themes" directory. /// public class DynamicModuleVirtualPathProvider : VirtualPathProvider, ICustomVirtualPathProvider { - private readonly IDependenciesFolder _dependenciesFolder; + private readonly IExtensionDependenciesManager _extensionDependenciesManager; private readonly IEnumerable _loaders; - public DynamicModuleVirtualPathProvider(IDependenciesFolder dependenciesFolder, IEnumerable loaders) { - _dependenciesFolder = dependenciesFolder; + public DynamicModuleVirtualPathProvider(IExtensionDependenciesManager extensionDependenciesManager, IEnumerable loaders) { + _extensionDependenciesManager = extensionDependenciesManager; _loaders = loaders; Logger = NullLogger.Instance; } public ILogger Logger { get; set; } - public override bool DirectoryExists(string virtualDir) { - return Previous.DirectoryExists(virtualDir); - } - - public override bool FileExists(string virtualPath) { - return Previous.FileExists(virtualPath); - } - public override string GetFileHash(string virtualPath, IEnumerable virtualPathDependencies) { var result = GetFileHashWorker(virtualPath, virtualPathDependencies); Logger.Debug("GetFileHash(\"{0}\"): {1}", virtualPath, result); @@ -42,48 +34,29 @@ namespace Orchard.FileSystems.Dependencies { private string GetFileHashWorker(string virtualPath, IEnumerable virtualPathDependencies) { virtualPath = VirtualPathUtility.ToAppRelative(virtualPath); - var desc = GetDependencyDescriptor(virtualPath); + var desc = GetExtensionDescriptor(virtualPath); if (desc != null) { // We are only interested in ".csproj" files loaded from "DynamicExtensionLoader" var dynamicExtensionLoader = _loaders.Where(l => l.Name == desc.LoaderName).FirstOrDefault() as DynamicExtensionLoader; if (dynamicExtensionLoader != null) { - if (virtualPath.Equals(desc.VirtualPath, StringComparison.OrdinalIgnoreCase)) { - - var otherDependencies = dynamicExtensionLoader.GetFileHashDependencies(desc); - if (otherDependencies.Any()) { - - var allDependencies = virtualPathDependencies.OfType().Concat(otherDependencies).ToList(); - - if (Logger.IsEnabled(LogLevel.Debug)) { - Logger.Debug("GetFileHash(\"{0}\") - virtual path dependencies:", virtualPath); - foreach (var dependency in allDependencies) { - Logger.Debug(" Dependency: \"{0}\"", dependency); - } - } - - return base.GetFileHash(virtualPath, allDependencies); - } + return desc.FileHash; } } } return base.GetFileHash(virtualPath, virtualPathDependencies); } - public override VirtualFile GetFile(string virtualPath) { - return Previous.GetFile(virtualPath); - } - - private DependencyDescriptor GetDependencyDescriptor(string virtualPath) { + private ActivatedExtensionDescriptor GetExtensionDescriptor(string virtualPath) { var prefix = PrefixMatch(virtualPath, DynamicExtensionLoader.ExtensionsVirtualPathPrefixes); if (prefix == null) return null; - var moduleName = ModuleMatch(virtualPath, prefix); - if (moduleName == null) + var moduleId = ModuleMatch(virtualPath, prefix); + if (moduleId == null) return null; - return _dependenciesFolder.GetDescriptor(moduleName); + return _extensionDependenciesManager.GetDescriptor(moduleId); } private static string ModuleMatch(string virtualPath, string prefix) { @@ -91,8 +64,8 @@ namespace Orchard.FileSystems.Dependencies { if (index < 0) return null; - var moduleName = virtualPath.Substring(prefix.Length, index - prefix.Length); - return (string.IsNullOrEmpty(moduleName) ? null : moduleName); + var moduleId = virtualPath.Substring(prefix.Length, index - prefix.Length); + return (string.IsNullOrEmpty(moduleId) ? null : moduleId); } private static string PrefixMatch(string virtualPath, params string[] prefixes) { diff --git a/src/Orchard/FileSystems/Dependencies/IExtensionDependenciesManager.cs b/src/Orchard/FileSystems/Dependencies/IExtensionDependenciesManager.cs index 21cb8e03f..12127228e 100644 --- a/src/Orchard/FileSystems/Dependencies/IExtensionDependenciesManager.cs +++ b/src/Orchard/FileSystems/Dependencies/IExtensionDependenciesManager.cs @@ -3,8 +3,17 @@ using System.Collections.Generic; using Orchard.Caching; namespace Orchard.FileSystems.Dependencies { + public class ActivatedExtensionDescriptor { + public string ExtensionId { get; set; } + public string LoaderName { get; set; } + public string VirtualPath { get; set; } + public string FileHash { get; set; } + } + public interface IExtensionDependenciesManager : IVolatileProvider { void StoreDependencies(IEnumerable dependencyDescriptors, Func fileHashProvider); + IEnumerable GetVirtualPathDependencies(DependencyDescriptor descriptor); + ActivatedExtensionDescriptor GetDescriptor(string extensionId); } } \ No newline at end of file From a281658f9a1a33b9ed9a761090bc649d84e47d0b Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Sun, 29 May 2011 02:42:57 -0700 Subject: [PATCH 098/139] Minor code refactoring Spmplify/relayer things a little bit --HG-- branch : 1.x --- .../Extensions/ExtensionLoaderCoordinator.cs | 57 ++++++-------- .../DefaultExtensionDependenciesManager.cs | 75 +++++++------------ .../DynamicModuleVirtualPathProvider.cs | 8 +- .../IExtensionDependenciesManager.cs | 6 +- .../WebFormsExtensionsVirtualPathProvider.cs | 2 +- .../Razor/IRazorCompilationEvents.cs | 2 +- 6 files changed, 54 insertions(+), 96 deletions(-) diff --git a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs index de7c91b33..964dbb6a5 100644 --- a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs +++ b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs @@ -74,7 +74,7 @@ namespace Orchard.Environment.Extensions { // And finally save the new entries in the dependencies folder _dependenciesFolder.StoreDescriptors(context.NewDependencies); - _extensionDependenciesManager.StoreDependencies(context.NewDependencies, path => GetFileHash(context, path)); + _extensionDependenciesManager.StoreDependencies(context.NewDependencies, desc => GetExtensionHash(context, desc)); Logger.Information("Done loading extensions..."); @@ -85,35 +85,18 @@ namespace Orchard.Environment.Extensions { } } - private string GetFileHash(ExtensionLoadingContext context, string extensionId) { + private string GetExtensionHash(ExtensionLoadingContext context, DependencyDescriptor dependencyDescriptor) { var hash = new Hash(); - hash.AddString(extensionId); + hash.AddString(dependencyDescriptor.Name); - { - ExtensionProbeEntry extensionProbe; - if (context.ProcessedExtensions.TryGetValue(extensionId, out extensionProbe)) { - if (extensionProbe != null) { - var virtualPathDependencies = extensionProbe.VirtualPathDependencies; - foreach (var virtualpathDependency in virtualPathDependencies) { - DateTime dateTime; - if (context.VirtualPathModficationDates.TryGetValue(virtualpathDependency, out dateTime)) { - hash.AddDateTime(dateTime); - } - } - } - } + foreach (var virtualpathDependency in context.ProcessedExtensions[dependencyDescriptor.Name].VirtualPathDependencies) { + hash.AddDateTime(GetVirtualPathModificationTimeUtc(context.VirtualPathModficationDates, virtualpathDependency)); } - { - ExtensionReferenceProbeEntry extensionReferenceProbe; - if (context.ProcessedReferences.TryGetValue(extensionId, out extensionReferenceProbe)) { - if (extensionReferenceProbe != null) { - DateTime dateTime; - if (context.VirtualPathModficationDates.TryGetValue(extensionReferenceProbe.VirtualPath, out dateTime)) { - hash.AddDateTime(dateTime); - } - } - } + foreach (var reference in dependencyDescriptor.References) { + hash.AddString(reference.Name); + hash.AddString(reference.LoaderName); + hash.AddDateTime(GetVirtualPathModificationTimeUtc(context.VirtualPathModficationDates, reference.VirtualPath)); } return hash.Value; @@ -215,7 +198,7 @@ namespace Orchard.Environment.Extensions { .Where(probe => probe != null)) .GroupBy(e => e.Descriptor.Id) .ToDictionary(g => g.Key, g => g.AsEnumerable() - .OrderByDescending(probe => GetLatestModificationTimeUtc(virtualPathModficationDates, probe)) + .OrderByDescending(probe => GetVirtualPathDepedenciesModificationTimeUtc(virtualPathModficationDates, probe)) .ThenBy(probe => probe.Loader.Order), StringComparer.OrdinalIgnoreCase); var deletedDependencies = previousDependencies @@ -254,25 +237,27 @@ namespace Orchard.Environment.Extensions { }; } - private DateTime GetLatestModificationTimeUtc(Dictionary virtualPathDependencies, ExtensionProbeEntry probe) { + private DateTime GetVirtualPathDepedenciesModificationTimeUtc(IDictionary virtualPathDependencies, ExtensionProbeEntry probe) { if (!probe.VirtualPathDependencies.Any()) return DateTime.MinValue; Logger.Information("Retrieving modification dates of dependencies of extension '{0}'", probe.Descriptor.Id); - var result = probe.VirtualPathDependencies.Max(path => { - DateTime dateTime; - if (!virtualPathDependencies.TryGetValue(path, out dateTime)) { - dateTime = _virtualPathProvider.GetFileLastWriteTimeUtc(path); - virtualPathDependencies.Add(path, dateTime); - } - return dateTime; - }); + var result = probe.VirtualPathDependencies.Max(path => GetVirtualPathModificationTimeUtc(virtualPathDependencies, path)); Logger.Information("Done retrieving modification dates of dependencies of extension '{0}'", probe.Descriptor.Id); return result; } + private DateTime GetVirtualPathModificationTimeUtc(IDictionary virtualPathDependencies, string path) { + DateTime dateTime; + if (!virtualPathDependencies.TryGetValue(path, out dateTime)) { + dateTime = _virtualPathProvider.GetFileLastWriteTimeUtc(path); + virtualPathDependencies.Add(path, dateTime); + } + return dateTime; + } + IEnumerable ProcessExtensionReferences(ExtensionLoadingContext context, ExtensionProbeEntry activatedExtension) { if (activatedExtension == null) return Enumerable.Empty(); diff --git a/src/Orchard/FileSystems/Dependencies/DefaultExtensionDependenciesManager.cs b/src/Orchard/FileSystems/Dependencies/DefaultExtensionDependenciesManager.cs index 7d6101c0e..80d8809f5 100644 --- a/src/Orchard/FileSystems/Dependencies/DefaultExtensionDependenciesManager.cs +++ b/src/Orchard/FileSystems/Dependencies/DefaultExtensionDependenciesManager.cs @@ -14,7 +14,7 @@ namespace Orchard.FileSystems.Dependencies { /// public class DefaultExtensionDependenciesManager : IExtensionDependenciesManager { private const string BasePath = "Dependencies"; - private const string FileName = "Dependencies.ModuleCompilation.xml"; + private const string FileName = "dependencies.compiled.xml"; private readonly ICacheManager _cacheManager; private readonly IAppDataFolder _appDataFolder; private readonly InvalidationToken _writeThroughToken; @@ -33,12 +33,12 @@ namespace Orchard.FileSystems.Dependencies { get { return _appDataFolder.Combine(BasePath, FileName); } } - public void StoreDependencies(IEnumerable dependencyDescriptors, Func fileHashProvider) { + public void StoreDependencies(IEnumerable dependencyDescriptors, Func fileHashProvider) { Logger.Information("Storing module dependency file."); var newDocument = CreateDocument(dependencyDescriptors, fileHashProvider); var previousDocument = ReadDocument(PersistencePath); - if (CompareXmlDocuments(newDocument, previousDocument)) { + if (XNode.DeepEquals(newDocument.Root, previousDocument.Root)) { Logger.Debug("Existing document is identical to new one. Skipping save."); } else { @@ -48,8 +48,9 @@ namespace Orchard.FileSystems.Dependencies { Logger.Information("Done storing module dependency file."); } - public IEnumerable GetVirtualPathDependencies(DependencyDescriptor descriptor) { - if (IsSupportedLoader(descriptor.LoaderName)) { + public IEnumerable GetVirtualPathDependencies(string extensionId) { + var descriptor = GetDescriptor(extensionId); + if (descriptor != null && IsSupportedLoader(descriptor.LoaderName)) { // Currently, we return the same file for every module. An improvement would be to return // a specific file per module (this would decrease the number of recompilations needed // when modules change on disk). @@ -62,38 +63,31 @@ namespace Orchard.FileSystems.Dependencies { } public IEnumerable LoadDescriptors() { - return _cacheManager.Get(PersistencePath, - ctx => { - _appDataFolder.CreateDirectory(BasePath); - ctx.Monitor(_appDataFolder.WhenPathChanges(ctx.Key)); + return _cacheManager.Get(PersistencePath, ctx => { + _appDataFolder.CreateDirectory(BasePath); + ctx.Monitor(_appDataFolder.WhenPathChanges(ctx.Key)); - _writeThroughToken.IsCurrent = true; - ctx.Monitor(_writeThroughToken); + _writeThroughToken.IsCurrent = true; + ctx.Monitor(_writeThroughToken); - return ReadDescriptors(ctx.Key).ToList(); - }); + return ReadDescriptors(ctx.Key).ToList(); + }); } - private XDocument CreateDocument(IEnumerable dependencies, Func fileHashProvider) { + private XDocument CreateDocument(IEnumerable dependencies, Func fileHashProvider) { Func ns = (name => XName.Get(name)); - var document = new XDocument(); - document.Add(new XElement(ns("Dependencies"))); - var elements = FilterDependencies(dependencies).Select( - d => new XElement("Dependency", - new XElement(ns("ExtensionId"), d.Name), - new XElement(ns("LoaderName"), d.LoaderName), - new XElement(ns("VirtualPath"), d.VirtualPath), - new XElement(ns("FileHash"), fileHashProvider(d.Name)), - new XElement(ns("References"), FilterReferences(d.References) - .Select(r => new XElement(ns("Reference"), - new XElement(ns("ReferenceId"), r.Name), - new XElement(ns("LoaderName"), r.LoaderName), - new XElement(ns("VirtualPath"), r.VirtualPath), - new XElement(ns("FileHash"), fileHashProvider(r.Name)))).ToArray()))); + var elements = dependencies + .Where(dep => IsSupportedLoader(dep.LoaderName)) + .OrderBy(dep => dep.Name, StringComparer.OrdinalIgnoreCase) + .Select(descriptor => + new XElement(ns("Dependency"), + new XElement(ns("ExtensionId"), descriptor.Name), + new XElement(ns("LoaderName"), descriptor.LoaderName), + new XElement(ns("VirtualPath"), descriptor.VirtualPath), + new XElement(ns("Hash"), fileHashProvider(descriptor)))); - document.Root.Add(elements); - return document; + return new XDocument(new XElement(ns("Dependencies"), elements.ToArray())); } private IEnumerable ReadDescriptors(string persistancePath) { @@ -108,23 +102,10 @@ namespace Orchard.FileSystems.Dependencies { ExtensionId = elem(e, "ExtensionId"), VirtualPath = elem(e, "VirtualPath"), LoaderName = elem(e, "LoaderName"), - FileHash = elem(e, "FileHash"), - //References = e.Elements(ns("References")).Elements(ns("Reference")).Select(r => new DependencyReferenceDescriptor { - // Name = elem(r, "Name"), - // LoaderName = elem(r, "LoaderName"), - // VirtualPath = elem(r, "VirtualPath") - //}) + Hash = elem(e, "Hash"), }).ToList(); } - private IEnumerable FilterDependencies(IEnumerable dependencies) { - return dependencies.Where(dep => IsSupportedLoader(dep.LoaderName)); - } - - private IEnumerable FilterReferences(IEnumerable references) { - return references.Where(dep => IsSupportedLoader(dep.LoaderName)); - } - private bool IsSupportedLoader(string loaderName) { //Note: this is hard-coded for now, to avoid adding more responsibilities to the IExtensionLoader // implementations. @@ -151,15 +132,11 @@ namespace Orchard.FileSystems.Dependencies { } } catch (Exception e) { - Logger.Information(e, "Error reading file '{0}'", persistancePath); + Logger.Information(e, "Error reading file '{0}'. Assuming empty.", persistancePath); return new XDocument(); } } - private bool CompareXmlDocuments(XDocument doc1, XDocument doc2) { - return XNode.DeepEquals(doc1.Root, doc2.Root); - } - private class InvalidationToken : IVolatileToken { public bool IsCurrent { get; set; } } diff --git a/src/Orchard/FileSystems/Dependencies/DynamicModuleVirtualPathProvider.cs b/src/Orchard/FileSystems/Dependencies/DynamicModuleVirtualPathProvider.cs index ce5715567..497728c9a 100644 --- a/src/Orchard/FileSystems/Dependencies/DynamicModuleVirtualPathProvider.cs +++ b/src/Orchard/FileSystems/Dependencies/DynamicModuleVirtualPathProvider.cs @@ -36,12 +36,8 @@ namespace Orchard.FileSystems.Dependencies { var desc = GetExtensionDescriptor(virtualPath); if (desc != null) { - // We are only interested in ".csproj" files loaded from "DynamicExtensionLoader" - var dynamicExtensionLoader = _loaders.Where(l => l.Name == desc.LoaderName).FirstOrDefault() as DynamicExtensionLoader; - if (dynamicExtensionLoader != null) { - if (virtualPath.Equals(desc.VirtualPath, StringComparison.OrdinalIgnoreCase)) { - return desc.FileHash; - } + if (desc.VirtualPath.Equals(virtualPath, StringComparison.OrdinalIgnoreCase)) { + return desc.Hash; } } return base.GetFileHash(virtualPath, virtualPathDependencies); diff --git a/src/Orchard/FileSystems/Dependencies/IExtensionDependenciesManager.cs b/src/Orchard/FileSystems/Dependencies/IExtensionDependenciesManager.cs index 12127228e..e2ad97a79 100644 --- a/src/Orchard/FileSystems/Dependencies/IExtensionDependenciesManager.cs +++ b/src/Orchard/FileSystems/Dependencies/IExtensionDependenciesManager.cs @@ -7,13 +7,13 @@ namespace Orchard.FileSystems.Dependencies { public string ExtensionId { get; set; } public string LoaderName { get; set; } public string VirtualPath { get; set; } - public string FileHash { get; set; } + public string Hash { get; set; } } public interface IExtensionDependenciesManager : IVolatileProvider { - void StoreDependencies(IEnumerable dependencyDescriptors, Func fileHashProvider); + void StoreDependencies(IEnumerable dependencyDescriptors, Func fileHashProvider); - IEnumerable GetVirtualPathDependencies(DependencyDescriptor descriptor); + IEnumerable GetVirtualPathDependencies(string extensionId); ActivatedExtensionDescriptor GetDescriptor(string extensionId); } } \ No newline at end of file diff --git a/src/Orchard/FileSystems/Dependencies/WebFormsExtensionsVirtualPathProvider.cs b/src/Orchard/FileSystems/Dependencies/WebFormsExtensionsVirtualPathProvider.cs index cb6045fe7..c75bcabca 100644 --- a/src/Orchard/FileSystems/Dependencies/WebFormsExtensionsVirtualPathProvider.cs +++ b/src/Orchard/FileSystems/Dependencies/WebFormsExtensionsVirtualPathProvider.cs @@ -62,7 +62,7 @@ namespace Orchard.FileSystems.Dependencies { var dependencies = virtualPathDependencies .OfType() - .Concat(file.Loaders.SelectMany(dl => _extensionDependenciesManager.GetVirtualPathDependencies(dl.Descriptor))) + .Concat(file.Loaders.SelectMany(dl => _extensionDependenciesManager.GetVirtualPathDependencies(dl.Descriptor.Name))) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); diff --git a/src/Orchard/Mvc/ViewEngines/Razor/IRazorCompilationEvents.cs b/src/Orchard/Mvc/ViewEngines/Razor/IRazorCompilationEvents.cs index 5577285ab..8bdf1cb9e 100644 --- a/src/Orchard/Mvc/ViewEngines/Razor/IRazorCompilationEvents.cs +++ b/src/Orchard/Mvc/ViewEngines/Razor/IRazorCompilationEvents.cs @@ -77,7 +77,7 @@ namespace Orchard.Mvc.ViewEngines.Razor { loader, descriptor, references = loader.GetCompilationReferences(descriptor), - dependencies = _extensionDependenciesManager.GetVirtualPathDependencies(descriptor) + dependencies = _extensionDependenciesManager.GetVirtualPathDependencies(descriptor.Name) })); // Add assemblies From 487b04e6b6c7329598c399b2d0b69b2b0e4fdd47 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Sun, 29 May 2011 10:51:37 -0700 Subject: [PATCH 099/139] Owner Editor is displayed by default for visual compatibility --HG-- branch : 1.x --- .../Core/Common/OwnerEditor/OwnerEditorSettings.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Orchard.Web/Core/Common/OwnerEditor/OwnerEditorSettings.cs b/src/Orchard.Web/Core/Common/OwnerEditor/OwnerEditorSettings.cs index 2e457c52e..b9b070e0c 100644 --- a/src/Orchard.Web/Core/Common/OwnerEditor/OwnerEditorSettings.cs +++ b/src/Orchard.Web/Core/Common/OwnerEditor/OwnerEditorSettings.cs @@ -7,6 +7,12 @@ using Orchard.ContentManagement.ViewModels; namespace Orchard.Core.Common.OwnerEditor { public class OwnerEditorSettings { + + public OwnerEditorSettings() { + // owner editor should is displayed by default + ShowOwnerEditor = true; + } + public bool ShowOwnerEditor { get; set; } } From 234375704a903ededd5f88c36a4d678776edbdb7 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Sun, 29 May 2011 13:08:38 -0700 Subject: [PATCH 100/139] Fix bug in hashing algorithm As per .NET guidelines and implementation details, can't use string.GetHashCode() to persist string hash code values on disk. --HG-- branch : 1.x --- .../Extensions/ExtensionLoaderCoordinator.cs | 4 +- src/Orchard/Utility/Hash.cs | 37 +++++++++++++++++-- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs index 964dbb6a5..8cc499a13 100644 --- a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs +++ b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs @@ -87,14 +87,14 @@ namespace Orchard.Environment.Extensions { private string GetExtensionHash(ExtensionLoadingContext context, DependencyDescriptor dependencyDescriptor) { var hash = new Hash(); - hash.AddString(dependencyDescriptor.Name); + hash.AddStringInvariant(dependencyDescriptor.Name); foreach (var virtualpathDependency in context.ProcessedExtensions[dependencyDescriptor.Name].VirtualPathDependencies) { hash.AddDateTime(GetVirtualPathModificationTimeUtc(context.VirtualPathModficationDates, virtualpathDependency)); } foreach (var reference in dependencyDescriptor.References) { - hash.AddString(reference.Name); + hash.AddStringInvariant(reference.Name); hash.AddString(reference.LoaderName); hash.AddDateTime(GetVirtualPathModificationTimeUtc(context.VirtualPathModficationDates, reference.VirtualPath)); } diff --git a/src/Orchard/Utility/Hash.cs b/src/Orchard/Utility/Hash.cs index dacfead52..7cfa01c10 100644 --- a/src/Orchard/Utility/Hash.cs +++ b/src/Orchard/Utility/Hash.cs @@ -10,12 +10,28 @@ namespace Orchard.Utility { public class Hash { private long _hash; - public string Value { get { return _hash.ToString("x", CultureInfo.InvariantCulture); } } + public string Value { + get { + return _hash.ToString("x", CultureInfo.InvariantCulture); + } + } + + public override string ToString() { + return Value; + } public void AddString(string value) { if (string.IsNullOrEmpty(value)) return; - _hash += value.GetHashCode(); + + _hash += GetStringHashCode(value); + } + + public void AddStringInvariant(string value) { + if (string.IsNullOrEmpty(value)) + return; + + AddString(value.ToLowerInvariant()); } public void AddTypeReference(Type type) { @@ -24,7 +40,22 @@ namespace Orchard.Utility { } public void AddDateTime(DateTime dateTime) { - _hash += dateTime.ToUniversalTime().ToBinary(); + _hash += dateTime.ToBinary(); + } + + /// + /// We need a custom string hash code function, because .NET string.GetHashCode() + /// function is not guaranteed to be constant across multiple executions. + /// + private static long GetStringHashCode(string s) { + unchecked { + long result = 352654597L; + foreach (var ch in s) { + long h = ch.GetHashCode(); + result = result + (h << 27) + h; + } + return result; + } } } } From 1fd64b705d0b40ac8e3a7e20e8c71233b9fe0f2c Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Sun, 29 May 2011 13:58:06 -0700 Subject: [PATCH 101/139] PERF: Move async token monitoring to a service interface Update UT --HG-- branch : 1.x --- .../Commands/CodeGenerationCommandsTests.cs | 1 + .../Migrations/SchemaCommandGeneratorTests.cs | 1 + .../Services/FileBasedProjectSystemTests.cs | 1 + .../Services/PackageInstallerTests.cs | 1 + .../RecipeHandlers/ModuleRecipeHandlerTest.cs | 1 + .../RecipeHandlers/ThemeRecipeHandlerTest.cs | 1 + .../Recipes/Services/RecipeManagerTests.cs | 1 + .../DataMigration/DataMigrationTests.cs | 1 + .../ExtensionLoaderCoordinatorTests.cs | 15 +++-- .../Extensions/ExtensionManagerTests.cs | 19 +++--- .../Features/FeatureManagerTests.cs | 1 + src/Orchard.Tests/Stubs/StubCacheManager.cs | 19 ++++++ .../Caching/DefaultAsyncTokenProvider.cs | 64 +++++++++++++++++++ src/Orchard/Caching/IAsyncTokenProvider.cs | 7 ++ .../Extensions/ExtensionManager.cs | 23 +++++-- .../ExtensionMonitoringCoordinator.cs | 57 ++--------------- src/Orchard/Environment/OrchardStarter.cs | 1 + src/Orchard/Orchard.Framework.csproj | 2 + 18 files changed, 143 insertions(+), 73 deletions(-) create mode 100644 src/Orchard/Caching/DefaultAsyncTokenProvider.cs create mode 100644 src/Orchard/Caching/IAsyncTokenProvider.cs diff --git a/src/Orchard.Tests.Modules/CodeGeneration/Commands/CodeGenerationCommandsTests.cs b/src/Orchard.Tests.Modules/CodeGeneration/Commands/CodeGenerationCommandsTests.cs index f41c865e9..165364532 100644 --- a/src/Orchard.Tests.Modules/CodeGeneration/Commands/CodeGenerationCommandsTests.cs +++ b/src/Orchard.Tests.Modules/CodeGeneration/Commands/CodeGenerationCommandsTests.cs @@ -50,6 +50,7 @@ namespace Orchard.Tests.Modules.CodeGeneration.Commands { builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As(); _container = builder.Build(); diff --git a/src/Orchard.Tests.Modules/Migrations/SchemaCommandGeneratorTests.cs b/src/Orchard.Tests.Modules/Migrations/SchemaCommandGeneratorTests.cs index 9372d4d0a..1307339b5 100644 --- a/src/Orchard.Tests.Modules/Migrations/SchemaCommandGeneratorTests.cs +++ b/src/Orchard.Tests.Modules/Migrations/SchemaCommandGeneratorTests.cs @@ -88,6 +88,7 @@ namespace Orchard.Tests.Modules.Migrations { builder.RegisterType().As(); builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>)); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As(); _session = _sessionFactory.OpenSession(); diff --git a/src/Orchard.Tests.Modules/Packaging/Services/FileBasedProjectSystemTests.cs b/src/Orchard.Tests.Modules/Packaging/Services/FileBasedProjectSystemTests.cs index 902d45dde..5f1c54830 100644 --- a/src/Orchard.Tests.Modules/Packaging/Services/FileBasedProjectSystemTests.cs +++ b/src/Orchard.Tests.Modules/Packaging/Services/FileBasedProjectSystemTests.cs @@ -31,6 +31,7 @@ namespace Orchard.Tests.Modules.Packaging.Services { builder.RegisterType().As(); builder.RegisterInstance(new Mock().Object); builder.RegisterType().As(); + builder.RegisterType().As(); _mockedVirtualPathProvider = new Mock(); builder.RegisterInstance(_mockedVirtualPathProvider.Object).As(); diff --git a/src/Orchard.Tests.Modules/Packaging/Services/PackageInstallerTests.cs b/src/Orchard.Tests.Modules/Packaging/Services/PackageInstallerTests.cs index 59a97b051..626477065 100644 --- a/src/Orchard.Tests.Modules/Packaging/Services/PackageInstallerTests.cs +++ b/src/Orchard.Tests.Modules/Packaging/Services/PackageInstallerTests.cs @@ -31,6 +31,7 @@ namespace Orchard.Tests.Modules.Packaging.Services { builder.RegisterType().As(); builder.RegisterInstance(new Mock().Object); builder.RegisterType().As(); + builder.RegisterType().As(); _mockedVirtualPathProvider = new Mock(); builder.RegisterInstance(_mockedVirtualPathProvider.Object).As(); diff --git a/src/Orchard.Tests.Modules/Recipes/RecipeHandlers/ModuleRecipeHandlerTest.cs b/src/Orchard.Tests.Modules/Recipes/RecipeHandlers/ModuleRecipeHandlerTest.cs index e436f311d..e1af609e9 100644 --- a/src/Orchard.Tests.Modules/Recipes/RecipeHandlers/ModuleRecipeHandlerTest.cs +++ b/src/Orchard.Tests.Modules/Recipes/RecipeHandlers/ModuleRecipeHandlerTest.cs @@ -50,6 +50,7 @@ namespace Orchard.Tests.Modules.Recipes.RecipeHandlers { builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As(); builder.RegisterType().As(); diff --git a/src/Orchard.Tests.Modules/Recipes/RecipeHandlers/ThemeRecipeHandlerTest.cs b/src/Orchard.Tests.Modules/Recipes/RecipeHandlers/ThemeRecipeHandlerTest.cs index ed898f7f2..740e1aee9 100644 --- a/src/Orchard.Tests.Modules/Recipes/RecipeHandlers/ThemeRecipeHandlerTest.cs +++ b/src/Orchard.Tests.Modules/Recipes/RecipeHandlers/ThemeRecipeHandlerTest.cs @@ -53,6 +53,7 @@ namespace Orchard.Tests.Modules.Recipes.RecipeHandlers { builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As(); builder.RegisterType().As(); diff --git a/src/Orchard.Tests.Modules/Recipes/Services/RecipeManagerTests.cs b/src/Orchard.Tests.Modules/Recipes/Services/RecipeManagerTests.cs index 0b4e5a01e..540d8f922 100644 --- a/src/Orchard.Tests.Modules/Recipes/Services/RecipeManagerTests.cs +++ b/src/Orchard.Tests.Modules/Recipes/Services/RecipeManagerTests.cs @@ -73,6 +73,7 @@ namespace Orchard.Tests.Modules.Recipes.Services { builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterInstance(_folders).As(); builder.RegisterType().As(); builder.RegisterType().As(); diff --git a/src/Orchard.Tests/DataMigration/DataMigrationTests.cs b/src/Orchard.Tests/DataMigration/DataMigrationTests.cs index 2912a3646..ae4b65864 100644 --- a/src/Orchard.Tests/DataMigration/DataMigrationTests.cs +++ b/src/Orchard.Tests/DataMigration/DataMigrationTests.cs @@ -68,6 +68,7 @@ namespace Orchard.Tests.DataMigration { builder.RegisterType().As(); builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>)); builder.RegisterType().As(); + builder.RegisterType().As(); _session = _sessionFactory.OpenSession(); builder.RegisterInstance(new DefaultContentManagerTests.TestSessionLocator(_session)).As(); foreach(var type in dataMigrations) { diff --git a/src/Orchard.Tests/Environment/Extensions/ExtensionLoaderCoordinatorTests.cs b/src/Orchard.Tests/Environment/Extensions/ExtensionLoaderCoordinatorTests.cs index 457a00288..a88a7576e 100644 --- a/src/Orchard.Tests/Environment/Extensions/ExtensionLoaderCoordinatorTests.cs +++ b/src/Orchard.Tests/Environment/Extensions/ExtensionLoaderCoordinatorTests.cs @@ -27,6 +27,7 @@ namespace Orchard.Tests.Environment.Extensions { builder.RegisterInstance(_folders).As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); _container = builder.Build(); _manager = _container.Resolve(); @@ -360,7 +361,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager()); + IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); var testFeature = extensionManager.AvailableExtensions() .SelectMany(x => x.Features); @@ -386,7 +387,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager()); + IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); var testFeature = extensionManager.AvailableExtensions() .SelectMany(x => x.Features); @@ -415,7 +416,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager()); + IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); var testFeature = extensionManager.AvailableExtensions() .SelectMany(x => x.Features) .Single(x => x.Id == "TestFeature"); @@ -445,7 +446,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager()); + IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); var testFeature = extensionManager.AvailableExtensions() .SelectMany(x => x.Features) .Single(x => x.Id == "TestFeature"); @@ -473,7 +474,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager()); + IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); var testModule = extensionManager.AvailableExtensions() .SelectMany(x => x.Features) .Single(x => x.Id == "TestModule"); @@ -497,7 +498,7 @@ Version: 1.0.3 OrchardVersion: 1 "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager()); + IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); var minimalisticModule = extensionManager.AvailableExtensions().Single(x => x.Id == "Minimalistic"); Assert.That(minimalisticModule.Features.Count(), Is.EqualTo(1)); @@ -516,7 +517,7 @@ Version: 1.0.3 OrchardVersion: 1 "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager()); + IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); var minimalisticModule = extensionManager.AvailableExtensions().Single(x => x.Id == "Minimalistic"); Assert.That(minimalisticModule.Features.Count(), Is.EqualTo(1)); diff --git a/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs b/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs index a2d91a123..4f7b6245b 100644 --- a/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs +++ b/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs @@ -28,6 +28,7 @@ namespace Orchard.Tests.Environment.Extensions { builder.RegisterInstance(_folders).As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); _container = builder.Build(); _manager = _container.Resolve(); @@ -283,7 +284,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager()); + IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); var testFeature = extensionManager.AvailableExtensions() .SelectMany(x => x.Features); @@ -309,7 +310,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager()); + IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); var testFeature = extensionManager.AvailableExtensions() .SelectMany(x => x.Features); @@ -346,7 +347,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager()); + IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); var testFeature = extensionManager.AvailableExtensions() .SelectMany(x => x.Features) .Single(x => x.Id == "TestFeature"); @@ -376,7 +377,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager()); + IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); var testFeature = extensionManager.AvailableExtensions() .SelectMany(x => x.Features) .Single(x => x.Id == "TestFeature"); @@ -404,7 +405,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager()); + IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); var testModule = extensionManager.AvailableExtensions() .SelectMany(x => x.Features) .Single(x => x.Id == "TestModule"); @@ -428,7 +429,7 @@ Version: 1.0.3 OrchardVersion: 1 "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager()); + IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); var minimalisticModule = extensionManager.AvailableExtensions().Single(x => x.Id == "Minimalistic"); Assert.That(minimalisticModule.Features.Count(), Is.EqualTo(1)); @@ -464,7 +465,7 @@ Features: Dependencies: Beta "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager()); + IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); var features = extensionManager.AvailableFeatures(); Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo(" folders, StubLoaders loader, string expectedOrder) { - var extensionManager = new ExtensionManager(folders, new[] { loader }, new StubCacheManager()); + var extensionManager = new ExtensionManager(folders, new[] { loader }, new StubCacheManager(), new StubAsyncTokenProvider()); var features = extensionManager.AvailableFeatures(); Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo(expectedOrder)); } diff --git a/src/Orchard.Tests/Environment/Features/FeatureManagerTests.cs b/src/Orchard.Tests/Environment/Features/FeatureManagerTests.cs index 20bc33887..fd622317c 100644 --- a/src/Orchard.Tests/Environment/Features/FeatureManagerTests.cs +++ b/src/Orchard.Tests/Environment/Features/FeatureManagerTests.cs @@ -39,6 +39,7 @@ namespace Orchard.Tests.Environment.Features { builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); diff --git a/src/Orchard.Tests/Stubs/StubCacheManager.cs b/src/Orchard.Tests/Stubs/StubCacheManager.cs index 0537ef6e0..a25790d54 100644 --- a/src/Orchard.Tests/Stubs/StubCacheManager.cs +++ b/src/Orchard.Tests/Stubs/StubCacheManager.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using Orchard.Caching; namespace Orchard.Tests.Stubs { @@ -16,4 +18,21 @@ namespace Orchard.Tests.Stubs { return _defaultCacheManager.GetCache(); } } + + public class StubAsyncTokenProvider : IAsyncTokenProvider { + public IVolatileToken GetToken(Action> task) { + var tokens = new List(); + task(tokens.Add); + return new Token(tokens); + } + public class Token : IVolatileToken { + private readonly List _tokens; + + public Token(List tokens) { + _tokens = tokens; + } + + public bool IsCurrent { get { return _tokens.All(t => t.IsCurrent); } } + } + } } diff --git a/src/Orchard/Caching/DefaultAsyncTokenProvider.cs b/src/Orchard/Caching/DefaultAsyncTokenProvider.cs new file mode 100644 index 000000000..9bf8e9918 --- /dev/null +++ b/src/Orchard/Caching/DefaultAsyncTokenProvider.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Orchard.Logging; + +namespace Orchard.Caching { + public class DefaultAsyncTokenProvider : IAsyncTokenProvider { + public DefaultAsyncTokenProvider() { + Logger = NullLogger.Instance; + } + + public ILogger Logger { get; set; } + + public IVolatileToken GetToken(Action> task) { + var token = new AsyncVolativeToken(task, Logger); + token.QueueWorkItem(); + return token; + } + + public class AsyncVolativeToken : IVolatileToken { + private readonly Action> _task; + private readonly List _taskTokens = new List(); + private volatile Exception _taskException; + private volatile bool _isTaskFinished; + + public AsyncVolativeToken(Action> task, ILogger logger) { + _task = task; + Logger = logger; + } + + public ILogger Logger { get; set; } + + public void QueueWorkItem() { + // Start a work item to collect tokens in our internal array + ThreadPool.QueueUserWorkItem((state) => { + try { + _task(token => _taskTokens.Add(token)); + } + catch (Exception e) { + Logger.Error(e, "Error while monitoring extension files. Assuming extensions are not current."); + _taskException = e; + } + finally { + _isTaskFinished = true; + } + }); + } + + public bool IsCurrent { + get { + // We are current until the task has finished + if (_taskException != null) { + return false; + } + if (_isTaskFinished) { + return _taskTokens.All(t => t.IsCurrent); + } + return true; + } + } + } + } +} \ No newline at end of file diff --git a/src/Orchard/Caching/IAsyncTokenProvider.cs b/src/Orchard/Caching/IAsyncTokenProvider.cs new file mode 100644 index 000000000..efea7b80e --- /dev/null +++ b/src/Orchard/Caching/IAsyncTokenProvider.cs @@ -0,0 +1,7 @@ +using System; + +namespace Orchard.Caching { + public interface IAsyncTokenProvider { + IVolatileToken GetToken(Action> task); + } +} \ No newline at end of file diff --git a/src/Orchard/Environment/Extensions/ExtensionManager.cs b/src/Orchard/Environment/Extensions/ExtensionManager.cs index 19e80c12b..d16c0b8f6 100644 --- a/src/Orchard/Environment/Extensions/ExtensionManager.cs +++ b/src/Orchard/Environment/Extensions/ExtensionManager.cs @@ -13,14 +13,21 @@ using Orchard.Utility.Extensions; namespace Orchard.Environment.Extensions { public class ExtensionManager : IExtensionManager { private readonly IEnumerable _folders; + private readonly IAsyncTokenProvider _asyncTokenProvider; private readonly ICacheManager _cacheManager; private readonly IEnumerable _loaders; public Localizer T { get; set; } public ILogger Logger { get; set; } - public ExtensionManager(IEnumerable folders, IEnumerable loaders, ICacheManager cacheManager) { + public ExtensionManager( + IEnumerable folders, + IEnumerable loaders, + ICacheManager cacheManager, + IAsyncTokenProvider asyncTokenProvider) { + _folders = folders; + _asyncTokenProvider = asyncTokenProvider; _cacheManager = cacheManager; _loaders = loaders.OrderBy(x => x.Order).ToArray(); T = NullLocalizer.Instance; @@ -38,7 +45,7 @@ namespace Orchard.Environment.Extensions { } public IEnumerable AvailableFeatures() { - return _cacheManager.Get("...", ctx => + return _cacheManager.Get("...", ctx => AvailableExtensions().SelectMany(ext => ext.Features).OrderByDependenciesAndPriorities(HasDependency, GetPriority).ToReadOnlyCollection()); } @@ -58,7 +65,7 @@ namespace Orchard.Environment.Extensions { // Themes implicitly depend on modules to ensure build and override ordering return true; } - + if (DefaultExtensionTypes.IsTheme(subject.Extension.ExtensionType)) { // Theme depends on another if it is its base theme return item.Extension.BaseTheme == subject.Id; @@ -91,9 +98,11 @@ namespace Orchard.Environment.Extensions { extensionEntry = _cacheManager.Get(extensionId, ctx => { var entry = BuildEntry(extensionDescriptor); if (entry != null) { - foreach (var loader in _loaders) { - loader.Monitor(entry.Descriptor, (token) => ctx.Monitor(token)); - } + ctx.Monitor(_asyncTokenProvider.GetToken(monitor => { + foreach (var loader in _loaders) { + loader.Monitor(entry.Descriptor, token => monitor(token)); + } + })); } return entry; }); @@ -134,7 +143,7 @@ namespace Orchard.Environment.Extensions { } return extensionId; } - + private ExtensionEntry BuildEntry(ExtensionDescriptor descriptor) { foreach (var loader in _loaders) { ExtensionEntry entry = loader.Load(descriptor); diff --git a/src/Orchard/Environment/Extensions/ExtensionMonitoringCoordinator.cs b/src/Orchard/Environment/Extensions/ExtensionMonitoringCoordinator.cs index e66bf1191..de139fd87 100644 --- a/src/Orchard/Environment/Extensions/ExtensionMonitoringCoordinator.cs +++ b/src/Orchard/Environment/Extensions/ExtensionMonitoringCoordinator.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using Orchard.Caching; using Orchard.Environment.Extensions.Loaders; using Orchard.Environment.Extensions.Models; @@ -11,14 +10,18 @@ using Orchard.Logging; namespace Orchard.Environment.Extensions { public class ExtensionMonitoringCoordinator : IExtensionMonitoringCoordinator { private readonly IVirtualPathMonitor _virtualPathMonitor; + private readonly IAsyncTokenProvider _asyncTokenProvider; private readonly IExtensionManager _extensionManager; private readonly IEnumerable _loaders; - public ExtensionMonitoringCoordinator(IVirtualPathMonitor virtualPathMonitor, + public ExtensionMonitoringCoordinator( + IVirtualPathMonitor virtualPathMonitor, + IAsyncTokenProvider asyncTokenProvider, IExtensionManager extensionManager, IEnumerable loaders) { _virtualPathMonitor = virtualPathMonitor; + _asyncTokenProvider = asyncTokenProvider; _extensionManager = extensionManager; _loaders = loaders; @@ -33,11 +36,8 @@ namespace Orchard.Environment.Extensions { if (Disabled) return; - //PREF: Monitor extensions asynchronously. IsCurrent will be 'true' - // until all tokens are collected by the work function. - var token = new AsyncVolativeToken(MonitorExtensionsWork, Logger); - monitor(token); - token.QueueWorkItem(); + //PERF: Monitor extensions asynchronously. + monitor(_asyncTokenProvider.GetToken(MonitorExtensionsWork)); } public void MonitorExtensionsWork(Action monitor) { @@ -55,48 +55,5 @@ namespace Orchard.Environment.Extensions { } Logger.Information("Done monitoring extension files..."); } - - public class AsyncVolativeToken : IVolatileToken { - private readonly Action> _task; - private readonly List _taskTokens = new List(); - private volatile Exception _taskException; - private volatile bool _isTaskFinished; - - public AsyncVolativeToken(Action> task, ILogger logger) { - _task = task; - Logger = logger; - } - - public ILogger Logger { get; set; } - - public void QueueWorkItem() { - // Start a work item to collect tokens in our internal array - ThreadPool.QueueUserWorkItem((state) => { - try { - _task(token => _taskTokens.Add(token)); - } - catch (Exception e) { - Logger.Error(e, "Error while monitoring extension files. Assuming extensions are not current."); - _taskException = e; - } - finally { - _isTaskFinished = true; - } - }); - } - - public bool IsCurrent { - get { - // We are current until the task has finished - if (_taskException != null) { - return false; - } - if (_isTaskFinished) { - return _taskTokens.All(t => t.IsCurrent); - } - return true; - } - } - } } } \ No newline at end of file diff --git a/src/Orchard/Environment/OrchardStarter.cs b/src/Orchard/Environment/OrchardStarter.cs index 6e94ae1bf..0078986a5 100644 --- a/src/Orchard/Environment/OrchardStarter.cs +++ b/src/Orchard/Environment/OrchardStarter.cs @@ -39,6 +39,7 @@ namespace Orchard.Environment { // a single default host implementation is needed for bootstrapping a web app domain builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index 885e238a8..0adf5fcb3 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -175,7 +175,9 @@ + + From 48a48e4cfe46e86a1240d5cdcca4adbb9d77bdc6 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Mon, 30 May 2011 12:12:00 -0700 Subject: [PATCH 102/139] PERF: Don't look for themes in "~/Core". It's not supported, hence it's unecessary disk I/O. --HG-- branch : 1.x --- src/Orchard/Environment/OrchardStarter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Orchard/Environment/OrchardStarter.cs b/src/Orchard/Environment/OrchardStarter.cs index 0078986a5..7480ec7f3 100644 --- a/src/Orchard/Environment/OrchardStarter.cs +++ b/src/Orchard/Environment/OrchardStarter.cs @@ -84,7 +84,7 @@ namespace Orchard.Environment { builder.RegisterType().As().SingleInstance() .WithParameter(new NamedParameter("paths", new[] { "~/Core", "~/Modules" })); builder.RegisterType().As().SingleInstance() - .WithParameter(new NamedParameter("paths", new[] { "~/Core", "~/Themes" })); + .WithParameter(new NamedParameter("paths", new[] { "~/Themes" })); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); From 3ca3234bbf3118c7891def3e7ce0596bb08fe21e Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Mon, 30 May 2011 14:17:06 -0700 Subject: [PATCH 103/139] Refactor extension folder harvesting Use composition over inheritance. This will be useful later to improve performance of module/theme manifest harvesting. --HG-- branch : 1.x rename : src/Orchard/Environment/Extensions/Folders/ExtensionFolders.cs => src/Orchard/Environment/Extensions/Folders/ExtensionHarvester.cs --- .../Migrations/SchemaCommandGeneratorTests.cs | 2 +- .../Recipes/Services/RecipeManagerTests.cs | 3 +- .../DataMigration/DataMigrationTests.cs | 2 +- .../Extensions/ExtensionFoldersTests.cs | 12 +++-- .../ExtensionLoaderCoordinatorTests.cs | 2 +- .../Extensions/ExtensionManagerTests.cs | 2 +- .../Services/PackageManager.cs | 2 +- ...ensionFolders.cs => ExtensionHarvester.cs} | 51 ++++++++----------- .../Extensions/Folders/IExtensionHarvester.cs | 8 +++ .../Extensions/Folders/ModuleFolders.cs | 17 +++++-- .../Extensions/Folders/ThemeFolders.cs | 17 +++++-- src/Orchard/Environment/OrchardStarter.cs | 1 + src/Orchard/Orchard.Framework.csproj | 3 +- 13 files changed, 70 insertions(+), 52 deletions(-) rename src/Orchard/Environment/Extensions/Folders/{ExtensionFolders.cs => ExtensionHarvester.cs} (89%) create mode 100644 src/Orchard/Environment/Extensions/Folders/IExtensionHarvester.cs diff --git a/src/Orchard.Tests.Modules/Migrations/SchemaCommandGeneratorTests.cs b/src/Orchard.Tests.Modules/Migrations/SchemaCommandGeneratorTests.cs index 1307339b5..551fffe5b 100644 --- a/src/Orchard.Tests.Modules/Migrations/SchemaCommandGeneratorTests.cs +++ b/src/Orchard.Tests.Modules/Migrations/SchemaCommandGeneratorTests.cs @@ -125,7 +125,7 @@ Features: public IEnumerable AvailableExtensions() { foreach (var e in Manifests) { string name = e.Key; - yield return ExtensionFolders.GetDescriptorForExtension("~/", name, DefaultExtensionTypes.Module, Manifests[name]); + yield return ExtensionHarvester.GetDescriptorForExtension("~/", name, DefaultExtensionTypes.Module, Manifests[name]); } } } diff --git a/src/Orchard.Tests.Modules/Recipes/Services/RecipeManagerTests.cs b/src/Orchard.Tests.Modules/Recipes/Services/RecipeManagerTests.cs index 540d8f922..372cba562 100644 --- a/src/Orchard.Tests.Modules/Recipes/Services/RecipeManagerTests.cs +++ b/src/Orchard.Tests.Modules/Recipes/Services/RecipeManagerTests.cs @@ -62,7 +62,8 @@ namespace Orchard.Tests.Modules.Recipes.Services { } var builder = new ContainerBuilder(); - _folders = new ModuleFolders(new[] { _tempFolderName }, new StubCacheManager(), new StubWebSiteFolder()); + var harvester = new ExtensionHarvester(new StubCacheManager(), new StubWebSiteFolder()); + _folders = new ModuleFolders(new[] { _tempFolderName }, harvester); builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); diff --git a/src/Orchard.Tests/DataMigration/DataMigrationTests.cs b/src/Orchard.Tests/DataMigration/DataMigrationTests.cs index ae4b65864..9697aa0aa 100644 --- a/src/Orchard.Tests/DataMigration/DataMigrationTests.cs +++ b/src/Orchard.Tests/DataMigration/DataMigrationTests.cs @@ -92,7 +92,7 @@ namespace Orchard.Tests.DataMigration { public IEnumerable AvailableExtensions() { foreach (var e in Manifests) { string name = e.Key; - yield return ExtensionFolders.GetDescriptorForExtension("~/", name, DefaultExtensionTypes.Module, Manifests[name]); + yield return ExtensionHarvester.GetDescriptorForExtension("~/", name, DefaultExtensionTypes.Module, Manifests[name]); } } } diff --git a/src/Orchard.Tests/Environment/Extensions/ExtensionFoldersTests.cs b/src/Orchard.Tests/Environment/Extensions/ExtensionFoldersTests.cs index 9fddee3d5..bf677980d 100644 --- a/src/Orchard.Tests/Environment/Extensions/ExtensionFoldersTests.cs +++ b/src/Orchard.Tests/Environment/Extensions/ExtensionFoldersTests.cs @@ -49,7 +49,8 @@ namespace Orchard.Tests.Environment.Extensions { [Test] public void IdsFromFoldersWithModuleTxtShouldBeListed() { - IExtensionFolders folders = new ModuleFolders(new[] { _tempFolderName }, new StubCacheManager(), new StubWebSiteFolder()); + var harvester = new ExtensionHarvester(new StubCacheManager(), new StubWebSiteFolder()); + IExtensionFolders folders = new ModuleFolders(new[] { _tempFolderName }, harvester); var ids = folders.AvailableExtensions().Select(d => d.Id); Assert.That(ids.Count(), Is.EqualTo(5)); Assert.That(ids, Has.Some.EqualTo("Sample1")); // Sample1 - obviously @@ -61,7 +62,8 @@ namespace Orchard.Tests.Environment.Extensions { [Test] public void ModuleTxtShouldBeParsedAndReturnedAsYamlDocument() { - IExtensionFolders folders = new ModuleFolders(new[] { _tempFolderName }, new StubCacheManager(), new StubWebSiteFolder()); + var harvester = new ExtensionHarvester(new StubCacheManager(), new StubWebSiteFolder()); + IExtensionFolders folders = new ModuleFolders(new[] { _tempFolderName }, harvester); var sample1 = folders.AvailableExtensions().Single(d => d.Id == "Sample1"); Assert.That(sample1.Id, Is.Not.Empty); Assert.That(sample1.Author, Is.EqualTo("Bertrand Le Roy")); // Sample1 @@ -69,7 +71,8 @@ namespace Orchard.Tests.Environment.Extensions { [Test] public void NamesFromFoldersWithModuleTxtShouldFallBackToIdIfNotGiven() { - IExtensionFolders folders = new ModuleFolders(new[] { _tempFolderName }, new StubCacheManager(), new StubWebSiteFolder()); + var harvester = new ExtensionHarvester(new StubCacheManager(), new StubWebSiteFolder()); + IExtensionFolders folders = new ModuleFolders(new[] { _tempFolderName }, harvester); var names = folders.AvailableExtensions().Select(d => d.Name); Assert.That(names.Count(), Is.EqualTo(5)); Assert.That(names, Has.Some.EqualTo("Le plug-in français")); // Sample1 @@ -81,7 +84,8 @@ namespace Orchard.Tests.Environment.Extensions { [Test] public void PathsFromFoldersWithModuleTxtShouldFallBackAppropriatelyIfNotGiven() { - IExtensionFolders folders = new ModuleFolders(new[] { _tempFolderName }, new StubCacheManager(), new StubWebSiteFolder()); + var harvester = new ExtensionHarvester(new StubCacheManager(), new StubWebSiteFolder()); + IExtensionFolders folders = new ModuleFolders(new[] { _tempFolderName }, harvester); var paths = folders.AvailableExtensions().Select(d => d.Path); Assert.That(paths.Count(), Is.EqualTo(5)); Assert.That(paths, Has.Some.EqualTo("Sample1")); // Sample1 - Id, Name invalid URL segment diff --git a/src/Orchard.Tests/Environment/Extensions/ExtensionLoaderCoordinatorTests.cs b/src/Orchard.Tests/Environment/Extensions/ExtensionLoaderCoordinatorTests.cs index a88a7576e..c9b62564a 100644 --- a/src/Orchard.Tests/Environment/Extensions/ExtensionLoaderCoordinatorTests.cs +++ b/src/Orchard.Tests/Environment/Extensions/ExtensionLoaderCoordinatorTests.cs @@ -46,7 +46,7 @@ namespace Orchard.Tests.Environment.Extensions { public IEnumerable AvailableExtensions() { foreach (var e in Manifests) { string name = e.Key; - yield return ExtensionFolders.GetDescriptorForExtension("~/", name, _extensionType, Manifests[name]); + yield return ExtensionHarvester.GetDescriptorForExtension("~/", name, _extensionType, Manifests[name]); } } } diff --git a/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs b/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs index 4f7b6245b..0b62c0c47 100644 --- a/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs +++ b/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs @@ -51,7 +51,7 @@ namespace Orchard.Tests.Environment.Extensions { public IEnumerable AvailableExtensions() { foreach (var e in Manifests) { string name = e.Key; - yield return ExtensionFolders.GetDescriptorForExtension("~/", name, _extensionType, Manifests[name]); + yield return ExtensionHarvester.GetDescriptorForExtension("~/", name, _extensionType, Manifests[name]); } } } diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageManager.cs b/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageManager.cs index ea8ac6aac..77b4660d9 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageManager.cs +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageManager.cs @@ -78,7 +78,7 @@ namespace Orchard.Packaging.Services { if (packageFile != null) { string extensionId = Path.GetFileName(Path.GetDirectoryName(packageFile.Path).TrimEnd('/', '\\')); using (StreamReader streamReader = new StreamReader(packageFile.GetStream())) { - return ExtensionFolders.GetDescriptorForExtension("", extensionId, extensionType, streamReader.ReadToEnd()); + return ExtensionHarvester.GetDescriptorForExtension("", extensionId, extensionType, streamReader.ReadToEnd()); } } diff --git a/src/Orchard/Environment/Extensions/Folders/ExtensionFolders.cs b/src/Orchard/Environment/Extensions/Folders/ExtensionHarvester.cs similarity index 89% rename from src/Orchard/Environment/Extensions/Folders/ExtensionFolders.cs rename to src/Orchard/Environment/Extensions/Folders/ExtensionHarvester.cs index 466494348..fbd25ee4e 100644 --- a/src/Orchard/Environment/Extensions/Folders/ExtensionFolders.cs +++ b/src/Orchard/Environment/Extensions/Folders/ExtensionHarvester.cs @@ -10,7 +10,7 @@ using Orchard.Logging; using Orchard.Utility.Extensions; namespace Orchard.Environment.Extensions.Folders { - public class ExtensionFolders : IExtensionFolders { + public class ExtensionHarvester : IExtensionHarvester { private const string NameSection = "name"; private const string PathSection = "path"; private const string DescriptionSection = "description"; @@ -29,23 +29,10 @@ namespace Orchard.Environment.Extensions.Folders { private const string PrioritySection = "priority"; private const string FeaturesSection = "features"; - private readonly IEnumerable _paths; - private readonly string _manifestName; - private readonly string _extensionType; - private readonly bool _manifestIsOptional; private readonly ICacheManager _cacheManager; private readonly IWebSiteFolder _webSiteFolder; - protected ExtensionFolders( - IEnumerable paths, - string manifestName, - bool manifestIsOptional, - ICacheManager cacheManager, - IWebSiteFolder webSiteFolder) { - _paths = paths; - _manifestName = manifestName; - _extensionType = manifestName == "Theme.txt" ? DefaultExtensionTypes.Theme : DefaultExtensionTypes.Module; - _manifestIsOptional = manifestIsOptional; + public ExtensionHarvester(ICacheManager cacheManager, IWebSiteFolder webSiteFolder) { _cacheManager = cacheManager; _webSiteFolder = webSiteFolder; Logger = NullLogger.Instance; @@ -55,24 +42,30 @@ namespace Orchard.Environment.Extensions.Folders { public Localizer T { get; set; } public ILogger Logger { get; set; } - public IEnumerable AvailableExtensions() { - return _paths - .SelectMany(path => _cacheManager.Get(path, ctx => { - ctx.Monitor(_webSiteFolder.WhenPathChanges(ctx.Key)); - return AvailableExtensionsInFolder(ctx.Key); - })) + public IEnumerable HarvestExtensions(IEnumerable paths, string extensionType, string manifestName, bool manifestIsOptional) { + return paths + .SelectMany(path => HarvestExtensions(path, extensionType, manifestName, manifestIsOptional)) .ToList(); } - private List AvailableExtensionsInFolder(string path) { + private IEnumerable HarvestExtensions(string path, string extensionType, string manifestName, bool manifestIsOptional) { + string key = string.Format("{0}-{1}-{2}", path, manifestName, extensionType); + + return _cacheManager.Get(key, ctx => { + ctx.Monitor(_webSiteFolder.WhenPathChanges(path)); + return AvailableExtensionsInFolder(path, extensionType, manifestName, manifestIsOptional); + }); + } + + private List AvailableExtensionsInFolder(string path, string extensionType, string manifestName, bool manifestIsOptional) { Logger.Information("Start looking for extensions in '{0}'...", path); var subfolderPaths = _webSiteFolder.ListDirectories(path); var localList = new List(); foreach (var subfolderPath in subfolderPaths) { var extensionId = Path.GetFileName(subfolderPath.TrimEnd('/', '\\')); - var manifestPath = Path.Combine(subfolderPath, _manifestName); + var manifestPath = Path.Combine(subfolderPath, manifestName); try { - var descriptor = GetExtensionDescriptor(path, extensionId, manifestPath); + var descriptor = GetExtensionDescriptor(path, extensionId, extensionType, manifestPath, manifestIsOptional); if (descriptor == null) continue; @@ -124,12 +117,12 @@ namespace Orchard.Environment.Extensions.Folders { return extensionDescriptor; } - private ExtensionDescriptor GetExtensionDescriptor(string locationPath, string extensionId, string manifestPath) { + private ExtensionDescriptor GetExtensionDescriptor(string locationPath, string extensionId, string extensionType, string manifestPath, bool manifestIsOptional) { return _cacheManager.Get(manifestPath, context => { context.Monitor(_webSiteFolder.WhenPathChanges(manifestPath)); var manifestText = _webSiteFolder.ReadFile(manifestPath); if (manifestText == null) { - if (_manifestIsOptional) { + if (manifestIsOptional) { manifestText = string.Format("Id: {0}", extensionId); } else { @@ -137,14 +130,10 @@ namespace Orchard.Environment.Extensions.Folders { } } - return GetDescriptorForExtension(locationPath, extensionId, manifestText); + return GetDescriptorForExtension(locationPath, extensionId, extensionType, manifestText); }); } - private ExtensionDescriptor GetDescriptorForExtension(string locationPath, string extensionId, string manifestText) { - return GetDescriptorForExtension(locationPath, extensionId, _extensionType, manifestText); - } - private static Dictionary ParseManifest(string manifestText) { var manifest = new Dictionary(); diff --git a/src/Orchard/Environment/Extensions/Folders/IExtensionHarvester.cs b/src/Orchard/Environment/Extensions/Folders/IExtensionHarvester.cs new file mode 100644 index 000000000..5afa502af --- /dev/null +++ b/src/Orchard/Environment/Extensions/Folders/IExtensionHarvester.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; +using Orchard.Environment.Extensions.Models; + +namespace Orchard.Environment.Extensions.Folders { + public interface IExtensionHarvester { + IEnumerable HarvestExtensions(IEnumerable paths, string extensionType, string manifestName, bool manifestIsOptional); + } +} \ No newline at end of file diff --git a/src/Orchard/Environment/Extensions/Folders/ModuleFolders.cs b/src/Orchard/Environment/Extensions/Folders/ModuleFolders.cs index dd8569f41..b504661df 100644 --- a/src/Orchard/Environment/Extensions/Folders/ModuleFolders.cs +++ b/src/Orchard/Environment/Extensions/Folders/ModuleFolders.cs @@ -1,11 +1,18 @@ using System.Collections.Generic; -using Orchard.Caching; -using Orchard.FileSystems.WebSite; +using Orchard.Environment.Extensions.Models; namespace Orchard.Environment.Extensions.Folders { - public class ModuleFolders : ExtensionFolders { - public ModuleFolders(IEnumerable paths, ICacheManager cacheManager, IWebSiteFolder webSiteFolder) : - base(paths, "Module.txt", false/*isManifestOptional*/, cacheManager, webSiteFolder) { + public class ModuleFolders : IExtensionFolders { + private readonly IEnumerable _paths; + private readonly IExtensionHarvester _extensionHarvester; + + public ModuleFolders(IEnumerable paths, IExtensionHarvester extensionHarvester) { + _paths = paths; + _extensionHarvester = extensionHarvester; + } + + public IEnumerable AvailableExtensions() { + return _extensionHarvester.HarvestExtensions(_paths, DefaultExtensionTypes.Module, "Module.txt", false/*isManifestOptional*/); } } } \ No newline at end of file diff --git a/src/Orchard/Environment/Extensions/Folders/ThemeFolders.cs b/src/Orchard/Environment/Extensions/Folders/ThemeFolders.cs index b832514ad..c8a607e56 100644 --- a/src/Orchard/Environment/Extensions/Folders/ThemeFolders.cs +++ b/src/Orchard/Environment/Extensions/Folders/ThemeFolders.cs @@ -1,11 +1,18 @@ using System.Collections.Generic; -using Orchard.Caching; -using Orchard.FileSystems.WebSite; +using Orchard.Environment.Extensions.Models; namespace Orchard.Environment.Extensions.Folders { - public class ThemeFolders : ExtensionFolders { - public ThemeFolders(IEnumerable paths, ICacheManager cacheManager, IWebSiteFolder webSiteFolder) : - base(paths, "Theme.txt", false/*manifestIsOptional*/, cacheManager, webSiteFolder) { + public class ThemeFolders : IExtensionFolders { + private readonly IEnumerable _paths; + private readonly IExtensionHarvester _extensionHarvester; + + public ThemeFolders(IEnumerable paths, IExtensionHarvester extensionHarvester) { + _paths = paths; + _extensionHarvester = extensionHarvester; + } + + public IEnumerable AvailableExtensions() { + return _extensionHarvester.HarvestExtensions(_paths, DefaultExtensionTypes.Theme, "Theme.txt", false/*isManifestOptional*/); } } } \ No newline at end of file diff --git a/src/Orchard/Environment/OrchardStarter.cs b/src/Orchard/Environment/OrchardStarter.cs index 7480ec7f3..4e7533c3d 100644 --- a/src/Orchard/Environment/OrchardStarter.cs +++ b/src/Orchard/Environment/OrchardStarter.cs @@ -81,6 +81,7 @@ namespace Orchard.Environment { builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); { + builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance() .WithParameter(new NamedParameter("paths", new[] { "~/Core", "~/Modules" })); builder.RegisterType().As().SingleInstance() diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index 0adf5fcb3..bedebd48f 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -178,6 +178,7 @@ + @@ -710,7 +711,7 @@ - + From 750493c447187457c0eb6b0cd99bb97bfbf698d2 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Mon, 30 May 2011 17:33:59 -0700 Subject: [PATCH 104/139] Abstract away cache context This is needed to support proper behavior of cache context when executing tasks in parallel. --HG-- branch : 1.x --- src/Orchard.Tests/Stubs/StubCacheManager.cs | 2 +- src/Orchard/Caching/Cache.cs | 24 +++----- .../Caching/DefaultAcquireContextContext.cs | 56 +++++++++++++++++++ src/Orchard/Caching/DefaultCacheHolder.cs | 7 ++- src/Orchard/Caching/IAcquireContextContext.cs | 5 ++ src/Orchard/Environment/OrchardStarter.cs | 1 + src/Orchard/Orchard.Framework.csproj | 3 + 7 files changed, 81 insertions(+), 17 deletions(-) create mode 100644 src/Orchard/Caching/DefaultAcquireContextContext.cs create mode 100644 src/Orchard/Caching/IAcquireContextContext.cs diff --git a/src/Orchard.Tests/Stubs/StubCacheManager.cs b/src/Orchard.Tests/Stubs/StubCacheManager.cs index a25790d54..868ac6a44 100644 --- a/src/Orchard.Tests/Stubs/StubCacheManager.cs +++ b/src/Orchard.Tests/Stubs/StubCacheManager.cs @@ -8,7 +8,7 @@ namespace Orchard.Tests.Stubs { private readonly ICacheManager _defaultCacheManager; public StubCacheManager() { - _defaultCacheManager = new DefaultCacheManager(this.GetType(), new DefaultCacheHolder()); + _defaultCacheManager = new DefaultCacheManager(this.GetType(), new DefaultCacheHolder(new DefaultAcquireContextContext())); } public TResult Get(TKey key, Func, TResult> acquire) { return _defaultCacheManager.Get(key, acquire); diff --git a/src/Orchard/Caching/Cache.cs b/src/Orchard/Caching/Cache.cs index ac8c449e2..7c9f6d496 100644 --- a/src/Orchard/Caching/Cache.cs +++ b/src/Orchard/Caching/Cache.cs @@ -5,9 +5,11 @@ using System.Linq; namespace Orchard.Caching { public class Cache : ICache { + private readonly IAcquireContextContext _acquireContextContext; private readonly ConcurrentDictionary _entries; - public Cache() { + public Cache(IAcquireContextContext acquireContextContext) { + _acquireContextContext = acquireContextContext; _entries = new ConcurrentDictionary(); } @@ -19,30 +21,30 @@ namespace Orchard.Caching { (k, currentEntry) => (currentEntry.GetTokens() != null && currentEntry.GetTokens().Any(t => !t.IsCurrent) ? CreateEntry(k, acquire) : currentEntry)); // Bubble up volatile tokens to parent context - if (CacheAquireContext.ThreadInstance != null && entry.GetTokens() != null) { + if (_acquireContextContext.Instance != null && entry.GetTokens() != null) { foreach (var token in entry.GetTokens()) - CacheAquireContext.ThreadInstance.Monitor(token); + _acquireContextContext.Instance.Monitor(token); } return entry.Result; } - private static CacheEntry CreateEntry(TKey k, Func, TResult> acquire) { + private CacheEntry CreateEntry(TKey k, Func, TResult> acquire) { var entry = new CacheEntry(); var context = new AcquireContext(k, entry.AddToken); IAcquireContext parentContext = null; try { // Push context - parentContext = CacheAquireContext.ThreadInstance; - CacheAquireContext.ThreadInstance = context; + parentContext = _acquireContextContext.Instance; + _acquireContextContext.Instance = context; entry.Result = acquire(context); } finally { // Pop context - CacheAquireContext.ThreadInstance = parentContext; + _acquireContextContext.Instance = parentContext; } return entry; } @@ -64,12 +66,4 @@ namespace Orchard.Caching { } } } - - /// - /// Keep track of nested caches contexts on a given thread - /// - internal static class CacheAquireContext { - [ThreadStatic] - public static IAcquireContext ThreadInstance; - } } diff --git a/src/Orchard/Caching/DefaultAcquireContextContext.cs b/src/Orchard/Caching/DefaultAcquireContextContext.cs new file mode 100644 index 000000000..e6dd87f41 --- /dev/null +++ b/src/Orchard/Caching/DefaultAcquireContextContext.cs @@ -0,0 +1,56 @@ +using System.Security.Principal; +using System.Threading; + +namespace Orchard.Caching { + /// + /// Keep track of nested caches contexts on a given thread + /// + public class DefaultAcquireContextContext : IAcquireContextContext { + public IAcquireContext Instance { + get { + var principal = Thread.CurrentPrincipal as SurrogatePrincipal; + if (principal == null) + return null; + return principal.AcquireContext; + } + set { + var surrogatePrincipal = Thread.CurrentPrincipal as SurrogatePrincipal; + if (value == null) { + if (surrogatePrincipal != null) { + surrogatePrincipal.AcquireContext = null; + Thread.CurrentPrincipal = surrogatePrincipal.ActualPrincipal; + } + } + else { + if (surrogatePrincipal == null) { + surrogatePrincipal = new SurrogatePrincipal(Thread.CurrentPrincipal); + Thread.CurrentPrincipal = surrogatePrincipal; + } + surrogatePrincipal.AcquireContext = value; + } + } + } + + public class SurrogatePrincipal : IPrincipal { + private readonly IPrincipal _principal; + + public SurrogatePrincipal(IPrincipal principal) { + _principal = principal; + } + + public bool IsInRole(string role) { + return _principal.IsInRole(role); + } + + public IIdentity Identity { + get { return _principal.Identity; } + } + + public IPrincipal ActualPrincipal { + get { return _principal; } + } + + public IAcquireContext AcquireContext { get; set; } + } + } +} \ No newline at end of file diff --git a/src/Orchard/Caching/DefaultCacheHolder.cs b/src/Orchard/Caching/DefaultCacheHolder.cs index d30d886e9..c44122968 100644 --- a/src/Orchard/Caching/DefaultCacheHolder.cs +++ b/src/Orchard/Caching/DefaultCacheHolder.cs @@ -7,8 +7,13 @@ namespace Orchard.Caching { /// The cache holder is responsible for actually storing the references to cached entities. /// public class DefaultCacheHolder : ICacheHolder { + private readonly IAcquireContextContext _acquireContextContext; private readonly ConcurrentDictionary _caches = new ConcurrentDictionary(); + public DefaultCacheHolder(IAcquireContextContext acquireContextContext) { + _acquireContextContext = acquireContextContext; + } + class CacheKey : Tuple { public CacheKey(Type component, Type key, Type result) : base(component, key, result) { @@ -24,7 +29,7 @@ namespace Orchard.Caching { /// An entry from the cache, or a new, empty one, if none is found. public ICache GetCache(Type component) { var cacheKey = new CacheKey(component, typeof(TKey), typeof(TResult)); - var result = _caches.GetOrAdd(cacheKey, k => new Cache()); + var result = _caches.GetOrAdd(cacheKey, k => new Cache(_acquireContextContext)); return (Cache)result; } } diff --git a/src/Orchard/Caching/IAcquireContextContext.cs b/src/Orchard/Caching/IAcquireContextContext.cs new file mode 100644 index 000000000..acf4953e3 --- /dev/null +++ b/src/Orchard/Caching/IAcquireContextContext.cs @@ -0,0 +1,5 @@ +namespace Orchard.Caching { + public interface IAcquireContextContext { + IAcquireContext Instance { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard/Environment/OrchardStarter.cs b/src/Orchard/Environment/OrchardStarter.cs index 4e7533c3d..46b6d7ab1 100644 --- a/src/Orchard/Environment/OrchardStarter.cs +++ b/src/Orchard/Environment/OrchardStarter.cs @@ -39,6 +39,7 @@ namespace Orchard.Environment { // a single default host implementation is needed for bootstrapping a web app domain builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index bedebd48f..32aed5d6b 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -148,6 +148,8 @@ + + @@ -178,6 +180,7 @@ + From 1c16b10aa049ee3b0b85a60167d3f61d20495083 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Mon, 30 May 2011 17:37:51 -0700 Subject: [PATCH 105/139] Run extension harvesting code in parallel task Extension harvesting is a mix of independent operations requiring file i/o and cpu usage, so they are good candidate for parellelization. Also make module harvesting in ~/Core a separate folder instance. --HG-- branch : 1.x --- .../Environment/Extensions/ExtensionManager.cs | 9 +++++++-- .../Extensions/Folders/CoreModuleFolders.cs | 18 ++++++++++++++++++ src/Orchard/Environment/OrchardStarter.cs | 4 +++- 3 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 src/Orchard/Environment/Extensions/Folders/CoreModuleFolders.cs diff --git a/src/Orchard/Environment/Extensions/ExtensionManager.cs b/src/Orchard/Environment/Extensions/ExtensionManager.cs index d16c0b8f6..28b8b60af 100644 --- a/src/Orchard/Environment/Extensions/ExtensionManager.cs +++ b/src/Orchard/Environment/Extensions/ExtensionManager.cs @@ -41,11 +41,16 @@ namespace Orchard.Environment.Extensions { } public IEnumerable AvailableExtensions() { - return _folders.SelectMany(folder => folder.AvailableExtensions()); + return _cacheManager.Get("AvailableExtensions", ctx => + _folders + .AsParallel() // Execute in parallel for each folder + .SelectMany(folder => folder.AvailableExtensions()) + .ToList() // Force execution inside the cache entry + ); } public IEnumerable AvailableFeatures() { - return _cacheManager.Get("...", ctx => + return _cacheManager.Get("AvailableFeatures", ctx => AvailableExtensions().SelectMany(ext => ext.Features).OrderByDependenciesAndPriorities(HasDependency, GetPriority).ToReadOnlyCollection()); } diff --git a/src/Orchard/Environment/Extensions/Folders/CoreModuleFolders.cs b/src/Orchard/Environment/Extensions/Folders/CoreModuleFolders.cs new file mode 100644 index 000000000..8ad897916 --- /dev/null +++ b/src/Orchard/Environment/Extensions/Folders/CoreModuleFolders.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using Orchard.Environment.Extensions.Models; + +namespace Orchard.Environment.Extensions.Folders { + public class CoreModuleFolders : IExtensionFolders { + private readonly IEnumerable _paths; + private readonly IExtensionHarvester _extensionHarvester; + + public CoreModuleFolders(IEnumerable paths, IExtensionHarvester extensionHarvester) { + _paths = paths; + _extensionHarvester = extensionHarvester; + } + + public IEnumerable AvailableExtensions() { + return _extensionHarvester.HarvestExtensions(_paths, DefaultExtensionTypes.Module, "Module.txt", false/*isManifestOptional*/); + } + } +} \ No newline at end of file diff --git a/src/Orchard/Environment/OrchardStarter.cs b/src/Orchard/Environment/OrchardStarter.cs index 46b6d7ab1..12e3ec101 100644 --- a/src/Orchard/Environment/OrchardStarter.cs +++ b/src/Orchard/Environment/OrchardStarter.cs @@ -84,7 +84,9 @@ namespace Orchard.Environment { { builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance() - .WithParameter(new NamedParameter("paths", new[] { "~/Core", "~/Modules" })); + .WithParameter(new NamedParameter("paths", new[] { "~/Modules" })); + builder.RegisterType().As().SingleInstance() + .WithParameter(new NamedParameter("paths", new[] { "~/Core" })); builder.RegisterType().As().SingleInstance() .WithParameter(new NamedParameter("paths", new[] { "~/Themes" })); From 7122000ae0eaa8ef0b7ad69590a1d11b056fd25c Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Mon, 30 May 2011 22:31:27 -0700 Subject: [PATCH 106/139] Refactor cache context to support correct parallel execution We need a new service, IParallelCacheContext, which allows tracking the current cache context across threads and tasks running in the thread pool. --HG-- branch : 1.x --- .../Providers/CommonPartProviderTests.cs | 1 + .../Metadata/ContentDefinitionManagerTests.cs | 1 + .../Commands/CodeGenerationCommandsTests.cs | 1 + .../Migrations/SchemaCommandGeneratorTests.cs | 1 + .../Services/FileBasedProjectSystemTests.cs | 1 + .../Services/PackageInstallerTests.cs | 1 + .../RecipeHandlers/ModuleRecipeHandlerTest.cs | 1 + .../RecipeHandlers/ThemeRecipeHandlerTest.cs | 1 + .../Recipes/Services/RecipeManagerTests.cs | 1 + .../Controllers/AccountControllerTests.cs | 1 + src/Orchard.Tests/Caching/CacheTests.cs | 2 +- .../Caching/ClockCachingTests.cs | 3 +- .../DataMigration/DataMigrationTests.cs | 1 + .../Descriptors/PlacementFileParserTests.cs | 1 + .../ExtensionLoaderCoordinatorTests.cs | 18 ++-- .../Extensions/ExtensionManagerTests.cs | 27 +++-- .../Features/FeatureManagerTests.cs | 1 + .../Dependencies/DependenciesFolderTests.cs | 1 + src/Orchard.Tests/Stubs/StubCacheManager.cs | 13 ++- src/Orchard/Caching/AcquireContext.cs | 15 +++ src/Orchard/Caching/Cache.cs | 16 +-- .../Caching/DefaultAcquireContextContext.cs | 56 ---------- .../Caching/DefaultCacheContextAccessor.cs | 20 ++++ src/Orchard/Caching/DefaultCacheHolder.cs | 8 +- .../Caching/DefaultParallelCacheContext.cs | 102 ++++++++++++++++++ src/Orchard/Caching/IAcquireContextContext.cs | 5 - src/Orchard/Caching/ICacheContextAccessor.cs | 5 + src/Orchard/Caching/IParallelCacheContext.cs | 36 +++++++ .../Extensions/ExtensionManager.cs | 17 +-- src/Orchard/Environment/OrchardStarter.cs | 3 +- src/Orchard/Orchard.Framework.csproj | 6 +- 31 files changed, 264 insertions(+), 102 deletions(-) delete mode 100644 src/Orchard/Caching/DefaultAcquireContextContext.cs create mode 100644 src/Orchard/Caching/DefaultCacheContextAccessor.cs create mode 100644 src/Orchard/Caching/DefaultParallelCacheContext.cs delete mode 100644 src/Orchard/Caching/IAcquireContextContext.cs create mode 100644 src/Orchard/Caching/ICacheContextAccessor.cs create mode 100644 src/Orchard/Caching/IParallelCacheContext.cs diff --git a/src/Orchard.Core.Tests/Common/Providers/CommonPartProviderTests.cs b/src/Orchard.Core.Tests/Common/Providers/CommonPartProviderTests.cs index b8a19e0e9..b4cfbda26 100644 --- a/src/Orchard.Core.Tests/Common/Providers/CommonPartProviderTests.cs +++ b/src/Orchard.Core.Tests/Common/Providers/CommonPartProviderTests.cs @@ -55,6 +55,7 @@ namespace Orchard.Core.Tests.Common.Providers { builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterInstance(new Mock().Object); builder.RegisterInstance(new Mock().Object); builder.RegisterInstance(new Mock().Object); diff --git a/src/Orchard.Core.Tests/Settings/Metadata/ContentDefinitionManagerTests.cs b/src/Orchard.Core.Tests/Settings/Metadata/ContentDefinitionManagerTests.cs index 6bafface8..f1fa72a95 100644 --- a/src/Orchard.Core.Tests/Settings/Metadata/ContentDefinitionManagerTests.cs +++ b/src/Orchard.Core.Tests/Settings/Metadata/ContentDefinitionManagerTests.cs @@ -50,6 +50,7 @@ namespace Orchard.Core.Tests.Settings.Metadata { .As(typeof(IMapper)); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); _container = builder.Build(); diff --git a/src/Orchard.Tests.Modules/CodeGeneration/Commands/CodeGenerationCommandsTests.cs b/src/Orchard.Tests.Modules/CodeGeneration/Commands/CodeGenerationCommandsTests.cs index 165364532..b4fcb32d7 100644 --- a/src/Orchard.Tests.Modules/CodeGeneration/Commands/CodeGenerationCommandsTests.cs +++ b/src/Orchard.Tests.Modules/CodeGeneration/Commands/CodeGenerationCommandsTests.cs @@ -50,6 +50,7 @@ namespace Orchard.Tests.Modules.CodeGeneration.Commands { builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); diff --git a/src/Orchard.Tests.Modules/Migrations/SchemaCommandGeneratorTests.cs b/src/Orchard.Tests.Modules/Migrations/SchemaCommandGeneratorTests.cs index 551fffe5b..5e62bac6f 100644 --- a/src/Orchard.Tests.Modules/Migrations/SchemaCommandGeneratorTests.cs +++ b/src/Orchard.Tests.Modules/Migrations/SchemaCommandGeneratorTests.cs @@ -88,6 +88,7 @@ namespace Orchard.Tests.Modules.Migrations { builder.RegisterType().As(); builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>)); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); diff --git a/src/Orchard.Tests.Modules/Packaging/Services/FileBasedProjectSystemTests.cs b/src/Orchard.Tests.Modules/Packaging/Services/FileBasedProjectSystemTests.cs index 5f1c54830..a372cc629 100644 --- a/src/Orchard.Tests.Modules/Packaging/Services/FileBasedProjectSystemTests.cs +++ b/src/Orchard.Tests.Modules/Packaging/Services/FileBasedProjectSystemTests.cs @@ -31,6 +31,7 @@ namespace Orchard.Tests.Modules.Packaging.Services { builder.RegisterType().As(); builder.RegisterInstance(new Mock().Object); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As(); _mockedVirtualPathProvider = new Mock(); diff --git a/src/Orchard.Tests.Modules/Packaging/Services/PackageInstallerTests.cs b/src/Orchard.Tests.Modules/Packaging/Services/PackageInstallerTests.cs index 626477065..9285d92d3 100644 --- a/src/Orchard.Tests.Modules/Packaging/Services/PackageInstallerTests.cs +++ b/src/Orchard.Tests.Modules/Packaging/Services/PackageInstallerTests.cs @@ -31,6 +31,7 @@ namespace Orchard.Tests.Modules.Packaging.Services { builder.RegisterType().As(); builder.RegisterInstance(new Mock().Object); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As(); _mockedVirtualPathProvider = new Mock(); diff --git a/src/Orchard.Tests.Modules/Recipes/RecipeHandlers/ModuleRecipeHandlerTest.cs b/src/Orchard.Tests.Modules/Recipes/RecipeHandlers/ModuleRecipeHandlerTest.cs index e1af609e9..3739ca6b8 100644 --- a/src/Orchard.Tests.Modules/Recipes/RecipeHandlers/ModuleRecipeHandlerTest.cs +++ b/src/Orchard.Tests.Modules/Recipes/RecipeHandlers/ModuleRecipeHandlerTest.cs @@ -50,6 +50,7 @@ namespace Orchard.Tests.Modules.Recipes.RecipeHandlers { builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As(); diff --git a/src/Orchard.Tests.Modules/Recipes/RecipeHandlers/ThemeRecipeHandlerTest.cs b/src/Orchard.Tests.Modules/Recipes/RecipeHandlers/ThemeRecipeHandlerTest.cs index 740e1aee9..1df9a5b16 100644 --- a/src/Orchard.Tests.Modules/Recipes/RecipeHandlers/ThemeRecipeHandlerTest.cs +++ b/src/Orchard.Tests.Modules/Recipes/RecipeHandlers/ThemeRecipeHandlerTest.cs @@ -53,6 +53,7 @@ namespace Orchard.Tests.Modules.Recipes.RecipeHandlers { builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As(); diff --git a/src/Orchard.Tests.Modules/Recipes/Services/RecipeManagerTests.cs b/src/Orchard.Tests.Modules/Recipes/Services/RecipeManagerTests.cs index 372cba562..67478b692 100644 --- a/src/Orchard.Tests.Modules/Recipes/Services/RecipeManagerTests.cs +++ b/src/Orchard.Tests.Modules/Recipes/Services/RecipeManagerTests.cs @@ -74,6 +74,7 @@ namespace Orchard.Tests.Modules.Recipes.Services { builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterInstance(_folders).As(); builder.RegisterType().As(); diff --git a/src/Orchard.Tests.Modules/Users/Controllers/AccountControllerTests.cs b/src/Orchard.Tests.Modules/Users/Controllers/AccountControllerTests.cs index b5d4cff1a..b11119ca6 100644 --- a/src/Orchard.Tests.Modules/Users/Controllers/AccountControllerTests.cs +++ b/src/Orchard.Tests.Modules/Users/Controllers/AccountControllerTests.cs @@ -77,6 +77,7 @@ namespace Orchard.Tests.Modules.Users.Controllers { builder.RegisterInstance(new Mock().Object); builder.RegisterInstance(new Mock().Object); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); diff --git a/src/Orchard.Tests/Caching/CacheTests.cs b/src/Orchard.Tests/Caching/CacheTests.cs index 09bdf353a..13af703fb 100644 --- a/src/Orchard.Tests/Caching/CacheTests.cs +++ b/src/Orchard.Tests/Caching/CacheTests.cs @@ -15,7 +15,7 @@ namespace Orchard.Tests.Caching { builder.RegisterModule(new CacheModule()); builder.RegisterType().As(); builder.RegisterType().As().SingleInstance(); - builder.RegisterType().As(); + builder.RegisterType().As(); _container = builder.Build(); _cacheManager = _container.Resolve(new TypedParameter(typeof(Type), GetType())); } diff --git a/src/Orchard.Tests/Caching/ClockCachingTests.cs b/src/Orchard.Tests/Caching/ClockCachingTests.cs index ac6618406..8464182bf 100644 --- a/src/Orchard.Tests/Caching/ClockCachingTests.cs +++ b/src/Orchard.Tests/Caching/ClockCachingTests.cs @@ -18,9 +18,8 @@ namespace Orchard.Tests.Caching { builder.RegisterModule(new CacheModule()); builder.RegisterType().As(); builder.RegisterType().As().SingleInstance(); - builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterInstance(_clock = new StubClock()); - builder.RegisterType().As(); _container = builder.Build(); _cacheManager = _container.Resolve(new TypedParameter(typeof(Type), GetType())); } diff --git a/src/Orchard.Tests/DataMigration/DataMigrationTests.cs b/src/Orchard.Tests/DataMigration/DataMigrationTests.cs index 9697aa0aa..c1de4c80f 100644 --- a/src/Orchard.Tests/DataMigration/DataMigrationTests.cs +++ b/src/Orchard.Tests/DataMigration/DataMigrationTests.cs @@ -68,6 +68,7 @@ namespace Orchard.Tests.DataMigration { builder.RegisterType().As(); builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>)); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As(); _session = _sessionFactory.OpenSession(); builder.RegisterInstance(new DefaultContentManagerTests.TestSessionLocator(_session)).As(); diff --git a/src/Orchard.Tests/DisplayManagement/Descriptors/PlacementFileParserTests.cs b/src/Orchard.Tests/DisplayManagement/Descriptors/PlacementFileParserTests.cs index a9b621afc..187a355f5 100644 --- a/src/Orchard.Tests/DisplayManagement/Descriptors/PlacementFileParserTests.cs +++ b/src/Orchard.Tests/DisplayManagement/Descriptors/PlacementFileParserTests.cs @@ -15,6 +15,7 @@ namespace Orchard.Tests.DisplayManagement.Descriptors { protected override void Register(Autofac.ContainerBuilder builder) { builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As() .As().InstancePerLifetimeScope(); } diff --git a/src/Orchard.Tests/Environment/Extensions/ExtensionLoaderCoordinatorTests.cs b/src/Orchard.Tests/Environment/Extensions/ExtensionLoaderCoordinatorTests.cs index c9b62564a..071e8faa8 100644 --- a/src/Orchard.Tests/Environment/Extensions/ExtensionLoaderCoordinatorTests.cs +++ b/src/Orchard.Tests/Environment/Extensions/ExtensionLoaderCoordinatorTests.cs @@ -27,6 +27,7 @@ namespace Orchard.Tests.Environment.Extensions { builder.RegisterInstance(_folders).As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As(); _container = builder.Build(); @@ -116,6 +117,9 @@ namespace Orchard.Tests.Environment.Extensions { #endregion } + private ExtensionManager CreateExtensionManager(StubFolders extensionFolder, StubLoaders extensionLoader) { + return new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubParallelCacheContext(), new StubAsyncTokenProvider()); + } [Test] public void AvailableExtensionsShouldFollowCatalogLocations() { @@ -361,7 +365,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); ; var testFeature = extensionManager.AvailableExtensions() .SelectMany(x => x.Features); @@ -387,7 +391,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); ; var testFeature = extensionManager.AvailableExtensions() .SelectMany(x => x.Features); @@ -416,7 +420,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); ; var testFeature = extensionManager.AvailableExtensions() .SelectMany(x => x.Features) .Single(x => x.Id == "TestFeature"); @@ -446,7 +450,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); ; var testFeature = extensionManager.AvailableExtensions() .SelectMany(x => x.Features) .Single(x => x.Id == "TestFeature"); @@ -474,7 +478,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); ; var testModule = extensionManager.AvailableExtensions() .SelectMany(x => x.Features) .Single(x => x.Id == "TestModule"); @@ -498,7 +502,7 @@ Version: 1.0.3 OrchardVersion: 1 "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); ; var minimalisticModule = extensionManager.AvailableExtensions().Single(x => x.Id == "Minimalistic"); Assert.That(minimalisticModule.Features.Count(), Is.EqualTo(1)); @@ -517,7 +521,7 @@ Version: 1.0.3 OrchardVersion: 1 "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); ; var minimalisticModule = extensionManager.AvailableExtensions().Single(x => x.Id == "Minimalistic"); Assert.That(minimalisticModule.Features.Count(), Is.EqualTo(1)); diff --git a/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs b/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs index 0b62c0c47..de340b474 100644 --- a/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs +++ b/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs @@ -28,6 +28,7 @@ namespace Orchard.Tests.Environment.Extensions { builder.RegisterInstance(_folders).As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As(); _container = builder.Build(); @@ -284,7 +285,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); var testFeature = extensionManager.AvailableExtensions() .SelectMany(x => x.Features); @@ -310,7 +311,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); var testFeature = extensionManager.AvailableExtensions() .SelectMany(x => x.Features); @@ -323,6 +324,14 @@ Features: } } + private static ExtensionManager CreateExtensionManager(StubFolders extensionFolder, StubLoaders extensionLoader) { + return CreateExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }); + } + + private static ExtensionManager CreateExtensionManager(IEnumerable extensionFolder, IEnumerable extensionLoader) { + return new ExtensionManager(extensionFolder, extensionLoader, new StubCacheManager(), new StubParallelCacheContext(), new StubAsyncTokenProvider()); + } + [Test] public void ExtensionManagerShouldReturnEmptyFeatureIfFeatureDoesNotExist() { var featureDescriptor = new FeatureDescriptor { Id = "NoSuchFeature", Extension = new ExtensionDescriptor { Id = "NoSuchFeature" } }; @@ -347,7 +356,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); ; var testFeature = extensionManager.AvailableExtensions() .SelectMany(x => x.Features) .Single(x => x.Id == "TestFeature"); @@ -377,7 +386,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); ; var testFeature = extensionManager.AvailableExtensions() .SelectMany(x => x.Features) .Single(x => x.Id == "TestFeature"); @@ -405,7 +414,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); ; var testModule = extensionManager.AvailableExtensions() .SelectMany(x => x.Features) .Single(x => x.Id == "TestModule"); @@ -429,7 +438,7 @@ Version: 1.0.3 OrchardVersion: 1 "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); ; var minimalisticModule = extensionManager.AvailableExtensions().Single(x => x.Id == "Minimalistic"); Assert.That(minimalisticModule.Features.Count(), Is.EqualTo(1)); @@ -465,7 +474,7 @@ Features: Dependencies: Beta "); - IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager(), new StubAsyncTokenProvider()); + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); ; var features = extensionManager.AvailableFeatures(); Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo(" folders, StubLoaders loader, string expectedOrder) { - var extensionManager = new ExtensionManager(folders, new[] { loader }, new StubCacheManager(), new StubAsyncTokenProvider()); + var extensionManager = CreateExtensionManager(folders, new[] { loader }); var features = extensionManager.AvailableFeatures(); Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo(expectedOrder)); } diff --git a/src/Orchard.Tests/Environment/Features/FeatureManagerTests.cs b/src/Orchard.Tests/Environment/Features/FeatureManagerTests.cs index fd622317c..d348b1db3 100644 --- a/src/Orchard.Tests/Environment/Features/FeatureManagerTests.cs +++ b/src/Orchard.Tests/Environment/Features/FeatureManagerTests.cs @@ -39,6 +39,7 @@ namespace Orchard.Tests.Environment.Features { builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); diff --git a/src/Orchard.Tests/FileSystems/Dependencies/DependenciesFolderTests.cs b/src/Orchard.Tests/FileSystems/Dependencies/DependenciesFolderTests.cs index e01023967..5d2749c74 100644 --- a/src/Orchard.Tests/FileSystems/Dependencies/DependenciesFolderTests.cs +++ b/src/Orchard.Tests/FileSystems/Dependencies/DependenciesFolderTests.cs @@ -17,6 +17,7 @@ namespace Orchard.Tests.FileSystems.Dependencies { builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As(); builder.RegisterType().As(); return builder.Build(); } diff --git a/src/Orchard.Tests/Stubs/StubCacheManager.cs b/src/Orchard.Tests/Stubs/StubCacheManager.cs index 868ac6a44..308e48da1 100644 --- a/src/Orchard.Tests/Stubs/StubCacheManager.cs +++ b/src/Orchard.Tests/Stubs/StubCacheManager.cs @@ -8,7 +8,7 @@ namespace Orchard.Tests.Stubs { private readonly ICacheManager _defaultCacheManager; public StubCacheManager() { - _defaultCacheManager = new DefaultCacheManager(this.GetType(), new DefaultCacheHolder(new DefaultAcquireContextContext())); + _defaultCacheManager = new DefaultCacheManager(this.GetType(), new DefaultCacheHolder(new DefaultCacheContextAccessor())); } public TResult Get(TKey key, Func, TResult> acquire) { return _defaultCacheManager.Get(key, acquire); @@ -19,6 +19,17 @@ namespace Orchard.Tests.Stubs { } } + public class StubParallelCacheContext : IParallelCacheContext { + public ITask CreateContextAwareTask(Func function) { + throw new NotImplementedException(); + } + + public IEnumerable RunInParallel(IEnumerable source, Func selector) { + return source.Select(selector); + } + } + + public class StubAsyncTokenProvider : IAsyncTokenProvider { public IVolatileToken GetToken(Action> task) { var tokens = new List(); diff --git a/src/Orchard/Caching/AcquireContext.cs b/src/Orchard/Caching/AcquireContext.cs index fd6fc66d5..560f421ca 100644 --- a/src/Orchard/Caching/AcquireContext.cs +++ b/src/Orchard/Caching/AcquireContext.cs @@ -14,4 +14,19 @@ namespace Orchard.Caching { public TKey Key { get; private set; } public Action Monitor { get; private set; } } + + /// + /// Simple implementation of "IAcquireContext" given a lamdba + /// + public class SimpleAcquireContext : IAcquireContext { + private readonly Action _monitor; + + public SimpleAcquireContext(Action monitor) { + _monitor = monitor; + } + + public Action Monitor { + get { return _monitor; } + } + } } diff --git a/src/Orchard/Caching/Cache.cs b/src/Orchard/Caching/Cache.cs index 7c9f6d496..9f9dfe711 100644 --- a/src/Orchard/Caching/Cache.cs +++ b/src/Orchard/Caching/Cache.cs @@ -5,11 +5,11 @@ using System.Linq; namespace Orchard.Caching { public class Cache : ICache { - private readonly IAcquireContextContext _acquireContextContext; + private readonly ICacheContextAccessor _cacheContextAccessor; private readonly ConcurrentDictionary _entries; - public Cache(IAcquireContextContext acquireContextContext) { - _acquireContextContext = acquireContextContext; + public Cache(ICacheContextAccessor cacheContextAccessor) { + _cacheContextAccessor = cacheContextAccessor; _entries = new ConcurrentDictionary(); } @@ -21,9 +21,9 @@ namespace Orchard.Caching { (k, currentEntry) => (currentEntry.GetTokens() != null && currentEntry.GetTokens().Any(t => !t.IsCurrent) ? CreateEntry(k, acquire) : currentEntry)); // Bubble up volatile tokens to parent context - if (_acquireContextContext.Instance != null && entry.GetTokens() != null) { + if (_cacheContextAccessor.Current != null && entry.GetTokens() != null) { foreach (var token in entry.GetTokens()) - _acquireContextContext.Instance.Monitor(token); + _cacheContextAccessor.Current.Monitor(token); } return entry.Result; @@ -37,14 +37,14 @@ namespace Orchard.Caching { IAcquireContext parentContext = null; try { // Push context - parentContext = _acquireContextContext.Instance; - _acquireContextContext.Instance = context; + parentContext = _cacheContextAccessor.Current; + _cacheContextAccessor.Current = context; entry.Result = acquire(context); } finally { // Pop context - _acquireContextContext.Instance = parentContext; + _cacheContextAccessor.Current = parentContext; } return entry; } diff --git a/src/Orchard/Caching/DefaultAcquireContextContext.cs b/src/Orchard/Caching/DefaultAcquireContextContext.cs deleted file mode 100644 index e6dd87f41..000000000 --- a/src/Orchard/Caching/DefaultAcquireContextContext.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.Security.Principal; -using System.Threading; - -namespace Orchard.Caching { - /// - /// Keep track of nested caches contexts on a given thread - /// - public class DefaultAcquireContextContext : IAcquireContextContext { - public IAcquireContext Instance { - get { - var principal = Thread.CurrentPrincipal as SurrogatePrincipal; - if (principal == null) - return null; - return principal.AcquireContext; - } - set { - var surrogatePrincipal = Thread.CurrentPrincipal as SurrogatePrincipal; - if (value == null) { - if (surrogatePrincipal != null) { - surrogatePrincipal.AcquireContext = null; - Thread.CurrentPrincipal = surrogatePrincipal.ActualPrincipal; - } - } - else { - if (surrogatePrincipal == null) { - surrogatePrincipal = new SurrogatePrincipal(Thread.CurrentPrincipal); - Thread.CurrentPrincipal = surrogatePrincipal; - } - surrogatePrincipal.AcquireContext = value; - } - } - } - - public class SurrogatePrincipal : IPrincipal { - private readonly IPrincipal _principal; - - public SurrogatePrincipal(IPrincipal principal) { - _principal = principal; - } - - public bool IsInRole(string role) { - return _principal.IsInRole(role); - } - - public IIdentity Identity { - get { return _principal.Identity; } - } - - public IPrincipal ActualPrincipal { - get { return _principal; } - } - - public IAcquireContext AcquireContext { get; set; } - } - } -} \ No newline at end of file diff --git a/src/Orchard/Caching/DefaultCacheContextAccessor.cs b/src/Orchard/Caching/DefaultCacheContextAccessor.cs new file mode 100644 index 000000000..56cd3db91 --- /dev/null +++ b/src/Orchard/Caching/DefaultCacheContextAccessor.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Orchard.Caching { + public class DefaultCacheContextAccessor : ICacheContextAccessor { + [ThreadStatic] + private static IAcquireContext _threadInstance; + + public static IAcquireContext ThreadInstance { + get { return _threadInstance; } + set { _threadInstance = value; } + } + + public IAcquireContext Current { + get { return ThreadInstance; } + set { ThreadInstance = value; } + } + } +} \ No newline at end of file diff --git a/src/Orchard/Caching/DefaultCacheHolder.cs b/src/Orchard/Caching/DefaultCacheHolder.cs index c44122968..83b286e69 100644 --- a/src/Orchard/Caching/DefaultCacheHolder.cs +++ b/src/Orchard/Caching/DefaultCacheHolder.cs @@ -7,11 +7,11 @@ namespace Orchard.Caching { /// The cache holder is responsible for actually storing the references to cached entities. /// public class DefaultCacheHolder : ICacheHolder { - private readonly IAcquireContextContext _acquireContextContext; + private readonly ICacheContextAccessor _cacheContextAccessor; private readonly ConcurrentDictionary _caches = new ConcurrentDictionary(); - public DefaultCacheHolder(IAcquireContextContext acquireContextContext) { - _acquireContextContext = acquireContextContext; + public DefaultCacheHolder(ICacheContextAccessor cacheContextAccessor) { + _cacheContextAccessor = cacheContextAccessor; } class CacheKey : Tuple { @@ -29,7 +29,7 @@ namespace Orchard.Caching { /// An entry from the cache, or a new, empty one, if none is found. public ICache GetCache(Type component) { var cacheKey = new CacheKey(component, typeof(TKey), typeof(TResult)); - var result = _caches.GetOrAdd(cacheKey, k => new Cache(_acquireContextContext)); + var result = _caches.GetOrAdd(cacheKey, k => new Cache(_cacheContextAccessor)); return (Cache)result; } } diff --git a/src/Orchard/Caching/DefaultParallelCacheContext.cs b/src/Orchard/Caching/DefaultParallelCacheContext.cs new file mode 100644 index 000000000..e6fad8bef --- /dev/null +++ b/src/Orchard/Caching/DefaultParallelCacheContext.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Orchard.Caching { + public class DefaultParallelCacheContext : IParallelCacheContext { + private readonly ICacheContextAccessor _cacheContextAccessor; + + public DefaultParallelCacheContext(ICacheContextAccessor cacheContextAccessor) { + _cacheContextAccessor = cacheContextAccessor; + } + + public IEnumerable RunInParallel(IEnumerable source, Func selector) { + // Create tasks that capture the current thread context + var tasks = source.Select(item => this.CreateContextAwareTask(() => selector(item))).ToList(); + + // Run tasks in parallel and combine results immediately + var result = tasks.AsParallel().Select(task => task.Execute()).ToList(); + + // Forward tokens collected by tasks to the current context + foreach(var task in tasks) { + task.Finish(); + } + return result; + } + + /// + /// Create a task that wraps some piece of code that implictly depends on the cache context. + /// The return task can be used in any execution thread (e.g. System.Threading.Tasks). + /// + public ITask CreateContextAwareTask(Func function) { + return new TaskWithAcquireContext(_cacheContextAccessor, function); + } + + public class TaskWithAcquireContext : ITask { + private readonly ICacheContextAccessor _cacheContextAccessor; + private readonly Func _function; + private IList _tokens; + + public TaskWithAcquireContext(ICacheContextAccessor cacheContextAccessor, Func function) { + _cacheContextAccessor = cacheContextAccessor; + _function = function; + } + + /// + /// Execute task and collect eventual volatile tokens + /// + public T Execute() { + IAcquireContext parentContext = _cacheContextAccessor.Current; + try { + // Push context + if (parentContext == null) { + _cacheContextAccessor.Current = new SimpleAcquireContext(AddToken); + } + + // Execute lambda + return _function(); + } + finally { + // Pop context + if (parentContext == null) { + _cacheContextAccessor.Current = parentContext; + } + } + } + + /// + /// Return tokens collected during task execution + /// + public IEnumerable Tokens { + get { + if (_tokens == null) + return Enumerable.Empty(); + return _tokens; + } + } + + public void Dispose() { + Finish(); + } + + /// + /// Forward collected tokens to current cache context + /// + public void Finish() { + var tokens = _tokens; + _tokens = null; + if (_cacheContextAccessor.Current != null && tokens != null) { + foreach (var token in tokens) { + _cacheContextAccessor.Current.Monitor(token); + } + } + } + + private void AddToken(IVolatileToken token) { + if (_tokens == null) + _tokens = new List(); + _tokens.Add(token); + } + } + } +} \ No newline at end of file diff --git a/src/Orchard/Caching/IAcquireContextContext.cs b/src/Orchard/Caching/IAcquireContextContext.cs deleted file mode 100644 index acf4953e3..000000000 --- a/src/Orchard/Caching/IAcquireContextContext.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Orchard.Caching { - public interface IAcquireContextContext { - IAcquireContext Instance { get; set; } - } -} \ No newline at end of file diff --git a/src/Orchard/Caching/ICacheContextAccessor.cs b/src/Orchard/Caching/ICacheContextAccessor.cs new file mode 100644 index 000000000..c4b3e4bf3 --- /dev/null +++ b/src/Orchard/Caching/ICacheContextAccessor.cs @@ -0,0 +1,5 @@ +namespace Orchard.Caching { + public interface ICacheContextAccessor { + IAcquireContext Current { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard/Caching/IParallelCacheContext.cs b/src/Orchard/Caching/IParallelCacheContext.cs new file mode 100644 index 000000000..b7e76b664 --- /dev/null +++ b/src/Orchard/Caching/IParallelCacheContext.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +namespace Orchard.Caching { + /// + /// Provides services to enable parallel tasks aware of the current cache context. + /// + public interface IParallelCacheContext { + /// + /// Create a task that wraps some piece of code that implictly depends on the cache context. + /// The return task can be used in any execution thread (e.g. System.Threading.Tasks). + /// + ITask CreateContextAwareTask(Func function); + + IEnumerable RunInParallel(IEnumerable source, Func selector); + } + + public interface ITask : IDisposable { + /// + /// Execute task and collect eventual volatile tokens + /// + T Execute(); + + /// + /// Return tokens collected during task execution. May be empty if nothing collected, + /// or if the task was executed in the same context as the current + /// ICacheContextAccessor.Current. + /// + IEnumerable Tokens { get; } + + /// + /// Forward collected tokens to current cache context + /// + void Finish(); + } +} diff --git a/src/Orchard/Environment/Extensions/ExtensionManager.cs b/src/Orchard/Environment/Extensions/ExtensionManager.cs index 28b8b60af..2c18ca734 100644 --- a/src/Orchard/Environment/Extensions/ExtensionManager.cs +++ b/src/Orchard/Environment/Extensions/ExtensionManager.cs @@ -15,6 +15,7 @@ namespace Orchard.Environment.Extensions { private readonly IEnumerable _folders; private readonly IAsyncTokenProvider _asyncTokenProvider; private readonly ICacheManager _cacheManager; + private readonly IParallelCacheContext _parallelCacheContext; private readonly IEnumerable _loaders; public Localizer T { get; set; } @@ -24,11 +25,13 @@ namespace Orchard.Environment.Extensions { IEnumerable folders, IEnumerable loaders, ICacheManager cacheManager, + IParallelCacheContext parallelCacheContext, IAsyncTokenProvider asyncTokenProvider) { _folders = folders; _asyncTokenProvider = asyncTokenProvider; _cacheManager = cacheManager; + _parallelCacheContext = parallelCacheContext; _loaders = loaders.OrderBy(x => x.Order).ToArray(); T = NullLocalizer.Instance; Logger = NullLogger.Instance; @@ -42,16 +45,18 @@ namespace Orchard.Environment.Extensions { public IEnumerable AvailableExtensions() { return _cacheManager.Get("AvailableExtensions", ctx => - _folders - .AsParallel() // Execute in parallel for each folder - .SelectMany(folder => folder.AvailableExtensions()) - .ToList() // Force execution inside the cache entry - ); + _parallelCacheContext + .RunInParallel(_folders, folder => folder.AvailableExtensions()) + .SelectMany(descriptors => descriptors) + .ToReadOnlyCollection()); } public IEnumerable AvailableFeatures() { return _cacheManager.Get("AvailableFeatures", ctx => - AvailableExtensions().SelectMany(ext => ext.Features).OrderByDependenciesAndPriorities(HasDependency, GetPriority).ToReadOnlyCollection()); + AvailableExtensions() + .SelectMany(ext => ext.Features) + .OrderByDependenciesAndPriorities(HasDependency, GetPriority) + .ToReadOnlyCollection()); } internal static int GetPriority(FeatureDescriptor featureDescriptor) { diff --git a/src/Orchard/Environment/OrchardStarter.cs b/src/Orchard/Environment/OrchardStarter.cs index 12e3ec101..66654d908 100644 --- a/src/Orchard/Environment/OrchardStarter.cs +++ b/src/Orchard/Environment/OrchardStarter.cs @@ -39,7 +39,8 @@ namespace Orchard.Environment { // a single default host implementation is needed for bootstrapping a web app domain builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); - builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index 32aed5d6b..4c317f8a5 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -148,8 +148,10 @@ - - + + + + From f04e67d151302258a8f40c517699dada0d7eed35 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Mon, 30 May 2011 22:55:44 -0700 Subject: [PATCH 107/139] PERF: Extension loader probing performed in parallel --HG-- branch : 1.x --- .../Extensions/ExtensionLoaderCoordinator.cs | 24 +++++++++++++------ .../Extensions/ExtensionLoadingContext.cs | 2 +- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs index 8cc499a13..620a1c7f0 100644 --- a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs +++ b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs @@ -20,6 +20,7 @@ namespace Orchard.Environment.Extensions { private readonly IVirtualPathMonitor _virtualPathMonitor; private readonly IEnumerable _loaders; private readonly IHostEnvironment _hostEnvironment; + private readonly IParallelCacheContext _parallelCacheContext; private readonly IBuildManager _buildManager; public ExtensionLoaderCoordinator( @@ -30,6 +31,7 @@ namespace Orchard.Environment.Extensions { IVirtualPathMonitor virtualPathMonitor, IEnumerable loaders, IHostEnvironment hostEnvironment, + IParallelCacheContext parallelCacheContext, IBuildManager buildManager) { _dependenciesFolder = dependenciesFolder; @@ -39,6 +41,7 @@ namespace Orchard.Environment.Extensions { _virtualPathMonitor = virtualPathMonitor; _loaders = loaders.OrderBy(l => l.Order); _hostEnvironment = hostEnvironment; + _parallelCacheContext = parallelCacheContext; _buildManager = buildManager; T = NullLocalizer.Instance; @@ -193,13 +196,13 @@ namespace Orchard.Environment.Extensions { var previousDependencies = _dependenciesFolder.LoadDescriptors().ToList(); var virtualPathModficationDates = new Dictionary(StringComparer.OrdinalIgnoreCase); - var availableExtensionsProbes = availableExtensions.SelectMany(extension => _loaders - .Select(loader => loader.Probe(extension)) - .Where(probe => probe != null)) - .GroupBy(e => e.Descriptor.Id) - .ToDictionary(g => g.Key, g => g.AsEnumerable() - .OrderByDescending(probe => GetVirtualPathDepedenciesModificationTimeUtc(virtualPathModficationDates, probe)) - .ThenBy(probe => probe.Loader.Order), StringComparer.OrdinalIgnoreCase); + + var availableExtensionsProbes = _parallelCacheContext + .RunInParallel(availableExtensions, extension => _loaders.Select(loader => loader.Probe(extension))) + .SelectMany(entries => entries) + .Where(entry => entry != null) + .GroupBy(entry => entry.Descriptor.Id) + .ToDictionary(g => g.Key, g => SortExtensionProbeEntries(g, virtualPathModficationDates), StringComparer.OrdinalIgnoreCase); var deletedDependencies = previousDependencies .Where(e => !availableExtensions.Any(e2 => StringComparer.OrdinalIgnoreCase.Equals(e2.Id, e.Name))) @@ -237,6 +240,13 @@ namespace Orchard.Environment.Extensions { }; } + private IEnumerable SortExtensionProbeEntries(IEnumerable entries, Dictionary virtualPathModficationDates) { + return entries + .OrderByDescending(probe => GetVirtualPathDepedenciesModificationTimeUtc(virtualPathModficationDates, probe)) + .ThenBy(probe => probe.Loader.Order) + .ToList(); + } + private DateTime GetVirtualPathDepedenciesModificationTimeUtc(IDictionary virtualPathDependencies, ExtensionProbeEntry probe) { if (!probe.VirtualPathDependencies.Any()) return DateTime.MinValue; diff --git a/src/Orchard/Environment/Extensions/ExtensionLoadingContext.cs b/src/Orchard/Environment/Extensions/ExtensionLoadingContext.cs index bc5d72068..e73a5314e 100644 --- a/src/Orchard/Environment/Extensions/ExtensionLoadingContext.cs +++ b/src/Orchard/Environment/Extensions/ExtensionLoadingContext.cs @@ -50,7 +50,7 @@ namespace Orchard.Environment.Extensions { /// For every extension name, the list of loaders that can potentially load /// that extension (in order of "best-of" applicable) /// - public IDictionary> AvailableExtensionsProbes { get; set; } + public IDictionary> AvailableExtensionsProbes { get; set; } /// /// For every reference name, list of potential loaders/locations From 07dbc2b136c4ce5033be4677cd972a3ad7ba8191 Mon Sep 17 00:00:00 2001 From: Suha Can Date: Tue, 31 May 2011 18:09:45 -0700 Subject: [PATCH 108/139] #17879: Export a field duplicate settings for all field types The bug was lower in the stack, in ContentDefinitionWriter. So besides Import/Export, the Experimental module was also susceptible to the same. There may still be some loose end related to FieldSettings in general, doesn't seem to be a well explored area. --HG-- branch : 1.x --- .../MetaData/Services/ContentDefinitionWriter.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Orchard/ContentManagement/MetaData/Services/ContentDefinitionWriter.cs b/src/Orchard/ContentManagement/MetaData/Services/ContentDefinitionWriter.cs index 86800dab9..df204433c 100644 --- a/src/Orchard/ContentManagement/MetaData/Services/ContentDefinitionWriter.cs +++ b/src/Orchard/ContentManagement/MetaData/Services/ContentDefinitionWriter.cs @@ -26,7 +26,13 @@ namespace Orchard.ContentManagement.MetaData.Services { var partElement = NewElement(partDefinition.Name, partDefinition.Settings); foreach(var partField in partDefinition.Fields) { var attributeName = partField.Name + "." + partField.FieldDefinition.Name; - var partFieldElement = NewElement(attributeName, partField.Settings); + var fieldSettings = new SettingsDictionary(); + foreach (var partFieldSetting in partField.Settings.Keys) { + if (partFieldSetting.StartsWith(partField.FieldDefinition.Name)) { + fieldSettings.Add(partFieldSetting, partField.Settings[partFieldSetting]); + } + } + var partFieldElement = NewElement(attributeName, fieldSettings); partElement.Add(partFieldElement); } return partElement; From 5f04d394c49da27b6cf4d1c44ea43014be92c1c4 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Tue, 31 May 2011 18:21:41 -0700 Subject: [PATCH 109/139] Minor cleanup --HG-- branch : 1.x --- .../Caching/DefaultAsyncTokenProvider.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Orchard/Caching/DefaultAsyncTokenProvider.cs b/src/Orchard/Caching/DefaultAsyncTokenProvider.cs index 9bf8e9918..b6769ec82 100644 --- a/src/Orchard/Caching/DefaultAsyncTokenProvider.cs +++ b/src/Orchard/Caching/DefaultAsyncTokenProvider.cs @@ -33,18 +33,18 @@ namespace Orchard.Caching { public void QueueWorkItem() { // Start a work item to collect tokens in our internal array - ThreadPool.QueueUserWorkItem((state) => { - try { - _task(token => _taskTokens.Add(token)); - } - catch (Exception e) { - Logger.Error(e, "Error while monitoring extension files. Assuming extensions are not current."); - _taskException = e; - } - finally { - _isTaskFinished = true; - } - }); + ThreadPool.QueueUserWorkItem(state => { + try { + _task(token => _taskTokens.Add(token)); + } + catch (Exception e) { + Logger.Error(e, "Error while monitoring extension files. Assuming extensions are not current."); + _taskException = e; + } + finally { + _isTaskFinished = true; + } + }); } public bool IsCurrent { From 4c2785024c79d1f96c8ddc1efb0897d61d619165 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Tue, 31 May 2011 18:22:47 -0700 Subject: [PATCH 110/139] Ensure task result are returned ordered This is to avoid a potentially un-wanted side-effect of having an enumeration returned in a different order when executed in parallel. --HG-- branch : 1.x --- src/Orchard/Caching/DefaultParallelCacheContext.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Orchard/Caching/DefaultParallelCacheContext.cs b/src/Orchard/Caching/DefaultParallelCacheContext.cs index e6fad8bef..0d4678900 100644 --- a/src/Orchard/Caching/DefaultParallelCacheContext.cs +++ b/src/Orchard/Caching/DefaultParallelCacheContext.cs @@ -15,10 +15,14 @@ namespace Orchard.Caching { var tasks = source.Select(item => this.CreateContextAwareTask(() => selector(item))).ToList(); // Run tasks in parallel and combine results immediately - var result = tasks.AsParallel().Select(task => task.Execute()).ToList(); + var result = tasks + .AsParallel() // prepare for parallel execution + .AsOrdered() // preserve initial enumeration order + .Select(task => task.Execute()) // prepare tasks to run in parallel + .ToArray(); // force evaluation // Forward tokens collected by tasks to the current context - foreach(var task in tasks) { + foreach (var task in tasks) { task.Finish(); } return result; From df9abf1cc04c0946edf62cd675077ce901241dd4 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Tue, 31 May 2011 18:23:29 -0700 Subject: [PATCH 111/139] Load references of dependencies in parallel. --HG-- branch : 1.x --- .../Extensions/ExtensionLoaderCoordinator.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs index 620a1c7f0..df522c82c 100644 --- a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs +++ b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs @@ -197,22 +197,26 @@ namespace Orchard.Environment.Extensions { var virtualPathModficationDates = new Dictionary(StringComparer.OrdinalIgnoreCase); + Logger.Information("Probing extensions"); var availableExtensionsProbes = _parallelCacheContext - .RunInParallel(availableExtensions, extension => _loaders.Select(loader => loader.Probe(extension))) + .RunInParallel(availableExtensions, extension => + _loaders.Select(loader => loader.Probe(extension)).Where(entry => entry != null).ToArray()) .SelectMany(entries => entries) - .Where(entry => entry != null) .GroupBy(entry => entry.Descriptor.Id) .ToDictionary(g => g.Key, g => SortExtensionProbeEntries(g, virtualPathModficationDates), StringComparer.OrdinalIgnoreCase); + Logger.Information("Done probing extensions"); var deletedDependencies = previousDependencies .Where(e => !availableExtensions.Any(e2 => StringComparer.OrdinalIgnoreCase.Equals(e2.Id, e.Name))) .ToList(); // Collect references for all modules - var references = - availableExtensions - .SelectMany(extension => _loaders.SelectMany(loader => loader.ProbeReferences(extension))) - .ToList(); + Logger.Information("Probing extension references"); + var references = _parallelCacheContext + .RunInParallel(availableExtensions, extension => _loaders.SelectMany(loader => loader.ProbeReferences(extension)).ToList()) + .SelectMany(entries => entries) + .ToList(); + Logger.Information("Done probing extension references"); var referencesByModule = references .GroupBy(entry => entry.Descriptor.Id, StringComparer.OrdinalIgnoreCase) From 1bb16a470c95480be2ac95e4e9e7b500f355f52e Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Tue, 31 May 2011 18:24:18 -0700 Subject: [PATCH 112/139] Load features in parallel. --HG-- branch : 1.x --- src/Orchard/Environment/Extensions/ExtensionManager.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Orchard/Environment/Extensions/ExtensionManager.cs b/src/Orchard/Environment/Extensions/ExtensionManager.cs index 2c18ca734..3701c9764 100644 --- a/src/Orchard/Environment/Extensions/ExtensionManager.cs +++ b/src/Orchard/Environment/Extensions/ExtensionManager.cs @@ -46,7 +46,7 @@ namespace Orchard.Environment.Extensions { public IEnumerable AvailableExtensions() { return _cacheManager.Get("AvailableExtensions", ctx => _parallelCacheContext - .RunInParallel(_folders, folder => folder.AvailableExtensions()) + .RunInParallel(_folders, folder => folder.AvailableExtensions().ToList()) .SelectMany(descriptors => descriptors) .ToReadOnlyCollection()); } @@ -90,8 +90,9 @@ namespace Orchard.Environment.Extensions { public IEnumerable LoadFeatures(IEnumerable featureDescriptors) { Logger.Information("Loading features"); - var result = featureDescriptors - .Select(descriptor => _cacheManager.Get(descriptor.Id, ctx => LoadFeature(descriptor))) + var result = + _parallelCacheContext + .RunInParallel(featureDescriptors, descriptor => _cacheManager.Get(descriptor.Id, ctx => LoadFeature(descriptor))) .ToArray(); Logger.Information("Done loading features"); From 3ac3aead61d2efb720ef8b1f701954fb203c57e4 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Tue, 31 May 2011 19:19:07 -0700 Subject: [PATCH 113/139] Use supported VPP api to retrieve modification date --HG-- branch : 1.x --- .../FileSystems/VirtualPath/DefaultVirtualPathProvider.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs b/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs index 2e0ed315d..35ef6c2a0 100644 --- a/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs +++ b/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs @@ -116,11 +116,15 @@ namespace Orchard.FileSystems.VirtualPath { } public virtual DateTime GetFileLastWriteTimeUtc(string virtualPath) { - return File.GetLastWriteTimeUtc(MapPath(virtualPath)); + var dependency = HostingEnvironment.VirtualPathProvider.GetCacheDependency(virtualPath, new[] { virtualPath }, DateTime.UtcNow); + if (dependency == null) { + throw new Exception(string.Format("Invalid virtual path: '{0}'", virtualPath)); + } + return dependency.UtcLastModified; } public string GetFileHash(string virtualPath) { - return GetFileHash(virtualPath, new[] {virtualPath}); + return GetFileHash(virtualPath, new[] { virtualPath }); } public string GetFileHash(string virtualPath, IEnumerable dependencies) { From 4daaeb0b40ec65d9999dd2a16f8c1850fb82852f Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Tue, 31 May 2011 20:57:54 -0700 Subject: [PATCH 114/139] Removed unused dependency --HG-- branch : 1.x --- .../Environment/Extensions/ExtensionLoaderCoordinator.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs index df522c82c..41734cb5c 100644 --- a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs +++ b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs @@ -17,7 +17,6 @@ namespace Orchard.Environment.Extensions { private readonly IExtensionDependenciesManager _extensionDependenciesManager; private readonly IExtensionManager _extensionManager; private readonly IVirtualPathProvider _virtualPathProvider; - private readonly IVirtualPathMonitor _virtualPathMonitor; private readonly IEnumerable _loaders; private readonly IHostEnvironment _hostEnvironment; private readonly IParallelCacheContext _parallelCacheContext; @@ -28,7 +27,6 @@ namespace Orchard.Environment.Extensions { IExtensionDependenciesManager extensionDependenciesManager, IExtensionManager extensionManager, IVirtualPathProvider virtualPathProvider, - IVirtualPathMonitor virtualPathMonitor, IEnumerable loaders, IHostEnvironment hostEnvironment, IParallelCacheContext parallelCacheContext, @@ -38,7 +36,6 @@ namespace Orchard.Environment.Extensions { _extensionDependenciesManager = extensionDependenciesManager; _extensionManager = extensionManager; _virtualPathProvider = virtualPathProvider; - _virtualPathMonitor = virtualPathMonitor; _loaders = loaders.OrderBy(l => l.Order); _hostEnvironment = hostEnvironment; _parallelCacheContext = parallelCacheContext; From e353220ecfc205d66d48f50e9d83959dc517e878 Mon Sep 17 00:00:00 2001 From: Andre Rodrigues Date: Wed, 1 Jun 2011 01:12:01 -0700 Subject: [PATCH 115/139] Fixing broken UT. --HG-- branch : 1.x --- .../Providers/CommonPartProviderTests.cs | 62 ++++++++++++++++--- .../DefaultContentDisplay.cs | 12 +++- 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/src/Orchard.Core.Tests/Common/Providers/CommonPartProviderTests.cs b/src/Orchard.Core.Tests/Common/Providers/CommonPartProviderTests.cs index b8a19e0e9..dfc37bb07 100644 --- a/src/Orchard.Core.Tests/Common/Providers/CommonPartProviderTests.cs +++ b/src/Orchard.Core.Tests/Common/Providers/CommonPartProviderTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Web; using System.Web.Routing; using Autofac; using JetBrains.Annotations; @@ -23,13 +24,17 @@ using Orchard.Core.Scheduling.Models; using Orchard.Core.Scheduling.Services; using Orchard.DisplayManagement; using Orchard.DisplayManagement.Descriptors; +using Orchard.DisplayManagement.Descriptors.ShapeAttributeStrategy; +using Orchard.DisplayManagement.Descriptors.ShapePlacementStrategy; using Orchard.DisplayManagement.Implementation; using Orchard.Environment.Extensions; +using Orchard.Environment.Extensions.Models; +using Orchard.FileSystems.VirtualPath; using Orchard.Localization; using Orchard.Security; using Orchard.Tasks.Scheduling; +using Orchard.Tests.DisplayManagement.Descriptors; using Orchard.Tests.Modules; -using Orchard.Core.Common.ViewModels; using System.Web.Mvc; using Orchard.Tests.Stubs; using Orchard.Themes; @@ -48,20 +53,32 @@ namespace Orchard.Core.Tests.Common.Providers { builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); - builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); - builder.RegisterInstance(new Mock().Object); + builder.RegisterType().As(); builder.RegisterInstance(new Mock().Object); - builder.RegisterInstance(new Mock().Object); + + builder.RegisterInstance(new RequestContext(new StubHttpContext(), new RouteData())); builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); + DefaultShapeTableManagerTests.TestShapeProvider.FeatureShapes = new Dictionary> { + { TestFeature(), new[] { "Parts_Common_Owner_Edit" } } + }; + + builder.RegisterType().As() + .As() + .InstancePerLifetimeScope(); + + builder.RegisterInstance(new RouteCollection()); + builder.RegisterModule(new ShapeAttributeBindingModule()); + _authn = new Mock(); _authz = new Mock(); _membership = new Mock(); @@ -71,6 +88,24 @@ namespace Orchard.Core.Tests.Common.Providers { builder.RegisterInstance(_authz.Object); builder.RegisterInstance(_membership.Object); builder.RegisterInstance(_contentDefinitionManager.Object); + + var virtualPathProviderMock = new Mock(); + virtualPathProviderMock.Setup(a => a.ToAppRelative(It.IsAny())).Returns("~/yadda"); + + builder.RegisterInstance(virtualPathProviderMock.Object); + } + + static Feature TestFeature() { + return new Feature { + Descriptor = new FeatureDescriptor { + Id = "Testing", + Dependencies = Enumerable.Empty(), + Extension = new ExtensionDescriptor { + Id = "Testing", + ExtensionType = DefaultExtensionTypes.Module, + } + } + }; } protected override IEnumerable DatabaseTypes { @@ -119,9 +154,8 @@ namespace Orchard.Core.Tests.Common.Providers { var user = contentManager.New("User"); _authn.Setup(x => x.GetAuthenticatedUser()).Returns(user); - var createUtc = _clock.UtcNow; var item = contentManager.Create("test-item", VersionOptions.Draft, init => { }); - var viewModel = new OwnerEditorViewModel() { Owner = "User" }; + var viewModel = new OwnerEditorViewModel { Owner = "User" }; updateModel.Setup(x => x.TryUpdateModel(viewModel, "", null, null)).Returns(true); contentManager.UpdateEditor(item.ContentItem, updateModel.Object); } @@ -146,6 +180,16 @@ namespace Orchard.Core.Tests.Common.Providers { } } + class StubThemeService : IThemeManager { + private readonly ExtensionDescriptor _theme = new ExtensionDescriptor { + Id = "SafeMode", + Name = "SafeMode", + Location = "~/Themes", + }; + + public ExtensionDescriptor GetRequestTheme(RequestContext requestContext) { return _theme; } + } + [Test] public void PublishingShouldNotThrowExceptionIfOwnerIsNull() { var contentManager = _container.Resolve(); @@ -177,9 +221,13 @@ namespace Orchard.Core.Tests.Common.Providers { var updater = new UpdatModelStub() { Owner = "" }; + _container.Resolve().Discover = + b => b.Describe("Parts_Common_Owner_Edit").From(TestFeature()) + .Placement(ShapePlacementParsingStrategy.BuildPredicate(c => true, new KeyValuePair("Path", "~/yadda")), new PlacementInfo { Location = "Match" }); + contentManager.UpdateEditor(item.ContentItem, updater); - Assert.That(updater.ModelErrors.ContainsKey("CommonPart.Owner"), Is.True); + Assert.That(updater.ModelErrors.ContainsKey("OwnerEditor.Owner"), Is.True); } [Test] diff --git a/src/Orchard/ContentManagement/DefaultContentDisplay.cs b/src/Orchard/ContentManagement/DefaultContentDisplay.cs index ba049b3ca..dccd0c448 100644 --- a/src/Orchard/ContentManagement/DefaultContentDisplay.cs +++ b/src/Orchard/ContentManagement/DefaultContentDisplay.cs @@ -6,6 +6,7 @@ using ClaySharp.Implementation; using Orchard.ContentManagement.Handlers; using Orchard.DisplayManagement; using Orchard.DisplayManagement.Descriptors; +using Orchard.FileSystems.VirtualPath; using Orchard.Logging; using Orchard.Themes; using Orchard.UI.Zones; @@ -17,18 +18,23 @@ namespace Orchard.ContentManagement { private readonly IShapeTableManager _shapeTableManager; private readonly Lazy _themeService; private readonly RequestContext _requestContext; + private readonly IVirtualPathProvider _virtualPathProvider; public DefaultContentDisplay( Lazy> handlers, IShapeFactory shapeFactory, IShapeTableManager shapeTableManager, Lazy themeService, - RequestContext requestContext) { + RequestContext requestContext, + IVirtualPathProvider virtualPathProvider) { + _handlers = handlers; _shapeFactory = shapeFactory; _shapeTableManager = shapeTableManager; _themeService = themeService; _requestContext = requestContext; + _virtualPathProvider = virtualPathProvider; + Logger = NullLogger.Instance; } @@ -111,11 +117,11 @@ namespace Orchard.ContentManagement { Stereotype = stereotype, DisplayType = displayType, Differentiator = differentiator, - Path = VirtualPathUtility.AppendTrailingSlash(VirtualPathUtility.ToAppRelative(request.Path)) // get the current app-relative path, i.e. ~/my-blog/foo + Path = VirtualPathUtility.AppendTrailingSlash(_virtualPathProvider.ToAppRelative(request.Path)) // get the current app-relative path, i.e. ~/my-blog/foo }; var placement = descriptor.Placement(placementContext); - if(placement != null) { + if (placement != null) { placement.Source = placementContext.Source; return placement; } From a721f208029f28ce8625c6fb7abf867bcf3812cb Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 1 Jun 2011 11:59:18 -0700 Subject: [PATCH 116/139] Remove extra ";" --HG-- branch : 1.x --- .../XmlRpc/Controllers/HomeControllerTests.cs | 2 +- .../Extensions/ExtensionLoaderCoordinatorTests.cs | 14 +++++++------- .../Extensions/ExtensionManagerTests.cs | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Orchard.Tests.Modules/XmlRpc/Controllers/HomeControllerTests.cs b/src/Orchard.Tests.Modules/XmlRpc/Controllers/HomeControllerTests.cs index d9bb4b10c..3a91336d2 100644 --- a/src/Orchard.Tests.Modules/XmlRpc/Controllers/HomeControllerTests.cs +++ b/src/Orchard.Tests.Modules/XmlRpc/Controllers/HomeControllerTests.cs @@ -15,7 +15,7 @@ namespace Orchard.Tests.Modules.XmlRpc.Controllers { var thing2 = new StubHandler(); var builder = new ContainerBuilder(); - //builder.RegisterModule(new ImplicitCollectionSupportModule()); ; + //builder.RegisterModule(new ImplicitCollectionSupportModule()); builder.RegisterType(); builder.RegisterType().As>(); builder.RegisterType().As>(); diff --git a/src/Orchard.Tests/Environment/Extensions/ExtensionLoaderCoordinatorTests.cs b/src/Orchard.Tests/Environment/Extensions/ExtensionLoaderCoordinatorTests.cs index 071e8faa8..94f07fb6d 100644 --- a/src/Orchard.Tests/Environment/Extensions/ExtensionLoaderCoordinatorTests.cs +++ b/src/Orchard.Tests/Environment/Extensions/ExtensionLoaderCoordinatorTests.cs @@ -365,7 +365,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); ; + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); var testFeature = extensionManager.AvailableExtensions() .SelectMany(x => x.Features); @@ -391,7 +391,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); ; + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); var testFeature = extensionManager.AvailableExtensions() .SelectMany(x => x.Features); @@ -420,7 +420,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); ; + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); var testFeature = extensionManager.AvailableExtensions() .SelectMany(x => x.Features) .Single(x => x.Id == "TestFeature"); @@ -450,7 +450,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); ; + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); var testFeature = extensionManager.AvailableExtensions() .SelectMany(x => x.Features) .Single(x => x.Id == "TestFeature"); @@ -478,7 +478,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); ; + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); var testModule = extensionManager.AvailableExtensions() .SelectMany(x => x.Features) .Single(x => x.Id == "TestModule"); @@ -502,7 +502,7 @@ Version: 1.0.3 OrchardVersion: 1 "); - IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); ; + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); var minimalisticModule = extensionManager.AvailableExtensions().Single(x => x.Id == "Minimalistic"); Assert.That(minimalisticModule.Features.Count(), Is.EqualTo(1)); @@ -521,7 +521,7 @@ Version: 1.0.3 OrchardVersion: 1 "); - IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); ; + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); var minimalisticModule = extensionManager.AvailableExtensions().Single(x => x.Id == "Minimalistic"); Assert.That(minimalisticModule.Features.Count(), Is.EqualTo(1)); diff --git a/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs b/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs index de340b474..7a771c277 100644 --- a/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs +++ b/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs @@ -356,7 +356,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); ; + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); var testFeature = extensionManager.AvailableExtensions() .SelectMany(x => x.Features) .Single(x => x.Id == "TestFeature"); @@ -386,7 +386,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); ; + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); var testFeature = extensionManager.AvailableExtensions() .SelectMany(x => x.Features) .Single(x => x.Id == "TestFeature"); @@ -414,7 +414,7 @@ Features: Description: Contains the Phi type. "); - IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); ; + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); var testModule = extensionManager.AvailableExtensions() .SelectMany(x => x.Features) .Single(x => x.Id == "TestModule"); @@ -438,7 +438,7 @@ Version: 1.0.3 OrchardVersion: 1 "); - IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); ; + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); var minimalisticModule = extensionManager.AvailableExtensions().Single(x => x.Id == "Minimalistic"); Assert.That(minimalisticModule.Features.Count(), Is.EqualTo(1)); @@ -474,7 +474,7 @@ Features: Dependencies: Beta "); - IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); ; + IExtensionManager extensionManager = CreateExtensionManager(extensionFolder, extensionLoader); var features = extensionManager.AvailableFeatures(); Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo(" Date: Wed, 1 Jun 2011 12:50:20 -0700 Subject: [PATCH 117/139] #17826: SiteSettings lose encryption keys when edited Work Items: #17826 --HG-- branch : 1.x --- src/Orchard.Tests/DataMigration/SchemaBuilderTests.cs | 2 +- .../Orchard.MultiTenancy/Controllers/AdminController.cs | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Orchard.Tests/DataMigration/SchemaBuilderTests.cs b/src/Orchard.Tests/DataMigration/SchemaBuilderTests.cs index b727082a5..0bf6a3e89 100644 --- a/src/Orchard.Tests/DataMigration/SchemaBuilderTests.cs +++ b/src/Orchard.Tests/DataMigration/SchemaBuilderTests.cs @@ -142,7 +142,7 @@ namespace Orchard.Tests.DataMigration { _schemaBuilder .ExecuteSql("insert into TEST_User VALUES (DEFAULT, DEFAULT)"); - // ensure wehave one record woth the default value + // ensure we have one record with the default value var command = _session.Connection.CreateCommand(); command.CommandText = "SELECT count(*) FROM TEST_User WHERE Lastname = 'Doe'"; Assert.That(command.ExecuteScalar(), Is.EqualTo(1)); diff --git a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Controllers/AdminController.cs index e59f8c05c..8bd2cf769 100644 --- a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Controllers/AdminController.cs +++ b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Controllers/AdminController.cs @@ -120,15 +120,18 @@ namespace Orchard.MultiTenancy.Controllers { try { _tenantService.UpdateTenant( - new ShellSettings - { + new ShellSettings { Name = tenant.Name, RequestUrlHost = viewModel.RequestUrlHost, RequestUrlPrefix = viewModel.RequestUrlPrefix, DataProvider = viewModel.DataProvider, DataConnectionString = viewModel.DatabaseConnectionString, DataTablePrefix = viewModel.DatabaseTablePrefix, - State = tenant.State + State = tenant.State, + EncryptionAlgorithm = tenant.EncryptionAlgorithm, + EncryptionKey = tenant.EncryptionKey, + HashAlgorithm = tenant.HashAlgorithm, + HashKey = tenant.HashKey }); return RedirectToAction("Index"); From 34755f62054aa35bbb6e25041a8046d4a3e2c269 Mon Sep 17 00:00:00 2001 From: Suha Can Date: Wed, 1 Jun 2011 13:36:33 -0700 Subject: [PATCH 118/139] Adding field name and field type name information to PartFieldDefinitionBuilder to allow field settings to be used and persisted correctly. Naively written code in external modules such as DateTimeField,ImageField drivers can be updated. Also removed previous workaround. --HG-- branch : 1.x --- .../MetaData/Builders/ContentPartDefinitionBuilder.cs | 8 ++++++++ .../Builders/ContentPartFieldDefinitionBuilder.cs | 3 +++ .../MetaData/Services/ContentDefinitionWriter.cs | 8 +------- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Orchard/ContentManagement/MetaData/Builders/ContentPartDefinitionBuilder.cs b/src/Orchard/ContentManagement/MetaData/Builders/ContentPartDefinitionBuilder.cs index c4497e16b..0f874af11 100644 --- a/src/Orchard/ContentManagement/MetaData/Builders/ContentPartDefinitionBuilder.cs +++ b/src/Orchard/ContentManagement/MetaData/Builders/ContentPartDefinitionBuilder.cs @@ -86,6 +86,14 @@ namespace Orchard.ContentManagement.MetaData.Builders { return new ContentPartFieldDefinition(_fieldDefinition, _fieldName, _settings); } + public override string Name { + get { return _fieldName; } + } + + public override string FieldType { + get { return _fieldDefinition.Name; } + } + public override ContentPartFieldDefinitionBuilder OfType(ContentFieldDefinition fieldDefinition) { _fieldDefinition = fieldDefinition; return this; diff --git a/src/Orchard/ContentManagement/MetaData/Builders/ContentPartFieldDefinitionBuilder.cs b/src/Orchard/ContentManagement/MetaData/Builders/ContentPartFieldDefinitionBuilder.cs index e49d18e56..3972c97fe 100644 --- a/src/Orchard/ContentManagement/MetaData/Builders/ContentPartFieldDefinitionBuilder.cs +++ b/src/Orchard/ContentManagement/MetaData/Builders/ContentPartFieldDefinitionBuilder.cs @@ -14,6 +14,9 @@ namespace Orchard.ContentManagement.MetaData.Builders { return this; } + public abstract string Name { get; } + public abstract string FieldType { get; } + public abstract ContentPartFieldDefinitionBuilder OfType(ContentFieldDefinition fieldDefinition); public abstract ContentPartFieldDefinitionBuilder OfType(string fieldType); } diff --git a/src/Orchard/ContentManagement/MetaData/Services/ContentDefinitionWriter.cs b/src/Orchard/ContentManagement/MetaData/Services/ContentDefinitionWriter.cs index df204433c..86800dab9 100644 --- a/src/Orchard/ContentManagement/MetaData/Services/ContentDefinitionWriter.cs +++ b/src/Orchard/ContentManagement/MetaData/Services/ContentDefinitionWriter.cs @@ -26,13 +26,7 @@ namespace Orchard.ContentManagement.MetaData.Services { var partElement = NewElement(partDefinition.Name, partDefinition.Settings); foreach(var partField in partDefinition.Fields) { var attributeName = partField.Name + "." + partField.FieldDefinition.Name; - var fieldSettings = new SettingsDictionary(); - foreach (var partFieldSetting in partField.Settings.Keys) { - if (partFieldSetting.StartsWith(partField.FieldDefinition.Name)) { - fieldSettings.Add(partFieldSetting, partField.Settings[partFieldSetting]); - } - } - var partFieldElement = NewElement(attributeName, fieldSettings); + var partFieldElement = NewElement(attributeName, partField.Settings); partElement.Add(partFieldElement); } return partElement; From 172e53dbdeacd22115d8646fac79883069e5b92e Mon Sep 17 00:00:00 2001 From: Nathan Heskew Date: Wed, 1 Jun 2011 14:07:59 -0700 Subject: [PATCH 119/139] #17897: Removing a (featureless) ResourceManifest registration from setup Also tidying up some of the related resource usage. work item: 17897 --HG-- branch : 1.x --- src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs | 2 -- .../Modules/Orchard.Setup/Views/Setup/Index.cshtml | 4 ++-- src/Orchard.Web/Modules/Orchard.jQuery/ResourceManifest.cs | 4 +++- src/Orchard.Web/Themes/SafeMode/Views/Layout.cshtml | 1 - 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs b/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs index 8e3bf32d0..596fb636b 100644 --- a/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs +++ b/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs @@ -36,7 +36,6 @@ using Orchard.UI.PageTitle; using Orchard.UI.Resources; using Orchard.UI.Zones; using IFilterProvider = Orchard.Mvc.Filters.IFilterProvider; -using ResourceManifest = Orchard.Core.Shapes.ResourceManifest; namespace Orchard.Setup { public class SetupMode : Module { @@ -64,7 +63,6 @@ namespace Orchard.Setup { builder.RegisterType().As().InstancePerLifetimeScope(); builder.RegisterType().As().InstancePerLifetimeScope(); builder.RegisterType().As().InstancePerMatchingLifetimeScope("shell"); - builder.RegisterType().As().InstancePerLifetimeScope(); builder.RegisterType().As().InstancePerLifetimeScope(); builder.RegisterType().As().InstancePerLifetimeScope(); diff --git a/src/Orchard.Web/Modules/Orchard.Setup/Views/Setup/Index.cshtml b/src/Orchard.Web/Modules/Orchard.Setup/Views/Setup/Index.cshtml index 5a45f7208..dc02d74b1 100644 --- a/src/Orchard.Web/Modules/Orchard.Setup/Views/Setup/Index.cshtml +++ b/src/Orchard.Web/Modules/Orchard.Setup/Views/Setup/Index.cshtml @@ -1,7 +1,7 @@ @model Orchard.Setup.ViewModels.SetupViewModel @{ - Script.Require("jquery").AtFoot(); - Script.Include("setup.js").AtFoot(); + Script.Require("jQuery"); + Script.Include("setup.js"); }

    @Html.TitleForPage(T("Get Started").ToString())

    diff --git a/src/Orchard.Web/Modules/Orchard.jQuery/ResourceManifest.cs b/src/Orchard.Web/Modules/Orchard.jQuery/ResourceManifest.cs index 9de723f57..5cdf1a755 100644 --- a/src/Orchard.Web/Modules/Orchard.jQuery/ResourceManifest.cs +++ b/src/Orchard.Web/Modules/Orchard.jQuery/ResourceManifest.cs @@ -1,4 +1,6 @@ -namespace Orchard.UI.Resources { +using Orchard.UI.Resources; + +namespace Orchard.jQuery { public class ResourceManifest : IResourceManifestProvider { public void BuildManifests(ResourceManifestBuilder builder) { var manifest = builder.Add(); diff --git a/src/Orchard.Web/Themes/SafeMode/Views/Layout.cshtml b/src/Orchard.Web/Themes/SafeMode/Views/Layout.cshtml index 8da098958..5d37c838c 100644 --- a/src/Orchard.Web/Themes/SafeMode/Views/Layout.cshtml +++ b/src/Orchard.Web/Themes/SafeMode/Views/Layout.cshtml @@ -1,6 +1,5 @@ @using Orchard.UI.Resources; @{ - Script.Require("jQuery"); Script.Require("ShapesBase"); Style.Include("site.css"); RegisterLink(new LinkEntry { Condition = "lte IE 6", Rel = "stylesheet", Type="text/css", Href = Href("../Styles/ie6.css")}.AddAttribute("media", "screen, projection")); From dda45b654e8c747a41a3bde47dd59081055e2e50 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 1 Jun 2011 14:38:55 -0700 Subject: [PATCH 120/139] PERF: Revert previous change. Using file system directly is 4x times faster. --HG-- branch : 1.x --- .../FileSystems/VirtualPath/DefaultVirtualPathProvider.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs b/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs index 35ef6c2a0..844dde656 100644 --- a/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs +++ b/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs @@ -116,11 +116,17 @@ namespace Orchard.FileSystems.VirtualPath { } public virtual DateTime GetFileLastWriteTimeUtc(string virtualPath) { +#if true + // This code is less "pure" than the code below, but performs fewer file I/O, and it + // has been measured to make a significant difference (4x) on slow file systems. + return File.GetLastWriteTimeUtc(MapPath(virtualPath)); +#else var dependency = HostingEnvironment.VirtualPathProvider.GetCacheDependency(virtualPath, new[] { virtualPath }, DateTime.UtcNow); if (dependency == null) { throw new Exception(string.Format("Invalid virtual path: '{0}'", virtualPath)); } return dependency.UtcLastModified; +#endif } public string GetFileHash(string virtualPath) { From 86c2f66488297ac8d98bfa28956db867cda2ba3b Mon Sep 17 00:00:00 2001 From: Nathan Heskew Date: Wed, 1 Jun 2011 15:28:15 -0700 Subject: [PATCH 121/139] #17894 Making sure the AntiForgeryAuthorizationFilter makes a good attempt at getting the name ("area" value) of the currently executing module work item: 17894 --HG-- branch : 1.x --- .../Mvc/AntiForgery/AntiForgeryAuthorizationFilter.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Orchard/Mvc/AntiForgery/AntiForgeryAuthorizationFilter.cs b/src/Orchard/Mvc/AntiForgery/AntiForgeryAuthorizationFilter.cs index 26c16f897..b99bcb8ea 100644 --- a/src/Orchard/Mvc/AntiForgery/AntiForgeryAuthorizationFilter.cs +++ b/src/Orchard/Mvc/AntiForgery/AntiForgeryAuthorizationFilter.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Specialized; using System.Web; using System.Web.Mvc; +using System.Web.Routing; using JetBrains.Annotations; using Orchard.Environment.Extensions; using Orchard.Mvc.Filters; @@ -40,7 +41,7 @@ namespace Orchard.Mvc.AntiForgery { } private bool IsAntiForgeryProtectionEnabled(ControllerContext context) { - string currentModule = context.RouteData.Values["area"].ToString(); + string currentModule = GetArea(context.RouteData); if (!String.IsNullOrEmpty(currentModule)) { foreach (var descriptor in _extensionManager.AvailableExtensions()) { if (String.Equals(descriptor.Id, currentModule, StringComparison.OrdinalIgnoreCase)) { @@ -55,6 +56,13 @@ namespace Orchard.Mvc.AntiForgery { return false; } + private static string GetArea(RouteData routeData) { + if (routeData.Values.ContainsKey("area")) + return routeData.Values["area"] as string; + + return routeData.DataTokens["area"] as string ?? ""; + } + private static bool ShouldValidateGet(AuthorizationContext context) { const string tokenFieldName = "__RequestVerificationToken"; From 2a43983151bff679b5f9a370a16a0e91637cb8ef Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Thu, 2 Jun 2011 10:31:46 -0700 Subject: [PATCH 122/139] PERF: Build shape table in parallel We have lots of file I/O to scan directories for templates, so it was a good candidate to run in parallel. --HG-- branch : 1.x --- .../ShapeAttributeBindingStrategyTests.cs | 5 +-- .../ShapeTemplateBindingStrategyTests.cs | 6 +-- .../StylesheetBindingStrategyTests.cs | 5 +-- .../Descriptors/DefaultShapeTableManager.cs | 42 +++++++------------ .../Descriptors/ShapeTableBuilder.cs | 11 +++-- .../ShapeTemplateBindingStrategy.cs | 12 ++++-- 6 files changed, 36 insertions(+), 45 deletions(-) diff --git a/src/Orchard.Tests/DisplayManagement/Descriptors/ShapeAttributeBindingStrategyTests.cs b/src/Orchard.Tests/DisplayManagement/Descriptors/ShapeAttributeBindingStrategyTests.cs index 29039308d..250920075 100644 --- a/src/Orchard.Tests/DisplayManagement/Descriptors/ShapeAttributeBindingStrategyTests.cs +++ b/src/Orchard.Tests/DisplayManagement/Descriptors/ShapeAttributeBindingStrategyTests.cs @@ -59,10 +59,9 @@ namespace Orchard.Tests.DisplayManagement.Descriptors { } static IEnumerable GetAlterationBuilders(IShapeTableProvider strategy) { - IList alterationBuilders = new List(); - var builder = new ShapeTableBuilder(alterationBuilders, null); + var builder = new ShapeTableBuilder(null); strategy.Discover(builder); - return alterationBuilders.Select(alterationBuilder => alterationBuilder.Build()); + return builder.BuildAlterations(); } [Test] diff --git a/src/Orchard.Tests/DisplayManagement/Descriptors/ShapeTemplateBindingStrategyTests.cs b/src/Orchard.Tests/DisplayManagement/Descriptors/ShapeTemplateBindingStrategyTests.cs index 7dca10877..5b9f11252 100644 --- a/src/Orchard.Tests/DisplayManagement/Descriptors/ShapeTemplateBindingStrategyTests.cs +++ b/src/Orchard.Tests/DisplayManagement/Descriptors/ShapeTemplateBindingStrategyTests.cs @@ -147,13 +147,11 @@ namespace Orchard.Tests.DisplayManagement.Descriptors { _testViewEngine.Add("~/Modules/Alpha/Views/AlphaShape.blah", null); var strategy = _container.Resolve(); - IList alterationBuilders = new List(); - var builder = new ShapeTableBuilder(alterationBuilders,null); + var builder = new ShapeTableBuilder(null); strategy.Discover(builder); - var alterations = alterationBuilders.Select(alterationBuilder=>alterationBuilder.Build()); + var alterations = builder.BuildAlterations(); Assert.That(alterations.Any(alteration => alteration.ShapeType.Equals("AlphaShape", StringComparison.OrdinalIgnoreCase))); } - } } diff --git a/src/Orchard.Tests/DisplayManagement/Descriptors/StylesheetBindingStrategyTests.cs b/src/Orchard.Tests/DisplayManagement/Descriptors/StylesheetBindingStrategyTests.cs index daddb42d0..1a4a982b1 100644 --- a/src/Orchard.Tests/DisplayManagement/Descriptors/StylesheetBindingStrategyTests.cs +++ b/src/Orchard.Tests/DisplayManagement/Descriptors/StylesheetBindingStrategyTests.cs @@ -146,10 +146,9 @@ namespace Orchard.Tests.DisplayManagement.Descriptors { _testViewEngine.Add("~/Modules/Alpha/Styles/alpha-shape.css", null); var strategy = _container.Resolve(); - IList alterationBuilders = new List(); - var builder = new ShapeTableBuilder(alterationBuilders,null); + var builder = new ShapeTableBuilder(null); strategy.Discover(builder); - var alterations = alterationBuilders.Select(alterationBuilder=>alterationBuilder.Build()); + var alterations = builder.BuildAlterations(); Assert.That(alterations.Any(alteration => alteration.ShapeType == "Style")); diff --git a/src/Orchard/DisplayManagement/Descriptors/DefaultShapeTableManager.cs b/src/Orchard/DisplayManagement/Descriptors/DefaultShapeTableManager.cs index 1fafaf52d..4d2b9b48c 100644 --- a/src/Orchard/DisplayManagement/Descriptors/DefaultShapeTableManager.cs +++ b/src/Orchard/DisplayManagement/Descriptors/DefaultShapeTableManager.cs @@ -14,13 +14,16 @@ namespace Orchard.DisplayManagement.Descriptors { private readonly IEnumerable> _bindingStrategies; private readonly IExtensionManager _extensionManager; private readonly ICacheManager _cacheManager; + private readonly IParallelCacheContext _parallelCacheContext; public DefaultShapeTableManager( IEnumerable> bindingStrategies, IExtensionManager extensionManager, - ICacheManager cacheManager) { + ICacheManager cacheManager, + IParallelCacheContext parallelCacheContext) { _extensionManager = extensionManager; _cacheManager = cacheManager; + _parallelCacheContext = parallelCacheContext; _bindingStrategies = bindingStrategies; Logger = NullLogger.Instance; } @@ -31,19 +34,20 @@ namespace Orchard.DisplayManagement.Descriptors { return _cacheManager.Get(themeName ?? "", x => { Logger.Information("Start building shape table"); - var builderFactory = new ShapeTableBuilderFactory(); - foreach (var bindingStrategy in _bindingStrategies) { + var alterationSets = _parallelCacheContext.RunInParallel(_bindingStrategies, bindingStrategy => { Feature strategyDefaultFeature = bindingStrategy.Metadata.ContainsKey("Feature") ? - (Feature) bindingStrategy.Metadata["Feature"] : - null; + (Feature)bindingStrategy.Metadata["Feature"] : + null; - var builder = builderFactory.CreateTableBuilder(strategyDefaultFeature); + var builder = new ShapeTableBuilder(strategyDefaultFeature); bindingStrategy.Value.Discover(builder); - } + return builder.BuildAlterations().ToList(); + }); - var alterations = builderFactory.BuildAlterations() - .Where(alteration => IsModuleOrRequestedTheme(alteration, themeName)) - .OrderByDependenciesAndPriorities(AlterationHasDependency, GetPriority); + var alterations = alterationSets + .SelectMany(shapeAlterations => shapeAlterations) + .Where(alteration => IsModuleOrRequestedTheme(alteration, themeName)) + .OrderByDependenciesAndPriorities(AlterationHasDependency, GetPriority); var descriptors = alterations.GroupBy(alteration => alteration.ShapeType, StringComparer.OrdinalIgnoreCase) .Select(group => group.Aggregate( @@ -98,7 +102,7 @@ namespace Orchard.DisplayManagement.Descriptors { var availableFeatures = _extensionManager.AvailableFeatures(); var themeFeature = availableFeatures.SingleOrDefault(fd => fd.Id == themeName); - while(themeFeature != null) { + while (themeFeature != null) { var baseTheme = themeFeature.Extension.BaseTheme; if (String.IsNullOrEmpty(baseTheme)) { return false; @@ -110,21 +114,5 @@ namespace Orchard.DisplayManagement.Descriptors { } return false; } - - class ShapeTableBuilderFactory { - readonly IList _alterationBuilders = new List(); - - public ShapeTableBuilder CreateTableBuilder(Feature feature) { - return new ShapeTableBuilder(_alterationBuilders, feature); - } - - public IEnumerable BuildAlterations() { - return _alterationBuilders.Select(b => b.Build()); - } - - } - - - } } diff --git a/src/Orchard/DisplayManagement/Descriptors/ShapeTableBuilder.cs b/src/Orchard/DisplayManagement/Descriptors/ShapeTableBuilder.cs index 53175e69a..b4645a912 100644 --- a/src/Orchard/DisplayManagement/Descriptors/ShapeTableBuilder.cs +++ b/src/Orchard/DisplayManagement/Descriptors/ShapeTableBuilder.cs @@ -1,13 +1,13 @@ using System.Collections.Generic; +using System.Linq; using Orchard.Environment.Extensions.Models; namespace Orchard.DisplayManagement.Descriptors { public class ShapeTableBuilder { - readonly IList _alterationBuilders; - readonly Feature _feature; + private readonly IList _alterationBuilders = new List(); + private readonly Feature _feature; - public ShapeTableBuilder(IList alterationBuilders, Feature feature) { - _alterationBuilders = alterationBuilders; + public ShapeTableBuilder(Feature feature) { _feature = feature; } @@ -17,5 +17,8 @@ namespace Orchard.DisplayManagement.Descriptors { return alterationBuilder; } + public IEnumerable BuildAlterations() { + return _alterationBuilders.Select(b => b.Build()); + } } } \ No newline at end of file diff --git a/src/Orchard/DisplayManagement/Descriptors/ShapeTemplateStrategy/ShapeTemplateBindingStrategy.cs b/src/Orchard/DisplayManagement/Descriptors/ShapeTemplateStrategy/ShapeTemplateBindingStrategy.cs index 2f40a39d1..d73824f7f 100644 --- a/src/Orchard/DisplayManagement/Descriptors/ShapeTemplateStrategy/ShapeTemplateBindingStrategy.cs +++ b/src/Orchard/DisplayManagement/Descriptors/ShapeTemplateStrategy/ShapeTemplateBindingStrategy.cs @@ -23,6 +23,7 @@ namespace Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy { private readonly IVirtualPathProvider _virtualPathProvider; private readonly IEnumerable _harvesters; private readonly IEnumerable _shapeTemplateViewEngines; + private readonly IParallelCacheContext _parallelCacheContext; public ShapeTemplateBindingStrategy( @@ -32,7 +33,9 @@ namespace Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy { ICacheManager cacheManager, IVirtualPathMonitor virtualPathMonitor, IVirtualPathProvider virtualPathProvider, - IEnumerable shapeTemplateViewEngines) { + IEnumerable shapeTemplateViewEngines, + IParallelCacheContext parallelCacheContext) { + _harvesters = harvesters; _shellDescriptor = shellDescriptor; _extensionManager = extensionManager; @@ -40,6 +43,7 @@ namespace Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy { _virtualPathMonitor = virtualPathMonitor; _virtualPathProvider = virtualPathProvider; _shapeTemplateViewEngines = shapeTemplateViewEngines; + _parallelCacheContext = parallelCacheContext; Logger = NullLogger.Instance; } @@ -59,7 +63,7 @@ namespace Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy { var activeFeatures = availableFeatures.Where(FeatureIsEnabled); var activeExtensions = Once(activeFeatures); - var hits = activeExtensions.SelectMany(extensionDescriptor => { + var hits = _parallelCacheContext.RunInParallel(activeExtensions, extensionDescriptor => { Logger.Information("Start discovering candidate views filenames"); var pathContexts = harvesterInfos.SelectMany(harvesterInfo => harvesterInfo.subPaths.Select(subPath => { var basePath = Path.Combine(extensionDescriptor.Location, extensionDescriptor.Id).Replace(Path.DirectorySeparatorChar, '/'); @@ -92,8 +96,8 @@ namespace Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy { return harvestShapeHits.Select(harvestShapeHit => new { harvestShapeInfo, harvestShapeHit, fileContext }); }); - return shapeContexts.Select(shapeContext => new { extensionDescriptor, shapeContext }); - }); + return shapeContexts.Select(shapeContext => new { extensionDescriptor, shapeContext }).ToList(); + }).SelectMany(hits2 => hits2); foreach (var iter in hits) { From 53f84b3cbc508dcb474ccb25e1e1699dbd8149fd Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Thu, 2 Jun 2011 10:34:01 -0700 Subject: [PATCH 123/139] PERF: Compact list of tokens for cache entries Due to the way we nest cache context implicitly, there were cases where we had 1,500+ tokens with only 200+ unique ones. We now call "distinct" on the tokens when after an entry is created. This decreases memory usage and also improve performance on cache hits (fewer tokens to go though each time). --HG-- branch : 1.x --- src/Orchard/Caching/Cache.cs | 54 +++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/src/Orchard/Caching/Cache.cs b/src/Orchard/Caching/Cache.cs index 9f9dfe711..5836377db 100644 --- a/src/Orchard/Caching/Cache.cs +++ b/src/Orchard/Caching/Cache.cs @@ -16,19 +16,33 @@ namespace Orchard.Caching { public TResult Get(TKey key, Func, TResult> acquire) { var entry = _entries.AddOrUpdate(key, // "Add" lambda - k => CreateEntry(k, acquire), + k => AddEntry(k, acquire), // "Update" lamdba - (k, currentEntry) => (currentEntry.GetTokens() != null && currentEntry.GetTokens().Any(t => !t.IsCurrent) ? CreateEntry(k, acquire) : currentEntry)); - - // Bubble up volatile tokens to parent context - if (_cacheContextAccessor.Current != null && entry.GetTokens() != null) { - foreach (var token in entry.GetTokens()) - _cacheContextAccessor.Current.Monitor(token); - } + (k, currentEntry) => UpdateEntry(currentEntry, k, acquire)); return entry.Result; } + private CacheEntry AddEntry(TKey k, Func, TResult> acquire) { + var entry = CreateEntry(k, acquire); + PropagateTokens(entry); + return entry; + } + + private CacheEntry UpdateEntry(CacheEntry currentEntry, TKey k, Func, TResult> acquire) { + var entry = (currentEntry.Tokens.Any(t => !t.IsCurrent)) ? CreateEntry(k, acquire) : currentEntry; + PropagateTokens(entry); + return entry; + } + + private void PropagateTokens(CacheEntry entry) { + // Bubble up volatile tokens to parent context + if (_cacheContextAccessor.Current != null) { + foreach (var token in entry.Tokens) + _cacheContextAccessor.Current.Monitor(token); + } + } + private CacheEntry CreateEntry(TKey k, Func, TResult> acquire) { var entry = new CacheEntry(); @@ -46,23 +60,31 @@ namespace Orchard.Caching { // Pop context _cacheContextAccessor.Current = parentContext; } + entry.CompactTokens(); return entry; } private class CacheEntry { + private IList _tokens; public TResult Result { get; set; } - private IList Tokens { get; set; } - public void AddToken(IVolatileToken volatileToken) { - if (Tokens == null) { - Tokens = new List(); + public IEnumerable Tokens { + get { + return _tokens ?? Enumerable.Empty(); } - - Tokens.Add(volatileToken); } - public IEnumerable GetTokens() { - return Tokens; + public void AddToken(IVolatileToken volatileToken) { + if (_tokens == null) { + _tokens = new List(); + } + + _tokens.Add(volatileToken); + } + + public void CompactTokens() { + if (_tokens != null) + _tokens = _tokens.Distinct().ToArray(); } } } From ea11dbf606d59786641a38af804fbc875e1b6cc8 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Thu, 2 Jun 2011 10:35:05 -0700 Subject: [PATCH 124/139] PERF: Collect extension modification dates in parallel Lots of file I/O, runs faster in parallel. --HG-- branch : 1.x --- .../Extensions/ExtensionLoaderCoordinator.cs | 26 +++++++++---------- .../Extensions/ExtensionLoadingContext.cs | 3 ++- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs index 41734cb5c..ad1c42f8b 100644 --- a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs +++ b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; @@ -192,15 +193,19 @@ namespace Orchard.Environment.Extensions { var previousDependencies = _dependenciesFolder.LoadDescriptors().ToList(); - var virtualPathModficationDates = new Dictionary(StringComparer.OrdinalIgnoreCase); + var virtualPathModficationDates = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); Logger.Information("Probing extensions"); - var availableExtensionsProbes = _parallelCacheContext + var availableExtensionsProbes1 = _parallelCacheContext .RunInParallel(availableExtensions, extension => _loaders.Select(loader => loader.Probe(extension)).Where(entry => entry != null).ToArray()) .SelectMany(entries => entries) - .GroupBy(entry => entry.Descriptor.Id) - .ToDictionary(g => g.Key, g => SortExtensionProbeEntries(g, virtualPathModficationDates), StringComparer.OrdinalIgnoreCase); + .GroupBy(entry => entry.Descriptor.Id); + + var availableExtensionsProbes = _parallelCacheContext + .RunInParallel(availableExtensionsProbes1, g => + new { Id = g.Key, Entries = SortExtensionProbeEntries(g, virtualPathModficationDates)}) + .ToDictionary(g => g.Id, g => g.Entries, StringComparer.OrdinalIgnoreCase); Logger.Information("Done probing extensions"); var deletedDependencies = previousDependencies @@ -241,14 +246,14 @@ namespace Orchard.Environment.Extensions { }; } - private IEnumerable SortExtensionProbeEntries(IEnumerable entries, Dictionary virtualPathModficationDates) { + private IEnumerable SortExtensionProbeEntries(IEnumerable entries, ConcurrentDictionary virtualPathModficationDates) { return entries .OrderByDescending(probe => GetVirtualPathDepedenciesModificationTimeUtc(virtualPathModficationDates, probe)) .ThenBy(probe => probe.Loader.Order) .ToList(); } - private DateTime GetVirtualPathDepedenciesModificationTimeUtc(IDictionary virtualPathDependencies, ExtensionProbeEntry probe) { + private DateTime GetVirtualPathDepedenciesModificationTimeUtc(ConcurrentDictionary virtualPathDependencies, ExtensionProbeEntry probe) { if (!probe.VirtualPathDependencies.Any()) return DateTime.MinValue; @@ -260,13 +265,8 @@ namespace Orchard.Environment.Extensions { return result; } - private DateTime GetVirtualPathModificationTimeUtc(IDictionary virtualPathDependencies, string path) { - DateTime dateTime; - if (!virtualPathDependencies.TryGetValue(path, out dateTime)) { - dateTime = _virtualPathProvider.GetFileLastWriteTimeUtc(path); - virtualPathDependencies.Add(path, dateTime); - } - return dateTime; + private DateTime GetVirtualPathModificationTimeUtc(ConcurrentDictionary virtualPathDependencies, string path) { + return virtualPathDependencies.GetOrAdd(path, p => _virtualPathProvider.GetFileLastWriteTimeUtc(p)); } IEnumerable ProcessExtensionReferences(ExtensionLoadingContext context, ExtensionProbeEntry activatedExtension) { diff --git a/src/Orchard/Environment/Extensions/ExtensionLoadingContext.cs b/src/Orchard/Environment/Extensions/ExtensionLoadingContext.cs index e73a5314e..0bce25cd7 100644 --- a/src/Orchard/Environment/Extensions/ExtensionLoadingContext.cs +++ b/src/Orchard/Environment/Extensions/ExtensionLoadingContext.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using Orchard.Environment.Extensions.Loaders; @@ -28,7 +29,7 @@ namespace Orchard.Environment.Extensions { /// /// Keep track of modification date of files (VirtualPath => DateTime) /// - public IDictionary VirtualPathModficationDates { get; set; } + public ConcurrentDictionary VirtualPathModficationDates { get; set; } /// /// List of extensions (modules) present in the system From c435aea7756198367725f2ac1e17390e685b9b0b Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Thu, 2 Jun 2011 12:11:29 -0700 Subject: [PATCH 125/139] #17757: Fixing multi-tenancy when more than one tenant Work Items: 17757 --HG-- branch : 1.x --- src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs b/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs index 596fb636b..50ddb32f6 100644 --- a/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs +++ b/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs @@ -77,6 +77,8 @@ namespace Orchard.Setup { builder.RegisterType().As().InstancePerLifetimeScope(); builder.RegisterType().As().InstancePerLifetimeScope(); + builder.RegisterType().As().InstancePerLifetimeScope(); + // in progress - adding services for display/shape support in setup builder.RegisterType().As(); builder.RegisterType().As(); From 94d54408f9796cf7449a68825cce4640252b06ac Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Thu, 2 Jun 2011 12:15:03 -0700 Subject: [PATCH 126/139] Fixing css discrepencies in admin for text boxes --HG-- branch : 1.x --- src/Orchard.Web/Themes/TheAdmin/Styles/site.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Orchard.Web/Themes/TheAdmin/Styles/site.css b/src/Orchard.Web/Themes/TheAdmin/Styles/site.css index 97334a548..3fda91fa6 100644 --- a/src/Orchard.Web/Themes/TheAdmin/Styles/site.css +++ b/src/Orchard.Web/Themes/TheAdmin/Styles/site.css @@ -588,7 +588,7 @@ label input { color:#7c7c7c; } /* todo: (heskew) try to get .text on stuff like .text-box */ -select, textarea, input.text, input.textMedium, input.text-box { +select, textarea, input.text, input.textMedium, input.text-small, input.text-box { font-family:inherit; padding:3px; border:1px solid #bdbcbc; @@ -608,7 +608,7 @@ input.textMedium { select { padding:1px; } -select:focus, textarea:focus, input.text:focus, input.text-box:focus { +select:focus, textarea:focus, input.text:focus, input.text-box:focus, input.text-small:focus, input.textMedium:focus { border-color:#666d51; } input.check-box { From 0bb96b5633a334d0e90df491f279c43efc9b78c8 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Thu, 2 Jun 2011 14:13:52 -0700 Subject: [PATCH 127/139] Fixing specflow tests --HG-- branch : 1.x --- src/Orchard.Specs/Bindings/OrchardSiteFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Orchard.Specs/Bindings/OrchardSiteFactory.cs b/src/Orchard.Specs/Bindings/OrchardSiteFactory.cs index 015d489f4..e1b2048e9 100644 --- a/src/Orchard.Specs/Bindings/OrchardSiteFactory.cs +++ b/src/Orchard.Specs/Bindings/OrchardSiteFactory.cs @@ -23,7 +23,7 @@ namespace Orchard.Specs.Bindings { webApp.GivenIHaveACleanSiteWith( virtualDirectory, TableData( - new { extension = "Module", names = "Orchard.Setup, Orchard.Pages, Orchard.Blogs, Orchard.Messaging, Orchard.Media, Orchard.MediaPicker, Orchard.Modules, Orchard.Packaging, Orchard.PublishLater, Orchard.Themes, Orchard.Scripting, Orchard.Widgets, Orchard.Users, Orchard.Lists, Orchard.ContentTypes, Orchard.Roles, Orchard.Comments, Orchard.jQuery, Orchard.Tags, TinyMce, Orchard.Packaging, Orchard.Recipes" }, + new { extension = "Module", names = "Orchard.Setup, Orchard.Pages, Orchard.Blogs, Orchard.Messaging, Orchard.Media, Orchard.MediaPicker, Orchard.Modules, Orchard.Packaging, Orchard.PublishLater, Orchard.Themes, Orchard.Scripting, Orchard.Widgets, Orchard.Users, Orchard.Lists, Orchard.ContentTypes, Orchard.Roles, Orchard.Comments, Orchard.jQuery, Orchard.Tags, TinyMce, Orchard.Packaging, Orchard.Recipes, Orchard.Warmup" }, new { extension = "Core", names = "Common, Containers, Dashboard, Feeds, HomePage, Navigation, Contents, Routable, Scheduling, Settings, Shapes, XmlRpc" }, new { extension = "Theme", names = "SafeMode, TheAdmin, TheThemeMachine" })); From e1b9eb60bd8a9e472db16fb459690431eaf9e282 Mon Sep 17 00:00:00 2001 From: Nathan Heskew Date: Thu, 2 Jun 2011 15:43:53 -0700 Subject: [PATCH 128/139] Updating the Common feature's XmlRpcHandler to set CreatedUtc to the posted date (when/if it's given) --HG-- branch : 1.x --- .../Core/Common/Services/XmlRpcHandler.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Orchard.Web/Core/Common/Services/XmlRpcHandler.cs b/src/Orchard.Web/Core/Common/Services/XmlRpcHandler.cs index b4a9ff5b9..936ae0876 100644 --- a/src/Orchard.Web/Core/Common/Services/XmlRpcHandler.cs +++ b/src/Orchard.Web/Core/Common/Services/XmlRpcHandler.cs @@ -22,7 +22,7 @@ namespace Orchard.Core.Common.Services { public void Process(XmlRpcContext context) { switch (context.Request.MethodName) { case "metaWeblog.newPost": - MetaWeblogSetCustomPublishedDate( + MetaWeblogSetCustomCreatedDate( GetId(context.Response), Convert.ToString(context.Request.Params[0].Value), Convert.ToString(context.Request.Params[1].Value), @@ -32,7 +32,7 @@ namespace Orchard.Core.Common.Services { context._drivers); break; case "metaWeblog.editPost": - MetaWeblogSetCustomPublishedDate( + MetaWeblogSetCustomCreatedDate( GetId(context.Response), Convert.ToString(context.Request.Params[0].Value), Convert.ToString(context.Request.Params[1].Value), @@ -44,12 +44,12 @@ namespace Orchard.Core.Common.Services { } } - private void MetaWeblogSetCustomPublishedDate(int contentItemId, string appKey, string userName, string password, XRpcStruct content, bool publish, ICollection drivers) { + private void MetaWeblogSetCustomCreatedDate(int contentItemId, string appKey, string userName, string password, XRpcStruct content, bool publish, ICollection drivers) { if (!publish) return; - var publishedUtc = content.Optional("dateCreated"); - if (publishedUtc == null || publishedUtc > DateTime.UtcNow) // only pre-dating of content with the CommonPart + var createdUtc = content.Optional("dateCreated"); + if (createdUtc == null || createdUtc > DateTime.UtcNow) return; var driver = new XmlRpcDriver(item => { @@ -58,10 +58,10 @@ namespace Orchard.Core.Common.Services { var id = (int)item; var contentItem = _contentManager.Get(id, VersionOptions.Latest); - if (contentItem == null || !contentItem.Is()) + if (contentItem == null || !contentItem.Is()) // only pre-dating of content with the CommonPart (obviously) return; - contentItem.As().PublishedUtc = publishedUtc; + contentItem.As().CreatedUtc = createdUtc; }); if (contentItemId > 0) From 3006f0ef4554d6c35335bda26de538d7803d08ef Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Thu, 2 Jun 2011 15:53:51 -0700 Subject: [PATCH 129/139] Adding unit test for multi tenancy --HG-- branch : 1.x --- src/Orchard.Specs/MultiTenancy.feature | 31 +- src/Orchard.Specs/MultiTenancy.feature.cs | 355 +++++++++++++--------- 2 files changed, 246 insertions(+), 140 deletions(-) diff --git a/src/Orchard.Specs/MultiTenancy.feature b/src/Orchard.Specs/MultiTenancy.feature index 9df15228f..ae353c32e 100644 --- a/src/Orchard.Specs/MultiTenancy.feature +++ b/src/Orchard.Specs/MultiTenancy.feature @@ -55,7 +55,36 @@ Scenario: A new tenant goes to the setup screen Then I should see "Welcome to Orchard" And I should see "Finish Setup" And the status should be 200 "OK" - + +Scenario: Several tenants are configured and go to setup screen + Given I have installed Orchard + And I have installed "Orchard.MultiTenancy" + When I go to "Admin/MultiTenancy/Add" + And I fill in + | name | value | + | Name | Scott1 | + | RequestUrlHost | scott1.example.org | + And I hit "Save" + And I am redirected + And I go to "Admin/MultiTenancy/Add" + And I fill in + | name | value | + | Name | Scott2 | + | RequestUrlHost | scott2.example.org | + And I hit "Save" + And I am redirected + And I go to "Admin/MultiTenancy/Add" + And I fill in + | name | value | + | Name | Scott3 | + | RequestUrlHost | scott3.example.org | + And I hit "Save" + And I am redirected + And I go to "/Setup" on host scott1.example.org + And I go to "/Setup" on host scott2.example.org + And I go to "/Setup" on host scott3.example.org + Then I should see "Welcome to Orchard" + Scenario: A new tenant with preconfigured database goes to the setup screen Given I have installed Orchard And I have installed "Orchard.MultiTenancy" diff --git a/src/Orchard.Specs/MultiTenancy.feature.cs b/src/Orchard.Specs/MultiTenancy.feature.cs index 98e4f25c0..0553e34f6 100644 --- a/src/Orchard.Specs/MultiTenancy.feature.cs +++ b/src/Orchard.Specs/MultiTenancy.feature.cs @@ -205,10 +205,10 @@ this.ScenarioSetup(scenarioInfo); } [NUnit.Framework.TestAttribute()] - [NUnit.Framework.DescriptionAttribute("A new tenant with preconfigured database goes to the setup screen")] - public virtual void ANewTenantWithPreconfiguredDatabaseGoesToTheSetupScreen() + [NUnit.Framework.DescriptionAttribute("Several tenants are configured and go to setup screen")] + public virtual void SeveralTenantsAreConfiguredAndGoToSetupScreen() { - TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("A new tenant with preconfigured database goes to the setup screen", ((string[])(null))); + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Several tenants are configured and go to setup screen", ((string[])(null))); #line 59 this.ScenarioSetup(scenarioInfo); #line 60 @@ -223,101 +223,76 @@ this.ScenarioSetup(scenarioInfo); "value"}); table4.AddRow(new string[] { "Name", - "Scott"}); + "Scott1"}); table4.AddRow(new string[] { "RequestUrlHost", - "scott.example.org"}); - table4.AddRow(new string[] { - "DataProvider", - "SqlCe"}); + "scott1.example.org"}); #line 63 testRunner.And("I fill in", ((string)(null)), table4); -#line 68 +#line 67 testRunner.And("I hit \"Save\""); -#line 69 +#line 68 testRunner.And("I am redirected"); -#line 70 - testRunner.And("I go to \"/Setup\" on host scott.example.org"); -#line 71 - testRunner.Then("I should see \"Welcome to Orchard\""); -#line 72 - testRunner.And("I should see \"Finish Setup\""); -#line 73 - testRunner.And("I should not see \"SQL Server Compact\""); -#line 74 - testRunner.And("the status should be 200 \"OK\""); -#line hidden - testRunner.CollectScenarioErrors(); - } - - [NUnit.Framework.TestAttribute()] - [NUnit.Framework.DescriptionAttribute("A new tenant runs the setup")] - public virtual void ANewTenantRunsTheSetup() - { - TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("A new tenant runs the setup", ((string[])(null))); -#line 76 -this.ScenarioSetup(scenarioInfo); -#line 77 - testRunner.Given("I have installed Orchard"); -#line 78 - testRunner.And("I have installed \"Orchard.MultiTenancy\""); -#line 79 - testRunner.When("I go to \"Admin/MultiTenancy/Add\""); +#line 69 + testRunner.And("I go to \"Admin/MultiTenancy/Add\""); #line hidden TechTalk.SpecFlow.Table table5 = new TechTalk.SpecFlow.Table(new string[] { "name", "value"}); table5.AddRow(new string[] { "Name", - "Scott"}); + "Scott2"}); table5.AddRow(new string[] { "RequestUrlHost", - "scott.example.org"}); -#line 80 + "scott2.example.org"}); +#line 70 testRunner.And("I fill in", ((string)(null)), table5); -#line 84 +#line 74 testRunner.And("I hit \"Save\""); -#line 85 - testRunner.And("I go to \"/Setup\" on host scott.example.org"); +#line 75 + testRunner.And("I am redirected"); +#line 76 + testRunner.And("I go to \"Admin/MultiTenancy/Add\""); #line hidden TechTalk.SpecFlow.Table table6 = new TechTalk.SpecFlow.Table(new string[] { "name", "value"}); table6.AddRow(new string[] { - "SiteName", - "Scott Site"}); + "Name", + "Scott3"}); table6.AddRow(new string[] { - "AdminPassword", - "6655321"}); - table6.AddRow(new string[] { - "ConfirmPassword", - "6655321"}); -#line 86 + "RequestUrlHost", + "scott3.example.org"}); +#line 77 testRunner.And("I fill in", ((string)(null)), table6); -#line 91 - testRunner.And("I hit \"Finish Setup\""); -#line 92 - testRunner.And("I go to \"/\""); -#line 93 - testRunner.Then("I should see \"Scott Site\""); -#line 94 - testRunner.And("I should see \"Welcome\""); +#line 81 + testRunner.And("I hit \"Save\""); +#line 82 + testRunner.And("I am redirected"); +#line 83 + testRunner.And("I go to \"/Setup\" on host scott1.example.org"); +#line 84 + testRunner.And("I go to \"/Setup\" on host scott2.example.org"); +#line 85 + testRunner.And("I go to \"/Setup\" on host scott3.example.org"); +#line 86 + testRunner.Then("I should see \"Welcome to Orchard\""); #line hidden testRunner.CollectScenarioErrors(); } [NUnit.Framework.TestAttribute()] - [NUnit.Framework.DescriptionAttribute("An existing initialized tenant cannot have its database option cleared")] - public virtual void AnExistingInitializedTenantCannotHaveItsDatabaseOptionCleared() + [NUnit.Framework.DescriptionAttribute("A new tenant with preconfigured database goes to the setup screen")] + public virtual void ANewTenantWithPreconfiguredDatabaseGoesToTheSetupScreen() { - TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("An existing initialized tenant cannot have its database option cleared", ((string[])(null))); -#line 96 + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("A new tenant with preconfigured database goes to the setup screen", ((string[])(null))); +#line 88 this.ScenarioSetup(scenarioInfo); -#line 97 +#line 89 testRunner.Given("I have installed Orchard"); -#line 98 +#line 90 testRunner.And("I have installed \"Orchard.MultiTenancy\""); -#line 99 +#line 91 testRunner.When("I go to \"Admin/MultiTenancy/Add\""); #line hidden TechTalk.SpecFlow.Table table7 = new TechTalk.SpecFlow.Table(new string[] { @@ -329,36 +304,138 @@ this.ScenarioSetup(scenarioInfo); table7.AddRow(new string[] { "RequestUrlHost", "scott.example.org"}); -#line 100 + table7.AddRow(new string[] { + "DataProvider", + "SqlCe"}); +#line 92 testRunner.And("I fill in", ((string)(null)), table7); -#line 104 +#line 97 testRunner.And("I hit \"Save\""); -#line 105 +#line 98 + testRunner.And("I am redirected"); +#line 99 testRunner.And("I go to \"/Setup\" on host scott.example.org"); +#line 100 + testRunner.Then("I should see \"Welcome to Orchard\""); +#line 101 + testRunner.And("I should see \"Finish Setup\""); +#line 102 + testRunner.And("I should not see \"SQL Server Compact\""); +#line 103 + testRunner.And("the status should be 200 \"OK\""); +#line hidden + testRunner.CollectScenarioErrors(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("A new tenant runs the setup")] + public virtual void ANewTenantRunsTheSetup() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("A new tenant runs the setup", ((string[])(null))); +#line 105 +this.ScenarioSetup(scenarioInfo); +#line 106 + testRunner.Given("I have installed Orchard"); +#line 107 + testRunner.And("I have installed \"Orchard.MultiTenancy\""); +#line 108 + testRunner.When("I go to \"Admin/MultiTenancy/Add\""); #line hidden TechTalk.SpecFlow.Table table8 = new TechTalk.SpecFlow.Table(new string[] { "name", "value"}); table8.AddRow(new string[] { + "Name", + "Scott"}); + table8.AddRow(new string[] { + "RequestUrlHost", + "scott.example.org"}); +#line 109 + testRunner.And("I fill in", ((string)(null)), table8); +#line 113 + testRunner.And("I hit \"Save\""); +#line 114 + testRunner.And("I go to \"/Setup\" on host scott.example.org"); +#line hidden + TechTalk.SpecFlow.Table table9 = new TechTalk.SpecFlow.Table(new string[] { + "name", + "value"}); + table9.AddRow(new string[] { "SiteName", "Scott Site"}); - table8.AddRow(new string[] { + table9.AddRow(new string[] { "AdminPassword", "6655321"}); - table8.AddRow(new string[] { + table9.AddRow(new string[] { "ConfirmPassword", "6655321"}); -#line 106 - testRunner.And("I fill in", ((string)(null)), table8); -#line 111 - testRunner.And("I hit \"Finish Setup\""); -#line 112 - testRunner.And("I go to \"/Admin/MultiTenancy/Edit/Scott\" on host localhost"); -#line 113 - testRunner.Then("I should see \"

    Edit Tenant

    \""); -#line 114 - testRunner.And("I should see \"

    Scott

    \""); #line 115 + testRunner.And("I fill in", ((string)(null)), table9); +#line 120 + testRunner.And("I hit \"Finish Setup\""); +#line 121 + testRunner.And("I go to \"/\""); +#line 122 + testRunner.Then("I should see \"Scott Site\""); +#line 123 + testRunner.And("I should see \"Welcome\""); +#line hidden + testRunner.CollectScenarioErrors(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("An existing initialized tenant cannot have its database option cleared")] + public virtual void AnExistingInitializedTenantCannotHaveItsDatabaseOptionCleared() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("An existing initialized tenant cannot have its database option cleared", ((string[])(null))); +#line 125 +this.ScenarioSetup(scenarioInfo); +#line 126 + testRunner.Given("I have installed Orchard"); +#line 127 + testRunner.And("I have installed \"Orchard.MultiTenancy\""); +#line 128 + testRunner.When("I go to \"Admin/MultiTenancy/Add\""); +#line hidden + TechTalk.SpecFlow.Table table10 = new TechTalk.SpecFlow.Table(new string[] { + "name", + "value"}); + table10.AddRow(new string[] { + "Name", + "Scott"}); + table10.AddRow(new string[] { + "RequestUrlHost", + "scott.example.org"}); +#line 129 + testRunner.And("I fill in", ((string)(null)), table10); +#line 133 + testRunner.And("I hit \"Save\""); +#line 134 + testRunner.And("I go to \"/Setup\" on host scott.example.org"); +#line hidden + TechTalk.SpecFlow.Table table11 = new TechTalk.SpecFlow.Table(new string[] { + "name", + "value"}); + table11.AddRow(new string[] { + "SiteName", + "Scott Site"}); + table11.AddRow(new string[] { + "AdminPassword", + "6655321"}); + table11.AddRow(new string[] { + "ConfirmPassword", + "6655321"}); +#line 135 + testRunner.And("I fill in", ((string)(null)), table11); +#line 140 + testRunner.And("I hit \"Finish Setup\""); +#line 141 + testRunner.And("I go to \"/Admin/MultiTenancy/Edit/Scott\" on host localhost"); +#line 142 + testRunner.Then("I should see \"

    Edit Tenant

    \""); +#line 143 + testRunner.And("I should see \"

    Scott

    \""); +#line 144 testRunner.And("I should not see \"Allow the tenant to set up the database\""); #line hidden testRunner.CollectScenarioErrors(); @@ -369,15 +446,15 @@ this.ScenarioSetup(scenarioInfo); public virtual void DefaultTenantCannotBeDisabled() { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Default tenant cannot be disabled", ((string[])(null))); -#line 117 +#line 146 this.ScenarioSetup(scenarioInfo); -#line 118 +#line 147 testRunner.Given("I have installed Orchard"); -#line 119 +#line 148 testRunner.And("I have installed \"Orchard.MultiTenancy\""); -#line 120 +#line 149 testRunner.When("I go to \"Admin/MultiTenancy\""); -#line 121 +#line 150 testRunner.Then("I should not see \"tenant list"); -#line 172 +#line 201 testRunner.Then("I should see \"Name: Alpha\""); -#line 173 +#line 202 testRunner.And("I should see \"Request Url Host: example.org\""); #line hidden testRunner.CollectScenarioErrors(); From bbe6a420f3ddbce59655b52b9e75b5050c235f5b Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Thu, 2 Jun 2011 15:54:18 -0700 Subject: [PATCH 130/139] Fixing tagged content items view order --HG-- branch : 1.x --- .../Modules/Orchard.Tags/Services/TagService.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.Tags/Services/TagService.cs b/src/Orchard.Web/Modules/Orchard.Tags/Services/TagService.cs index 0b025de03..426c14b91 100644 --- a/src/Orchard.Web/Modules/Orchard.Tags/Services/TagService.cs +++ b/src/Orchard.Web/Modules/Orchard.Tags/Services/TagService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; +using Orchard.Core.Common.Models; using Orchard.Data; using Orchard.Localization; using Orchard.Logging; @@ -128,12 +129,11 @@ namespace Orchard.Tags.Services { } public IEnumerable GetTaggedContentItems(int tagId, int skip, int take, VersionOptions options) { - return _contentTagRepository - .Fetch(x => x.TagRecord.Id == tagId) - .Skip(skip) - .Take(take) - .Select(t => _orchardServices.ContentManager.Get(t.TagsPartRecord.Id, options)) - .Where(c => c != null); + return _orchardServices.ContentManager + .Query() + .Where(tpr => tpr.Tags.Any(tr => tr.TagRecord.Id == tagId)) + .Join().OrderByDescending(x => x.CreatedUtc) + .Slice(skip, take); } public int GetTaggedContentItemCount(int tagId) { From f0f71008374c85e2755e436b715fb1941caa7fb8 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Thu, 2 Jun 2011 17:13:08 -0700 Subject: [PATCH 131/139] #17901: Missing attribute value Work Item: 17901 --HG-- branch : 1.x --- src/Orchard.Web/Core/Settings/Commands/SiteSettingsCommands.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Orchard.Web/Core/Settings/Commands/SiteSettingsCommands.cs b/src/Orchard.Web/Core/Settings/Commands/SiteSettingsCommands.cs index 067f42c3e..cd3e6fdb5 100644 --- a/src/Orchard.Web/Core/Settings/Commands/SiteSettingsCommands.cs +++ b/src/Orchard.Web/Core/Settings/Commands/SiteSettingsCommands.cs @@ -26,7 +26,7 @@ namespace Orchard.Core.Settings.Commands { "use the current request context heuristic to discover the base url. " + "If 'Force' is true, set the site base url even if it is already set. " + "The default behavior is to not override the setting.")] - [OrchardSwitches("BaseUrl")] + [OrchardSwitches("BaseUrl,Force")] public string SetBaseUrl() { // Don't do anything if set and not forcing if (!string.IsNullOrEmpty(_siteService.GetSiteSettings().BaseUrl)) { From c20d722ee9417eb0efa9a70ccd9e3bfdcfe5d780 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Thu, 2 Jun 2011 17:23:14 -0700 Subject: [PATCH 132/139] Allow disabling parallel behavior through HostComponents.config Safety net... --HG-- branch : 1.x --- .../Caching/DefaultParallelCacheContext.cs | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/src/Orchard/Caching/DefaultParallelCacheContext.cs b/src/Orchard/Caching/DefaultParallelCacheContext.cs index 0d4678900..79284a509 100644 --- a/src/Orchard/Caching/DefaultParallelCacheContext.cs +++ b/src/Orchard/Caching/DefaultParallelCacheContext.cs @@ -10,22 +10,32 @@ namespace Orchard.Caching { _cacheContextAccessor = cacheContextAccessor; } + /// + /// Allow disabling parallel behavior through HostComponents.config + /// + public bool Disabled { get; set; } + public IEnumerable RunInParallel(IEnumerable source, Func selector) { - // Create tasks that capture the current thread context - var tasks = source.Select(item => this.CreateContextAwareTask(() => selector(item))).ToList(); - - // Run tasks in parallel and combine results immediately - var result = tasks - .AsParallel() // prepare for parallel execution - .AsOrdered() // preserve initial enumeration order - .Select(task => task.Execute()) // prepare tasks to run in parallel - .ToArray(); // force evaluation - - // Forward tokens collected by tasks to the current context - foreach (var task in tasks) { - task.Finish(); + if (Disabled) { + return source.Select(selector); + } + else { + // Create tasks that capture the current thread context + var tasks = source.Select(item => this.CreateContextAwareTask(() => selector(item))).ToList(); + + // Run tasks in parallel and combine results immediately + var result = tasks + .AsParallel() // prepare for parallel execution + .AsOrdered() // preserve initial enumeration order + .Select(task => task.Execute()) // prepare tasks to run in parallel + .ToArray(); // force evaluation + + // Forward tokens collected by tasks to the current context + foreach (var task in tasks) { + task.Finish(); + } + return result; } - return result; } /// From 886d3454d4f8cd651d9378e675642e467f157319 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Fri, 3 Jun 2011 00:34:58 -0700 Subject: [PATCH 133/139] Fix UTs --HG-- branch : 1.x --- .../Descriptors/DefaultShapeTableManagerTests.cs | 1 + .../Descriptors/ShapeTemplateBindingStrategyTests.cs | 1 + src/Orchard.Tests/DisplayManagement/ShapeFactoryTests.cs | 1 + src/Orchard.Tests/DisplayManagement/ShapeHelperTests.cs | 1 + src/Orchard.Tests/DisplayManagement/SubsystemTests.cs | 1 + 5 files changed, 5 insertions(+) diff --git a/src/Orchard.Tests/DisplayManagement/Descriptors/DefaultShapeTableManagerTests.cs b/src/Orchard.Tests/DisplayManagement/Descriptors/DefaultShapeTableManagerTests.cs index c74821b8f..f12c39490 100644 --- a/src/Orchard.Tests/DisplayManagement/Descriptors/DefaultShapeTableManagerTests.cs +++ b/src/Orchard.Tests/DisplayManagement/Descriptors/DefaultShapeTableManagerTests.cs @@ -18,6 +18,7 @@ namespace Orchard.Tests.DisplayManagement.Descriptors { protected override void Register(ContainerBuilder builder) { builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); var features = new [] { new FeatureDescriptor { diff --git a/src/Orchard.Tests/DisplayManagement/Descriptors/ShapeTemplateBindingStrategyTests.cs b/src/Orchard.Tests/DisplayManagement/Descriptors/ShapeTemplateBindingStrategyTests.cs index 5b9f11252..b0c783b7a 100644 --- a/src/Orchard.Tests/DisplayManagement/Descriptors/ShapeTemplateBindingStrategyTests.cs +++ b/src/Orchard.Tests/DisplayManagement/Descriptors/ShapeTemplateBindingStrategyTests.cs @@ -30,6 +30,7 @@ namespace Orchard.Tests.DisplayManagement.Descriptors { builder.Register(ctx => _descriptor); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); diff --git a/src/Orchard.Tests/DisplayManagement/ShapeFactoryTests.cs b/src/Orchard.Tests/DisplayManagement/ShapeFactoryTests.cs index bda26c966..ffd4ca289 100644 --- a/src/Orchard.Tests/DisplayManagement/ShapeFactoryTests.cs +++ b/src/Orchard.Tests/DisplayManagement/ShapeFactoryTests.cs @@ -20,6 +20,7 @@ namespace Orchard.Tests.DisplayManagement { builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); _container = builder.Build(); } diff --git a/src/Orchard.Tests/DisplayManagement/ShapeHelperTests.cs b/src/Orchard.Tests/DisplayManagement/ShapeHelperTests.cs index 953efdadf..36ab18689 100644 --- a/src/Orchard.Tests/DisplayManagement/ShapeHelperTests.cs +++ b/src/Orchard.Tests/DisplayManagement/ShapeHelperTests.cs @@ -19,6 +19,7 @@ namespace Orchard.Tests.DisplayManagement { builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); _container = builder.Build(); } diff --git a/src/Orchard.Tests/DisplayManagement/SubsystemTests.cs b/src/Orchard.Tests/DisplayManagement/SubsystemTests.cs index e2e7eb30f..f57ba0c73 100644 --- a/src/Orchard.Tests/DisplayManagement/SubsystemTests.cs +++ b/src/Orchard.Tests/DisplayManagement/SubsystemTests.cs @@ -48,6 +48,7 @@ namespace Orchard.Tests.DisplayManagement { builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterInstance(new DefaultDisplayManagerTests.TestWorkContextAccessor(workContext)).As(); builder.RegisterInstance(new SimpleShapes()).WithMetadata("Feature", testFeature); builder.RegisterInstance(new RouteCollection()); From cf69b8b2ae720983433abfc7f06836968138d999 Mon Sep 17 00:00:00 2001 From: Dave Reed Date: Fri, 3 Jun 2011 10:09:34 -0700 Subject: [PATCH 134/139] #17899: AdminMenuPart fails when Default Position is for a previously unused position --HG-- branch : 1.x --- src/Orchard/Utility/Position.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Orchard/Utility/Position.cs b/src/Orchard/Utility/Position.cs index 0dcadf66b..66fc7aa19 100644 --- a/src/Orchard/Utility/Position.cs +++ b/src/Orchard/Utility/Position.cs @@ -25,9 +25,11 @@ namespace Orchard.Utility { public static string GetNextMinor(int major, IEnumerable menuItems) { var majorStr = major.ToString(CultureInfo.InvariantCulture); - var allmajors = menuItems.Where(mi => PositionHasMajorNumber(mi, major)).ToArray(); - var maxMenuItem = menuItems.Where(mi => PositionHasMajorNumber(mi, major)).OrderByDescending(mi => mi.Position, new FlatPositionComparer()).FirstOrDefault(); + if (maxMenuItem == null) { + // first one in this major position number + return majorStr; + } var positionParts = maxMenuItem.Position.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries).Where(s => s.Trim().Length > 0); if (positionParts.Count() > 1) { int result; From 84dfae22acda7b3aef5fa6870b935605d467905c Mon Sep 17 00:00:00 2001 From: Andre Rodrigues Date: Fri, 3 Jun 2011 10:54:26 -0700 Subject: [PATCH 135/139] #17816: Caching sitesettings. --HG-- branch : 1.x --- src/Orchard.Web/Core/Orchard.Core.csproj | 1 + src/Orchard.Web/Core/Orchard.Core.csproj.rej | 18 +++ .../Handlers/SiteSettingsPartHandler.cs | 11 +- .../Core/Settings/Models/SiteSettingsCache.cs | 141 ++++++++++++++++++ .../Core/Settings/Services/SiteService.cs | 14 +- .../Modules/Orchard.Setup/SetupMode.cs | 4 + src/Orchard/Settings/ISiteService.cs | 1 + 7 files changed, 184 insertions(+), 6 deletions(-) create mode 100644 src/Orchard.Web/Core/Orchard.Core.csproj.rej create mode 100644 src/Orchard.Web/Core/Settings/Models/SiteSettingsCache.cs diff --git a/src/Orchard.Web/Core/Orchard.Core.csproj b/src/Orchard.Web/Core/Orchard.Core.csproj index c3f4c0885..027136699 100644 --- a/src/Orchard.Web/Core/Orchard.Core.csproj +++ b/src/Orchard.Web/Core/Orchard.Core.csproj @@ -208,6 +208,7 @@ + diff --git a/src/Orchard.Web/Core/Orchard.Core.csproj.rej b/src/Orchard.Web/Core/Orchard.Core.csproj.rej new file mode 100644 index 000000000..6e53a6bbe --- /dev/null +++ b/src/Orchard.Web/Core/Orchard.Core.csproj.rej @@ -0,0 +1,18 @@ +--- Orchard.Core.csproj ++++ Orchard.Core.csproj +@@ -18,6 +18,7 @@ + 3.5 + + ++ false + + + true +@@ -200,6 +201,7 @@ + + + ++ + + + diff --git a/src/Orchard.Web/Core/Settings/Handlers/SiteSettingsPartHandler.cs b/src/Orchard.Web/Core/Settings/Handlers/SiteSettingsPartHandler.cs index 52707ff5e..80a2256f8 100644 --- a/src/Orchard.Web/Core/Settings/Handlers/SiteSettingsPartHandler.cs +++ b/src/Orchard.Web/Core/Settings/Handlers/SiteSettingsPartHandler.cs @@ -1,4 +1,5 @@ -using JetBrains.Annotations; +using System; +using JetBrains.Annotations; using Orchard.Core.Settings.Models; using Orchard.Data; using Orchard.ContentManagement.Handlers; @@ -11,6 +12,14 @@ namespace Orchard.Core.Settings.Handlers { Filters.Add(new ActivatingFilter("Site")); Filters.Add(StorageFilter.For(repository)); Filters.Add(StorageFilter.For(repository2)); + + OnInitializing(InitializeSiteSettings); + } + + private static void InitializeSiteSettings(InitializingContentContext initializingContentContext, SiteSettingsPart siteSettingsPart) { + siteSettingsPart.Record.SiteSalt = Guid.NewGuid().ToString("N"); + siteSettingsPart.Record.SiteName = "My Orchard Project Application"; + siteSettingsPart.Record.PageTitleSeparator = " - "; } } } \ No newline at end of file diff --git a/src/Orchard.Web/Core/Settings/Models/SiteSettingsCache.cs b/src/Orchard.Web/Core/Settings/Models/SiteSettingsCache.cs new file mode 100644 index 000000000..b09a11ec4 --- /dev/null +++ b/src/Orchard.Web/Core/Settings/Models/SiteSettingsCache.cs @@ -0,0 +1,141 @@ +using Orchard.ContentManagement; +using Orchard.Settings; + +namespace Orchard.Core.Settings.Models { + public class SiteSettingsCache : ISite { + private readonly int _id; + + private SiteSettingsPart _siteSettingsPart; + + private string _pageTitleSeparator; + private string _siteName; + private string _siteSalt; + private string _superUser; + private string _homePage; + private string _siteCulture; + private ResourceDebugMode _resourceDebugMode; + private int _pageSize; + private string _baseUrl; + + public SiteSettingsCache(ISite site) { + _id = site.Id; + _pageTitleSeparator = site.PageTitleSeparator; + _siteName = site.SiteName; + _siteSalt = site.SiteSalt; + _superUser = site.SuperUser; + _homePage = site.HomePage; + _siteCulture = site.SiteCulture; + _resourceDebugMode = site.ResourceDebugMode; + _pageSize = site.PageSize; + _baseUrl = site.BaseUrl; + } + + public int Id { + get { return _id; } + } + + public string PageTitleSeparator { + get { return _pageTitleSeparator; } + + set { + _pageTitleSeparator = value; + SiteSettingsPart.PageTitleSeparator = value; + } + } + + public string SiteName { + get { return _siteName; } + + set { + _siteName = value; + SiteSettingsPart.SiteName = value; + } + } + + public string SiteSalt { + get { return _siteSalt; } + + set { + _siteSalt = value; + SiteSettingsPart.Record.SiteSalt = value; + } + } + + public string SuperUser { + get { return _superUser; } + + set { + _superUser = value; + SiteSettingsPart.SuperUser = value; + } + } + + public string HomePage { + get { return _homePage; } + + set { + _homePage = value; + SiteSettingsPart.HomePage = value; + } + } + + public string SiteCulture { + get { return _siteCulture; } + + set { + _siteCulture = value; + SiteSettingsPart.SiteCulture = value; + } + } + + public ResourceDebugMode ResourceDebugMode { + get { return _resourceDebugMode; } + + set { + _resourceDebugMode = value; + SiteSettingsPart.ResourceDebugMode = value; + } + } + + public int PageSize { + get { return _pageSize; } + + set { + _pageSize = value; + SiteSettingsPart.PageSize = value; + } + } + + public string BaseUrl { + get { return _baseUrl; } + + set { + _baseUrl = value; + SiteSettingsPart.BaseUrl = value; + } + } + + public ContentItem ContentItem { + get { return SiteSettingsPart.ContentItem; } + } + + private ISiteService SiteService { get; set; } + + private SiteSettingsPart SiteSettingsPart { + get { + if (_siteSettingsPart == null) { + _siteSettingsPart = SiteService.GetSiteSettingsPart() as SiteSettingsPart; + } + + return _siteSettingsPart; + } + + set { _siteSettingsPart = value; } + } + + public void ResetCache(ISiteService siteService) { + SiteService = siteService; + SiteSettingsPart = null; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Settings/Services/SiteService.cs b/src/Orchard.Web/Core/Settings/Services/SiteService.cs index e9a9f140f..1a697618f 100644 --- a/src/Orchard.Web/Core/Settings/Services/SiteService.cs +++ b/src/Orchard.Web/Core/Settings/Services/SiteService.cs @@ -20,23 +20,27 @@ namespace Orchard.Core.Settings.Services { ICacheManager cacheManager) { _contentManager = contentManager; _cacheManager = cacheManager; + Logger = NullLogger.Instance; } public ILogger Logger { get; set; } public ISite GetSiteSettings() { + SiteSettingsCache siteSettingsCache = _cacheManager.Get("SiteSettings", + ctx => new SiteSettingsCache(GetSiteSettingsPart())); + siteSettingsCache.ResetCache(this); + return siteSettingsCache; + } + + public ISite GetSiteSettingsPart() { var siteId = _cacheManager.Get("SiteId", ctx => { var site = _contentManager.Query("Site") .Slice(0, 1) .FirstOrDefault(); if (site == null) { - site = _contentManager.Create("Site", item => { - item.Record.SiteSalt = Guid.NewGuid().ToString("N"); - item.Record.SiteName = "My Orchard Project Application"; - item.Record.PageTitleSeparator = " - "; - }).ContentItem; + site = _contentManager.Create("Site").ContentItem; } return site.Id; diff --git a/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs b/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs index 50ddb32f6..af3c32067 100644 --- a/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs +++ b/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs @@ -140,6 +140,10 @@ namespace Orchard.Setup { return site.As(); } + + public ISite GetSiteSettingsPart() { + return GetSiteSettings(); + } } class SafeModeSite : ContentPart, ISite { diff --git a/src/Orchard/Settings/ISiteService.cs b/src/Orchard/Settings/ISiteService.cs index cdd2c4888..e67d58ef0 100644 --- a/src/Orchard/Settings/ISiteService.cs +++ b/src/Orchard/Settings/ISiteService.cs @@ -1,5 +1,6 @@ namespace Orchard.Settings { public interface ISiteService : IDependency { ISite GetSiteSettings(); + ISite GetSiteSettingsPart(); } } From a446088fc3a79004115060cfbe691fa7f185217c Mon Sep 17 00:00:00 2001 From: Andre Rodrigues Date: Fri, 3 Jun 2011 11:10:58 -0700 Subject: [PATCH 136/139] Fixing UT. --HG-- branch : 1.x --- .../Users/Controllers/AccountControllerTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Orchard.Tests.Modules/Users/Controllers/AccountControllerTests.cs b/src/Orchard.Tests.Modules/Users/Controllers/AccountControllerTests.cs index b11119ca6..6d9dddc3d 100644 --- a/src/Orchard.Tests.Modules/Users/Controllers/AccountControllerTests.cs +++ b/src/Orchard.Tests.Modules/Users/Controllers/AccountControllerTests.cs @@ -100,7 +100,8 @@ namespace Orchard.Tests.Modules.Users.Controllers { protected override IEnumerable DatabaseTypes { get { return new[] { typeof(UserPartRecord), - typeof(SiteSettingsPartRecord), + typeof(SiteSettingsPartRecord), + typeof(SiteSettings2PartRecord), typeof(RegistrationSettingsPartRecord), typeof(ContentTypeRecord), typeof(ContentItemRecord), From 44800c2b00a0290002b4d967eeefc099b3cefed7 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Fri, 3 Jun 2011 17:34:38 -0700 Subject: [PATCH 137/139] #17773: bug fix Work Item: 17773 --HG-- branch : 1.x --- .../Services/CommentService.cs | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.Comments/Services/CommentService.cs b/src/Orchard.Web/Modules/Orchard.Comments/Services/CommentService.cs index 56b5533ca..51e34d6ca 100644 --- a/src/Orchard.Web/Modules/Orchard.Comments/Services/CommentService.cs +++ b/src/Orchard.Web/Modules/Orchard.Comments/Services/CommentService.cs @@ -69,28 +69,22 @@ namespace Orchard.Comments.Services { } public CommentPart CreateComment(CreateCommentContext context, bool moderateComments) { - var comment = _orchardServices.ContentManager.Create("Comment"); - - comment.Record.Author = context.Author; - comment.Record.CommentDateUtc = _clock.UtcNow; - comment.Record.CommentText = context.CommentText; - comment.Record.Email = context.Email; - comment.Record.SiteName = context.SiteName; - comment.Record.UserName = (_orchardServices.WorkContext.CurrentUser != null ? _orchardServices.WorkContext.CurrentUser.UserName : null); - comment.Record.CommentedOn = context.CommentedOn; - - comment.Record.Status = _commentValidator.ValidateComment(comment) - ? moderateComments ? CommentStatus.Pending : CommentStatus.Approved - : CommentStatus.Spam; - - // store id of the next layer for large-grained operations, e.g. rss on blog - //TODO:(rpaquay) Get rid of this (comment aspect takes care of container) - var commentedOn = _orchardServices.ContentManager.Get(comment.Record.CommentedOn); - if (commentedOn != null && commentedOn.Container != null) { - comment.Record.CommentedOnContainer = commentedOn.Container.ContentItem.Id; - } - - return comment; + return _orchardServices.ContentManager.Create("Comment", comment => { + comment.Record.Author = context.Author; + comment.Record.CommentDateUtc = _clock.UtcNow; + comment.Record.CommentText = context.CommentText; + comment.Record.Email = context.Email; + comment.Record.SiteName = context.SiteName; + comment.Record.UserName = (_orchardServices.WorkContext.CurrentUser != null ? _orchardServices.WorkContext.CurrentUser.UserName : null); + comment.Record.CommentedOn = context.CommentedOn; + comment.Record.Status = _commentValidator.ValidateComment(comment) + ? moderateComments ? CommentStatus.Pending : CommentStatus.Approved + : CommentStatus.Spam; + var commentedOn = _orchardServices.ContentManager.Get(comment.Record.CommentedOn); + if (commentedOn != null && commentedOn.Container != null) { + comment.Record.CommentedOnContainer = commentedOn.Container.ContentItem.Id; + } + }); } public void UpdateComment(int id, string name, string email, string siteName, string commentText, CommentStatus status) { From 2dda95fbf4ae4da9368c226939e2d1638470fb6e Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Fri, 3 Jun 2011 17:43:20 -0700 Subject: [PATCH 138/139] #17828: Fixing background covering links in admin menu Work Items: 17828 --HG-- branch : 1.x --- src/Orchard.Web/Themes/TheAdmin/Styles/site.css | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Orchard.Web/Themes/TheAdmin/Styles/site.css b/src/Orchard.Web/Themes/TheAdmin/Styles/site.css index 3fda91fa6..7c832c20d 100644 --- a/src/Orchard.Web/Themes/TheAdmin/Styles/site.css +++ b/src/Orchard.Web/Themes/TheAdmin/Styles/site.css @@ -184,7 +184,6 @@ hr {border:0; height:1px; color:#e4e5e6; background-color:#e4e5e6;} #footer { clear:both; height:70px; - position:relative; margin-top:-70px; /*Top margin set negative px of footer height*/ background:url(images/vinesBackgroundBottom.gif) no-repeat bottom left; } From a605420d1152fbd5c75b8e8b7bc4d5313c415bbd Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Fri, 3 Jun 2011 17:44:46 -0700 Subject: [PATCH 139/139] Updating Warmup UI --HG-- branch : 1.x --- .../Warmup/WarmupUpdaterTests.cs | 120 ++++++++++++-- .../Parts.Settings.SiteSettingsPart.cshtml | 12 +- .../Modules/Orchard.Warmup/AdminMenu.cs | 5 +- .../Content/Admin/images/offline.gif | Bin 0 -> 293 bytes .../Content/Admin/images/online.gif | Bin 0 -> 405 bytes .../Modules/Orchard.Warmup/Content/Web.config | 21 +++ .../Controllers/AdminController.cs | 52 +++--- .../Orchard.Warmup/Models/ReportEntry.cs | 10 ++ .../Orchard.Warmup/Orchard.Warmup.csproj | 12 +- .../Services/IWarmupReportManager.cs | 9 + .../Services/WarmupReportManager.cs | 58 +++++++ .../Orchard.Warmup/Services/WarmupUpdater.cs | 155 ++++++++++++------ .../Styles/orchard-warmup-admin.css | 8 + .../ViewModels/WarmupViewModel.cs | 9 + .../Orchard.Warmup/Views/Admin/Index.cshtml | 60 +++++-- .../Parts.Warmup.SiteSettings.cshtml | 28 ---- 16 files changed, 416 insertions(+), 143 deletions(-) create mode 100644 src/Orchard.Web/Modules/Orchard.Warmup/Content/Admin/images/offline.gif create mode 100644 src/Orchard.Web/Modules/Orchard.Warmup/Content/Admin/images/online.gif create mode 100644 src/Orchard.Web/Modules/Orchard.Warmup/Content/Web.config create mode 100644 src/Orchard.Web/Modules/Orchard.Warmup/Models/ReportEntry.cs create mode 100644 src/Orchard.Web/Modules/Orchard.Warmup/Services/IWarmupReportManager.cs create mode 100644 src/Orchard.Web/Modules/Orchard.Warmup/Services/WarmupReportManager.cs create mode 100644 src/Orchard.Web/Modules/Orchard.Warmup/Styles/orchard-warmup-admin.css create mode 100644 src/Orchard.Web/Modules/Orchard.Warmup/ViewModels/WarmupViewModel.cs delete mode 100644 src/Orchard.Web/Modules/Orchard.Warmup/Views/EditorTemplates/Parts.Warmup.SiteSettings.cshtml diff --git a/src/Orchard.Tests.Modules/Warmup/WarmupUpdaterTests.cs b/src/Orchard.Tests.Modules/Warmup/WarmupUpdaterTests.cs index 017ff114f..1984cd2bf 100644 --- a/src/Orchard.Tests.Modules/Warmup/WarmupUpdaterTests.cs +++ b/src/Orchard.Tests.Modules/Warmup/WarmupUpdaterTests.cs @@ -6,6 +6,7 @@ using System.Xml; using Autofac; using Moq; using NUnit.Framework; +using Orchard.Environment.Configuration; using Orchard.Environment.Warmup; using Orchard.FileSystems.AppData; using Orchard.FileSystems.LockFile; @@ -26,11 +27,13 @@ namespace Orchard.Tests.Modules.Warmup { private Mock _webDownloader; private IOrchardServices _orchardServices; private WarmupSettingsPart _settings; + private IWarmupReportManager _reportManager; private readonly string _basePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); private string _warmupFilename, _lockFilename; private const string WarmupFolder = "Warmup"; + private const string TenantFolder = "Sites/Default"; [TestFixtureTearDown] public void Clean() { @@ -60,15 +63,18 @@ namespace Orchard.Tests.Modules.Warmup { builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterInstance(new ShellSettings { Name = "Default" }).As(); builder.RegisterInstance(_clock = new StubClock()).As(); builder.RegisterInstance(_webDownloader.Object).As(); _container = builder.Build(); _lockFileManager = _container.Resolve(); _warmupUpdater = _container.Resolve(); + _reportManager = _container.Resolve(); - _warmupFilename = _appDataFolder.Combine(WarmupFolder, "warmup.txt"); - _lockFilename = _appDataFolder.Combine(WarmupFolder, "warmup.txt.lock"); + _warmupFilename = _appDataFolder.Combine(TenantFolder, "warmup.txt"); + _lockFilename = _appDataFolder.Combine(TenantFolder, "warmup.txt.lock"); } [Test] @@ -91,8 +97,7 @@ namespace Orchard.Tests.Modules.Warmup { _lockFileManager.TryAcquireLock(_lockFilename, ref lockFile); using(lockFile) { _warmupUpdater.Generate(); - // warmup file + lock file - Assert.That(_appDataFolder.ListFiles(WarmupFolder).Count(), Is.EqualTo(2)); + Assert.That(_appDataFolder.ListFiles(WarmupFolder).Count(), Is.EqualTo(0)); } _warmupUpdater.Generate(); @@ -114,11 +119,14 @@ namespace Orchard.Tests.Modules.Warmup { _warmupUpdater.Generate(); var files = _appDataFolder.ListFiles(WarmupFolder).ToList(); - // warmup + content files - Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, "warmup.txt"))); Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net")))); Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/About")))); + files = _appDataFolder.ListFiles(TenantFolder).ToList(); + + Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(TenantFolder, "warmup.txt"))); + Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(TenantFolder, "warmup.xml"))); + var homepageContent = _appDataFolder.ReadFile(_appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net"))); var aboutcontent = _appDataFolder.ReadFile(_appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/About"))); @@ -141,8 +149,6 @@ namespace Orchard.Tests.Modules.Warmup { _warmupUpdater.Generate(); var files = _appDataFolder.ListFiles(WarmupFolder).ToList(); - // warmup + content file - Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, "warmup.txt"))); Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net")))); Assert.That(files, Has.None.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/About")))); } @@ -158,9 +164,7 @@ namespace Orchard.Tests.Modules.Warmup { _warmupUpdater.Generate(); var files = _appDataFolder.ListFiles(WarmupFolder).ToList(); - // warmup + content file - Assert.That(files.Count, Is.EqualTo(2)); - Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, "warmup.txt"))); + Assert.That(files.Count, Is.EqualTo(1)); Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net")))); } @@ -258,8 +262,6 @@ namespace Orchard.Tests.Modules.Warmup { _warmupUpdater.Generate(); var files = _appDataFolder.ListFiles(WarmupFolder).ToList(); - // warmup + content files - Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, "warmup.txt"))); Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://www.orchardproject.net")))); Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://www.orchardproject.net/About")))); Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net")))); @@ -276,6 +278,96 @@ namespace Orchard.Tests.Modules.Warmup { Assert.That(aboutcontent, Is.EqualTo("Bar")); Assert.That(wwwaboutcontent, Is.EqualTo("Bar")); } - + + [Test] + public void ReportIsCreated() { + _settings.Urls = @" / + /About"; + + ((StubWorkContextAccessor.WorkContextImpl.StubSite)_orchardServices.WorkContext.CurrentSite).BaseUrl = "http://www.orchardproject.net/"; + + _webDownloader + .Setup(w => w.Download("http://www.orchardproject.net/")) + .Returns(new DownloadResult { Content = "Foo", StatusCode = HttpStatusCode.OK }); + + _webDownloader + .Setup(w => w.Download("http://www.orchardproject.net/About")) + .Returns(new DownloadResult { Content = "Bar", StatusCode = HttpStatusCode.OK }); + + _warmupUpdater.Generate(); + var files = _appDataFolder.ListFiles(WarmupFolder).ToList(); + + Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://www.orchardproject.net")))); + Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://www.orchardproject.net/About")))); + Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net")))); + Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/About")))); + + var report = _reportManager.Read().ToList(); + + Assert.That(report.Count(), Is.EqualTo(2)); + Assert.That(report, Has.Some.Matches(x => x.RelativeUrl == "/")); + Assert.That(report, Has.Some.Matches(x => x.RelativeUrl == "/About")); + } + + [Test] + public void ShouldNotDeleteOtherFiles() { + _settings.Urls = @" / + /About"; + + ((StubWorkContextAccessor.WorkContextImpl.StubSite)_orchardServices.WorkContext.CurrentSite).BaseUrl = "http://www.orchardproject.net/"; + + _webDownloader + .Setup(w => w.Download("http://www.orchardproject.net/")) + .Returns(new DownloadResult { Content = "Foo", StatusCode = HttpStatusCode.OK }); + + _webDownloader + .Setup(w => w.Download("http://www.orchardproject.net/About")) + .Returns(new DownloadResult { Content = "Bar", StatusCode = HttpStatusCode.OK }); + + // Create a static file in the warmup folder + _appDataFolder.CreateFile(_appDataFolder.Combine(WarmupFolder, "foo.txt"), "Foo"); + + _warmupUpdater.Generate(); + var files = _appDataFolder.ListFiles(WarmupFolder).ToList(); + + Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, "foo.txt"))); + + Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://www.orchardproject.net")))); + Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://www.orchardproject.net/About")))); + Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net")))); + Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/About")))); + } + + + [Test] + public void ClearingUrlsShouldDeleteContent() { + _settings.Urls = @" / + /About"; + + ((StubWorkContextAccessor.WorkContextImpl.StubSite)_orchardServices.WorkContext.CurrentSite).BaseUrl = "http://www.orchardproject.net/"; + + _webDownloader + .Setup(w => w.Download("http://www.orchardproject.net/")) + .Returns(new DownloadResult { Content = "Foo", StatusCode = HttpStatusCode.OK }); + + _webDownloader + .Setup(w => w.Download("http://www.orchardproject.net/About")) + .Returns(new DownloadResult { Content = "Bar", StatusCode = HttpStatusCode.OK }); + + _warmupUpdater.Generate(); + var files = _appDataFolder.ListFiles(WarmupFolder).ToList(); + + Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://www.orchardproject.net")))); + Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://www.orchardproject.net/About")))); + Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net")))); + Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/About")))); + + _settings.Urls = @""; + + _warmupUpdater.Generate(); + files = _appDataFolder.ListFiles(WarmupFolder).ToList(); + + Assert.That(files.Count, Is.EqualTo(0)); + } } } diff --git a/src/Orchard.Web/Core/Settings/Views/EditorTemplates/Parts.Settings.SiteSettingsPart.cshtml b/src/Orchard.Web/Core/Settings/Views/EditorTemplates/Parts.Settings.SiteSettingsPart.cshtml index d41a5e723..be3daaadb 100644 --- a/src/Orchard.Web/Core/Settings/Views/EditorTemplates/Parts.Settings.SiteSettingsPart.cshtml +++ b/src/Orchard.Web/Core/Settings/Views/EditorTemplates/Parts.Settings.SiteSettingsPart.cshtml @@ -14,6 +14,12 @@ @Html.EditorFor(m => m.SiteName) @Html.ValidationMessage("SiteName", "*") +
    + + @Html.TextBoxFor(m => m.BaseUrl, new { @class = "textMedium" }) + @T("Enter the fully qualified base url of your website.") + @T("e.g., http://localhost:30320/orchardlocal, http://www.yourdomain.com") +
    @Html.DropDownList("SiteCulture", new SelectList(Model.SiteCultures, Model.SiteCulture)) @@ -41,10 +47,4 @@ @Html.TextBoxFor(m => m.PageSize, new { @class = "text-small" }) @T("Determines the default number of items that are shown per page.")
    -
    - - @Html.TextBoxFor(m => m.BaseUrl, new { @class = "textMedium" }) - @T("Enter the fully qualified base url of your website.") - @T("e.g., http://localhost:30320/orchardlocal, http://www.yourdomain.com") -
    \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/AdminMenu.cs b/src/Orchard.Web/Modules/Orchard.Warmup/AdminMenu.cs index 006cb26e0..457138c5f 100644 --- a/src/Orchard.Web/Modules/Orchard.Warmup/AdminMenu.cs +++ b/src/Orchard.Web/Modules/Orchard.Warmup/AdminMenu.cs @@ -10,8 +10,9 @@ namespace Orchard.Warmup { public void GetNavigation(NavigationBuilder builder) { builder .Add(T("Settings"), menu => menu - .Add(T("Warmup" ), "10.0", item => item.Action("Index", "Admin", new { area = "Orchard.Warmup" }).Permission(StandardPermissions.SiteOwner)) - ); + .Add(T("Performance"), "10.0", subMenu => subMenu.Action("Index", "Admin", new { area = "Orchard.Warmup" }).Permission(StandardPermissions.SiteOwner) + .Add(T("Warmup"), "10.0", item => item.Action("Index", "Admin", new { area = "Orchard.Warmup" }).Permission(StandardPermissions.SiteOwner).LocalNav()) + )); } } } diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Content/Admin/images/offline.gif b/src/Orchard.Web/Modules/Orchard.Warmup/Content/Admin/images/offline.gif new file mode 100644 index 0000000000000000000000000000000000000000..95a2792acdd0bd1cdfc7af069d61f8dd523938ea GIT binary patch literal 293 zcmZ?wbhEHb6k-r!I3mXI|Ns9B{&vqId~OE2e1HDrb!o!$XulV+0k4nkeN+^Adq(H0 zhj=7m- z5eJh%PrIXl0V_)zyR%!7iWt)-%Y7ae3`^V|Cg@Jzz|P8B!5}IkSf|dwC@3P@QdQGg z&)nFYo6i)M#>`Y&u9A>6E0u9>W_AlhP{@+y(XkaO&aUpB-oE|;3}zNqHg*o1cE{`L S8yah>be%kP?!1yBgEaskzF;H( literal 0 HcmV?d00001 diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Content/Admin/images/online.gif b/src/Orchard.Web/Modules/Orchard.Warmup/Content/Admin/images/online.gif new file mode 100644 index 0000000000000000000000000000000000000000..67a22969ae541437cf464e02f693d3183d41b369 GIT binary patch literal 405 zcmZ?wbhEHb6k-r!xT?nR|NsBQ<=&6pJZf6k=-J`sG{Gu)rEm4h%3YUsrmqi7-x`{; zGdy&ONAhaF$Q9oDRa#kFL$ymal2-Z@?2Y>K_xH>TJ^LPSd-mn=+wZTpKiu&5-`@jI zcFsK8_2t*6Yws>iJKHhsbld*NJ6eyHe*F2N{bc3MH&=i*GEfgF{$ycfU{GVw0T~JM z69d~OhshUMG^BbDFA*$MaN%HQDT)x`;o>{$@=$|^#dXz#gApoHliZRHRy4F8Wn@m` zxbc>aBUyk=fs4DeyiAOZn>EOVosYA*g+ZK?kDbAVg^RaOnn6UAmy2a8gP^bwqof?8 z;v7*%Az{Hq(`PC(NX(Hho4>HDhfzhAK}w-~(iE5aMn)x7new)dMGQqHeU;UTF3G9s knc2Dd1q@*kQ894|Q$t*R`~re*db+r~fBN?QlOuyQ0GsEB2><{9 literal 0 HcmV?d00001 diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Content/Web.config b/src/Orchard.Web/Modules/Orchard.Warmup/Content/Web.config new file mode 100644 index 000000000..0dc62ece6 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Warmup/Content/Web.config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.Warmup/Controllers/AdminController.cs index 07e23012c..89b6d2c95 100644 --- a/src/Orchard.Web/Modules/Orchard.Warmup/Controllers/AdminController.cs +++ b/src/Orchard.Web/Modules/Orchard.Warmup/Controllers/AdminController.cs @@ -1,25 +1,28 @@ using System; using System.IO; +using System.Linq; using System.Web.Mvc; using Orchard.ContentManagement; using Orchard.Core.Contents.Controllers; -using Orchard.FileSystems.AppData; using Orchard.Localization; using Orchard.Security; using Orchard.Warmup.Models; using Orchard.UI.Notify; using Orchard.Warmup.Services; +using Orchard.Warmup.ViewModels; namespace Orchard.Warmup.Controllers { [ValidateInput(false)] public class AdminController : Controller, IUpdateModel { - private readonly IWarmupScheduler _warmupScheduler; + private readonly IWarmupUpdater _warmupUpdater; + private readonly IWarmupReportManager _reportManager; public AdminController( IOrchardServices services, - IWarmupScheduler warmupScheduler, - IAppDataFolder appDataFolder) { - _warmupScheduler = warmupScheduler; + IWarmupUpdater warmupUpdater, + IWarmupReportManager reportManager) { + _warmupUpdater = warmupUpdater; + _reportManager = reportManager; Services = services; T = NullLocalizer.Instance; @@ -33,7 +36,13 @@ namespace Orchard.Warmup.Controllers { return new HttpUnauthorizedResult(); var warmupPart = Services.WorkContext.CurrentSite.As(); - return View(warmupPart); + + var viewModel = new WarmupViewModel { + Settings = warmupPart, + ReportEntries = _reportManager.Read() + }; + + return View(viewModel); } [FormValueRequired("submit")] @@ -42,11 +51,14 @@ namespace Orchard.Warmup.Controllers { if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not authorized to manage settings"))) return new HttpUnauthorizedResult(); - var warmupPart = Services.WorkContext.CurrentSite.As(); + var viewModel = new WarmupViewModel { + Settings = Services.WorkContext.CurrentSite.As(), + ReportEntries = Enumerable.Empty() + }; - if(TryUpdateModel(warmupPart)) { - if (!String.IsNullOrEmpty(warmupPart.Urls)) { - using (var urlReader = new StringReader(warmupPart.Urls)) { + if (TryUpdateModel(viewModel)) { + if (!String.IsNullOrEmpty(viewModel.Settings.Urls)) { + using (var urlReader = new StringReader(viewModel.Settings.Urls)) { string relativeUrl; while (null != (relativeUrl = urlReader.ReadLine())) { if (!Uri.IsWellFormedUriString(relativeUrl, UriKind.Relative) || !(relativeUrl.StartsWith("/"))) { @@ -57,30 +69,20 @@ namespace Orchard.Warmup.Controllers { } } - if (warmupPart.Scheduled) { - if (warmupPart.Delay <= 0) { + if (viewModel.Settings.Scheduled) { + if (viewModel.Settings.Delay <= 0) { AddModelError("Delay", T("Delay must be greater than zero.")); } } if (ModelState.IsValid) { Services.Notifier.Information(T("Warmup updated successfully.")); - } - - return View(warmupPart); - } - - [FormValueRequired("submit.Generate")] - [HttpPost, ActionName("Index")] - public ActionResult IndexPostGenerate() { - var result = IndexPost(); - + } if (ModelState.IsValid) { - _warmupScheduler.Schedule(true); - Services.Notifier.Information(T("Static pages are currently being generated.")); + _warmupUpdater.Generate(); } - return result; + return RedirectToAction("Index"); } bool IUpdateModel.TryUpdateModel(TModel model, string prefix, string[] includeProperties, string[] excludeProperties) { diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Models/ReportEntry.cs b/src/Orchard.Web/Modules/Orchard.Warmup/Models/ReportEntry.cs new file mode 100644 index 000000000..c5ae4ab2d --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Warmup/Models/ReportEntry.cs @@ -0,0 +1,10 @@ +using System; + +namespace Orchard.Warmup.Models { + public class ReportEntry { + public string RelativeUrl { get; set; } + public string Filename { get; set; } + public int StatusCode { get; set; } + public DateTime CreatedUtc { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Orchard.Warmup.csproj b/src/Orchard.Web/Modules/Orchard.Warmup/Orchard.Warmup.csproj index 1f958bf1a..431a0cd3b 100644 --- a/src/Orchard.Web/Modules/Orchard.Warmup/Orchard.Warmup.csproj +++ b/src/Orchard.Web/Modules/Orchard.Warmup/Orchard.Warmup.csproj @@ -59,6 +59,9 @@ + + + @@ -83,8 +86,11 @@ + + + @@ -93,10 +99,12 @@ + - - + + Designer + diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Services/IWarmupReportManager.cs b/src/Orchard.Web/Modules/Orchard.Warmup/Services/IWarmupReportManager.cs new file mode 100644 index 000000000..9a0c397a4 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Warmup/Services/IWarmupReportManager.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; +using Orchard.Warmup.Models; + +namespace Orchard.Warmup.Services { + public interface IWarmupReportManager : IDependency { + IEnumerable Read(); + void Create(IEnumerable reportEntries); + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Services/WarmupReportManager.cs b/src/Orchard.Web/Modules/Orchard.Warmup/Services/WarmupReportManager.cs new file mode 100644 index 000000000..658554546 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Warmup/Services/WarmupReportManager.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Xml; +using System.Xml.Linq; +using Orchard.Environment.Configuration; +using Orchard.FileSystems.AppData; +using Orchard.Warmup.Models; + +namespace Orchard.Warmup.Services { + public class WarmupReportManager : IWarmupReportManager { + private readonly IAppDataFolder _appDataFolder; + private const string WarmupReportFilename = "warmup.xml"; + private readonly string _warmupReportPath; + + public WarmupReportManager( + ShellSettings shellSettings, + IAppDataFolder appDataFolder) { + _appDataFolder = appDataFolder; + + _warmupReportPath = _appDataFolder.Combine("Sites", _appDataFolder.Combine(shellSettings.Name, WarmupReportFilename)); + } + + public IEnumerable Read() { + if(!_appDataFolder.FileExists(_warmupReportPath)) { + yield break; + } + + var warmupReportContent = _appDataFolder.ReadFile(_warmupReportPath); + + var doc = XDocument.Parse(warmupReportContent); + foreach (var entryNode in doc.Root.Descendants("ReportEntry")) { + yield return new ReportEntry { + CreatedUtc = XmlConvert.ToDateTime(entryNode.Attribute("CreatedUtc").Value, XmlDateTimeSerializationMode.Utc), + Filename = entryNode.Attribute("Filename").Value, + RelativeUrl = entryNode.Attribute("RelativeUrl").Value, + StatusCode = Int32.Parse(entryNode.Attribute("StatusCode").Value) + }; + } + } + + public void Create(IEnumerable reportEntries) { + var report = new XDocument(new XElement("WarmupReport")); + + foreach (var reportEntry in reportEntries) { + report.Root.Add( + new XElement("ReportEntry", + new XAttribute("RelativeUrl", reportEntry.RelativeUrl), + new XAttribute("Filename", reportEntry.Filename), + new XAttribute("StatusCode", reportEntry.StatusCode), + new XAttribute("CreatedUtc", XmlConvert.ToString(reportEntry.CreatedUtc, XmlDateTimeSerializationMode.Utc)) + ) + ); + } + + _appDataFolder.CreateFile(_warmupReportPath, report.ToString()); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Services/WarmupUpdater.cs b/src/Orchard.Web/Modules/Orchard.Warmup/Services/WarmupUpdater.cs index 9ebb7afdc..486abe97e 100644 --- a/src/Orchard.Web/Modules/Orchard.Warmup/Services/WarmupUpdater.cs +++ b/src/Orchard.Web/Modules/Orchard.Warmup/Services/WarmupUpdater.cs @@ -1,9 +1,11 @@ using System; +using System.Collections.Generic; using System.IO; using System.Net; using System.Web; using System.Xml; using Orchard.ContentManagement; +using Orchard.Environment.Configuration; using Orchard.Environment.Warmup; using Orchard.FileSystems.AppData; using Orchard.FileSystems.LockFile; @@ -18,8 +20,11 @@ namespace Orchard.Warmup.Services { private readonly IClock _clock; private readonly IAppDataFolder _appDataFolder; private readonly IWebDownloader _webDownloader; + private readonly IWarmupReportManager _reportManager; private const string BaseFolder = "Warmup"; private const string WarmupFilename = "warmup.txt"; + + private readonly string _warmupPath; private readonly string _lockFilename; public WarmupUpdater( @@ -27,13 +32,18 @@ namespace Orchard.Warmup.Services { ILockFileManager lockFileManager, IClock clock, IAppDataFolder appDataFolder, - IWebDownloader webDownloader) { + IWebDownloader webDownloader, + IWarmupReportManager reportManager, + ShellSettings shellSettings) { _orchardServices = orchardServices; _lockFileManager = lockFileManager; _clock = clock; _appDataFolder = appDataFolder; _webDownloader = webDownloader; - _lockFilename = _appDataFolder.Combine(BaseFolder, WarmupFilename + ".lock"); + _reportManager = reportManager; + + _lockFilename = _appDataFolder.Combine("Sites", _appDataFolder.Combine(shellSettings.Name, WarmupFilename + ".lock")); + _warmupPath = _appDataFolder.Combine("Sites", _appDataFolder.Combine(shellSettings.Name, WarmupFilename)); Logger = NullLogger.Instance; } @@ -44,8 +54,8 @@ namespace Orchard.Warmup.Services { var baseUrl = _orchardServices.WorkContext.CurrentSite.BaseUrl; var part = _orchardServices.WorkContext.CurrentSite.As(); - // do nothing while the base url setting is not defined, or if there is no page defined - if (String.IsNullOrWhiteSpace(baseUrl) || String.IsNullOrWhiteSpace(part.Urls)) { + // do nothing while the base url setting is not defined + if (String.IsNullOrWhiteSpace(baseUrl)) { return; } @@ -60,10 +70,9 @@ namespace Orchard.Warmup.Services { // check if we need to regenerate the pages by reading the last time it has been done // 1- if the warmup file doesn't exists, generate the pages // 2- otherwise, if the scheduled generation option is on, check if the delay is over - var warmupPath = _appDataFolder.Combine(BaseFolder, WarmupFilename); - if(_appDataFolder.FileExists(warmupPath)) { + if (_appDataFolder.FileExists(_warmupPath)) { try { - var warmupContent = _appDataFolder.ReadFile(warmupPath); + var warmupContent = _appDataFolder.ReadFile(_warmupPath); var expired = XmlConvert.ToDateTimeOffset(warmupContent).AddMinutes(part.Delay); if (expired > _clock.UtcNow) { return; @@ -71,63 +80,104 @@ namespace Orchard.Warmup.Services { } catch { // invalid file, delete continue processing - _appDataFolder.DeleteFile(warmupPath); + _appDataFolder.DeleteFile(_warmupPath); } } - // delete existing static page files - foreach (var filename in _appDataFolder.ListFiles(BaseFolder)) { - var prefix = _appDataFolder.Combine(BaseFolder, "http"); - - // delete only static page files - if (!filename.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { - continue; - } - - try { - _appDataFolder.DeleteFile(filename); - } - catch(Exception e) { - // ignore files which could not be deleted - Logger.Error(e, "Could not delete file {0}", filename); - } - } - - // loop over every relative url to generate the contents - using (var urlReader = new StringReader(part.Urls)) { - string relativeUrl; - while (null != (relativeUrl = urlReader.ReadLine())) { - string url = null; - relativeUrl = relativeUrl.Trim(); + // delete peviously generated pages, by reading the Warmup Report file + try { + var encodedPrefix = WarmupUtility.EncodeUrl("http://www."); + foreach (var reportEntry in _reportManager.Read()) { try { - url = VirtualPathUtility.RemoveTrailingSlash(baseUrl) + relativeUrl; - var download = _webDownloader.Download(url); - - if (download != null && download.StatusCode == HttpStatusCode.OK) { - var filename = WarmupUtility.EncodeUrl(url.TrimEnd('/')); - var path = _appDataFolder.Combine(BaseFolder, filename); - _appDataFolder.CreateFile(path, download.Content); - - // if the base url contains http://www, then also render the www-less one - - if (url.StartsWith("http://www.", StringComparison.OrdinalIgnoreCase)) { - url = "http://" + url.Substring("http://www.".Length); - filename = WarmupUtility.EncodeUrl(url.TrimEnd('/')); - path = _appDataFolder.Combine(BaseFolder, filename); - _appDataFolder.CreateFile(path, download.Content); - } + // use FileName as the SiteBaseUrl could have changed in the meantime + var path = _appDataFolder.Combine(BaseFolder, reportEntry.Filename); + _appDataFolder.DeleteFile(path); + // delete the www-less version too if it's available + if (reportEntry.Filename.StartsWith(encodedPrefix, StringComparison.OrdinalIgnoreCase)) { + var filename = WarmupUtility.EncodeUrl("http://") + reportEntry.Filename.Substring(encodedPrefix.Length); + path = _appDataFolder.Combine(BaseFolder, filename); + _appDataFolder.DeleteFile(path); } } catch (Exception e) { - Logger.Error(e, "Could not extract warmup page content for: ", url); + Logger.Error(e, "Could not delete specific warmup file: ", reportEntry.Filename); + } + } + } + catch(Exception e) { + Logger.Error(e, "Could not read warmup report file"); + } + + var reportEntries = new List(); + + if (!String.IsNullOrEmpty(part.Urls)) { + // loop over every relative url to generate the contents + using (var urlReader = new StringReader(part.Urls)) { + string relativeUrl; + while (null != (relativeUrl = urlReader.ReadLine())) { + string url = null; + relativeUrl = relativeUrl.Trim(); + + try { + url = VirtualPathUtility.RemoveTrailingSlash(baseUrl) + relativeUrl; + var filename = WarmupUtility.EncodeUrl(url.TrimEnd('/')); + var path = _appDataFolder.Combine(BaseFolder, filename); + + var download = _webDownloader.Download(url); + + if (download != null) { + if (download.StatusCode == HttpStatusCode.OK) { + // success + _appDataFolder.CreateFile(path, download.Content); + + reportEntries.Add(new ReportEntry { + RelativeUrl = relativeUrl, + Filename = filename, + StatusCode = (int) download.StatusCode, + CreatedUtc = _clock.UtcNow + }); + + // if the base url contains http://www, then also render the www-less one); + + if (url.StartsWith("http://www.", StringComparison.OrdinalIgnoreCase)) { + url = "http://" + url.Substring("http://www.".Length); + filename = WarmupUtility.EncodeUrl(url.TrimEnd('/')); + path = _appDataFolder.Combine(BaseFolder, filename); + _appDataFolder.CreateFile(path, download.Content); + } + } + else { + reportEntries.Add(new ReportEntry { + RelativeUrl = relativeUrl, + Filename = filename, + StatusCode = (int) download.StatusCode, + CreatedUtc = _clock.UtcNow + }); + } + } + else { + // download failed + reportEntries.Add(new ReportEntry { + RelativeUrl = relativeUrl, + Filename = filename, + StatusCode = 0, + CreatedUtc = _clock.UtcNow + }); + } + } + catch (Exception e) { + Logger.Error(e, "Could not extract warmup page content for: ", url); + } } } } + _reportManager.Create(reportEntries); + // finally write the time the generation has been executed - _appDataFolder.CreateFile(warmupPath, XmlConvert.ToString(_clock.UtcNow, XmlDateTimeSerializationMode.Utc)); + _appDataFolder.CreateFile(_warmupPath, XmlConvert.ToString(_clock.UtcNow, XmlDateTimeSerializationMode.Utc)); } } @@ -139,9 +189,8 @@ namespace Orchard.Warmup.Services { } using (lockFile) { - var warmupPath = _appDataFolder.Combine(BaseFolder, WarmupFilename); - if (_appDataFolder.FileExists(warmupPath)) { - _appDataFolder.DeleteFile(warmupPath); + if (_appDataFolder.FileExists(_warmupPath)) { + _appDataFolder.DeleteFile(_warmupPath); } } diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Styles/orchard-warmup-admin.css b/src/Orchard.Web/Modules/Orchard.Warmup/Styles/orchard-warmup-admin.css new file mode 100644 index 000000000..4847a44f8 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Warmup/Styles/orchard-warmup-admin.css @@ -0,0 +1,8 @@ + +td.status-error { + background: transparent url(../Content/Admin/images/offline.gif) no-repeat right; +} + +td.status-ok { + background: transparent url(../Content/Admin/images/online.gif) no-repeat right; +} diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/ViewModels/WarmupViewModel.cs b/src/Orchard.Web/Modules/Orchard.Warmup/ViewModels/WarmupViewModel.cs new file mode 100644 index 000000000..8b2dc20ba --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Warmup/ViewModels/WarmupViewModel.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; +using Orchard.Warmup.Models; + +namespace Orchard.Warmup.ViewModels { + public class WarmupViewModel { + public WarmupSettingsPart Settings { get; set; } + public IEnumerable ReportEntries { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Views/Admin/Index.cshtml b/src/Orchard.Web/Modules/Orchard.Warmup/Views/Admin/Index.cshtml index c46c14fd6..9b6c8d2f9 100644 --- a/src/Orchard.Web/Modules/Orchard.Warmup/Views/Admin/Index.cshtml +++ b/src/Orchard.Web/Modules/Orchard.Warmup/Views/Admin/Index.cshtml @@ -1,38 +1,72 @@ -@model Orchard.Warmup.Models.WarmupSettingsPart +@model Orchard.Warmup.ViewModels.WarmupViewModel @using Orchard.Utility.Extensions; @using Orchard.Warmup.Models; -@{ Layout.Title = T("Settings").ToString(); } +@{ + Style.Include("orchard-warmup-admin.css"); + Layout.Title = T("Performance").ToString(); +} + +

    The urls below will be requested using @Html.Link(WorkContext.CurrentSite.BaseUrl, WorkContext.CurrentSite.BaseUrl) as a base url. You can change it on the @Html.ActionLink(T("General Settings page").Text, "Index", new { controller = "Admin", area = "Settings" }).

    @using (Html.BeginFormAntiForgeryPost()) { @Html.ValidationSummary() -
    - @T("Warmup")
    - - @Html.TextAreaFor(m => m.Urls, new { @class = "textMedium" }) + @Html.TextAreaFor(m => m.Settings.Urls, new { @class = "textMedium" }) @T("This must be a set of relative paths, e.g., /, /About")
    - @Html.EditorFor(m => m.Scheduled) - + @Html.EditorFor(m => m.Settings.Scheduled) +
    -
    +
    @T("Every") - @Html.TextBoxFor(m => m.Delay, new { @class = "text-small" }) + @Html.TextBoxFor(m => m.Settings.Delay, new { @class = "text-small" }) @T("minutes") @Html.ValidationMessage("Delay", "*")
    - @Html.EditorFor(m => m.OnPublish) - + @Html.EditorFor(m => m.Settings.OnPublish) +
    -
    } + +
    + +
    + + + + + + + + + + + + + + + @foreach (var reportEntry in Model.ReportEntries) { + + + + + + } +
    @T("Url")@T("Status")@T("Date")
    + @Html.Link(Html.Encode(reportEntry.RelativeUrl), reportEntry.RelativeUrl, new { target = "_blank" }) + + @reportEntry.StatusCode + + @Display.DateTimeRelative(dateTimeUtc: reportEntry.CreatedUtc).ToString() +
    + diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Views/EditorTemplates/Parts.Warmup.SiteSettings.cshtml b/src/Orchard.Web/Modules/Orchard.Warmup/Views/EditorTemplates/Parts.Warmup.SiteSettings.cshtml deleted file mode 100644 index 6fb7c7a40..000000000 --- a/src/Orchard.Web/Modules/Orchard.Warmup/Views/EditorTemplates/Parts.Warmup.SiteSettings.cshtml +++ /dev/null @@ -1,28 +0,0 @@ -@model Orchard.Warmup.Models.WarmupSettingsPartRecord -@using Orchard.Utility.Extensions; -@using Orchard.Warmup.Models; - -
    - @T("Warmup") - -
    - - @Html.TextAreaFor(m => m.Urls, new { @class = "textMedium" }) - @Html.ValidationMessage("Urls", "*") - @T("This must be a set of virtual paths, e.g., ~/, ~/About") -
    - -
    - @Html.EditorFor(m => m.Scheduled) - -
    -
    m.Urls)">@T("Delay to generate pages") - @Html.TextBoxFor(m => m.Delay, new { @class = "" }) @T("minutes") - @Html.ValidationMessage("Delay", "*") -
    -
    - @Html.EditorFor(m => m.OnPublish) - -
    -
    \ No newline at end of file