From 87a64b740669ecebd2f52db56a61acdf6da14501 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 19 Nov 2018 14:40:59 +0100 Subject: [PATCH] Refactor filesystems (again) --- .../Composers/FileSystemsComposer.cs | 12 +- src/Umbraco.Core/Composing/Current.cs | 3 + src/Umbraco.Core/ContentExtensions.cs | 2 +- .../Events/QueuingEventDispatcher.cs | 2 +- src/Umbraco.Core/IO/FileSystemAttribute.cs | 25 --- src/Umbraco.Core/IO/FileSystems.cs | 186 +++++++++--------- src/Umbraco.Core/IO/IFileSystems.cs | 5 - src/Umbraco.Core/IO/IMediaFileSystem.cs | 2 +- src/Umbraco.Core/IO/MediaFileSystem.cs | 1 - src/Umbraco.Core/IO/ShadowFileSystems.cs | 71 ++----- src/Umbraco.Core/Models/UserExtensions.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 - src/Umbraco.Tests/IO/FileSystemsTests.cs | 18 +- src/Umbraco.Tests/IO/ShadowFileSystemTests.cs | 100 ++++++---- src/Umbraco.Tests/Models/ContentTests.cs | 6 +- .../Scoping/ScopeFileSystemsTests.cs | 10 +- .../TestHelpers/TestObjects-Mocks.cs | 3 - src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 2 +- src/Umbraco.Web/Editors/UsersController.cs | 6 +- 19 files changed, 203 insertions(+), 254 deletions(-) delete mode 100644 src/Umbraco.Core/IO/FileSystemAttribute.cs diff --git a/src/Umbraco.Core/Composing/Composers/FileSystemsComposer.cs b/src/Umbraco.Core/Composing/Composers/FileSystemsComposer.cs index 56a0ba316c..22e0d33456 100644 --- a/src/Umbraco.Core/Composing/Composers/FileSystemsComposer.cs +++ b/src/Umbraco.Core/Composing/Composers/FileSystemsComposer.cs @@ -76,15 +76,13 @@ namespace Umbraco.Core.Composing.Composers // register IFileSystems, which gives access too all filesystems container.RegisterSingleton(factory => factory.GetInstance()); - // register IMediaFileSystem - var virtualRoot = GetMediaFileSystemVirtualRoot(); - container.RegisterSingleton("media", factory => new PhysicalFileSystem(virtualRoot)); - container.RegisterSingleton(factory => factory.GetInstance().GetFileSystem()); + // register IMediaFileSystem with its actual, underlying filesystem factory + container.RegisterSingleton(factory => factory.GetInstance().GetFileSystem(MediaInnerFileSystemFactory)); return container; } - private static string GetMediaFileSystemVirtualRoot() + private static IFileSystem MediaInnerFileSystemFactory() { // for the time being, we still use the FileSystemProvider config file // but, detect if ppl are trying to use it to change the "provider" @@ -93,13 +91,13 @@ namespace Umbraco.Core.Composing.Composers var config = (FileSystemProvidersSection)ConfigurationManager.GetSection("umbracoConfiguration/FileSystemProviders"); var p = config?.Providers["media"]; - if (p == null) return virtualRoot; + if (p == null) return new PhysicalFileSystem(virtualRoot); if (!string.IsNullOrWhiteSpace(p.Type) && p.Type != "Umbraco.Core.IO.PhysicalFileSystem, Umbraco.Core") throw new InvalidOperationException("Setting a provider type in FileSystemProviders.config is not supported anymore, see FileSystemsComposer for help."); virtualRoot = p?.Parameters["virtualRoot"]?.Value ?? "~/media"; - return virtualRoot; + return new PhysicalFileSystem(virtualRoot); } } } diff --git a/src/Umbraco.Core/Composing/Current.cs b/src/Umbraco.Core/Composing/Current.cs index cfadb37d6d..7792b31dc1 100644 --- a/src/Umbraco.Core/Composing/Current.cs +++ b/src/Umbraco.Core/Composing/Current.cs @@ -104,6 +104,9 @@ namespace Umbraco.Core.Composing public static IFileSystems FileSystems => Container.GetInstance(); + public static IMediaFileSystem MediaFileSystem + => Container.GetInstance(); + public static UrlSegmentProviderCollection UrlSegmentProviders => Container.GetInstance(); diff --git a/src/Umbraco.Core/ContentExtensions.cs b/src/Umbraco.Core/ContentExtensions.cs index ee6602e9aa..d26f04d454 100644 --- a/src/Umbraco.Core/ContentExtensions.cs +++ b/src/Umbraco.Core/ContentExtensions.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core { // this ain't pretty private static IMediaFileSystem _mediaFileSystem; - private static IMediaFileSystem MediaFileSystem => _mediaFileSystem ?? (_mediaFileSystem = Current.FileSystems.MediaFileSystem); + private static IMediaFileSystem MediaFileSystem => _mediaFileSystem ?? (_mediaFileSystem = Current.MediaFileSystem); #region IContent diff --git a/src/Umbraco.Core/Events/QueuingEventDispatcher.cs b/src/Umbraco.Core/Events/QueuingEventDispatcher.cs index 54ed8580a0..b31b64e435 100644 --- a/src/Umbraco.Core/Events/QueuingEventDispatcher.cs +++ b/src/Umbraco.Core/Events/QueuingEventDispatcher.cs @@ -35,6 +35,6 @@ namespace Umbraco.Core.Events private IMediaFileSystem _mediaFileSystem; // fixme inject - private IMediaFileSystem MediaFileSystem => _mediaFileSystem ?? (_mediaFileSystem = Current.FileSystems.MediaFileSystem); + private IMediaFileSystem MediaFileSystem => _mediaFileSystem ?? (_mediaFileSystem = Current.MediaFileSystem); } } diff --git a/src/Umbraco.Core/IO/FileSystemAttribute.cs b/src/Umbraco.Core/IO/FileSystemAttribute.cs deleted file mode 100644 index cf59d4e145..0000000000 --- a/src/Umbraco.Core/IO/FileSystemAttribute.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; - -namespace Umbraco.Core.IO -{ - /// - /// Decorates a filesystem. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] - public class FileSystemAttribute : Attribute - { - /// - /// Initializes a new instance of the class. - /// - /// - public FileSystemAttribute(string alias) - { - Alias = alias; - } - - /// - /// Gets the alias of the filesystem. - /// - public string Alias { get; } - } -} diff --git a/src/Umbraco.Core/IO/FileSystems.cs b/src/Umbraco.Core/IO/FileSystems.cs index 24e7a73172..29ea2c44b1 100644 --- a/src/Umbraco.Core/IO/FileSystems.cs +++ b/src/Umbraco.Core/IO/FileSystems.cs @@ -10,7 +10,6 @@ namespace Umbraco.Core.IO { public class FileSystems : IFileSystems { - private readonly ConcurrentSet _wrappers = new ConcurrentSet(); private readonly IContainer _container; private readonly ILogger _logger; @@ -23,13 +22,16 @@ namespace Umbraco.Core.IO private ShadowWrapper _scriptsFileSystem; private ShadowWrapper _masterPagesFileSystem; private ShadowWrapper _mvcViewsFileSystem; - + // well-known file systems lazy initialization private object _wkfsLock = new object(); private bool _wkfsInitialized; - private object _wkfsObject; + private object _wkfsObject; // unused - private MediaFileSystem _mediaFileSystem; + // shadow support + private readonly List _shadowWrappers = new List(); + private readonly object _shadowLocker = new object(); + private static Guid _shadowCurrentId = Guid.Empty; // static - unique!! #region Constructor @@ -43,11 +45,19 @@ namespace Umbraco.Core.IO // for tests only, totally unsafe internal void Reset() { - _wrappers.Clear(); + _shadowWrappers.Clear(); _filesystems.Clear(); Volatile.Write(ref _wkfsInitialized, false); + _shadowCurrentId = Guid.Empty; } + // for tests only, totally unsafe + internal static void ResetShadowId() + { + _shadowCurrentId = Guid.Empty; + } + + // set by the scope provider when taking control of filesystems internal Func IsScoped { get; set; } = () => false; #endregion @@ -114,16 +124,6 @@ namespace Umbraco.Core.IO } } - /// - public IMediaFileSystem MediaFileSystem - { - get - { - if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems(); - return _mediaFileSystem; - } - } - private void EnsureWellKnownFileSystems() { LazyInitializer.EnsureInitialized(ref _wkfsObject, ref _wkfsInitialized, ref _wkfsLock, CreateWellKnownFileSystems); @@ -140,15 +140,20 @@ namespace Umbraco.Core.IO var masterPagesFileSystem = new PhysicalFileSystem(SystemDirectories.Masterpages); var mvcViewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews); - _macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, "Views/MacroPartials", () => IsScoped()); - _partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, "Views/Partials", () => IsScoped()); - _stylesheetsFileSystem = new ShadowWrapper(stylesheetsFileSystem, "css", () => IsScoped()); - _scriptsFileSystem = new ShadowWrapper(scriptsFileSystem, "scripts", () => IsScoped()); - _masterPagesFileSystem = new ShadowWrapper(masterPagesFileSystem, "masterpages", () => IsScoped()); - _mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, "Views", () => IsScoped()); + _macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, "Views/MacroPartials", IsScoped); + _partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, "Views/Partials", IsScoped); + _stylesheetsFileSystem = new ShadowWrapper(stylesheetsFileSystem, "css", IsScoped); + _scriptsFileSystem = new ShadowWrapper(scriptsFileSystem, "scripts", IsScoped); + _masterPagesFileSystem = new ShadowWrapper(masterPagesFileSystem, "masterpages", IsScoped); + _mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, "Views", IsScoped); - // filesystems obtained from GetFileSystemProvider are already wrapped and do not need to be wrapped again - _mediaFileSystem = GetFileSystem(); + // fixme locking? + _shadowWrappers.Add(_macroPartialFileSystem); + _shadowWrappers.Add(_partialViewsFileSystem); + _shadowWrappers.Add(_stylesheetsFileSystem); + _shadowWrappers.Add(_scriptsFileSystem); + _shadowWrappers.Add(_masterPagesFileSystem); + _shadowWrappers.Add(_mvcViewsFileSystem); return null; } @@ -166,37 +171,22 @@ namespace Umbraco.Core.IO /// Note that any filesystem created by this method *after* shadowing begins, will *not* be /// shadowing (and an exception will be thrown by the ShadowWrapper). /// - public TFileSystem GetFileSystem() + public TFileSystem GetFileSystem(Func innerFileSystemFactory) where TFileSystem : FileSystemWrapper { - var alias = GetFileSystemAlias(); + if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems(); - // note: GetOrAdd can run multiple times - and here, since we have side effects - // (adding to _wrappers) we want to be sure the factory runs only once, hence the - // additional Lazy. - return (TFileSystem) _filesystems.GetOrAdd(alias, _ => new Lazy(() => + var name = typeof(TFileSystem).FullName; + if (name == null) throw new Exception("panic!"); + + return (TFileSystem) _filesystems.GetOrAdd(name, _ => new Lazy(() => { - var supportingFileSystem = _container.GetInstance(alias); - var shadowWrapper = new ShadowWrapper(supportingFileSystem, "typed/" + alias, () => IsScoped()); - - _wrappers.Add(shadowWrapper); // _wrappers is a concurrent set - this is safe - - return _container.CreateInstance(new { innerFileSystem = shadowWrapper}); + var innerFileSystem = innerFileSystemFactory(); + var shadowWrapper = CreateShadowWrapper(innerFileSystem, "typed/" + name); + return _container.CreateInstance(new { innerFileSystem = shadowWrapper }); })).Value; } - private string GetFileSystemAlias() - { - var fileSystemType = typeof(TFileSystem); - - // find the attribute and get the alias - var attr = (FileSystemAttribute) fileSystemType.GetCustomAttributes(typeof(FileSystemAttribute), false).SingleOrDefault(); - if (attr == null) - throw new InvalidOperationException("Type " + fileSystemType.FullName + "is missing the required FileSystemProviderAttribute."); - - return attr.Alias; - } - #endregion #region Shadow @@ -223,50 +213,70 @@ namespace Umbraco.Core.IO { if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems(); - var typed = _wrappers.ToArray(); - var wrappers = new ShadowWrapper[typed.Length + 6]; - var i = 0; - while (i < typed.Length) wrappers[i] = typed[i++]; - wrappers[i++] = _macroPartialFileSystem; - wrappers[i++] = _partialViewsFileSystem; - wrappers[i++] = _stylesheetsFileSystem; - wrappers[i++] = _scriptsFileSystem; - wrappers[i++] = _masterPagesFileSystem; - wrappers[i] = _mvcViewsFileSystem; + return new ShadowFileSystems(this, id); // will invoke BeginShadow and EndShadow + } - return new ShadowFileSystems(id, wrappers, _logger); + internal void BeginShadow(Guid id) + { + lock (_shadowLocker) + { + // if we throw here, it means that something very wrong happened. + if (_shadowCurrentId != Guid.Empty) + throw new InvalidOperationException("Already shadowing."); + _shadowCurrentId = id; + + _logger.Debug("Shadow '{ShadowId}'", id); + + foreach (var wrapper in _shadowWrappers) + wrapper.Shadow(id); + } + } + + internal void EndShadow(Guid id, bool completed) + { + lock (_shadowLocker) + { + // if we throw here, it means that something very wrong happened. + if (_shadowCurrentId == Guid.Empty) + throw new InvalidOperationException("Not shadowing."); + if (id != _shadowCurrentId) + throw new InvalidOperationException("Not the current shadow."); + + _logger.Debug("UnShadow '{ShadowId}' {Status}", id, completed ? "complete" : "abort"); + + var exceptions = new List(); + foreach (var wrapper in _shadowWrappers) + { + try + { + // this may throw an AggregateException if some of the changes could not be applied + wrapper.UnShadow(completed); + } + catch (AggregateException ae) + { + exceptions.Add(ae); + } + } + + _shadowCurrentId = Guid.Empty; + + if (exceptions.Count > 0) + throw new AggregateException(completed ? "Failed to apply all changes (see exceptions)." : "Failed to abort (see exceptions).", exceptions); + } + } + + private ShadowWrapper CreateShadowWrapper(IFileSystem filesystem, string shadowPath) + { + lock (_shadowLocker) + { + var wrapper = new ShadowWrapper(filesystem, shadowPath, IsScoped); + if (_shadowCurrentId != Guid.Empty) + wrapper.Shadow(_shadowCurrentId); + _shadowWrappers.Add(wrapper); + return wrapper; + } } #endregion - - private class ConcurrentSet - where T : class - { - private readonly HashSet _set = new HashSet(); - - public void Add(T item) - { - lock (_set) - { - _set.Add(item); - } - } - - public void Clear() - { - lock (_set) - { - _set.Clear(); - } - } - - public T[] ToArray() - { - lock (_set) - { - return _set.ToArray(); - } - } - } } } diff --git a/src/Umbraco.Core/IO/IFileSystems.cs b/src/Umbraco.Core/IO/IFileSystems.cs index 1af5377c43..d74ad48145 100644 --- a/src/Umbraco.Core/IO/IFileSystems.cs +++ b/src/Umbraco.Core/IO/IFileSystems.cs @@ -34,10 +34,5 @@ /// Gets the MVC views filesystem. /// IFileSystem MvcViewsFileSystem { get; } - - /// - /// Gets the media filesystem. - /// - IMediaFileSystem MediaFileSystem { get; } } } diff --git a/src/Umbraco.Core/IO/IMediaFileSystem.cs b/src/Umbraco.Core/IO/IMediaFileSystem.cs index e1957857bf..ed88516135 100644 --- a/src/Umbraco.Core/IO/IMediaFileSystem.cs +++ b/src/Umbraco.Core/IO/IMediaFileSystem.cs @@ -63,4 +63,4 @@ namespace Umbraco.Core.IO /// The filesystem-relative path to the copy of the media file. string CopyFile(IContentBase content, PropertyType propertyType, string sourcepath); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/IO/MediaFileSystem.cs b/src/Umbraco.Core/IO/MediaFileSystem.cs index c36a086bf8..867fe94c03 100644 --- a/src/Umbraco.Core/IO/MediaFileSystem.cs +++ b/src/Umbraco.Core/IO/MediaFileSystem.cs @@ -15,7 +15,6 @@ namespace Umbraco.Core.IO /// /// A custom file system provider for media /// - [FileSystem("media")] public class MediaFileSystem : FileSystemWrapper, IMediaFileSystem { /// diff --git a/src/Umbraco.Core/IO/ShadowFileSystems.cs b/src/Umbraco.Core/IO/ShadowFileSystems.cs index 80aa1791fd..bce0cc6df7 100644 --- a/src/Umbraco.Core/IO/ShadowFileSystems.cs +++ b/src/Umbraco.Core/IO/ShadowFileSystems.cs @@ -1,41 +1,26 @@ using System; -using System.Collections.Generic; -using Umbraco.Core.Logging; namespace Umbraco.Core.IO { + // shadow filesystems is definitively ... too convoluted + internal class ShadowFileSystems : ICompletable { - private static readonly object Locker = new object(); - private static Guid _currentId = Guid.Empty; - - private readonly Guid _id; - private readonly ShadowWrapper[] _wrappers; - private readonly ILogger _logger; - + private readonly FileSystems _fileSystems; private bool _completed; // invoked by the filesystems when shadowing - // can only be 1 shadow at a time (static) - public ShadowFileSystems(Guid id, ShadowWrapper[] wrappers, ILogger logger) + public ShadowFileSystems(FileSystems fileSystems, Guid id) { - lock (Locker) - { - // if we throw here, it means that something very wrong happened. - if (_currentId != Guid.Empty) - throw new InvalidOperationException("Already shadowing."); - _currentId = id; - } + _fileSystems = fileSystems; + Id = id; - _logger = logger; - _logger.Debug("Shadow '{ShadowId}'", id); - _id = id; - - _wrappers = wrappers; - foreach (var wrapper in _wrappers) - wrapper.Shadow(id); + _fileSystems.BeginShadow(id); } + // for tests + public Guid Id { get; } + // invoked by the scope when exiting, if completed public void Complete() { @@ -45,41 +30,7 @@ namespace Umbraco.Core.IO // invoked by the scope when exiting public void Dispose() { - lock (Locker) - { - // if we throw here, it means that something very wrong happened. - if (_currentId == Guid.Empty) - throw new InvalidOperationException("Not shadowing."); - if (_id != _currentId) - throw new InvalidOperationException("Not the current shadow."); - - _logger.Debug("UnShadow '{ShadowId}' {Status}", _id, _completed ? "complete" : "abort"); - - var exceptions = new List(); - foreach (var wrapper in _wrappers) - { - try - { - // this may throw an AggregateException if some of the changes could not be applied - wrapper.UnShadow(_completed); - } - catch (AggregateException ae) - { - exceptions.Add(ae); - } - } - - _currentId = Guid.Empty; - - if (exceptions.Count > 0) - throw new AggregateException(_completed ? "Failed to apply all changes (see exceptions)." : "Failed to abort (see exceptions).", exceptions); - } - } - - // for tests - internal static void ResetId() - { - _currentId = Guid.Empty; + _fileSystems.EndShadow(Id, _completed); } } } diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index 82e4935616..ad515f4197 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -100,7 +100,7 @@ namespace Umbraco.Core.Models } //use the custom avatar - var avatarUrl = Current.FileSystems.MediaFileSystem.GetUrl(user.Avatar); + var avatarUrl = Current.MediaFileSystem.GetUrl(user.Avatar); return new[] { avatarUrl + "?width=30&height=30&mode=crop", diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index e7d85472fb..352300333d 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -576,7 +576,6 @@ - diff --git a/src/Umbraco.Tests/IO/FileSystemsTests.cs b/src/Umbraco.Tests/IO/FileSystemsTests.cs index fe27229c72..4842b3feec 100644 --- a/src/Umbraco.Tests/IO/FileSystemsTests.cs +++ b/src/Umbraco.Tests/IO/FileSystemsTests.cs @@ -56,7 +56,7 @@ namespace Umbraco.Tests.IO [Test] public void Can_Get_MediaFileSystem() { - var fileSystem = FileSystems.GetFileSystem(); + var fileSystem = _container.GetInstance(); Assert.NotNull(fileSystem); } @@ -67,14 +67,6 @@ namespace Umbraco.Tests.IO Assert.NotNull(fileSystem); } - [Test] - public void MediaFileSystem_Is_Singleton() - { - var fileSystem1 = FileSystems.GetFileSystem(); - var fileSystem2 = FileSystems.GetFileSystem(); - Assert.AreSame(fileSystem1, fileSystem2); - } - [Test] public void IMediaFileSystem_Is_Singleton() { @@ -86,7 +78,7 @@ namespace Umbraco.Tests.IO [Test] public void Can_Delete_MediaFiles() { - var fs = FileSystems.GetFileSystem(); + var fs = _container.GetInstance(); var ms = new MemoryStream(Encoding.UTF8.GetBytes("test")); var virtPath = fs.GetMediaPath("file.txt", Guid.NewGuid(), Guid.NewGuid()); fs.AddFile(virtPath, ms); @@ -108,6 +100,9 @@ namespace Umbraco.Tests.IO Assert.IsTrue(Directory.Exists(physPath)); } + + // fixme - don't make sense anymore + /* [Test] public void Cannot_Get_InvalidFileSystem() { @@ -134,12 +129,13 @@ namespace Umbraco.Tests.IO { } } - [FileSystem("noconfig")] + [InnerFileSystem("noconfig")] internal class NonConfiguredFileSystem : FileSystemWrapper { public NonConfiguredFileSystem(IFileSystem innerFileSystem) : base(innerFileSystem) { } } + */ } } diff --git a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs index 90fdf3810d..304f7b1efb 100644 --- a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs +++ b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs @@ -1,10 +1,12 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Scoping; @@ -25,7 +27,7 @@ namespace Umbraco.Tests.IO { SafeCallContext.Clear(); ClearFiles(); - ShadowFileSystems.ResetId(); + FileSystems.ResetShadowId(); } [TearDown] @@ -33,7 +35,7 @@ namespace Umbraco.Tests.IO { SafeCallContext.Clear(); ClearFiles(); - ShadowFileSystems.ResetId(); + FileSystems.ResetShadowId(); } private static void ClearFiles() @@ -373,6 +375,13 @@ namespace Umbraco.Tests.IO Assert.IsFalse(File.Exists(path + "/ShadowTests/sub/sub/f2.txt")); } + class FS : FileSystemWrapper + { + public FS(IFileSystem innerFileSystem) + : base(innerFileSystem) + { } + } + [Test] public void ShadowScopeComplete() { @@ -385,13 +394,16 @@ namespace Umbraco.Tests.IO var scopedFileSystems = false; - var fs = new PhysicalFileSystem(path, "ignore"); - var sw = new ShadowWrapper(fs, "shadow", () => scopedFileSystems); - var swa = new[] { sw }; + var phy = new PhysicalFileSystem(path, "ignore"); + + var container = Mock.Of(); + var fileSystems = new FileSystems(container, logger) { IsScoped = () => scopedFileSystems }; + var fs = fileSystems.GetFileSystem(() => phy); + var sw = (ShadowWrapper) fs.InnerFileSystem; using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f1.txt", ms); - Assert.IsTrue(fs.FileExists("sub/f1.txt")); + Assert.IsTrue(phy.FileExists("sub/f1.txt")); Guid id; @@ -400,47 +412,55 @@ namespace Umbraco.Tests.IO Assert.IsTrue(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f2.txt", ms); - Assert.IsTrue(fs.FileExists("sub/f2.txt")); + Assert.IsTrue(phy.FileExists("sub/f2.txt")); sw.UnShadow(true); - Assert.IsTrue(fs.FileExists("sub/f2.txt")); + Assert.IsTrue(phy.FileExists("sub/f2.txt")); Assert.IsFalse(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); // shadow with scope but no complete does not complete scopedFileSystems = true; // pretend we have a scope - var scope = new ShadowFileSystems(id = Guid.NewGuid(), swa, logger); + var scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid()); Assert.IsTrue(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f3.txt", ms); - Assert.IsFalse(fs.FileExists("sub/f3.txt")); - Assert.AreEqual(1, Directory.GetDirectories(appdata + "/TEMP/ShadowFs").Length); + Assert.IsFalse(phy.FileExists("sub/f3.txt")); + var dirs = Directory.GetDirectories(appdata + "/TEMP/ShadowFs"); + Assert.AreEqual(1, dirs.Length); + Assert.AreEqual((appdata + "/TEMP/ShadowFs/" + id).Replace('\\', '/'), dirs[0].Replace('\\', '/')); + dirs = Directory.GetDirectories(dirs[0]); + var typedDir = dirs.FirstOrDefault(x => x.Replace('\\', '/').EndsWith("/typed")); + Assert.IsNotNull(typedDir); + dirs = Directory.GetDirectories(typedDir); + var scopedDir = dirs.FirstOrDefault(x => x.Replace('\\', '/').EndsWith("/Umbraco.Tests.IO.ShadowFileSystemTests+FS")); // this is where files go + Assert.IsNotNull(scopedDir); scope.Dispose(); scopedFileSystems = false; - Assert.IsFalse(fs.FileExists("sub/f3.txt")); + Assert.IsFalse(phy.FileExists("sub/f3.txt")); TestHelper.TryAssert(() => Assert.IsFalse(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id))); // shadow with scope and complete does complete scopedFileSystems = true; // pretend we have a scope - scope = new ShadowFileSystems(id = Guid.NewGuid(), swa, logger); + scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid()); Assert.IsTrue(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f4.txt", ms); - Assert.IsFalse(fs.FileExists("sub/f4.txt")); + Assert.IsFalse(phy.FileExists("sub/f4.txt")); Assert.AreEqual(1, Directory.GetDirectories(appdata + "/TEMP/ShadowFs").Length); scope.Complete(); scope.Dispose(); scopedFileSystems = false; TestHelper.TryAssert(() => Assert.AreEqual(0, Directory.GetDirectories(appdata + "/TEMP/ShadowFs").Length)); - Assert.IsTrue(fs.FileExists("sub/f4.txt")); + Assert.IsTrue(phy.FileExists("sub/f4.txt")); Assert.IsFalse(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); // test scope for "another thread" scopedFileSystems = true; // pretend we have a scope - scope = new ShadowFileSystems(id = Guid.NewGuid(), swa, logger); + scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid()); Assert.IsTrue(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f5.txt", ms); - Assert.IsFalse(fs.FileExists("sub/f5.txt")); + Assert.IsFalse(phy.FileExists("sub/f5.txt")); // pretend we're another thread w/out scope scopedFileSystems = false; @@ -448,11 +468,11 @@ namespace Umbraco.Tests.IO sw.AddFile("sub/f6.txt", ms); scopedFileSystems = true; // pretend we have a scope - Assert.IsTrue(fs.FileExists("sub/f6.txt")); // other thread has written out to fs + Assert.IsTrue(phy.FileExists("sub/f6.txt")); // other thread has written out to fs scope.Complete(); scope.Dispose(); scopedFileSystems = false; - Assert.IsTrue(fs.FileExists("sub/f5.txt")); + Assert.IsTrue(phy.FileExists("sub/f5.txt")); Assert.IsFalse(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); } @@ -467,22 +487,25 @@ namespace Umbraco.Tests.IO var scopedFileSystems = false; - var fs = new PhysicalFileSystem(path, "ignore"); - var sw = new ShadowWrapper(fs, "shadow", () => scopedFileSystems); - var swa = new[] { sw }; + var phy = new PhysicalFileSystem(path, "ignore"); + + var container = Mock.Of(); + var fileSystems = new FileSystems(container, logger) { IsScoped = () => scopedFileSystems }; + var fs = fileSystems.GetFileSystem(() => phy); + var sw = (ShadowWrapper) fs.InnerFileSystem; using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f1.txt", ms); - Assert.IsTrue(fs.FileExists("sub/f1.txt")); + Assert.IsTrue(phy.FileExists("sub/f1.txt")); Guid id; scopedFileSystems = true; // pretend we have a scope - var scope = new ShadowFileSystems(id = Guid.NewGuid(), swa, logger); + var scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid()); Assert.IsTrue(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f2.txt", ms); - Assert.IsFalse(fs.FileExists("sub/f2.txt")); + Assert.IsFalse(phy.FileExists("sub/f2.txt")); // pretend we're another thread w/out scope scopedFileSystems = false; @@ -490,15 +513,15 @@ namespace Umbraco.Tests.IO sw.AddFile("sub/f2.txt", ms); scopedFileSystems = true; // pretend we have a scope - Assert.IsTrue(fs.FileExists("sub/f2.txt")); // other thread has written out to fs + Assert.IsTrue(phy.FileExists("sub/f2.txt")); // other thread has written out to fs scope.Complete(); scope.Dispose(); scopedFileSystems = false; - Assert.IsTrue(fs.FileExists("sub/f2.txt")); + Assert.IsTrue(phy.FileExists("sub/f2.txt")); TestHelper.TryAssert(() => Assert.IsFalse(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id))); string text; - using (var s = fs.OpenFile("sub/f2.txt")) + using (var s = phy.OpenFile("sub/f2.txt")) using (var r = new StreamReader(s)) text = r.ReadToEnd(); @@ -517,22 +540,25 @@ namespace Umbraco.Tests.IO var scopedFileSystems = false; - var fs = new PhysicalFileSystem(path, "ignore"); - var sw = new ShadowWrapper(fs, "shadow", () => scopedFileSystems); - var swa = new[] { sw }; + var phy = new PhysicalFileSystem(path, "ignore"); + + var container = Mock.Of(); + var fileSystems = new FileSystems(container, logger) { IsScoped = () => scopedFileSystems }; + var fs = fileSystems.GetFileSystem(() => phy); + var sw = (ShadowWrapper)fs.InnerFileSystem; using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f1.txt", ms); - Assert.IsTrue(fs.FileExists("sub/f1.txt")); + Assert.IsTrue(phy.FileExists("sub/f1.txt")); Guid id; scopedFileSystems = true; // pretend we have a scope - var scope = new ShadowFileSystems(id = Guid.NewGuid(), swa, logger); + var scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid()); Assert.IsTrue(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f2.txt", ms); - Assert.IsFalse(fs.FileExists("sub/f2.txt")); + Assert.IsFalse(phy.FileExists("sub/f2.txt")); // pretend we're another thread w/out scope scopedFileSystems = false; @@ -540,11 +566,11 @@ namespace Umbraco.Tests.IO sw.AddFile("sub/f2.txt/f2.txt", ms); scopedFileSystems = true; // pretend we have a scope - Assert.IsTrue(fs.FileExists("sub/f2.txt/f2.txt")); // other thread has written out to fs + Assert.IsTrue(phy.FileExists("sub/f2.txt/f2.txt")); // other thread has written out to fs using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f3.txt", ms); - Assert.IsFalse(fs.FileExists("sub/f3.txt")); + Assert.IsFalse(phy.FileExists("sub/f3.txt")); scope.Complete(); @@ -570,7 +596,7 @@ namespace Umbraco.Tests.IO } // still, the rest of the changes has been applied ok - Assert.IsTrue(fs.FileExists("sub/f3.txt")); + Assert.IsTrue(phy.FileExists("sub/f3.txt")); } [Test] diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index f32e7c3046..bb9f176e0c 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -10,6 +10,7 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.Composing.Composers; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -40,9 +41,8 @@ namespace Umbraco.Tests.Models base.Compose(); Container.Register(_ => Mock.Of()); - Container.Register(); - Container.Register(factory => factory.GetInstance()); - Container.Register(factory => factory.GetInstance().MediaFileSystem); + Container.ComposeFileSystems(); + Container.Register(_ => Mock.Of()); Container.Register(_ => Mock.Of()); } diff --git a/src/Umbraco.Tests/Scoping/ScopeFileSystemsTests.cs b/src/Umbraco.Tests/Scoping/ScopeFileSystemsTests.cs index f095e30553..680eddf953 100644 --- a/src/Umbraco.Tests/Scoping/ScopeFileSystemsTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopeFileSystemsTests.cs @@ -37,7 +37,7 @@ namespace Umbraco.Tests.Scoping { base.TearDown(); SafeCallContext.Clear(); - ShadowFileSystems.ResetId(); + FileSystems.ResetShadowId(); ClearFiles(); } @@ -53,7 +53,7 @@ namespace Umbraco.Tests.Scoping public void CreateMediaTest(bool complete) { var physMediaFileSystem = new PhysicalFileSystem(IOHelper.MapPath("media"), "ignore"); - var mediaFileSystem = Current.FileSystems.MediaFileSystem; + var mediaFileSystem = Current.MediaFileSystem; Assert.IsFalse(physMediaFileSystem.FileExists("f1.txt")); @@ -72,12 +72,12 @@ namespace Umbraco.Tests.Scoping if (complete) { - Assert.IsTrue(Current.FileSystems.MediaFileSystem.FileExists("f1.txt")); + Assert.IsTrue(Current.MediaFileSystem.FileExists("f1.txt")); Assert.IsTrue(physMediaFileSystem.FileExists("f1.txt")); } else { - Assert.IsFalse(Current.FileSystems.MediaFileSystem.FileExists("f1.txt")); + Assert.IsFalse(Current.MediaFileSystem.FileExists("f1.txt")); Assert.IsFalse(physMediaFileSystem.FileExists("f1.txt")); } } @@ -86,7 +86,7 @@ namespace Umbraco.Tests.Scoping public void MultiThread() { var physMediaFileSystem = new PhysicalFileSystem(IOHelper.MapPath("media"), "ignore"); - var mediaFileSystem = Current.FileSystems.MediaFileSystem; + var mediaFileSystem = Current.MediaFileSystem; var scopeProvider = ScopeProvider; using (var scope = scopeProvider.CreateScope(scopeFileSystems: true)) diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs index 0a0d381570..06654a3519 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs @@ -150,9 +150,6 @@ namespace Umbraco.Tests.TestHelpers MockFs(fileSystems, x => x.ScriptsFileSystem); MockFs(fileSystems, x => x.StylesheetsFileSystem); - var mediaFs = new MediaFileSystem(Mock.Of()); - Mock.Get(fileSystems).Setup(x => x.MediaFileSystem).Returns(mediaFs); - return fileSystems; } diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 26c4c1e785..6104f365cf 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -297,7 +297,7 @@ namespace Umbraco.Tests.Testing // register filesystems Container.RegisterSingleton(factory => TestObjects.GetFileSystemsMock()); - Container.RegisterSingleton(factory => factory.GetInstance().MediaFileSystem); + Container.RegisterSingleton(factory => new MediaFileSystem(Mock.Of())); // no factory (noop) Container.RegisterSingleton(); diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index f7edd3de8f..dc5d45799f 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -105,7 +105,7 @@ namespace Umbraco.Web.Editors using (var fs = System.IO.File.OpenRead(file.LocalFileName)) { - Current.FileSystems.MediaFileSystem.AddFile(user.Avatar, fs, true); + Current.MediaFileSystem.AddFile(user.Avatar, fs, true); } userService.Save(user); @@ -146,8 +146,8 @@ namespace Umbraco.Web.Editors if (filePath.IsNullOrWhiteSpace() == false) { - if (Current.FileSystems.MediaFileSystem.FileExists(filePath)) - Current.FileSystems.MediaFileSystem.DeleteFile(filePath); + if (Current.MediaFileSystem.FileExists(filePath)) + Current.MediaFileSystem.DeleteFile(filePath); } return Request.CreateResponse(HttpStatusCode.OK, found.GetUserAvatarUrls(ApplicationCache.StaticCache));