Compare commits

..

1 Commits

Author SHA1 Message Date
Sebastiaan Janssen 10ab3c71fd Bump version 2014-10-08 09:44:59 +02:00
66 changed files with 581 additions and 1697 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
@ECHO OFF
SET release=6.2.6
SET release=6.2.4
SET comment=
SET version=%release%
+1 -1
View File
@@ -28,7 +28,7 @@
<dependency id="SharpZipLib" version="[0.86.0, 1.0.0)" />
<dependency id="MySql.Data" version="[6.6.5]" />
<dependency id="xmlrpcnet" version="[2.5.0, 3.0.0)" />
<dependency id="ClientDependency" version="[1.8.2.1, 2.0.0)" />
<dependency id="ClientDependency" version="[1.7.1.2, 2.0.0)" />
<dependency id="ClientDependency-Mvc" version="[1.7.0.4, 2.0.0)" />
<dependency id="Newtonsoft.Json" version="[4.5.11, 6.0.0)" />
<dependency id="Examine" version="[0.1.57, 1.0.0)" />
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<UmbracoVersion>6.2.6</UmbracoVersion>
<UmbracoVersion>6.2.4</UmbracoVersion>
</PropertyGroup>
<Target Name="CopyUmbracoFilesToWebRoot" BeforeTargets="AfterBuild">
<PropertyGroup>
-11
View File
@@ -222,16 +222,5 @@ namespace SqlCE4Umbraco
return new SqlCeDataReaderHelper(SqlCeApplicationBlock.ExecuteReader(ConnectionString, CommandType.Text,
commandText, parameters));
}
internal IRecordsReader ExecuteReader(string commandText)
{
return ExecuteReader(commandText, new SqlCEParameter(string.Empty, string.Empty));
}
internal int ExecuteNonQuery(string commandText)
{
return ExecuteNonQuery(commandText, new SqlCEParameter(string.Empty, string.Empty));
}
}
}
@@ -843,11 +843,6 @@ namespace Umbraco.Core.Configuration
get { return GetKeyAsNode("/settings/scheduledTasks"); }
}
internal static string ScheduledTasksBaseUrl
{
get { return GetKey("/settings/scheduledTasks/@baseUrl"); }
}
/// <summary>
/// Gets a list of characters that will be replaced when generating urls
/// </summary>
@@ -5,7 +5,7 @@ namespace Umbraco.Core.Configuration
{
public class UmbracoVersion
{
private static readonly Version Version = new Version("6.2.6");
private static readonly Version Version = new Version("6.2.4");
/// <summary>
/// Gets the current version of Umbraco.
+1 -5
View File
@@ -449,14 +449,10 @@ namespace Umbraco.Core.Models
public override object DeepClone()
{
var clone = (Content)base.DeepClone();
//turn off change tracking
clone.DisableChangeTracking();
//need to manually clone this since it's not settable
clone._contentType = (IContentType)ContentType.DeepClone();
//this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);
//re-enable tracking
clone.EnableChangeTracking();
return clone;
@@ -10,14 +10,12 @@ namespace Umbraco.Core.Models
internal class ContentPreviewEntity<TContent> : ContentXmlEntity<TContent>
where TContent : IContentBase
{
public ContentPreviewEntity(TContent content, Func<TContent, XElement> xml)
: base(content, xml)
public ContentPreviewEntity(bool previewExists, TContent content, Func<TContent, XElement> xml)
: base(previewExists, content, xml)
{
Version = content.Version;
}
public Guid Version
{
get { return Content.Version; }
}
public Guid Version { get; private set; }
}
}
+1 -6
View File
@@ -599,18 +599,13 @@ namespace Umbraco.Core.Models
public override object DeepClone()
{
var clone = (ContentTypeBase)base.DeepClone();
//turn off change tracking
clone.DisableChangeTracking();
//need to manually wire up the event handlers for the property type collections - we've ensured
// its ignored from the auto-clone process because its return values are unions, not raw and
// we end up with duplicates, see: http://issues.umbraco.org/issue/U4-4842
clone._propertyTypes = (PropertyTypeCollection)_propertyTypes.DeepClone();
clone._propertyTypes.CollectionChanged += clone.PropertyTypesChanged;
//this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);
//re-enable tracking
clone.EnableChangeTracking();
return clone;
}
@@ -221,15 +221,11 @@ namespace Umbraco.Core.Models
public override object DeepClone()
{
var clone = (ContentTypeCompositionBase)base.DeepClone();
//turn off change tracking
clone.DisableChangeTracking();
//need to manually assign since this is an internal field and will not be automatically mapped
clone.RemovedContentTypeKeyTracker = new List<int>();
clone._contentTypeComposition = ContentTypeComposition.Select(x => (IContentTypeComposition)x.DeepClone()).ToList();
//this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);
//re-enable tracking
clone.EnableChangeTracking();
return clone;
}
+5 -9
View File
@@ -11,11 +11,13 @@ namespace Umbraco.Core.Models
internal class ContentXmlEntity<TContent> : IAggregateRoot
where TContent : IContentBase
{
private readonly bool _entityExists;
private readonly Func<TContent, XElement> _xml;
public ContentXmlEntity(TContent content, Func<TContent, XElement> xml)
{
public ContentXmlEntity(bool entityExists, TContent content, Func<TContent, XElement> xml)
{
if (content == null) throw new ArgumentNullException("content");
_entityExists = entityExists;
_xml = xml;
Content = content;
}
@@ -30,7 +32,6 @@ namespace Umbraco.Core.Models
{
get { return _xml(Content); }
}
public TContent Content { get; private set; }
public int Id
@@ -43,14 +44,9 @@ namespace Umbraco.Core.Models
public DateTime CreateDate { get; set; }
public DateTime UpdateDate { get; set; }
/// <summary>
/// Special case, always return false, this will cause the repositories managing
/// this object to always do an 'insert' but these are special repositories that
/// do an InsertOrUpdate on insert since the data for this needs to be managed this way
/// </summary>
public bool HasIdentity
{
get { return false; }
get { return _entityExists; }
}
public object DeepClone()
@@ -236,17 +236,9 @@ namespace Umbraco.Core.Models.EntityBase
//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 = (Entity)MemberwiseClone();
//ensure the clone has it's own dictionaries
clone.ResetChangeTrackingCollections();
//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;
//Using data contract serializer - has issues
@@ -14,16 +14,6 @@ namespace Umbraco.Core.Models.EntityBase
[DataContract(IsReference = true)]
public abstract class TracksChangesEntityBase : IRememberBeingDirty
{
//TODO: This needs to go on to ICanBeDirty http://issues.umbraco.org/issue/U4-5662
public virtual IEnumerable<string> GetDirtyProperties()
{
return _propertyChangedInfo.Where(x => x.Value).Select(x => x.Key);
}
private bool _changeTrackingEnabled = true;
/// <summary>
/// Tracks the properties that have changed
/// </summary>
@@ -45,9 +35,6 @@ namespace Umbraco.Core.Models.EntityBase
/// <param name="propertyInfo">The property info.</param>
protected virtual void OnPropertyChanged(PropertyInfo propertyInfo)
{
//return if we're not tracking changes
if (_changeTrackingEnabled == false) return;
_propertyChangedInfo[propertyInfo.Name] = true;
if (PropertyChanged != null)
@@ -139,22 +126,6 @@ namespace Umbraco.Core.Models.EntityBase
_propertyChangedInfo = new Dictionary<string, bool>();
}
protected void ResetChangeTrackingCollections()
{
_propertyChangedInfo = new Dictionary<string, bool>();
_lastPropertyChangedInfo = new Dictionary<string, bool>();
}
protected void DisableChangeTracking()
{
_changeTrackingEnabled = false;
}
protected void EnableChangeTracking()
{
_changeTrackingEnabled = true;
}
/// <summary>
/// Used by inheritors to set the value of properties, this will detect if the property value actually changed and if it did
/// it will ensure that the property has a dirty flag set.
@@ -172,20 +143,12 @@ namespace Umbraco.Core.Models.EntityBase
{
var initVal = value;
var newVal = setValue(value);
if (!Equals(initVal, newVal))
//don't track changes, just set the value (above)
if (_changeTrackingEnabled == false) return false;
if (Equals(initVal, newVal) == false)
{
OnPropertyChanged(propertySelector);
return true;
}
return false;
}
}
}
+2 -5
View File
@@ -108,15 +108,12 @@ namespace Umbraco.Core.Models
public override object DeepClone()
{
var clone = (File)base.DeepClone();
//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
clone.ResetDirtyProperties(false);
//re-enable tracking
clone.EnableChangeTracking();
return clone;
}
+1 -5
View File
@@ -638,14 +638,10 @@ namespace Umbraco.Core.Models
public override object DeepClone()
{
var clone = (Member)base.DeepClone();
//turn off change tracking
clone.DisableChangeTracking();
//need to manually clone this since it's not settable
clone._contentType = (IMemberType)ContentType.DeepClone();
//this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);
//re-enable tracking
clone.EnableChangeTracking();
return clone;
+2 -5
View File
@@ -448,18 +448,15 @@ namespace Umbraco.Core.Models.Membership
public override object DeepClone()
{
var clone = (User)base.DeepClone();
//turn off change tracking
clone.DisableChangeTracking();
//need to create new collections otherwise they'll get copied by ref
clone._addedSections = new List<string>();
clone._removedSections = new List<string>();
clone._sectionCollection = new ObservableCollection<string>(_sectionCollection.ToList());
//re-create the event handler
clone._sectionCollection.CollectionChanged += clone.SectionCollectionChanged;
//this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);
//re-enable tracking
clone.EnableChangeTracking();
return clone;
}
+1 -5
View File
@@ -151,14 +151,10 @@ namespace Umbraco.Core.Models
public override object DeepClone()
{
var clone = (Property)base.DeepClone();
//turn off change tracking
clone.DisableChangeTracking();
//need to manually assign since this is a readonly property
clone._propertyType = (PropertyType)PropertyType.DeepClone();
//this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);
//re-enable tracking
clone.EnableChangeTracking();
return clone;
}
+2 -5
View File
@@ -432,17 +432,14 @@ namespace Umbraco.Core.Models
public override object DeepClone()
{
var clone = (PropertyType)base.DeepClone();
//turn off change tracking
clone.DisableChangeTracking();
//need to manually assign the Lazy value as it will not be automatically mapped
if (PropertyGroupId != null)
{
clone._propertyGroupId = new Lazy<int>(() => PropertyGroupId.Value);
}
//this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);
//re-enable tracking
clone.EnableChangeTracking();
return clone;
}
+2 -5
View File
@@ -201,15 +201,12 @@ namespace Umbraco.Core.Models
public override object DeepClone()
{
var clone = (Template)base.DeepClone();
//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
clone.ResetDirtyProperties(false);
//re-enable tracking
clone.EnableChangeTracking();
return clone;
}
-23
View File
@@ -287,29 +287,6 @@ namespace Umbraco.Core.Models
}
}
public override object DeepClone()
{
var clone = (UmbracoEntity) base.DeepClone();
//turn off change tracking
clone.DisableChangeTracking();
//This ensures that any value in the dictionary that is deep cloneable is cloned too
foreach (var key in clone.AdditionalData.Keys.ToArray())
{
var deepCloneable = clone.AdditionalData[key] as IDeepCloneable;
if (deepCloneable != null)
{
clone.AdditionalData[key] = deepCloneable.DeepClone();
}
}
//this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);
//re-enable tracking
clone.EnableChangeTracking();
return clone;
}
/// <summary>
/// Some entities may expose additional data that other's might not, this custom data will be available in this collection
/// </summary>
@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Linq;
using System.Text.RegularExpressions;
using Umbraco.Core.Logging;
@@ -20,95 +18,6 @@ namespace Umbraco.Core.Persistence
internal static event CreateTableEventHandler NewTable;
/// <summary>
/// This will handle the issue of inserting data into a table when there can be a violation of a primary key or unique constraint which
/// can occur when two threads are trying to insert data at the exact same time when the data violates this constraint.
/// </summary>
/// <param name="db"></param>
/// <param name="poco"></param>
/// <returns>
/// Returns the action that executed, either an insert or an update
///
/// NOTE: If an insert occurred and a PK value got generated, the poco object passed in will contain the updated value.
/// </returns>
/// <remarks>
/// In different databases, there are a few raw SQL options like MySql's ON DUPLICATE KEY UPDATE or MSSQL's MERGE WHEN MATCHED, but since we are
/// also supporting SQLCE for which this doesn't exist we cannot simply rely on the underlying database to help us here. So we'll actually need to
/// try to be as proficient as possible when we know this can occur and manually handle the issue.
///
/// We do this by first trying to Update the record, this will return the number of rows affected. If it is zero then we insert, if it is one, then
/// we know the update was successful and the row was already inserted by another thread. If the rowcount is zero and we insert and get an exception,
/// that's due to a race condition, in which case we need to retry and update.
/// </remarks>
internal static RecordPersistenceType InsertOrUpdate<T>(this Database db, T poco)
where T : class
{
return db.InsertOrUpdate(poco, null, null);
}
/// <summary>
/// This will handle the issue of inserting data into a table when there can be a violation of a primary key or unique constraint which
/// can occur when two threads are trying to insert data at the exact same time when the data violates this constraint.
/// </summary>
/// <param name="db"></param>
/// <param name="poco"></param>
/// <param name="updateArgs"></param>
/// <param name="updateCommand">If the entity has a composite key they you need to specify the update command explicitly</param>
/// <returns>
/// Returns the action that executed, either an insert or an update
///
/// NOTE: If an insert occurred and a PK value got generated, the poco object passed in will contain the updated value.
/// </returns>
/// <remarks>
/// In different databases, there are a few raw SQL options like MySql's ON DUPLICATE KEY UPDATE or MSSQL's MERGE WHEN MATCHED, but since we are
/// also supporting SQLCE for which this doesn't exist we cannot simply rely on the underlying database to help us here. So we'll actually need to
/// try to be as proficient as possible when we know this can occur and manually handle the issue.
///
/// We do this by first trying to Update the record, this will return the number of rows affected. If it is zero then we insert, if it is one, then
/// we know the update was successful and the row was already inserted by another thread. If the rowcount is zero and we insert and get an exception,
/// that's due to a race condition, in which case we need to retry and update.
/// </remarks>
internal static RecordPersistenceType InsertOrUpdate<T>(this Database db,
T poco,
string updateCommand,
object updateArgs)
where T : class
{
if (poco == null) throw new ArgumentNullException("poco");
var rowCount = updateCommand.IsNullOrWhiteSpace()
? db.Update(poco)
: db.Update<T>(updateCommand, updateArgs);
if (rowCount > 0) return RecordPersistenceType.Update;
try
{
db.Insert(poco);
return RecordPersistenceType.Insert;
}
//TODO: Need to find out if this is the same exception that will occur for all databases... pretty sure it will be
catch (SqlException ex)
{
//This will occur if the constraint was violated and this record was already inserted by another thread,
//at this exact same time, in this case we need to do an update
rowCount = updateCommand.IsNullOrWhiteSpace()
? db.Update(poco)
: db.Update<T>(updateCommand, updateArgs);
if (rowCount == 0)
{
//this would be strange! in this case the only circumstance would be that at the exact same time, 3 threads executed, one
// did the insert and the other somehow managed to do a delete precisely before this update was executed... now that would
// be real crazy. In that case we need to throw an exception.
throw new DataException("Record could not be inserted or updated");
}
return RecordPersistenceType.Update;
}
}
/// <summary>
/// This will escape single @ symbols for peta poco values so it doesn't think it's a parameter
/// </summary>
@@ -1,9 +0,0 @@
namespace Umbraco.Core.Persistence
{
internal enum RecordPersistenceType
{
Insert,
Update,
Delete
}
}
@@ -60,20 +60,13 @@ namespace Umbraco.Core.Persistence.Repositories
{
throw new NotImplementedException();
}
//NOTE: Not implemented because all ContentPreviewEntity will always return false for having an Identity
protected override void PersistUpdatedItem(ContentPreviewEntity<TContent> entity)
{
throw new NotImplementedException();
}
#endregion
protected override void PersistNewItem(ContentPreviewEntity<TContent> entity)
{
if (entity.Content.HasIdentity == false)
{
throw new InvalidOperationException("Cannot insert or update a preview for a content item that has no identity");
throw new InvalidOperationException("Cannot insert a preview for a content item that has no identity");
}
var previewPoco = new PreviewXmlDto
@@ -84,13 +77,33 @@ namespace Umbraco.Core.Persistence.Repositories
Xml = entity.Xml.ToString(SaveOptions.None)
};
//We need to do a special InsertOrUpdate here because we know that the PreviewXmlDto table has a composite key and thus
// a unique constraint which can be violated if 2+ threads try to execute the same insert sql at the same time.
Database.InsertOrUpdate(previewPoco,
//Since the table has a composite key, we need to specify an explit update statement
"SET xml = @Xml, timestamp = @Timestamp WHERE nodeId=@NodeId AND versionId=@VersionId",
new {NodeId = previewPoco.NodeId, VersionId = previewPoco.VersionId, Xml = previewPoco.Xml, Timestamp = previewPoco.Timestamp});
Database.Insert(previewPoco);
}
protected override void PersistUpdatedItem(ContentPreviewEntity<TContent> entity)
{
if (entity.Content.HasIdentity == false)
{
throw new InvalidOperationException("Cannot update a preview for a content item that has no identity");
}
var previewPoco = new PreviewXmlDto
{
NodeId = entity.Id,
Timestamp = DateTime.Now,
VersionId = entity.Version,
Xml = entity.Xml.ToString(SaveOptions.None)
};
Database.Update<PreviewXmlDto>(
"SET xml = @Xml, timestamp = @Timestamp WHERE nodeId = @Id AND versionId = @Version",
new
{
Xml = previewPoco.Xml,
Timestamp = previewPoco.Timestamp,
Id = previewPoco.NodeId,
Version = previewPoco.VersionId
});
}
}
}
@@ -575,8 +575,10 @@ namespace Umbraco.Core.Persistence.Repositories
/// <param name="content"></param>
/// <param name="xml"></param>
public void AddOrUpdateContentXml(IContent content, Func<IContent, XElement> xml)
{
_contentXmlRepository.AddOrUpdate(new ContentXmlEntity<IContent>(content, xml));
{
var contentExists = Database.ExecuteScalar<int>("SELECT COUNT(nodeId) FROM cmsContentXml WHERE nodeId = @Id", new { Id = content.Id }) != 0;
_contentXmlRepository.AddOrUpdate(new ContentXmlEntity<IContent>(contentExists, content, xml));
}
/// <summary>
@@ -595,7 +597,11 @@ namespace Umbraco.Core.Persistence.Repositories
/// <param name="xml"></param>
public void AddOrUpdatePreviewXml(IContent content, Func<IContent, XElement> xml)
{
_contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity<IContent>(content, xml));
var previewExists =
Database.ExecuteScalar<int>("SELECT COUNT(nodeId) FROM cmsPreviewXml WHERE nodeId = @Id AND versionId = @Version",
new { Id = content.Id, Version = content.Version }) != 0;
_contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity<IContent>(previewExists, content, xml));
}
#endregion
@@ -55,12 +55,6 @@ namespace Umbraco.Core.Persistence.Repositories
{
get { throw new NotImplementedException(); }
}
//NOTE: Not implemented because all ContentXmlEntity will always return false for having an Identity
protected override void PersistUpdatedItem(ContentXmlEntity<TContent> entity)
{
throw new NotImplementedException();
}
#endregion
@@ -74,21 +68,22 @@ namespace Umbraco.Core.Persistence.Repositories
{
if (entity.Content.HasIdentity == false)
{
throw new InvalidOperationException("Cannot insert or update an xml entry for a content item that has no identity");
throw new InvalidOperationException("Cannot insert an xml entry for a content item that has no identity");
}
var poco = new ContentXmlDto
{
NodeId = entity.Id,
Xml = entity.Xml.ToString(SaveOptions.None)
};
//We need to do a special InsertOrUpdate here because we know that the ContentXmlDto table has a 1:1 relation
// with the content table and a record may or may not exist so the
// unique constraint which can be violated if 2+ threads try to execute the same insert sql at the same time.
Database.InsertOrUpdate(poco);
var poco = new ContentXmlDto { NodeId = entity.Id, Xml = entity.Xml.ToString(SaveOptions.None) };
Database.Insert(poco);
}
protected override void PersistUpdatedItem(ContentXmlEntity<TContent> entity)
{
if (entity.Content.HasIdentity == false)
{
throw new InvalidOperationException("Cannot update an xml entry for a content item that has no identity");
}
var poco = new ContentXmlDto { NodeId = entity.Id, Xml = entity.Xml.ToString(SaveOptions.None) };
Database.Update(poco);
}
}
}
@@ -181,12 +181,18 @@ namespace Umbraco.Core.Persistence.Repositories
public void AddOrUpdateContentXml(IMedia content, Func<IMedia, XElement> xml)
{
_contentXmlRepository.AddOrUpdate(new ContentXmlEntity<IMedia>(content, xml));
var contentExists = Database.ExecuteScalar<int>("SELECT COUNT(nodeId) FROM cmsContentXml WHERE nodeId = @Id", new { Id = content.Id }) != 0;
_contentXmlRepository.AddOrUpdate(new ContentXmlEntity<IMedia>(contentExists, content, xml));
}
public void AddOrUpdatePreviewXml(IMedia content, Func<IMedia, XElement> xml)
{
_contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity<IMedia>(content, xml));
var previewExists =
Database.ExecuteScalar<int>("SELECT COUNT(nodeId) FROM cmsPreviewXml WHERE nodeId = @Id AND versionId = @Version",
new { Id = content.Id, Version = content.Version }) != 0;
_contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity<IMedia>(previewExists, content, xml));
}
protected override void PerformDeleteVersion(int id, Guid versionId)
@@ -609,12 +609,18 @@ namespace Umbraco.Core.Persistence.Repositories
public void AddOrUpdateContentXml(IMember content, Func<IMember, XElement> xml)
{
_contentXmlRepository.AddOrUpdate(new ContentXmlEntity<IMember>(content, xml));
var contentExists = Database.ExecuteScalar<int>("SELECT COUNT(nodeId) FROM cmsContentXml WHERE nodeId = @Id", new { Id = content.Id }) != 0;
_contentXmlRepository.AddOrUpdate(new ContentXmlEntity<IMember>(contentExists, content, xml));
}
public void AddOrUpdatePreviewXml(IMember content, Func<IMember, XElement> xml)
{
_contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity<IMember>(content, xml));
var previewExists =
Database.ExecuteScalar<int>("SELECT COUNT(nodeId) FROM cmsPreviewXml WHERE nodeId = @Id AND versionId = @Version",
new { Id = content.Id, Version = content.Version }) != 0;
_contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity<IMember>(previewExists, content, xml));
}
private IMember BuildFromDto(List<MemberReadOnlyDto> dtos)
@@ -17,17 +17,15 @@ namespace Umbraco.Core.Sync
/// status. This will attempt to determine the internal umbraco base url that can be used by the current
/// server to send a request to itself if it is in a load balanced environment.
/// </summary>
/// <returns>The full base url including schema (i.e. http://myserver:80/umbraco ) - or <c>null</c> if the url
/// cannot be determined at the moment (usually because the first request has not properly completed yet).</returns>
public static string GetCurrentServerUmbracoBaseUrl(ApplicationContext appContext)
/// <returns>The full base url including schema (i.e. http://myserver:80/umbraco )</returns>
public static string GetCurrentServerUmbracoBaseUrl()
{
var status = GetStatus();
if (status == CurrentServerEnvironmentStatus.Single)
{
// single install, return null if no config/original url, else use config/original url as base
// use http or https as appropriate
return GetBaseUrl(appContext);
//if it's a single install, then the base url has to be the first url registered
return string.Format("http://{0}", ApplicationContext.Current.OriginalRequestUrl);
}
var servers = UmbracoSettings.DistributionServers;
@@ -35,9 +33,8 @@ namespace Umbraco.Core.Sync
var nodes = servers.SelectNodes("./server");
if (nodes == null)
{
// cannot be determined, return null if no config/original url, else use config/original url as base
// use http or https as appropriate
return GetBaseUrl(appContext);
//cannot be determined, then the base url has to be the first url registered
return string.Format("http://{0}", ApplicationContext.Current.OriginalRequestUrl);
}
var xmlNodes = nodes.Cast<XmlNode>().ToList();
@@ -61,12 +58,11 @@ namespace Umbraco.Core.Sync
xmlNode.InnerText,
xmlNode.AttributeValue<string>("forcePortnumber").IsNullOrWhiteSpace() ? "80" : xmlNode.AttributeValue<string>("forcePortnumber"),
IOHelper.ResolveUrl(SystemDirectories.Umbraco).TrimStart('/'));
}
}
}
// cannot be determined, return null if no config/original url, else use config/original url as base
// use http or https as appropriate
return GetBaseUrl(appContext);
//cannot be determined, then the base url has to be the first url registered
return string.Format("http://{0}", ApplicationContext.Current.OriginalRequestUrl);
}
/// <summary>
@@ -89,7 +85,7 @@ namespace Umbraco.Core.Sync
}
var master = nodes.Cast<XmlNode>().FirstOrDefault();
if (master == null)
{
return CurrentServerEnvironmentStatus.Unknown;
@@ -108,29 +104,13 @@ namespace Umbraco.Core.Sync
}
if ((appId.IsNullOrWhiteSpace() == false && appId.Trim().InvariantEquals(HttpRuntime.AppDomainAppId))
|| (serverName.IsNullOrWhiteSpace() == false && serverName.Trim().InvariantEquals(NetworkHelper.MachineName)))
|| (serverName.IsNullOrWhiteSpace() == false && serverName.Trim().InvariantEquals(NetworkHelper.MachineName)))
{
//match by appdid or server name!
return CurrentServerEnvironmentStatus.Master;
return CurrentServerEnvironmentStatus.Master;
}
return CurrentServerEnvironmentStatus.Slave;
}
private static string GetBaseUrl(ApplicationContext appContext)
{
return (
// is config empty?
UmbracoSettings.ScheduledTasksBaseUrl.IsNullOrWhiteSpace()
// is the orig req empty?
? appContext.OriginalRequestUrl.IsNullOrWhiteSpace()
// we've got nothing
? null
//the orig req url is not null, use that
: string.Format("http{0}://{1}", GlobalSettings.UseSSL ? "s" : "", appContext.OriginalRequestUrl)
// the config has been specified, use that
: string.Format("http{0}://{1}", GlobalSettings.UseSSL ? "s" : "", UmbracoSettings.ScheduledTasksBaseUrl))
.EnsureEndsWith('/');
}
}
}
-1
View File
@@ -178,7 +178,6 @@
<Compile Include="Events\SendToPublishEventArgs.cs" />
<Compile Include="IApplicationEventHandler.cs" />
<Compile Include="IDisposeOnRequestEnd.cs" />
<Compile Include="Persistence\RecordPersistenceType.cs" />
<Compile Include="IO\ResizedImage.cs" />
<Compile Include="IO\UmbracoMediaFile.cs" />
<Compile Include="Media\MediaSubfolderCounter.cs" />
@@ -21,7 +21,6 @@ namespace Umbraco.Tests.BusinessLogic
/// Creates a new app tree linked to an application, then delete the application and make sure the tree is gone as well
///</summary>
[Test()]
[Ignore]
public void ApplicationTree_Make_New_Then_Delete_App()
{
//create new app
@@ -33,7 +32,7 @@ namespace Umbraco.Tests.BusinessLogic
Assert.IsNotNull(app);
//create the new app tree assigned to the new app
ApplicationTree.MakeNew(false, false, 0, app.alias, name, name, "icon.jpg", "icon.jpg", "", "umbraco.loadContent, umbraco", string.Empty);
ApplicationTree.MakeNew(false, false, 0, app.alias, name, name, "icon.jpg", "icon.jpg", "nullassembly", "nulltype", string.Empty);
var tree = ApplicationTree.getByAlias(name);
Assert.IsNotNull(tree);
@@ -194,7 +194,6 @@ namespace Umbraco.Tests.CodeFirst
}
[Test]
[Ignore("fails, and we don't want to know why")]
public void Can_Resolve_Full_List_Of_Models_Implementing_ContentTypeBase()
{
ContentTypeDefinitionFactory.ClearContentTypeCache();
@@ -1,200 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Web.Scheduling;
namespace Umbraco.Tests.Scheduling
{
[TestFixture]
public class BackgroundTaskRunnerTests
{
[Test]
public void Startup_And_Shutdown()
{
BackgroundTaskRunner<IBackgroundTask> tManager;
using (tManager = new BackgroundTaskRunner<IBackgroundTask>(true, true))
{
tManager.StartUp();
}
NUnit.Framework.Assert.IsFalse(tManager.IsRunning);
}
[Test]
public void Startup_Starts_Automatically()
{
BackgroundTaskRunner<BaseTask> tManager;
using (tManager = new BackgroundTaskRunner<BaseTask>(true, true))
{
tManager.Add(new MyTask());
NUnit.Framework.Assert.IsTrue(tManager.IsRunning);
}
}
[Test]
public void Task_Runs()
{
var myTask = new MyTask();
var waitHandle = new ManualResetEvent(false);
BackgroundTaskRunner<BaseTask> tManager;
using (tManager = new BackgroundTaskRunner<BaseTask>(true, true))
{
tManager.TaskCompleted += (sender, task) => waitHandle.Set();
tManager.Add(myTask);
//wait for ITasks to complete
waitHandle.WaitOne();
NUnit.Framework.Assert.IsTrue(myTask.Ended != default(DateTime));
}
}
[Test]
public void Many_Tasks_Run()
{
var tasks = new Dictionary<BaseTask, ManualResetEvent>();
for (var i = 0; i < 10; i++)
{
tasks.Add(new MyTask(), new ManualResetEvent(false));
}
BackgroundTaskRunner<BaseTask> tManager;
using (tManager = new BackgroundTaskRunner<BaseTask>(true, true))
{
tManager.TaskCompleted += (sender, task) => tasks[task.Task].Set();
tasks.ForEach(t => tManager.Add(t.Key));
//wait for all ITasks to complete
WaitHandle.WaitAll(tasks.Values.Select(x => (WaitHandle)x).ToArray());
foreach (var task in tasks)
{
NUnit.Framework.Assert.IsTrue(task.Key.Ended != default(DateTime));
}
}
}
[Test]
public void Tasks_Can_Keep_Being_Added_And_Will_Execute()
{
Func<Dictionary<BaseTask, ManualResetEvent>> getTasks = () =>
{
var newTasks = new Dictionary<BaseTask, ManualResetEvent>();
for (var i = 0; i < 10; i++)
{
newTasks.Add(new MyTask(), new ManualResetEvent(false));
}
return newTasks;
};
IDictionary<BaseTask, ManualResetEvent> tasks = getTasks();
BackgroundTaskRunner<BaseTask> tManager;
using (tManager = new BackgroundTaskRunner<BaseTask>(true, true))
{
tManager.TaskCompleted += (sender, task) => tasks[task.Task].Set();
//execute first batch
tasks.ForEach(t => tManager.Add(t.Key));
//wait for all ITasks to complete
WaitHandle.WaitAll(tasks.Values.Select(x => (WaitHandle)x).ToArray());
foreach (var task in tasks)
{
NUnit.Framework.Assert.IsTrue(task.Key.Ended != default(DateTime));
}
//execute another batch after a bit
Thread.Sleep(2000);
tasks = getTasks();
tasks.ForEach(t => tManager.Add(t.Key));
//wait for all ITasks to complete
WaitHandle.WaitAll(tasks.Values.Select(x => (WaitHandle)x).ToArray());
foreach (var task in tasks)
{
NUnit.Framework.Assert.IsTrue(task.Key.Ended != default(DateTime));
}
}
}
[Test]
public void Task_Queue_Will_Be_Completed_Before_Shutdown()
{
var tasks = new Dictionary<BaseTask, ManualResetEvent>();
for (var i = 0; i < 10; i++)
{
tasks.Add(new MyTask(), new ManualResetEvent(false));
}
BackgroundTaskRunner<BaseTask> tManager;
using (tManager = new BackgroundTaskRunner<BaseTask>(true, true))
{
tManager.TaskCompleted += (sender, task) => tasks[task.Task].Set();
tasks.ForEach(t => tManager.Add(t.Key));
////wait for all ITasks to complete
//WaitHandle.WaitAll(tasks.Values.Select(x => (WaitHandle)x).ToArray());
tManager.Stop(false);
//immediate stop will block until complete - but since we are running on
// a single thread this doesn't really matter as the above will just process
// until complete.
tManager.Stop(true);
NUnit.Framework.Assert.AreEqual(0, tManager.TaskCount);
}
}
private class MyTask : BaseTask
{
public MyTask()
{
}
public override void Run()
{
Thread.Sleep(500);
}
public override void Cancel()
{
}
}
public abstract class BaseTask : IBackgroundTask
{
public Guid UniqueId { get; protected set; }
public abstract void Run();
public abstract void Cancel();
public DateTime Queued { get; set; }
public DateTime Started { get; set; }
public DateTime Ended { get; set; }
public virtual void Dispose()
{
Ended = DateTime.Now;
}
}
}
}
-1
View File
@@ -249,7 +249,6 @@
<Compile Include="PublishedContent\StronglyTypedModels\Textpage.cs" />
<Compile Include="PublishedContent\StronglyTypedModels\TypedModelBase.cs" />
<Compile Include="PublishedContent\StronglyTypedModels\UmbracoTemplatePage`T.cs" />
<Compile Include="Scheduling\BackgroundTaskRunnerTests.cs" />
<Compile Include="Services\LocalizationServiceTests.cs" />
<Compile Include="Services\MemberServiceTests.cs" />
<Compile Include="Services\MediaServiceTests.cs" />
+1 -1
View File
@@ -1 +1 @@
<%@ Application Inherits="Umbraco.Web.UmbracoApplication" Language="C#" %>
<%@ Application Codebehind="Global.asax.cs" Inherits="Umbraco.Web.UmbracoApplication" Language="C#" %>
+6 -13
View File
@@ -101,9 +101,9 @@
<Project>{07fbc26b-2927-4a22-8d96-d644c667fecc}</Project>
<Name>UmbracoExamine</Name>
</ProjectReference>
<Reference Include="ClientDependency.Core, Version=1.8.2.1, Culture=neutral, processorArchitecture=MSIL">
<Reference Include="ClientDependency.Core">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\ClientDependency.1.8.2.1\lib\net40\ClientDependency.Core.dll</HintPath>
<HintPath>..\packages\ClientDependency.1.7.1.2\lib\ClientDependency.Core.dll</HintPath>
</Reference>
<Reference Include="ClientDependency.Core.Mvc">
<SpecificVersion>False</SpecificVersion>
@@ -172,15 +172,12 @@
<Reference Include="System.EnterpriseServices" />
<Reference Include="System.Net.Http, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Net.Http.Formatting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.WebApi.Client.4.0.30506.0\lib\net40\System.Net.Http.Formatting.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Net.Http.WebRequest, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web">
<Name>System.Web</Name>
@@ -199,11 +196,9 @@
</Reference>
<Reference Include="System.Web.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.WebApi.Core.4.0.30506.0\lib\net40\System.Web.Http.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.Http.WebHost, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.WebApi.WebHost.4.0.30506.0\lib\net40\System.Web.Http.WebHost.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Private>True</Private>
@@ -2662,10 +2657,8 @@
<Folder Include="Views\Partials\" />
</ItemGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">11.0</VisualStudioVersion>
<VSToolsPath Condition="exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0\WebApplications\Microsoft.WebApplication.targets')">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0</VSToolsPath>
<VSToolsPath Condition="exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0\WebApplications\Microsoft.WebApplication.targets')">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0</VSToolsPath>
<VSToolsPath Condition="exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0\WebApplications\Microsoft.WebApplication.targets')">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0</VSToolsPath>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
@@ -2682,9 +2675,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\x86\*.* "$(TargetDir)x86\"
<WebProjectProperties>
<UseIIS>True</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>6260</DevelopmentServerPort>
<DevelopmentServerPort>6240</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:6260</IISUrl>
<IISUrl>http://localhost:6240</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>False</UseCustomServer>
<CustomServerUrl>
@@ -8,7 +8,7 @@
<h1>Create User</h1>
<div class="create-hold">
<p>You can now setup a new admin user to log into Umbraco, we recommend using a strong password for this (a password which is more than 4 characters and contains a mix of letters, numbers and symbols).
<p>You can now setup a new admin user to log into Umbraco, we recommend using a stong password for this (a password which is more than 4 characters and contains a mix of letters, numbers and symbols).
Please make a note of the chosen password.</p>
<p>The password can be changed once you have completed the installation and logged into the admin interface.</p>
</div>
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ClientDependency" version="1.8.2.1" targetFramework="net40" />
<package id="ClientDependency" version="1.7.1.2" targetFramework="net40" />
<package id="ClientDependency-Mvc" version="1.7.0.4" targetFramework="net40" />
<package id="Examine" version="0.1.57.2941" targetFramework="net40" />
<package id="log4net-mediumtrust" version="2.0.0" targetFramework="net40" />
+1 -8
View File
@@ -46,7 +46,7 @@
<compilation debug="true" xdt:Transform="SetAttributes(debug)" />
<trust xdt:Transform="Remove" />
<!--<trust level="Medium" originUrl=".*" xdt:Transform="Insert" />-->
<trust level="Medium" originUrl=".*" xdt:Transform="Insert" />
</system.web>
<runtime>
@@ -66,13 +66,6 @@
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Remove"
xdt:Locator="Condition(_defaultNamespace:assemblyIdentity[@name='System.Net.Http']])"/>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="System.Net.Http" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0"/>
</dependentAssembly>
<dependentAssembly xdt:Transform="Remove"
xdt:Locator="Condition(_defaultNamespace:assemblyIdentity[@name='System.Web.WebPages']])"/>
@@ -58,7 +58,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
var iscanon = !UnitTesting && !DomainHelper.ExistsDomainInPath(DomainHelper.GetAllDomains(false), content.Path, domainRootNodeId);
// and only if this is the canonical url (the one GetUrl would return)
if (iscanon)
_routesCache.Store(content.Id, route);
_routesCache.Store(contentId, route);
}
return content;
@@ -471,4 +471,4 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
#endregion
}
}
}
@@ -191,10 +191,6 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
}
}
LogHelper.Debug<PublishedMediaCache>(
"Could not retrieve media {0} from Examine index, reverting to looking up media via legacy library.GetMedia method",
() => id);
var media = global::umbraco.library.GetMedia(id, true);
if (media != null && media.Current != null)
{
@@ -216,10 +212,6 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
return ConvertFromXPathNavigator(media.Current);
}
LogHelper.Debug<PublishedMediaCache>(
"Could not retrieve media {0} from Examine index or from legacy library.GetMedia method",
() => id);
return null;
}
@@ -1,363 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Hosting;
using Umbraco.Core.Logging;
namespace Umbraco.Web.Scheduling
{
/// <summary>
/// This is used to create a background task runner which will stay alive in the background of and complete
/// any tasks that are queued. It is web aware and will ensure that it is shutdown correctly when the app domain
/// is shutdown.
/// </summary>
/// <typeparam name="T"></typeparam>
internal class BackgroundTaskRunner<T> : IDisposable, IRegisteredObject
where T : IBackgroundTask
{
private readonly bool _dedicatedThread;
private readonly bool _persistentThread;
private readonly BlockingCollection<T> _tasks = new BlockingCollection<T>();
private Task _consumer;
private volatile bool _isRunning = false;
private static readonly object Locker = new object();
private CancellationTokenSource _tokenSource;
internal event EventHandler<TaskEventArgs<T>> TaskError;
internal event EventHandler<TaskEventArgs<T>> TaskStarting;
internal event EventHandler<TaskEventArgs<T>> TaskCompleted;
internal event EventHandler<TaskEventArgs<T>> TaskCancelled;
public BackgroundTaskRunner(bool dedicatedThread = false, bool persistentThread = false)
{
_dedicatedThread = dedicatedThread;
_persistentThread = persistentThread;
HostingEnvironment.RegisterObject(this);
}
public int TaskCount
{
get { return _tasks.Count; }
}
public bool IsRunning
{
get { return _isRunning; }
}
public TaskStatus TaskStatus
{
get { return _consumer.Status; }
}
public void Add(T task)
{
//add any tasks first
LogHelper.Debug<BackgroundTaskRunner<T>>(" Task added {0}", () => task.GetType());
_tasks.Add(task);
//ensure's everything is started
StartUp();
}
public void StartUp()
{
if (!_isRunning)
{
lock (Locker)
{
//double check
if (!_isRunning)
{
_isRunning = true;
//Create a new token source since this is a new proces
_tokenSource = new CancellationTokenSource();
StartConsumer();
LogHelper.Debug<BackgroundTaskRunner<T>>("Starting");
}
}
}
}
public void ShutDown()
{
lock (Locker)
{
_isRunning = false;
try
{
if (_consumer != null)
{
//cancel all operations
_tokenSource.Cancel();
try
{
_consumer.Wait();
}
catch (AggregateException e)
{
//NOTE: We are logging Debug because we are expecting these errors
LogHelper.Debug<BackgroundTaskRunner<T>>("AggregateException thrown with the following inner exceptions:");
// Display information about each exception.
foreach (var v in e.InnerExceptions)
{
var exception = v as TaskCanceledException;
if (exception != null)
{
LogHelper.Debug<BackgroundTaskRunner<T>>(" .Net TaskCanceledException: .Net Task ID {0}", () => exception.Task.Id);
}
else
{
LogHelper.Debug<BackgroundTaskRunner<T>>(" Exception: {0}", () => v.GetType().Name);
}
}
}
}
if (_tasks.Count > 0)
{
LogHelper.Debug<BackgroundTaskRunner<T>>("Processing remaining tasks before shutdown: {0}", () => _tasks.Count);
//now we need to ensure the remaining queue is processed if there's any remaining,
// this will all be processed on the current/main thread.
T remainingTask;
while (_tasks.TryTake(out remainingTask))
{
ConsumeTaskInternal(remainingTask);
}
}
LogHelper.Debug<BackgroundTaskRunner<T>>("Shutdown");
//disposing these is really optional since they'll be disposed immediately since they are no longer running
//but we'll put this here anyways.
if (_consumer != null && (_consumer.IsCompleted || _consumer.IsCanceled))
{
_consumer.Dispose();
}
}
catch (Exception ex)
{
LogHelper.Error<BackgroundTaskRunner<T>>("Error occurred shutting down task runner", ex);
}
finally
{
HostingEnvironment.UnregisterObject(this);
}
}
}
/// <summary>
/// Starts the consumer task
/// </summary>
private void StartConsumer()
{
var token = _tokenSource.Token;
_consumer = Task.Factory.StartNew(() =>
StartThread(token),
token,
_dedicatedThread ? TaskCreationOptions.LongRunning : TaskCreationOptions.None,
TaskScheduler.Default);
//if this is not a persistent thread, wait till it's done and shut ourselves down
// thus ending the thread or giving back to the thread pool. If another task is added
// another thread will spawn or be taken from the pool to process.
if (!_persistentThread)
{
_consumer.ContinueWith(task => ShutDown());
}
}
/// <summary>
/// Invokes a new worker thread to consume tasks
/// </summary>
/// <param name="token"></param>
private void StartThread(CancellationToken token)
{
// Was cancellation already requested?
if (token.IsCancellationRequested)
{
LogHelper.Info<BackgroundTaskRunner<T>>("Thread {0} was cancelled before it got started.", () => Thread.CurrentThread.ManagedThreadId);
token.ThrowIfCancellationRequested();
}
TakeAndConsumeTask(token);
}
/// <summary>
/// Trys to get a task from the queue, if there isn't one it will wait a second and try again
/// </summary>
/// <param name="token"></param>
private void TakeAndConsumeTask(CancellationToken token)
{
if (token.IsCancellationRequested)
{
LogHelper.Info<BackgroundTaskRunner<T>>("Thread {0} was cancelled.", () => Thread.CurrentThread.ManagedThreadId);
token.ThrowIfCancellationRequested();
}
//If this is true, the thread will stay alive and just wait until there is anything in the queue
// and process it. When there is nothing in the queue, the thread will just block until there is
// something to process.
//When this is false, the thread will process what is currently in the queue and once that is
// done, the thread will end and we will shutdown the process
if (_persistentThread)
{
//This will iterate over the collection, if there is nothing to take
// the thread will block until there is something available.
//We need to pass our cancellation token so that the thread will
// cancel when we shutdown
foreach (var t in _tasks.GetConsumingEnumerable(token))
{
ConsumeTaskCancellable(t, token);
}
//recurse and keep going
TakeAndConsumeTask(token);
}
else
{
T repositoryTask;
while (_tasks.TryTake(out repositoryTask))
{
ConsumeTaskCancellable(repositoryTask, token);
}
//the task will end here
}
}
internal void ConsumeTaskCancellable(T task, CancellationToken token)
{
if (token.IsCancellationRequested)
{
OnTaskCancelled(new TaskEventArgs<T>(task));
//NOTE: Since the task hasn't started this is pretty pointless so leaving it out.
LogHelper.Info<BackgroundTaskRunner<T>>("Task {0}) was cancelled.",
() => task.GetType());
token.ThrowIfCancellationRequested();
}
ConsumeTaskInternal(task);
}
private void ConsumeTaskInternal(T task)
{
try
{
OnTaskStarting(new TaskEventArgs<T>(task));
try
{
using (task)
{
task.Run();
}
}
catch (Exception e)
{
OnTaskError(new TaskEventArgs<T>(task, e));
throw;
}
OnTaskCompleted(new TaskEventArgs<T>(task));
}
catch (Exception ex)
{
LogHelper.Error<BackgroundTaskRunner<T>>("An error occurred consuming task", ex);
}
}
protected virtual void OnTaskError(TaskEventArgs<T> e)
{
var handler = TaskError;
if (handler != null) handler(this, e);
}
protected virtual void OnTaskStarting(TaskEventArgs<T> e)
{
var handler = TaskStarting;
if (handler != null) handler(this, e);
}
protected virtual void OnTaskCompleted(TaskEventArgs<T> e)
{
var handler = TaskCompleted;
if (handler != null) handler(this, e);
}
protected virtual void OnTaskCancelled(TaskEventArgs<T> e)
{
var handler = TaskCancelled;
if (handler != null) handler(this, e);
}
#region Disposal
private readonly object _disposalLocker = new object();
public bool IsDisposed { get; private set; }
~BackgroundTaskRunner()
{
this.Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (this.IsDisposed || !disposing)
return;
lock (_disposalLocker)
{
if (IsDisposed)
return;
DisposeResources();
IsDisposed = true;
}
}
protected virtual void DisposeResources()
{
ShutDown();
}
#endregion
public void Stop(bool immediate)
{
if (immediate == false)
{
LogHelper.Debug<BackgroundTaskRunner<T>>("Application is shutting down, waiting for tasks to complete");
Dispose();
}
else
{
//NOTE: this will thread block the current operation if the manager
// is still shutting down because the Shutdown operation is also locked
// by this same lock instance. This would only matter if Stop is called by ASP.Net
// on two different threads though, otherwise the current thread will just block normally
// until the app is shutdown
lock (Locker)
{
LogHelper.Info<BackgroundTaskRunner<T>>("Application is shutting down immediately");
}
}
}
}
}
@@ -1,9 +0,0 @@
using System;
namespace Umbraco.Web.Scheduling
{
internal interface IBackgroundTask : IDisposable
{
void Run();
}
}
+11 -21
View File
@@ -1,7 +1,6 @@
using System;
using System.Net;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Sync;
@@ -9,33 +8,24 @@ namespace Umbraco.Web.Scheduling
{
internal class KeepAlive
{
public static void Start(ApplicationContext appContext)
public static void Start(object sender)
{
using (DisposableTimer.DebugDuration<KeepAlive>(() => "Keep alive executing", () => "Keep alive complete"))
{
var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl(appContext);
var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl();
if (string.IsNullOrWhiteSpace(umbracoBaseUrl))
var url = string.Format("{0}/ping.aspx", umbracoBaseUrl);
try
{
LogHelper.Warn<KeepAlive>("No url for service (yet), skip.");
using (var wc = new WebClient())
{
wc.DownloadString(url);
}
}
else
catch (Exception ee)
{
var url = string.Format("{0}ping.aspx", umbracoBaseUrl.EnsureEndsWith('/'));
try
{
using (var wc = new WebClient())
{
wc.DownloadString(url);
}
}
catch (Exception ee)
{
LogHelper.Error<KeepAlive>(
string.Format("Error in ping. The base url used in the request was: {0}, see http://our.umbraco.org/documentation/Using-Umbraco/Config-files/umbracoSettings/#ScheduledTasks documentation for details on setting a baseUrl if this is in error", umbracoBaseUrl)
, ee);
}
LogHelper.Error<KeepAlive>("Error in ping", ee);
}
}
+37 -12
View File
@@ -2,20 +2,38 @@ using System;
using System.Web;
using System.Web.Caching;
using umbraco.BusinessLogic;
using Umbraco.Core;
using Umbraco.Core.Logging;
namespace Umbraco.Web.Scheduling
{
//TODO: Refactor this to use a normal scheduling processor!
internal class LogScrubber : DisposableObject, IBackgroundTask
internal class LogScrubber
{
// this is a raw copy of the legacy code in all its uglyness
private readonly ApplicationContext _appContext;
public LogScrubber(ApplicationContext appContext)
CacheItemRemovedCallback _onCacheRemove;
const string LogScrubberTaskName = "ScrubLogs";
public void Start()
{
_appContext = appContext;
// log scrubbing
AddTask(LogScrubberTaskName, GetLogScrubbingInterval());
}
private static int GetLogScrubbingInterval()
{
int interval = 24 * 60 * 60; //24 hours
try
{
if (global::umbraco.UmbracoSettings.CleaningMiliseconds > -1)
interval = global::umbraco.UmbracoSettings.CleaningMiliseconds;
}
catch (Exception e)
{
LogHelper.Error<Scheduler>("Unable to locate a log scrubbing interval. Defaulting to 24 horus", e);
}
return interval;
}
private static int GetLogScrubbingMaximumAge()
@@ -34,19 +52,26 @@ namespace Umbraco.Web.Scheduling
}
/// <summary>
/// Handles the disposal of resources. Derived from abstract class <see cref="DisposableObject"/> which handles common required locking logic.
/// </summary>
protected override void DisposeResources()
private void AddTask(string name, int seconds)
{
_onCacheRemove = new CacheItemRemovedCallback(CacheItemRemoved);
HttpRuntime.Cache.Insert(name, seconds, null,
DateTime.Now.AddSeconds(seconds), System.Web.Caching.Cache.NoSlidingExpiration,
CacheItemPriority.NotRemovable, _onCacheRemove);
}
public void Run()
public void CacheItemRemoved(string k, object v, CacheItemRemovedReason r)
{
using (DisposableTimer.DebugDuration<LogScrubber>(() => "Log scrubbing executing", () => "Log scrubbing complete"))
if (k.Equals(LogScrubberTaskName))
{
Log.CleanLogs(GetLogScrubbingMaximumAge());
ScrubLogs();
}
AddTask(k, Convert.ToInt32(v));
}
private static void ScrubLogs()
{
Log.CleanLogs(GetLogScrubbingMaximumAge());
}
}
}
@@ -10,60 +10,35 @@ using Umbraco.Web.Mvc;
namespace Umbraco.Web.Scheduling
{
internal class ScheduledPublishing : DisposableObject, IBackgroundTask
internal class ScheduledPublishing
{
private readonly ApplicationContext _appContext;
private static bool _isPublishingRunning = false;
public ScheduledPublishing(ApplicationContext appContext)
public void Start(ApplicationContext appContext)
{
_appContext = appContext;
}
/// <summary>
/// Handles the disposal of resources. Derived from abstract class <see cref="DisposableObject"/> which handles common required locking logic.
/// </summary>
protected override void DisposeResources()
{
}
public void Run()
{
if (_appContext == null) return;
if (appContext == null) return;
using (DisposableTimer.DebugDuration<ScheduledPublishing>(() => "Scheduled publishing executing", () => "Scheduled publishing complete"))
{
{
if (_isPublishingRunning) return;
_isPublishingRunning = true;
var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl(_appContext);
try
{
if (string.IsNullOrWhiteSpace(umbracoBaseUrl))
var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl();
var url = string.Format("{0}/RestServices/ScheduledPublish/Index", umbracoBaseUrl);
using (var wc = new WebClient())
{
LogHelper.Warn<ScheduledPublishing>("No url for service (yet), skip.");
}
else
{
var url = string.Format("{0}RestServices/ScheduledPublish/Index", umbracoBaseUrl.EnsureEndsWith('/'));
using (var wc = new WebClient())
{
//pass custom the authorization header
wc.Headers.Set("Authorization", AdminTokenAuthorizeAttribute.GetAuthHeaderTokenVal(_appContext));
//pass custom the authorization header
wc.Headers.Set("Authorization", AdminTokenAuthorizeAttribute.GetAuthHeaderTokenVal(appContext));
var result = wc.UploadString(url, "");
}
var result = wc.UploadString(url, "");
}
}
catch (Exception ee)
{
LogHelper.Error<ScheduledPublishing>(
string.Format("An error occurred with the scheduled publishing. The base url used in the request was: {0}, see http://our.umbraco.org/documentation/Using-Umbraco/Config-files/umbracoSettings/#ScheduledTasks documentation for details on setting a baseUrl if this is in error", umbracoBaseUrl)
, ee);
LogHelper.Error<ScheduledPublishing>("An error occurred with the scheduled publishing", ee);
}
finally
{
@@ -71,5 +46,7 @@ namespace Umbraco.Web.Scheduling
}
}
}
}
}
+27 -37
View File
@@ -14,19 +14,39 @@ namespace Umbraco.Web.Scheduling
// would need to be a publicly available task (URL) which isn't really very good :(
// We should really be using the AdminTokenAuthorizeAttribute for this stuff
internal class ScheduledTasks : DisposableObject, IBackgroundTask
internal class ScheduledTasks
{
private readonly ApplicationContext _appContext;
private static readonly Hashtable ScheduledTaskTimes = new Hashtable();
private static bool _isPublishingRunning = false;
public ScheduledTasks(ApplicationContext appContext)
public void Start(ApplicationContext appContext)
{
_appContext = appContext;
using (DisposableTimer.DebugDuration<ScheduledTasks>(() => "Scheduled tasks executing", () => "Scheduled tasks complete"))
{
if (_isPublishingRunning) return;
_isPublishingRunning = true;
try
{
ProcessTasks();
}
catch (Exception ee)
{
LogHelper.Error<ScheduledTasks>("Error executing scheduled task", ee);
}
finally
{
_isPublishingRunning = false;
}
}
}
private void ProcessTasks()
private static void ProcessTasks()
{
var scheduledTasks = UmbracoSettings.ScheduledTasks;
if (scheduledTasks != null)
{
@@ -60,7 +80,7 @@ namespace Umbraco.Web.Scheduling
}
}
private bool GetTaskByHttp(string url)
private static bool GetTaskByHttp(string url)
{
var myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);
@@ -78,35 +98,5 @@ namespace Umbraco.Web.Scheduling
return false;
}
/// <summary>
/// Handles the disposal of resources. Derived from abstract class <see cref="DisposableObject"/> which handles common required locking logic.
/// </summary>
protected override void DisposeResources()
{
}
public void Run()
{
using (DisposableTimer.DebugDuration<ScheduledTasks>(() => "Scheduled tasks executing", () => "Scheduled tasks complete"))
{
if (_isPublishingRunning) return;
_isPublishingRunning = true;
try
{
ProcessTasks();
}
catch (Exception ee)
{
LogHelper.Error<ScheduledTasks>("Error executing scheduled task", ee);
}
finally
{
_isPublishingRunning = false;
}
}
}
}
}
+35 -73
View File
@@ -1,6 +1,4 @@
using System;
using System.Threading;
using System.Web;
using System.Threading;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Sync;
@@ -11,99 +9,60 @@ namespace Umbraco.Web.Scheduling
/// Used to do the scheduling for tasks, publishing, etc...
/// </summary>
/// <remarks>
/// All tasks are run in a background task runner which is web aware and will wind down the task correctly instead of killing it completely when
/// the app domain shuts down.
///
/// TODO: Much of this code is legacy and needs to be updated, there are a few new/better ways to do scheduling
/// in a web project nowadays.
///
/// //TODO: We need a much more robust way of handing scheduled tasks and also need to take into account app shutdowns during
/// a scheduled tasks operation
/// http://haacked.com/archive/2011/10/16/the-dangers-of-implementing-recurring-background-tasks-in-asp-net.aspx/
///
/// </remarks>
internal sealed class Scheduler : ApplicationEventHandler
{
private static Timer _pingTimer;
private static Timer _schedulingTimer;
private static BackgroundTaskRunner<ScheduledPublishing> _publishingRunner;
private static BackgroundTaskRunner<ScheduledTasks> _tasksRunner;
private static BackgroundTaskRunner<LogScrubber> _scrubberRunner;
private static Timer _logScrubberTimer;
private static volatile bool _started = false;
private static readonly object Locker = new object();
private static LogScrubber _scrubber;
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
if (umbracoApplication.Context == null)
return;
//subscribe to app init so we can subsribe to the application events
UmbracoApplicationBase.ApplicationInit += (sender, args) =>
{
var app = (HttpApplication)sender;
LogHelper.Debug<Scheduler>(() => "Initializing the scheduler");
//subscribe to the end of a successful request (a handler actually executed)
app.PostRequestHandlerExecute += (o, eventArgs) =>
{
if (_started == false)
{
lock (Locker)
{
if (_started == false)
{
_started = true;
LogHelper.Debug<Scheduler>(() => "Initializing the scheduler");
// time to setup the tasks
// time to setup the tasks
// these are the legacy tasks
// just copied over here for backward compatibility
// of course we should have a proper scheduler, see #U4-809
//We have 3 background runners that are web aware, if the app domain dies, these tasks will wind down correctly
_publishingRunner = new BackgroundTaskRunner<ScheduledPublishing>();
_tasksRunner = new BackgroundTaskRunner<ScheduledTasks>();
_scrubberRunner = new BackgroundTaskRunner<LogScrubber>();
//NOTE: It is important to note that we need to use the ctor for a timer without the 'state' object specified, this is in order
// to ensure that the timer itself is not GC'd since internally .net will pass itself in as the state object and that will keep it alive.
// There's references to this here: http://stackoverflow.com/questions/4962172/why-does-a-system-timers-timer-survive-gc-but-not-system-threading-timer
// we also make these timers static to ensure further GC safety.
//NOTE: It is important to note that we need to use the ctor for a timer without the 'state' object specified, this is in order
// to ensure that the timer itself is not GC'd since internally .net will pass itself in as the state object and that will keep it alive.
// There's references to this here: http://stackoverflow.com/questions/4962172/why-does-a-system-timers-timer-survive-gc-but-not-system-threading-timer
// we also make these timers static to ensure further GC safety.
// ping/keepalive
_pingTimer = new Timer(KeepAlive.Start);
_pingTimer.Change(60000, 300000);
// ping/keepalive - NOTE: we don't use a background runner for this because it does not need to be web aware, if the app domain dies, no problem
_pingTimer = new Timer(state => KeepAlive.Start(applicationContext));
_pingTimer.Change(60000, 300000);
// scheduled publishing/unpublishing
_schedulingTimer = new Timer(PerformScheduling);
_schedulingTimer.Change(30000, 60000);
// scheduled publishing/unpublishing
_schedulingTimer = new Timer(state => PerformScheduling(applicationContext));
_schedulingTimer.Change(60000, 60000);
//log scrubbing
_logScrubberTimer = new Timer(state => PerformLogScrub(applicationContext));
_logScrubberTimer.Change(60000, GetLogScrubbingInterval());
}
}
}
};
};
//log scrubbing
_scrubber = new LogScrubber();
_scrubber.Start();
}
private int GetLogScrubbingInterval()
{
int interval = 24 * 60 * 60; //24 hours
try
{
if (global::umbraco.UmbracoSettings.CleaningMiliseconds > -1)
interval = global::umbraco.UmbracoSettings.CleaningMiliseconds;
}
catch (Exception e)
{
LogHelper.Error<Scheduler>("Unable to locate a log scrubbing interval. Defaulting to 24 horus", e);
}
return interval;
}
private static void PerformLogScrub(ApplicationContext appContext)
{
_scrubberRunner.Add(new LogScrubber(appContext));
}
/// <summary>
/// This performs all of the scheduling on the one timer
/// </summary>
/// <param name="appContext"></param>
/// <param name="sender"></param>
/// <remarks>
/// No processing will be done if this server is a slave
/// </remarks>
private static void PerformScheduling(ApplicationContext appContext)
private static void PerformScheduling(object sender)
{
using (DisposableTimer.DebugDuration<Scheduler>(() => "Scheduling interval executing", () => "Scheduling interval complete"))
{
@@ -119,10 +78,12 @@ namespace Umbraco.Web.Scheduling
// then we will process the scheduling
//do the scheduled publishing
_publishingRunner.Add(new ScheduledPublishing(appContext));
var scheduledPublishing = new ScheduledPublishing();
scheduledPublishing.Start(ApplicationContext.Current);
//do the scheduled tasks
_tasksRunner.Add(new ScheduledTasks(appContext));
var scheduledTasks = new ScheduledTasks();
scheduledTasks.Start(ApplicationContext.Current);
break;
case CurrentServerEnvironmentStatus.Slave:
@@ -135,5 +96,6 @@ namespace Umbraco.Web.Scheduling
}
}
}
}
}
@@ -1,22 +0,0 @@
using System;
namespace Umbraco.Web.Scheduling
{
internal class TaskEventArgs<T> : EventArgs
where T : IBackgroundTask
{
public T Task { get; private set; }
public Exception Exception { get; private set; }
public TaskEventArgs(T task)
{
Task = task;
}
public TaskEventArgs(T task, Exception exception)
{
Task = task;
Exception = exception;
}
}
}
+4 -9
View File
@@ -93,9 +93,9 @@
<Project>{07fbc26b-2927-4a22-8d96-d644c667fecc}</Project>
<Name>UmbracoExamine</Name>
</ProjectReference>
<Reference Include="ClientDependency.Core, Version=1.8.2.1, Culture=neutral, processorArchitecture=MSIL">
<Reference Include="ClientDependency.Core">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\ClientDependency.1.8.2.1\lib\net40\ClientDependency.Core.dll</HintPath>
<HintPath>..\packages\ClientDependency.1.7.1.2\lib\ClientDependency.Core.dll</HintPath>
</Reference>
<Reference Include="CookComputing.XmlRpcV2, Version=2.5.0.0, Culture=neutral, PublicKeyToken=a7d6e17aa302004d, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
@@ -321,13 +321,10 @@
<Compile Include="Routing\ContentLastChanceFinderByNotFoundHandlers.cs" />
<Compile Include="Models\RegisterModel.cs" />
<Compile Include="Models\LoginModel.cs" />
<Compile Include="Scheduling\BackgroundTaskRunner.cs" />
<Compile Include="Scheduling\IBackgroundTask.cs" />
<Compile Include="Scheduling\KeepAlive.cs" />
<Compile Include="Scheduling\LogScrubber.cs" />
<Compile Include="Scheduling\ScheduledPublishing.cs" />
<Compile Include="Scheduling\ScheduledTasks.cs" />
<Compile Include="Scheduling\TaskEventArgs.cs" />
<Compile Include="Security\MembershipHelper.cs" />
<Compile Include="Security\Providers\MembersMembershipProvider.cs" />
<Compile Include="Security\Providers\MembersRoleProvider.cs" />
@@ -2044,10 +2041,8 @@
</ItemGroup>
<ItemGroup />
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">11.0</VisualStudioVersion>
<VSToolsPath Condition="exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0\WebApplications\Microsoft.WebApplication.targets')">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0</VSToolsPath>
<VSToolsPath Condition="exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0\WebApplications\Microsoft.WebApplication.targets')">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0</VSToolsPath>
<VSToolsPath Condition="exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0\WebApplications\Microsoft.WebApplication.targets')">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0</VSToolsPath>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ClientDependency" version="1.8.2.1" targetFramework="net40" />
<package id="ClientDependency" version="1.7.1.2" targetFramework="net40" />
<package id="Examine" version="0.1.57.2941" targetFramework="net40" />
<package id="HtmlAgilityPack" version="1.4.6" targetFramework="net40" />
<package id="Lucene.Net" version="2.9.4.1" targetFramework="net40" />
+48 -55
View File
@@ -1,7 +1,6 @@
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Text;
@@ -9,31 +8,35 @@ using System.Text.RegularExpressions;
using System.Web;
using System.Web.UI;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Web;
using Umbraco.Web.Cache;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;
using Umbraco.Web.Templates;
using umbraco.BusinessLogic;
using umbraco.cms.businesslogic;
using umbraco.cms.businesslogic.media;
using umbraco.cms.businesslogic.member;
using umbraco.cms.businesslogic.propertytype;
using umbraco.cms.businesslogic.relation;
using umbraco.cms.businesslogic.web;
using umbraco.cms.helpers;
using umbraco.presentation.cache;
using umbraco.scripting;
using umbraco.DataLayer;
using System.Web.Security;
using umbraco.cms.businesslogic.language;
using Umbraco.Core.IO;
using Language = umbraco.cms.businesslogic.language.Language;
using Media = umbraco.cms.businesslogic.media.Media;
using Member = umbraco.cms.businesslogic.member.Member;
using PropertyType = umbraco.cms.businesslogic.propertytype.PropertyType;
using Relation = umbraco.cms.businesslogic.relation.Relation;
using System.Collections;
using System.Collections.Generic;
using umbraco.cms.businesslogic.cache;
using umbraco.NodeFactory;
using UmbracoContext = umbraco.presentation.UmbracoContext;
using System.Linq;
namespace umbraco
{
@@ -467,48 +470,46 @@ namespace umbraco
{
if (UmbracoSettings.UmbracoLibraryCacheDuration > 0)
{
var xml = ApplicationContext.Current.ApplicationCache.GetCacheItem(
XPathNodeIterator retVal = ApplicationContext.Current.ApplicationCache.GetCacheItem(
string.Format(
"{0}_{1}_{2}", CacheKeys.MediaCacheKey, MediaId, Deep),
TimeSpan.FromSeconds(UmbracoSettings.UmbracoLibraryCacheDuration),
() => GetMediaDo(MediaId, Deep));
if (xml != null)
{
//returning the root element of the Media item fixes the problem
return xml.CreateNavigator().Select("/");
}
if (retVal != null)
return retVal;
}
else
{
var xml = GetMediaDo(MediaId, Deep);
//returning the root element of the Media item fixes the problem
return xml.CreateNavigator().Select("/");
return GetMediaDo(MediaId, Deep);
}
}
catch(Exception ex)
catch
{
LogHelper.Error<library>("An error occurred looking up media", ex);
}
LogHelper.Debug<library>("No media result for id {0}", () => MediaId);
var xd = new XmlDocument();
XmlDocument xd = new XmlDocument();
xd.LoadXml(string.Format("<error>No media is maching '{0}'</error>", MediaId));
return xd.CreateNavigator().Select("/");
}
private static XElement GetMediaDo(int mediaId, bool deep)
private static XPathNodeIterator GetMediaDo(int mediaId, bool deep)
{
var media = ApplicationContext.Current.Services.MediaService.GetById(mediaId);
if (media == null) return null;
var serializer = new EntityXmlSerializer();
var serialized = serializer.Serialize(
ApplicationContext.Current.Services.MediaService, ApplicationContext.Current.Services.DataTypeService,
media, deep);
return serialized;
var m = new Media(mediaId);
if (m.nodeObjectType == Media._objectType)
{
var mXml = new XmlDocument();
var xml = m.ToXml(mXml, deep);
//This will be null if the media isn't public (meaning it is in the trash)
if (xml == null) return null;
//TODO: This is an aweful way of loading in XML - it is very slow.
mXml.LoadXml(xml.OuterXml);
var xp = mXml.CreateNavigator();
var xpath = UmbracoSettings.UseLegacyXmlSchema ? "/node" : String.Format("/{0}", Casing.SafeAliasWithForcingCheck(m.ContentType.Alias));
return xp.Select(xpath);
}
return null;
}
/// <summary>
@@ -524,42 +525,35 @@ namespace umbraco
{
if (UmbracoSettings.UmbracoLibraryCacheDuration > 0)
{
var xml = ApplicationContext.Current.ApplicationCache.GetCacheItem(
var retVal = ApplicationContext.Current.ApplicationCache.GetCacheItem(
string.Format(
"{0}_{1}", CacheKeys.MemberLibraryCacheKey, MemberId),
TimeSpan.FromSeconds(UmbracoSettings.UmbracoLibraryCacheDuration),
() => GetMemberDo(MemberId));
if (xml != null)
{
return xml.CreateNavigator().Select("/");
}
if (retVal != null)
return retVal.CreateNavigator().Select("/");
}
else
{
return GetMemberDo(MemberId).CreateNavigator().Select("/");
}
}
catch (Exception ex)
catch
{
LogHelper.Error<library>("An error occurred looking up member", ex);
}
LogHelper.Debug<library>("No member result for id {0}", () => MemberId);
var xd = new XmlDocument();
XmlDocument xd = new XmlDocument();
xd.LoadXml(string.Format("<error>No member is maching '{0}'</error>", MemberId));
return xd.CreateNavigator().Select("/");
}
private static XElement GetMemberDo(int MemberId)
private static XmlDocument GetMemberDo(int MemberId)
{
var member = ApplicationContext.Current.Services.MemberService.GetById(MemberId);
if (member == null) return null;
var serializer = new EntityXmlSerializer();
var serialized = serializer.Serialize(
ApplicationContext.Current.Services.DataTypeService, member);
return serialized;
Member m = new Member(MemberId);
XmlDocument mXml = new XmlDocument();
mXml.LoadXml(m.ToXml(mXml, false).OuterXml);
return mXml;
}
/// <summary>
@@ -1358,9 +1352,8 @@ namespace umbraco
nav.MoveToId(HttpContext.Current.Items["pageID"].ToString());
return nav.Select(".");
}
catch (Exception ex)
catch
{
LogHelper.Error<library>("Could not retrieve current xml node", ex);
}
XmlDocument xd = new XmlDocument();
@@ -27,9 +27,8 @@ namespace umbraco.cms.presentation.Trees
//create singleton
private static readonly TreeDefinitionCollection instance = new TreeDefinitionCollection();
private static readonly object Locker = new object();
private static volatile bool _ensureTrees = false;
private static readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();
public static TreeDefinitionCollection Instance
{
get
@@ -128,12 +127,7 @@ namespace umbraco.cms.presentation.Trees
public void ReRegisterTrees()
{
//clears the trees/flag so that they are lazily refreshed on next access
lock (Locker)
{
this.Clear();
_ensureTrees = false;
}
EnsureTreesRegistered(true);
}
/// <summary>
@@ -142,68 +136,70 @@ namespace umbraco.cms.presentation.Trees
/// This will also store an instance of each tree object in the TreeDefinition class which should be
/// used when referencing all tree classes.
/// </summary>
private void EnsureTreesRegistered()
private void EnsureTreesRegistered(bool clearFirst = false)
{
if (_ensureTrees == false)
{
lock (Locker)
{
if (_ensureTrees == false)
{
using (var l = new UpgradeableReadLock(Lock))
{
if (clearFirst)
{
this.Clear();
}
var foundITrees = PluginManager.Current.ResolveTrees();
//if we already have tree, exit
if (this.Count > 0)
return;
var objTrees = ApplicationTree.getAll();
var appTrees = new List<ApplicationTree>();
appTrees.AddRange(objTrees);
var apps = Application.getAll();
foreach (var type in foundITrees)
{
//find the Application tree's who's combination of assembly name and tree type is equal to
//the Type that was found's full name.
//Since a tree can exist in multiple applications we'll need to register them all.
//The logic of this has changed in 6.0: http://issues.umbraco.org/issue/U4-1360
// we will support the old legacy way but the normal way is to match on assembly qualified names
var appTreesForType = appTrees.FindAll(
tree =>
{
//match the type on assembly qualified name if the assembly attribute is empty or if the
// tree type contains a comma (meaning it is assembly qualified)
if (tree.AssemblyName.IsNullOrWhiteSpace() || tree.Type.Contains(","))
{
return tree.GetRuntimeType() == type;
}
//otherwise match using legacy match rules
return (string.Format("{0}.{1}", tree.AssemblyName, tree.Type).InvariantEquals(type.FullName));
}
);
foreach (var appTree in appTreesForType)
{
//find the Application object whos name is the same as our appTree ApplicationAlias
var app = apps.Find(
a => (a.alias == appTree.ApplicationAlias)
);
var def = new TreeDefinition(type, appTree, app);
this.Add(def);
}
}
//sort our trees with the sort order definition
this.Sort((t1, t2) => t1.Tree.SortOrder.CompareTo(t2.Tree.SortOrder));
_ensureTrees = true;
}
}
}
l.UpgradeToWriteLock();
var foundITrees = PluginManager.Current.ResolveTrees();
var objTrees = ApplicationTree.getAll();
var appTrees = new List<ApplicationTree>();
appTrees.AddRange(objTrees);
var apps = Application.getAll();
foreach (var type in foundITrees)
{
//find the Application tree's who's combination of assembly name and tree type is equal to
//the Type that was found's full name.
//Since a tree can exist in multiple applications we'll need to register them all.
//The logic of this has changed in 6.0: http://issues.umbraco.org/issue/U4-1360
// we will support the old legacy way but the normal way is to match on assembly qualified names
var appTreesForType = appTrees.FindAll(
tree =>
{
//match the type on assembly qualified name if the assembly attribute is empty or if the
// tree type contains a comma (meaning it is assembly qualified)
if (tree.AssemblyName.IsNullOrWhiteSpace() || tree.Type.Contains(","))
{
return tree.GetRuntimeType() == type;
}
//otherwise match using legacy match rules
return (string.Format("{0}.{1}", tree.AssemblyName, tree.Type).InvariantEquals(type.FullName));
}
);
foreach (var appTree in appTreesForType)
{
//find the Application object whos name is the same as our appTree ApplicationAlias
var app = apps.Find(
a => (a.alias == appTree.ApplicationAlias)
);
var def = new TreeDefinition(type, appTree, app);
this.Add(def);
}
}
//sort our trees with the sort order definition
this.Sort((t1, t2) => t1.Tree.SortOrder.CompareTo(t2.Tree.SortOrder));
}
}
}
+44 -113
View File
@@ -26,22 +26,6 @@ namespace umbraco.BusinessLogic
internal const string AppConfigFileName = "applications.config";
private static string _appConfig;
private static readonly object Locker = new object();
private static IEnumerable<Application> _allAvailableSections;
private static volatile bool _isInitialized = false;
/// <summary>
/// Initializes the service with all available application plugins
/// </summary>
/// <param name="allAvailableSections">
/// All application plugins found in assemblies
/// </param>
/// <remarks>
/// This is used to populate the app.config file with any applications declared in plugins that don't exist in the file
/// </remarks>
internal static void Initialize(IEnumerable<Application> allAvailableSections)
{
_allAvailableSections = allAvailableSections;
}
/// <summary>
/// gets/sets the application.config file path
@@ -65,75 +49,52 @@ namespace umbraco.BusinessLogic
/// <summary>
/// The cache storage for all applications
/// </summary>
public static List<Application> GetSections()
internal static List<Application> Apps
{
return ApplicationContext.Current.ApplicationCache.GetCacheItem<List<Application>>(
CacheKeys.ApplicationsCacheKey,
() =>
{
////used for unit tests
//if (_testApps != null)
// return _testApps;
var list = ReadFromXmlAndSort();
//On first access we need to do some initialization
if (_isInitialized == false)
{
lock (Locker)
get
{
return ApplicationContext.Current.ApplicationCache.GetCacheItem(
CacheKeys.ApplicationsCacheKey,
() =>
{
if (_isInitialized == false)
////used for unit tests
//if (_testApps != null)
// return _testApps;
var tmp = new List<Application>();
try
{
//now we can check the non-volatile flag
if (_allAvailableSections != null)
{
var hasChanges = false;
LoadXml(doc =>
LoadXml(doc =>
{
//Now, load in the xml structure and update it with anything that is not declared there and save the file.
//NOTE: On the first iteration here, it will lazily scan all apps, etc... this is because this ienumerable is lazy
// based on the ApplicationRegistrar - and as noted there this is not an ideal way to do things but were stuck like this
// currently because of the legacy assemblies and types not in the Core.
//Get all the trees not registered in the config
var unregistered = _allAvailableSections
.Where(x => list.Any(l => l.alias == x.alias) == false)
.ToArray();
hasChanges = unregistered.Any();
var count = 0;
foreach (var attr in unregistered)
foreach (var addElement in doc.Root.Elements("add").OrderBy(x =>
{
var sortOrderAttr = x.Attribute("sortOrder");
return sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0;
}))
{
doc.Root.Add(new XElement("add",
new XAttribute("alias", attr.alias),
new XAttribute("name", attr.name),
new XAttribute("icon", attr.icon),
new XAttribute("sortOrder", attr.sortOrder)));
count++;
var sortOrderAttr = addElement.Attribute("sortOrder");
tmp.Add(new Application(addElement.Attribute("name").Value,
addElement.Attribute("alias").Value,
addElement.Attribute("icon").Value,
sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0));
}
//don't save if there's no changes
return count > 0;
}, true);
if (hasChanges)
{
//If there were changes, we need to re-read the structures from the XML
list = ReadFromXmlAndSort();
}
}
_isInitialized = true;
}, false);
return tmp;
}
}
}
catch
{
//this is a bit of a hack that just ensures the application doesn't crash when the
//installer is run and there is no database or connection string defined.
//the reason this method may get called during the installation is that the
//SqlHelper of this class is shared amongst everything "Application" wide.
return list;
});
//TODO: Perhaps we should log something here??
return null;
}
});
}
}
/// <summary>
@@ -239,7 +200,7 @@ namespace umbraco.BusinessLogic
[MethodImpl(MethodImplOptions.Synchronized)]
public static void MakeNew(string name, string alias, string icon)
{
MakeNew(name, alias, icon, GetSections().Max(x => x.sortOrder) + 1);
MakeNew(name, alias, icon, Apps.Max(x => x.sortOrder) + 1);
}
/// <summary>
@@ -263,9 +224,6 @@ namespace umbraco.BusinessLogic
new XAttribute("name", name),
new XAttribute("icon", icon),
new XAttribute("sortOrder", sortOrder)));
return true;
}, true);
//raise event
@@ -280,7 +238,7 @@ namespace umbraco.BusinessLogic
/// <returns></returns>
public static Application getByAlias(string appAlias)
{
return GetSections().Find(t => t.alias == appAlias);
return Apps.Find(t => t.alias == appAlias);
}
/// <summary>
@@ -301,9 +259,6 @@ namespace umbraco.BusinessLogic
LoadXml(doc =>
{
doc.Root.Elements("add").Where(x => x.Attribute("alias") != null && x.Attribute("alias").Value == this.alias).Remove();
return true;
}, true);
//raise event
@@ -316,7 +271,7 @@ namespace umbraco.BusinessLogic
/// <returns>Returns a Application Array</returns>
public static List<Application> getAll()
{
return GetSections();
return Apps;
}
/// <summary>
@@ -327,32 +282,8 @@ namespace umbraco.BusinessLogic
{
ApplicationStartupHandler.RegisterHandlers();
}
private static List<Application> ReadFromXmlAndSort()
{
var tmp = new List<Application>();
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 sortOrderAttr = addElement.Attribute("sortOrder");
tmp.Add(new Application(addElement.Attribute("name").Value,
addElement.Attribute("alias").Value,
addElement.Attribute("icon").Value,
sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0));
}
return false;
}, false);
return tmp;
}
internal static void LoadXml(Func<XDocument, bool> callback, bool saveAfterCallbackIfChanged)
internal static void LoadXml(Action<XDocument> callback, bool saveAfterCallback)
{
lock (Locker)
{
@@ -362,9 +293,9 @@ namespace umbraco.BusinessLogic
if (doc.Root != null)
{
var changed = callback.Invoke(doc);
callback.Invoke(doc);
if (saveAfterCallbackIfChanged && changed)
if (saveAfterCallback)
{
//ensure the folder is created!
Directory.CreateDirectory(Path.GetDirectoryName(AppConfigFilePath));
@@ -1,6 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlClient;
using System.Linq;
@@ -14,21 +12,8 @@ using umbraco.interfaces;
namespace umbraco.BusinessLogic
{
/// <summary>
/// A startup handler for putting the app config in the config file based on attributes found
/// </summary>
/// /// <remarks>
/// TODO: This is really not a very ideal process but the code is found here because tree plugins are in the Web project or the legacy business logic project.
/// Moving forward we can put the base tree plugin classes in the core and then this can all just be taken care of normally within the service.
/// </remarks>
public class ApplicationRegistrar : ApplicationEventHandler
public class ApplicationRegistrar : IApplicationStartupHandler
{
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
//initialize the section service with a lazy collection of found app plugins
Application.Initialize(new LazyEnumerableSections());
}
private ISqlHelper _sqlHelper;
protected ISqlHelper SqlHelper
{
@@ -47,51 +32,55 @@ namespace umbraco.BusinessLogic
}
}
/// <summary>
/// This class is here so that we can provide lazy access to tree scanning for when it is needed
/// </summary>
private class LazyEnumerableSections : IEnumerable<Application>
public ApplicationRegistrar()
{
public LazyEnumerableSections()
{
_lazySections = new Lazy<IEnumerable<Application>>(() =>
//don't do anything if the application is not configured!
if (!ApplicationContext.Current.IsConfigured)
return;
// Load all Applications by attribute and add them to the XML config
var types = PluginManager.Current.ResolveApplications();
//since applications don't populate their metadata from the attribute and because it is an interface,
//we need to interrogate the attributes for the data. Would be better to have a base class that contains
//metadata populated by the attribute. Oh well i guess.
var attrs = types.Select(x => x.GetCustomAttributes<ApplicationAttribute>(false).Single())
.Where(x => Application.getByAlias(x.Alias) == null);
var allAliases = Application.getAll().Select(x => x.alias).Concat(attrs.Select(x => x.Alias));
var inString = "'" + string.Join("','", allAliases) + "'";
Application.LoadXml(doc =>
{
// Load all Applications by attribute and add them to the XML config
var types = PluginManager.Current.ResolveApplications();
foreach (var attr in attrs)
{
doc.Root.Add(new XElement("add",
new XAttribute("alias", attr.Alias),
new XAttribute("name", attr.Name),
new XAttribute("icon", attr.Icon),
new XAttribute("sortOrder", attr.SortOrder)));
}
var db = ApplicationContext.Current.DatabaseContext.Database;
var exist = db.TableExist("umbracoApp");
if (exist)
{
var dbApps = SqlHelper.ExecuteReader("SELECT * FROM umbracoApp WHERE appAlias NOT IN (" + inString + ")");
while (dbApps.Read())
{
doc.Root.Add(new XElement("add",
new XAttribute("alias", dbApps.GetString("appAlias")),
new XAttribute("name", dbApps.GetString("appName")),
new XAttribute("icon", dbApps.GetString("appIcon")),
new XAttribute("sortOrder", dbApps.GetByte("sortOrder"))));
}
}
//since applications don't populate their metadata from the attribute and because it is an interface,
//we need to interrogate the attributes for the data. Would be better to have a base class that contains
//metadata populated by the attribute. Oh well i guess.
var attrs = types.Select(x => x.GetCustomAttributes<ApplicationAttribute>(false).Single());
return attrs.Select(x => new Application(x.Name, x.Alias, x.Icon, x.SortOrder)).ToArray();
});
}
private readonly Lazy<IEnumerable<Application>> _lazySections;
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
/// </returns>
public IEnumerator<Application> GetEnumerator()
{
return _lazySections.Value.GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
/// </returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}, true);
//TODO Shouldn't this be enabled and then delete the whole table?
//SqlHelper.ExecuteNonQuery("DELETE FROM umbracoApp");
}
}
}
+49 -166
View File
@@ -9,7 +9,6 @@ using Umbraco.Core.Cache;
using Umbraco.Core.IO;
using Umbraco.Core.Events;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using umbraco.DataLayer;
namespace umbraco.BusinessLogic
@@ -25,23 +24,6 @@ namespace umbraco.BusinessLogic
internal const string TreeConfigFileName = "trees.config";
private static string _treeConfig;
private static readonly object Locker = new object();
private static volatile bool _isInitialized = false;
private static IEnumerable<ApplicationTree> _allAvailableTrees;
/// <summary>
/// Initializes the service with any trees found in plugins
/// </summary>
/// <param name="allAvailableTrees">
/// A collection of all available tree found in assemblies in the application
/// </param>
/// <remarks>
/// This will update the trees.config with the found tree plugins that are not currently listed in the file when the first
/// access is made to resolve the tree collection
/// </remarks>
internal static void Intitialize(IEnumerable<ApplicationTree> allAvailableTrees)
{
_allAvailableTrees = allAvailableTrees;
}
/// <summary>
/// gets/sets the trees.config file path
@@ -63,136 +45,58 @@ namespace umbraco.BusinessLogic
}
/// <summary>
/// The main entry point to get application trees
/// The cache storage for all application trees
/// </summary>
/// <remarks>
/// This lazily on first access will scan for plugin trees and ensure the trees.config is up-to-date with the plugins. If plugins
/// haven't changed on disk then the file will not be saved. The trees are all then loaded from this config file into cache and returned.
/// </remarks>
private static List<ApplicationTree> GetAppTrees()
private static List<ApplicationTree> AppTrees
{
return ApplicationContext.Current.ApplicationCache.GetCacheItem<List<ApplicationTree>>(
CacheKeys.ApplicationTreeCacheKey,
() =>
{
var list = ReadFromXmlAndSort();
//On first access we need to do some initialization
if (_isInitialized == false)
{
lock (Locker)
{
if (_isInitialized == false)
{
//now we can check the non-volatile flag
if (_allAvailableTrees != null)
{
var hasChanges = false;
LoadXml(doc =>
{
//Now, load in the xml structure and update it with anything that is not declared there and save the file.
//NOTE: On the first iteration here, it will lazily scan all trees, etc... this is because this ienumerable is lazy
// based on the ApplicationTreeRegistrar - and as noted there this is not an ideal way to do things but were stuck like this
// currently because of the legacy assemblies and types not in the Core.
//Get all the trees not registered in the config
var unregistered = _allAvailableTrees
.Where(x => list.Any(l => l.Alias == x.Alias) == false)
.ToArray();
hasChanges = unregistered.Any();
if (hasChanges == false) return false;
//add the unregistered ones to the list and re-save the file if any changes were found
var count = 0;
foreach (var tree in unregistered)
{
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)));
count++;
}
//don't save if there's no changes
return count > 0;
}, true);
if (hasChanges)
{
//If there were changes, we need to re-read the structures from the XML
list = ReadFromXmlAndSort();
}
}
_isInitialized = true;
}
}
}
return list;
});
}
private static List<ApplicationTree> ReadFromXmlAndSort()
{
var list = new List<ApplicationTree>();
//read in the xml file containing trees and convert them all to ApplicationTree instances
LoadXml(doc =>
get
{
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");
return ApplicationContext.Current.ApplicationCache.GetCacheItem(
CacheKeys.ApplicationTreeCacheKey,
() =>
{
var list = new List<ApplicationTree>();
var clrType = System.Type.GetType(type);
if (clrType == null)
{
LogHelper.Warn(typeof(ApplicationTree), "The tree definition: " + addElement.ToString() + " could not be resolved to a .Net object type");
continue;
}
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;
}))
{
//check if the tree definition (applicationAlias + type + assembly) is already in the list
var applicationAlias = (string)addElement.Attribute("application");
var type = (string)addElement.Attribute("type");
var assembly = (string)addElement.Attribute("assembly");
if (list.Any(tree => tree.ApplicationAlias.InvariantEquals(applicationAlias) && tree.GetRuntimeType() == clrType) == false)
{
list.Add(new ApplicationTree(
addElement.Attribute("silent") != null && Convert.ToBoolean(addElement.Attribute("silent").Value),
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,
(string)addElement.Attribute("assembly"), //this could be empty: http://issues.umbraco.org/issue/U4-1360
addElement.Attribute("type").Value,
addElement.Attribute("action") != null ? addElement.Attribute("action").Value : ""));
}
}
//check if the tree definition (applicationAlias + type + assembly) is already in the list
return false;
if (!list.Any(tree => tree.ApplicationAlias.InvariantEquals(applicationAlias)
&& tree.Type.InvariantEquals(type)
&& tree.AssemblyName.InvariantEquals(assembly)))
{
list.Add(new ApplicationTree(
addElement.Attribute("silent") != null ? Convert.ToBoolean(addElement.Attribute("silent").Value) : false,
addElement.Attribute("initialize") != null ? Convert.ToBoolean(addElement.Attribute("initialize").Value) : true,
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,
(string)addElement.Attribute("assembly"), //this could be empty: http://issues.umbraco.org/issue/U4-1360
addElement.Attribute("type").Value,
addElement.Attribute("action") != null ? addElement.Attribute("action").Value : ""));
}
}, false);
return list;
}
}, false);
return list;
});
}
}
/// <summary>
@@ -352,9 +256,6 @@ namespace umbraco.BusinessLogic
new XAttribute("type", type),
new XAttribute("action", string.IsNullOrEmpty(action) ? "" : action)));
}
return true;
}, true);
OnNew(new ApplicationTree(silent, initialize, sortOrder, applicationAlias, alias, title, iconClosed, iconOpened, assemblyName, type, action), new EventArgs());
@@ -386,8 +287,6 @@ namespace umbraco.BusinessLogic
el.Add(new XAttribute("action", string.IsNullOrEmpty(this.Action) ? "" : this.Action));
}
return true;
}, true);
OnUpdated(this, new EventArgs());
@@ -405,9 +304,6 @@ namespace umbraco.BusinessLogic
{
doc.Root.Elements("add").Where(x => x.Attribute("application") != null && x.Attribute("application").Value == this.ApplicationAlias &&
x.Attribute("alias") != null && x.Attribute("alias").Value == this.Alias).Remove();
return true;
}, true);
OnDeleted(this, new EventArgs());
@@ -421,7 +317,7 @@ namespace umbraco.BusinessLogic
/// <returns>An ApplicationTree instance</returns>
public static ApplicationTree getByAlias(string treeAlias)
{
return GetAppTrees().Find(t => (t.Alias == treeAlias));
return AppTrees.Find(t => (t.Alias == treeAlias));
}
@@ -431,7 +327,7 @@ namespace umbraco.BusinessLogic
/// <returns>Returns a ApplicationTree Array</returns>
public static ApplicationTree[] getAll()
{
return GetAppTrees().OrderBy(x => x.SortOrder).ToArray();
return AppTrees.OrderBy(x => x.SortOrder).ToArray();
}
/// <summary>
@@ -452,7 +348,7 @@ namespace umbraco.BusinessLogic
/// <returns>Returns a ApplicationTree Array</returns>
public static ApplicationTree[] getApplicationTree(string applicationAlias, bool onlyInitializedApplications)
{
var list = GetAppTrees().FindAll(
var list = AppTrees.FindAll(
t =>
{
if (onlyInitializedApplications)
@@ -464,34 +360,21 @@ namespace umbraco.BusinessLogic
return list.OrderBy(x => x.SortOrder).ToArray();
}
/// <summary>
/// Loads in the xml structure from disk if one is found, otherwise loads in an empty xml structure, calls the
/// callback with the xml document and saves the structure back to disk if saveAfterCallback is true.
/// </summary>
/// <param name="callback"></param>
/// <param name="saveAfterCallbackIfChanges"></param>
internal static void LoadXml(Func<XDocument, bool> callback, bool saveAfterCallbackIfChanges)
internal static void LoadXml(Action<XDocument> callback, bool saveAfterCallback)
{
lock (Locker)
{
var doc = File.Exists(TreeConfigFilePath)
? XDocument.Load(TreeConfigFilePath)
: XDocument.Parse("<?xml version=\"1.0\"?><trees />");
if (doc.Root != null)
{
var hasChanges = callback.Invoke(doc);
callback.Invoke(doc);
if (saveAfterCallbackIfChanges && hasChanges
//Don't save it if it is empty, in some very rare cases if the app domain get's killed in the middle of this process
// in some insane way the file saved will be empty. I'm pretty sure it's not actually anything to do with the xml doc and
// more about the IO trying to save the XML doc, but it doesn't hurt to check.
&& doc.Root != null && doc.Root.Elements().Any())
if (saveAfterCallback)
{
//ensures the folder exists
Directory.CreateDirectory(Path.GetDirectoryName(TreeConfigFilePath));
//saves it
doc.Save(TreeConfigFilePath);
//remove the cache now that it has changed SD: I'm leaving this here even though it
@@ -1,6 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Umbraco.Core;
@@ -11,84 +9,81 @@ using umbraco.interfaces;
namespace umbraco.BusinessLogic
{
public class ApplicationTreeRegistrar : ApplicationEventHandler
public class ApplicationTreeRegistrar : IApplicationStartupHandler
{
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
public ApplicationTreeRegistrar()
{
//Call initialize on the tree service with the lazy enumerable class below, when the tree service needs to resolve,
// it will lazily do the scanning and comparing so it's not actually done on app start.
ApplicationTree.Intitialize(new LazyEnumerableTrees());
}
//don't do anything if the application or database is not configured!
if (ApplicationContext.Current == null
|| !ApplicationContext.Current.IsConfigured
|| !ApplicationContext.Current.DatabaseContext.IsDatabaseConfigured)
return;
/// <summary>
/// This class is here so that we can provide lazy access to tree scanning for when it is needed
/// </summary>
private class LazyEnumerableTrees : IEnumerable<ApplicationTree>
{
public LazyEnumerableTrees()
// Load all Trees by attribute and add them to the XML config
var types = PluginManager.Current.ResolveAttributedTrees();
var items = types
.Select(x =>
new Tuple<Type, TreeAttribute>(x, x.GetCustomAttributes<TreeAttribute>(false).Single()))
.Where(x => ApplicationTree.getByAlias(x.Item2.Alias) == null);
var allAliases = ApplicationTree.getAll().Select(x => x.Alias).Concat(items.Select(x => x.Item2.Alias));
var inString = "'" + string.Join("','", allAliases) + "'";
ApplicationTree.LoadXml(doc =>
{
_lazyTrees = new Lazy<IEnumerable<ApplicationTree>>(() =>
foreach (var tuple in items)
{
var added = new List<string>();
var type = tuple.Item1;
var attr = tuple.Item2;
//Add the new tree that doesn't exist in the config that was found by type finding
// Load all Controller Trees by attribute
var types = PluginManager.Current.ResolveAttributedTrees();
//convert them to ApplicationTree instances
var items = types
.Select(x =>
new Tuple<Type, TreeAttribute>(x, x.GetCustomAttributes<TreeAttribute>(false).Single()))
.Select(x => new ApplicationTree(
x.Item2.Silent, x.Item2.Initialize, (byte) x.Item2.SortOrder, x.Item2.ApplicationAlias, x.Item2.Alias, x.Item2.Title, x.Item2.IconClosed, x.Item2.IconOpen,
"",
x.Item1.GetFullNameWithAssembly(),
x.Item2.Action))
.ToArray();
doc.Root.Add(new XElement("add",
new XAttribute("silent", attr.Silent),
new XAttribute("initialize", attr.Initialize),
new XAttribute("sortOrder", attr.SortOrder),
new XAttribute("alias", attr.Alias),
new XAttribute("application", attr.ApplicationAlias),
new XAttribute("title", attr.Title),
new XAttribute("iconClosed", attr.IconClosed),
new XAttribute("iconOpen", attr.IconOpen),
// don't add the assembly, we don't need this:
// http://issues.umbraco.org/issue/U4-1360
//new XAttribute("assembly", assemblyName),
//new XAttribute("type", typeName),
// instead, store the assembly type name
new XAttribute("type", type.GetFullNameWithAssembly()),
new XAttribute("action", attr.Action)));
}
added.AddRange(items.Select(x => x.Alias));
//add any trees that were found in the database that don't exist in the config
//find the legacy trees
var legacyTreeTypes = PluginManager.Current.ResolveAttributedTrees();
//convert them to ApplicationTree instances
var legacyItems = legacyTreeTypes
.Select(x =>
new Tuple<Type, global::umbraco.businesslogic.TreeAttribute>(
x,
x.GetCustomAttributes<global::umbraco.businesslogic.TreeAttribute>(false).SingleOrDefault()))
.Where(x => x.Item2 != null)
//make sure the legacy tree isn't added on top of the controller tree!
.Where(x => added.InvariantContains(x.Item2.Alias) == false)
.Select(x => new ApplicationTree(x.Item2.Silent, x.Item2.Initialize, (byte) x.Item2.SortOrder, x.Item2.ApplicationAlias, x.Item2.Alias, x.Item2.Title, x.Item2.IconClosed, x.Item2.IconOpen,
"",
x.Item1.GetFullNameWithAssembly(),
x.Item2.Action));
var db = ApplicationContext.Current.DatabaseContext.Database;
var exist = db.TableExist("umbracoAppTree");
if (exist)
{
var appTrees = db.Fetch<AppTreeDto>("WHERE treeAlias NOT IN (" + inString + ")");
foreach (var appTree in appTrees)
{
var action = appTree.Action;
return items.Concat(legacyItems).ToArray();
});
}
doc.Root.Add(new XElement("add",
new XAttribute("silent", appTree.Silent),
new XAttribute("initialize", appTree.Initialize),
new XAttribute("sortOrder", appTree.SortOrder),
new XAttribute("alias", appTree.Alias),
new XAttribute("application", appTree.AppAlias),
new XAttribute("title", appTree.Title),
new XAttribute("iconClosed", appTree.IconClosed),
new XAttribute("iconOpen", appTree.IconOpen),
new XAttribute("assembly", appTree.HandlerAssembly),
new XAttribute("type", appTree.HandlerType),
new XAttribute("action", string.IsNullOrEmpty(action) ? "" : action)));
}
}
private readonly Lazy<IEnumerable<ApplicationTree>> _lazyTrees;
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
/// </returns>
public IEnumerator<ApplicationTree> GetEnumerator()
{
return _lazyTrees.Value.GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
/// </returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}, true);
}
}
}
+4 -2
View File
@@ -93,6 +93,7 @@ namespace umbraco.cms.businesslogic
/// </summary>
public class DictionaryItem
{
private string _key;
internal Guid UniqueId { get; private set; }
@@ -117,12 +118,13 @@ namespace umbraco.cms.businesslogic
{
EnsureCache();
if (!DictionaryItems.ContainsKey(key))
var item = DictionaryItems.Values.SingleOrDefault(x => x.key == key);
if (item == null)
{
throw new ArgumentException("No key " + key + " exists in dictionary");
}
var item = DictionaryItems[key];
this.id = item.id;
this._key = item.key;
this.ParentId = item.ParentId;
+1 -8
View File
@@ -494,14 +494,7 @@ namespace umbraco.cms.businesslogic.web
return hasAccess;
}
[Obsolete("This method has been replaced because of a spelling mistake. Use the HasAccess method instead.", false)]
public static bool HasAccces(int documentId, object memberId)
{
// Call the correctly named version of this method
return HasAccess(documentId, memberId);
}
public static bool HasAccess(int documentId, object memberId)
{
bool hasAccess = false;
var node = new CMSNode(documentId);
@@ -518,7 +511,7 @@ namespace umbraco.cms.businesslogic.web
if (member != null)
{
foreach (string role in Roles.GetRolesForUser(member.UserName))
foreach (string role in Roles.GetRolesForUser())
{
if (currentNode.SelectSingleNode("./group [@id='" + role + "']") != null)
{
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ClientDependency" version="1.8.2.1" targetFramework="net40" />
<package id="ClientDependency" version="1.7.1.2" targetFramework="net40" />
<package id="HtmlAgilityPack" version="1.4.6" targetFramework="net40" />
<package id="SharpZipLib" version="0.86.0" targetFramework="net40" />
<package id="Tidy.Net" version="1.0.0" targetFramework="net40" />
+2 -2
View File
@@ -104,9 +104,9 @@
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="ClientDependency.Core, Version=1.8.2.1, Culture=neutral, processorArchitecture=MSIL">
<Reference Include="ClientDependency.Core">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\ClientDependency.1.8.2.1\lib\net40\ClientDependency.Core.dll</HintPath>
<HintPath>..\packages\ClientDependency.1.7.1.2\lib\ClientDependency.Core.dll</HintPath>
</Reference>
<Reference Include="HtmlAgilityPack, Version=1.4.6.0, Culture=neutral, PublicKeyToken=bd319b19eaf3b43a, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
+1 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ClientDependency" version="1.8.2.1" targetFramework="net40" />
<package id="ClientDependency" version="1.7.1.2" targetFramework="net40" />
</packages>
+3 -3
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -66,9 +66,9 @@
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="ClientDependency.Core, Version=1.8.2.1, Culture=neutral, processorArchitecture=MSIL">
<Reference Include="ClientDependency.Core">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\ClientDependency.1.8.2.1\lib\net40\ClientDependency.Core.dll</HintPath>
<HintPath>..\packages\ClientDependency.1.7.1.2\lib\ClientDependency.Core.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.configuration" />
+1 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ClientDependency" version="1.8.2.1" targetFramework="net40" />
<package id="ClientDependency" version="1.7.1.2" targetFramework="net40" />
</packages>
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<ProjectType>Local</ProjectType>
@@ -112,9 +112,9 @@
<Project>{651E1350-91B6-44B7-BD60-7207006D7003}</Project>
<Name>Umbraco.Web</Name>
</ProjectReference>
<Reference Include="ClientDependency.Core, Version=1.8.2.1, Culture=neutral, processorArchitecture=MSIL">
<Reference Include="ClientDependency.Core">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\ClientDependency.1.8.2.1\lib\net40\ClientDependency.Core.dll</HintPath>
<HintPath>..\packages\ClientDependency.1.7.1.2\lib\ClientDependency.Core.dll</HintPath>
</Reference>
<Reference Include="System">
<Name>System</Name>
+1 -1
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ClientDependency" version="1.8.2.1" targetFramework="net40" />
<package id="ClientDependency" version="1.7.1.2" targetFramework="net40" />
<package id="Microsoft.ApplicationBlocks.Data" version="1.0.1559.20655" targetFramework="net40" />
</packages>
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<ProjectType>Local</ProjectType>
@@ -104,9 +104,9 @@
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="ClientDependency.Core, Version=1.8.2.1, Culture=neutral, processorArchitecture=MSIL">
<Reference Include="ClientDependency.Core">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\ClientDependency.1.8.2.1\lib\net40\ClientDependency.Core.dll</HintPath>
<HintPath>..\packages\ClientDependency.1.7.1.2\lib\ClientDependency.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ApplicationBlocks.Data, Version=1.0.1559.20655, Culture=neutral">
<SpecificVersion>False</SpecificVersion>