diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index 8fcda3d10e..79b453e9cb 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; -using System.Globalization; -using System.IO; +using System.IO; using System.Linq; using Umbraco.Core.Logging; @@ -9,7 +8,7 @@ namespace Umbraco.Core.IO { public class PhysicalFileSystem : IFileSystem { - internal string RootPath { get; private set; } + private readonly string _rootPath; private readonly string _rootUrl; public PhysicalFileSystem(string virtualRoot) @@ -18,8 +17,12 @@ namespace Umbraco.Core.IO if (virtualRoot.StartsWith("~/") == false) throw new ArgumentException("The virtualRoot argument must be a virtual path and start with '~/'"); - RootPath = IOHelper.MapPath(virtualRoot); + _rootPath = IOHelper.MapPath(virtualRoot); + _rootPath = EnsureDirectorySeparatorChar(_rootPath); + _rootPath = _rootPath.TrimEnd(Path.DirectorySeparatorChar); + _rootUrl = IOHelper.ResolveUrl(virtualRoot); + _rootUrl = _rootUrl.TrimEnd('/'); } public PhysicalFileSystem(string rootPath, string rootUrl) @@ -43,18 +46,18 @@ namespace Umbraco.Core.IO rootPath = Path.Combine(localRoot, rootPath); } - RootPath = rootPath; - _rootUrl = rootUrl; + _rootPath = rootPath.TrimEnd(Path.DirectorySeparatorChar); + _rootUrl = rootUrl.TrimEnd('/'); } public IEnumerable GetDirectories(string path) { - path = EnsureTrailingSeparator(GetFullPath(path)); + var fullPath = GetFullPath(path); try { - if (Directory.Exists(path)) - return Directory.EnumerateDirectories(path).Select(GetRelativePath); + if (Directory.Exists(fullPath)) + return Directory.EnumerateDirectories(fullPath).Select(GetRelativePath); } catch (UnauthorizedAccessException ex) { @@ -75,12 +78,13 @@ namespace Umbraco.Core.IO public void DeleteDirectory(string path, bool recursive) { - if (DirectoryExists(path) == false) + var fullPath = GetFullPath(path); + if (Directory.Exists(fullPath) == false) return; try { - Directory.Delete(GetFullPath(path), recursive); + Directory.Delete(fullPath, recursive); } catch (DirectoryNotFoundException ex) { @@ -90,7 +94,8 @@ namespace Umbraco.Core.IO public bool DirectoryExists(string path) { - return Directory.Exists(GetFullPath(path)); + var fullPath = GetFullPath(path); + return Directory.Exists(fullPath); } public void AddFile(string path, Stream stream) @@ -100,17 +105,17 @@ namespace Umbraco.Core.IO public void AddFile(string path, Stream stream, bool overrideIfExists) { - var fsRelativePath = GetRelativePath(path); + var fullPath = GetFullPath(path); + var exists = File.Exists(fullPath); + if (exists && overrideIfExists == false) + throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); - var exists = FileExists(fsRelativePath); - if (exists && overrideIfExists == false) throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); - - EnsureDirectory(Path.GetDirectoryName(fsRelativePath)); + Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); // ensure it exists if (stream.CanSeek) stream.Seek(0, 0); - using (var destination = (Stream)File.Create(GetFullPath(fsRelativePath))) + using (var destination = (Stream)File.Create(fullPath)) stream.CopyTo(destination); } @@ -121,9 +126,7 @@ namespace Umbraco.Core.IO public IEnumerable GetFiles(string path, string filter) { - var fsRelativePath = GetRelativePath(path); - - var fullPath = EnsureTrailingSeparator(GetFullPath(fsRelativePath)); + var fullPath = GetFullPath(path); try { @@ -150,12 +153,13 @@ namespace Umbraco.Core.IO public void DeleteFile(string path) { - if (!FileExists(path)) + var fullPath = GetFullPath(path); + if (File.Exists(fullPath) == false) return; try { - File.Delete(GetFullPath(path)); + File.Delete(fullPath); } catch (FileNotFoundException ex) { @@ -165,53 +169,101 @@ namespace Umbraco.Core.IO public bool FileExists(string path) { - return File.Exists(GetFullPath(path)); + var fullpath = GetFullPath(path); + return File.Exists(fullpath); } + /// + /// Gets the relative path. + /// + /// The full path or url. + /// The path, relative to this filesystem's root. + /// + /// The relative path is relative to this filesystem's root, not starting with any + /// directory separator. All separators are converted to Path.DirectorySeparatorChar. + /// public string GetRelativePath(string fullPathOrUrl) { - var relativePath = fullPathOrUrl - .TrimStart(_rootUrl) - .Replace('/', Path.DirectorySeparatorChar) - .TrimStart(RootPath) - .TrimStart(Path.DirectorySeparatorChar); + // test url + var path = EnsureUrlSeparatorChar(fullPathOrUrl); + if (PathStartsWith(path, _rootUrl, '/')) + return path.Substring(_rootUrl.Length) + .Replace('/', Path.DirectorySeparatorChar) + .TrimStart(Path.DirectorySeparatorChar); - return relativePath; + // test path + path = EnsureDirectorySeparatorChar(fullPathOrUrl); + if (PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar)) + return path.Substring(_rootPath.Length) + .TrimStart(Path.DirectorySeparatorChar); + + return fullPathOrUrl; + + // previous code kept for reference + + //var relativePath = fullPathOrUrl + // .TrimStart(_rootUrl) + // .Replace('/', Path.DirectorySeparatorChar) + // .TrimStart(RootPath) + // .TrimStart(Path.DirectorySeparatorChar); + //return relativePath; } + /// + /// Gets the full path. + /// + /// The full or relative path. + /// The full path. + /// + /// On the physical filesystem, the full path is the rooted (ie non-relative), safe (ie within this + /// filesystem's root) path. All separators are converted to Path.DirectorySeparatorChar. + /// public string GetFullPath(string path) { - //if the path starts with a '/' then it's most likely not a FS relative path which is required so convert it - if (path.StartsWith("/")) - { + // normalize + var opath = path; + path = EnsureDirectorySeparatorChar(path); + + // not sure what we are doing here - so if input starts with a (back) slash, + // we assume it's not a FS relative path and we try to convert it... but it + // really makes little sense? + if (path.StartsWith(Path.DirectorySeparatorChar.ToString())) path = GetRelativePath(path); - } // if already a full path, return - if (path.StartsWith(RootPath)) + if (PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar)) return path; // else combine and sanitize, ie GetFullPath will take care of any relative // segments in path, eg '../../foo.tmp' - it may throw a SecurityException // if the combined path reaches illegal parts of the filesystem - var fpath = Path.Combine(RootPath, path); + var fpath = Path.Combine(_rootPath, path); fpath = Path.GetFullPath(fpath); // at that point, path is within legal parts of the filesystem, ie we have // permissions to reach that path, but it may nevertheless be outside of // our root path, due to relative segments, so better check - if (fpath.StartsWith(RootPath)) + if (PathStartsWith(fpath, _rootPath, Path.DirectorySeparatorChar)) return fpath; - throw new FileSecurityException("File '" + path + "' is outside this filesystem's root."); + throw new FileSecurityException("File '" + opath + "' is outside this filesystem's root."); + } + + private static bool PathStartsWith(string path, string root, char separator) + { + // either it is identical to root, + // or it is root + separator + anything + + if (path.StartsWith(root, StringComparison.OrdinalIgnoreCase) == false) return false; + if (path.Length == root.Length) return true; + if (path.Length < root.Length) return false; + return path[root.Length] == separator; } public string GetUrl(string path) { - return _rootUrl.TrimEnd("/") + "/" + path - .TrimStart(Path.DirectorySeparatorChar) - .Replace(Path.DirectorySeparatorChar, '/') - .TrimEnd("/"); + path = EnsureUrlSeparatorChar(path).Trim('/'); + return _rootUrl + "/" + path; } public DateTimeOffset GetLastModified(string path) @@ -238,9 +290,19 @@ namespace Umbraco.Core.IO protected string EnsureTrailingSeparator(string path) { - if (!path.EndsWith(Path.DirectorySeparatorChar.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal)) - path = path + Path.DirectorySeparatorChar; + return path.EnsureEndsWith(Path.DirectorySeparatorChar); + } + protected string EnsureDirectorySeparatorChar(string path) + { + path = path.Replace('/', Path.DirectorySeparatorChar); + path = path.Replace('\\', Path.DirectorySeparatorChar); + return path; + } + + protected string EnsureUrlSeparatorChar(string path) + { + path = path.Replace('\\', '/'); return path; } diff --git a/src/Umbraco.Core/Models/File.cs b/src/Umbraco.Core/Models/File.cs index ae4aac6a1c..ce6121f46f 100644 --- a/src/Umbraco.Core/Models/File.cs +++ b/src/Umbraco.Core/Models/File.cs @@ -2,6 +2,8 @@ using System.IO; using System.Reflection; using System.Runtime.Serialization; +using System.Text; +using Umbraco.Core.IO; using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models @@ -15,12 +17,20 @@ namespace Umbraco.Core.Models { private string _path; private string _originalPath; - private string _content = string.Empty; //initialize to empty string, not null - protected File(string path) + // initialize to string.Empty so that it is possible to save a new file, + // should use the lazyContent ctor to set it to null when loading existing. + // cannot simply use HasIdentity as some classes (eg Script) override it + // in a weird way. + private string _content; + internal Func GetFileContent { get; set; } + + protected File(string path, Func getFileContent = null) { _path = path; _originalPath = _path; + GetFileContent = getFileContent; + _content = getFileContent != null ? null : string.Empty; } private static readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo(x => x.Content); @@ -96,15 +106,26 @@ namespace Umbraco.Core.Models /// /// Gets or sets the Content of a File /// + /// Marked as DoNotClone, because it should be lazy-reloaded from disk. [DataMember] + [DoNotClone] public virtual string Content { - get { return _content; } + get + { + if (_content != null) + return _content; + + // else, must lazy-load, and ensure it's not null + if (GetFileContent != null) + _content = GetFileContent(this); + return _content ?? (_content = string.Empty); + } set { SetPropertyValueAndDetectChanges(o => { - _content = value; + _content = value ?? string.Empty; // cannot set to null return _content; }, _content, ContentSelector); } @@ -121,17 +142,32 @@ namespace Umbraco.Core.Models return true; } + // this exists so that class that manage name and alias differently, eg Template, + // can implement their own cloning - (though really, not sure it's even needed) + protected virtual void DeepCloneNameAndAlias(File clone) + { + // set fields that have a lazy value, by forcing evaluation of the lazy + clone._name = Name; + clone._alias = Alias; + } + public override object DeepClone() { - var clone = (File)base.DeepClone(); - //turn off change tracking + var clone = (File) base.DeepClone(); + + // clear fields that were memberwise-cloned and that we don't want to clone + clone._content = null; + + // turn off change tracking clone.DisableChangeTracking(); - //need to manually assign since they are readonly properties - clone._alias = Alias; - clone._name = Name; - //this shouldn't really be needed since we're not tracking + + // ... + DeepCloneNameAndAlias(clone); + + // this shouldn't really be needed since we're not tracking clone.ResetDirtyProperties(false); - //re-enable tracking + + // re-enable tracking clone.EnableChangeTracking(); return clone; diff --git a/src/Umbraco.Core/Models/PartialView.cs b/src/Umbraco.Core/Models/PartialView.cs index d483de4176..75914820f0 100644 --- a/src/Umbraco.Core/Models/PartialView.cs +++ b/src/Umbraco.Core/Models/PartialView.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.Serialization; -using System.Text.RegularExpressions; -using Umbraco.Core.IO; +using Umbraco.Core.Services; namespace Umbraco.Core.Models { @@ -15,12 +12,14 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class PartialView : File, IPartialView { - public PartialView(string path) - : base(path) - { - base.Path = path; - } + : this(path, null) + { } + internal PartialView(string path, Func getFileContent) + : base(path, getFileContent) + { } + + internal PartialViewType ViewType { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PartialViewType.cs b/src/Umbraco.Core/Models/PartialViewType.cs new file mode 100644 index 0000000000..2b45448271 --- /dev/null +++ b/src/Umbraco.Core/Models/PartialViewType.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Core.Models +{ + internal enum PartialViewType : byte + { + Unknown = 0, // default + PartialView = 1, + PartialViewMacro = 2 + } +} diff --git a/src/Umbraco.Core/Models/Script.cs b/src/Umbraco.Core/Models/Script.cs index 71f0684447..325885d9ba 100644 --- a/src/Umbraco.Core/Models/Script.cs +++ b/src/Umbraco.Core/Models/Script.cs @@ -1,7 +1,5 @@ using System; -using System.Linq; using System.Runtime.Serialization; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; @@ -15,16 +13,17 @@ namespace Umbraco.Core.Models public class Script : File { public Script(string path) - : base(path) - { - - } + : this(path, (Func) null) + { } + + internal Script(string path, Func getFileContent) + : base(path, getFileContent) + { } [Obsolete("This is no longer used and will be removed from the codebase in future versions")] public Script(string path, IContentSection contentConfig) - : this(path) - { - } + : base(path) + { } /// /// Indicates whether the current entity has an identity, which in this case is a path/name. diff --git a/src/Umbraco.Core/Models/Stylesheet.cs b/src/Umbraco.Core/Models/Stylesheet.cs index ca203d835d..060246df54 100644 --- a/src/Umbraco.Core/Models/Stylesheet.cs +++ b/src/Umbraco.Core/Models/Stylesheet.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Data; using System.Linq; using System.Runtime.Serialization; -using System.Text; using Umbraco.Core.IO; using Umbraco.Core.Strings.Css; @@ -16,12 +16,16 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class Stylesheet : File { - public Stylesheet(string path) - : base(path.EnsureEndsWith(".css")) - { + public Stylesheet(string path) + : this(path, null) + { } + + internal Stylesheet(string path, Func getFileContent) + : base(path.EnsureEndsWith(".css"), getFileContent) + { InitializeProperties(); } - + private Lazy> _properties; private void InitializeProperties() @@ -81,7 +85,7 @@ namespace Umbraco.Core.Models /// /// /// - void Property_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + void Property_PropertyChanged(object sender, PropertyChangedEventArgs e) { var prop = (StylesheetProperty) sender; diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs index 3a8cbf8794..4aca88f286 100644 --- a/src/Umbraco.Core/Models/Template.cs +++ b/src/Umbraco.Core/Models/Template.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.Drawing; +using System.IO; using System.Reflection; using System.Runtime.Serialization; +using System.Text; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; @@ -29,12 +32,16 @@ namespace Umbraco.Core.Models private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); public Template(string name, string alias) - : base(string.Empty) + : this(name, alias, (Func) null) + { } + + internal Template(string name, string alias, Func getFileContent) + : base(string.Empty, getFileContent) { _name = name; _alias = alias.ToCleanString(CleanStringType.UnderscoreAlias); _masterTemplateId = new Lazy(() => -1); - } + } [Obsolete("This constructor should not be used, file path is determined by alias, setting the path here will have no affect")] public Template(string path, string name, string alias) @@ -123,7 +130,6 @@ namespace Umbraco.Core.Models Key = Guid.NewGuid(); } - public void SetMasterTemplate(ITemplate masterTemplate) { if (masterTemplate == null) @@ -139,27 +145,9 @@ namespace Umbraco.Core.Models } - public override object DeepClone() + protected override void DeepCloneNameAndAlias(File clone) { - - //We cannot call in to the base classes to clone because the base File class treats Alias, Name.. differently so we need to manually do the clone - - //Memberwise clone on Entity will work since it doesn't have any deep elements - // for any sub class this will work for standard properties as well that aren't complex object's themselves. - var clone = (Template)MemberwiseClone(); - //turn off change tracking - clone.DisableChangeTracking(); - //Automatically deep clone ref properties that are IDeepCloneable - DeepCloneHelper.DeepCloneRefProperties(this, clone); - - //this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; + // do nothing - prevents File from doing its stuff } - - } } diff --git a/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs b/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs index d1742dc030..60cde916b6 100644 --- a/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs @@ -33,9 +33,9 @@ namespace Umbraco.Core.Persistence.Factories #region Implementation of IEntityFactory - public Template BuildEntity(TemplateDto dto, IEnumerable childDefinitions) + public Template BuildEntity(TemplateDto dto, IEnumerable childDefinitions, Func getFileContent) { - var template = new Template(dto.NodeDto.Text, dto.Alias) + var template = new Template(dto.NodeDto.Text, dto.Alias, getFileContent) { CreateDate = dto.NodeDto.CreateDate, Id = dto.NodeId, diff --git a/src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs b/src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs index a87ec1e764..0b5b42660d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs @@ -79,7 +79,7 @@ namespace Umbraco.Core.Persistence.Repositories public object GetCacheItem(string cacheKey, Func getCacheItem) { - return InnerProvider.GetCacheItem(cacheKey, () => + var cached = InnerProvider.GetCacheItem(cacheKey, () => { var result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem); var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache @@ -87,18 +87,21 @@ namespace Umbraco.Core.Persistence.Repositories return CheckCloneableAndTracksChanges(value); }); + return CheckCloneableAndTracksChanges(cached); } public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { - return InnerProvider.GetCacheItem(cacheKey, () => + var cached = InnerProvider.GetCacheItem(cacheKey, () => { var result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem); var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache if (value == null) return null; // do not store null values (backward compat) return CheckCloneableAndTracksChanges(value); - }, timeout, isSliding, priority, removedCallback, dependentFiles); + }, timeout, isSliding, priority, removedCallback, dependentFiles); + + return CheckCloneableAndTracksChanges(cached); } public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) diff --git a/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs b/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs index 391a777e5e..04ac5b6f62 100644 --- a/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs @@ -219,6 +219,15 @@ namespace Umbraco.Core.Persistence.Repositories return list; } + protected string GetFileContent(string filename) + { + using (var stream = FileSystem.OpenFile(filename)) + using (var reader = new StreamReader(stream, Encoding.UTF8, true)) + { + return reader.ReadToEnd(); + } + } + /// /// Dispose any disposable properties /// diff --git a/src/Umbraco.Core/Persistence/Repositories/PartialViewMacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PartialViewMacroRepository.cs index f4d6906cd1..e055c3dd93 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PartialViewMacroRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PartialViewMacroRepository.cs @@ -7,7 +7,6 @@ namespace Umbraco.Core.Persistence.Repositories { internal class PartialViewMacroRepository : PartialViewRepository { - public PartialViewMacroRepository(IUnitOfWork work) : this(work, new PhysicalFileSystem(SystemDirectories.MvcViews + "/MacroPartials/")) { @@ -18,5 +17,6 @@ namespace Umbraco.Core.Persistence.Repositories { } + protected override PartialViewType ViewType { get { return PartialViewType.PartialViewMacro; } } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/PartialViewRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PartialViewRepository.cs index 2fd0240701..254d2cbc0c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PartialViewRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PartialViewRepository.cs @@ -20,6 +20,8 @@ namespace Umbraco.Core.Persistence.Repositories { } + protected virtual PartialViewType ViewType { get { return PartialViewType.PartialView; } } + public override IPartialView Get(string id) { if (FileSystem.FileExists(id) == false) @@ -27,29 +29,19 @@ namespace Umbraco.Core.Persistence.Repositories return null; } - string content; - using (var stream = FileSystem.OpenFile(id)) - { - var bytes = new byte[stream.Length]; - stream.Position = 0; - stream.Read(bytes, 0, (int)stream.Length); - content = Encoding.UTF8.GetString(bytes); - } - var path = FileSystem.GetRelativePath(id); var created = FileSystem.GetCreated(path).UtcDateTime; var updated = FileSystem.GetLastModified(path).UtcDateTime; - - var script = new PartialView(path) + var script = new PartialView(path, file => GetFileContent(file.Path)) { //id can be the hash Id = path.GetHashCode(), - Content = content, Key = path.EncodeAsGuid(), CreateDate = created, UpdateDate = updated, - VirtualPath = FileSystem.GetUrl(id) + VirtualPath = FileSystem.GetUrl(id), + ViewType = ViewType }; //on initial construction we don't want to have dirty properties tracked @@ -59,6 +51,19 @@ namespace Umbraco.Core.Persistence.Repositories return script; } + public override void AddOrUpdate(IPartialView entity) + { + var partialView = entity as PartialView; + if (partialView != null) + partialView.ViewType = ViewType; + + base.AddOrUpdate(entity); + + // ensure that from now on, content is lazy-loaded + if (partialView != null && partialView.GetFileContent == null) + partialView.GetFileContent = file => GetFileContent(file.Path); + } + public override IEnumerable GetAll(params string[] ids) { //ensure they are de-duplicated, easy win if people don't do this as this can cause many excess queries diff --git a/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs index f8fc459aec..e288efdc85 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Text; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Models; @@ -28,33 +26,27 @@ namespace Umbraco.Core.Persistence.Repositories public override Script Get(string id) { - if(FileSystem.FileExists(id) == false) - { - return null; - } - - string content; - using (var stream = FileSystem.OpenFile(id)) - { - var bytes = new byte[stream.Length]; - stream.Position = 0; - stream.Read(bytes, 0, (int)stream.Length); - content = Encoding.UTF8.GetString(bytes); - } - + // get the relative path within the filesystem + // (though... id should be relative already) var path = FileSystem.GetRelativePath(id); + + if (FileSystem.FileExists(path) == false) + return null; + + // content will be lazy-loaded when required var created = FileSystem.GetCreated(path).UtcDateTime; var updated = FileSystem.GetLastModified(path).UtcDateTime; + //var content = GetFileContent(path); - var script = new Script(path) + var script = new Script(path, file => GetFileContent(file.Path)) { //id can be the hash Id = path.GetHashCode(), - Content = content, Key = path.EncodeAsGuid(), + //Content = content, CreateDate = created, UpdateDate = updated, - VirtualPath = FileSystem.GetUrl(id) + VirtualPath = FileSystem.GetUrl(path) }; //on initial construction we don't want to have dirty properties tracked @@ -64,6 +56,15 @@ namespace Umbraco.Core.Persistence.Repositories return script; } + public override void AddOrUpdate(Script entity) + { + base.AddOrUpdate(entity); + + // ensure that from now on, content is lazy-loaded + if (entity.GetFileContent == null) + entity.GetFileContent = file => GetFileContent(file.Path); + } + public override IEnumerable