Compare commits

...

10 Commits

Author SHA1 Message Date
Bjarke Berg 6190e2462a Review fixes 2019-08-07 10:10:46 +02:00
Bjarke Berg 69fb17a154 Added localizations of reasons 2019-08-07 09:57:37 +02:00
Bjarke Berg 90be3cb6c7 Fix test 2019-08-06 13:19:43 +02:00
Bjarke Berg c4b81a6273 Added localizations of reasons 2019-08-05 15:26:07 +02:00
Bjarke Berg 4b9c58f88f Added syntax based default isolation level + handle retries in UI 2019-08-05 07:09:43 +02:00
Bjarke Berg 4abb904389 Added spy functionality fix for Sql Ce 2019-07-31 10:08:47 +02:00
Bjarke Berg 291c30b5a1 Added spy functionality 2019-07-31 09:01:05 +02:00
Bjarke Berg 60183fe899 Added spy functionality 2019-07-31 08:50:18 +02:00
Bjarke Berg 61f27574b6 Just return the root, if the groups contains the root. 2019-07-31 08:14:24 +02:00
Bjarke Berg e2c8b55b33 AB#2083 - If the start nodes is set, but is set to the root only, then don't go to the database. 2019-07-29 14:36:39 +02:00
33 changed files with 519 additions and 241 deletions
@@ -0,0 +1,16 @@
using System;
namespace Umbraco.Core.Exceptions
{
/// <summary>
/// An exception that is thrown if the a query as waited for the lock too long
/// </summary>
public class LockTimeoutException : Exception
{
public LockTimeoutException(Exception inner)
: base(string.Empty, inner)
{ }
public short Reason { get; set; }
}
}
@@ -6,6 +6,7 @@ using Umbraco.Core.Migrations.Upgrade.Common;
using Umbraco.Core.Migrations.Upgrade.V_8_0_0; using Umbraco.Core.Migrations.Upgrade.V_8_0_0;
using Umbraco.Core.Migrations.Upgrade.V_8_0_1; using Umbraco.Core.Migrations.Upgrade.V_8_0_1;
using Umbraco.Core.Migrations.Upgrade.V_8_1_0; using Umbraco.Core.Migrations.Upgrade.V_8_1_0;
using Umbraco.Core.Migrations.Upgrade.V_8_2_0;
namespace Umbraco.Core.Migrations.Upgrade namespace Umbraco.Core.Migrations.Upgrade
{ {
@@ -182,6 +183,8 @@ namespace Umbraco.Core.Migrations.Upgrade
To<RenameUserLoginDtoDateIndex>("{0372A42B-DECF-498D-B4D1-6379E907EB94}"); To<RenameUserLoginDtoDateIndex>("{0372A42B-DECF-498D-B4D1-6379E907EB94}");
To<FixContentNuCascade>("{5B1E0D93-F5A3-449B-84BA-65366B84E2D4}"); To<FixContentNuCascade>("{5B1E0D93-F5A3-449B-84BA-65366B84E2D4}");
// to 8.2.0....
To<AddLockTableColumns>("{8DC6A949-0FE4-4846-9C1D-61E49DDADFF9}");
//FINAL //FINAL
} }
} }
@@ -0,0 +1,19 @@
using System.Linq;
using Umbraco.Core.Persistence.Dtos;
namespace Umbraco.Core.Migrations.Upgrade.V_8_2_0
{
public class AddLockTableColumns : MigrationBase
{
public AddLockTableColumns(IMigrationContext context)
: base(context)
{ }
public override void Migrate()
{
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToList();
AddColumnIfNotExists<LockDto>(columns, "writeLockReasonId");
}
}
}
+2 -1
View File
@@ -56,7 +56,7 @@ namespace Umbraco.Core.Models
/// </returns> /// </returns>
internal static string[] GetUserAvatarUrls(this IUser user, IAppCache cache) internal static string[] GetUserAvatarUrls(this IUser user, IAppCache cache)
{ {
// If FIPS is required, never check the Gravatar service as it only supports MD5 hashing. // If FIPS is required, never check the Gravatar service as it only supports MD5 hashing.
// Unfortunately, if the FIPS setting is enabled on Windows, using MD5 will throw an exception // Unfortunately, if the FIPS setting is enabled on Windows, using MD5 will throw an exception
// and the website will not run. // and the website will not run.
// Also, check if the user has explicitly removed all avatars including a Gravatar, this will be possible and the value will be "none" // Also, check if the user has explicitly removed all avatars including a Gravatar, this will be possible and the value will be "none"
@@ -310,6 +310,7 @@ namespace Umbraco.Core.Models
// assume groupSn and userSn each don't contain duplicates // assume groupSn and userSn each don't contain duplicates
var asn = groupSn.Concat(userSn).Distinct().ToArray(); var asn = groupSn.Concat(userSn).Distinct().ToArray();
var paths = asn.Length > 0 var paths = asn.Length > 0
? entityService.GetAllPaths(objectType, asn).ToDictionary(x => x.Id, x => x.Path) ? entityService.GetAllPaths(objectType, asn).ToDictionary(x => x.Id, x => x.Path)
: new Dictionary<int, string>(); : new Dictionary<int, string>();
+25 -11
View File
@@ -1,62 +1,76 @@
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
namespace Umbraco.Core namespace Umbraco.Core
{ {
static partial class Constants static partial class Constants
{ {
/// <summary> /// <summary>
/// Defines lock objects. /// Defines lock objects.
/// </summary> /// </summary>
public static class Locks public static class Locks
{ {
/// <summary> /// <summary>
/// All servers. /// All servers.
/// </summary> /// </summary>
public const int Servers = -331; public const int Servers = -331;
/// <summary> /// <summary>
/// All content and media types. /// All content and media types.
/// </summary> /// </summary>
public const int ContentTypes = -332; public const int ContentTypes = -332;
/// <summary> /// <summary>
/// The entire content tree, i.e. all content items. /// The entire content tree, i.e. all content items.
/// </summary> /// </summary>
public const int ContentTree = -333; public const int ContentTree = -333;
/// <summary> /// <summary>
/// The entire media tree, i.e. all media items. /// The entire media tree, i.e. all media items.
/// </summary> /// </summary>
public const int MediaTree = -334; public const int MediaTree = -334;
/// <summary> /// <summary>
/// The entire member tree, i.e. all members. /// The entire member tree, i.e. all members.
/// </summary> /// </summary>
public const int MemberTree = -335; public const int MemberTree = -335;
/// <summary> /// <summary>
/// All media types. /// All media types.
/// </summary> /// </summary>
public const int MediaTypes = -336; public const int MediaTypes = -336;
/// <summary> /// <summary>
/// All member types. /// All member types.
/// </summary> /// </summary>
public const int MemberTypes = -337; public const int MemberTypes = -337;
/// <summary> /// <summary>
/// All domains. /// All domains.
/// </summary> /// </summary>
public const int Domains = -338; public const int Domains = -338;
/// <summary> /// <summary>
/// All key-values. /// All key-values.
/// </summary> /// </summary>
public const int KeyValues = -339; public const int KeyValues = -339;
/// <summary> /// <summary>
/// All languages. /// All languages.
/// </summary> /// </summary>
public const int Languages = -340; public const int Languages = -340;
public static class Reason
{
public const short Default = 1;
public const short MoveContentToRecycleBin = 10000;
public const short RestoreContentFromRecycleBin = 10001;
public const short EmptyRecycleBin = 10002;
public const short MoveContent = 10003;
public const short CopyContent = 10004;
public const short SortContent = 10005;
public const short DeleteContentOfTypes = 10006;
}
} }
} }
} }
@@ -1,43 +0,0 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Runtime.CompilerServices;
namespace Umbraco.Core.Persistence
{
internal static class DatabaseNodeLockExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ValidateDatabase(IUmbracoDatabase database)
{
if (database == null)
throw new ArgumentNullException("database");
if (database.GetCurrentTransactionIsolationLevel() < IsolationLevel.RepeatableRead)
throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
}
// updating a record within a repeatable-read transaction gets an exclusive lock on
// that record which will be kept until the transaction is ended, effectively locking
// out all other accesses to that record - thus obtaining an exclusive lock over the
// protected resources.
public static void AcquireLockNodeWriteLock(this IUmbracoDatabase database, int nodeId)
{
ValidateDatabase(database);
database.Execute("UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id",
new { @id = nodeId });
}
// reading a record within a repeatable-read transaction gets a shared lock on
// that record which will be kept until the transaction is ended, effectively preventing
// other write accesses to that record - thus obtaining a shared lock over the protected
// resources.
public static void AcquireLockNodeReadLock(this IUmbracoDatabase database, int nodeId)
{
ValidateDatabase(database);
database.ExecuteScalar<int>("SELECT value FROM umbracoLock WHERE id=@id",
new { @id = nodeId });
}
}
}
@@ -20,5 +20,9 @@ namespace Umbraco.Core.Persistence.Dtos
[NullSetting(NullSetting = NullSettings.NotNull)] [NullSetting(NullSetting = NullSettings.NotNull)]
[Length(64)] [Length(64)]
public string Name { get; set; } public string Name { get; set; }
[Column("writeLockReasonId")]
[NullSetting(NullSetting = NullSettings.Null)]
public short WriteLockReasonId { get; set; }
} }
} }
+2 -1
View File
@@ -1,4 +1,5 @@
using NPoco; using System.Data;
using NPoco;
using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.SqlSyntax;
@@ -0,0 +1,18 @@
using System;
using NPoco;
using Umbraco.Core.Exceptions;
namespace Umbraco.Core.Persistence.Interceptors
{
public class LockTimeoutInterceptor : IExceptionInterceptor
{
public void OnException(IDatabase database, Exception exception)
{
if (database is IUmbracoDatabase umbracoDatabase && umbracoDatabase.SqlContext.SqlSyntax.IsLockTimeoutException(exception))
{
throw new LockTimeoutException(exception);
}
}
}
}
@@ -5,6 +5,7 @@ using NPoco;
using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseAnnotations;
using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Querying;
using IsolationLevel = System.Data.IsolationLevel;
namespace Umbraco.Core.Persistence.SqlSyntax namespace Umbraco.Core.Persistence.SqlSyntax
{ {
@@ -76,6 +77,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax
string ConvertIntegerToOrderableString { get; } string ConvertIntegerToOrderableString { get; }
string ConvertDateToOrderableString { get; } string ConvertDateToOrderableString { get; }
string ConvertDecimalToOrderableString { get; } string ConvertDecimalToOrderableString { get; }
bool IsReadUncommittedSupported { get; }
IsolationLevel DefaultIsolationLevel { get; }
IEnumerable<string> GetTablesInSchema(IDatabase db); IEnumerable<string> GetTablesInSchema(IDatabase db);
IEnumerable<ColumnInfo> GetColumnsInSchema(IDatabase db); IEnumerable<ColumnInfo> GetColumnsInSchema(IDatabase db);
@@ -121,5 +124,9 @@ namespace Umbraco.Core.Persistence.SqlSyntax
/// unspecified.</para> /// unspecified.</para>
/// </remarks> /// </remarks>
bool TryGetDefaultConstraint(IDatabase db, string tableName, string columnName, out string constraintName); bool TryGetDefaultConstraint(IDatabase db, string tableName, string columnName, out string constraintName);
void ReadLock(IDatabase db, params int[] lockIds);
void WriteLock(IDatabase db, int? writeLockReasonId, params int[] lockIds);
bool IsLockTimeoutException(Exception exception);
} }
} }
@@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data.SqlServerCe;
using System.Linq; using System.Linq;
using System.Transactions;
using NPoco; using NPoco;
using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseAnnotations;
using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.DatabaseModelDefinitions;
@@ -12,11 +14,13 @@ namespace Umbraco.Core.Persistence.SqlSyntax
/// </summary> /// </summary>
public class SqlCeSyntaxProvider : MicrosoftSqlSyntaxProviderBase<SqlCeSyntaxProvider> public class SqlCeSyntaxProvider : MicrosoftSqlSyntaxProviderBase<SqlCeSyntaxProvider>
{ {
public override Sql<ISqlContext> SelectTop(Sql<ISqlContext> sql, int top) public override Sql<ISqlContext> SelectTop(Sql<ISqlContext> sql, int top)
{ {
return new Sql<ISqlContext>(sql.SqlContext, sql.SQL.Insert(sql.SQL.IndexOf(' '), " TOP " + top), sql.Arguments); return new Sql<ISqlContext>(sql.SqlContext, sql.SQL.Insert(sql.SQL.IndexOf(' '), " TOP " + top), sql.Arguments);
} }
public override bool SupportsClustered() public override bool SupportsClustered()
{ {
return false; return false;
@@ -47,6 +51,9 @@ namespace Umbraco.Core.Persistence.SqlSyntax
return indexType; return indexType;
} }
public override bool IsReadUncommittedSupported => false;
public override System.Data.IsolationLevel DefaultIsolationLevel => System.Data.IsolationLevel.RepeatableRead;
public override string GetConcat(params string[] args) public override string GetConcat(params string[] args)
{ {
return "(" + string.Join("+", args) + ")"; return "(" + string.Join("+", args) + ")";
@@ -143,6 +150,45 @@ where table_name=@0 and column_name=@1", tableName, columnName).FirstOrDefault()
return hasDefault; return hasDefault;
} }
public override void WriteLock(IDatabase db, int? writeLockReasonId, params int[] lockIds)
{
// soon as we get Database, a transaction is started
if (db.Transaction.IsolationLevel < (System.Data.IsolationLevel) IsolationLevel.RepeatableRead)
throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
db.Execute(@"SET LOCK_TIMEOUT 1800;");
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
foreach (var lockId in lockIds)
{
var i = db.Execute(@"UPDATE umbracoLock SET value = value*-1, writeLockReasonId = @writeLockReasonId WHERE id=@id", new { id = lockId, writeLockReasonId = writeLockReasonId });
if (i == 0) // ensure we are actually locking!
throw new Exception($"LockObject with id={lockId} does not exist.");
}
}
public override bool IsLockTimeoutException(Exception exception)
{
return exception is SqlCeLockTimeoutException;
}
public override void ReadLock(IDatabase db, params int[] lockIds)
{
// soon as we get Database, a transaction is started
if (db.Transaction.IsolationLevel < (System.Data.IsolationLevel) IsolationLevel.RepeatableRead)
throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
foreach (var lockId in lockIds)
{
var i = db.ExecuteScalar<int?>("SELECT value FROM umbracoLock WHERE id=@id", new { id = lockId });
if (i == null) // ensure we are actually locking!
throw new Exception($"LockObject with id={lockId} does not exist.");
}
}
public override bool DoesTableExist(IDatabase db, string tableName) public override bool DoesTableExist(IDatabase db, string tableName)
{ {
var result = var result =
@@ -2,11 +2,12 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Data; using System.Data;
using System.Data.Common; using System.Data.Common;
using System.Data.SqlClient;
using System.Linq; using System.Linq;
using NPoco; using NPoco;
using Umbraco.Core.Logging; using Umbraco.Core.Logging;
using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Scoping;
namespace Umbraco.Core.Persistence.SqlSyntax namespace Umbraco.Core.Persistence.SqlSyntax
{ {
@@ -173,6 +174,9 @@ namespace Umbraco.Core.Persistence.SqlSyntax
return items.Select(x => new Tuple<string, string, string, string>(x.TableName, x.ColumnName, x.Name, x.Definition)); return items.Select(x => new Tuple<string, string, string, string>(x.TableName, x.ColumnName, x.Name, x.Definition));
} }
public override bool IsReadUncommittedSupported => true;
public override System.Data.IsolationLevel DefaultIsolationLevel => System.Data.IsolationLevel.ReadCommitted;
public override IEnumerable<string> GetTablesInSchema(IDatabase db) public override IEnumerable<string> GetTablesInSchema(IDatabase db)
{ {
var items = db.Fetch<dynamic>("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())"); var items = db.Fetch<dynamic>("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())");
@@ -237,6 +241,44 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName)
return !constraintName.IsNullOrWhiteSpace(); return !constraintName.IsNullOrWhiteSpace();
} }
public override void WriteLock(IDatabase db, int? writeLockReasonId, params int[] lockIds)
{
// soon as we get Database, a transaction is started
if (db.Transaction.IsolationLevel < IsolationLevel.ReadCommitted)
throw new InvalidOperationException("A transaction with minimum ReadCommitted isolation level is required.");
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
foreach (var lockId in lockIds)
{
db.Execute(@"SET LOCK_TIMEOUT 1800;");
var i = db.Execute(@"UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = value*-1, writeLockReasonId = @writeLockReasonId WHERE id=@id", new { id = lockId, writeLockReasonId = writeLockReasonId });
if (i == 0) // ensure we are actually locking!
throw new Exception($"LockObject with id={lockId} does not exist.");
}
}
public override bool IsLockTimeoutException(Exception exception)
{
return exception is SqlException sqlException && sqlException.Number == 1222;
}
public override void ReadLock(IDatabase db, params int[] lockIds)
{
// soon as we get Database, a transaction is started
if (db.Transaction.IsolationLevel < IsolationLevel.ReadCommitted)
throw new InvalidOperationException("A transaction with minimum ReadCommitted isolation level is required.");
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
foreach (var lockId in lockIds)
{
var i = db.ExecuteScalar<int?>("SELECT value FROM umbracoLock WITH (REPEATABLEREAD) WHERE id=@id", new { id = lockId });
if (i == null) // ensure we are actually locking!
throw new ArgumentException($"LockObject with id={lockId} does not exist.", nameof(lockIds));
}
}
public override bool DoesTableExist(IDatabase db, string tableName) public override bool DoesTableExist(IDatabase db, string tableName)
{ {
var result = var result =
@@ -200,7 +200,10 @@ namespace Umbraco.Core.Persistence.SqlSyntax
return "NVARCHAR"; return "NVARCHAR";
} }
public abstract bool IsReadUncommittedSupported { get; }
public abstract IsolationLevel DefaultIsolationLevel { get; }
public virtual IEnumerable<string> GetTablesInSchema(IDatabase db) public virtual IEnumerable<string> GetTablesInSchema(IDatabase db)
{ {
return new List<string>(); return new List<string>();
@@ -224,6 +227,10 @@ namespace Umbraco.Core.Persistence.SqlSyntax
public abstract IEnumerable<Tuple<string, string, string, bool>> GetDefinedIndexes(IDatabase db); public abstract IEnumerable<Tuple<string, string, string, bool>> GetDefinedIndexes(IDatabase db);
public abstract bool TryGetDefaultConstraint(IDatabase db, string tableName, string columnName, out string constraintName); public abstract bool TryGetDefaultConstraint(IDatabase db, string tableName, string columnName, out string constraintName);
public abstract void ReadLock(IDatabase db, params int[] lockIds);
public abstract void WriteLock(IDatabase db, int? writeLockReasonId, params int[] lockIds);
public abstract bool IsLockTimeoutException(Exception exception);
public virtual bool DoesTableExist(IDatabase db, string tableName) public virtual bool DoesTableExist(IDatabase db, string tableName)
{ {
@@ -559,5 +566,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax
public virtual string ConvertIntegerToOrderableString => "REPLACE(STR({0}, 8), SPACE(1), '0')"; public virtual string ConvertIntegerToOrderableString => "REPLACE(STR({0}, 8), SPACE(1), '0')";
public virtual string ConvertDateToOrderableString => "CONVERT(nvarchar, {0}, 102)"; public virtual string ConvertDateToOrderableString => "CONVERT(nvarchar, {0}, 102)";
public virtual string ConvertDecimalToOrderableString => "REPLACE(STR({0}, 20, 9), SPACE(1), '0')"; public virtual string ConvertDecimalToOrderableString => "REPLACE(STR({0}, 20, 9), SPACE(1), '0')";
} }
} }
@@ -20,9 +20,6 @@ namespace Umbraco.Core.Persistence
/// </remarks> /// </remarks>
public class UmbracoDatabase : Database, IUmbracoDatabase public class UmbracoDatabase : Database, IUmbracoDatabase
{ {
// Umbraco's default isolation level is RepeatableRead
private const IsolationLevel DefaultIsolationLevel = IsolationLevel.RepeatableRead;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly RetryPolicy _connectionRetryPolicy; private readonly RetryPolicy _connectionRetryPolicy;
private readonly RetryPolicy _commandRetryPolicy; private readonly RetryPolicy _commandRetryPolicy;
@@ -38,7 +35,7 @@ namespace Umbraco.Core.Persistence
/// <para>Also used by DatabaseBuilder for creating databases and installing/upgrading.</para> /// <para>Also used by DatabaseBuilder for creating databases and installing/upgrading.</para>
/// </remarks> /// </remarks>
public UmbracoDatabase(string connectionString, ISqlContext sqlContext, DbProviderFactory provider, ILogger logger, RetryPolicy connectionRetryPolicy = null, RetryPolicy commandRetryPolicy = null) public UmbracoDatabase(string connectionString, ISqlContext sqlContext, DbProviderFactory provider, ILogger logger, RetryPolicy connectionRetryPolicy = null, RetryPolicy commandRetryPolicy = null)
: base(connectionString, sqlContext.DatabaseType, provider, DefaultIsolationLevel) : base(connectionString, sqlContext.DatabaseType, provider, sqlContext.SqlSyntax.DefaultIsolationLevel)
{ {
SqlContext = sqlContext; SqlContext = sqlContext;
@@ -54,7 +51,7 @@ namespace Umbraco.Core.Persistence
/// </summary> /// </summary>
/// <remarks>Internal for unit tests only.</remarks> /// <remarks>Internal for unit tests only.</remarks>
internal UmbracoDatabase(DbConnection connection, ISqlContext sqlContext, ILogger logger) internal UmbracoDatabase(DbConnection connection, ISqlContext sqlContext, ILogger logger)
: base(connection, sqlContext.DatabaseType, DefaultIsolationLevel) : base(connection, sqlContext.DatabaseType, sqlContext.SqlSyntax.DefaultIsolationLevel)
{ {
SqlContext = sqlContext; SqlContext = sqlContext;
_logger = logger; _logger = logger;
@@ -7,6 +7,7 @@ using NPoco.FluentMappings;
using Umbraco.Core.Exceptions; using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging; using Umbraco.Core.Logging;
using Umbraco.Core.Persistence.FaultHandling; using Umbraco.Core.Persistence.FaultHandling;
using Umbraco.Core.Persistence.Interceptors;
using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.SqlSyntax;
@@ -238,13 +239,15 @@ namespace Umbraco.Core.Persistence
// so that everything NPoco is properly cached for the lifetime of the application // so that everything NPoco is properly cached for the lifetime of the application
_pocoMappers = new NPoco.MapperCollection { new PocoMapper() }; _pocoMappers = new NPoco.MapperCollection { new PocoMapper() };
var factory = new FluentPocoDataFactory(GetPocoDataFactoryResolver); var factory = new FluentPocoDataFactory(GetPocoDataFactoryResolver);
_pocoDataFactory = factory; _pocoDataFactory = factory;
var config = new FluentConfig(xmappers => factory); var config = new FluentConfig(xmappers => factory);
// create the database factory // create the database factory
_npocoDatabaseFactory = DatabaseFactory.Config(x => x _npocoDatabaseFactory = DatabaseFactory.Config(x => x
.UsingDatabase(CreateDatabaseInstance) // creating UmbracoDatabase instances .UsingDatabase(CreateDatabaseInstance) // creating UmbracoDatabase instances
.WithFluentConfig(config)); // with proper configuration .WithFluentConfig(config) // with proper configuration
.WithInterceptor(new LockTimeoutInterceptor()));
if (_npocoDatabaseFactory == null) if (_npocoDatabaseFactory == null)
throw new NullReferenceException("The call to UmbracoDatabaseFactory.Config yielded a null UmbracoDatabaseFactory instance."); throw new NullReferenceException("The call to UmbracoDatabaseFactory.Config yielded a null UmbracoDatabaseFactory instance.");
+25
View File
@@ -1,4 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.ComponentModel;
using Umbraco.Core.Cache; using Umbraco.Core.Cache;
using Umbraco.Core.Events; using Umbraco.Core.Events;
using Umbraco.Core.Persistence; using Umbraco.Core.Persistence;
@@ -57,6 +59,29 @@ namespace Umbraco.Core.Scoping
/// Write-locks some lock objects. /// Write-locks some lock objects.
/// </summary> /// </summary>
/// <param name="lockIds">The lock object identifiers.</param> /// <param name="lockIds">The lock object identifiers.</param>
[Obsolete("Use the overload with reason")]
[Browsable(false)]
void WriteLock(params int[] lockIds); void WriteLock(params int[] lockIds);
/// <summary>
/// Write-locks some lock objects.
/// </summary>
/// <param name="writeLockReasonId">The id of the reason to take the write lock. See Constants.Locks.Reason.*</param>
/// <param name="lockIds">The lock object identifiers.</param>
void WriteLock(short writeLockReasonId, params int[] lockIds);
/// <summary>
/// Bypasses all db-locks and reads the WriteLockReasonId column on the specified locks.
/// </summary>
/// <param name="lockIds">The lock ids from the Constants.Locks.*</param>
/// <returns>A dictionary with the lock is as key, and the last started work as value. Note value can be null if we don't know or use SqlCe.</returns>
IDictionary<int, short> TrySpyLock(params int[] lockIds);
/// <summary>
/// Bypasses all db-locks and reads the WriteLockReasonId column on the specified lock.
/// </summary>
/// <param name="lockId">The lock id from the Constants.Locks.*</param>
/// <returns>The last started work. Note this can be null if we don't know or use SqlCe.</returns>
short TrySpyLock(int lockId);
} }
} }
+35 -24
View File
@@ -1,11 +1,15 @@
using System; using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data; using System.Data;
using System.Linq;
using Umbraco.Core.Cache; using Umbraco.Core.Cache;
using Umbraco.Core.Composing; using Umbraco.Core.Composing;
using Umbraco.Core.Events; using Umbraco.Core.Events;
using Umbraco.Core.IO; using Umbraco.Core.IO;
using Umbraco.Core.Logging; using Umbraco.Core.Logging;
using Umbraco.Core.Persistence; using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos;
namespace Umbraco.Core.Scoping namespace Umbraco.Core.Scoping
{ {
@@ -33,8 +37,6 @@ namespace Umbraco.Core.Scoping
private ICompletable _fscope; private ICompletable _fscope;
private IEventDispatcher _eventDispatcher; private IEventDispatcher _eventDispatcher;
private const IsolationLevel DefaultIsolationLevel = IsolationLevel.RepeatableRead;
// initializes a new scope // initializes a new scope
private Scope(ScopeProvider scopeProvider, private Scope(ScopeProvider scopeProvider,
ILogger logger, FileSystems fileSystems, Scope parent, ScopeContext scopeContext, bool detachable, ILogger logger, FileSystems fileSystems, Scope parent, ScopeContext scopeContext, bool detachable,
@@ -205,7 +207,7 @@ namespace Umbraco.Core.Scoping
{ {
if (_isolationLevel != IsolationLevel.Unspecified) return _isolationLevel; if (_isolationLevel != IsolationLevel.Unspecified) return _isolationLevel;
if (ParentScope != null) return ParentScope.IsolationLevel; if (ParentScope != null) return ParentScope.IsolationLevel;
return DefaultIsolationLevel; return Database.SqlContext.SqlSyntax.DefaultIsolationLevel;
} }
} }
@@ -235,6 +237,7 @@ namespace Umbraco.Core.Scoping
try try
{ {
_database.BeginTransaction(IsolationLevel); _database.BeginTransaction(IsolationLevel);
_database.Execute("SET LOCK_TIMEOUT 1800;");
return _database; return _database;
} }
catch catch
@@ -490,35 +493,43 @@ namespace Umbraco.Core.Scoping
/// <inheritdoc /> /// <inheritdoc />
public void ReadLock(params int[] lockIds) public void ReadLock(params int[] lockIds)
{ {
// soon as we get Database, a transaction is started Database.SqlContext.SqlSyntax.ReadLock(Database, lockIds);
}
if (Database.Transaction.IsolationLevel < IsolationLevel.RepeatableRead) /// <inheritdoc />
throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required."); [Obsolete("Use the overload with reason")]
[Browsable(false)]
public void WriteLock(params int[] lockIds)
{
WriteLock(Constants.Locks.Reason.Default, lockIds);
}
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks /// <inheritdoc />
foreach (var lockId in lockIds) public void WriteLock(short writeLockReasonId, params int[] lockIds)
{
Database.SqlContext.SqlSyntax.WriteLock(Database, writeLockReasonId, lockIds);
}
/// <inheritdoc />
public IDictionary<int, short> TrySpyLock(params int[] lockIds)
{
using (var db = _scopeProvider.DatabaseFactory.CreateDatabase())
{ {
var i = Database.ExecuteScalar<int?>("SELECT value FROM umbracoLock WHERE id=@id", new { id = lockId }); if (!db.SqlContext.SqlSyntax.IsReadUncommittedSupported)
if (i == null) // ensure we are actually locking! {
throw new Exception($"LockObject with id={lockId} does not exist."); return lockIds.ToDictionary(x => x, x=> Constants.Locks.Reason.Default);
};
return db.Fetch<LockDto>("SELECT * FROM umbracoLock WITH (NOLOCK) WHERE id IN (@lockIds)", new { lockIds })
.ToDictionary(x => x.Id, x => x.WriteLockReasonId);
} }
} }
/// <inheritdoc /> /// <inheritdoc />
public void WriteLock(params int[] lockIds) public short TrySpyLock(int lockId)
{ {
// soon as we get Database, a transaction is started return TrySpyLock(new []{lockId})[lockId];
if (Database.Transaction.IsolationLevel < IsolationLevel.RepeatableRead)
throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
foreach (var lockId in lockIds)
{
var i = Database.Execute("UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id", new { id = lockId });
if (i == 0) // ensure we are actually locking!
throw new Exception($"LockObject with id={lockId} does not exist.");
}
} }
} }
} }
@@ -32,7 +32,7 @@ namespace Umbraco.Core.Services.Implement
private IQuery<IContent> _queryNotTrashed; private IQuery<IContent> _queryNotTrashed;
//TODO: The non-lazy object should be injected //TODO: The non-lazy object should be injected
private readonly Lazy<PropertyValidationService> _propertyValidationService = new Lazy<PropertyValidationService>(() => new PropertyValidationService()); private readonly Lazy<PropertyValidationService> _propertyValidationService = new Lazy<PropertyValidationService>(() => new PropertyValidationService());
#region Constructors #region Constructors
@@ -1666,7 +1666,7 @@ namespace Umbraco.Core.Services.Implement
return OperationResult.Cancel(evtMsgs); return OperationResult.Cancel(evtMsgs);
} }
scope.WriteLock(Constants.Locks.ContentTree); scope.WriteLock(Constants.Locks.Reason.EmptyRecycleBin, Constants.Locks.ContentTree);
// if it's not trashed yet, and published, we should unpublish // if it's not trashed yet, and published, we should unpublish
// but... Unpublishing event makes no sense (not going to cancel?) and no need to save // but... Unpublishing event makes no sense (not going to cancel?) and no need to save
@@ -1790,7 +1790,7 @@ namespace Umbraco.Core.Services.Implement
using (var scope = ScopeProvider.CreateScope()) using (var scope = ScopeProvider.CreateScope())
{ {
scope.WriteLock(Constants.Locks.ContentTree); scope.WriteLock(Constants.Locks.Reason.MoveContentToRecycleBin, Constants.Locks.ContentTree);
var originalPath = content.Path; var originalPath = content.Path;
var moveEventInfo = new MoveEventInfo<IContent>(content, originalPath, Constants.System.RecycleBinContent); var moveEventInfo = new MoveEventInfo<IContent>(content, originalPath, Constants.System.RecycleBinContent);
@@ -1849,7 +1849,7 @@ namespace Umbraco.Core.Services.Implement
using (var scope = ScopeProvider.CreateScope()) using (var scope = ScopeProvider.CreateScope())
{ {
scope.WriteLock(Constants.Locks.ContentTree); scope.WriteLock(Constants.Locks.Reason.MoveContent, Constants.Locks.ContentTree);
var parent = parentId == Constants.System.Root ? null : GetById(parentId); var parent = parentId == Constants.System.Root ? null : GetById(parentId);
if (parentId != Constants.System.Root && (parent == null || parent.Trashed)) if (parentId != Constants.System.Root && (parent == null || parent.Trashed))
@@ -1969,7 +1969,7 @@ namespace Umbraco.Core.Services.Implement
using (var scope = ScopeProvider.CreateScope()) using (var scope = ScopeProvider.CreateScope())
{ {
scope.WriteLock(Constants.Locks.ContentTree); scope.WriteLock(Constants.Locks.Reason.EmptyRecycleBin, Constants.Locks.ContentTree);
// v7 EmptyingRecycleBin and EmptiedRecycleBin events are greatly simplified since // v7 EmptyingRecycleBin and EmptiedRecycleBin events are greatly simplified since
// each deleted items will have its own deleting/deleted events. so, files and such // each deleted items will have its own deleting/deleted events. so, files and such
@@ -2052,7 +2052,7 @@ namespace Umbraco.Core.Services.Implement
var copies = new List<Tuple<IContent, IContent>>(); var copies = new List<Tuple<IContent, IContent>>();
scope.WriteLock(Constants.Locks.ContentTree); scope.WriteLock(Constants.Locks.Reason.CopyContent, Constants.Locks.ContentTree);
// a copy is not published (but not really unpublishing either) // a copy is not published (but not really unpublishing either)
// update the create author and last edit author // update the create author and last edit author
@@ -2198,7 +2198,7 @@ namespace Umbraco.Core.Services.Implement
using (var scope = ScopeProvider.CreateScope()) using (var scope = ScopeProvider.CreateScope())
{ {
scope.WriteLock(Constants.Locks.ContentTree); scope.WriteLock(Constants.Locks.Reason.SortContent, Constants.Locks.ContentTree);
var ret = Sort(scope, itemsA, userId, evtMsgs, raiseEvents); var ret = Sort(scope, itemsA, userId, evtMsgs, raiseEvents);
scope.Complete(); scope.Complete();
@@ -2227,7 +2227,7 @@ namespace Umbraco.Core.Services.Implement
using (var scope = ScopeProvider.CreateScope()) using (var scope = ScopeProvider.CreateScope())
{ {
scope.WriteLock(Constants.Locks.ContentTree); scope.WriteLock(Constants.Locks.Reason.SortContent, Constants.Locks.ContentTree);
var itemsA = GetByIds(idsA).ToArray(); var itemsA = GetByIds(idsA).ToArray();
var ret = Sort(scope, itemsA, userId, evtMsgs, raiseEvents); var ret = Sort(scope, itemsA, userId, evtMsgs, raiseEvents);
@@ -2729,7 +2729,7 @@ namespace Umbraco.Core.Services.Implement
// //
using (var scope = ScopeProvider.CreateScope()) using (var scope = ScopeProvider.CreateScope())
{ {
scope.WriteLock(Constants.Locks.ContentTree); scope.WriteLock(Constants.Locks.Reason.DeleteContentOfTypes,Constants.Locks.ContentTree);
var query = Query<IContent>().WhereIn(x => x.ContentTypeId, contentTypeIdsA); var query = Query<IContent>().WhereIn(x => x.ContentTypeId, contentTypeIdsA);
var contents = _documentRepository.Get(query).ToArray(); var contents = _documentRepository.Get(query).ToArray();
@@ -3028,6 +3028,6 @@ namespace Umbraco.Core.Services.Implement
#endregion #endregion
} }
} }
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using Umbraco.Core.Events; using Umbraco.Core.Events;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging; using Umbraco.Core.Logging;
using Umbraco.Core.Models; using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Entities;
@@ -257,20 +258,38 @@ namespace Umbraco.Core.Services.Implement
/// <inheritdoc /> /// <inheritdoc />
public virtual IEnumerable<IEntitySlim> GetChildren(int parentId) public virtual IEnumerable<IEntitySlim> GetChildren(int parentId)
{ {
using (ScopeProvider.CreateScope(autoComplete: true)) using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{ {
var query = Query<IUmbracoEntity>().Where(x => x.ParentId == parentId); try
return _entityRepository.GetByQuery(query); {
var query = Query<IUmbracoEntity>().Where(x => x.ParentId == parentId);
return _entityRepository.GetByQuery(query);
}
catch (LockTimeoutException e)
{
e.Reason = scope.TrySpyLock(Constants.Locks.ContentTree);
throw;
}
} }
} }
/// <inheritdoc /> /// <inheritdoc />
public virtual IEnumerable<IEntitySlim> GetChildren(int parentId, UmbracoObjectTypes objectType) public virtual IEnumerable<IEntitySlim> GetChildren(int parentId, UmbracoObjectTypes objectType)
{ {
using (ScopeProvider.CreateScope(autoComplete: true)) using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{ {
var query = Query<IUmbracoEntity>().Where(x => x.ParentId == parentId); try
return _entityRepository.GetByQuery(query, objectType.GetGuid()); {
var query = Query<IUmbracoEntity>().Where(x => x.ParentId == parentId);
return _entityRepository.GetByQuery(query, objectType.GetGuid());
}
catch (LockTimeoutException e)
{
e.Reason = scope.TrySpyLock(Constants.Locks.ContentTree);
throw;
}
} }
} }
@@ -18,6 +18,11 @@ namespace Umbraco.Core.Services
return manager.Localize(fullKey, Thread.CurrentThread.CurrentUICulture); return manager.Localize(fullKey, Thread.CurrentThread.CurrentUICulture);
} }
public static string LocalizeLockReason(this ILocalizedTextService manager, short writeLockReasonId)
{
return manager.Localize($"writeLockReasons/Code_{writeLockReasonId}", Thread.CurrentThread.CurrentUICulture);
}
/// <summary> /// <summary>
/// Localize using the current thread culture /// Localize using the current thread culture
/// </summary> /// </summary>
+3 -1
View File
@@ -204,6 +204,7 @@
<Compile Include="CompositionExtensions_Essentials.cs" /> <Compile Include="CompositionExtensions_Essentials.cs" />
<Compile Include="CompositionExtensions_FileSystems.cs" /> <Compile Include="CompositionExtensions_FileSystems.cs" />
<Compile Include="CompositionExtensions_Uniques.cs" /> <Compile Include="CompositionExtensions_Uniques.cs" />
<Compile Include="Exceptions\LockTimeoutException.cs" />
<Compile Include="FactoryExtensions.cs" /> <Compile Include="FactoryExtensions.cs" />
<Compile Include="Composing\RegisterFactory.cs" /> <Compile Include="Composing\RegisterFactory.cs" />
<Compile Include="Composing\Current.cs" /> <Compile Include="Composing\Current.cs" />
@@ -243,6 +244,7 @@
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\RichTextPreValueMigrator.cs" /> <Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\RichTextPreValueMigrator.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\DropDownFlexiblePreValueMigrator.cs" /> <Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\DropDownFlexiblePreValueMigrator.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\MergeDateAndDateTimePropertyEditor.cs" /> <Compile Include="Migrations\Upgrade\V_8_0_0\MergeDateAndDateTimePropertyEditor.cs" />
<Compile Include="Migrations\Upgrade\V_8_2_0\AddLockTableColumns.cs" />
<Compile Include="Models\Entities\EntityExtensions.cs" /> <Compile Include="Models\Entities\EntityExtensions.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\PreValueMigratorBase.cs" /> <Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\PreValueMigratorBase.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\PreValueDto.cs" /> <Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\PreValueDto.cs" />
@@ -265,6 +267,7 @@
<Compile Include="Models\PublishedContent\ILivePublishedModelFactory.cs" /> <Compile Include="Models\PublishedContent\ILivePublishedModelFactory.cs" />
<Compile Include="Models\PublishedContent\IPublishedContentType.cs" /> <Compile Include="Models\PublishedContent\IPublishedContentType.cs" />
<Compile Include="Models\PublishedContent\IPublishedPropertyType.cs" /> <Compile Include="Models\PublishedContent\IPublishedPropertyType.cs" />
<Compile Include="Persistence\Interceptors\LockTimeoutInterceptor.cs" />
<Compile Include="PropertyEditors\IIgnoreUserStartNodesConfig.cs" /> <Compile Include="PropertyEditors\IIgnoreUserStartNodesConfig.cs" />
<Compile Include="PublishedContentExtensions.cs" /> <Compile Include="PublishedContentExtensions.cs" />
<Compile Include="Models\PublishedContent\UrlMode.cs" /> <Compile Include="Models\PublishedContent\UrlMode.cs" />
@@ -979,7 +982,6 @@
<Compile Include="Persistence\DatabaseModelDefinitions\ModificationType.cs" /> <Compile Include="Persistence\DatabaseModelDefinitions\ModificationType.cs" />
<Compile Include="Persistence\DatabaseModelDefinitions\SystemMethods.cs" /> <Compile Include="Persistence\DatabaseModelDefinitions\SystemMethods.cs" />
<Compile Include="Persistence\DatabaseModelDefinitions\TableDefinition.cs" /> <Compile Include="Persistence\DatabaseModelDefinitions\TableDefinition.cs" />
<Compile Include="Persistence\DatabaseNodeLockExtensions.cs" />
<Compile Include="Persistence\DbCommandExtensions.cs" /> <Compile Include="Persistence\DbCommandExtensions.cs" />
<Compile Include="Persistence\DbConnectionExtensions.cs" /> <Compile Include="Persistence\DbConnectionExtensions.cs" />
<Compile Include="Persistence\EntityNotFoundException.cs" /> <Compile Include="Persistence\EntityNotFoundException.cs" />
@@ -56,6 +56,8 @@ namespace Umbraco.Tests.Models
[TestCase("1,-1", "1", "1")] // was an issue [TestCase("1,-1", "1", "1")] // was an issue
[TestCase("-1,1", "1", "1")] // was an issue [TestCase("-1,1", "1", "1")] // was an issue
[TestCase("-1", "", "-1")]
[TestCase("", "-1", "-1")]
public void CombineStartNodes(string groupSn, string userSn, string expected) public void CombineStartNodes(string groupSn, string userSn, string expected)
{ {
+9 -8
View File
@@ -5,6 +5,7 @@ using System.Threading;
using NPoco; using NPoco;
using NUnit.Framework; using NUnit.Framework;
using Umbraco.Core; using Umbraco.Core;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Persistence; using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Dtos;
using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers;
@@ -37,7 +38,7 @@ namespace Umbraco.Tests.Persistence
{ {
using (var scope = ScopeProvider.CreateScope()) using (var scope = ScopeProvider.CreateScope())
{ {
scope.Database.AcquireLockNodeReadLock(Constants.Locks.Servers); scope.WriteLock(Constants.Locks.Reason.Default, Constants.Locks.Servers);
scope.Complete(); scope.Complete();
} }
} }
@@ -62,7 +63,7 @@ namespace Umbraco.Tests.Persistence
{ {
try try
{ {
scope.Database.AcquireLockNodeReadLock(Constants.Locks.Servers); scope.ReadLock(Constants.Locks.Servers);
lock (locker) lock (locker)
{ {
acquired++; acquired++;
@@ -131,7 +132,7 @@ namespace Umbraco.Tests.Persistence
if (entered == threadCount) m1.Set(); if (entered == threadCount) m1.Set();
} }
ms[ic].WaitOne(); ms[ic].WaitOne();
scope.Database.AcquireLockNodeWriteLock(Constants.Locks.Servers); scope.WriteLock(Constants.Locks.Reason.Default,Constants.Locks.Servers);
lock (locker) lock (locker)
{ {
acquired++; acquired++;
@@ -201,7 +202,7 @@ namespace Umbraco.Tests.Persistence
thread2.Join(); thread2.Join();
Assert.IsNotNull(e1); Assert.IsNotNull(e1);
Assert.IsInstanceOf<SqlCeLockTimeoutException>(e1); Assert.IsInstanceOf<LockTimeoutException>(e1);
// the assertion below depends on timing conditions - on a fast enough environment, // the assertion below depends on timing conditions - on a fast enough environment,
// thread1 dies (deadlock) and frees thread2, which succeeds - however on a slow // thread1 dies (deadlock) and frees thread2, which succeeds - however on a slow
@@ -210,7 +211,7 @@ namespace Umbraco.Tests.Persistence
// //
//Assert.IsNull(e2); //Assert.IsNull(e2);
if (e2 != null) if (e2 != null)
Assert.IsInstanceOf<SqlCeLockTimeoutException>(e2); Assert.IsInstanceOf<LockTimeoutException>(e2);
} }
private void DeadLockTestThread(int id1, int id2, EventWaitHandle myEv, WaitHandle otherEv, ref Exception exception) private void DeadLockTestThread(int id1, int id2, EventWaitHandle myEv, WaitHandle otherEv, ref Exception exception)
@@ -221,7 +222,7 @@ namespace Umbraco.Tests.Persistence
{ {
otherEv.WaitOne(); otherEv.WaitOne();
Console.WriteLine($"[{id1}] WAIT {id1}"); Console.WriteLine($"[{id1}] WAIT {id1}");
scope.Database.AcquireLockNodeWriteLock(id1); scope.WriteLock(Constants.Locks.Reason.Default,id1);
Console.WriteLine($"[{id1}] GRANT {id1}"); Console.WriteLine($"[{id1}] GRANT {id1}");
WriteLocks(scope.Database); WriteLocks(scope.Database);
myEv.Set(); myEv.Set();
@@ -232,7 +233,7 @@ namespace Umbraco.Tests.Persistence
Thread.Sleep(200); // cannot wait due to deadlock... just give it a bit of time Thread.Sleep(200); // cannot wait due to deadlock... just give it a bit of time
Console.WriteLine($"[{id1}] WAIT {id2}"); Console.WriteLine($"[{id1}] WAIT {id2}");
scope.Database.AcquireLockNodeWriteLock(id2); scope.WriteLock(Constants.Locks.Reason.Default,id2);
Console.WriteLine($"[{id1}] GRANT {id2}"); Console.WriteLine($"[{id1}] GRANT {id2}");
WriteLocks(scope.Database); WriteLocks(scope.Database);
} }
@@ -284,7 +285,7 @@ namespace Umbraco.Tests.Persistence
{ {
otherEv.WaitOne(); otherEv.WaitOne();
Console.WriteLine($"[{id}] WAIT {id}"); Console.WriteLine($"[{id}] WAIT {id}");
scope.Database.AcquireLockNodeWriteLock(id); scope.WriteLock(Constants.Locks.Reason.Default, id);
Console.WriteLine($"[{id}] GRANT {id}"); Console.WriteLine($"[{id}] GRANT {id}");
WriteLocks(scope.Database); WriteLocks(scope.Database);
myEv.Set(); myEv.Set();
+25 -44
View File
@@ -1222,7 +1222,7 @@
"arraybuffer.slice": { "arraybuffer.slice": {
"version": "0.0.7", "version": "0.0.7",
"resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
"integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", "integrity": "sha1-O7xCdd1YTMGxCAm4nU6LY6aednU=",
"dev": true "dev": true
}, },
"asap": { "asap": {
@@ -1284,7 +1284,7 @@
"async-limiter": { "async-limiter": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", "integrity": "sha1-ePrtjD0HSrgfIrTphdeehzj3IPg=",
"dev": true "dev": true
}, },
"asynckit": { "asynckit": {
@@ -1535,7 +1535,7 @@
}, },
"bl": { "bl": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
"integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==",
"dev": true, "dev": true,
"requires": { "requires": {
@@ -2516,7 +2516,7 @@
"content-type": { "content-type": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=",
"dev": true "dev": true
}, },
"continuable-cache": { "continuable-cache": {
@@ -4045,7 +4045,7 @@
"debug": { "debug": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=",
"dev": true, "dev": true,
"requires": { "requires": {
"ms": "2.0.0" "ms": "2.0.0"
@@ -4081,7 +4081,7 @@
"debug": { "debug": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=",
"dev": true, "dev": true,
"requires": { "requires": {
"ms": "2.0.0" "ms": "2.0.0"
@@ -4476,7 +4476,7 @@
"eventemitter3": { "eventemitter3": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz",
"integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==", "integrity": "sha1-CQtNbNvWRe0Qv3UNS1QHlC17oWM=",
"dev": true "dev": true
}, },
"exec-buffer": { "exec-buffer": {
@@ -5309,8 +5309,7 @@
"ansi-regex": { "ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"aproba": { "aproba": {
"version": "1.2.0", "version": "1.2.0",
@@ -5331,14 +5330,12 @@
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@@ -5353,20 +5350,17 @@
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@@ -5483,8 +5477,7 @@
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@@ -5496,7 +5489,6 @@
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@@ -5511,7 +5503,6 @@
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
@@ -5519,14 +5510,12 @@
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.2.4", "version": "2.2.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.1", "safe-buffer": "^5.1.1",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@@ -5545,7 +5534,6 @@
"version": "0.5.1", "version": "0.5.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
} }
@@ -5626,8 +5614,7 @@
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@@ -5639,7 +5626,6 @@
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@@ -5725,8 +5711,7 @@
"safe-buffer": { "safe-buffer": {
"version": "5.1.1", "version": "5.1.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"safer-buffer": { "safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
@@ -5762,7 +5747,6 @@
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",
@@ -5782,7 +5766,6 @@
"version": "3.0.1", "version": "3.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-regex": "^2.0.0"
} }
@@ -5826,14 +5809,12 @@
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"yallist": { "yallist": {
"version": "3.0.2", "version": "3.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
} }
} }
}, },
@@ -7575,7 +7556,7 @@
"has-binary2": { "has-binary2": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz",
"integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", "integrity": "sha1-d3asYn8+p3JQz8My2rfd9eT10R0=",
"dev": true, "dev": true,
"requires": { "requires": {
"isarray": "2.0.1" "isarray": "2.0.1"
@@ -7778,7 +7759,7 @@
"http-proxy": { "http-proxy": {
"version": "1.17.0", "version": "1.17.0",
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz",
"integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", "integrity": "sha1-etOElGWPhGBeL220Q230EPTlvpo=",
"dev": true, "dev": true,
"requires": { "requires": {
"eventemitter3": "^3.0.0", "eventemitter3": "^3.0.0",
@@ -13616,7 +13597,7 @@
"qjobs": { "qjobs": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz",
"integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", "integrity": "sha1-xF6cYYAL0IfviNfiVkI73Unl0HE=",
"dev": true "dev": true
}, },
"qs": { "qs": {
@@ -14787,7 +14768,7 @@
"debug": { "debug": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=",
"dev": true, "dev": true,
"requires": { "requires": {
"ms": "2.0.0" "ms": "2.0.0"
@@ -15860,7 +15841,7 @@
"type-is": { "type-is": {
"version": "1.6.16", "version": "1.6.16",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
"integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", "integrity": "sha1-+JzjQVQcZysl7nrjxz3uOyvlAZQ=",
"dev": true, "dev": true,
"requires": { "requires": {
"media-typer": "0.3.0", "media-typer": "0.3.0",
@@ -15902,7 +15883,7 @@
"ultron": { "ultron": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
"integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", "integrity": "sha1-n+FTahCmZKZSZqHjzPhf02MCvJw=",
"dev": true "dev": true
}, },
"unc-path-regex": { "unc-path-regex": {
@@ -16505,7 +16486,7 @@
"ws": { "ws": {
"version": "3.3.3", "version": "3.3.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz",
"integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", "integrity": "sha1-8c+E/i1ekB686U767OeF8YeiKPI=",
"dev": true, "dev": true,
"requires": { "requires": {
"async-limiter": "~1.0.0", "async-limiter": "~1.0.0",
@@ -3,7 +3,7 @@
* @name umbraco.directives.directive:umbTree * @name umbraco.directives.directive:umbTree
* @restrict E * @restrict E
**/ **/
function umbTreeDirective($q, $rootScope, treeService, notificationsService, userService) { function umbTreeDirective($q, $rootScope, treeService, notificationsService, userService, $timeout) {
return { return {
restrict: 'E', restrict: 'E',
@@ -234,6 +234,8 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use
/** Method to load in the tree data */ /** Method to load in the tree data */
function loadTree() { function loadTree() {
if ($scope.section) { if ($scope.section) {
//default args //default args
@@ -243,33 +245,52 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use
if ($scope.customtreeparams) { if ($scope.customtreeparams) {
args["queryString"] = $scope.customtreeparams; args["queryString"] = $scope.customtreeparams;
} }
return treeService.getTree(args) $scope.isLoading = true;
.then(function (data) { $scope.isLoadingReason = "";
//Only use the tree data, if we are still on the correct section
if(data.alias !== $scope.section){ return getTree(args);
return $q.reject();
}
//set the data once we have it
$scope.tree = data;
//set the root as the current active tree
$scope.activeTree = $scope.tree.root;
emitEvent("treeLoaded", { tree: $scope.tree });
emitEvent("treeNodeExpanded", { tree: $scope.tree, node: $scope.tree.root, children: $scope.tree.root.children });
return $q.when(data);
}, function (reason) {
notificationsService.error("Tree Error", reason);
return $q.reject(reason);
});
} }
else { else {
return $q.reject(); return $q.reject();
} }
} }
function getTree(args){
return treeService.getTree(args)
.then(function (data) {
//Only use the tree data, if we are still on the correct section
if(data.alias !== $scope.section){
return $q.reject();
}
//set the data once we have it
$scope.tree = data;
//set the root as the current active tree
$scope.activeTree = $scope.tree.root;
emitEvent("treeLoaded", { tree: $scope.tree });
emitEvent("treeNodeExpanded", { tree: $scope.tree, node: $scope.tree.root, children: $scope.tree.root.children });
$scope.isLoading = false;
$scope.isLoadingReason = "";
return $q.when(data);
}, function (reason) {
if(reason.status === 409){
$scope.isLoading = true;
$scope.isLoadingReason = reason.data;
$timeout(function() { $q.when(getTree(args));}, 5000);
}else{
notificationsService.error("Tree Error", reason);
return $q.reject(reason);
}
});
}
function loadChildren(node, forceReload) { function loadChildren(node, forceReload) {
//emit treeNodeExpanding event, if a callback object is set on the tree //emit treeNodeExpanding event, if a callback object is set on the tree
@@ -1,53 +1,62 @@
<ul class="umb-tree" ng-class="{'hide-options': hideoptions === 'true'}"> <div>
<div ng-if="isLoading">
<umb-load-indicator></umb-load-indicator>
<div style="position: absolute; top: 50%; padding-top: 30px; text-align: center; width: 76%; padding-left: 13%;">{{isLoadingReason}}</div>
</div>
<li ng-if="!tree.root.containsGroups"> <div ng-if="!isLoading">
<div class="umb-tree-root" data-element="tree-root" ng-class="getNodeCssClass(tree.root)" ng-hide="hideheader === 'true'" on-right-click="altSelect(tree.root, $event)"> <ul class="umb-tree" ng-class="{'hide-options': hideoptions === 'true'}">
<h5>
<a ng-href="#/{{section}}" ng-click="select(tree.root, $event)" class="umb-tree-root-link umb-outline" data-element="tree-root-link">
<i ng-if="enablecheckboxes === 'true'" ng-class="selectEnabledNodeClass(tree.root)"></i>
{{tree.name}}
</a>
</h5>
<button data-element="tree-item-options" class="umb-options btn-reset sr-only sr-only--focusable sr-only--hoverable" ng-hide="tree.root.isContainer || !tree.root.menuUrl" ng-click="options(tree.root, $event)" ng-swipe-right="options(tree.root, $event)">
<i></i><i></i><i></i>
</button>
</div>
<umb-tree-item class="umb-animated"
ng-repeat="child in tree.root.children"
enablelistviewexpand="{{enablelistviewexpand}}"
node="child"
current-node="currentNode"
tree="this"
is-dialog="isdialog"
section="{{section}}">
</umb-tree-item>
</li>
<!-- REPEAT for each group in the tree --> <li ng-if="!tree.root.containsGroups">
<li ng-if="tree.root.containsGroups" <div class="umb-tree-root" data-element="tree-root" ng-class="getNodeCssClass(tree.root)" ng-hide="hideheader === 'true'" on-right-click="altSelect(tree.root, $event)">
ng-repeat="group in tree.root.children"> <h5>
<a ng-href="#/{{section}}" ng-click="select(tree.root, $event)" class="umb-tree-root-link umb-outline" data-element="tree-root-link">
<i ng-if="enablecheckboxes === 'true'" ng-class="selectEnabledNodeClass(tree.root)"></i>
{{tree.name}}
</a>
</h5>
<button data-element="tree-item-options" class="umb-options btn-reset sr-only sr-only--focusable sr-only--hoverable" ng-hide="tree.root.isContainer || !tree.root.menuUrl" ng-click="options(tree.root, $event)" ng-swipe-right="options(tree.root, $event)">
<i></i><i></i><i></i>
</button>
</div>
<umb-tree-item class="umb-animated"
ng-repeat="child in tree.root.children"
enablelistviewexpand="{{enablelistviewexpand}}"
node="child"
current-node="currentNode"
tree="this"
is-dialog="isdialog"
section="{{section}}">
</umb-tree-item>
</li>
<div class="umb-tree-root" data-element="tree-root" ng-class="getNodeCssClass(group)" ng-hide="hideheader === 'true'" on-right-click="altSelect(group, $event)"> <!-- REPEAT for each group in the tree -->
<h5> <li ng-if="tree.root.containsGroups"
<a ng-href="#/{{section}}" ng-click="select(group, $event)" class="umb-tree-root-link umb-outline" data-element="tree-root-link"> ng-repeat="group in tree.root.children">
<i ng-if="enablecheckboxes === 'true'" ng-class="selectEnabledNodeClass(group)"></i>
{{group.name}}
</a>
</h5>
<button data-element="tree-item-options" class="umb-options umb-outline btn-reset sr-only sr-only--focusable sr-only--hoverable" ng-hide="group.isContainer || !group.menuUrl" ng-click="options(group, $event)" ng-swipe-right="options(group, $event)">
<i></i><i></i><i></i>
</button>
</div>
<umb-tree-item class="umb-animated" <div class="umb-tree-root" data-element="tree-root" ng-class="getNodeCssClass(group)" ng-hide="hideheader === 'true'" on-right-click="altSelect(group, $event)">
ng-repeat="child in group.children" <h5>
enablelistviewexpand="{{enablelistviewexpand}}" <a ng-href="#/{{section}}" ng-click="select(group, $event)" class="umb-tree-root-link umb-outline" data-element="tree-root-link">
node="child" <i ng-if="enablecheckboxes === 'true'" ng-class="selectEnabledNodeClass(group)"></i>
current-node="currentNode" {{group.name}}
tree="this" </a>
is-dialog="isdialog" </h5>
section="{{section}}"> <button data-element="tree-item-options" class="umb-options umb-outline btn-reset sr-only sr-only--focusable sr-only--hoverable" ng-hide="group.isContainer || !group.menuUrl" ng-click="options(group, $event)" ng-swipe-right="options(group, $event)">
</umb-tree-item> <i></i><i></i><i></i>
</li> </button>
<!-- END REPEAT --> </div>
</ul>
<umb-tree-item class="umb-animated"
ng-repeat="child in group.children"
enablelistviewexpand="{{enablelistviewexpand}}"
node="child"
current-node="currentNode"
tree="this"
is-dialog="isdialog"
section="{{section}}">
</umb-tree-item>
</li>
<!-- END REPEAT -->
</ul>
</div>
</div>
+16 -6
View File
@@ -263,7 +263,7 @@
<key alias="scheduledPublishDocumentation"><![CDATA[<a href="https://our.umbraco.com/documentation/Getting-Started/Data/Scheduled-Publishing/#timezones" target="_blank">Hvad betyder det?</a>]]></key> <key alias="scheduledPublishDocumentation"><![CDATA[<a href="https://our.umbraco.com/documentation/Getting-Started/Data/Scheduled-Publishing/#timezones" target="_blank">Hvad betyder det?</a>]]></key>
<key alias="nestedContentDeleteItem">Er du sikker på, at du vil slette dette element?</key> <key alias="nestedContentDeleteItem">Er du sikker på, at du vil slette dette element?</key>
<key alias="nestedContentEditorNotSupported">Egenskaben %0% anvender editoren %1% som ikke er understøttet af Nested Content.</key> <key alias="nestedContentEditorNotSupported">Egenskaben %0% anvender editoren %1% som ikke er understøttet af Nested Content.</key>
<key alias="nestedContentNoContentTypes">Der er ikke konfigureret nogen indholdstyper for denne egenskab.</key> <key alias="nestedContentNoContentTypes">Der er ikke konfigureret nogen indholdstyper for denne egenskab.</key>
<key alias="addTextBox">Tilføj en ny tekstboks</key> <key alias="addTextBox">Tilføj en ny tekstboks</key>
<key alias="removeTextBox">Fjern denne tekstboks</key> <key alias="removeTextBox">Fjern denne tekstboks</key>
<key alias="contentRoot">Indholdsrod</key> <key alias="contentRoot">Indholdsrod</key>
@@ -284,8 +284,8 @@
<key alias="readyToPublish">Klar til at udgive?</key> <key alias="readyToPublish">Klar til at udgive?</key>
<key alias="readyToSave">Klar til at gemme?</key> <key alias="readyToSave">Klar til at gemme?</key>
<key alias="sendForApproval">Send til godkendelse</key> <key alias="sendForApproval">Send til godkendelse</key>
<key alias="schedulePublishHelp">Vælg dato og klokkeslæt for at udgive og/eller afpublicere indholdet.</key> <key alias="schedulePublishHelp">Vælg dato og klokkeslæt for at udgive og/eller afpublicere indholdet.</key>
<key alias="createEmpty">Opret ny</key> <key alias="createEmpty">Opret ny</key>
<key alias="createFromClipboard">Indsæt fra udklipsmappen</key> <key alias="createFromClipboard">Indsæt fra udklipsmappen</key>
</area> </area>
<area alias="blueprints"> <area alias="blueprints">
@@ -1571,8 +1571,8 @@ Mange hilsner fra Umbraco robotten
<area alias="emptyStates"> <area alias="emptyStates">
<key alias="emptyDictionaryTree">Ingen ordbog elementer at vælge imellem</key> <key alias="emptyDictionaryTree">Ingen ordbog elementer at vælge imellem</key>
</area> </area>
<area alias="textbox"> <area alias="textbox">
<key alias="characters_left"><![CDATA[<strong>%0%</strong> tegn tilbage.]]></key> <key alias="characters_left"><![CDATA[<strong>%0%</strong> tegn tilbage.]]></key>
<key alias="characters_exceed"><![CDATA[Maksimum %0% tegn, <strong>%1%</strong> for mange.]]></key> <key alias="characters_exceed"><![CDATA[Maksimum %0% tegn, <strong>%1%</strong> for mange.]]></key>
</area> </area>
<area alias="recycleBin"> <area alias="recycleBin">
@@ -1606,8 +1606,18 @@ Mange hilsner fra Umbraco robotten
<key alias="settingsPublishedStatus">Published Cache</key> <key alias="settingsPublishedStatus">Published Cache</key>
<key alias="settingsModelsBuilder">Models Builder</key> <key alias="settingsModelsBuilder">Models Builder</key>
<key alias="settingsHealthCheck">Health Check</key> <key alias="settingsHealthCheck">Health Check</key>
<key alias="settingsProfiler">Profiling</key> <key alias="settingsProfiler">Profiling</key>
<key alias="memberIntro">Kom godt i gang</key> <key alias="memberIntro">Kom godt i gang</key>
<key alias="formsInstall">Installer Umbraco Forms</key> <key alias="formsInstall">Installer Umbraco Forms</key>
</area> </area>
<area alias="writeLockReasons">
<key alias="Code_1">Langvaring operation</key>
<key alias="Code_10000">Flytter indhold til papirkurven</key>
<key alias="Code_10001">Genskaber indhold fra papirkurven</key>
<key alias="Code_10002">Sletter indhold fra papirkurven</key>
<key alias="Code_10003">Flytter indhold</key>
<key alias="Code_10004">Kopier indhold</key>
<key alias="Code_10005">Sorterer indhold</key>
<key alias="Code_10006">Sletter alt indhold af en type</key>
</area>
</language> </language>
+14 -4
View File
@@ -334,7 +334,7 @@
<key alias="noDocumentTypesEditPermissions">Edit permissions for this document type</key> <key alias="noDocumentTypesEditPermissions">Edit permissions for this document type</key>
<key alias="noMediaTypes" version="7.0"><![CDATA[There are no allowed media types available for creating media here. You must enable these in <strong>Media Types Types</strong> within the <strong>Settings</strong> section, by editing the <strong>Allowed child node types</strong> under <strong>Permissions</strong>.]]></key> <key alias="noMediaTypes" version="7.0"><![CDATA[There are no allowed media types available for creating media here. You must enable these in <strong>Media Types Types</strong> within the <strong>Settings</strong> section, by editing the <strong>Allowed child node types</strong> under <strong>Permissions</strong>.]]></key>
<key alias="noMediaTypesWithNoSettingsAccess">The selected media in the tree doesn't allow for any other media to be created below it.</key> <key alias="noMediaTypesWithNoSettingsAccess">The selected media in the tree doesn't allow for any other media to be created below it.</key>
<key alias="noMediaTypesEditPermissions">Edit permissions for this media type</key> <key alias="noMediaTypesEditPermissions">Edit permissions for this media type</key>
<key alias="documentTypeWithoutTemplate">Document Type without a template</key> <key alias="documentTypeWithoutTemplate">Document Type without a template</key>
<key alias="newFolder">New folder</key> <key alias="newFolder">New folder</key>
<key alias="newDataType">New data type</key> <key alias="newDataType">New data type</key>
@@ -1130,7 +1130,7 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="cancelAndUploadAnother">Cancel and upload another package</key> <key alias="cancelAndUploadAnother">Cancel and upload another package</key>
<key alias="accept">I accept</key> <key alias="accept">I accept</key>
<key alias="termsOfUse">terms of use</key> <key alias="termsOfUse">terms of use</key>
<key alias="pathToFile">Path to file</key> <key alias="pathToFile">Path to file</key>
<key alias="pathToFileDescription">Absolute path to file (ie: /bin/umbraco.bin)</key> <key alias="pathToFileDescription">Absolute path to file (ie: /bin/umbraco.bin)</key>
<key alias="installed">Installed</key> <key alias="installed">Installed</key>
@@ -2121,8 +2121,18 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="settingsPublishedStatus">Published Status</key> <key alias="settingsPublishedStatus">Published Status</key>
<key alias="settingsModelsBuilder">Models Builder</key> <key alias="settingsModelsBuilder">Models Builder</key>
<key alias="settingsHealthCheck">Health Check</key> <key alias="settingsHealthCheck">Health Check</key>
<key alias="settingsProfiler">Profiling</key> <key alias="settingsProfiler">Profiling</key>
<key alias="memberIntro">Getting Started</key> <key alias="memberIntro">Getting Started</key>
<key alias="formsInstall">Install Umbraco Forms</key> <key alias="formsInstall">Install Umbraco Forms</key>
</area>> </area>
<area alias="writeLockReasons">
<key alias="Code_1">Langvaring operation</key>
<key alias="Code_10000">Move content to recycle bin</key>
<key alias="Code_10001">Restores content from recycle bin</key>
<key alias="Code_10002">Deletes content from recycle bin</key>
<key alias="Code_10003">Moves content</key>
<key alias="Code_10004">Copying content</key>
<key alias="Code_10005">Sort content</key>
<key alias="Code_10006">Deletes all content of a given type</key>
</area>
</language> </language>
@@ -2135,8 +2135,18 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="settingsPublishedStatus">Published Status</key> <key alias="settingsPublishedStatus">Published Status</key>
<key alias="settingsModelsBuilder">Models Builder</key> <key alias="settingsModelsBuilder">Models Builder</key>
<key alias="settingsHealthCheck">Health Check</key> <key alias="settingsHealthCheck">Health Check</key>
<key alias="settingsProfiler">Profiling</key> <key alias="settingsProfiler">Profiling</key>
<key alias="memberIntro">Getting Started</key> <key alias="memberIntro">Getting Started</key>
<key alias="formsInstall">Install Umbraco Forms</key> <key alias="formsInstall">Install Umbraco Forms</key>
</area> </area>
<area alias="writeLockReasons">
<key alias="Code_1">Langvaring operation</key>
<key alias="Code_10000">Move content to recycle bin</key>
<key alias="Code_10001">Restores content from recycle bin</key>
<key alias="Code_10002">Deletes content from recycle bin</key>
<key alias="Code_10003">Moves content</key>
<key alias="Code_10004">Copying content</key>
<key alias="Code_10005">Sort content</key>
<key alias="Code_10006">Deletes all content of a given type</key>
</area>
</language> </language>
@@ -13,7 +13,7 @@ namespace Umbraco.Web.Media.TypeDetector
{ {
document = XDocument.Load(fileStream); document = XDocument.Load(fileStream);
} }
catch (System.Exception ex) catch (System.Exception)
{ {
return false; return false;
} }
+1
View File
@@ -321,6 +321,7 @@
<Compile Include="WebApi\Filters\HttpQueryStringModelBinder.cs" /> <Compile Include="WebApi\Filters\HttpQueryStringModelBinder.cs" />
<Compile Include="WebApi\HttpActionContextExtensions.cs" /> <Compile Include="WebApi\HttpActionContextExtensions.cs" />
<Compile Include="Models\ContentEditing\IContentSave.cs" /> <Compile Include="Models\ContentEditing\IContentSave.cs" />
<Compile Include="WebApi\LockTimeoutExceptionHandlerAttribute.cs" />
<Compile Include="WebApi\SerializeVersionAttribute.cs" /> <Compile Include="WebApi\SerializeVersionAttribute.cs" />
<Compile Include="WebApi\TrimModelBinder.cs" /> <Compile Include="WebApi\TrimModelBinder.cs" />
<Compile Include="Editors\CodeFileController.cs" /> <Compile Include="Editors\CodeFileController.cs" />
@@ -0,0 +1,33 @@
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Filters;
using Umbraco.Core;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Services;
using Umbraco.Web.Composing;
namespace Umbraco.Web.WebApi
{
public class LockTimeoutExceptionHandlerAttribute : FilterAttribute, IExceptionFilter
{
private readonly ILocalizedTextService _localizedTextService;
public LockTimeoutExceptionHandlerAttribute()
{
_localizedTextService = Current.Factory.GetInstance<ILocalizedTextService>();
}
public Task ExecuteExceptionFilterAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
{
if (actionExecutedContext.Exception is LockTimeoutException timeoutException)
{
var reason = _localizedTextService.LocalizeLockReason(timeoutException.Reason);
actionExecutedContext.Response = actionExecutedContext.Request.CreateResponse(HttpStatusCode.Conflict, reason);
}
return Task.CompletedTask;
}
}
}
@@ -25,6 +25,7 @@ namespace Umbraco.Web.WebApi
[UmbracoWebApiRequireHttps] [UmbracoWebApiRequireHttps]
[CheckIfUserTicketDataIsStale] [CheckIfUserTicketDataIsStale]
[UnhandedExceptionLoggerConfiguration] [UnhandedExceptionLoggerConfiguration]
[LockTimeoutExceptionHandler]
[EnableDetailedErrors] [EnableDetailedErrors]
public abstract class UmbracoAuthorizedApiController : UmbracoApiController public abstract class UmbracoAuthorizedApiController : UmbracoApiController
{ {