Refactor filesystems

This commit is contained in:
Stephan
2018-10-26 15:06:53 +02:00
parent 1644434ea7
commit eee7222e0b
25 changed files with 380 additions and 466 deletions
@@ -1,28 +1,105 @@
using Umbraco.Core.IO;
using System;
using System.Configuration;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
namespace Umbraco.Core.Composing.Composers
{
public static class FileSystemsComposer
{
/*
* HOW TO REPLACE THE MEDIA UNDERLYING FILESYSTEM
* ----------------------------------------------
*
* Create a component and use it to modify the composition by adding something like:
*
* composition.Container.RegisterSingleton<IFileSystem>("media", factory => ...);
*
* where the ... part returns the new underlying filesystem, as an IFileSystem.
*
*
* HOW TO IMPLEMENT MY OWN FILESYSTEM
* ----------------------------------
*
* Declare your filesystem interface:
*
* public interface IMyFileSystem : IFileSystem
* { }
*
* Create your filesystem class:
*
* [FileSystem("my")]
* public class MyFileSystem : FileSystemWrapper, IFormsFileSystem
* {
* public FormsFileSystem(IFileSystem innerFileSystem)
* : base(innerFileSystem)
* { }
* }
*
* Register both the underlying filesystem, and your filesystem, in a component:
*
* composition.Container.RegisterSingleton<IFileSystem>("my", factory => ...);
* composition.Container.RegisterSingleton<IMyFileSystem>(factory =>
* factory.GetInstance<FileSystems>().GetFileSystem<MyFileSystem>();
*
* And that's it, you can inject IMyFileSystem wherever it's needed.
*
*
* WHAT IS SHADOWING
* -----------------
*
* Shadowing is the technology used for Deploy to implement some sort of
* transaction-management on top of filesystems. The plumbing explained above,
* compared to creating your own physical filesystem, ensures that your filesystem
* would participate into such transactions.
*
* Also note that in order for things to work correctly, all filesystems should
* be instantiated before shadowing - so if registering a new filesystem in a
* component, it's a good idea to initialize it. This would be enough (in the
* component):
*
* public void Initialize(IMyFileSystem fs)
* { }
*
*
*/
public static IContainer ComposeFileSystems(this IContainer container)
{
// register FileSystems, which manages all filesystems
container.RegisterSingleton<FileSystems>();
// it needs to be registered (not only the interface) because it provides additional
// functionality eg for scoping, and is injected in the scope provider - whereas the
// interface is really for end-users to get access to filesystems.
//container.RegisterSingleton<FileSystems>();
container.RegisterSingleton(factory => factory.CreateInstance<FileSystems>(new { container} ));
// register IFileSystems, which gives access too all filesystems
container.RegisterSingleton<IFileSystems>(factory => factory.GetInstance<FileSystems>());
// fixme - review registering mediafilesystem. it seems to create cyclic dependencies for castle.
// let's try naming it so the default is overwritten...
// register MediaFileSystem, which can be injected directly
// note: the actual MediaFileSystem implementation is created by FileSystems directly,
// without being registered in the container - this just gives access to it
container.Register(f => f.GetInstance<FileSystems>().MediaFileSystem);
// register IMediaFileSystem
var virtualRoot = GetMediaFileSystemVirtualRoot();
container.RegisterSingleton<IFileSystem>("media", factory => new PhysicalFileSystem(virtualRoot));
container.RegisterSingleton<IMediaFileSystem>(factory => factory.GetInstance<FileSystems>().GetFileSystem<MediaFileSystem>());
return container;
}
private static string GetMediaFileSystemVirtualRoot()
{
// for the time being, we still use the FileSystemProvider config file
// but, detect if ppl are trying to use it to change the "provider"
var virtualRoot = "~/media";
var config = (FileSystemProvidersSection)ConfigurationManager.GetSection("umbracoConfiguration/FileSystemProviders");
var p = config?.Providers["media"];
if (p == null) return 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;
}
}
}
+2 -2
View File
@@ -18,8 +18,8 @@ namespace Umbraco.Core
public static class ContentExtensions
{
// this ain't pretty
private static MediaFileSystem _mediaFileSystem;
private static MediaFileSystem MediaFileSystem => _mediaFileSystem ?? (_mediaFileSystem = Current.FileSystems.MediaFileSystem);
private static IMediaFileSystem _mediaFileSystem;
private static IMediaFileSystem MediaFileSystem => _mediaFileSystem ?? (_mediaFileSystem = Current.FileSystems.MediaFileSystem);
#region IContent
@@ -32,9 +32,9 @@ namespace Umbraco.Core.Events
}
}
private MediaFileSystem _mediaFileSystem;
private IMediaFileSystem _mediaFileSystem;
// fixme inject
private MediaFileSystem MediaFileSystem => _mediaFileSystem ?? (_mediaFileSystem = Current.FileSystems.MediaFileSystem);
private IMediaFileSystem MediaFileSystem => _mediaFileSystem ?? (_mediaFileSystem = Current.FileSystems.MediaFileSystem);
}
}
@@ -0,0 +1,25 @@
using System;
namespace Umbraco.Core.IO
{
/// <summary>
/// Decorates a filesystem.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class FileSystemAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="FileSystemAttribute"/> class.
/// </summary>
/// <param name="alias"></param>
public FileSystemAttribute(string alias)
{
Alias = alias;
}
/// <summary>
/// Gets the alias of the filesystem.
/// </summary>
public string Alias { get; }
}
}
@@ -1,19 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Umbraco.Core.CodeAnnotations;
namespace Umbraco.Core.IO
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class FileSystemProviderAttribute : Attribute
{
public string Alias { get; private set; }
public FileSystemProviderAttribute(string alias)
{
Alias = alias;
}
}
}
+22 -22
View File
@@ -16,103 +16,103 @@ namespace Umbraco.Core.IO
/// </remarks>
public abstract class FileSystemWrapper : IFileSystem
{
protected FileSystemWrapper(IFileSystem wrapped)
protected FileSystemWrapper(IFileSystem innerFileSystem)
{
Wrapped = wrapped;
InnerFileSystem = innerFileSystem;
}
internal IFileSystem Wrapped { get; set; }
internal IFileSystem InnerFileSystem { get; set; }
public IEnumerable<string> GetDirectories(string path)
{
return Wrapped.GetDirectories(path);
return InnerFileSystem.GetDirectories(path);
}
public void DeleteDirectory(string path)
{
Wrapped.DeleteDirectory(path);
InnerFileSystem.DeleteDirectory(path);
}
public void DeleteDirectory(string path, bool recursive)
{
Wrapped.DeleteDirectory(path, recursive);
InnerFileSystem.DeleteDirectory(path, recursive);
}
public bool DirectoryExists(string path)
{
return Wrapped.DirectoryExists(path);
return InnerFileSystem.DirectoryExists(path);
}
public void AddFile(string path, Stream stream)
{
Wrapped.AddFile(path, stream);
InnerFileSystem.AddFile(path, stream);
}
public void AddFile(string path, Stream stream, bool overrideExisting)
{
Wrapped.AddFile(path, stream, overrideExisting);
InnerFileSystem.AddFile(path, stream, overrideExisting);
}
public IEnumerable<string> GetFiles(string path)
{
return Wrapped.GetFiles(path);
return InnerFileSystem.GetFiles(path);
}
public IEnumerable<string> GetFiles(string path, string filter)
{
return Wrapped.GetFiles(path, filter);
return InnerFileSystem.GetFiles(path, filter);
}
public Stream OpenFile(string path)
{
return Wrapped.OpenFile(path);
return InnerFileSystem.OpenFile(path);
}
public void DeleteFile(string path)
{
Wrapped.DeleteFile(path);
InnerFileSystem.DeleteFile(path);
}
public bool FileExists(string path)
{
return Wrapped.FileExists(path);
return InnerFileSystem.FileExists(path);
}
public string GetRelativePath(string fullPathOrUrl)
{
return Wrapped.GetRelativePath(fullPathOrUrl);
return InnerFileSystem.GetRelativePath(fullPathOrUrl);
}
public string GetFullPath(string path)
{
return Wrapped.GetFullPath(path);
return InnerFileSystem.GetFullPath(path);
}
public string GetUrl(string path)
{
return Wrapped.GetUrl(path);
return InnerFileSystem.GetUrl(path);
}
public DateTimeOffset GetLastModified(string path)
{
return Wrapped.GetLastModified(path);
return InnerFileSystem.GetLastModified(path);
}
public DateTimeOffset GetCreated(string path)
{
return Wrapped.GetCreated(path);
return InnerFileSystem.GetCreated(path);
}
public long GetSize(string path)
{
return Wrapped.GetSize(path);
return InnerFileSystem.GetSize(path);
}
public bool CanAddPhysical => Wrapped.CanAddPhysical;
public bool CanAddPhysical => InnerFileSystem.CanAddPhysical;
public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false)
{
Wrapped.AddFile(path, physicalPath, overrideIfExists, copy);
InnerFileSystem.AddFile(path, physicalPath, overrideIfExists, copy);
}
}
}
+33 -158
View File
@@ -1,36 +1,20 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Composing;
namespace Umbraco.Core.IO
{
public interface IFileSystems // fixme move!
{
IFileSystem MacroPartialsFileSystem { get; }
IFileSystem PartialViewsFileSystem { get; }
IFileSystem StylesheetsFileSystem { get; }
IFileSystem ScriptsFileSystem { get; }
IFileSystem MasterPagesFileSystem { get; }
IFileSystem MvcViewsFileSystem { get; }
MediaFileSystem MediaFileSystem { get; }
}
public class FileSystems : IFileSystems
{
private readonly IFileSystemProvidersSection _config;
private readonly ConcurrentSet<ShadowWrapper> _wrappers = new ConcurrentSet<ShadowWrapper>();
private readonly IContainer _container;
private readonly ILogger _logger;
private readonly ConcurrentDictionary<string, ProviderConstructionInfo> _providerLookup = new ConcurrentDictionary<string, ProviderConstructionInfo>();
private readonly ConcurrentDictionary<string, IFileSystem> _filesystems = new ConcurrentDictionary<string, IFileSystem>();
private readonly ConcurrentDictionary<string, Lazy<IFileSystem>> _filesystems = new ConcurrentDictionary<string, Lazy<IFileSystem>>();
// wrappers for shadow support
private ShadowWrapper _macroPartialFileSystem;
@@ -50,11 +34,9 @@ namespace Umbraco.Core.IO
#region Constructor
// DI wants a public ctor
// but IScopeProviderInternal is not public
public FileSystems(ILogger logger)
public FileSystems(IContainer container, ILogger logger)
{
// fixme inject config section => can be used by tests
_config = (FileSystemProvidersSection) ConfigurationManager.GetSection("umbracoConfiguration/FileSystemProviders");
_container = container;
_logger = logger;
}
@@ -62,7 +44,6 @@ namespace Umbraco.Core.IO
internal void Reset()
{
_wrappers.Clear();
_providerLookup.Clear();
_filesystems.Clear();
Volatile.Write(ref _wkfsInitialized, false);
}
@@ -73,6 +54,7 @@ namespace Umbraco.Core.IO
#region Well-Known FileSystems
/// <inheritdoc />
public IFileSystem MacroPartialsFileSystem
{
get
@@ -82,6 +64,7 @@ namespace Umbraco.Core.IO
}
}
/// <inheritdoc />
public IFileSystem PartialViewsFileSystem
{
get
@@ -91,6 +74,7 @@ namespace Umbraco.Core.IO
}
}
/// <inheritdoc />
public IFileSystem StylesheetsFileSystem
{
get
@@ -100,6 +84,7 @@ namespace Umbraco.Core.IO
}
}
/// <inheritdoc />
public IFileSystem ScriptsFileSystem
{
get
@@ -108,16 +93,18 @@ namespace Umbraco.Core.IO
return _scriptsFileSystem;
}
}
/// <inheritdoc />
public IFileSystem MasterPagesFileSystem
{
get
{
if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems();
return _masterPagesFileSystem;// fixme - see 7.6?!
return _masterPagesFileSystem;
}
}
/// <inheritdoc />
public IFileSystem MvcViewsFileSystem
{
get
@@ -127,7 +114,8 @@ namespace Umbraco.Core.IO
}
}
public MediaFileSystem MediaFileSystem
/// <inheritdoc />
public IMediaFileSystem MediaFileSystem
{
get
{
@@ -160,7 +148,7 @@ namespace Umbraco.Core.IO
_mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, "Views", () => IsScoped());
// filesystems obtained from GetFileSystemProvider are already wrapped and do not need to be wrapped again
_mediaFileSystem = GetFileSystemProvider<MediaFileSystem>();
_mediaFileSystem = GetFileSystem<MediaFileSystem>();
return null;
}
@@ -169,155 +157,42 @@ namespace Umbraco.Core.IO
#region Providers
/// <summary>
/// used to cache the lookup of how to construct this object so we don't have to reflect each time.
/// </summary>
private class ProviderConstructionInfo
{
public object[] Parameters { get; set; }
public ConstructorInfo Constructor { get; set; }
//public string ProviderAlias { get; set; }
}
/// <summary>
/// Gets an underlying (non-typed) filesystem supporting a strongly-typed filesystem.
/// </summary>
/// <param name="alias">The alias of the strongly-typed filesystem.</param>
/// <returns>The non-typed filesystem supporting the strongly-typed filesystem with the specified alias.</returns>
/// <remarks>This method should not be used directly, used <see cref="GetFileSystemProvider{TFileSystem}()"/> instead.</remarks>
internal IFileSystem GetUnderlyingFileSystemProvider(string alias)
{
return GetUnderlyingFileSystemProvider(alias, null);
}
/// <summary>
/// Gets an underlying (non-typed) filesystem supporting a strongly-typed filesystem.
/// </summary>
/// <param name="alias">The alias of the strongly-typed filesystem.</param>
/// <param name="fallback">A fallback creator for the filesystem.</param>
/// <returns>The non-typed filesystem supporting the strongly-typed filesystem with the specified alias.</returns>
/// <remarks>This method should not be used directly, used <see cref="GetFileSystem{TFileSystem}"/> instead.</remarks>
internal IFileSystem GetUnderlyingFileSystemProvider(string alias, Func<IFileSystem> fallback)
{
// either get the constructor info from cache or create it and add to cache
var ctorInfo = _providerLookup.GetOrAdd(alias, _ => GetUnderlyingFileSystemCtor(alias, fallback));
return ctorInfo == null ? fallback() : (IFileSystem) ctorInfo.Constructor.Invoke(ctorInfo.Parameters);
}
private IFileSystem GetUnderlyingFileSystemNoCache(string alias, Func<IFileSystem> fallback)
{
var ctorInfo = GetUnderlyingFileSystemCtor(alias, fallback);
return ctorInfo == null ? fallback() : (IFileSystem) ctorInfo.Constructor.Invoke(ctorInfo.Parameters);
}
private ProviderConstructionInfo GetUnderlyingFileSystemCtor(string alias, Func<IFileSystem> fallback)
{
// get config
if (_config.Providers.TryGetValue(alias, out var providerConfig) == false)
{
if (fallback != null) return null;
throw new ArgumentException($"No provider found with alias {alias}.");
}
// get the filesystem type
var providerType = Type.GetType(providerConfig.Type);
if (providerType == null)
throw new InvalidOperationException($"Could not find type {providerConfig.Type}.");
// ensure it implements IFileSystem
if (providerType.IsAssignableFrom(typeof (IFileSystem)))
throw new InvalidOperationException($"Type {providerType.FullName} does not implement IFileSystem.");
// find a ctor matching the config parameters
var paramCount = providerConfig.Parameters?.Count ?? 0;
var constructor = providerType.GetConstructors().SingleOrDefault(x
=> x.GetParameters().Length == paramCount && x.GetParameters().All(y => providerConfig.Parameters.Keys.Contains(y.Name)));
if (constructor == null)
throw new InvalidOperationException($"Type {providerType.FullName} has no ctor matching the {paramCount} configuration parameter(s).");
var parameters = new object[paramCount];
if (providerConfig.Parameters != null) // keeps ReSharper happy
{
var allKeys = providerConfig.Parameters.Keys.ToArray();
for (var i = 0; i < paramCount; i++)
parameters[i] = providerConfig.Parameters[allKeys[i]];
}
return new ProviderConstructionInfo
{
Constructor = constructor,
Parameters = parameters,
//ProviderAlias = s
};
}
/// <summary>
/// Gets a strongly-typed filesystem.
/// </summary>
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
/// <returns>A strongly-typed filesystem of the specified type.</returns>
/// <remarks>
/// <para>Ideally, this should cache the instances, but that would break backward compatibility, so we
/// only do it for our own MediaFileSystem - for everything else, it's the responsibility of the caller
/// to ensure that they maintain singletons. This is important for singletons, as each filesystem maintains
/// its own shadow and having multiple instances would lead to inconsistencies.</para>
/// <para>Note that any filesystem created by this method *after* shadowing begins, will *not* be
/// shadowing (and an exception will be thrown by the ShadowWrapper).</para>
/// </remarks>
// fixme - should it change for v8?
public TFileSystem GetFileSystemProvider<TFileSystem>()
where TFileSystem : FileSystemWrapper
{
return GetFileSystemProvider<TFileSystem>(null);
}
/// <summary>
/// Gets a strongly-typed filesystem.
/// </summary>
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
/// <param name="fallback">A fallback creator for the inner filesystem.</param>
/// <returns>A strongly-typed filesystem of the specified type.</returns>
/// <remarks>
/// <para>The fallback creator is used only if nothing is configured.</para>
/// <para>Ideally, this should cache the instances, but that would break backward compatibility, so we
/// only do it for our own MediaFileSystem - for everything else, it's the responsibility of the caller
/// to ensure that they maintain singletons. This is important for singletons, as each filesystem maintains
/// its own shadow and having multiple instances would lead to inconsistencies.</para>
/// <para>Note that any filesystem created by this method *after* shadowing begins, will *not* be
/// shadowing (and an exception will be thrown by the ShadowWrapper).</para>
/// </remarks>
public TFileSystem GetFileSystemProvider<TFileSystem>(Func<IFileSystem> fallback)
public TFileSystem GetFileSystem<TFileSystem>()
where TFileSystem : FileSystemWrapper
{
var alias = GetFileSystemAlias<TFileSystem>();
return (TFileSystem)_filesystems.GetOrAdd(alias, _ =>
{
// gets the inner fs, create the strongly-typed fs wrapping the inner fs, register & return
// so we are double-wrapping here
// could be optimized by having FileSystemWrapper inherit from ShadowWrapper, maybe
var innerFs = GetUnderlyingFileSystemNoCache(alias, fallback);
var shadowWrapper = new ShadowWrapper(innerFs, "typed/" + alias, () => IsScoped());
var fs = (IFileSystem)Activator.CreateInstance(typeof(TFileSystem), shadowWrapper);
_wrappers.Add(shadowWrapper); // keeping a reference to the wrapper
return fs;
});
// 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<IFileSystem>(() =>
{
var supportingFileSystem = _container.GetInstance<IFileSystem>(alias);
var shadowWrapper = new ShadowWrapper(supportingFileSystem, "typed/" + alias, () => IsScoped());
_wrappers.Add(shadowWrapper); // _wrappers is a concurrent set - this is safe
return _container.CreateInstance<TFileSystem>(new { innerFileSystem = shadowWrapper});
})).Value;
}
private string GetFileSystemAlias<TFileSystem>()
{
var fsType = typeof(TFileSystem);
// validate the ctor
var constructor = fsType.GetConstructors().SingleOrDefault(x
=> x.GetParameters().Length >= 1 && TypeHelper.IsTypeAssignableFrom<IFileSystem>(x.GetParameters().First().ParameterType));
if (constructor == null)
throw new InvalidOperationException("Type " + fsType.FullName + " must inherit from FileSystemWrapper and have a constructor that accepts one parameter of type " + typeof(IFileSystem).FullName + ".");
var fileSystemType = typeof(TFileSystem);
// find the attribute and get the alias
var attr = (FileSystemProviderAttribute)fsType.GetCustomAttributes(typeof(FileSystemProviderAttribute), false).SingleOrDefault();
var attr = (FileSystemAttribute) fileSystemType.GetCustomAttributes(typeof(FileSystemAttribute), false).SingleOrDefault();
if (attr == null)
throw new InvalidOperationException("Type " + fsType.FullName + "is missing the required FileSystemProviderAttribute.");
throw new InvalidOperationException("Type " + fileSystemType.FullName + "is missing the required FileSystemProviderAttribute.");
return attr.Alias;
}
@@ -334,9 +209,9 @@ namespace Umbraco.Core.IO
// be created directly (via ctor) or via GetFileSystem<T> is *not* shadowed.
// shadow must be enabled in an app event handler before anything else ie before any filesystem
// is actually created and used - after, it is too late - enabling shadow has a neglictible perfs
// is actually created and used - after, it is too late - enabling shadow has a negligible perfs
// impact.
// NO! by the time an app event handler is instanciated it is already too late, see note in ctor.
// NO! by the time an app event handler is instantiated it is already too late, see note in ctor.
//internal void EnableShadow()
//{
// if (_mvcViewsFileSystem != null) // test one of the fs...
+1 -1
View File
@@ -5,7 +5,7 @@ using System.IO;
namespace Umbraco.Core.IO
{
/// <summary>
/// Provides methods allowing the manipulation of files within an Umbraco application.
/// Provides methods allowing the manipulation of files.
/// </summary>
public interface IFileSystem
{
+43
View File
@@ -0,0 +1,43 @@
namespace Umbraco.Core.IO
{
/// <summary>
/// Provides the system filesystems.
/// </summary>
public interface IFileSystems
{
/// <summary>
/// Gets the macro partials filesystem.
/// </summary>
IFileSystem MacroPartialsFileSystem { get; }
/// <summary>
/// Gets the partial views filesystem.
/// </summary>
IFileSystem PartialViewsFileSystem { get; }
/// <summary>
/// Gets the stylesheets filesystem.
/// </summary>
IFileSystem StylesheetsFileSystem { get; }
/// <summary>
/// Gets the scripts filesystem.
/// </summary>
IFileSystem ScriptsFileSystem { get; }
/// <summary>
/// Gets the masterpages filesystem.
/// </summary>
IFileSystem MasterPagesFileSystem { get; }
/// <summary>
/// Gets the MVC views filesystem.
/// </summary>
IFileSystem MvcViewsFileSystem { get; }
/// <summary>
/// Gets the media filesystem.
/// </summary>
IMediaFileSystem MediaFileSystem { get; }
}
}
+66
View File
@@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.IO;
using Umbraco.Core.Models;
namespace Umbraco.Core.IO
{
/// <summary>
/// Provides methods allowing the manipulation of media files.
/// </summary>
public interface IMediaFileSystem : IFileSystem
{
/// <summary>
/// Delete media files.
/// </summary>
/// <param name="files">Files to delete (filesystem-relative paths).</param>
void DeleteMediaFiles(IEnumerable<string> files);
/// <summary>
/// Gets the file path of a media file.
/// </summary>
/// <param name="filename">The file name.</param>
/// <param name="cuid">The unique identifier of the content/media owning the file.</param>
/// <param name="puid">The unique identifier of the property type owning the file.</param>
/// <returns>The filesystem-relative path to the media file.</returns>
/// <remarks>With the old media path scheme, this CREATES a new media path each time it is invoked.</remarks>
string GetMediaPath(string filename, Guid cuid, Guid puid);
/// <summary>
/// Gets the file path of a media file.
/// </summary>
/// <param name="filename">The file name.</param>
/// <param name="prevpath">A previous file path.</param>
/// <param name="cuid">The unique identifier of the content/media owning the file.</param>
/// <param name="puid">The unique identifier of the property type owning the file.</param>
/// <returns>The filesystem-relative path to the media file.</returns>
/// <remarks>In the old, legacy, number-based scheme, we try to re-use the media folder
/// specified by <paramref name="prevpath"/>. Else, we CREATE a new one. Each time we are invoked.</remarks>
string GetMediaPath(string filename, string prevpath, Guid cuid, Guid puid);
/// <summary>
/// Stores a media file associated to a property of a content item.
/// </summary>
/// <param name="content">The content item owning the media file.</param>
/// <param name="propertyType">The property type owning the media file.</param>
/// <param name="filename">The media file name.</param>
/// <param name="filestream">A stream containing the media bytes.</param>
/// <param name="oldpath">An optional filesystem-relative filepath to the previous media file.</param>
/// <returns>The filesystem-relative filepath to the media file.</returns>
/// <remarks>
/// <para>The file is considered "owned" by the content/propertyType.</para>
/// <para>If an <paramref name="oldpath"/> is provided then that file (and associated thumbnails if any) is deleted
/// before the new file is saved, and depending on the media path scheme, the folder may be reused for the new file.</para>
/// </remarks>
string StoreFile(IContentBase content, PropertyType propertyType, string filename, Stream filestream, string oldpath);
/// <summary>
/// Copies a media file as a new media file, associated to a property of a content item.
/// </summary>
/// <param name="content">The content item owning the copy of the media file.</param>
/// <param name="propertyType">The property type owning the copy of the media file.</param>
/// <param name="sourcepath">The filesystem-relative path to the source media file.</param>
/// <returns>The filesystem-relative path to the copy of the media file.</returns>
string CopyFile(IContentBase content, PropertyType propertyType, string sourcepath);
}
}
+18 -95
View File
@@ -15,11 +15,14 @@ namespace Umbraco.Core.IO
/// <summary>
/// A custom file system provider for media
/// </summary>
[FileSystemProvider("media")]
public class MediaFileSystem : FileSystemWrapper
[FileSystem("media")]
public class MediaFileSystem : FileSystemWrapper, IMediaFileSystem
{
public MediaFileSystem(IFileSystem wrapped)
: base(wrapped)
/// <summary>
/// Initializes a new instance of the <see cref="MediaFileSystem"/> class.
/// </summary>
public MediaFileSystem(IFileSystem innerFileSystem)
: base(innerFileSystem)
{
ContentConfig = Current.Container.GetInstance<IContentSection>();
Logger = Current.Container.GetInstance<ILogger>();
@@ -32,58 +35,16 @@ namespace Umbraco.Core.IO
private IContentSection ContentConfig { get; }
private ILogger Logger { get; }
/// <summary>
/// Deletes all files passed in.
/// </summary>
/// <param name="files"></param>
/// <param name="onError"></param>
/// <returns></returns>
internal bool DeleteFiles(IEnumerable<string> files, Action<string, Exception> onError = null)
{
//ensure duplicates are removed
files = files.Distinct();
var allsuccess = true;
var rootRelativePath = GetRelativePath("/");
Parallel.ForEach(files, file =>
{
try
{
if (file.IsNullOrWhiteSpace()) return;
var relativeFilePath = GetRelativePath(file);
if (FileExists(relativeFilePath) == false) return;
var parentDirectory = Path.GetDirectoryName(relativeFilePath);
// don't want to delete the media folder if not using directories.
if (ContentConfig.UploadAllowDirectories && parentDirectory != rootRelativePath)
{
//issue U4-771: if there is a parent directory the recursive parameter should be true
DeleteDirectory(parentDirectory, string.IsNullOrEmpty(parentDirectory) == false);
}
else
{
DeleteFile(file);
}
}
catch (Exception e)
{
onError?.Invoke(file, e);
allsuccess = false;
}
});
return allsuccess;
}
/// <inheritoc />
public void DeleteMediaFiles(IEnumerable<string> files)
{
files = files.Distinct();
Parallel.ForEach(files, file =>
// kinda try to keep things under control
var options = new ParallelOptions { MaxDegreeOfParallelism = 20 };
Parallel.ForEach(files, options, file =>
{
try
{
@@ -97,21 +58,14 @@ namespace Umbraco.Core.IO
}
catch (Exception e)
{
Logger.Error<MediaFileSystem>(e, "Failed to delete attached file '{File}'", file);
Logger.Error<MediaFileSystem>(e, "Failed to delete media file '{File}'.", file);
}
});
}
#region Media Path
/// <summary>
/// Gets the file path of a media file.
/// </summary>
/// <param name="filename">The file name.</param>
/// <param name="cuid">The unique identifier of the content/media owning the file.</param>
/// <param name="puid">The unique identifier of the property type owning the file.</param>
/// <returns>The filesystem-relative path to the media file.</returns>
/// <remarks>With the old media path scheme, this CREATES a new media path each time it is invoked.</remarks>
/// <inheritoc />
public string GetMediaPath(string filename, Guid cuid, Guid puid)
{
filename = Path.GetFileName(filename);
@@ -121,16 +75,7 @@ namespace Umbraco.Core.IO
return MediaPathScheme.GetFilePath(cuid, puid, filename);
}
/// <summary>
/// Gets the file path of a media file.
/// </summary>
/// <param name="filename">The file name.</param>
/// <param name="prevpath">A previous file path.</param>
/// <param name="cuid">The unique identifier of the content/media owning the file.</param>
/// <param name="puid">The unique identifier of the property type owning the file.</param>
/// <returns>The filesystem-relative path to the media file.</returns>
/// <remarks>In the old, legacy, number-based scheme, we try to re-use the media folder
/// specified by <paramref name="prevpath"/>. Else, we CREATE a new one. Each time we are invoked.</remarks>
/// <inheritoc />
public string GetMediaPath(string filename, string prevpath, Guid cuid, Guid puid)
{
filename = Path.GetFileName(filename);
@@ -144,20 +89,7 @@ namespace Umbraco.Core.IO
#region Associated Media Files
/// <summary>
/// Stores a media file associated to a property of a content item.
/// </summary>
/// <param name="content">The content item owning the media file.</param>
/// <param name="propertyType">The property type owning the media file.</param>
/// <param name="filename">The media file name.</param>
/// <param name="filestream">A stream containing the media bytes.</param>
/// <param name="oldpath">An optional filesystem-relative filepath to the previous media file.</param>
/// <returns>The filesystem-relative filepath to the media file.</returns>
/// <remarks>
/// <para>The file is considered "owned" by the content/propertyType.</para>
/// <para>If an <paramref name="oldpath"/> is provided then that file (and associated thumbnails if any) is deleted
/// before the new file is saved, and depending on the media path scheme, the folder may be reused for the new file.</para>
/// </remarks>
/// <inheritoc />
public string StoreFile(IContentBase content, PropertyType propertyType, string filename, Stream filestream, string oldpath)
{
if (content == null) throw new ArgumentNullException(nameof(content));
@@ -176,13 +108,7 @@ namespace Umbraco.Core.IO
return filepath;
}
/// <summary>
/// Copies a media file as a new media file, associated to a property of a content item.
/// </summary>
/// <param name="content">The content item owning the copy of the media file.</param>
/// <param name="propertyType">The property type owning the copy of the media file.</param>
/// <param name="sourcepath">The filesystem-relative path to the source media file.</param>
/// <returns>The filesystem-relative path to the copy of the media file.</returns>
/// <inheritoc />
public string CopyFile(IContentBase content, PropertyType propertyType, string sourcepath)
{
if (content == null) throw new ArgumentNullException(nameof(content));
@@ -199,9 +125,6 @@ namespace Umbraco.Core.IO
return filepath;
}
#endregion
#endregion
}
}
+11 -84
View File
@@ -6,19 +6,6 @@ namespace Umbraco.Core.IO
{
internal class ShadowFileSystems : ICompletable
{
// note: taking a reference to the _manager instead of using manager.Current
// to avoid using Current everywhere but really, we support only 1 scope at
// a time, not multiple scopes in case of multiple managers (not supported)
// fixme - why are we managing logical call context here? should be bound
// to the current scope, always => REFACTOR! but there should be something in
// place (static?) to ensure we only have one concurrent shadow FS?
//
// => yes, that's _currentId - need to cleanup this entirely
// and, we probably need a way to stop shadowing entirely without cycling the app
private const string ItemKey = "Umbraco.Core.IO.ShadowFileSystems";
private static readonly object Locker = new object();
private static Guid _currentId = Guid.Empty;
@@ -28,26 +15,13 @@ namespace Umbraco.Core.IO
private bool _completed;
//static ShadowFileSystems()
//{
// SafeCallContext.Register(
// () =>
// {
// var scope = CallContext.LogicalGetData(ItemKey);
// CallContext.FreeNamedDataSlot(ItemKey);
// return scope;
// },
// o =>
// {
// if (CallContext.LogicalGetData(ItemKey) != null) throw new InvalidOperationException();
// if (o != null) CallContext.LogicalSetData(ItemKey, o);
// });
//}
// invoked by the filesystems when shadowing
// can only be 1 shadow at a time (static)
public ShadowFileSystems(Guid id, ShadowWrapper[] wrappers, ILogger logger)
{
lock (Locker)
{
// if we throw here, it means that something very wrong happened.
if (_currentId != Guid.Empty)
throw new InvalidOperationException("Already shadowing.");
_currentId = id;
@@ -62,56 +36,23 @@ namespace Umbraco.Core.IO
wrapper.Shadow(id);
}
// fixme - remove
//// internal for tests + FileSystems
//// do NOT use otherwise
//internal static ShadowFileSystems CreateScope(Guid id, ShadowWrapper[] wrappers, ILogger logger)
//{
// lock (Locker)
// {
// if (CallContext.LogicalGetData(ItemKey) != null) throw new InvalidOperationException("Already shadowing.");
// CallContext.LogicalSetData(ItemKey, ItemKey); // value does not matter
// }
// return new ShadowFileSystems(id, wrappers, logger);
//}
//internal static bool InScope => NoScope == false;
//internal static bool NoScope => CallContext.LogicalGetData(ItemKey) == null;
// invoked by the scope when exiting, if completed
public void Complete()
{
_completed = true;
//lock (Locker)
//{
// _logger.Debug<ShadowFileSystems>("UnShadow " + _id + " (complete).");
// var exceptions = new List<Exception>();
// foreach (var wrapper in _wrappers)
// {
// try
// {
// // this may throw an AggregateException if some of the changes could not be applied
// wrapper.UnShadow(true);
// }
// catch (AggregateException ae)
// {
// exceptions.Add(ae);
// }
// }
// if (exceptions.Count > 0)
// throw new AggregateException("Failed to apply all changes (see exceptions).", exceptions);
// // last, & *only* if successful (otherwise we'll unshadow & cleanup as best as we can)
// CallContext.FreeNamedDataSlot(ItemKey);
//}
}
// 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<ShadowFileSystems>("UnShadow '{ShadowId}' {Status}", _id, _completed ? "complete" : "abort");
var exceptions = new List<Exception>();
@@ -132,20 +73,6 @@ namespace Umbraco.Core.IO
if (exceptions.Count > 0)
throw new AggregateException(_completed ? "Failed to apply all changes (see exceptions)." : "Failed to abort (see exceptions).", exceptions);
//if (CallContext.LogicalGetData(ItemKey) == null) return;
//try
//{
// _logger.Debug<ShadowFileSystems>("UnShadow " + _id + " (abort)");
// foreach (var wrapper in _wrappers)
// wrapper.UnShadow(false); // should not throw
//}
//finally
//{
// // last, & always
// CallContext.FreeNamedDataSlot(ItemKey);
//}
}
}
@@ -28,13 +28,13 @@ namespace Umbraco.Core.Services.Implement
private readonly IContentTypeRepository _contentTypeRepository;
private readonly IDocumentBlueprintRepository _documentBlueprintRepository;
private readonly ILanguageRepository _languageRepository;
private readonly MediaFileSystem _mediaFileSystem;
private readonly IMediaFileSystem _mediaFileSystem;
private IQuery<IContent> _queryNotTrashed;
#region Constructors
public ContentService(IScopeProvider provider, ILogger logger,
IEventMessagesFactory eventMessagesFactory, MediaFileSystem mediaFileSystem,
IEventMessagesFactory eventMessagesFactory, IMediaFileSystem mediaFileSystem,
IDocumentRepository documentRepository, IEntityRepository entityRepository, IAuditRepository auditRepository,
IContentTypeRepository contentTypeRepository, IDocumentBlueprintRepository documentBlueprintRepository, ILanguageRepository languageRepository)
: base(provider, logger, eventMessagesFactory)
@@ -1471,9 +1471,7 @@ namespace Umbraco.Core.Services.Implement
var args = new DeleteEventArgs<IContent>(c, false); // raise event & get flagged files
scope.Events.Dispatch(Deleted, this, args, nameof(Deleted));
// fixme not going to work, do it differently
_mediaFileSystem.DeleteFiles(args.MediaFilesToDelete, // remove flagged files
(file, e) => Logger.Error<ContentService>(e, "An error occurred while deleting file attached to nodes: {File}", file));
// media files deleted by QueuingEventDispatcher
}
}
@@ -26,11 +26,11 @@ namespace Umbraco.Core.Services.Implement
private readonly IAuditRepository _auditRepository;
private readonly IEntityRepository _entityRepository;
private readonly MediaFileSystem _mediaFileSystem;
private readonly IMediaFileSystem _mediaFileSystem;
#region Constructors
public MediaService(IScopeProvider provider, MediaFileSystem mediaFileSystem, ILogger logger, IEventMessagesFactory eventMessagesFactory,
public MediaService(IScopeProvider provider, IMediaFileSystem mediaFileSystem, ILogger logger, IEventMessagesFactory eventMessagesFactory,
IMediaRepository mediaRepository, IAuditRepository auditRepository, IMediaTypeRepository mediaTypeRepository,
IEntityRepository entityRepository)
: base(provider, logger, eventMessagesFactory)
@@ -888,8 +888,7 @@ namespace Umbraco.Core.Services.Implement
var args = new DeleteEventArgs<IMedia>(c, false); // raise event & get flagged files
scope.Events.Dispatch(Deleted, this, args);
_mediaFileSystem.DeleteFiles(args.MediaFilesToDelete, // remove flagged files
(file, e) => Logger.Error<MediaService>(e, "An error occurred while deleting file attached to nodes: {File}", file));
// media files deleted by QueuingEventDispatcher
}
}
@@ -28,14 +28,14 @@ namespace Umbraco.Core.Services.Implement
private readonly IAuditRepository _auditRepository;
private readonly IMemberGroupService _memberGroupService;
private readonly MediaFileSystem _mediaFileSystem;
private readonly IMediaFileSystem _mediaFileSystem;
//only for unit tests!
internal MembershipProviderBase MembershipProvider { get; set; }
#region Constructor
public MemberService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMemberGroupService memberGroupService, MediaFileSystem mediaFileSystem,
public MemberService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMemberGroupService memberGroupService, IMediaFileSystem mediaFileSystem,
IMemberRepository memberRepository, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, IAuditRepository auditRepository)
: base(provider, logger, eventMessagesFactory)
{
@@ -927,10 +927,7 @@ namespace Umbraco.Core.Services.Implement
args.CanCancel = false;
scope.Events.Dispatch(Deleted, this, args);
// fixme - this is MOOT because the event will not trigger immediately
// it's been refactored already (think it's the dispatcher that deals with it?)
_mediaFileSystem.DeleteFiles(args.MediaFilesToDelete, // remove flagged files
(file, e) => Logger.Error<MemberService>(e, "An error occurred while deleting file attached to nodes: {File}", file));
// media files deleted by QueuingEventDispatcher
}
#endregion
+3 -1
View File
@@ -329,6 +329,8 @@
<Compile Include="Events\ExportedMemberEventArgs.cs" />
<Compile Include="Events\RolesEventArgs.cs" />
<Compile Include="Events\UserGroupWithUsers.cs" />
<Compile Include="IO\IFileSystems.cs" />
<Compile Include="IO\IMediaFileSystem.cs" />
<Compile Include="IO\MediaPathSchemes\CombinedGuidsMediaPathScheme.cs" />
<Compile Include="IO\IMediaPathScheme.cs" />
<Compile Include="IO\MediaPathSchemes\OriginalMediaPathScheme.cs" />
@@ -574,7 +576,7 @@
<Compile Include="IntExtensions.cs" />
<Compile Include="IO\FileSecurityException.cs" />
<Compile Include="IO\FileSystemExtensions.cs" />
<Compile Include="IO\FileSystemProviderAttribute.cs" />
<Compile Include="IO\FileSystemAttribute.cs" />
<Compile Include="IO\FileSystems.cs" />
<Compile Include="IO\FileSystemWrapper.cs" />
<Compile Include="IO\IFileSystem.cs" />
+43 -41
View File
@@ -6,6 +6,7 @@ using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Composing;
using Umbraco.Core.Composing.Composers;
using Umbraco.Core.IO;
using Umbraco.Core.IO.MediaPathSchemes;
using Umbraco.Core.Logging;
@@ -29,11 +30,12 @@ namespace Umbraco.Tests.IO
_container = Current.Container = ContainerFactory.Create();
_container.Register(_ => Mock.Of<ILogger>());
_container.Register<FileSystems>();
_container.Register(_ => Mock.Of<IDataTypeService>());
_container.Register(_ => Mock.Of<IContentSection>());
_container.RegisterSingleton<IMediaPathScheme, OriginalMediaPathScheme>();
_container.ComposeFileSystems();
// make sure we start clean
// because some tests will create corrupt or weird filesystems
FileSystems.Reset();
@@ -52,25 +54,39 @@ namespace Umbraco.Tests.IO
private FileSystems FileSystems => _container.GetInstance<FileSystems>();
[Test]
public void Can_Get_Base_File_System()
public void Can_Get_MediaFileSystem()
{
var fileSystem = FileSystems.GetUnderlyingFileSystemProvider("media");
var fileSystem = FileSystems.GetFileSystem<MediaFileSystem>();
Assert.NotNull(fileSystem);
}
[Test]
public void Can_Get_Typed_File_System()
public void Can_Get_IMediaFileSystem()
{
var fileSystem = FileSystems.GetFileSystemProvider<MediaFileSystem>();
var fileSystem = _container.GetInstance<IMediaFileSystem>();
Assert.NotNull(fileSystem);
}
[Test]
public void Media_Fs_Safe_Delete()
public void MediaFileSystem_Is_Singleton()
{
var fs = FileSystems.GetFileSystemProvider<MediaFileSystem>();
var fileSystem1 = FileSystems.GetFileSystem<MediaFileSystem>();
var fileSystem2 = FileSystems.GetFileSystem<MediaFileSystem>();
Assert.AreSame(fileSystem1, fileSystem2);
}
[Test]
public void IMediaFileSystem_Is_Singleton()
{
var fileSystem1 = _container.GetInstance<IMediaFileSystem>();
var fileSystem2 = _container.GetInstance<IMediaFileSystem>();
Assert.AreSame(fileSystem1, fileSystem2);
}
[Test]
public void Can_Delete_MediaFiles()
{
var fs = FileSystems.GetFileSystem<MediaFileSystem>();
var ms = new MemoryStream(Encoding.UTF8.GetBytes("test"));
var virtPath = fs.GetMediaPath("file.txt", Guid.NewGuid(), Guid.NewGuid());
fs.AddFile(virtPath, ms);
@@ -92,51 +108,37 @@ namespace Umbraco.Tests.IO
Assert.IsTrue(Directory.Exists(physPath));
}
public void Singleton_Typed_File_System()
[Test]
public void Cannot_Get_InvalidFileSystem()
{
var fs1 = FileSystems.GetFileSystemProvider<MediaFileSystem>();
var fs2 = FileSystems.GetFileSystemProvider<MediaFileSystem>();
Assert.AreSame(fs1, fs2);
// throws because InvalidTypedFileSystem does not have the proper attribute with an alias
Assert.Throws<InvalidOperationException>(() => FileSystems.GetFileSystem<InvalidFileSystem>());
}
[Test]
public void Exception_Thrown_On_Invalid_Typed_File_System()
{
Assert.Throws<InvalidOperationException>(() => FileSystems.GetFileSystemProvider<InvalidTypedFileSystem>());
}
[Test]
public void Exception_Thrown_On_NonConfigured_Typed_File_System()
public void Cannot_Get_NonConfiguredFileSystem()
{
// note: we need to reset the manager between tests else the Accept_Fallback test would corrupt that one
Assert.Throws<ArgumentException>(() => FileSystems.GetFileSystemProvider<NonConfiguredTypeFileSystem>());
// throws because NonConfiguredFileSystem has the proper attribute with an alias,
// but then the container cannot find an IFileSystem implementation for that alias
Assert.Throws<InvalidOperationException>(() => FileSystems.GetFileSystem<NonConfiguredFileSystem>());
// all we'd need to pass is to register something like:
//_container.Register<IFileSystem>("noconfig", factory => new PhysicalFileSystem("~/foo"));
}
[Test]
public void Accept_Fallback_On_NonConfigured_Typed_File_System()
internal class InvalidFileSystem : FileSystemWrapper
{
var fs = FileSystems.GetFileSystemProvider<NonConfiguredTypeFileSystem>(() => new PhysicalFileSystem("~/App_Data/foo"));
Assert.NotNull(fs);
}
/// <summary>
/// Used in unit tests, for a typed file system we need to inherit from FileSystemWrapper and they MUST have a ctor
/// that only accepts a base IFileSystem object
/// </summary>
internal class InvalidTypedFileSystem : FileSystemWrapper
{
public InvalidTypedFileSystem(IFileSystem wrapped, string invalidParam)
: base(wrapped)
public InvalidFileSystem(IFileSystem innerFileSystem)
: base(innerFileSystem)
{ }
}
[FileSystemProvider("noconfig")]
internal class NonConfiguredTypeFileSystem : FileSystemWrapper
[FileSystem("noconfig")]
internal class NonConfiguredFileSystem : FileSystemWrapper
{
public NonConfiguredTypeFileSystem(IFileSystem wrapped)
: base(wrapped)
public NonConfiguredFileSystem(IFileSystem innerFileSystem)
: base(innerFileSystem)
{ }
}
}
@@ -33,8 +33,7 @@ namespace Umbraco.Tests.Scoping
_testObjects = new TestObjects(container);
Current.Container.RegisterSingleton(f => Current.Container);
Current.Container.RegisterSingleton(factory => new FileSystems(factory.TryGetInstance<ILogger>()));
Current.Container.RegisterSingleton(factory => new FileSystems(container, factory.TryGetInstance<ILogger>()));
Current.Container.RegisterCollectionBuilder<MapperCollectionBuilder>();
SettingsForTests.Reset(); // ensure we have configuration
+1 -1
View File
@@ -240,7 +240,7 @@ namespace Umbraco.Tests.TestHelpers
databaseFactory = new UmbracoDatabaseFactory(Constants.System.UmbracoConnectionName, GetDefaultSqlSyntaxProviders(logger), logger, mappers);
}
fileSystems = fileSystems ?? new FileSystems(logger);
fileSystems = fileSystems ?? new FileSystems(Current.Container, logger);
var scopeProvider = new ScopeProvider(databaseFactory, fileSystems, logger);
return scopeProvider;
}
+2 -2
View File
@@ -17,10 +17,10 @@ namespace Umbraco.Web.Editors
[PluginController("UmbracoApi")]
public class ImagesController : UmbracoAuthorizedApiController
{
private readonly MediaFileSystem _mediaFileSystem;
private readonly IMediaFileSystem _mediaFileSystem;
private readonly IContentSection _contentSection;
public ImagesController(MediaFileSystem mediaFileSystem, IContentSection contentSection)
public ImagesController(IMediaFileSystem mediaFileSystem, IContentSection contentSection)
{
_mediaFileSystem = mediaFileSystem;
_contentSection = contentSection;
@@ -15,11 +15,11 @@ namespace Umbraco.Web.Media
/// </summary>
internal class UploadAutoFillProperties
{
private readonly MediaFileSystem _mediaFileSystem;
private readonly IMediaFileSystem _mediaFileSystem;
private readonly ILogger _logger;
private readonly IContentSection _contentSection;
public UploadAutoFillProperties(MediaFileSystem mediaFileSystem, ILogger logger, IContentSection contentSection)
public UploadAutoFillProperties(IMediaFileSystem mediaFileSystem, ILogger logger, IContentSection contentSection)
{
_mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
@@ -15,11 +15,11 @@ namespace Umbraco.Web.PropertyEditors
[DataEditor(Constants.PropertyEditors.Aliases.UploadField, "File upload", "fileupload", Icon = "icon-download-alt", Group = "media")]
public class FileUploadPropertyEditor : DataEditor
{
private readonly MediaFileSystem _mediaFileSystem;
private readonly IMediaFileSystem _mediaFileSystem;
private readonly IContentSection _contentSection;
private readonly UploadAutoFillProperties _uploadAutoFillProperties;
public FileUploadPropertyEditor(ILogger logger, MediaFileSystem mediaFileSystem, IContentSection contentSection)
public FileUploadPropertyEditor(ILogger logger, IMediaFileSystem mediaFileSystem, IContentSection contentSection)
: base(logger)
{
_mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem));
@@ -15,9 +15,9 @@ namespace Umbraco.Web.PropertyEditors
/// </summary>
internal class FileUploadPropertyValueEditor : DataValueEditor
{
private readonly MediaFileSystem _mediaFileSystem;
private readonly IMediaFileSystem _mediaFileSystem;
public FileUploadPropertyValueEditor(DataEditorAttribute attribute, MediaFileSystem mediaFileSystem)
public FileUploadPropertyValueEditor(DataEditorAttribute attribute, IMediaFileSystem mediaFileSystem)
: base(attribute)
{
_mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem));
@@ -22,7 +22,7 @@ namespace Umbraco.Web.PropertyEditors
[DataEditor(Constants.PropertyEditors.Aliases.ImageCropper, "Image Cropper", "imagecropper", ValueType = ValueTypes.Json, HideLabel = false, Group="media", Icon="icon-crop")]
public class ImageCropperPropertyEditor : DataEditor
{
private readonly MediaFileSystem _mediaFileSystem;
private readonly IMediaFileSystem _mediaFileSystem;
private readonly IContentSection _contentSettings;
private readonly IDataTypeService _dataTypeService;
private readonly UploadAutoFillProperties _autoFillProperties;
@@ -30,7 +30,7 @@ namespace Umbraco.Web.PropertyEditors
/// <summary>
/// Initializes a new instance of the <see cref="ImageCropperPropertyEditor"/> class.
/// </summary>
public ImageCropperPropertyEditor(ILogger logger, MediaFileSystem mediaFileSystem, IContentSection contentSettings, IDataTypeService dataTypeService)
public ImageCropperPropertyEditor(ILogger logger, IMediaFileSystem mediaFileSystem, IContentSection contentSettings, IDataTypeService dataTypeService)
: base(logger)
{
_mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem));
@@ -19,9 +19,9 @@ namespace Umbraco.Web.PropertyEditors
internal class ImageCropperPropertyValueEditor : DataValueEditor // fixme core vs web?
{
private readonly ILogger _logger;
private readonly MediaFileSystem _mediaFileSystem;
private readonly IMediaFileSystem _mediaFileSystem;
public ImageCropperPropertyValueEditor(DataEditorAttribute attribute, ILogger logger, MediaFileSystem mediaFileSystem)
public ImageCropperPropertyValueEditor(DataEditorAttribute attribute, ILogger logger, IMediaFileSystem mediaFileSystem)
: base(attribute)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));