Refactor Shadow FileSystems
This commit is contained in:
@@ -2,23 +2,16 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Web.Hosting;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
public class FileSystemProviderManager
|
||||
{
|
||||
private readonly FileSystemProvidersSection _config;
|
||||
private readonly object _shadowLocker = new object();
|
||||
private readonly WeakSet<FileSystemWrapper> _fs = new WeakSet<FileSystemWrapper>();
|
||||
private readonly bool _shadowEnabled;
|
||||
private Guid _shadow = Guid.Empty;
|
||||
private FileSystemWrapper[] _shadowFs;
|
||||
private readonly WeakSet<ShadowWrapper> _wrappers = new WeakSet<ShadowWrapper>();
|
||||
|
||||
// actual well-known filesystems returned by properties
|
||||
private readonly IFileSystem2 _macroPartialFileSystem;
|
||||
@@ -30,13 +23,13 @@ namespace Umbraco.Core.IO
|
||||
private readonly IFileSystem2 _mvcViewsFileSystem;
|
||||
|
||||
// when shadowing is enabled, above filesystems, as wrappers
|
||||
private readonly FileSystemWrapper2 _macroPartialFileSystemWrapper;
|
||||
private readonly FileSystemWrapper2 _partialViewsFileSystemWrapper;
|
||||
private readonly FileSystemWrapper2 _stylesheetsFileSystemWrapper;
|
||||
private readonly FileSystemWrapper2 _scriptsFileSystemWrapper;
|
||||
private readonly FileSystemWrapper2 _xsltFileSystemWrapper;
|
||||
private readonly FileSystemWrapper2 _masterPagesFileSystemWrapper;
|
||||
private readonly FileSystemWrapper2 _mvcViewsFileSystemWrapper;
|
||||
private readonly ShadowWrapper _macroPartialFileSystemWrapper;
|
||||
private readonly ShadowWrapper _partialViewsFileSystemWrapper;
|
||||
private readonly ShadowWrapper _stylesheetsFileSystemWrapper;
|
||||
private readonly ShadowWrapper _scriptsFileSystemWrapper;
|
||||
private readonly ShadowWrapper _xsltFileSystemWrapper;
|
||||
private readonly ShadowWrapper _masterPagesFileSystemWrapper;
|
||||
private readonly ShadowWrapper _mvcViewsFileSystemWrapper;
|
||||
|
||||
#region Singleton & Constructor
|
||||
|
||||
@@ -59,31 +52,15 @@ namespace Umbraco.Core.IO
|
||||
_masterPagesFileSystem = new PhysicalFileSystem(SystemDirectories.Masterpages);
|
||||
_mvcViewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews);
|
||||
|
||||
// if shadow is enable we need a mean to replace the filesystem by a shadowed filesystem, however we cannot
|
||||
// replace the actual filesystem as we don't know if anything is not holding an app-long reference to them,
|
||||
// so we have to force-wrap each of them and work with the wrapped filesystem. if shadow is not enabled,
|
||||
// no need to wrap (small perfs improvement).
|
||||
|
||||
// fixme - irks!
|
||||
// but cannot be enabled by deploy from an application event handler, because by the time an app event handler
|
||||
// is instanciated it is already too late and some filesystems have been referenced by Core. here we force
|
||||
// enable for deploy... but maybe it should be some sort of config option?
|
||||
_shadowEnabled = AppDomain.CurrentDomain.GetAssemblies().Any(x => x.GetName().Name == "Umbraco.Deploy");
|
||||
|
||||
if (_shadowEnabled)
|
||||
{
|
||||
_macroPartialFileSystem = _macroPartialFileSystemWrapper = new FileSystemWrapper2(_macroPartialFileSystem);
|
||||
_partialViewsFileSystem = _partialViewsFileSystemWrapper = new FileSystemWrapper2(_partialViewsFileSystem);
|
||||
_stylesheetsFileSystem = _stylesheetsFileSystemWrapper = new FileSystemWrapper2(_stylesheetsFileSystem);
|
||||
_scriptsFileSystem = _scriptsFileSystemWrapper = new FileSystemWrapper2(_scriptsFileSystem);
|
||||
_xsltFileSystem = _xsltFileSystemWrapper = new FileSystemWrapper2(_xsltFileSystem);
|
||||
_masterPagesFileSystem = _masterPagesFileSystemWrapper = new FileSystemWrapper2(_masterPagesFileSystem);
|
||||
_mvcViewsFileSystem = _mvcViewsFileSystemWrapper = new FileSystemWrapper2(_mvcViewsFileSystem);
|
||||
}
|
||||
|
||||
// filesystems obtained from GetFileSystemProvider are already wrapped and do not need to be wrapped again,
|
||||
// whether shadow is enabled or not
|
||||
_macroPartialFileSystem = _macroPartialFileSystemWrapper = new ShadowWrapper(_macroPartialFileSystem, "Views/MacroPartials");
|
||||
_partialViewsFileSystem = _partialViewsFileSystemWrapper = new ShadowWrapper(_partialViewsFileSystem, "Views/Partials");
|
||||
_stylesheetsFileSystem = _stylesheetsFileSystemWrapper = new ShadowWrapper(_stylesheetsFileSystem, "css");
|
||||
_scriptsFileSystem = _scriptsFileSystemWrapper = new ShadowWrapper(_scriptsFileSystem, "scripts");
|
||||
_xsltFileSystem = _xsltFileSystemWrapper = new ShadowWrapper(_xsltFileSystem, "xslt");
|
||||
_masterPagesFileSystem = _masterPagesFileSystemWrapper = new ShadowWrapper(_masterPagesFileSystem, "masterpages");
|
||||
_mvcViewsFileSystem = _mvcViewsFileSystemWrapper = new ShadowWrapper(_mvcViewsFileSystem, "Views");
|
||||
|
||||
// filesystems obtained from GetFileSystemProvider are already wrapped and do not need to be wrapped again
|
||||
MediaFileSystem = GetFileSystemProvider<MediaFileSystem>();
|
||||
}
|
||||
|
||||
@@ -194,12 +171,14 @@ namespace Umbraco.Core.IO
|
||||
|
||||
return attr.Alias;
|
||||
});
|
||||
|
||||
|
||||
// gets the inner fs, create the strongly-typed fs wrapping the inner fs, register & return
|
||||
// so we are double-wrapping here
|
||||
// could be optimized by having FileSystemWrapper inherit from ShadowWrapper, maybe
|
||||
var innerFs = GetUnderlyingFileSystemProvider(alias);
|
||||
var shadowWrapper = new ShadowWrapper(innerFs, "typed/" + alias);
|
||||
var fs = (TFileSystem) Activator.CreateInstance(typeof (TFileSystem), innerFs);
|
||||
if (_shadowEnabled)
|
||||
_fs.Add(fs);
|
||||
_wrappers.Add(shadowWrapper); // keeping a weak reference to the wrapper
|
||||
return fs;
|
||||
}
|
||||
|
||||
@@ -225,88 +204,21 @@ namespace Umbraco.Core.IO
|
||||
// _shadowEnabled = true;
|
||||
//}
|
||||
|
||||
internal void Shadow(Guid id)
|
||||
public ShadowFileSystemsScope Shadow(Guid id)
|
||||
{
|
||||
lock (_shadowLocker)
|
||||
{
|
||||
if (_shadowEnabled == false) throw new InvalidOperationException("Shadowing is not enabled.");
|
||||
if (_shadow != Guid.Empty) throw new InvalidOperationException("Already shadowing (" + _shadow + ").");
|
||||
_shadow = id;
|
||||
var typed = _wrappers.ToArray();
|
||||
var wrappers = new ShadowWrapper[typed.Length + 7];
|
||||
var i = 0;
|
||||
while (i < typed.Length) wrappers[i] = typed[i++];
|
||||
wrappers[i++] = _macroPartialFileSystemWrapper;
|
||||
wrappers[i++] = _partialViewsFileSystemWrapper;
|
||||
wrappers[i++] = _stylesheetsFileSystemWrapper;
|
||||
wrappers[i++] = _scriptsFileSystemWrapper;
|
||||
wrappers[i++] = _xsltFileSystemWrapper;
|
||||
wrappers[i++] = _masterPagesFileSystemWrapper;
|
||||
wrappers[i] = _mvcViewsFileSystemWrapper;
|
||||
|
||||
LogHelper.Debug<FileSystemProviderManager>("Shadow " + id + ".");
|
||||
|
||||
ShadowFs(id, _macroPartialFileSystemWrapper, "Views/MacroPartials");
|
||||
ShadowFs(id, _partialViewsFileSystemWrapper, "Views/Partials");
|
||||
ShadowFs(id, _stylesheetsFileSystemWrapper, "css");
|
||||
ShadowFs(id, _scriptsFileSystemWrapper, "scripts");
|
||||
ShadowFs(id, _xsltFileSystemWrapper, "xslt");
|
||||
ShadowFs(id, _masterPagesFileSystemWrapper, "masterpages");
|
||||
ShadowFs(id, _mvcViewsFileSystemWrapper, "Views");
|
||||
|
||||
_shadowFs = _fs.ToArray();
|
||||
foreach (var fs in _shadowFs)
|
||||
ShadowFs(id, fs, "stfs/" + fs.GetType().FullName);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ShadowFs(Guid id, FileSystemWrapper filesystem, string path)
|
||||
{
|
||||
var virt = "~/App_Data/Shadow/" + id + "/" + path;
|
||||
var dir = HostingEnvironment.MapPath(virt);
|
||||
if (dir == null) throw new InvalidOperationException("Could not map path.");
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
// shadow filesystem pretends to be IFileSystem2 even though the inner filesystem
|
||||
// is not, by invoking the GetSize extension method when needed.
|
||||
var shadowFs = new ShadowFileSystem(filesystem.Wrapped, new PhysicalFileSystem(virt));
|
||||
filesystem.Wrapped = shadowFs;
|
||||
}
|
||||
|
||||
internal void UnShadow(bool complete)
|
||||
{
|
||||
lock (_shadowLocker)
|
||||
{
|
||||
if (_shadow == Guid.Empty) return;
|
||||
|
||||
// copy and null before anything else
|
||||
var shadow = _shadow;
|
||||
var shadowFs = _shadowFs;
|
||||
_shadow = Guid.Empty;
|
||||
_shadowFs = null;
|
||||
|
||||
LogHelper.Debug<FileSystemProviderManager>("UnShadow " + shadow + (complete?" (complete)":" (abort)") + ".");
|
||||
|
||||
if (complete)
|
||||
{
|
||||
((ShadowFileSystem) _macroPartialFileSystemWrapper.Wrapped).Complete();
|
||||
((ShadowFileSystem) _partialViewsFileSystemWrapper.Wrapped).Complete();
|
||||
((ShadowFileSystem) _stylesheetsFileSystemWrapper.Wrapped).Complete();
|
||||
((ShadowFileSystem) _scriptsFileSystemWrapper.Wrapped).Complete();
|
||||
((ShadowFileSystem) _xsltFileSystemWrapper.Wrapped).Complete();
|
||||
((ShadowFileSystem) _masterPagesFileSystemWrapper.Wrapped).Complete();
|
||||
((ShadowFileSystem) _mvcViewsFileSystemWrapper.Wrapped).Complete();
|
||||
|
||||
foreach (var fs in shadowFs)
|
||||
((ShadowFileSystem) fs.Wrapped).Complete();
|
||||
}
|
||||
|
||||
UnShadowFs(_macroPartialFileSystemWrapper);
|
||||
UnShadowFs(_partialViewsFileSystemWrapper);
|
||||
UnShadowFs(_stylesheetsFileSystemWrapper);
|
||||
UnShadowFs(_scriptsFileSystemWrapper);
|
||||
UnShadowFs(_xsltFileSystemWrapper);
|
||||
UnShadowFs(_masterPagesFileSystemWrapper);
|
||||
UnShadowFs(_mvcViewsFileSystemWrapper);
|
||||
|
||||
foreach (var fs in shadowFs)
|
||||
UnShadowFs(fs);
|
||||
}
|
||||
}
|
||||
|
||||
private static void UnShadowFs(FileSystemWrapper filesystem)
|
||||
{
|
||||
var inner = ((ShadowFileSystem) filesystem.Wrapped).Inner;
|
||||
filesystem.Wrapped = inner;
|
||||
return ShadowFileSystemsScope.CreateScope(id, wrappers);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Umbraco.Core.IO
|
||||
///
|
||||
/// This abstract class just wraps the 'real' IFileSystem object passed in to its constructor.
|
||||
/// </remarks>
|
||||
public abstract class FileSystemWrapper : IFileSystem
|
||||
public abstract class FileSystemWrapper : IFileSystem2
|
||||
{
|
||||
protected FileSystemWrapper(IFileSystem wrapped)
|
||||
{
|
||||
@@ -102,17 +102,12 @@ namespace Umbraco.Core.IO
|
||||
{
|
||||
return Wrapped.GetCreated(path);
|
||||
}
|
||||
}
|
||||
|
||||
public class FileSystemWrapper2 : FileSystemWrapper, IFileSystem2
|
||||
{
|
||||
public FileSystemWrapper2(IFileSystem2 fs)
|
||||
: base(fs)
|
||||
{ }
|
||||
|
||||
public long GetSize(string path)
|
||||
{
|
||||
return ((IFileSystem2) Wrapped).GetSize(path);
|
||||
}
|
||||
// explicitely implementing - not breaking
|
||||
long IFileSystem2.GetSize(string path)
|
||||
{
|
||||
var wrapped2 = Wrapped as IFileSystem2;
|
||||
return wrapped2 == null ? Wrapped.GetSize(path) : wrapped2.GetSize(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ namespace Umbraco.Core.IO
|
||||
/// A custom file system provider for media
|
||||
/// </summary>
|
||||
[FileSystemProvider("media")]
|
||||
public class MediaFileSystem : FileSystemWrapper2
|
||||
public class MediaFileSystem : FileSystemWrapper
|
||||
{
|
||||
private readonly IContentSection _contentConfig;
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@ using System.Linq;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
// at the moment this is just a wrapper
|
||||
|
||||
public class ShadowFileSystem : IFileSystem2
|
||||
{
|
||||
private readonly IFileSystem _fs;
|
||||
@@ -18,30 +16,51 @@ namespace Umbraco.Core.IO
|
||||
_sfs = sfs;
|
||||
}
|
||||
|
||||
public IFileSystem Inner { get { return _fs; } }
|
||||
public IFileSystem Inner
|
||||
{
|
||||
get { return _fs; }
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
if (_nodes == null) return;
|
||||
var exceptions = new List<Exception>();
|
||||
foreach (var kvp in _nodes)
|
||||
{
|
||||
if (kvp.Value.IsExist)
|
||||
{
|
||||
if (kvp.Value.IsFile)
|
||||
{
|
||||
using (var stream = _sfs.OpenFile(kvp.Key))
|
||||
_fs.AddFile(kvp.Key, stream, true);
|
||||
try
|
||||
{
|
||||
using (var stream = _sfs.OpenFile(kvp.Key))
|
||||
_fs.AddFile(kvp.Key, stream, true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
exceptions.Add(new Exception("Could not save file \"" + kvp.Key + "\".", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (kvp.Value.IsDir)
|
||||
_fs.DeleteDirectory(kvp.Key, true);
|
||||
else
|
||||
_fs.DeleteFile(kvp.Key);
|
||||
try
|
||||
{
|
||||
if (kvp.Value.IsDir)
|
||||
_fs.DeleteDirectory(kvp.Key, true);
|
||||
else
|
||||
_fs.DeleteFile(kvp.Key);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
exceptions.Add(new Exception("Could not delete " + (kvp.Value.IsDir ? "directory": "file") + " \"" + kvp.Key + "\".", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
_nodes.Clear();
|
||||
|
||||
if (exceptions.Count == 0) return;
|
||||
throw new AggregateException("Failed to apply all changes (see exceptions).", exceptions);
|
||||
}
|
||||
|
||||
private Dictionary<string, ShadowNode> _nodes;
|
||||
@@ -255,6 +274,8 @@ namespace Umbraco.Core.IO
|
||||
ShadowNode sf;
|
||||
if (Nodes.TryGetValue(NormPath(path), out sf) == false)
|
||||
{
|
||||
// the inner filesystem (_fs) can be IFileSystem2... or just IFileSystem
|
||||
// figure it out and use the most effective GetSize method
|
||||
var fs2 = _fs as IFileSystem2;
|
||||
return fs2 == null ? _fs.GetSize(path) : fs2.GetSize(path);
|
||||
}
|
||||
|
||||
114
src/Umbraco.Core/IO/ShadowFileSystemsScope.cs
Normal file
114
src/Umbraco.Core/IO/ShadowFileSystemsScope.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Remoting.Messaging;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
public class ShadowFileSystemsScope : IDisposable
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
159
src/Umbraco.Core/IO/ShadowWrapper.cs
Normal file
159
src/Umbraco.Core/IO/ShadowWrapper.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Web.Hosting;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
public class ShadowWrapper : IFileSystem2
|
||||
{
|
||||
private readonly IFileSystem _innerFileSystem;
|
||||
private readonly string _shadowPath;
|
||||
private ShadowFileSystem _shadowFileSystem;
|
||||
private string _shadowDir;
|
||||
|
||||
public ShadowWrapper(IFileSystem innerFileSystem, string shadowPath)
|
||||
{
|
||||
_innerFileSystem = innerFileSystem;
|
||||
_shadowPath = shadowPath;
|
||||
}
|
||||
|
||||
internal void Shadow(Guid id)
|
||||
{
|
||||
// note: no thread-safety here, because ShadowFs is thread-safe due to the check
|
||||
// on ShadowFileSystemsScope.None - and if None is false then we should be running
|
||||
// in a single thread anyways
|
||||
|
||||
var virt = "~/App_Data/Shadow/" + id + "/" + _shadowPath;
|
||||
_shadowDir = IOHelper.MapPath(virt);
|
||||
Directory.CreateDirectory(_shadowDir);
|
||||
var tempfs = new PhysicalFileSystem(virt);
|
||||
_shadowFileSystem = new ShadowFileSystem(_innerFileSystem, tempfs);
|
||||
}
|
||||
|
||||
internal void UnShadow(bool complete)
|
||||
{
|
||||
var shadowFileSystem = _shadowFileSystem;
|
||||
var dir = _shadowDir;
|
||||
_shadowFileSystem = null;
|
||||
_shadowDir = null;
|
||||
|
||||
try
|
||||
{
|
||||
// this may throw an AggregateException if some of the changes could not be applied
|
||||
if (complete) shadowFileSystem.Complete();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// in any case, cleanup
|
||||
try
|
||||
{
|
||||
Directory.Delete(dir, true);
|
||||
dir = dir.Substring(0, dir.Length - _shadowPath.Length - 1);
|
||||
if (Directory.EnumerateFileSystemEntries(dir).Any() == false)
|
||||
Directory.Delete(dir, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ugly, isn't it? but if we cannot cleanup, bah, just leave it there
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IFileSystem FileSystem
|
||||
{
|
||||
get { return ShadowFileSystemsScope.NoScope ? _innerFileSystem : _shadowFileSystem; }
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetDirectories(string path)
|
||||
{
|
||||
return FileSystem.GetDirectories(path);
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path)
|
||||
{
|
||||
FileSystem.DeleteDirectory(path);
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path, bool recursive)
|
||||
{
|
||||
FileSystem.DeleteDirectory(path, recursive);
|
||||
}
|
||||
|
||||
public bool DirectoryExists(string path)
|
||||
{
|
||||
return FileSystem.DirectoryExists(path);
|
||||
}
|
||||
|
||||
public void AddFile(string path, Stream stream)
|
||||
{
|
||||
FileSystem.AddFile(path, stream);
|
||||
}
|
||||
|
||||
public void AddFile(string path, Stream stream, bool overrideExisting)
|
||||
{
|
||||
FileSystem.AddFile(path, stream, overrideExisting);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetFiles(string path)
|
||||
{
|
||||
return FileSystem.GetFiles(path);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetFiles(string path, string filter)
|
||||
{
|
||||
return FileSystem.GetFiles(path, filter);
|
||||
}
|
||||
|
||||
public Stream OpenFile(string path)
|
||||
{
|
||||
return FileSystem.OpenFile(path);
|
||||
}
|
||||
|
||||
public void DeleteFile(string path)
|
||||
{
|
||||
FileSystem.DeleteFile(path);
|
||||
}
|
||||
|
||||
public bool FileExists(string path)
|
||||
{
|
||||
return FileSystem.FileExists(path);
|
||||
}
|
||||
|
||||
public string GetRelativePath(string fullPathOrUrl)
|
||||
{
|
||||
return FileSystem.GetRelativePath(fullPathOrUrl);
|
||||
}
|
||||
|
||||
public string GetFullPath(string path)
|
||||
{
|
||||
return FileSystem.GetFullPath(path);
|
||||
}
|
||||
|
||||
public string GetUrl(string path)
|
||||
{
|
||||
return FileSystem.GetUrl(path);
|
||||
}
|
||||
|
||||
public DateTimeOffset GetLastModified(string path)
|
||||
{
|
||||
return FileSystem.GetLastModified(path);
|
||||
}
|
||||
|
||||
public DateTimeOffset GetCreated(string path)
|
||||
{
|
||||
return FileSystem.GetCreated(path);
|
||||
}
|
||||
|
||||
public long GetSize(string path)
|
||||
{
|
||||
var filesystem = FileSystem; // will be either a ShadowFileSystem OR the actual underlying IFileSystem
|
||||
|
||||
// and the underlying filesystem can be IFileSystem2... or just IFileSystem
|
||||
// figure it out and use the most effective GetSize method
|
||||
var filesystem2 = filesystem as IFileSystem2;
|
||||
return filesystem2 == null ? filesystem.GetSize(path) : filesystem2.GetSize(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Web;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Media;
|
||||
|
||||
|
||||
@@ -181,15 +181,12 @@ namespace Umbraco.Core.Persistence
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
if (NonContextValue != null) throw new InvalidOperationException();
|
||||
NonContextValue = database;
|
||||
if (database != null) NonContextValue = database;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (HttpContext.Current.Items[typeof(DefaultDatabaseFactory)] != null) throw new InvalidOperationException();
|
||||
if (database == null)
|
||||
HttpContext.Current.Items.Remove(typeof(DefaultDatabaseFactory));
|
||||
else
|
||||
HttpContext.Current.Items[typeof(DefaultDatabaseFactory)] = database;
|
||||
if (database != null) HttpContext.Current.Items[typeof(DefaultDatabaseFactory)] = database;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -358,6 +358,8 @@
|
||||
<Compile Include="IApplicationEventHandler.cs" />
|
||||
<Compile Include="IDisposeOnRequestEnd.cs" />
|
||||
<Compile Include="IO\ShadowFileSystem.cs" />
|
||||
<Compile Include="IO\ShadowFileSystemsScope.cs" />
|
||||
<Compile Include="IO\ShadowWrapper.cs" />
|
||||
<Compile Include="Logging\AsyncForwardingAppenderBase.cs" />
|
||||
<Compile Include="Logging\ImageProcessorLogger.cs" />
|
||||
<Compile Include="Logging\IQueue.cs" />
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.IO;
|
||||
|
||||
namespace Umbraco.Tests.IO
|
||||
@@ -14,14 +14,32 @@ namespace Umbraco.Tests.IO
|
||||
{
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{ }
|
||||
{
|
||||
ClearFiles();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FileSysTests");
|
||||
if (Directory.Exists(path) == false) return;
|
||||
Directory.Delete(path, true);
|
||||
ClearFiles();
|
||||
}
|
||||
|
||||
private static void ClearFiles()
|
||||
{
|
||||
var path = IOHelper.MapPath("FileSysTests");
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
foreach (var file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories))
|
||||
File.Delete(file);
|
||||
Directory.Delete(path, true);
|
||||
}
|
||||
path = IOHelper.MapPath("App_Data");
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
foreach (var file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories))
|
||||
File.Delete(file);
|
||||
Directory.Delete(path, true);
|
||||
}
|
||||
}
|
||||
|
||||
private static string NormPath(string path)
|
||||
@@ -32,7 +50,7 @@ namespace Umbraco.Tests.IO
|
||||
[Test]
|
||||
public void ShadowDeleteDirectory()
|
||||
{
|
||||
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FileSysTests");
|
||||
var path = IOHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
@@ -66,7 +84,7 @@ namespace Umbraco.Tests.IO
|
||||
[Test]
|
||||
public void ShadowDeleteDirectoryInDir()
|
||||
{
|
||||
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FileSysTests");
|
||||
var path = IOHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
@@ -115,7 +133,7 @@ namespace Umbraco.Tests.IO
|
||||
[Test]
|
||||
public void ShadowDeleteFile()
|
||||
{
|
||||
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FileSysTests");
|
||||
var path = IOHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
@@ -154,7 +172,7 @@ namespace Umbraco.Tests.IO
|
||||
[Test]
|
||||
public void ShadowDeleteFileInDir()
|
||||
{
|
||||
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FileSysTests");
|
||||
var path = IOHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
@@ -209,7 +227,7 @@ namespace Umbraco.Tests.IO
|
||||
[Test]
|
||||
public void ShadowCantCreateFile()
|
||||
{
|
||||
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FileSysTests");
|
||||
var path = IOHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
@@ -228,7 +246,7 @@ namespace Umbraco.Tests.IO
|
||||
[Test]
|
||||
public void ShadowCreateFile()
|
||||
{
|
||||
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FileSysTests");
|
||||
var path = IOHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
@@ -267,7 +285,7 @@ namespace Umbraco.Tests.IO
|
||||
[Test]
|
||||
public void ShadowCreateFileInDir()
|
||||
{
|
||||
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FileSysTests");
|
||||
var path = IOHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
@@ -307,7 +325,7 @@ namespace Umbraco.Tests.IO
|
||||
[Test]
|
||||
public void ShadowAbort()
|
||||
{
|
||||
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FileSysTests");
|
||||
var path = IOHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
@@ -328,7 +346,7 @@ namespace Umbraco.Tests.IO
|
||||
[Test]
|
||||
public void ShadowComplete()
|
||||
{
|
||||
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FileSysTests");
|
||||
var path = IOHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
@@ -353,10 +371,183 @@ namespace Umbraco.Tests.IO
|
||||
Assert.IsFalse(File.Exists(path + "/ShadowTests/sub/sub/f2.txt"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShadowScopeComplete()
|
||||
{
|
||||
var path = IOHelper.MapPath("FileSysTests");
|
||||
var appdata = IOHelper.MapPath("App_Data");
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
var fs = new PhysicalFileSystem(path, "ignore");
|
||||
var sw = new ShadowWrapper(fs, "shadow");
|
||||
var swa = new[] { sw };
|
||||
|
||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
||||
sw.AddFile("sub/f1.txt", ms);
|
||||
Assert.IsTrue(fs.FileExists("sub/f1.txt"));
|
||||
|
||||
Guid id;
|
||||
|
||||
// explicit shadow without scope does not work
|
||||
sw.Shadow(id = Guid.NewGuid());
|
||||
Assert.IsTrue(Directory.Exists(appdata + "/Shadow/" + id));
|
||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
||||
sw.AddFile("sub/f2.txt", ms);
|
||||
Assert.IsTrue(fs.FileExists("sub/f2.txt"));
|
||||
sw.UnShadow(true);
|
||||
Assert.IsTrue(fs.FileExists("sub/f2.txt"));
|
||||
Assert.IsFalse(Directory.Exists(appdata + "/Shadow/" + id));
|
||||
|
||||
// shadow with scope but no complete does not complete
|
||||
var scope = ShadowFileSystemsScope.CreateScope(id = Guid.NewGuid(), swa);
|
||||
Assert.IsTrue(Directory.Exists(appdata + "/Shadow/" + id));
|
||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
||||
sw.AddFile("sub/f3.txt", ms);
|
||||
Assert.IsFalse(fs.FileExists("sub/f3.txt"));
|
||||
Assert.AreEqual(1, Directory.GetDirectories(appdata + "/Shadow").Length);
|
||||
scope.Dispose();
|
||||
Assert.IsFalse(fs.FileExists("sub/f3.txt"));
|
||||
Assert.IsFalse(Directory.Exists(appdata + "/Shadow/" + id));
|
||||
|
||||
// shadow with scope and complete does complete
|
||||
scope = ShadowFileSystemsScope.CreateScope(id = Guid.NewGuid(), swa);
|
||||
Assert.IsTrue(Directory.Exists(appdata + "/Shadow/" + id));
|
||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
||||
sw.AddFile("sub/f4.txt", ms);
|
||||
Assert.IsFalse(fs.FileExists("sub/f4.txt"));
|
||||
Assert.AreEqual(1, Directory.GetDirectories(appdata + "/Shadow").Length);
|
||||
scope.Complete();
|
||||
Assert.IsTrue(fs.FileExists("sub/f4.txt"));
|
||||
Assert.AreEqual(0, Directory.GetDirectories(appdata + "/Shadow").Length);
|
||||
scope.Dispose();
|
||||
Assert.IsTrue(fs.FileExists("sub/f4.txt"));
|
||||
Assert.IsFalse(Directory.Exists(appdata + "/Shadow/" + id));
|
||||
|
||||
// test scope for "another thread"
|
||||
|
||||
scope = ShadowFileSystemsScope.CreateScope(id = Guid.NewGuid(), swa);
|
||||
Assert.IsTrue(Directory.Exists(appdata + "/Shadow/" + id));
|
||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
||||
sw.AddFile("sub/f5.txt", ms);
|
||||
Assert.IsFalse(fs.FileExists("sub/f5.txt"));
|
||||
using (new SafeCallContext()) // pretend we're another thread w/out scope
|
||||
{
|
||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
||||
sw.AddFile("sub/f6.txt", ms);
|
||||
}
|
||||
Assert.IsTrue(fs.FileExists("sub/f6.txt")); // other thread has written out to fs
|
||||
scope.Complete();
|
||||
Assert.IsTrue(fs.FileExists("sub/f5.txt"));
|
||||
scope.Dispose();
|
||||
Assert.IsTrue(fs.FileExists("sub/f5.txt"));
|
||||
Assert.IsFalse(Directory.Exists(appdata + "/Shadow/" + id));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShadowScopeCompleteWithFileConflict()
|
||||
{
|
||||
var path = IOHelper.MapPath("FileSysTests");
|
||||
var appdata = IOHelper.MapPath("App_Data");
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
var fs = new PhysicalFileSystem(path, "ignore");
|
||||
var sw = new ShadowWrapper(fs, "shadow");
|
||||
var swa = new[] { sw };
|
||||
|
||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
||||
sw.AddFile("sub/f1.txt", ms);
|
||||
Assert.IsTrue(fs.FileExists("sub/f1.txt"));
|
||||
|
||||
Guid id;
|
||||
|
||||
var scope = ShadowFileSystemsScope.CreateScope(id = Guid.NewGuid(), swa);
|
||||
Assert.IsTrue(Directory.Exists(appdata + "/Shadow/" + id));
|
||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
||||
sw.AddFile("sub/f2.txt", ms);
|
||||
Assert.IsFalse(fs.FileExists("sub/f2.txt"));
|
||||
using (new SafeCallContext()) // pretend we're another thread w/out scope
|
||||
{
|
||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("bar")))
|
||||
sw.AddFile("sub/f2.txt", ms);
|
||||
}
|
||||
Assert.IsTrue(fs.FileExists("sub/f2.txt")); // other thread has written out to fs
|
||||
scope.Complete();
|
||||
Assert.IsTrue(fs.FileExists("sub/f2.txt"));
|
||||
scope.Dispose();
|
||||
Assert.IsTrue(fs.FileExists("sub/f2.txt"));
|
||||
Assert.IsFalse(Directory.Exists(appdata + "/Shadow/" + id));
|
||||
|
||||
string text;
|
||||
using (var s = fs.OpenFile("sub/f2.txt"))
|
||||
using (var r = new StreamReader(s))
|
||||
text = r.ReadToEnd();
|
||||
|
||||
// the shadow filesystem will happily overwrite anything it can
|
||||
Assert.AreEqual("foo", text);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShadowScopeCompleteWithDirectoryConflict()
|
||||
{
|
||||
var path = IOHelper.MapPath("FileSysTests");
|
||||
var appdata = IOHelper.MapPath("App_Data");
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
var fs = new PhysicalFileSystem(path, "ignore");
|
||||
var sw = new ShadowWrapper(fs, "shadow");
|
||||
var swa = new[] { sw };
|
||||
|
||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
||||
sw.AddFile("sub/f1.txt", ms);
|
||||
Assert.IsTrue(fs.FileExists("sub/f1.txt"));
|
||||
|
||||
Guid id;
|
||||
|
||||
var scope = ShadowFileSystemsScope.CreateScope(id = Guid.NewGuid(), swa);
|
||||
Assert.IsTrue(Directory.Exists(appdata + "/Shadow/" + id));
|
||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
||||
sw.AddFile("sub/f2.txt", ms);
|
||||
Assert.IsFalse(fs.FileExists("sub/f2.txt"));
|
||||
using (new SafeCallContext()) // pretend we're another thread w/out scope
|
||||
{
|
||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("bar")))
|
||||
sw.AddFile("sub/f2.txt/f2.txt", ms);
|
||||
}
|
||||
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")))
|
||||
sw.AddFile("sub/f3.txt", ms);
|
||||
Assert.IsFalse(fs.FileExists("sub/f3.txt"));
|
||||
|
||||
try
|
||||
{
|
||||
// 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
|
||||
scope.Complete();
|
||||
Assert.Fail("Expected AggregateException.");
|
||||
}
|
||||
catch (AggregateException ae)
|
||||
{
|
||||
Assert.AreEqual(1, ae.InnerExceptions.Count);
|
||||
var e = ae.InnerExceptions[0];
|
||||
Assert.IsNotNull(e.InnerException);
|
||||
Assert.IsInstanceOf<AggregateException>(e);
|
||||
ae = (AggregateException) e;
|
||||
|
||||
Assert.AreEqual(1, ae.InnerExceptions.Count);
|
||||
e = ae.InnerExceptions[0];
|
||||
Assert.IsNotNull(e.InnerException);
|
||||
Assert.IsInstanceOf<UnauthorizedAccessException>(e.InnerException);
|
||||
}
|
||||
|
||||
// still, the rest of the changes has been applied ok
|
||||
Assert.IsTrue(fs.FileExists("sub/f3.txt"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetFilesReturnsChildrenOnly()
|
||||
{
|
||||
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FileSysTests");
|
||||
var path = IOHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
File.WriteAllText(path + "/f1.txt", "foo");
|
||||
Directory.CreateDirectory(path + "/test");
|
||||
@@ -378,7 +569,7 @@ namespace Umbraco.Tests.IO
|
||||
[Test]
|
||||
public void DeleteDirectoryAndFiles()
|
||||
{
|
||||
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FileSysTests");
|
||||
var path = IOHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
File.WriteAllText(path + "/f1.txt", "foo");
|
||||
Directory.CreateDirectory(path + "/test");
|
||||
|
||||
Reference in New Issue
Block a user