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;
|
2020-09-17 11:35:29 +02:00
|
|
|
|
using Microsoft.Extensions.Logging;
|
2020-08-23 23:36:48 +02:00
|
|
|
|
using Microsoft.Extensions.Options;
|
2021-02-18 11:06:02 +01:00
|
|
|
|
using Umbraco.Cms.Core.Configuration.Models;
|
|
|
|
|
|
using Umbraco.Cms.Core.Hosting;
|
|
|
|
|
|
using Umbraco.Extensions;
|
2016-11-03 10:31:44 +01:00
|
|
|
|
|
2021-02-18 11:06:02 +01:00
|
|
|
|
namespace Umbraco.Cms.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
|
|
|
|
{
|
2020-10-30 11:16:17 +00:00
|
|
|
|
private readonly IServiceProvider _container;
|
2020-09-17 11:35:29 +02:00
|
|
|
|
private readonly ILogger<FileSystems> _logger;
|
|
|
|
|
|
private readonly ILoggerFactory _loggerFactory;
|
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
|
2020-10-30 11:16:17 +00:00
|
|
|
|
public FileSystems(IServiceProvider container, ILogger<FileSystems> logger, ILoggerFactory loggerFactory, IIOHelper ioHelper, IOptions<GlobalSettings> globalSettings, IHostingEnvironment hostingEnvironment)
|
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;
|
2020-09-17 11:35:29 +02:00
|
|
|
|
_loggerFactory = loggerFactory;
|
2019-11-13 11:26:03 +01:00
|
|
|
|
_ioHelper = ioHelper;
|
2020-08-23 23:36:48 +02:00
|
|
|
|
_globalSettings = globalSettings.Value;
|
2020-06-05 11:36:59 +02:00
|
|
|
|
_hostingEnvironment = hostingEnvironment;
|
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
|
2019-11-19 07:52:40 +01:00
|
|
|
|
public 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
|
|
|
|
{
|
2020-09-24 10:29:53 +02:00
|
|
|
|
var logger = _loggerFactory.CreateLogger<PhysicalFileSystem>();
|
2020-11-26 14:16:24 +01:00
|
|
|
|
|
|
|
|
|
|
//TODO this is fucked, why do PhysicalFileSystem has a root url? Mvc views cannot be accessed by url!
|
|
|
|
|
|
var macroPartialFileSystem = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, logger, _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MacroPartials), _hostingEnvironment.ToAbsolute(Constants.SystemDirectories.MacroPartials));
|
|
|
|
|
|
var partialViewsFileSystem = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, logger, _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.PartialViews), _hostingEnvironment.ToAbsolute(Constants.SystemDirectories.PartialViews));
|
|
|
|
|
|
var stylesheetsFileSystem = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, logger, _hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoCssPath), _hostingEnvironment.ToAbsolute(_globalSettings.UmbracoCssPath));
|
|
|
|
|
|
var scriptsFileSystem = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, logger, _hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoScriptsPath), _hostingEnvironment.ToAbsolute(_globalSettings.UmbracoScriptsPath));
|
|
|
|
|
|
var mvcViewsFileSystem = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, logger, _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MvcViews), _hostingEnvironment.ToAbsolute(Constants.SystemDirectories.MvcViews));
|
2020-09-17 11:35:29 +02:00
|
|
|
|
|
2020-09-28 14:59:17 +02:00
|
|
|
|
_macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, _ioHelper, _hostingEnvironment, _loggerFactory, "macro-partials", IsScoped);
|
|
|
|
|
|
_partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, _ioHelper, _hostingEnvironment, _loggerFactory, "partials", IsScoped);
|
|
|
|
|
|
_stylesheetsFileSystem = new ShadowWrapper(stylesheetsFileSystem, _ioHelper, _hostingEnvironment, _loggerFactory, "css", IsScoped);
|
|
|
|
|
|
_scriptsFileSystem = new ShadowWrapper(scriptsFileSystem, _ioHelper, _hostingEnvironment, _loggerFactory, "scripts", IsScoped);
|
|
|
|
|
|
_mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, _ioHelper, _hostingEnvironment, _loggerFactory, "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;
|
2020-08-23 23:36:48 +02:00
|
|
|
|
private GlobalSettings _globalSettings;
|
2020-06-05 11:36:59 +02:00
|
|
|
|
private readonly IHostingEnvironment _hostingEnvironment;
|
2019-02-20 16:32:28 +01:00
|
|
|
|
|
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-11-19 07:52:40 +01:00
|
|
|
|
public 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();
|
|
|
|
|
|
|
2020-11-18 07:25:11 -08:00
|
|
|
|
var id = ShadowWrapper.CreateShadowId(_hostingEnvironment);
|
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;
|
|
|
|
|
|
|
2020-09-16 10:24:05 +02:00
|
|
|
|
_logger.LogDebug("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.");
|
|
|
|
|
|
|
2020-09-16 10:24:05 +02:00
|
|
|
|
_logger.LogDebug("UnShadow '{ShadowId}' {Status}", id, completed ? "complete" : "abort");
|
2018-11-19 14:40:59 +01:00
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
{
|
2020-10-26 10:47:14 +00:00
|
|
|
|
var wrapper = new ShadowWrapper(filesystem, _ioHelper, _hostingEnvironment, _loggerFactory, 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
|
|
|
|
}
|
|
|
|
|
|
}
|