2016-11-03 10:31:44 +01:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Concurrent;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Linq;
|
2017-05-31 15:25:24 +02:00
|
|
|
|
using System.Threading;
|
2016-11-03 10:31:44 +01:00
|
|
|
|
using Umbraco.Core.Logging;
|
2017-05-30 15:56:27 +02:00
|
|
|
|
using Umbraco.Core.Composing;
|
2016-11-03 10:31:44 +01:00
|
|
|
|
|
|
|
|
|
|
namespace Umbraco.Core.IO
|
2017-05-12 14:49:44 +02:00
|
|
|
|
{
|
2018-07-20 09:49:05 +02:00
|
|
|
|
public class FileSystems : IFileSystems
|
2016-11-03 10:31:44 +01:00
|
|
|
|
{
|
2018-10-26 15:06:53 +02:00
|
|
|
|
private readonly IContainer _container;
|
2016-11-03 10:31:44 +01:00
|
|
|
|
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
|
|
|
|
|
2016-11-03 10:31:44 +01: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
|
2016-11-03 10:31:44 +01:00
|
|
|
|
|
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)
|
2016-11-03 10:31:44 +01:00
|
|
|
|
{
|
2018-10-26 15:06:53 +02:00
|
|
|
|
_container = container;
|
2016-11-03 10:31:44 +01:00
|
|
|
|
_logger = logger;
|
2017-05-12 14:49:44 +02:00
|
|
|
|
}
|
2016-11-03 10:31:44 +01: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;
|
2016-11-03 10:31:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
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;
|
2017-05-30 18:43:24 +02:00
|
|
|
|
|
2016-11-03 10:31:44 +01:00
|
|
|
|
#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
|
|
|
|
}
|
2016-11-03 10:31:44 +01: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
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-03 10:31:44 +01: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
|
2016-11-03 10:31:44 +01:00
|
|
|
|
// 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.
|
2016-11-03 10:31:44 +01:00
|
|
|
|
//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)
|
2016-11-03 10:31:44 +01:00
|
|
|
|
{
|
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
|
2016-11-03 10:31:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-11-19 14:40:59 +01:00
|
|
|
|
internal void BeginShadow(Guid id)
|
2016-11-03 10:31:44 +01:00
|
|
|
|
{
|
2018-11-19 14:40:59 +01:00
|
|
|
|
lock (_shadowLocker)
|
2016-11-03 10:31:44 +01:00
|
|
|
|
{
|
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);
|
2016-11-03 10:31:44 +01:00
|
|
|
|
}
|
2018-11-19 14:40:59 +01:00
|
|
|
|
}
|
2016-11-03 10:31:44 +01:00
|
|
|
|
|
2018-11-19 14:40:59 +01:00
|
|
|
|
internal void EndShadow(Guid id, bool completed)
|
|
|
|
|
|
{
|
|
|
|
|
|
lock (_shadowLocker)
|
2016-11-03 10:31:44 +01:00
|
|
|
|
{
|
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)
|
2016-11-03 10:31:44 +01:00
|
|
|
|
{
|
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);
|
|
|
|
|
|
}
|
2016-11-03 10:31:44 +01:00
|
|
|
|
}
|
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);
|
2016-11-03 10:31:44 +01:00
|
|
|
|
}
|
2018-11-19 14:40:59 +01:00
|
|
|
|
}
|
2016-11-03 10:31:44 +01:00
|
|
|
|
|
2018-11-19 14:40:59 +01:00
|
|
|
|
private ShadowWrapper CreateShadowWrapper(IFileSystem filesystem, string shadowPath)
|
|
|
|
|
|
{
|
|
|
|
|
|
lock (_shadowLocker)
|
2016-11-03 10:31:44 +01:00
|
|
|
|
{
|
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;
|
2016-11-03 10:31:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2018-11-19 14:40:59 +01:00
|
|
|
|
|
|
|
|
|
|
#endregion
|
2016-11-03 10:31:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|