From fe445f5ecd74eeb128b6e1d002942d253ce461ce Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 20 Jun 2018 10:49:12 +0200 Subject: [PATCH] Implement media path scheme --- src/Umbraco.Core/IO/IMediaPathScheme.cs | 41 ++ src/Umbraco.Core/IO/MediaFileSystem.cs | 406 +----------------- .../CombinedGuidsMediaPathScheme.cs | 43 ++ .../OriginalMediaPathScheme.cs | 92 ++++ .../TwoGuidsMediaPathScheme.cs | 31 ++ .../Runtime/CoreRuntimeComponent.cs | 3 + src/Umbraco.Core/Umbraco.Core.csproj | 4 + 7 files changed, 224 insertions(+), 396 deletions(-) create mode 100644 src/Umbraco.Core/IO/IMediaPathScheme.cs create mode 100644 src/Umbraco.Core/IO/MediaPathSchemes/CombinedGuidsMediaPathScheme.cs create mode 100644 src/Umbraco.Core/IO/MediaPathSchemes/OriginalMediaPathScheme.cs create mode 100644 src/Umbraco.Core/IO/MediaPathSchemes/TwoGuidsMediaPathScheme.cs diff --git a/src/Umbraco.Core/IO/IMediaPathScheme.cs b/src/Umbraco.Core/IO/IMediaPathScheme.cs new file mode 100644 index 0000000000..5cfb43ed77 --- /dev/null +++ b/src/Umbraco.Core/IO/IMediaPathScheme.cs @@ -0,0 +1,41 @@ +using System; + +namespace Umbraco.Core.IO +{ + /// + /// Represents a media file path scheme. + /// + public interface IMediaPathScheme + { + // fixme + // to anyone finding this code: YES the Initialize() method is CompletelyBroken™ (temporal whatever) + // but at the moment, the media filesystem wants a scheme which wants a filesystem, and it's all + // convoluted due to how filesystems are managed in FileSystems - clear that part first! + + /// + /// Initialize. + /// + void Initialize(IFileSystem filesystem); + + /// + /// Gets a media file path. + /// + /// The (content, media) item unique identifier. + /// The property type unique identifier. + /// The file name. + /// A previous filename. + /// The filesystem-relative complete file path. + string GetFilePath(Guid itemGuid, Guid propertyGuid, string filename, string previous = null); + + /// + /// Gets the directory that can be deleted when the file is deleted. + /// + /// The filesystem-relative path of the file. + /// The filesystem-relative path of the directory. + /// + /// The directory, and anything below it, will be deleted. + /// Can return null (or empty) when no directory should be deleted. + /// + string GetDeleteDirectory(string filepath); + } +} diff --git a/src/Umbraco.Core/IO/MediaFileSystem.cs b/src/Umbraco.Core/IO/MediaFileSystem.cs index b1404121e5..ad37c1f8dc 100644 --- a/src/Umbraco.Core/IO/MediaFileSystem.cs +++ b/src/Umbraco.Core/IO/MediaFileSystem.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Composing; using Umbraco.Core.Exceptions; +using Umbraco.Core.IO.MediaPathSchemes; using Umbraco.Core.Logging; using Umbraco.Core.Media; using Umbraco.Core.Media.Exif; @@ -24,10 +25,6 @@ namespace Umbraco.Core.IO [FileSystemProvider("media")] public class MediaFileSystem : FileSystemWrapper { - private readonly object _folderCounterLock = new object(); - private long _folderCounter; - private bool _folderCounterInitialized; - public MediaFileSystem(IFileSystem wrapped) : base(wrapped) { @@ -36,7 +33,10 @@ namespace Umbraco.Core.IO Current.Container.InjectProperties(this); UploadAutoFillProperties = new UploadAutoFillProperties(this, Logger, ContentConfig); - } + } + + [Inject] + internal IMediaPathScheme MediaPathScheme { get; set; } [Inject] internal IContentSection ContentConfig { get; set; } @@ -45,14 +45,6 @@ namespace Umbraco.Core.IO internal ILogger Logger { get; set; } internal UploadAutoFillProperties UploadAutoFillProperties { get; } - - // fixme - work-in-progress - // the new scheme is now the default scheme - media items with old paths will still - // work, but whenever a file is uploaded, the new scheme is used. we have a temp. - // option to let people opt-out of the new scheme. eventually, the media filesystem - // will only support the new scheme - but it should be an interface, and it should - // be possible to switch its implementation to whatever people want to use - public bool UseTheNewMediaPathScheme => ContentConfig.UseTheNewMediaPathScheme; /// /// Deletes all files passed in. @@ -112,24 +104,9 @@ namespace Umbraco.Core.IO if (FileExists(file) == false) return; DeleteFile(file); -#pragma warning disable 162 // unreachable code - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - if (UseTheNewMediaPathScheme == false) - { - // old scheme: filepath is "/" OR "-" - // remove the directory if any - var dir = Path.GetDirectoryName(file); - if (string.IsNullOrWhiteSpace(dir) == false) - DeleteDirectory(dir, true); - } - else - { - // new scheme: path is "/" where xuid is a combination of cuid and puid - // remove the directory - var dir = Path.GetDirectoryName(file); - DeleteDirectory(dir, true); - } -#pragma warning restore 162 + var directory = MediaPathScheme.GetDeleteDirectory(file); + if (!directory.IsNullOrWhiteSpace()) + DeleteDirectory(directory, true); } catch (Exception e) { @@ -154,40 +131,7 @@ namespace Umbraco.Core.IO if (filename == null) throw new ArgumentException("Cannot become a safe filename.", nameof(filename)); filename = IOHelper.SafeFileName(filename.ToLowerInvariant()); - string folder; -#pragma warning disable 162 // unreachable code - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - if (UseTheNewMediaPathScheme == false) - { - // old scheme: filepath is "/" OR "-" - // default media filesystem maps to "~/media/" - folder = GetNextFolder(); - } - else - { - // new scheme: path is "/" where xuid is a combination of cuid and puid - // default media filesystem maps to "~/media/" - // 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 - folder = Combine(cuid, puid).ToHexString(/*'/', 2, 4*/); // could use ext to fragment path eg 12/e4/f2/... - } -#pragma warning restore 162 - - var filepath = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories - ? Path.Combine(folder, filename).Replace('\\', '/') - : folder + "-" + filename; - - return filepath; - } - - private static byte[] Combine(Guid guid1, Guid guid2) - { - var bytes1 = guid1.ToByteArray(); - var bytes2 = guid2.ToByteArray(); - var bytes = new byte[bytes1.Length]; - for (var i = 0; i < bytes1.Length; i++) - bytes[i] = (byte) (bytes1[i] ^ bytes2[i]); - return bytes; + return MediaPathScheme.GetFilePath(cuid, puid, filename); } /// @@ -202,64 +146,11 @@ namespace Umbraco.Core.IO /// specified by . Else, we CREATE a new one. Each time we are invoked. public string GetMediaPath(string filename, string prevpath, Guid cuid, Guid puid) { -#pragma warning disable 162 // unreachable code - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - if (UseTheNewMediaPathScheme || string.IsNullOrWhiteSpace(prevpath)) - return GetMediaPath(filename, cuid, puid); - filename = Path.GetFileName(filename); if (filename == null) throw new ArgumentException("Cannot become a safe filename.", nameof(filename)); filename = IOHelper.SafeFileName(filename.ToLowerInvariant()); - // old scheme, with a previous path - // prevpath should be "/" OR "-" - // and we want to reuse the "" part, so try to find it - - var sep = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories ? "/" : "-"; - var pos = prevpath.IndexOf(sep, StringComparison.Ordinal); - var s = pos > 0 ? prevpath.Substring(0, pos) : null; - - var folder = (pos > 0 && int.TryParse(s, out _)) ? s : GetNextFolder(); - - // ReSharper disable once AssignNullToNotNullAttribute - var filepath = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories - ? Path.Combine(folder, filename) - : folder + "-" + filename; - - return filepath; -#pragma warning restore 162 // unreachable code - } - - /// - /// Gets the next media folder in the original number-based scheme. - /// - /// - /// Should be private, is internal for legacy FileHandlerData which is obsolete. - internal string GetNextFolder() - { - EnsureFolderCounterIsInitialized(); - return Interlocked.Increment(ref _folderCounter).ToString(CultureInfo.InvariantCulture); - } - - private void EnsureFolderCounterIsInitialized() - { - lock (_folderCounterLock) - { - if (_folderCounterInitialized) return; - - _folderCounter = 1000; // seed - var directories = GetDirectories(""); - foreach (var directory in directories) - { - if (long.TryParse(directory, out var folderNumber) && folderNumber > _folderCounter) - _folderCounter = folderNumber; - } - - // note: not multi-domains ie LB safe as another domain could create directories - // while we read and parse them - don't fix, move to new scheme eventually - - _folderCounterInitialized = true; - } + return MediaPathScheme.GetFilePath(cuid, puid, filename, prevpath); } #endregion @@ -429,282 +320,5 @@ namespace Umbraco.Core.IO } #endregion - - // fixme - remove - - //#region Manage thumbnails - - //// note: this does not find 'custom' thumbnails? - //// will find _thumb and _big-thumb but NOT _custom? - //public IEnumerable GetThumbnails(string path) - //{ - // var parentDirectory = Path.GetDirectoryName(path); - // var extension = Path.GetExtension(path); - - // return GetFiles(parentDirectory) - // .Where(x => x.StartsWith(path.TrimEnd(extension) + "_thumb") || x.StartsWith(path.TrimEnd(extension) + "_big-thumb")) - // .ToList(); - //} - - //public void DeleteFile(string path, bool deleteThumbnails) - //{ - // base.DeleteFile(path); - - // if (deleteThumbnails == false) - // return; - - // DeleteThumbnails(path); - //} - - //public void DeleteThumbnails(string path) - //{ - // GetThumbnails(path) - // .ForEach(x => base.DeleteFile(x)); - //} - - //public void CopyThumbnails(string sourcePath, string targetPath) - //{ - // var targetPathBase = Path.GetDirectoryName(targetPath) ?? ""; - // foreach (var sourceThumbPath in GetThumbnails(sourcePath)) - // { - // var sourceThumbFilename = Path.GetFileName(sourceThumbPath) ?? ""; - // var targetThumbPath = Path.Combine(targetPathBase, sourceThumbFilename); - // this.CopyFile(sourceThumbPath, targetThumbPath); - // } - //} - - //#endregion - - //#region GenerateThumbnails - - //public IEnumerable GenerateThumbnails(Image image, string filepath, string preValue) - //{ - // if (string.IsNullOrWhiteSpace(preValue)) - // return GenerateThumbnails(image, filepath); - - // var additionalSizes = new List(); - // var sep = preValue.Contains(",") ? "," : ";"; - // var values = preValue.Split(new[] { sep }, StringSplitOptions.RemoveEmptyEntries); - // foreach (var value in values) - // { - // int size; - // if (int.TryParse(value, out size)) - // additionalSizes.Add(size); - // } - - // return GenerateThumbnails(image, filepath, additionalSizes); - //} - - //public IEnumerable GenerateThumbnails(Image image, string filepath, IEnumerable additionalSizes = null) - //{ - // var w = image.Width; - // var h = image.Height; - - // var sizes = additionalSizes == null ? DefaultSizes.Keys : DefaultSizes.Keys.Concat(additionalSizes); - - // // start with default sizes, - // // add additional sizes, - // // filter out duplicates, - // // filter out those that would be larger that the original image - // // and create the thumbnail - // return sizes - // .Distinct() - // .Where(x => w >= x && h >= x) - // .Select(x => GenerateResized(image, filepath, DefaultSizes.ContainsKey(x) ? DefaultSizes[x] : "", x)) - // .ToList(); // now - //} - - //public IEnumerable GenerateThumbnails(Stream filestream, string filepath, PropertyType propertyType) - //{ - // // get the original image from the original stream - // if (filestream.CanSeek) filestream.Seek(0, 0); // fixme - what if we cannot seek? - // using (var image = Image.FromStream(filestream)) - // { - // return GenerateThumbnails(image, filepath, propertyType); - // } - //} - - //public IEnumerable GenerateThumbnails(Image image, string filepath, PropertyType propertyType) - //{ - // // if the editor is an upload field, check for additional thumbnail sizes - // // that can be defined in the prevalue for the property data type. otherwise, - // // just use the default sizes. - // var sizes = propertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias - // ? DataTypeService - // .GetPreValuesByDataTypeId(propertyType.DataTypeDefinitionId) - // .FirstOrDefault() - // : string.Empty; - - // return GenerateThumbnails(image, filepath, sizes); - //} - - //#endregion - - //#region GenerateResized - Generate at resized filepath derived from origin filepath - - //public ResizedImage GenerateResized(Image originImage, string originFilepath, string sizeName, int maxWidthHeight) - //{ - // return GenerateResized(originImage, originFilepath, sizeName, maxWidthHeight, -1, -1); - //} - - //public ResizedImage GenerateResized(Image originImage, string originFilepath, string sizeName, int fixedWidth, int fixedHeight) - //{ - // return GenerateResized(originImage, originFilepath, sizeName, -1, fixedWidth, fixedHeight); - //} - - //public ResizedImage GenerateResized(Image originImage, string originFilepath, string sizeName, int maxWidthHeight, int fixedWidth, int fixedHeight) - //{ - // if (string.IsNullOrWhiteSpace(sizeName)) - // sizeName = "UMBRACOSYSTHUMBNAIL"; - // var extension = Path.GetExtension(originFilepath) ?? string.Empty; - // var filebase = originFilepath.TrimEnd(extension); - // var resizedFilepath = filebase + "_" + sizeName + extension; - - // return GenerateResizedAt(originImage, resizedFilepath, maxWidthHeight, fixedWidth, fixedHeight); - //} - - //#endregion - - //#region GenerateResizedAt - Generate at specified resized filepath - - //public ResizedImage GenerateResizedAt(Image originImage, string resizedFilepath, int maxWidthHeight) - //{ - // return GenerateResizedAt(originImage, resizedFilepath, maxWidthHeight, -1, -1); - //} - - //public ResizedImage GenerateResizedAt(Image originImage, int fixedWidth, int fixedHeight, string resizedFilepath) - //{ - // return GenerateResizedAt(originImage, resizedFilepath, -1, fixedWidth, fixedHeight); - //} - - //public ResizedImage GenerateResizedAt(Image originImage, string resizedFilepath, int maxWidthHeight, int fixedWidth, int fixedHeight) - //{ - // // target dimensions - // int width, height; - - // // if maxWidthHeight then get ratio - // if (maxWidthHeight > 0) - // { - // var fx = (float)originImage.Size.Width / maxWidthHeight; - // var fy = (float)originImage.Size.Height / maxWidthHeight; - // var f = Math.Max(fx, fy); // fit in thumbnail size - // width = (int)Math.Round(originImage.Size.Width / f); - // height = (int)Math.Round(originImage.Size.Height / f); - // if (width == 0) width = 1; - // if (height == 0) height = 1; - // } - // else if (fixedWidth > 0 && fixedHeight > 0) - // { - // width = fixedWidth; - // height = fixedHeight; - // } - // else - // { - // width = height = 1; - // } - - // // create new image with best quality settings - // using (var bitmap = new Bitmap(width, height)) - // using (var graphics = Graphics.FromImage(bitmap)) - // { - // // if the image size is rather large we cannot use the best quality interpolation mode - // // because we'll get out of mem exceptions. So we detect how big the image is and use - // // the mid quality interpolation mode when the image size exceeds our max limit. - // graphics.InterpolationMode = originImage.Width > 5000 || originImage.Height > 5000 - // ? InterpolationMode.Bilinear // mid quality - // : InterpolationMode.HighQualityBicubic; // best quality - - // // everything else is best-quality - // graphics.SmoothingMode = SmoothingMode.HighQuality; - // graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; - // graphics.CompositingQuality = CompositingQuality.HighQuality; - - // // copy the old image to the new and resize - // var rect = new Rectangle(0, 0, width, height); - // graphics.DrawImage(originImage, rect, 0, 0, originImage.Width, originImage.Height, GraphicsUnit.Pixel); - - // // copy metadata - // // fixme - er... no? - - // // get an encoder - based upon the file type - // var extension = (Path.GetExtension(resizedFilepath) ?? "").TrimStart('.').ToLowerInvariant(); - // var encoders = ImageCodecInfo.GetImageEncoders(); - // ImageCodecInfo encoder; - // switch (extension) - // { - // case "png": - // encoder = encoders.Single(t => t.MimeType.Equals("image/png")); - // break; - // case "gif": - // encoder = encoders.Single(t => t.MimeType.Equals("image/gif")); - // break; - // case "tif": - // case "tiff": - // encoder = encoders.Single(t => t.MimeType.Equals("image/tiff")); - // break; - // case "bmp": - // encoder = encoders.Single(t => t.MimeType.Equals("image/bmp")); - // break; - // // TODO: this is dirty, defaulting to jpg but the return value of this thing is used all over the - // // place so left it here, but it needs to not set a codec if it doesn't know which one to pick - // // Note: when fixing this: both .jpg and .jpeg should be handled as extensions - // default: - // encoder = encoders.Single(t => t.MimeType.Equals("image/jpeg")); - // break; - // } - - // // set compresion ratio to 90% - // var encoderParams = new EncoderParameters(); - // encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 90L); - - // // save the new image - // using (var stream = new MemoryStream()) - // { - // bitmap.Save(stream, encoder, encoderParams); - // stream.Seek(0, 0); - // if (resizedFilepath.Contains("UMBRACOSYSTHUMBNAIL")) - // { - // var filepath = resizedFilepath.Replace("UMBRACOSYSTHUMBNAIL", maxWidthHeight.ToInvariantString()); - // AddFile(filepath, stream); - // if (extension != "jpg") - // { - // filepath = filepath.TrimEnd(extension) + "jpg"; - // stream.Seek(0, 0); - // AddFile(filepath, stream); - // } - // // TODO: Remove this, this is ONLY here for backwards compatibility but it is essentially completely unusable see U4-5385 - // stream.Seek(0, 0); - // resizedFilepath = resizedFilepath.Replace("UMBRACOSYSTHUMBNAIL", width + "x" + height); - // } - - // AddFile(resizedFilepath, stream); - // } - - // return new ResizedImage(resizedFilepath, width, height); - // } - //} - - //#endregion - - //#region Inner classes - - //public class ResizedImage - //{ - // public ResizedImage() - // { } - - // public ResizedImage(string filepath, int width, int height) - // { - // Filepath = filepath; - // Width = width; - // Height = height; - // } - - // public string Filepath { get; set; } - // public int Width { get; set; } - // public int Height { get; set; } - //} - - //#endregion } } diff --git a/src/Umbraco.Core/IO/MediaPathSchemes/CombinedGuidsMediaPathScheme.cs b/src/Umbraco.Core/IO/MediaPathSchemes/CombinedGuidsMediaPathScheme.cs new file mode 100644 index 0000000000..ef71aff3bc --- /dev/null +++ b/src/Umbraco.Core/IO/MediaPathSchemes/CombinedGuidsMediaPathScheme.cs @@ -0,0 +1,43 @@ +using System; +using System.IO; + +namespace Umbraco.Core.IO.MediaPathSchemes +{ + /// + /// Implements a combined-guids media path scheme. + /// + /// + /// Path is "{combinedGuid}/{filename>}" where combinedGuid is a combination of itemGuid and propertyGuid. + /// + public class CombinedGuidsMediaPathScheme : IMediaPathScheme + { + /// + public void Initialize(IFileSystem filesystem) + { } + + /// + public string GetFilePath(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 = Combine(itemGuid, propertyGuid).ToHexString(/*'/', 2, 4*/); // could use ext to fragment path eg 12/e4/f2/... + return Path.Combine(directory, filename).Replace('\\', '/'); + } + + /// + public string GetDeleteDirectory(string filepath) + { + return Path.GetDirectoryName(filepath); + } + + private static byte[] Combine(Guid guid1, Guid guid2) + { + var bytes1 = guid1.ToByteArray(); + var bytes2 = guid2.ToByteArray(); + var bytes = new byte[bytes1.Length]; + for (var i = 0; i < bytes1.Length; i++) + bytes[i] = (byte) (bytes1[i] ^ bytes2[i]); + return bytes; + } + } +} diff --git a/src/Umbraco.Core/IO/MediaPathSchemes/OriginalMediaPathScheme.cs b/src/Umbraco.Core/IO/MediaPathSchemes/OriginalMediaPathScheme.cs new file mode 100644 index 0000000000..1ed2ea59ce --- /dev/null +++ b/src/Umbraco.Core/IO/MediaPathSchemes/OriginalMediaPathScheme.cs @@ -0,0 +1,92 @@ +using System; +using System.Globalization; +using System.IO; +using System.Threading; +using Umbraco.Core.Configuration; + +namespace Umbraco.Core.IO.MediaPathSchemes +{ + /// + /// Implements the original media path scheme. + /// + /// + /// Path is "{number}/{filename}" or "{number}-{filename}" where number is an incremented counter. + /// Use '/' or '-' depending on UploadAllowDirectories setting. + /// + // scheme: path is "/" where number is an incremented counter + public class OriginalMediaPathScheme : IMediaPathScheme + { + private readonly object _folderCounterLock = new object(); + private IFileSystem _filesystem; + private long _folderCounter; + private bool _folderCounterInitialized; + + /// + public void Initialize(IFileSystem filesystem) + { + _filesystem = filesystem; + } + + /// + public string GetFilePath(Guid itemGuid, Guid propertyGuid, string filename, string previous = null) + { + string directory; + if (previous != null) + { + // old scheme, with a previous path + // prevpath should be "/" OR "-" + // and we want to reuse the "" part, so try to find it + + var sep = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories ? "/" : "-"; + var pos = previous.IndexOf(sep, StringComparison.Ordinal); + var s = pos > 0 ? previous.Substring(0, pos) : null; + + directory = pos > 0 && int.TryParse(s, out _) ? s : GetNextDirectory(); + } + else + { + directory = GetNextDirectory(); + } + + if (directory == null) + throw new InvalidOperationException("Cannot use a null directory."); + + return UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories + ? Path.Combine(directory, filename).Replace('\\', '/') + : directory + "-" + filename; + } + + /// + public string GetDeleteDirectory(string filepath) + { + return Path.GetDirectoryName(filepath); + } + + private string GetNextDirectory() + { + EnsureFolderCounterIsInitialized(); + return Interlocked.Increment(ref _folderCounter).ToString(CultureInfo.InvariantCulture); + } + + private void EnsureFolderCounterIsInitialized() + { + lock (_folderCounterLock) + { + if (_folderCounterInitialized) return; + + _folderCounter = 1000; // seed + var directories = _filesystem.GetDirectories(""); + foreach (var directory in directories) + { + if (long.TryParse(directory, out var folderNumber) && folderNumber > _folderCounter) + _folderCounter = folderNumber; + } + + // note: not multi-domains ie LB safe as another domain could create directories + // while we read and parse them - don't fix, move to new scheme eventually + + _folderCounterInitialized = true; + } + } + } +} diff --git a/src/Umbraco.Core/IO/MediaPathSchemes/TwoGuidsMediaPathScheme.cs b/src/Umbraco.Core/IO/MediaPathSchemes/TwoGuidsMediaPathScheme.cs new file mode 100644 index 0000000000..bf2a8cde28 --- /dev/null +++ b/src/Umbraco.Core/IO/MediaPathSchemes/TwoGuidsMediaPathScheme.cs @@ -0,0 +1,31 @@ +using System; +using System.IO; + +namespace Umbraco.Core.IO.MediaPathSchemes +{ + /// + /// Implements a two-guids media path scheme. + /// + /// + /// Path is "{itemGuid}-{propertyGuid}/{filename}". + /// + public class TwoGuidsMediaPathScheme : IMediaPathScheme + { + /// + public void Initialize(IFileSystem filesystem) + { } + + /// + public string GetFilePath(Guid itemGuid, Guid propertyGuid, string filename, string previous = null) + { + var directory = itemGuid.ToString("N") + "-" + propertyGuid.ToString("N"); + return Path.Combine(directory, filename).Replace('\\', '/'); + } + + /// + public string GetDeleteDirectory(string filepath) + { + return Path.GetDirectoryName(filepath); + } + } +} diff --git a/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs b/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs index a90d5b5b4c..90d3ece254 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Composing.CompositionRoots; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; +using Umbraco.Core.IO.MediaPathSchemes; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; using Umbraco.Core.Migrations; @@ -117,6 +118,8 @@ namespace Umbraco.Core.Runtime // by default, register a noop factory composition.Container.RegisterSingleton(); + + composition.Container.RegisterSingleton(); } internal void Initialize(IEnumerable mapperProfiles) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index e3ae9e31d5..9ebeb12b77 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -308,6 +308,10 @@ + + + +