U4-9322 - filesystems & cleanup
This commit is contained in:
@@ -164,7 +164,7 @@ namespace Umbraco.Core
|
|||||||
public static ApplicationContext Current { get; internal set; }
|
public static ApplicationContext Current { get; internal set; }
|
||||||
|
|
||||||
// fixme
|
// fixme
|
||||||
internal IScopeProvider ScopeProvider { get { return DatabaseContext.ScopeProvider; } }
|
internal IScopeProvider ScopeProvider { get { return _databaseContext == null ? null : _databaseContext.ScopeProvider; } }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the application wide cache accessor
|
/// Returns the application wide cache accessor
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ using System.Configuration;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Umbraco.Core.Configuration;
|
using Umbraco.Core.Configuration;
|
||||||
|
using Umbraco.Core.Scoping;
|
||||||
|
|
||||||
namespace Umbraco.Core.IO
|
namespace Umbraco.Core.IO
|
||||||
{
|
{
|
||||||
public class FileSystemProviderManager
|
public class FileSystemProviderManager
|
||||||
{
|
{
|
||||||
private readonly FileSystemProvidersSection _config;
|
private readonly FileSystemProvidersSection _config;
|
||||||
@@ -40,6 +41,12 @@ namespace Umbraco.Core.IO
|
|||||||
get { return Instance; }
|
get { return Instance; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IScopeProviderInternal ScopeProvider
|
||||||
|
{
|
||||||
|
// fixme - 'course this is bad, but enough for now
|
||||||
|
get { return ApplicationContext.Current == null ? null : ApplicationContext.Current.ScopeProvider as IScopeProviderInternal; }
|
||||||
|
}
|
||||||
|
|
||||||
internal FileSystemProviderManager()
|
internal FileSystemProviderManager()
|
||||||
{
|
{
|
||||||
_config = (FileSystemProvidersSection) ConfigurationManager.GetSection("umbracoConfiguration/FileSystemProviders");
|
_config = (FileSystemProvidersSection) ConfigurationManager.GetSection("umbracoConfiguration/FileSystemProviders");
|
||||||
@@ -52,13 +59,13 @@ namespace Umbraco.Core.IO
|
|||||||
_masterPagesFileSystem = new PhysicalFileSystem(SystemDirectories.Masterpages);
|
_masterPagesFileSystem = new PhysicalFileSystem(SystemDirectories.Masterpages);
|
||||||
_mvcViewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews);
|
_mvcViewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews);
|
||||||
|
|
||||||
_macroPartialFileSystem = _macroPartialFileSystemWrapper = new ShadowWrapper(_macroPartialFileSystem, "Views/MacroPartials");
|
_macroPartialFileSystem = _macroPartialFileSystemWrapper = new ShadowWrapper(_macroPartialFileSystem, "Views/MacroPartials", ScopeProvider);
|
||||||
_partialViewsFileSystem = _partialViewsFileSystemWrapper = new ShadowWrapper(_partialViewsFileSystem, "Views/Partials");
|
_partialViewsFileSystem = _partialViewsFileSystemWrapper = new ShadowWrapper(_partialViewsFileSystem, "Views/Partials", ScopeProvider);
|
||||||
_stylesheetsFileSystem = _stylesheetsFileSystemWrapper = new ShadowWrapper(_stylesheetsFileSystem, "css");
|
_stylesheetsFileSystem = _stylesheetsFileSystemWrapper = new ShadowWrapper(_stylesheetsFileSystem, "css", ScopeProvider);
|
||||||
_scriptsFileSystem = _scriptsFileSystemWrapper = new ShadowWrapper(_scriptsFileSystem, "scripts");
|
_scriptsFileSystem = _scriptsFileSystemWrapper = new ShadowWrapper(_scriptsFileSystem, "scripts", ScopeProvider);
|
||||||
_xsltFileSystem = _xsltFileSystemWrapper = new ShadowWrapper(_xsltFileSystem, "xslt");
|
_xsltFileSystem = _xsltFileSystemWrapper = new ShadowWrapper(_xsltFileSystem, "xslt", ScopeProvider);
|
||||||
_masterPagesFileSystem = _masterPagesFileSystemWrapper = new ShadowWrapper(_masterPagesFileSystem, "masterpages");
|
_masterPagesFileSystem = _masterPagesFileSystemWrapper = new ShadowWrapper(_masterPagesFileSystem, "masterpages", ScopeProvider);
|
||||||
_mvcViewsFileSystem = _mvcViewsFileSystemWrapper = new ShadowWrapper(_mvcViewsFileSystem, "Views");
|
_mvcViewsFileSystem = _mvcViewsFileSystemWrapper = new ShadowWrapper(_mvcViewsFileSystem, "Views", ScopeProvider);
|
||||||
|
|
||||||
// filesystems obtained from GetFileSystemProvider are already wrapped and do not need to be wrapped again
|
// filesystems obtained from GetFileSystemProvider are already wrapped and do not need to be wrapped again
|
||||||
MediaFileSystem = GetFileSystemProvider<MediaFileSystem>();
|
MediaFileSystem = GetFileSystemProvider<MediaFileSystem>();
|
||||||
@@ -92,7 +99,7 @@ namespace Umbraco.Core.IO
|
|||||||
}
|
}
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, ProviderConstructionInfo> _providerLookup = new ConcurrentDictionary<string, ProviderConstructionInfo>();
|
private readonly ConcurrentDictionary<string, ProviderConstructionInfo> _providerLookup = new ConcurrentDictionary<string, ProviderConstructionInfo>();
|
||||||
private readonly ConcurrentDictionary<Type, string> _aliases = new ConcurrentDictionary<Type, string>();
|
private readonly ConcurrentDictionary<Type, string> _aliases = new ConcurrentDictionary<Type, string>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an underlying (non-typed) filesystem supporting a strongly-typed filesystem.
|
/// Gets an underlying (non-typed) filesystem supporting a strongly-typed filesystem.
|
||||||
@@ -121,7 +128,7 @@ namespace Umbraco.Core.IO
|
|||||||
|
|
||||||
// find a ctor matching the config parameters
|
// find a ctor matching the config parameters
|
||||||
var paramCount = providerConfig.Parameters != null ? providerConfig.Parameters.Count : 0;
|
var paramCount = providerConfig.Parameters != null ? providerConfig.Parameters.Count : 0;
|
||||||
var constructor = providerType.GetConstructors().SingleOrDefault(x
|
var constructor = providerType.GetConstructors().SingleOrDefault(x
|
||||||
=> x.GetParameters().Length == paramCount && x.GetParameters().All(y => providerConfig.Parameters.AllKeys.Contains(y.Name)));
|
=> x.GetParameters().Length == paramCount && x.GetParameters().All(y => providerConfig.Parameters.AllKeys.Contains(y.Name)));
|
||||||
if (constructor == null)
|
if (constructor == null)
|
||||||
throw new InvalidOperationException(string.Format("Type {0} has no ctor matching the {1} configuration parameter(s).", providerType.FullName, paramCount));
|
throw new InvalidOperationException(string.Format("Type {0} has no ctor matching the {1} configuration parameter(s).", providerType.FullName, paramCount));
|
||||||
@@ -129,7 +136,7 @@ namespace Umbraco.Core.IO
|
|||||||
var parameters = new object[paramCount];
|
var parameters = new object[paramCount];
|
||||||
if (providerConfig.Parameters != null) // keeps ReSharper happy
|
if (providerConfig.Parameters != null) // keeps ReSharper happy
|
||||||
for (var i = 0; i < paramCount; i++)
|
for (var i = 0; i < paramCount; i++)
|
||||||
parameters[i] = providerConfig.Parameters[providerConfig.Parameters.AllKeys[i]].Value;
|
parameters[i] = providerConfig.Parameters[providerConfig.Parameters.AllKeys[i]].Value;
|
||||||
|
|
||||||
return new ProviderConstructionInfo
|
return new ProviderConstructionInfo
|
||||||
{
|
{
|
||||||
@@ -159,7 +166,7 @@ namespace Umbraco.Core.IO
|
|||||||
var alias = _aliases.GetOrAdd(typeof (TFileSystem), fsType =>
|
var alias = _aliases.GetOrAdd(typeof (TFileSystem), fsType =>
|
||||||
{
|
{
|
||||||
// validate the ctor
|
// validate the ctor
|
||||||
var constructor = fsType.GetConstructors().SingleOrDefault(x
|
var constructor = fsType.GetConstructors().SingleOrDefault(x
|
||||||
=> x.GetParameters().Length == 1 && TypeHelper.IsTypeAssignableFrom<IFileSystem>(x.GetParameters().Single().ParameterType));
|
=> x.GetParameters().Length == 1 && TypeHelper.IsTypeAssignableFrom<IFileSystem>(x.GetParameters().Single().ParameterType));
|
||||||
if (constructor == null)
|
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 + ".");
|
throw new InvalidOperationException("Type " + fsType.FullName + " must inherit from FileSystemWrapper and have a constructor that accepts one parameter of type " + typeof(IFileSystem).FullName + ".");
|
||||||
@@ -176,8 +183,8 @@ namespace Umbraco.Core.IO
|
|||||||
// so we are double-wrapping here
|
// so we are double-wrapping here
|
||||||
// could be optimized by having FileSystemWrapper inherit from ShadowWrapper, maybe
|
// could be optimized by having FileSystemWrapper inherit from ShadowWrapper, maybe
|
||||||
var innerFs = GetUnderlyingFileSystemProvider(alias);
|
var innerFs = GetUnderlyingFileSystemProvider(alias);
|
||||||
var shadowWrapper = new ShadowWrapper(innerFs, "typed/" + alias);
|
var shadowWrapper = new ShadowWrapper(innerFs, "typed/" + alias, ScopeProvider);
|
||||||
var fs = (TFileSystem) Activator.CreateInstance(typeof (TFileSystem), innerFs);
|
var fs = (TFileSystem) Activator.CreateInstance(typeof (TFileSystem), shadowWrapper);
|
||||||
_wrappers.Add(shadowWrapper); // keeping a weak reference to the wrapper
|
_wrappers.Add(shadowWrapper); // keeping a weak reference to the wrapper
|
||||||
return fs;
|
return fs;
|
||||||
}
|
}
|
||||||
@@ -186,25 +193,7 @@ namespace Umbraco.Core.IO
|
|||||||
|
|
||||||
#region Shadow
|
#region Shadow
|
||||||
|
|
||||||
// note
|
internal ICompletable Shadow(Guid id)
|
||||||
// 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 GetFileSystemProvider<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
|
|
||||||
// impact.
|
|
||||||
// NO! by the time an app event handler is instanciated 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;
|
|
||||||
//}
|
|
||||||
|
|
||||||
public ICompletable Shadow(Guid id)
|
|
||||||
{
|
{
|
||||||
var typed = _wrappers.ToArray();
|
var typed = _wrappers.ToArray();
|
||||||
var wrappers = new ShadowWrapper[typed.Length + 7];
|
var wrappers = new ShadowWrapper[typed.Length + 7];
|
||||||
@@ -218,7 +207,7 @@ namespace Umbraco.Core.IO
|
|||||||
wrappers[i++] = _masterPagesFileSystemWrapper;
|
wrappers[i++] = _masterPagesFileSystemWrapper;
|
||||||
wrappers[i] = _mvcViewsFileSystemWrapper;
|
wrappers[i] = _mvcViewsFileSystemWrapper;
|
||||||
|
|
||||||
return ShadowFileSystemsScope.CreateScope(id, wrappers);
|
return new ShadowFileSystems(id, wrappers);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
73
src/Umbraco.Core/IO/ShadowFileSystems.cs
Normal file
73
src/Umbraco.Core/IO/ShadowFileSystems.cs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Umbraco.Core.Logging;
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
private static readonly object Locker = new object();
|
||||||
|
private static Guid _currentId = Guid.Empty;
|
||||||
|
private readonly Guid _id;
|
||||||
|
private readonly ShadowWrapper[] _wrappers;
|
||||||
|
private bool _completed;
|
||||||
|
|
||||||
|
public ShadowFileSystems(Guid id, ShadowWrapper[] wrappers)
|
||||||
|
{
|
||||||
|
lock (Locker)
|
||||||
|
{
|
||||||
|
if (_currentId != Guid.Empty)
|
||||||
|
throw new InvalidOperationException("Already shadowing.");
|
||||||
|
_currentId = id;
|
||||||
|
|
||||||
|
LogHelper.Debug<ShadowFileSystems>("Shadow " + id + ".");
|
||||||
|
_id = id;
|
||||||
|
_wrappers = wrappers;
|
||||||
|
foreach (var wrapper in _wrappers)
|
||||||
|
wrapper.Shadow(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Complete()
|
||||||
|
{
|
||||||
|
_completed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
lock (Locker)
|
||||||
|
{
|
||||||
|
LogHelper.Debug<ShadowFileSystems>("UnShadow " + _id + " (" + (_completed ? "complete" : "abort") + ").");
|
||||||
|
|
||||||
|
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(_completed);
|
||||||
|
}
|
||||||
|
catch (AggregateException ae)
|
||||||
|
{
|
||||||
|
exceptions.Add(ae);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentId = Guid.Empty;
|
||||||
|
|
||||||
|
if (exceptions.Count > 0)
|
||||||
|
throw new AggregateException(_completed ? "Failed to apply all changes (see exceptions)." : "Failed to abort (see exceptions).", exceptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for tests
|
||||||
|
internal static void ResetId()
|
||||||
|
{
|
||||||
|
_currentId = Guid.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.Remoting.Messaging;
|
|
||||||
using Umbraco.Core.Logging;
|
|
||||||
|
|
||||||
namespace Umbraco.Core.IO
|
|
||||||
{
|
|
||||||
internal class ShadowFileSystemsScope : 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)
|
|
||||||
|
|
||||||
private const string ItemKey = "Umbraco.Core.IO.ShadowFileSystemsScope";
|
|
||||||
private static readonly object Locker = new object();
|
|
||||||
private readonly Guid _id;
|
|
||||||
private readonly ShadowWrapper[] _wrappers;
|
|
||||||
|
|
||||||
static ShadowFileSystemsScope()
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private ShadowFileSystemsScope(Guid id, ShadowWrapper[] wrappers)
|
|
||||||
{
|
|
||||||
LogHelper.Debug<ShadowFileSystemsScope>("Shadow " + id + ".");
|
|
||||||
_id = id;
|
|
||||||
_wrappers = wrappers;
|
|
||||||
foreach (var wrapper in _wrappers)
|
|
||||||
wrapper.Shadow(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// internal for tests + FileSystemProviderManager
|
|
||||||
// do NOT use otherwise
|
|
||||||
internal static ShadowFileSystemsScope CreateScope(Guid id, ShadowWrapper[] wrappers)
|
|
||||||
{
|
|
||||||
lock (Locker)
|
|
||||||
{
|
|
||||||
if (CallContext.LogicalGetData(ItemKey) != null) throw new InvalidOperationException("Already shadowing.");
|
|
||||||
CallContext.LogicalSetData(ItemKey, ItemKey); // value does not matter
|
|
||||||
}
|
|
||||||
return new ShadowFileSystemsScope(id, wrappers);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static bool InScope
|
|
||||||
{
|
|
||||||
get { return NoScope == false; }
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static bool NoScope
|
|
||||||
{
|
|
||||||
get { return CallContext.LogicalGetData(ItemKey) == null; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Complete()
|
|
||||||
{
|
|
||||||
lock (Locker)
|
|
||||||
{
|
|
||||||
LogHelper.Debug<ShadowFileSystemsScope>("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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
lock (Locker)
|
|
||||||
{
|
|
||||||
if (CallContext.LogicalGetData(ItemKey) == null) return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
LogHelper.Debug<ShadowFileSystemsScope>("UnShadow " + _id + " (abort)");
|
|
||||||
foreach (var wrapper in _wrappers)
|
|
||||||
wrapper.UnShadow(false); // should not throw
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
// last, & always
|
|
||||||
CallContext.FreeNamedDataSlot(ItemKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,20 +2,23 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Umbraco.Core.Scoping;
|
||||||
|
|
||||||
namespace Umbraco.Core.IO
|
namespace Umbraco.Core.IO
|
||||||
{
|
{
|
||||||
internal class ShadowWrapper : IFileSystem2
|
internal class ShadowWrapper : IFileSystem2
|
||||||
{
|
{
|
||||||
|
private readonly IScopeProviderInternal _scopeProvider;
|
||||||
private readonly IFileSystem _innerFileSystem;
|
private readonly IFileSystem _innerFileSystem;
|
||||||
private readonly string _shadowPath;
|
private readonly string _shadowPath;
|
||||||
private ShadowFileSystem _shadowFileSystem;
|
private ShadowFileSystem _shadowFileSystem;
|
||||||
private string _shadowDir;
|
private string _shadowDir;
|
||||||
|
|
||||||
public ShadowWrapper(IFileSystem innerFileSystem, string shadowPath)
|
public ShadowWrapper(IFileSystem innerFileSystem, string shadowPath, IScopeProviderInternal scopeProvider)
|
||||||
{
|
{
|
||||||
_innerFileSystem = innerFileSystem;
|
_innerFileSystem = innerFileSystem;
|
||||||
_shadowPath = shadowPath;
|
_shadowPath = shadowPath;
|
||||||
|
_scopeProvider = scopeProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Shadow(Guid id)
|
internal void Shadow(Guid id)
|
||||||
@@ -62,7 +65,14 @@ namespace Umbraco.Core.IO
|
|||||||
|
|
||||||
private IFileSystem FileSystem
|
private IFileSystem FileSystem
|
||||||
{
|
{
|
||||||
get { return ShadowFileSystemsScope.NoScope ? _innerFileSystem : _shadowFileSystem; }
|
get
|
||||||
|
{
|
||||||
|
var isScoped = _scopeProvider != null && _scopeProvider.AmbientScope != null && _scopeProvider.AmbientScope.ScopedFileSystems;
|
||||||
|
|
||||||
|
return isScoped
|
||||||
|
? _shadowFileSystem
|
||||||
|
: _innerFileSystem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<string> GetDirectories(string path)
|
public IEnumerable<string> GetDirectories(string path)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Umbraco.Core.Cache;
|
using Umbraco.Core.Cache;
|
||||||
using Umbraco.Core.Events;
|
using Umbraco.Core.Events;
|
||||||
using Umbraco.Core.Persistence;
|
using Umbraco.Core.Persistence;
|
||||||
|
|||||||
18
src/Umbraco.Core/Scoping/IScopeInternal.cs
Normal file
18
src/Umbraco.Core/Scoping/IScopeInternal.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System.Data;
|
||||||
|
using Umbraco.Core.Events;
|
||||||
|
using Umbraco.Core.Persistence;
|
||||||
|
|
||||||
|
namespace Umbraco.Core.Scoping
|
||||||
|
{
|
||||||
|
internal interface IScopeInternal : IScope
|
||||||
|
{
|
||||||
|
IScopeInternal ParentScope { get; }
|
||||||
|
EventsDispatchMode DispatchMode { get; }
|
||||||
|
IsolationLevel IsolationLevel { get; }
|
||||||
|
UmbracoDatabase DatabaseOrNull { get; }
|
||||||
|
EventMessages MessagesOrNull { get; }
|
||||||
|
bool ScopedFileSystems { get; }
|
||||||
|
void ChildCompleted(bool? completed);
|
||||||
|
void Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ namespace Umbraco.Core.Scoping
|
|||||||
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
|
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
|
||||||
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
|
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
|
||||||
EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified,
|
EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified,
|
||||||
bool scopeFileSystems = false);
|
bool? scopeFileSystems = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a detached scope.
|
/// Creates a detached scope.
|
||||||
@@ -35,7 +35,7 @@ namespace Umbraco.Core.Scoping
|
|||||||
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
|
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
|
||||||
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
|
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
|
||||||
EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified,
|
EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified,
|
||||||
bool scopeFileSystems = false);
|
bool? scopeFileSystems = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attaches a scope.
|
/// Attaches a scope.
|
||||||
|
|||||||
@@ -14,12 +14,12 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the ambient scope.
|
/// Gets the ambient scope.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IScope AmbientScope { get; }
|
IScopeInternal AmbientScope { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the ambient scope if any, else creates and returns a <see cref="NoScope"/>.
|
/// Gets the ambient scope if any, else creates and returns a <see cref="NoScope"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IScope GetAmbientOrNoScope();
|
IScopeInternal GetAmbientOrNoScope();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resets the ambient scope.
|
/// Resets the ambient scope.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Data;
|
||||||
using Umbraco.Core.Cache;
|
using Umbraco.Core.Cache;
|
||||||
using Umbraco.Core.Events;
|
using Umbraco.Core.Events;
|
||||||
using Umbraco.Core.Persistence;
|
using Umbraco.Core.Persistence;
|
||||||
@@ -8,7 +9,7 @@ namespace Umbraco.Core.Scoping
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Implements <see cref="IScope"/> when there is no scope.
|
/// Implements <see cref="IScope"/> when there is no scope.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class NoScope : IScope
|
internal class NoScope : IScopeInternal
|
||||||
{
|
{
|
||||||
private readonly ScopeProvider _scopeProvider;
|
private readonly ScopeProvider _scopeProvider;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
@@ -105,5 +106,12 @@ namespace Umbraco.Core.Scoping
|
|||||||
_disposed = true;
|
_disposed = true;
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IScopeInternal ParentScope { get { return null; } }
|
||||||
|
public EventsDispatchMode DispatchMode { get {return EventsDispatchMode.Unspecified; } }
|
||||||
|
public IsolationLevel IsolationLevel { get {return IsolationLevel.Unspecified; } }
|
||||||
|
public bool ScopedFileSystems { get { return false; } }
|
||||||
|
public void ChildCompleted(bool? completed) { }
|
||||||
|
public void Reset() { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using Umbraco.Core.Cache;
|
using Umbraco.Core.Cache;
|
||||||
using Umbraco.Core.Events;
|
using Umbraco.Core.Events;
|
||||||
|
using Umbraco.Core.IO;
|
||||||
using Umbraco.Core.Persistence;
|
using Umbraco.Core.Persistence;
|
||||||
|
|
||||||
namespace Umbraco.Core.Scoping
|
namespace Umbraco.Core.Scoping
|
||||||
@@ -11,7 +11,7 @@ namespace Umbraco.Core.Scoping
|
|||||||
/// Implements <see cref="IScope"/>.
|
/// Implements <see cref="IScope"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>Not thread-safe obviously.</remarks>
|
/// <remarks>Not thread-safe obviously.</remarks>
|
||||||
internal class Scope : IScope
|
internal class Scope : IScopeInternal
|
||||||
{
|
{
|
||||||
private readonly ScopeProvider _scopeProvider;
|
private readonly ScopeProvider _scopeProvider;
|
||||||
private readonly IsolationLevel _isolationLevel;
|
private readonly IsolationLevel _isolationLevel;
|
||||||
@@ -24,14 +24,15 @@ namespace Umbraco.Core.Scoping
|
|||||||
|
|
||||||
private IsolatedRuntimeCache _isolatedRuntimeCache;
|
private IsolatedRuntimeCache _isolatedRuntimeCache;
|
||||||
private UmbracoDatabase _database;
|
private UmbracoDatabase _database;
|
||||||
private IEventDispatcher _eventDispatcher;
|
private ICompletable _fscope;
|
||||||
|
private IEventDispatcher _eventDispatcher;
|
||||||
|
|
||||||
// this is v7, in v8 this has to change to RepeatableRead
|
// this is v7, in v8 this has to change to RepeatableRead
|
||||||
private const IsolationLevel DefaultIsolationLevel = IsolationLevel.ReadCommitted;
|
private const IsolationLevel DefaultIsolationLevel = IsolationLevel.ReadCommitted;
|
||||||
|
|
||||||
// initializes a new scope
|
// initializes a new scope
|
||||||
public Scope(ScopeProvider scopeProvider, bool detachable,
|
private Scope(ScopeProvider scopeProvider,
|
||||||
ScopeContext scopeContext,
|
Scope parent, ScopeContext scopeContext, bool detachable,
|
||||||
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
|
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
|
||||||
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
|
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
|
||||||
EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified,
|
EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified,
|
||||||
@@ -44,10 +45,62 @@ namespace Umbraco.Core.Scoping
|
|||||||
_dispatchMode = dispatchMode;
|
_dispatchMode = dispatchMode;
|
||||||
_scopeFileSystem = scopeFileSystems;
|
_scopeFileSystem = scopeFileSystems;
|
||||||
Detachable = detachable;
|
Detachable = detachable;
|
||||||
|
|
||||||
#if DEBUG_SCOPES
|
#if DEBUG_SCOPES
|
||||||
_scopeProvider.Register(this);
|
_scopeProvider.Register(this);
|
||||||
Console.WriteLine("create " + _instanceId.ToString("N").Substring(0, 8));
|
Console.WriteLine("create " + _instanceId.ToString("N").Substring(0, 8));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (detachable)
|
||||||
|
{
|
||||||
|
if (parent != null) throw new ArgumentException("Cannot set parent on detachable scope.", "parent");
|
||||||
|
if (scopeContext != null) throw new ArgumentException("Cannot set context on detachable scope.", "scopeContext");
|
||||||
|
|
||||||
|
// detachable creates its own scope context
|
||||||
|
_scopeContext = new ScopeContext();
|
||||||
|
|
||||||
|
// see note below
|
||||||
|
if (scopeFileSystems == true)
|
||||||
|
_fscope = FileSystemProviderManager.Current.Shadow(Guid.NewGuid());
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent != null)
|
||||||
|
{
|
||||||
|
ParentScope = parent;
|
||||||
|
|
||||||
|
// cannot specify a different mode!
|
||||||
|
if (repositoryCacheMode != RepositoryCacheMode.Unspecified && parent.RepositoryCacheMode != repositoryCacheMode)
|
||||||
|
throw new ArgumentException("Cannot be different from parent.", "repositoryCacheMode");
|
||||||
|
|
||||||
|
// cannot specify a different mode!
|
||||||
|
if (_dispatchMode != EventsDispatchMode.Unspecified && parent._dispatchMode != dispatchMode)
|
||||||
|
throw new ArgumentException("Cannot be different from parent.", "dispatchMode");
|
||||||
|
|
||||||
|
// cannot specify a different fs scope!
|
||||||
|
if (scopeFileSystems != null && parent._scopeFileSystem != scopeFileSystems)
|
||||||
|
throw new ArgumentException("Cannot be different from parent.", "scopeFileSystems");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// the FS scope cannot be "on demand" like the rest, because we would need to hook into
|
||||||
|
// every scoped FS to trigger the creation of shadow FS "on demand", and that would be
|
||||||
|
// pretty pointless since if scopeFileSystems is true, we *know* we want to shadow
|
||||||
|
if (scopeFileSystems == true)
|
||||||
|
_fscope = FileSystemProviderManager.Current.Shadow(Guid.NewGuid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initializes a new scope
|
||||||
|
public Scope(ScopeProvider scopeProvider, bool detachable,
|
||||||
|
ScopeContext scopeContext,
|
||||||
|
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
|
||||||
|
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
|
||||||
|
EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified,
|
||||||
|
bool? scopeFileSystems = null)
|
||||||
|
: this(scopeProvider, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
// initializes a new scope in a nested scopes chain, with its parent
|
// initializes a new scope in a nested scopes chain, with its parent
|
||||||
@@ -56,38 +109,25 @@ namespace Umbraco.Core.Scoping
|
|||||||
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
|
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
|
||||||
EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified,
|
EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified,
|
||||||
bool? scopeFileSystems = null)
|
bool? scopeFileSystems = null)
|
||||||
: this(scopeProvider, false, null, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems)
|
: this(scopeProvider, parent, null, false, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems)
|
||||||
{
|
{
|
||||||
ParentScope = parent;
|
|
||||||
|
|
||||||
// cannot specify a different mode!
|
|
||||||
if (repositoryCacheMode != RepositoryCacheMode.Unspecified && parent.RepositoryCacheMode != repositoryCacheMode)
|
|
||||||
throw new ArgumentException("Cannot be different from parent.", "repositoryCacheMode");
|
|
||||||
|
|
||||||
// cannot specify a different mode!
|
|
||||||
if (_dispatchMode != EventsDispatchMode.Unspecified && parent._dispatchMode != dispatchMode)
|
|
||||||
throw new ArgumentException("Cannot be different from parent.", "dispatchMode");
|
|
||||||
|
|
||||||
// cannot specify a different fs scope!
|
|
||||||
if (scopeFileSystems != null && parent._scopeFileSystem != scopeFileSystems)
|
|
||||||
throw new ArgumentException("Cannot be different from parent.", "scopeFileSystems");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// initializes a new scope, replacing a NoScope instance
|
// initializes a new scope, replacing a NoScope instance
|
||||||
public Scope(ScopeProvider scopeProvider, NoScope noScope,
|
public Scope(ScopeProvider scopeProvider, NoScope noScope,
|
||||||
ScopeContext scopeContext,
|
ScopeContext scopeContext,
|
||||||
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
|
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
|
||||||
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
|
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
|
||||||
EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified,
|
EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified,
|
||||||
bool? scopeFileSystems = null)
|
bool? scopeFileSystems = null)
|
||||||
: this(scopeProvider, false, scopeContext, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems)
|
: this(scopeProvider, null, scopeContext, false, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems)
|
||||||
{
|
{
|
||||||
// steal everything from NoScope
|
// steal everything from NoScope
|
||||||
_database = noScope.DatabaseOrNull;
|
_database = noScope.DatabaseOrNull;
|
||||||
|
|
||||||
// make sure the NoScope can be replaced ie not in a transaction
|
// make sure the NoScope can be replaced ie not in a transaction
|
||||||
if (_database != null && _database.InTransaction)
|
if (_database != null && _database.InTransaction)
|
||||||
throw new Exception("NoScope instance is not free.");
|
throw new Exception("NoScope instance is not free.");
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG_SCOPES
|
#if DEBUG_SCOPES
|
||||||
@@ -95,7 +135,16 @@ namespace Umbraco.Core.Scoping
|
|||||||
public Guid InstanceId { get { return _instanceId; } }
|
public Guid InstanceId { get { return _instanceId; } }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private EventsDispatchMode DispatchMode
|
public bool ScopedFileSystems
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (ParentScope != null) return ParentScope.ScopedFileSystems;
|
||||||
|
return _fscope != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventsDispatchMode DispatchMode
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
@@ -124,7 +173,7 @@ namespace Umbraco.Core.Scoping
|
|||||||
if (ParentScope != null) return ParentScope.IsolatedRuntimeCache;
|
if (ParentScope != null) return ParentScope.IsolatedRuntimeCache;
|
||||||
|
|
||||||
return _isolatedRuntimeCache ?? (_isolatedRuntimeCache
|
return _isolatedRuntimeCache ?? (_isolatedRuntimeCache
|
||||||
= new IsolatedRuntimeCache(type => new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider())));
|
= new IsolatedRuntimeCache(type => new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,12 +182,21 @@ namespace Umbraco.Core.Scoping
|
|||||||
public bool Detachable { get; private set; }
|
public bool Detachable { get; private set; }
|
||||||
|
|
||||||
// the parent scope (in a nested scopes chain)
|
// the parent scope (in a nested scopes chain)
|
||||||
public Scope ParentScope { get; set; }
|
public IScopeInternal ParentScope { get; set; }
|
||||||
|
|
||||||
// the original scope (when attaching a detachable scope)
|
// the original scope (when attaching a detachable scope)
|
||||||
public IScope OrigScope { get; set; }
|
public IScopeInternal OrigScope { get; set; }
|
||||||
|
|
||||||
private IsolationLevel IsolationLevel
|
// the original context (when attaching a detachable scope)
|
||||||
|
public ScopeContext OrigContext { get; set; }
|
||||||
|
|
||||||
|
// the context (for attaching & detaching only)
|
||||||
|
public ScopeContext Context
|
||||||
|
{
|
||||||
|
get { return _scopeContext; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public IsolationLevel IsolationLevel
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
@@ -286,8 +344,11 @@ namespace Umbraco.Core.Scoping
|
|||||||
|
|
||||||
private void DisposeLastScope()
|
private void DisposeLastScope()
|
||||||
{
|
{
|
||||||
|
// figure out completed
|
||||||
var completed = _completed.HasValue && _completed.Value;
|
var completed = _completed.HasValue && _completed.Value;
|
||||||
|
|
||||||
|
// deal with database
|
||||||
|
bool ex = false;
|
||||||
if (_database != null)
|
if (_database != null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -297,28 +358,82 @@ namespace Umbraco.Core.Scoping
|
|||||||
else
|
else
|
||||||
_database.AbortTransaction();
|
_database.AbortTransaction();
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
ex = true;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_database.Dispose();
|
_database.Dispose();
|
||||||
_database = null;
|
_database = null;
|
||||||
|
|
||||||
|
if (ex)
|
||||||
|
RobustExit(false, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// deal with events
|
RobustExit(completed, false);
|
||||||
if (_eventDispatcher != null)
|
}
|
||||||
_eventDispatcher.ScopeExit(completed);
|
|
||||||
|
|
||||||
// if *we* created it, then get rid of it
|
private void RobustExit(bool completed, bool kabum)
|
||||||
if (_scopeProvider.AmbientContext == _scopeContext)
|
{
|
||||||
|
if (kabum) completed = false;
|
||||||
|
|
||||||
|
TryFinally(() =>
|
||||||
{
|
{
|
||||||
try
|
if (_scopeFileSystem == true)
|
||||||
{
|
{
|
||||||
_scopeProvider.AmbientContext.ScopeExit(completed);
|
if (completed)
|
||||||
|
_fscope.Complete();
|
||||||
|
_fscope.Dispose();
|
||||||
|
_fscope = null;
|
||||||
}
|
}
|
||||||
finally
|
}, () =>
|
||||||
|
{
|
||||||
|
// deal with events
|
||||||
|
if (kabum == false && _eventDispatcher != null)
|
||||||
|
_eventDispatcher.ScopeExit(completed);
|
||||||
|
}, () =>
|
||||||
|
{
|
||||||
|
// if *we* created it, then get rid of it
|
||||||
|
if (_scopeProvider.AmbientContext == _scopeContext)
|
||||||
{
|
{
|
||||||
_scopeProvider.AmbientContext = null;
|
try
|
||||||
|
{
|
||||||
|
_scopeProvider.AmbientContext.ScopeExit(completed);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_scopeProvider.AmbientContext = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}, () =>
|
||||||
|
{
|
||||||
|
if (Detachable)
|
||||||
|
{
|
||||||
|
// get out of the way, restore original
|
||||||
|
_scopeProvider.AmbientScope = OrigScope;
|
||||||
|
_scopeProvider.AmbientContext = OrigContext;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TryFinally(params Action[] actions)
|
||||||
|
{
|
||||||
|
TryFinally(0, actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TryFinally(int index, Action[] actions)
|
||||||
|
{
|
||||||
|
if (index == actions.Length) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
actions[index]();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
TryFinally(index + 1, actions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,15 +25,20 @@ namespace Umbraco.Core.Scoping
|
|||||||
() =>
|
() =>
|
||||||
{
|
{
|
||||||
var scope = StaticAmbientScope;
|
var scope = StaticAmbientScope;
|
||||||
|
var context = StaticAmbientContext;
|
||||||
StaticAmbientScope = null;
|
StaticAmbientScope = null;
|
||||||
return scope;
|
StaticAmbientContext = null;
|
||||||
|
return Tuple.Create(scope, context);
|
||||||
},
|
},
|
||||||
scope =>
|
o =>
|
||||||
{
|
{
|
||||||
var ambient = StaticAmbientScope;
|
// cannot re-attached over leaked scope/context
|
||||||
if (ambient != null)
|
if (StaticAmbientScope != null) throw new Exception("Found leaked scope when restoring call context.");
|
||||||
ambient.Dispose();
|
if (StaticAmbientContext != null) throw new Exception("Found leaked context when restoring call context.");
|
||||||
StaticAmbientScope = (IScope)scope;
|
|
||||||
|
var t = (Tuple<IScopeInternal, ScopeContext>)o;
|
||||||
|
StaticAmbientScope = t.Item1;
|
||||||
|
StaticAmbientContext = t.Item2;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,9 +99,9 @@ namespace Umbraco.Core.Scoping
|
|||||||
// only 1 instance which can be disposed and disposed again
|
// only 1 instance which can be disposed and disposed again
|
||||||
private static readonly ScopeReference StaticScopeReference = new ScopeReference(new ScopeProvider(null));
|
private static readonly ScopeReference StaticScopeReference = new ScopeReference(new ScopeProvider(null));
|
||||||
|
|
||||||
private static IScope CallContextValue
|
private static IScopeInternal CallContextValue
|
||||||
{
|
{
|
||||||
get { return (IScope) CallContext.LogicalGetData(ScopeItemKey); }
|
get { return (IScopeInternal) CallContext.LogicalGetData(ScopeItemKey); }
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
#if DEBUG_SCOPES
|
#if DEBUG_SCOPES
|
||||||
@@ -110,9 +115,9 @@ namespace Umbraco.Core.Scoping
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IScope HttpContextValue
|
private static IScopeInternal HttpContextValue
|
||||||
{
|
{
|
||||||
get { return (IScope) HttpContext.Current.Items[ScopeItemKey]; }
|
get { return (IScopeInternal) HttpContext.Current.Items[ScopeItemKey]; }
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
#if DEBUG_SCOPES
|
#if DEBUG_SCOPES
|
||||||
@@ -135,7 +140,7 @@ namespace Umbraco.Core.Scoping
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IScope StaticAmbientScope
|
private static IScopeInternal StaticAmbientScope
|
||||||
{
|
{
|
||||||
get { return HttpContext.Current == null ? CallContextValue : HttpContextValue; }
|
get { return HttpContext.Current == null ? CallContextValue : HttpContextValue; }
|
||||||
set
|
set
|
||||||
@@ -148,14 +153,14 @@ namespace Umbraco.Core.Scoping
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IScope AmbientScope
|
public IScopeInternal AmbientScope
|
||||||
{
|
{
|
||||||
get { return StaticAmbientScope; }
|
get { return StaticAmbientScope; }
|
||||||
set { StaticAmbientScope = value; }
|
set { StaticAmbientScope = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IScope GetAmbientOrNoScope()
|
public IScopeInternal GetAmbientOrNoScope()
|
||||||
{
|
{
|
||||||
return AmbientScope ?? (AmbientScope = new NoScope(this));
|
return AmbientScope ?? (AmbientScope = new NoScope(this));
|
||||||
}
|
}
|
||||||
@@ -184,7 +189,9 @@ namespace Umbraco.Core.Scoping
|
|||||||
throw new ArgumentException("Not a detachable scope.");
|
throw new ArgumentException("Not a detachable scope.");
|
||||||
|
|
||||||
otherScope.OrigScope = AmbientScope;
|
otherScope.OrigScope = AmbientScope;
|
||||||
|
otherScope.OrigContext = AmbientContext;
|
||||||
AmbientScope = otherScope;
|
AmbientScope = otherScope;
|
||||||
|
AmbientContext = otherScope.Context;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -206,7 +213,9 @@ namespace Umbraco.Core.Scoping
|
|||||||
throw new InvalidOperationException("Ambient scope is not detachable.");
|
throw new InvalidOperationException("Ambient scope is not detachable.");
|
||||||
|
|
||||||
AmbientScope = scope.OrigScope;
|
AmbientScope = scope.OrigScope;
|
||||||
|
AmbientContext = scope.OrigContext;
|
||||||
scope.OrigScope = null;
|
scope.OrigScope = null;
|
||||||
|
scope.OrigContext = null;
|
||||||
return scope;
|
return scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,7 +229,10 @@ namespace Umbraco.Core.Scoping
|
|||||||
var ambient = AmbientScope;
|
var ambient = AmbientScope;
|
||||||
if (ambient == null)
|
if (ambient == null)
|
||||||
{
|
{
|
||||||
return AmbientScope = new Scope(this, false, GetNewContext(), isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems);
|
var context = AmbientContext == null ? new ScopeContext() : null;
|
||||||
|
var scope = new Scope(this, false, context, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems);
|
||||||
|
if (AmbientContext == null) AmbientContext = context; // assign only if scope creation did not throw!
|
||||||
|
return AmbientScope = scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
// replace noScope with a real one
|
// replace noScope with a real one
|
||||||
@@ -234,20 +246,16 @@ namespace Umbraco.Core.Scoping
|
|||||||
var database = noScope.DatabaseOrNull;
|
var database = noScope.DatabaseOrNull;
|
||||||
if (database != null && database.InTransaction)
|
if (database != null && database.InTransaction)
|
||||||
throw new Exception("NoScope is in a transaction.");
|
throw new Exception("NoScope is in a transaction.");
|
||||||
return AmbientScope = new Scope(this, noScope, GetNewContext(), isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems);
|
var context = AmbientContext == null ? new ScopeContext() : null;
|
||||||
|
var scope = new Scope(this, noScope, context, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems);
|
||||||
|
if (AmbientContext == null) AmbientContext = context; // assign only if scope creation did not throw!
|
||||||
|
return AmbientScope = scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
var scope = ambient as Scope;
|
var ambientScope = ambient as Scope;
|
||||||
if (scope == null) throw new Exception("Ambient scope is not a Scope instance.");
|
if (ambientScope == null) throw new Exception("Ambient scope is not a Scope instance.");
|
||||||
|
|
||||||
return AmbientScope = new Scope(this, scope, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems);
|
return AmbientScope = new Scope(this, ambientScope, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems);
|
||||||
}
|
|
||||||
|
|
||||||
private ScopeContext GetNewContext()
|
|
||||||
{
|
|
||||||
return AmbientContext == null
|
|
||||||
? AmbientContext = new ScopeContext()
|
|
||||||
: null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@@ -19,11 +19,10 @@
|
|||||||
{
|
{
|
||||||
// dispose the entire chain (if any)
|
// dispose the entire chain (if any)
|
||||||
// reset (don't commit by default)
|
// reset (don't commit by default)
|
||||||
IScope scope;
|
IScopeInternal scope;
|
||||||
while ((scope = _scopeProvider.AmbientScope) != null)
|
while ((scope = _scopeProvider.AmbientScope) != null)
|
||||||
{
|
{
|
||||||
if (scope is Scope)
|
scope.Reset();
|
||||||
((Scope) scope).Reset();
|
|
||||||
scope.Dispose();
|
scope.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -354,7 +354,7 @@
|
|||||||
<Compile Include="ICompletable.cs" />
|
<Compile Include="ICompletable.cs" />
|
||||||
<Compile Include="IDisposeOnRequestEnd.cs" />
|
<Compile Include="IDisposeOnRequestEnd.cs" />
|
||||||
<Compile Include="IO\ShadowFileSystem.cs" />
|
<Compile Include="IO\ShadowFileSystem.cs" />
|
||||||
<Compile Include="IO\ShadowFileSystemsScope.cs" />
|
<Compile Include="IO\ShadowFileSystems.cs" />
|
||||||
<Compile Include="IO\ShadowWrapper.cs" />
|
<Compile Include="IO\ShadowWrapper.cs" />
|
||||||
<Compile Include="Logging\AsyncForwardingAppenderBase.cs" />
|
<Compile Include="Logging\AsyncForwardingAppenderBase.cs" />
|
||||||
<Compile Include="Logging\ImageProcessorLogger.cs" />
|
<Compile Include="Logging\ImageProcessorLogger.cs" />
|
||||||
@@ -525,6 +525,7 @@
|
|||||||
<Compile Include="SafeCallContext.cs" />
|
<Compile Include="SafeCallContext.cs" />
|
||||||
<Compile Include="Scoping\ActionTime.cs" />
|
<Compile Include="Scoping\ActionTime.cs" />
|
||||||
<Compile Include="Scoping\IScope.cs" />
|
<Compile Include="Scoping\IScope.cs" />
|
||||||
|
<Compile Include="Scoping\IScopeInternal.cs" />
|
||||||
<Compile Include="Scoping\IScopeProvider.cs" />
|
<Compile Include="Scoping\IScopeProvider.cs" />
|
||||||
<Compile Include="Scoping\IScopeProviderInternal.cs" />
|
<Compile Include="Scoping\IScopeProviderInternal.cs" />
|
||||||
<Compile Include="Scoping\NoScope.cs" />
|
<Compile Include="Scoping\NoScope.cs" />
|
||||||
|
|||||||
@@ -2,10 +2,11 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Umbraco.Core;
|
using Umbraco.Core;
|
||||||
using Umbraco.Core.IO;
|
using Umbraco.Core.IO;
|
||||||
|
using Umbraco.Core.Scoping;
|
||||||
using Umbraco.Tests.TestHelpers;
|
using Umbraco.Tests.TestHelpers;
|
||||||
|
|
||||||
namespace Umbraco.Tests.IO
|
namespace Umbraco.Tests.IO
|
||||||
@@ -23,6 +24,7 @@ namespace Umbraco.Tests.IO
|
|||||||
{
|
{
|
||||||
SafeCallContext.Clear();
|
SafeCallContext.Clear();
|
||||||
ClearFiles();
|
ClearFiles();
|
||||||
|
ShadowFileSystems.ResetId();
|
||||||
}
|
}
|
||||||
|
|
||||||
[TearDown]
|
[TearDown]
|
||||||
@@ -30,6 +32,7 @@ namespace Umbraco.Tests.IO
|
|||||||
{
|
{
|
||||||
SafeCallContext.Clear();
|
SafeCallContext.Clear();
|
||||||
ClearFiles();
|
ClearFiles();
|
||||||
|
ShadowFileSystems.ResetId();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ClearFiles()
|
private static void ClearFiles()
|
||||||
@@ -373,8 +376,11 @@ namespace Umbraco.Tests.IO
|
|||||||
var appdata = IOHelper.MapPath("App_Data");
|
var appdata = IOHelper.MapPath("App_Data");
|
||||||
Directory.CreateDirectory(path);
|
Directory.CreateDirectory(path);
|
||||||
|
|
||||||
|
var scopedFileSystems = false;
|
||||||
|
var scopeProvider = MockScopeProvider(() => scopedFileSystems);
|
||||||
|
|
||||||
var fs = new PhysicalFileSystem(path, "ignore");
|
var fs = new PhysicalFileSystem(path, "ignore");
|
||||||
var sw = new ShadowWrapper(fs, "shadow");
|
var sw = new ShadowWrapper(fs, "shadow", scopeProvider);
|
||||||
var swa = new[] { sw };
|
var swa = new[] { sw };
|
||||||
|
|
||||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
||||||
@@ -394,46 +400,52 @@ namespace Umbraco.Tests.IO
|
|||||||
Assert.IsFalse(Directory.Exists(appdata + "/Shadow/" + id));
|
Assert.IsFalse(Directory.Exists(appdata + "/Shadow/" + id));
|
||||||
|
|
||||||
// shadow with scope but no complete does not complete
|
// shadow with scope but no complete does not complete
|
||||||
var scope = ShadowFileSystemsScope.CreateScope(id = Guid.NewGuid(), swa);
|
scopedFileSystems = true; // pretend we have a scope
|
||||||
|
var scope = new ShadowFileSystems(id = Guid.NewGuid(), swa);
|
||||||
Assert.IsTrue(Directory.Exists(appdata + "/Shadow/" + id));
|
Assert.IsTrue(Directory.Exists(appdata + "/Shadow/" + id));
|
||||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
||||||
sw.AddFile("sub/f3.txt", ms);
|
sw.AddFile("sub/f3.txt", ms);
|
||||||
Assert.IsFalse(fs.FileExists("sub/f3.txt"));
|
Assert.IsFalse(fs.FileExists("sub/f3.txt"));
|
||||||
Assert.AreEqual(1, Directory.GetDirectories(appdata + "/Shadow").Length);
|
Assert.AreEqual(1, Directory.GetDirectories(appdata + "/Shadow").Length);
|
||||||
scope.Dispose();
|
scope.Dispose();
|
||||||
|
scopedFileSystems = false;
|
||||||
Assert.IsFalse(fs.FileExists("sub/f3.txt"));
|
Assert.IsFalse(fs.FileExists("sub/f3.txt"));
|
||||||
Assert.IsFalse(Directory.Exists(appdata + "/Shadow/" + id));
|
Assert.IsFalse(Directory.Exists(appdata + "/Shadow/" + id));
|
||||||
|
|
||||||
// shadow with scope and complete does complete
|
// shadow with scope and complete does complete
|
||||||
scope = ShadowFileSystemsScope.CreateScope(id = Guid.NewGuid(), swa);
|
scopedFileSystems = true; // pretend we have a scope
|
||||||
|
scope = new ShadowFileSystems(id = Guid.NewGuid(), swa);
|
||||||
Assert.IsTrue(Directory.Exists(appdata + "/Shadow/" + id));
|
Assert.IsTrue(Directory.Exists(appdata + "/Shadow/" + id));
|
||||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
||||||
sw.AddFile("sub/f4.txt", ms);
|
sw.AddFile("sub/f4.txt", ms);
|
||||||
Assert.IsFalse(fs.FileExists("sub/f4.txt"));
|
Assert.IsFalse(fs.FileExists("sub/f4.txt"));
|
||||||
Assert.AreEqual(1, Directory.GetDirectories(appdata + "/Shadow").Length);
|
Assert.AreEqual(1, Directory.GetDirectories(appdata + "/Shadow").Length);
|
||||||
scope.Complete();
|
scope.Complete();
|
||||||
Assert.IsTrue(fs.FileExists("sub/f4.txt"));
|
|
||||||
TestHelper.TryAssert(() => Assert.AreEqual(0, Directory.GetDirectories(appdata + "/Shadow").Length));
|
|
||||||
scope.Dispose();
|
scope.Dispose();
|
||||||
|
scopedFileSystems = false;
|
||||||
|
TestHelper.TryAssert(() => Assert.AreEqual(0, Directory.GetDirectories(appdata + "/Shadow").Length));
|
||||||
Assert.IsTrue(fs.FileExists("sub/f4.txt"));
|
Assert.IsTrue(fs.FileExists("sub/f4.txt"));
|
||||||
Assert.IsFalse(Directory.Exists(appdata + "/Shadow/" + id));
|
Assert.IsFalse(Directory.Exists(appdata + "/Shadow/" + id));
|
||||||
|
|
||||||
// test scope for "another thread"
|
// test scope for "another thread"
|
||||||
|
|
||||||
scope = ShadowFileSystemsScope.CreateScope(id = Guid.NewGuid(), swa);
|
scopedFileSystems = true; // pretend we have a scope
|
||||||
|
scope = new ShadowFileSystems(id = Guid.NewGuid(), swa);
|
||||||
Assert.IsTrue(Directory.Exists(appdata + "/Shadow/" + id));
|
Assert.IsTrue(Directory.Exists(appdata + "/Shadow/" + id));
|
||||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
||||||
sw.AddFile("sub/f5.txt", ms);
|
sw.AddFile("sub/f5.txt", ms);
|
||||||
Assert.IsFalse(fs.FileExists("sub/f5.txt"));
|
Assert.IsFalse(fs.FileExists("sub/f5.txt"));
|
||||||
using (new SafeCallContext()) // pretend we're another thread w/out scope
|
|
||||||
{
|
// pretend we're another thread w/out scope
|
||||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
scopedFileSystems = false;
|
||||||
sw.AddFile("sub/f6.txt", ms);
|
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
||||||
}
|
sw.AddFile("sub/f6.txt", ms);
|
||||||
|
scopedFileSystems = true; // pretend we have a scope
|
||||||
|
|
||||||
Assert.IsTrue(fs.FileExists("sub/f6.txt")); // other thread has written out to fs
|
Assert.IsTrue(fs.FileExists("sub/f6.txt")); // other thread has written out to fs
|
||||||
scope.Complete();
|
scope.Complete();
|
||||||
Assert.IsTrue(fs.FileExists("sub/f5.txt"));
|
|
||||||
scope.Dispose();
|
scope.Dispose();
|
||||||
|
scopedFileSystems = false;
|
||||||
Assert.IsTrue(fs.FileExists("sub/f5.txt"));
|
Assert.IsTrue(fs.FileExists("sub/f5.txt"));
|
||||||
Assert.IsFalse(Directory.Exists(appdata + "/Shadow/" + id));
|
Assert.IsFalse(Directory.Exists(appdata + "/Shadow/" + id));
|
||||||
}
|
}
|
||||||
@@ -445,8 +457,11 @@ namespace Umbraco.Tests.IO
|
|||||||
var appdata = IOHelper.MapPath("App_Data");
|
var appdata = IOHelper.MapPath("App_Data");
|
||||||
Directory.CreateDirectory(path);
|
Directory.CreateDirectory(path);
|
||||||
|
|
||||||
|
var scopedFileSystems = false;
|
||||||
|
var scopeProvider = MockScopeProvider(() => scopedFileSystems);
|
||||||
|
|
||||||
var fs = new PhysicalFileSystem(path, "ignore");
|
var fs = new PhysicalFileSystem(path, "ignore");
|
||||||
var sw = new ShadowWrapper(fs, "shadow");
|
var sw = new ShadowWrapper(fs, "shadow", scopeProvider);
|
||||||
var swa = new[] { sw };
|
var swa = new[] { sw };
|
||||||
|
|
||||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
||||||
@@ -455,20 +470,23 @@ namespace Umbraco.Tests.IO
|
|||||||
|
|
||||||
Guid id;
|
Guid id;
|
||||||
|
|
||||||
var scope = ShadowFileSystemsScope.CreateScope(id = Guid.NewGuid(), swa);
|
scopedFileSystems = true; // pretend we have a scope
|
||||||
|
var scope = new ShadowFileSystems(id = Guid.NewGuid(), swa);
|
||||||
Assert.IsTrue(Directory.Exists(appdata + "/Shadow/" + id));
|
Assert.IsTrue(Directory.Exists(appdata + "/Shadow/" + id));
|
||||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
||||||
sw.AddFile("sub/f2.txt", ms);
|
sw.AddFile("sub/f2.txt", ms);
|
||||||
Assert.IsFalse(fs.FileExists("sub/f2.txt"));
|
Assert.IsFalse(fs.FileExists("sub/f2.txt"));
|
||||||
using (new SafeCallContext()) // pretend we're another thread w/out scope
|
|
||||||
{
|
// pretend we're another thread w/out scope
|
||||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("bar")))
|
scopedFileSystems = false;
|
||||||
sw.AddFile("sub/f2.txt", ms);
|
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("bar")))
|
||||||
}
|
sw.AddFile("sub/f2.txt", ms);
|
||||||
|
scopedFileSystems = true; // pretend we have a scope
|
||||||
|
|
||||||
Assert.IsTrue(fs.FileExists("sub/f2.txt")); // other thread has written out to fs
|
Assert.IsTrue(fs.FileExists("sub/f2.txt")); // other thread has written out to fs
|
||||||
scope.Complete();
|
scope.Complete();
|
||||||
Assert.IsTrue(fs.FileExists("sub/f2.txt"));
|
|
||||||
scope.Dispose();
|
scope.Dispose();
|
||||||
|
scopedFileSystems = false;
|
||||||
Assert.IsTrue(fs.FileExists("sub/f2.txt"));
|
Assert.IsTrue(fs.FileExists("sub/f2.txt"));
|
||||||
TestHelper.TryAssert(() => Assert.IsFalse(Directory.Exists(appdata + "/Shadow/" + id)));
|
TestHelper.TryAssert(() => Assert.IsFalse(Directory.Exists(appdata + "/Shadow/" + id)));
|
||||||
|
|
||||||
@@ -488,8 +506,11 @@ namespace Umbraco.Tests.IO
|
|||||||
var appdata = IOHelper.MapPath("App_Data");
|
var appdata = IOHelper.MapPath("App_Data");
|
||||||
Directory.CreateDirectory(path);
|
Directory.CreateDirectory(path);
|
||||||
|
|
||||||
|
var scopedFileSystems = false;
|
||||||
|
var scopeProvider = MockScopeProvider(() => scopedFileSystems);
|
||||||
|
|
||||||
var fs = new PhysicalFileSystem(path, "ignore");
|
var fs = new PhysicalFileSystem(path, "ignore");
|
||||||
var sw = new ShadowWrapper(fs, "shadow");
|
var sw = new ShadowWrapper(fs, "shadow", scopeProvider);
|
||||||
var swa = new[] { sw };
|
var swa = new[] { sw };
|
||||||
|
|
||||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
||||||
@@ -498,27 +519,32 @@ namespace Umbraco.Tests.IO
|
|||||||
|
|
||||||
Guid id;
|
Guid id;
|
||||||
|
|
||||||
var scope = ShadowFileSystemsScope.CreateScope(id = Guid.NewGuid(), swa);
|
scopedFileSystems = true; // pretend we have a scope
|
||||||
|
var scope = new ShadowFileSystems(id = Guid.NewGuid(), swa);
|
||||||
Assert.IsTrue(Directory.Exists(appdata + "/Shadow/" + id));
|
Assert.IsTrue(Directory.Exists(appdata + "/Shadow/" + id));
|
||||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
||||||
sw.AddFile("sub/f2.txt", ms);
|
sw.AddFile("sub/f2.txt", ms);
|
||||||
Assert.IsFalse(fs.FileExists("sub/f2.txt"));
|
Assert.IsFalse(fs.FileExists("sub/f2.txt"));
|
||||||
using (new SafeCallContext()) // pretend we're another thread w/out scope
|
|
||||||
{
|
// pretend we're another thread w/out scope
|
||||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("bar")))
|
scopedFileSystems = false;
|
||||||
sw.AddFile("sub/f2.txt/f2.txt", ms);
|
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("bar")))
|
||||||
}
|
sw.AddFile("sub/f2.txt/f2.txt", ms);
|
||||||
|
scopedFileSystems = true; // pretend we have a scope
|
||||||
|
|
||||||
Assert.IsTrue(fs.FileExists("sub/f2.txt/f2.txt")); // other thread has written out to fs
|
Assert.IsTrue(fs.FileExists("sub/f2.txt/f2.txt")); // other thread has written out to fs
|
||||||
|
|
||||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
||||||
sw.AddFile("sub/f3.txt", ms);
|
sw.AddFile("sub/f3.txt", ms);
|
||||||
Assert.IsFalse(fs.FileExists("sub/f3.txt"));
|
Assert.IsFalse(fs.FileExists("sub/f3.txt"));
|
||||||
|
|
||||||
|
scope.Complete();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// no way this can work since we're trying to write a file
|
// no way this can work since we're trying to write a file
|
||||||
// but there's now a directory with the same name on the real fs
|
// but there's now a directory with the same name on the real fs
|
||||||
scope.Complete();
|
scope.Dispose();
|
||||||
Assert.Fail("Expected AggregateException.");
|
Assert.Fail("Expected AggregateException.");
|
||||||
}
|
}
|
||||||
catch (AggregateException ae)
|
catch (AggregateException ae)
|
||||||
@@ -576,5 +602,25 @@ namespace Umbraco.Tests.IO
|
|||||||
TestHelper.Try(() => Directory.Delete(path, true));
|
TestHelper.Try(() => Directory.Delete(path, true));
|
||||||
TestHelper.TryAssert(() => Assert.IsFalse(File.Exists(path + "/test/inner/f3.txt")));
|
TestHelper.TryAssert(() => Assert.IsFalse(File.Exists(path + "/test/inner/f3.txt")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void MockTest()
|
||||||
|
{
|
||||||
|
var scoped = false;
|
||||||
|
var provider = MockScopeProvider(() => scoped);
|
||||||
|
|
||||||
|
Assert.IsFalse(provider.AmbientScope.ScopedFileSystems);
|
||||||
|
scoped = true;
|
||||||
|
Assert.IsTrue(provider.AmbientScope.ScopedFileSystems);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IScopeProviderInternal MockScopeProvider(Func<bool> f)
|
||||||
|
{
|
||||||
|
var scopeMock = new Mock<IScopeInternal>();
|
||||||
|
scopeMock.Setup(x => x.ScopedFileSystems).Returns(f);
|
||||||
|
var providerMock = new Mock<IScopeProviderInternal>();
|
||||||
|
providerMock.Setup(x => x.AmbientScope).Returns(scopeMock.Object);
|
||||||
|
return providerMock.Object;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
157
src/Umbraco.Tests/Scoping/ScopeFileSystemsTests.cs
Normal file
157
src/Umbraco.Tests/Scoping/ScopeFileSystemsTests.cs
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Umbraco.Core;
|
||||||
|
using Umbraco.Core.IO;
|
||||||
|
using Umbraco.Core.Scoping;
|
||||||
|
using Umbraco.Tests.TestHelpers;
|
||||||
|
|
||||||
|
namespace Umbraco.Tests.Scoping
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
[DatabaseTestBehavior(DatabaseBehavior.EmptyDbFilePerTest)]
|
||||||
|
public class ScopeFileSystemsTests : BaseDatabaseFactoryTest
|
||||||
|
{
|
||||||
|
[SetUp]
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SafeCallContext.Clear();
|
||||||
|
ClearFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TearDown]
|
||||||
|
public override void TearDown()
|
||||||
|
{
|
||||||
|
base.TearDown();
|
||||||
|
SafeCallContext.Clear();
|
||||||
|
ClearFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ClearFiles()
|
||||||
|
{
|
||||||
|
TestHelper.DeleteDirectory(IOHelper.MapPath("FileSysTests"));
|
||||||
|
TestHelper.DeleteDirectory(IOHelper.MapPath("App_Data"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(true)]
|
||||||
|
[TestCase(false)]
|
||||||
|
public void CreateMediaTest(bool complete)
|
||||||
|
{
|
||||||
|
var physMediaFileSystem = new PhysicalFileSystem(IOHelper.MapPath("media"), "ignore");
|
||||||
|
var mediaFileSystem = FileSystemProviderManager.Current.MediaFileSystem;
|
||||||
|
|
||||||
|
var scopeProvider = ApplicationContext.ScopeProvider;
|
||||||
|
using (var scope = scopeProvider.CreateScope(scopeFileSystems: true))
|
||||||
|
{
|
||||||
|
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
||||||
|
mediaFileSystem.AddFile("f1.txt", ms);
|
||||||
|
Assert.IsTrue(mediaFileSystem.FileExists("f1.txt"));
|
||||||
|
Assert.IsFalse(physMediaFileSystem.FileExists("f1.txt"));
|
||||||
|
if (complete)
|
||||||
|
scope.Complete();
|
||||||
|
Assert.IsTrue(mediaFileSystem.FileExists("f1.txt"));
|
||||||
|
Assert.IsFalse(physMediaFileSystem.FileExists("f1.txt"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (complete)
|
||||||
|
{
|
||||||
|
Assert.IsTrue(FileSystemProviderManager.Current.MediaFileSystem.FileExists("f1.txt"));
|
||||||
|
Assert.IsTrue(physMediaFileSystem.FileExists("f1.txt"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert.IsFalse(FileSystemProviderManager.Current.MediaFileSystem.FileExists("f1.txt"));
|
||||||
|
Assert.IsFalse(physMediaFileSystem.FileExists("f1.txt"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void MultiThread()
|
||||||
|
{
|
||||||
|
var physMediaFileSystem = new PhysicalFileSystem(IOHelper.MapPath("media"), "ignore");
|
||||||
|
var mediaFileSystem = FileSystemProviderManager.Current.MediaFileSystem;
|
||||||
|
|
||||||
|
var scopeProvider = ApplicationContext.ScopeProvider;
|
||||||
|
using (var scope = scopeProvider.CreateScope(scopeFileSystems: true))
|
||||||
|
{
|
||||||
|
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
||||||
|
mediaFileSystem.AddFile("f1.txt", ms);
|
||||||
|
Assert.IsTrue(mediaFileSystem.FileExists("f1.txt"));
|
||||||
|
Assert.IsFalse(physMediaFileSystem.FileExists("f1.txt"));
|
||||||
|
|
||||||
|
using (new SafeCallContext())
|
||||||
|
{
|
||||||
|
Assert.IsFalse(mediaFileSystem.FileExists("f1.txt"));
|
||||||
|
|
||||||
|
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
||||||
|
mediaFileSystem.AddFile("f2.txt", ms);
|
||||||
|
Assert.IsTrue(mediaFileSystem.FileExists("f2.txt"));
|
||||||
|
Assert.IsTrue(physMediaFileSystem.FileExists("f2.txt"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.IsTrue(mediaFileSystem.FileExists("f2.txt"));
|
||||||
|
Assert.IsTrue(physMediaFileSystem.FileExists("f2.txt"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SingleShadow()
|
||||||
|
{
|
||||||
|
var scopeProvider = ApplicationContext.ScopeProvider;
|
||||||
|
using (var scope = scopeProvider.CreateScope(scopeFileSystems: true))
|
||||||
|
{
|
||||||
|
using (new SafeCallContext()) // not nesting!
|
||||||
|
{
|
||||||
|
// ok to create a 'normal' other scope
|
||||||
|
using (var other = scopeProvider.CreateScope())
|
||||||
|
{
|
||||||
|
other.Complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// not ok to create a 'scoped filesystems' other scope
|
||||||
|
// because at the moment we don't support concurrent scoped filesystems
|
||||||
|
Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
{
|
||||||
|
var other = scopeProvider.CreateScope(scopeFileSystems: true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SingleShadowEvenDetached()
|
||||||
|
{
|
||||||
|
var scopeProvider = ApplicationContext.ScopeProvider as IScopeProviderInternal;
|
||||||
|
using (var scope = scopeProvider.CreateScope(scopeFileSystems: true))
|
||||||
|
{
|
||||||
|
using (new SafeCallContext()) // not nesting!
|
||||||
|
{
|
||||||
|
// not ok to create a 'scoped filesystems' other scope
|
||||||
|
// because at the moment we don't support concurrent scoped filesystems
|
||||||
|
// even a detached one
|
||||||
|
Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
{
|
||||||
|
var other = scopeProvider.CreateDetachedScope(scopeFileSystems: true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var detached = scopeProvider.CreateDetachedScope(scopeFileSystems: true);
|
||||||
|
|
||||||
|
Assert.IsNull(scopeProvider.AmbientScope);
|
||||||
|
|
||||||
|
Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
{
|
||||||
|
// even if there is no ambient scope, there's a single shadow
|
||||||
|
using (var other = scopeProvider.CreateScope(scopeFileSystems: true))
|
||||||
|
{ }
|
||||||
|
});
|
||||||
|
|
||||||
|
scopeProvider.AttachScope(detached);
|
||||||
|
detached.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -540,5 +540,108 @@ namespace Umbraco.Tests.Scoping
|
|||||||
Assert.IsNull(ambientScope); // the scope is gone
|
Assert.IsNull(ambientScope); // the scope is gone
|
||||||
Assert.IsNotNull(ambientContext); // the context is still there
|
Assert.IsNotNull(ambientContext); // the context is still there
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ScopeContextException()
|
||||||
|
{
|
||||||
|
var scopeProvider = DatabaseContext.ScopeProvider;
|
||||||
|
|
||||||
|
bool? completed = null;
|
||||||
|
|
||||||
|
Assert.IsNull(scopeProvider.AmbientScope);
|
||||||
|
using (var scope = scopeProvider.CreateScope())
|
||||||
|
{
|
||||||
|
var detached = scopeProvider.CreateDetachedScope();
|
||||||
|
scopeProvider.AttachScope(detached);
|
||||||
|
// the exception does not prevent other enlisted items to run
|
||||||
|
// *and* it does not prevent the scope from properly going down
|
||||||
|
scopeProvider.Context.Enlist("name", c =>
|
||||||
|
{
|
||||||
|
throw new Exception("bang");
|
||||||
|
});
|
||||||
|
scopeProvider.Context.Enlist("other", c =>
|
||||||
|
{
|
||||||
|
completed = c;
|
||||||
|
});
|
||||||
|
detached.Complete();
|
||||||
|
Assert.Throws<AggregateException>(() =>
|
||||||
|
{
|
||||||
|
detached.Dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
// even though disposing of the scope has thrown, it has exited
|
||||||
|
// properly ie it has removed itself, and the app remains clean
|
||||||
|
|
||||||
|
Assert.AreSame(scope, scopeProvider.AmbientScope);
|
||||||
|
scope.Complete();
|
||||||
|
}
|
||||||
|
Assert.IsNull(scopeProvider.AmbientScope);
|
||||||
|
Assert.IsNull(scopeProvider.AmbientContext);
|
||||||
|
|
||||||
|
Assert.IsNotNull(completed);
|
||||||
|
Assert.AreEqual(true, completed);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void DetachableScope()
|
||||||
|
{
|
||||||
|
var scopeProvider = DatabaseContext.ScopeProvider;
|
||||||
|
|
||||||
|
Assert.IsNull(scopeProvider.AmbientScope);
|
||||||
|
using (var scope = scopeProvider.CreateScope())
|
||||||
|
{
|
||||||
|
Assert.IsInstanceOf<Scope>(scope);
|
||||||
|
Assert.IsNotNull(scopeProvider.AmbientScope);
|
||||||
|
Assert.AreSame(scope, scopeProvider.AmbientScope);
|
||||||
|
|
||||||
|
Assert.IsNotNull(scopeProvider.AmbientContext); // the ambient context
|
||||||
|
Assert.IsNotNull(scopeProvider.Context); // the ambient context too (getter only)
|
||||||
|
var context = scopeProvider.Context;
|
||||||
|
|
||||||
|
var detached = scopeProvider.CreateDetachedScope();
|
||||||
|
scopeProvider.AttachScope(detached);
|
||||||
|
|
||||||
|
Assert.AreEqual(detached, scopeProvider.AmbientScope);
|
||||||
|
Assert.AreNotSame(context, scopeProvider.Context);
|
||||||
|
|
||||||
|
// nesting under detached!
|
||||||
|
using (var nested = scopeProvider.CreateScope())
|
||||||
|
{
|
||||||
|
Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
{
|
||||||
|
// cannot detach a non-detachable scope
|
||||||
|
scopeProvider.DetachScope();
|
||||||
|
});
|
||||||
|
nested.Complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.AreEqual(detached, scopeProvider.AmbientScope);
|
||||||
|
Assert.AreNotSame(context, scopeProvider.Context);
|
||||||
|
|
||||||
|
// can detach
|
||||||
|
Assert.AreSame(detached, scopeProvider.DetachScope());
|
||||||
|
|
||||||
|
Assert.AreSame(scope, scopeProvider.AmbientScope);
|
||||||
|
Assert.AreSame(context, scopeProvider.AmbientContext);
|
||||||
|
|
||||||
|
Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
{
|
||||||
|
// cannot disposed a non-attached scope
|
||||||
|
// in fact, only the ambient scope can be disposed
|
||||||
|
detached.Dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
scopeProvider.AttachScope(detached);
|
||||||
|
detached.Complete();
|
||||||
|
detached.Dispose();
|
||||||
|
|
||||||
|
// has self-detached, and is gone!
|
||||||
|
|
||||||
|
Assert.AreSame(scope, scopeProvider.AmbientScope);
|
||||||
|
Assert.AreSame(context, scopeProvider.AmbientContext);
|
||||||
|
}
|
||||||
|
Assert.IsNull(scopeProvider.AmbientScope);
|
||||||
|
Assert.IsNull(scopeProvider.AmbientContext);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,6 +175,7 @@
|
|||||||
<Compile Include="Scoping\PassThroughEventDispatcherTests.cs" />
|
<Compile Include="Scoping\PassThroughEventDispatcherTests.cs" />
|
||||||
<Compile Include="Scoping\ScopeEventDispatcherTests.cs" />
|
<Compile Include="Scoping\ScopeEventDispatcherTests.cs" />
|
||||||
<Compile Include="Scoping\ScopedXmlTests.cs" />
|
<Compile Include="Scoping\ScopedXmlTests.cs" />
|
||||||
|
<Compile Include="Scoping\ScopeFileSystemsTests.cs" />
|
||||||
<Compile Include="Scoping\ScopeTests.cs" />
|
<Compile Include="Scoping\ScopeTests.cs" />
|
||||||
<Compile Include="TestHelpers\Entities\MockedPropertyTypes.cs" />
|
<Compile Include="TestHelpers\Entities\MockedPropertyTypes.cs" />
|
||||||
<Compile Include="TryConvertToTests.cs" />
|
<Compile Include="TryConvertToTests.cs" />
|
||||||
|
|||||||
Reference in New Issue
Block a user