2016-11-03 10:31:44 +01:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Concurrent;
|
|
|
|
|
|
using System.Collections.Generic;
|
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-11-28 11:05:41 +01:00
|
|
|
|
private readonly IFactory _container;
|
2016-11-03 10:31:44 +01:00
|
|
|
|
private readonly ILogger _logger;
|
2019-11-13 11:26:03 +01:00
|
|
|
|
private readonly IIOHelper _ioHelper;
|
2016-11-03 10:31:44 +01:00
|
|
|
|
|
2018-11-24 15:38:00 +01:00
|
|
|
|
private readonly ConcurrentDictionary<Type, Lazy<IFileSystem>> _filesystems = new ConcurrentDictionary<Type, 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 _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();
|
2019-02-20 14:10:15 +01:00
|
|
|
|
private static string _shadowCurrentId; // 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
|
2019-11-13 11:26:03 +01:00
|
|
|
|
public FileSystems(IFactory container, ILogger logger, IIOHelper ioHelper)
|
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;
|
2019-11-13 11:26:03 +01:00
|
|
|
|
_ioHelper = ioHelper;
|
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);
|
2019-02-20 14:10:15 +01:00
|
|
|
|
_shadowCurrentId = null;
|
2018-11-19 14:40:59 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// for tests only, totally unsafe
|
|
|
|
|
|
internal static void ResetShadowId()
|
|
|
|
|
|
{
|
2019-02-20 14:10:15 +01:00
|
|
|
|
_shadowCurrentId = null;
|
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 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
|
|
|
|
{
|
2019-11-13 11:26:03 +01:00
|
|
|
|
var macroPartialFileSystem = new PhysicalFileSystem(Constants.SystemDirectories.MacroPartials);
|
|
|
|
|
|
var partialViewsFileSystem = new PhysicalFileSystem(Constants.SystemDirectories.PartialViews);
|
|
|
|
|
|
var stylesheetsFileSystem = new PhysicalFileSystem(_ioHelper.Css);
|
|
|
|
|
|
var scriptsFileSystem = new PhysicalFileSystem(_ioHelper.Scripts);
|
|
|
|
|
|
var mvcViewsFileSystem = new PhysicalFileSystem(Constants.SystemDirectories.MvcViews);
|
2017-05-12 14:49:44 +02:00
|
|
|
|
|
2019-02-20 16:32:28 +01:00
|
|
|
|
_macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, "macro-partials", IsScoped);
|
|
|
|
|
|
_partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, "partials", IsScoped);
|
2018-11-19 14:40:59 +01:00
|
|
|
|
_stylesheetsFileSystem = new ShadowWrapper(stylesheetsFileSystem, "css", IsScoped);
|
|
|
|
|
|
_scriptsFileSystem = new ShadowWrapper(scriptsFileSystem, "scripts", IsScoped);
|
2019-02-20 16:32:28 +01:00
|
|
|
|
_mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, "views", IsScoped);
|
2018-11-19 14:40:59 +01:00
|
|
|
|
|
2019-01-26 09:42:14 -05:00
|
|
|
|
// TODO: do we need a lock here?
|
2018-11-19 14:40:59 +01:00
|
|
|
|
_shadowWrappers.Add(_macroPartialFileSystem);
|
|
|
|
|
|
_shadowWrappers.Add(_partialViewsFileSystem);
|
|
|
|
|
|
_shadowWrappers.Add(_stylesheetsFileSystem);
|
|
|
|
|
|
_shadowWrappers.Add(_scriptsFileSystem);
|
|
|
|
|
|
_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
|
|
|
|
|
|
|
2019-02-20 16:32:28 +01:00
|
|
|
|
private readonly Dictionary<Type, string> _paths = new Dictionary<Type, string>();
|
|
|
|
|
|
|
|
|
|
|
|
// internal for tests
|
|
|
|
|
|
internal IReadOnlyDictionary<Type, string> Paths => _paths;
|
|
|
|
|
|
|
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-24 15:38:00 +01:00
|
|
|
|
public TFileSystem GetFileSystem<TFileSystem>(IFileSystem supporting)
|
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-24 15:38:00 +01:00
|
|
|
|
return (TFileSystem) _filesystems.GetOrAdd(typeof(TFileSystem), _ => new Lazy<IFileSystem>(() =>
|
2018-11-19 14:40:59 +01:00
|
|
|
|
{
|
2019-02-20 16:32:28 +01:00
|
|
|
|
var typeofTFileSystem = typeof(TFileSystem);
|
|
|
|
|
|
|
|
|
|
|
|
// path must be unique and not collide with paths used in CreateWellKnownFileSystems
|
|
|
|
|
|
// for our well-known 'media' filesystem we can use the short 'media' path
|
|
|
|
|
|
// for others, put them under 'x/' and use ... something
|
|
|
|
|
|
string path;
|
|
|
|
|
|
if (typeofTFileSystem == typeof(MediaFileSystem))
|
|
|
|
|
|
{
|
|
|
|
|
|
path = "media";
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
lock (_paths)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_paths.TryGetValue(typeofTFileSystem, out path))
|
|
|
|
|
|
{
|
|
|
|
|
|
path = Guid.NewGuid().ToString("N").Substring(0, 6);
|
|
|
|
|
|
while (_paths.ContainsValue(path)) // this can't loop forever, right?
|
|
|
|
|
|
path = Guid.NewGuid().ToString("N").Substring(0, 6);
|
|
|
|
|
|
_paths[typeofTFileSystem] = path;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
path = "x/" + path;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var shadowWrapper = CreateShadowWrapper(supporting, path);
|
2018-11-24 16:41:27 +01:00
|
|
|
|
return _container.CreateInstance<TFileSystem>(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.
|
|
|
|
|
|
|
2019-02-20 14:10:15 +01:00
|
|
|
|
internal ICompletable Shadow()
|
2016-11-03 10:31:44 +01:00
|
|
|
|
{
|
2017-06-23 18:54:42 +02:00
|
|
|
|
if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems();
|
|
|
|
|
|
|
2019-02-20 14:10:15 +01:00
|
|
|
|
var id = ShadowWrapper.CreateShadowId();
|
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
|
|
|
|
}
|
|
|
|
|
|
|
2019-02-20 14:10:15 +01:00
|
|
|
|
internal void BeginShadow(string 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.
|
2019-02-20 14:10:15 +01:00
|
|
|
|
if (_shadowCurrentId != null)
|
2018-11-19 14:40:59 +01:00
|
|
|
|
throw new InvalidOperationException("Already shadowing.");
|
2019-02-20 14:10:15 +01:00
|
|
|
|
|
2018-11-19 14:40:59 +01:00
|
|
|
|
_shadowCurrentId = id;
|
|
|
|
|
|
|
2019-02-20 14:10:15 +01:00
|
|
|
|
_logger.Debug<ShadowFileSystems>("Shadow '{ShadowId}'", _shadowCurrentId);
|
2018-11-19 14:40:59 +01:00
|
|
|
|
|
|
|
|
|
|
foreach (var wrapper in _shadowWrappers)
|
2019-02-20 14:10:15 +01:00
|
|
|
|
wrapper.Shadow(_shadowCurrentId);
|
2016-11-03 10:31:44 +01:00
|
|
|
|
}
|
2018-11-19 14:40:59 +01:00
|
|
|
|
}
|
2016-11-03 10:31:44 +01:00
|
|
|
|
|
2019-02-20 14:10:15 +01:00
|
|
|
|
internal void EndShadow(string id, bool completed)
|
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.
|
2019-02-20 14:10:15 +01:00
|
|
|
|
if (_shadowCurrentId == null)
|
2018-11-19 14:40:59 +01:00
|
|
|
|
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
|
|
|
|
|
2019-02-20 14:10:15 +01:00
|
|
|
|
_shadowCurrentId = null;
|
2018-11-19 14:40:59 +01:00
|
|
|
|
|
|
|
|
|
|
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);
|
2019-02-20 14:10:15 +01:00
|
|
|
|
if (_shadowCurrentId != null)
|
2018-11-19 14:40:59 +01:00
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
}
|