Files
Umbraco-CMS/src/Umbraco.Core/IO/FileSystems.cs

282 lines
11 KiB
C#
Raw Normal View History

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
2017-05-31 15:25:24 +02:00
using System.Threading;
using Umbraco.Core.Logging;
2017-05-30 15:56:27 +02:00
using Umbraco.Core.Composing;
namespace Umbraco.Core.IO
2017-05-12 14:49:44 +02:00
{
2018-07-20 09:49:05 +02:00
public class FileSystems : IFileSystems
{
2018-10-26 15:06:53 +02:00
private readonly IContainer _container;
private readonly ILogger _logger;
2018-10-26 15:06:53 +02:00
private readonly ConcurrentDictionary<string, Lazy<IFileSystem>> _filesystems = new ConcurrentDictionary<string, Lazy<IFileSystem>>();
2017-05-12 14:49:44 +02:00
// wrappers for shadow support
2017-05-12 14:49:44 +02:00
private ShadowWrapper _macroPartialFileSystem;
private ShadowWrapper _partialViewsFileSystem;
private ShadowWrapper _stylesheetsFileSystem;
private ShadowWrapper _scriptsFileSystem;
private ShadowWrapper _masterPagesFileSystem;
private ShadowWrapper _mvcViewsFileSystem;
2018-11-19 14:40:59 +01:00
2017-05-31 15:25:24 +02:00
// well-known file systems lazy initialization
private object _wkfsLock = new object();
private bool _wkfsInitialized;
2018-11-19 14:40:59 +01:00
private object _wkfsObject; // unused
2017-05-31 15:25:24 +02:00
2018-11-19 14:40:59 +01:00
// shadow support
private readonly List<ShadowWrapper> _shadowWrappers = new List<ShadowWrapper>();
private readonly object _shadowLocker = new object();
private static Guid _shadowCurrentId = Guid.Empty; // static - unique!!
2017-05-12 14:49:44 +02:00
#region Constructor
2017-05-22 17:22:10 +02:00
// DI wants a public ctor
2018-10-26 15:06:53 +02:00
public FileSystems(IContainer container, ILogger logger)
{
2018-10-26 15:06:53 +02:00
_container = container;
_logger = logger;
2017-05-12 14:49:44 +02:00
}
2017-05-12 14:49:44 +02:00
// for tests only, totally unsafe
internal void Reset()
{
2018-11-19 14:40:59 +01:00
_shadowWrappers.Clear();
2017-05-12 14:49:44 +02:00
_filesystems.Clear();
2017-05-31 15:25:24 +02:00
Volatile.Write(ref _wkfsInitialized, false);
2018-11-19 14:40:59 +01:00
_shadowCurrentId = Guid.Empty;
}
// for tests only, totally unsafe
internal static void ResetShadowId()
{
_shadowCurrentId = Guid.Empty;
}
2018-11-19 14:40:59 +01:00
// set by the scope provider when taking control of filesystems
2017-05-31 15:25:24 +02:00
internal Func<bool> IsScoped { get; set; } = () => false;
#endregion
#region Well-Known FileSystems
2018-10-26 15:06:53 +02:00
/// <inheritdoc />
2017-05-31 15:25:24 +02:00
public IFileSystem MacroPartialsFileSystem
{
get
{
if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems();
return _macroPartialFileSystem;
}
}
2018-10-26 15:06:53 +02:00
/// <inheritdoc />
2017-05-31 15:25:24 +02:00
public IFileSystem PartialViewsFileSystem
{
get
{
if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems();
return _partialViewsFileSystem;
}
}
2018-10-26 15:06:53 +02:00
/// <inheritdoc />
2017-05-31 15:25:24 +02:00
public IFileSystem StylesheetsFileSystem
{
get
{
if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems();
return _stylesheetsFileSystem;
}
}
2018-10-26 15:06:53 +02:00
/// <inheritdoc />
2017-05-31 15:25:24 +02:00
public IFileSystem ScriptsFileSystem
{
get
{
if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems();
return _scriptsFileSystem;
}
}
2018-10-26 15:06:53 +02:00
/// <inheritdoc />
2017-05-31 15:25:24 +02:00
public IFileSystem MasterPagesFileSystem
{
get
{
if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems();
2018-10-26 15:06:53 +02:00
return _masterPagesFileSystem;
2017-05-31 15:25:24 +02:00
}
2017-07-20 11:21:28 +02:00
}
2017-05-31 15:25:24 +02:00
2018-10-26 15:06:53 +02:00
/// <inheritdoc />
2017-05-31 15:25:24 +02:00
public IFileSystem MvcViewsFileSystem
{
get
{
if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems();
return _mvcViewsFileSystem;
}
}
2017-05-12 14:49:44 +02:00
2017-05-31 15:25:24 +02:00
private void EnsureWellKnownFileSystems()
{
LazyInitializer.EnsureInitialized(ref _wkfsObject, ref _wkfsInitialized, ref _wkfsLock, CreateWellKnownFileSystems);
}
// need to return something to LazyInitializer.EnsureInitialized
// but it does not really matter what we return - here, null
private object CreateWellKnownFileSystems()
2017-05-12 14:49:44 +02:00
{
var macroPartialFileSystem = new PhysicalFileSystem(SystemDirectories.MacroPartials);
var partialViewsFileSystem = new PhysicalFileSystem(SystemDirectories.PartialViews);
var stylesheetsFileSystem = new PhysicalFileSystem(SystemDirectories.Css);
var scriptsFileSystem = new PhysicalFileSystem(SystemDirectories.Scripts);
var masterPagesFileSystem = new PhysicalFileSystem(SystemDirectories.Masterpages);
var mvcViewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews);
2018-11-19 14:40:59 +01:00
_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);
// fixme locking?
_shadowWrappers.Add(_macroPartialFileSystem);
_shadowWrappers.Add(_partialViewsFileSystem);
_shadowWrappers.Add(_stylesheetsFileSystem);
_shadowWrappers.Add(_scriptsFileSystem);
_shadowWrappers.Add(_masterPagesFileSystem);
_shadowWrappers.Add(_mvcViewsFileSystem);
2017-05-31 15:25:24 +02:00
return null;
2017-05-12 14:49:44 +02:00
}
#endregion
#region Providers
2017-05-12 14:49:44 +02:00
/// <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>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>
2018-11-19 14:40:59 +01:00
public TFileSystem GetFileSystem<TFileSystem>(Func<IFileSystem> innerFileSystemFactory)
2017-05-12 14:49:44 +02:00
where TFileSystem : FileSystemWrapper
{
2018-11-19 14:40:59 +01:00
if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems();
2018-10-26 15:06:53 +02:00
2018-11-19 14:40:59 +01:00
var name = typeof(TFileSystem).FullName;
if (name == null) throw new Exception("panic!");
2018-10-26 15:06:53 +02:00
2018-11-19 14:40:59 +01:00
return (TFileSystem) _filesystems.GetOrAdd(name, _ => new Lazy<IFileSystem>(() =>
{
var innerFileSystem = innerFileSystemFactory();
var shadowWrapper = CreateShadowWrapper(innerFileSystem, "typed/" + name);
return _container.CreateInstance<TFileSystem>(new { innerFileSystem = shadowWrapper });
2018-10-26 15:06:53 +02:00
})).Value;
2017-05-12 14:49:44 +02:00
}
#endregion
#region Shadow
// note
// shadowing is thread-safe, but entering and exiting shadow mode is not, and there is only one
// global shadow for the entire application, so great care should be taken to ensure that the
// application is *not* doing anything else when using a shadow.
// shadow applies to well-known filesystems *only* - at the moment, any other filesystem that would
// 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
2018-10-26 15:06:53 +02:00
// is actually created and used - after, it is too late - enabling shadow has a negligible perfs
// impact.
2018-10-26 15:06:53 +02:00
// 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...
// throw new InvalidOperationException("Cannot enable shadow once filesystems have been created.");
// _shadowEnabled = true;
//}
2017-05-12 14:49:44 +02:00
internal ICompletable Shadow(Guid id)
{
2017-06-23 18:54:42 +02:00
if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems();
2018-11-19 14:40:59 +01:00
return new ShadowFileSystems(this, id); // will invoke BeginShadow and EndShadow
}
2018-11-19 14:40:59 +01:00
internal void BeginShadow(Guid id)
{
2018-11-19 14:40:59 +01:00
lock (_shadowLocker)
{
2018-11-19 14:40:59 +01:00
// if we throw here, it means that something very wrong happened.
if (_shadowCurrentId != Guid.Empty)
throw new InvalidOperationException("Already shadowing.");
_shadowCurrentId = id;
_logger.Debug<ShadowFileSystems>("Shadow '{ShadowId}'", id);
foreach (var wrapper in _shadowWrappers)
wrapper.Shadow(id);
}
2018-11-19 14:40:59 +01:00
}
2018-11-19 14:40:59 +01:00
internal void EndShadow(Guid id, bool completed)
{
lock (_shadowLocker)
{
2018-11-19 14:40:59 +01:00
// 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<ShadowFileSystems>("UnShadow '{ShadowId}' {Status}", id, completed ? "complete" : "abort");
var exceptions = new List<Exception>();
foreach (var wrapper in _shadowWrappers)
{
2018-11-19 14:40:59 +01:00
try
{
// this may throw an AggregateException if some of the changes could not be applied
wrapper.UnShadow(completed);
}
catch (AggregateException ae)
{
exceptions.Add(ae);
}
}
2018-11-19 14:40:59 +01:00
_shadowCurrentId = Guid.Empty;
if (exceptions.Count > 0)
throw new AggregateException(completed ? "Failed to apply all changes (see exceptions)." : "Failed to abort (see exceptions).", exceptions);
}
2018-11-19 14:40:59 +01:00
}
2018-11-19 14:40:59 +01:00
private ShadowWrapper CreateShadowWrapper(IFileSystem filesystem, string shadowPath)
{
lock (_shadowLocker)
{
2018-11-19 14:40:59 +01:00
var wrapper = new ShadowWrapper(filesystem, shadowPath, IsScoped);
if (_shadowCurrentId != Guid.Empty)
wrapper.Shadow(_shadowCurrentId);
_shadowWrappers.Add(wrapper);
return wrapper;
}
}
2018-11-19 14:40:59 +01:00
#endregion
}
}