using System; using System.IO; using System.Reflection; using System.Runtime.Serialization; using System.Text; using Umbraco.Core.IO; using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { /// /// Represents an abstract file which provides basic functionality for a File with an Alias and Name /// [Serializable] [DataContract(IsReference = true)] public abstract class File : EntityBase, IFile { private string _path; private string _originalPath; // 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 = SanitizePath(path); _originalPath = _path; GetFileContent = getFileContent; _content = getFileContent != null ? null : string.Empty; } private static readonly Lazy Ps = new Lazy(); private class PropertySelectors { public readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo(x => x.Content); public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); } private string _alias; private string _name; private static string SanitizePath(string path) { return path .Replace('\\', System.IO.Path.DirectorySeparatorChar) .Replace('/', System.IO.Path.DirectorySeparatorChar); //Don't strip the start - this was a bug fixed in 7.3, see ScriptRepositoryTests.PathTests //.TrimStart(System.IO.Path.DirectorySeparatorChar) //.TrimStart('/'); } /// /// Gets or sets the Name of the File including extension /// [DataMember] public virtual string Name { get { return _name ?? (_name = System.IO.Path.GetFileName(Path)); } } /// /// Gets or sets the Alias of the File, which is the name without the extension /// [DataMember] public virtual string Alias { get { if (_alias == null) { var name = System.IO.Path.GetFileName(Path); if (name == null) return string.Empty; var lastIndexOf = name.LastIndexOf(".", StringComparison.InvariantCultureIgnoreCase); _alias = name.Substring(0, lastIndexOf); } return _alias; } } /// /// Gets or sets the Path to the File from the root of the file's associated IFileSystem /// [DataMember] public virtual string Path { get { return _path; } set { //reset _alias = null; _name = null; SetPropertyValueAndDetectChanges(SanitizePath(value), ref _path, Ps.Value.PathSelector); } } /// /// Gets the original path of the file /// public string OriginalPath { get { return _originalPath; } } /// /// Called to re-set the OriginalPath to the Path /// public void ResetOriginalPath() { _originalPath = _path; } /// /// 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 { 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( value ?? string.Empty, // cannot set to null ref _content, Ps.Value.ContentSelector); } } /// /// Gets or sets the file's virtual path (i.e. the file path relative to the root of the website) /// public string VirtualPath { get; set; } // 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(); // clear fields that were memberwise-cloned and that we don't want to clone clone._content = null; // turn off change tracking clone.DisableChangeTracking(); // ... DeepCloneNameAndAlias(clone); // this shouldn't really be needed since we're not tracking clone.ResetDirtyProperties(false); // re-enable tracking clone.EnableChangeTracking(); return clone; } } }