using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml.Linq; using Umbraco.Core.Cache; using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using File = System.IO.File; namespace Umbraco.Core.Services { internal class ApplicationTreeService : IApplicationTreeService { private readonly CacheHelper _cache; public ApplicationTreeService(CacheHelper cache) { _cache = cache; } internal const string TreeConfigFileName = "trees.config"; private static string _treeConfig; private static readonly object Locker = new object(); /// /// gets/sets the trees.config file path /// /// /// The setter is generally only going to be used in unit tests, otherwise it will attempt to resolve it using the IOHelper.MapPath /// internal static string TreeConfigFilePath { get { if (string.IsNullOrWhiteSpace(_treeConfig)) { _treeConfig = IOHelper.MapPath(SystemDirectories.Config + "/" + TreeConfigFileName); } return _treeConfig; } set { _treeConfig = value; } } /// /// The cache storage for all application trees /// private List GetAppTrees() { return _cache.GetCacheItem( CacheKeys.ApplicationTreeCacheKey, () => { var list = new List(); LoadXml(doc => { foreach (var addElement in doc.Root.Elements("add").OrderBy(x => { var sortOrderAttr = x.Attribute("sortOrder"); return sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0; })) { var applicationAlias = (string) addElement.Attribute("application"); var type = (string) addElement.Attribute("type"); var assembly = (string) addElement.Attribute("assembly"); var clrType = Type.GetType(type); if (clrType == null) { LogHelper.Warn("The tree definition: " + addElement.ToString() + " could not be resolved to a .Net object type"); continue; } //check if the tree definition (applicationAlias + type + assembly) is already in the list if (list.Any(tree => tree.ApplicationAlias.InvariantEquals(applicationAlias) && tree.GetRuntimeType() == clrType) == false) { list.Add(new ApplicationTree( addElement.Attribute("initialize") == null || Convert.ToBoolean(addElement.Attribute("initialize").Value), addElement.Attribute("sortOrder") != null ? Convert.ToByte(addElement.Attribute("sortOrder").Value) : (byte) 0, addElement.Attribute("application").Value, addElement.Attribute("alias").Value, addElement.Attribute("title").Value, addElement.Attribute("iconClosed").Value, addElement.Attribute("iconOpen").Value, addElement.Attribute("type").Value)); } } }, false); return list; }); } public void Intitialize(IEnumerable existingTrees) { LoadXml(doc => { foreach (var tree in existingTrees) { doc.Root.Add(new XElement("add", new XAttribute("initialize", tree.Initialize), new XAttribute("sortOrder", tree.SortOrder), new XAttribute("alias", tree.Alias), new XAttribute("application", tree.ApplicationAlias), new XAttribute("title", tree.Title), new XAttribute("iconClosed", tree.IconClosed), new XAttribute("iconOpen", tree.IconOpened), new XAttribute("type", tree.Type))); } }, true); } /// /// Creates a new application tree. /// /// if set to true [initialize]. /// The sort order. /// The application alias. /// The alias. /// The title. /// The icon closed. /// The icon opened. /// The type. public void MakeNew(bool initialize, byte sortOrder, string applicationAlias, string alias, string title, string iconClosed, string iconOpened, string type) { LoadXml(doc => { var el = doc.Root.Elements("add").SingleOrDefault(x => x.Attribute("alias").Value == alias && x.Attribute("application").Value == applicationAlias); if (el == null) { doc.Root.Add(new XElement("add", new XAttribute("initialize", initialize), new XAttribute("sortOrder", sortOrder), new XAttribute("alias", alias), new XAttribute("application", applicationAlias), new XAttribute("title", title), new XAttribute("iconClosed", iconClosed), new XAttribute("iconOpen", iconOpened), new XAttribute("type", type))); } }, true); OnNew(new ApplicationTree(initialize, sortOrder, applicationAlias, alias, title, iconClosed, iconOpened, type), new EventArgs()); } /// /// Saves this instance. /// public void SaveTree(ApplicationTree tree) { LoadXml(doc => { var el = doc.Root.Elements("add").SingleOrDefault(x => x.Attribute("alias").Value == tree.Alias && x.Attribute("application").Value == tree.ApplicationAlias); if (el != null) { el.RemoveAttributes(); el.Add(new XAttribute("initialize", tree.Initialize)); el.Add(new XAttribute("sortOrder", tree.SortOrder)); el.Add(new XAttribute("alias", tree.Alias)); el.Add(new XAttribute("application", tree.ApplicationAlias)); el.Add(new XAttribute("title", tree.Title)); el.Add(new XAttribute("iconClosed", tree.IconClosed)); el.Add(new XAttribute("iconOpen", tree.IconOpened)); el.Add(new XAttribute("type", tree.Type)); } }, true); OnUpdated(tree, new EventArgs()); } /// /// Deletes this instance. /// public void DeleteTree(ApplicationTree tree) { LoadXml(doc => { doc.Root.Elements("add").Where(x => x.Attribute("application") != null && x.Attribute("application").Value == tree.ApplicationAlias && x.Attribute("alias") != null && x.Attribute("alias").Value == tree.Alias).Remove(); }, true); OnDeleted(tree, new EventArgs()); } /// /// Gets an ApplicationTree by it's tree alias. /// /// The tree alias. /// An ApplicationTree instance public ApplicationTree GetByAlias(string treeAlias) { return GetAppTrees().Find(t => (t.Alias == treeAlias)); } /// /// Gets all applicationTrees registered in umbraco from the umbracoAppTree table.. /// /// Returns a ApplicationTree Array public IEnumerable GetAll() { return GetAppTrees().OrderBy(x => x.SortOrder); } /// /// Gets the application tree for the applcation with the specified alias /// /// The application alias. /// Returns a ApplicationTree Array public IEnumerable GetApplicationTrees(string applicationAlias) { return GetApplicationTrees(applicationAlias, false); } /// /// Gets the application tree for the applcation with the specified alias /// /// The application alias. /// /// Returns a ApplicationTree Array public IEnumerable GetApplicationTrees(string applicationAlias, bool onlyInitialized) { var list = GetAppTrees().FindAll( t => { if (onlyInitialized) return (t.ApplicationAlias == applicationAlias && t.Initialize); return (t.ApplicationAlias == applicationAlias); } ); return list.OrderBy(x => x.SortOrder).ToArray(); } internal void LoadXml(Action callback, bool saveAfterCallback) { lock (Locker) { var doc = File.Exists(TreeConfigFilePath) ? XDocument.Load(TreeConfigFilePath) : XDocument.Parse(""); if (doc.Root != null) { callback.Invoke(doc); if (saveAfterCallback) { Directory.CreateDirectory(Path.GetDirectoryName(TreeConfigFilePath)); doc.Save(TreeConfigFilePath); //remove the cache now that it has changed SD: I'm leaving this here even though it // is taken care of by events as well, I think unit tests may rely on it being cleared here. _cache.ClearCacheItem(CacheKeys.ApplicationTreeCacheKey); } } } } internal static event TypedEventHandler Deleted; private static void OnDeleted(ApplicationTree app, EventArgs args) { if (Deleted != null) { Deleted(app, args); } } internal static event TypedEventHandler New; private static void OnNew(ApplicationTree app, EventArgs args) { if (New != null) { New(app, args); } } internal static event TypedEventHandler Updated; private static void OnUpdated(ApplicationTree app, EventArgs args) { if (Updated != null) { Updated(app, args); } } } }