diff --git a/src/Umbraco.Core/IO/FileSystems.cs b/src/Umbraco.Core/IO/FileSystems.cs index 19d29e14ba..7875e0ddbe 100644 --- a/src/Umbraco.Core/IO/FileSystems.cs +++ b/src/Umbraco.Core/IO/FileSystems.cs @@ -29,7 +29,7 @@ namespace Umbraco.Core.IO // shadow support private readonly List _shadowWrappers = new List(); private readonly object _shadowLocker = new object(); - private static Guid _shadowCurrentId = Guid.Empty; // static - unique!! + private static string _shadowCurrentId; // static - unique!! #region Constructor // DI wants a public ctor @@ -45,13 +45,13 @@ namespace Umbraco.Core.IO _shadowWrappers.Clear(); _filesystems.Clear(); Volatile.Write(ref _wkfsInitialized, false); - _shadowCurrentId = Guid.Empty; + _shadowCurrentId = null; } // for tests only, totally unsafe internal static void ResetShadowId() { - _shadowCurrentId = Guid.Empty; + _shadowCurrentId = null; } // set by the scope provider when taking control of filesystems @@ -179,35 +179,37 @@ namespace Umbraco.Core.IO // 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. - internal ICompletable Shadow(Guid id) + internal ICompletable Shadow() { if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems(); + var id = ShadowWrapper.CreateShadowId(); return new ShadowFileSystems(this, id); // will invoke BeginShadow and EndShadow } - internal void BeginShadow(Guid id) + internal void BeginShadow(string id) { lock (_shadowLocker) { // if we throw here, it means that something very wrong happened. - if (_shadowCurrentId != Guid.Empty) + if (_shadowCurrentId != null) throw new InvalidOperationException("Already shadowing."); + _shadowCurrentId = id; - _logger.Debug("Shadow '{ShadowId}'", id); + _logger.Debug("Shadow '{ShadowId}'", _shadowCurrentId); foreach (var wrapper in _shadowWrappers) - wrapper.Shadow(id); + wrapper.Shadow(_shadowCurrentId); } } - internal void EndShadow(Guid id, bool completed) + internal void EndShadow(string id, bool completed) { lock (_shadowLocker) { // if we throw here, it means that something very wrong happened. - if (_shadowCurrentId == Guid.Empty) + if (_shadowCurrentId == null) throw new InvalidOperationException("Not shadowing."); if (id != _shadowCurrentId) throw new InvalidOperationException("Not the current shadow."); @@ -228,7 +230,7 @@ namespace Umbraco.Core.IO } } - _shadowCurrentId = Guid.Empty; + _shadowCurrentId = null; if (exceptions.Count > 0) throw new AggregateException(completed ? "Failed to apply all changes (see exceptions)." : "Failed to abort (see exceptions).", exceptions); @@ -240,7 +242,7 @@ namespace Umbraco.Core.IO lock (_shadowLocker) { var wrapper = new ShadowWrapper(filesystem, shadowPath, IsScoped); - if (_shadowCurrentId != Guid.Empty) + if (_shadowCurrentId != null) wrapper.Shadow(_shadowCurrentId); _shadowWrappers.Add(wrapper); return wrapper; diff --git a/src/Umbraco.Core/IO/ShadowFileSystems.cs b/src/Umbraco.Core/IO/ShadowFileSystems.cs index bce0cc6df7..daec6e8dc5 100644 --- a/src/Umbraco.Core/IO/ShadowFileSystems.cs +++ b/src/Umbraco.Core/IO/ShadowFileSystems.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.IO private bool _completed; // invoked by the filesystems when shadowing - public ShadowFileSystems(FileSystems fileSystems, Guid id) + public ShadowFileSystems(FileSystems fileSystems, string id) { _fileSystems = fileSystems; Id = id; @@ -19,7 +19,7 @@ namespace Umbraco.Core.IO } // for tests - public Guid Id { get; } + public string Id { get; } // invoked by the scope when exiting, if completed public void Complete() diff --git a/src/Umbraco.Core/IO/ShadowWrapper.cs b/src/Umbraco.Core/IO/ShadowWrapper.cs index d71f328713..09ce5f24d0 100644 --- a/src/Umbraco.Core/IO/ShadowWrapper.cs +++ b/src/Umbraco.Core/IO/ShadowWrapper.cs @@ -22,7 +22,33 @@ namespace Umbraco.Core.IO _isScoped = isScoped; } - internal void Shadow(Guid id) + public static string CreateShadowId() + { + const int retries = 50; // avoid infinite loop + const int idLength = 6; // 6 chars + + // shorten a Guid to idLength chars, and see whether it collides + // with an existing directory or not - if it does, try again, and + // we should end up with a unique identifier eventually - but just + // detect infinite loops (just in case) + + for (var i = 0; i < retries; i++) + { + var id = Guid.NewGuid().ToString("N").Substring(0, idLength); + + var virt = ShadowFsPath + "/" + id; + var shadowDir = IOHelper.MapPath(virt); + if (Directory.Exists(shadowDir)) + continue; + + Directory.CreateDirectory(shadowDir); + return id; + } + + throw new Exception($"Could not get a shadow identifier (tried {retries} times)"); + } + + internal void Shadow(string 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 diff --git a/src/Umbraco.Core/Scoping/Scope.cs b/src/Umbraco.Core/Scoping/Scope.cs index f9ea02b072..e9dd04c5fa 100644 --- a/src/Umbraco.Core/Scoping/Scope.cs +++ b/src/Umbraco.Core/Scoping/Scope.cs @@ -75,7 +75,7 @@ namespace Umbraco.Core.Scoping // see note below if (scopeFileSystems == true) - _fscope = fileSystems.Shadow(Guid.NewGuid()); + _fscope = fileSystems.Shadow(); return; } @@ -105,7 +105,7 @@ namespace Umbraco.Core.Scoping // 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 = fileSystems.Shadow(Guid.NewGuid()); + _fscope = fileSystems.Shadow(); } } diff --git a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs index 8d9c1be40a..2704f39724 100644 --- a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs +++ b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs @@ -405,10 +405,10 @@ namespace Umbraco.Tests.IO sw.AddFile("sub/f1.txt", ms); Assert.IsTrue(phy.FileExists("sub/f1.txt")); - Guid id; + string id; // explicit shadow without scope does not work - sw.Shadow(id = Guid.NewGuid()); + sw.Shadow(id = ShadowWrapper.CreateShadowId()); Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f2.txt", ms); @@ -419,7 +419,7 @@ namespace Umbraco.Tests.IO // shadow with scope but no complete does not complete scopedFileSystems = true; // pretend we have a scope - var scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid()); + var scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId()); Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f3.txt", ms); @@ -440,7 +440,7 @@ namespace Umbraco.Tests.IO // shadow with scope and complete does complete scopedFileSystems = true; // pretend we have a scope - scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid()); + scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId()); Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f4.txt", ms); @@ -456,7 +456,7 @@ namespace Umbraco.Tests.IO // test scope for "another thread" scopedFileSystems = true; // pretend we have a scope - scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid()); + scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId()); Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f5.txt", ms); @@ -498,10 +498,10 @@ namespace Umbraco.Tests.IO sw.AddFile("sub/f1.txt", ms); Assert.IsTrue(phy.FileExists("sub/f1.txt")); - Guid id; + string id; scopedFileSystems = true; // pretend we have a scope - var scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid()); + var scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId()); Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f2.txt", ms); @@ -551,10 +551,10 @@ namespace Umbraco.Tests.IO sw.AddFile("sub/f1.txt", ms); Assert.IsTrue(phy.FileExists("sub/f1.txt")); - Guid id; + string id; scopedFileSystems = true; // pretend we have a scope - var scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid()); + var scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId()); Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f2.txt", ms);