Refactor filesystems
This commit is contained in:
25
src/Umbraco.Core/IO/FileSystemAttribute.cs
Normal file
25
src/Umbraco.Core/IO/FileSystemAttribute.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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...
|
||||
|
||||
@@ -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
src/Umbraco.Core/IO/IFileSystems.cs
Normal file
43
src/Umbraco.Core/IO/IFileSystems.cs
Normal 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
src/Umbraco.Core/IO/IMediaFileSystem.cs
Normal file
66
src/Umbraco.Core/IO/IMediaFileSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user