Combined Guids media path scheme
This commit is contained in:
@@ -79,7 +79,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions
|
||||
composition.RegisterUnique<IFileSystems>(factory => factory.GetInstance<IO.FileSystems>());
|
||||
|
||||
// register the scheme for media paths
|
||||
composition.RegisterUnique<IMediaPathScheme, TwoGuidsMediaPathScheme>();
|
||||
composition.RegisterUnique<IMediaPathScheme, CombinedGuidsMediaPathScheme>();
|
||||
|
||||
// register the IMediaFileSystem implementation
|
||||
composition.RegisterFileSystem<IMediaFileSystem, MediaFileSystem>();
|
||||
|
||||
@@ -40,5 +40,72 @@ namespace Umbraco.Core
|
||||
|
||||
public DecomposedGuid(Guid value) : this() => this.Value = value;
|
||||
}
|
||||
|
||||
private static readonly char[] Base32Table =
|
||||
{
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
|
||||
'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5'
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Guid into a base-32 string.
|
||||
/// </summary>
|
||||
/// <param name="guid">A Guid.</param>
|
||||
/// <param name="length">The string length.</param>
|
||||
/// <returns>A base-32 encoded string.</returns>
|
||||
/// <remarks>
|
||||
/// <para>A base-32 string representation of a Guid is the shortest, efficient, representation
|
||||
/// that is case insensitive (base-64 is case sensitive).</para>
|
||||
/// <para>Length must be 1-26, anything else becomes 26.</para>
|
||||
/// </remarks>
|
||||
public static string ToBase32String(Guid guid, int length = 26)
|
||||
{
|
||||
if (length <= 0 || length > 26)
|
||||
length = 26;
|
||||
|
||||
var bytes = guid.ToByteArray(); // a Guid is 128 bits ie 16 bytes
|
||||
|
||||
// this could be optimized by making it unsafe,
|
||||
// and fixing the table + bytes + chars (see Convert.ToBase64CharArray)
|
||||
|
||||
// each block of 5 bytes = 5*8 = 40 bits
|
||||
// becomes 40 bits = 8*5 = 8 byte-32 chars
|
||||
// a Guid is 3 blocks + 8 bits
|
||||
|
||||
// so it turns into a 3*8+2 = 26 chars string
|
||||
var chars = new char[length];
|
||||
|
||||
var i = 0;
|
||||
var j = 0;
|
||||
|
||||
while (i < 15)
|
||||
{
|
||||
if (j == length) break;
|
||||
chars[j++] = Base32Table[(bytes[i] & 0b1111_1000) >> 3];
|
||||
if (j == length) break;
|
||||
chars[j++] = Base32Table[((bytes[i] & 0b0000_0111) << 2) | ((bytes[i + 1] & 0b1100_0000) >> 6)];
|
||||
if (j == length) break;
|
||||
chars[j++] = Base32Table[(bytes[i + 1] & 0b0011_1110) >> 1];
|
||||
if (j == length) break;
|
||||
chars[j++] = Base32Table[(bytes[i + 1] & 0b0000_0001) | ((bytes[i + 2] & 0b1111_0000) >> 4)];
|
||||
if (j == length) break;
|
||||
chars[j++] = Base32Table[((bytes[i + 2] & 0b0000_1111) << 1) | ((bytes[i + 3] & 0b1000_0000) >> 7)];
|
||||
if (j == length) break;
|
||||
chars[j++] = Base32Table[(bytes[i + 3] & 0b0111_1100) >> 2];
|
||||
if (j == length) break;
|
||||
chars[j++] = Base32Table[((bytes[i + 3] & 0b0000_0011) << 3) | ((bytes[i + 4] & 0b1110_0000) >> 5)];
|
||||
if (j == length) break;
|
||||
chars[j++] = Base32Table[bytes[i + 4] & 0b0001_1111];
|
||||
|
||||
i += 5;
|
||||
}
|
||||
|
||||
if (j < length)
|
||||
chars[j++] = Base32Table[(bytes[i] & 0b1111_1000) >> 3];
|
||||
if (j < length)
|
||||
chars[j] = Base32Table[(bytes[i] & 0b0000_0111) << 2];
|
||||
|
||||
return new string(chars);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,13 +11,17 @@ namespace Umbraco.Core.IO.MediaPathSchemes
|
||||
/// </remarks>
|
||||
public class CombinedGuidsMediaPathScheme : IMediaPathScheme
|
||||
{
|
||||
private const int DirectoryLength = 8;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetFilePath(IMediaFileSystem fileSystem, Guid itemGuid, Guid propertyGuid, string filename, string previous = null)
|
||||
{
|
||||
// assumes that cuid and puid keys can be trusted - and that a single property type
|
||||
// for a single content cannot store two different files with the same name
|
||||
var directory = HexEncoder.Encode(GuidUtils.Combine(itemGuid, propertyGuid).ToByteArray()/*'/', 2, 4*/); // could use ext to fragment path eg 12/e4/f2/...
|
||||
return Path.Combine(directory, filename).Replace('\\', '/').Substring(0, 9);
|
||||
|
||||
var combinedGuid = GuidUtils.Combine(itemGuid, propertyGuid);
|
||||
var directory = GuidUtils.ToBase32String(combinedGuid, DirectoryLength); // see also HexEncoder, we may want to fragment path eg 12/e4/f3...
|
||||
return Path.Combine(directory, filename).Replace('\\', '/');
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Umbraco.Core.IO
|
||||
public static string CreateShadowId()
|
||||
{
|
||||
const int retries = 50; // avoid infinite loop
|
||||
const int idLength = 6; // 6 chars
|
||||
const int idLength = 8; // 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
|
||||
@@ -34,7 +34,7 @@ namespace Umbraco.Core.IO
|
||||
|
||||
for (var i = 0; i < retries; i++)
|
||||
{
|
||||
var id = Guid.NewGuid().ToString("N").Substring(0, idLength);
|
||||
var id = GuidUtils.ToBase32String(Guid.NewGuid(), idLength);
|
||||
|
||||
var virt = ShadowFsPath + "/" + id;
|
||||
var shadowDir = IOHelper.MapPath(virt);
|
||||
|
||||
@@ -15,6 +15,14 @@ namespace Umbraco.Tests.CoreThings
|
||||
Assert.AreEqual(GuidUtils.Combine(a, b).ToByteArray(), Combine(a, b));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GuidThingTest()
|
||||
{
|
||||
var guid = new Guid("f918382f-2bba-453f-a3e2-1f594016ed3b");
|
||||
Assert.AreEqual("f22br4n0fm5fli5c", GuidUtils.ToBase32String(guid, 16));
|
||||
Assert.AreEqual("f22br4n0f", GuidUtils.ToBase32String(guid, 9));
|
||||
}
|
||||
|
||||
// Reference implementation taken from original code.
|
||||
private static byte[] Combine(Guid guid1, Guid guid2)
|
||||
{
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Umbraco.Tests.IO
|
||||
composition.Register(_ => Mock.Of<ILogger>());
|
||||
composition.Register(_ => Mock.Of<IDataTypeService>());
|
||||
composition.Register(_ => Mock.Of<IContentSection>());
|
||||
composition.RegisterUnique<IMediaPathScheme, OriginalMediaPathScheme>();
|
||||
composition.RegisterUnique<IMediaPathScheme, CombinedGuidsMediaPathScheme>();
|
||||
|
||||
composition.Configs.Add(SettingsForTests.GetDefaultGlobalSettings);
|
||||
composition.Configs.Add(SettingsForTests.GetDefaultUmbracoSettings);
|
||||
|
||||
@@ -243,7 +243,7 @@ namespace Umbraco.Tests.Testing
|
||||
Composition.WithCollectionBuilder<PropertyValueConverterCollectionBuilder>();
|
||||
Composition.RegisterUnique<IPublishedContentTypeFactory, PublishedContentTypeFactory>();
|
||||
|
||||
Composition.RegisterUnique<IMediaPathScheme, OriginalMediaPathScheme>();
|
||||
Composition.RegisterUnique<IMediaPathScheme, CombinedGuidsMediaPathScheme>();
|
||||
|
||||
// register empty content apps collection
|
||||
Composition.WithCollectionBuilder<ContentAppFactoryCollectionBuilder>();
|
||||
|
||||
Reference in New Issue
Block a user